Read about our upcoming Code of Conduct on this issue

snippets_finder.rb 5.48 KB
Newer Older
1
2
# frozen_string_literal: true

3
4
# Finder for retrieving snippets that a user can see, optionally scoped to a
# project or snippets author.
Douwe Maan's avatar
Douwe Maan committed
5
#
6
# Basic usage:
Douwe Maan's avatar
Douwe Maan committed
7
#
8
#     user = User.find(1)
Douwe Maan's avatar
Douwe Maan committed
9
#
10
#     SnippetsFinder.new(user).execute
Douwe Maan's avatar
Douwe Maan committed
11
#
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# To limit the snippets to a specific project, supply the `project:` option:
#
#     user = User.find(1)
#     project = Project.find(1)
#
#     SnippetsFinder.new(user, project: project).execute
#
# Limiting snippets to an author can be done by supplying the `author:` option:
#
#     user = User.find(1)
#     project = Project.find(1)
#
#     SnippetsFinder.new(user, author: user).execute
#
# To filter snippets using a specific visibility level, you can provide the
# `scope:` option:
#
#     user = User.find(1)
#     project = Project.find(1)
#
#     SnippetsFinder.new(user, author: user, scope: :are_public).execute
#
# Valid `scope:` values are:
#
# * `:are_private`
# * `:are_internal`
# * `:are_public`
#
# Any other value will be ignored.
41
class SnippetsFinder < UnionFinder
42
  include FinderMethods
43
  include Gitlab::Utils::StrongMemoize
44

45
46
  attr_accessor :current_user, :params
  delegate :explore, :only_personal, :only_project, :scope, to: :params
47

48
  def initialize(current_user = nil, params = {})
49
    @current_user = current_user
50
    @params = OpenStruct.new(params)
51
52
53
54
55
56
57

    if project && author
      raise(
        ArgumentError,
        'Filtering by both an author and a project is not supported, ' \
          'as this finder is not optimised for this use case'
      )
Douwe Maan's avatar
Douwe Maan committed
58
59
    end
  end
60

61
  def execute
62
63
64
65
66
67
68
69
70
    # The snippet query can be expensive, therefore if the
    # author or project params have been passed and they don't
    # exist, it's better to return
    return Snippet.none if author.nil? && params[:author].present?
    return Snippet.none if project.nil? && params[:project].present?

    items = init_collection
    items = by_ids(items)
    items.with_optional_visibility(visibility_from_scope).fresh
Douwe Maan's avatar
Douwe Maan committed
71
  end
72

73
74
  private

75
  def init_collection
76
77
    if explore
      snippets_for_explore
78
79
    elsif only_personal
      personal_snippets
80
    elsif project
81
82
      snippets_for_a_single_project
    else
83
      snippets_for_personal_and_multiple_projects
84
85
86
    end
  end

87
88
89
90
91
92
93
94
  # Produces a query that retrieves snippets for the Explore page
  #
  # We only show personal snippets here because this page is meant for
  # discovery, and project snippets are of limited interest here.
  def snippets_for_explore
    Snippet.public_to_user(current_user).only_personal_snippets
  end

95
  # Produces a query that retrieves snippets from multiple projects.
96
  #
97
98
99
100
101
102
103
104
105
106
  # The resulting query will, depending on the user's permissions, include the
  # following collections of snippets:
  #
  # 1. Snippets that don't belong to any project.
  # 2. Snippets of projects that are visible to the current user (e.g. snippets
  #    in public projects).
  # 3. Snippets of projects that the current user is a member of.
  #
  # Each collection is constructed in isolation, allowing for greater control
  # over the resulting SQL query.
107
108
109
  def snippets_for_personal_and_multiple_projects
    queries = []
    queries << personal_snippets unless only_project
110
111
112
113
114

    if Ability.allowed?(current_user, :read_cross_project)
      queries << snippets_of_visible_projects
      queries << snippets_of_authorized_projects if current_user
    end
115

116
    find_union(queries, Snippet)
117
118
  end

119
120
  def snippets_for_a_single_project
    Snippet.for_project_with_user(project, current_user)
Douwe Maan's avatar
Douwe Maan committed
121
122
  end

123
124
  def personal_snippets
    snippets_for_author_or_visible_to_user.only_personal_snippets
125
  end
126

127
128
129
130
131
132
  # Returns the snippets that the current user (logged in or not) can view.
  def snippets_of_visible_projects
    snippets_for_author_or_visible_to_user
      .only_include_projects_visible_to(current_user)
      .only_include_projects_with_snippets_enabled
  end
133

134
135
136
137
138
139
  # Returns the snippets that the currently logged in user has access to by
  # being a member of the project the snippets belong to.
  #
  # This method requires that `current_user` returns a `User` instead of `nil`,
  # and is optimised for this specific scenario.
  def snippets_of_authorized_projects
140
    base = author ? author.snippets : Snippet.all
141
142
143
144
145

    base
      .only_include_projects_with_snippets_enabled(include_private: true)
      .only_include_authorized_projects(current_user)
  end
146

147
148
149
150
151
152
153
154
  def snippets_for_author_or_visible_to_user
    if author
      snippets_for_author
    elsif current_user
      Snippet.visible_to_or_authored_by(current_user)
    else
      Snippet.public_to_user
    end
155
156
  end

157
158
  def snippets_for_author
    base = author.snippets
159

160
161
162
163
164
165
166
    if author == current_user
      # If the current user is also the author of all snippets, then we can
      # include private snippets.
      base
    else
      base.public_to_user(current_user)
    end
167
168
169
  end

  def visibility_from_scope
170
    case scope.to_s
171
    when 'are_private'
172
      Snippet::PRIVATE
173
    when 'are_internal'
174
      Snippet::INTERNAL
175
    when 'are_public'
176
      Snippet::PUBLIC
177
    else
178
      nil
179
180
    end
  end
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202

  def by_ids(items)
    return items unless params[:ids].present?

    items.id_in(params[:ids])
  end

  def author
    strong_memoize(:author) do
      next unless params[:author].present?

      params[:author].is_a?(User) ? params[:author] : User.find_by_id(params[:author])
    end
  end

  def project
    strong_memoize(:project) do
      next unless params[:project].present?

      params[:project].is_a?(Project) ? params[:project] : Project.find_by_id(params[:project])
    end
  end
203
end
204
205

SnippetsFinder.prepend_if_ee('EE::SnippetsFinder')