summaryrefslogtreecommitdiff
path: root/app/finders/snippets_finder.rb
blob: 9d3772d754193f20d7c2097cbab193f6373ece89 (plain)
1
2
3
4
5
6
7
8
9
10
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# Snippets Finder
#
# Used to filter Snippets collections by a set of params
#
# Arguments.
#
# current_user - The current user, nil also can be used.
# params:
#   visibility (integer) - Individual snippet visibility: Public(20), internal(10) or private(0).
#   project (Project) - Project related.
#   author (User) - Author related.
#
# params are optional
class SnippetsFinder < UnionFinder
  include Gitlab::Allowable
  include FinderMethods

  attr_accessor :current_user, :project, :params

  def initialize(current_user, params = {})
    @current_user = current_user
    @params = params
    @project = params[:project]
  end

  def execute
    items = init_collection
    items = by_author(items)
    items = by_visibility(items)

    items.fresh
  end

  private

  def init_collection
    if project.present?
      authorized_snippets_from_project
    else
      authorized_snippets
    end
  end

  def authorized_snippets_from_project
    if can?(current_user, :read_project_snippet, project)
      if project.team.member?(current_user)
        project.snippets
      else
        project.snippets.public_to_user(current_user)
      end
    else
      Snippet.none
    end
  end

  def authorized_snippets
    # This query was intentionally converted to a raw one to get it work in Rails 5.0.
    # In Rails 5.0 and 5.1 there's a bug: https://github.com/rails/arel/issues/531
    # Please convert it back when on rails 5.2 as it works again as expected since 5.2.
    Snippet.where("#{feature_available_projects} OR #{not_project_related}")
      .public_or_visible_to_user(current_user)
  end

  # Returns a collection of projects that is either public or visible to the
  # logged in user.
  #
  # A caller must pass in a block to modify individual parts of
  # the query, e.g. to apply .with_feature_available_for_user on top of it.
  # This is useful for performance as we can stick those additional filters
  # at the bottom of e.g. the UNION.
  def projects_for_user
    return yield(Project.public_to_user) unless current_user

    # If the current_user is allowed to see all projects,
    # we can shortcut and just return.
    return yield(Project.all) if current_user.full_private_access?

    authorized_projects = yield(Project.where('EXISTS (?)', current_user.authorizations_for_projects))

    levels = Gitlab::VisibilityLevel.levels_for_user(current_user)
    visible_projects = yield(Project.where(visibility_level: levels))

    # We use a UNION here instead of OR clauses since this results in better
    # performance.
    union = Gitlab::SQL::Union.new([authorized_projects.select('projects.id'), visible_projects.select('projects.id')])

    Project.from("(#{union.to_sql}) AS #{Project.table_name}")
  end

  def feature_available_projects
    # Don't return any project related snippets if the user cannot read cross project
    return table[:id].eq(nil).to_sql unless Ability.allowed?(current_user, :read_cross_project)

    projects = projects_for_user do |part|
      part.with_feature_available_for_user(:snippets, current_user)
    end.select(:id)

    # This query was intentionally converted to a raw one to get it work in Rails 5.0.
    # In Rails 5.0 and 5.1 there's a bug: https://github.com/rails/arel/issues/531
    # Please convert it back when on rails 5.2 as it works again as expected since 5.2.
    "snippets.project_id IN (#{projects.to_sql})"
  end

  def not_project_related
    table[:project_id].eq(nil).to_sql
  end

  def table
    Snippet.arel_table
  end

  def by_visibility(items)
    visibility = params[:visibility] || visibility_from_scope

    return items unless visibility

    items.where(visibility_level: visibility)
  end

  def by_author(items)
    return items unless params[:author]

    items.where(author_id: params[:author].id)
  end

  def visibility_from_scope
    case params[:scope].to_s
    when 'are_private'
      Snippet::PRIVATE
    when 'are_internal'
      Snippet::INTERNAL
    when 'are_public'
      Snippet::PUBLIC
    else
      nil
    end
  end
end