ability.rb 13.3 KB
Newer Older
gitlabhq's avatar
gitlabhq committed
1
class Ability
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
2
  class << self
3
    def allowed(user, subject)
4
      return anonymous_abilities(user, subject) if user.nil?
Douwe Maan's avatar
Douwe Maan committed
5
      return [] unless user.is_a?(User)
6
      return [] if user.blocked?
7

8
9
10
11
      case subject
      when CommitStatus then commit_status_abilities(user, subject)
      when Project then project_abilities(user, subject)
      when Issue then issue_abilities(user, subject)
12
      when ExternalIssue then external_issue_abilities(user, subject)
13
14
15
16
17
18
19
20
      when Note then note_abilities(user, subject)
      when ProjectSnippet then project_snippet_abilities(user, subject)
      when PersonalSnippet then personal_snippet_abilities(user, subject)
      when MergeRequest then merge_request_abilities(user, subject)
      when Group then group_abilities(user, subject)
      when Namespace then namespace_abilities(user, subject)
      when GroupMember then group_member_abilities(user, subject)
      when ProjectMember then project_member_abilities(user, subject)
Felipe Artur's avatar
Felipe Artur committed
21
      when User then user_abilities
James Lopez's avatar
James Lopez committed
22
      else []
23
24
25
      end.concat(global_abilities(user))
    end

26
27
    # List of possible abilities for anonymous user
    def anonymous_abilities(user, subject)
Douwe Maan's avatar
Douwe Maan committed
28
29
      case true
      when subject.is_a?(PersonalSnippet)
30
        anonymous_personal_snippet_abilities(subject)
31
32
      when subject.is_a?(ProjectSnippet)
        anonymous_project_snippet_abilities(subject)
33
      when subject.is_a?(CommitStatus)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
34
        anonymous_commit_status_abilities(subject)
Douwe Maan's avatar
Douwe Maan committed
35
      when subject.is_a?(Project) || subject.respond_to?(:project)
36
        anonymous_project_abilities(subject)
Douwe Maan's avatar
Douwe Maan committed
37
      when subject.is_a?(Group) || subject.respond_to?(:group)
38
        anonymous_group_abilities(subject)
Felipe Artur's avatar
Felipe Artur committed
39
      when subject.is_a?(User)
Felipe Artur's avatar
Felipe Artur committed
40
        anonymous_user_abilities
Douwe Maan's avatar
Douwe Maan committed
41
42
43
      else
        []
      end
44
45
    end

46
    def anonymous_project_abilities(subject)
Douwe Maan's avatar
Douwe Maan committed
47
      project = if subject.is_a?(Project)
48
49
                  subject
                else
50
                  subject.project
51
52
                end

53
      if project && project.public?
54
        rules = [
55
56
          :read_project,
          :read_wiki,
57
          :read_label,
58
59
          :read_milestone,
          :read_project_snippet,
Felipe Artur's avatar
Felipe Artur committed
60
          :read_project_member,
61
62
          :read_merge_request,
          :read_note,
63
          :read_commit_status,
64
          :read_container_registry,
65
66
          :download_code
        ]
67

Kamil Trzcinski's avatar
Kamil Trzcinski committed
68
        # Allow to read builds by anonymous user if guests are allowed
69
        rules << :read_build if project.public_builds?
70

71
72
73
        # Allow to read issues by anonymous user if issue is not confidential
        rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?

74
        rules - project_disabled_features_rules(project)
75
      else
76
77
78
        []
      end
    end
79

Kamil Trzcinski's avatar
Kamil Trzcinski committed
80
81
82
83
84
85
86
    def anonymous_commit_status_abilities(subject)
      rules = anonymous_project_abilities(subject.project)
      # If subject is Ci::Build which inherits from CommitStatus filter the abilities
      rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
      rules
    end

87
    def anonymous_group_abilities(subject)
Felipe Artur's avatar
Felipe Artur committed
88
89
      rules = []

Douwe Maan's avatar
Douwe Maan committed
90
      group = if subject.is_a?(Group)
91
92
93
94
95
                subject
              else
                subject.group
              end

Felipe Artur's avatar
Felipe Artur committed
96
      rules << :read_group if group.public?
Felipe Artur's avatar
Felipe Artur committed
97
98

      rules
99
100
    end

101
    def anonymous_personal_snippet_abilities(snippet)
102
103
104
105
      if snippet.public?
        [:read_personal_snippet]
      else
        []
106
107
108
      end
    end

109
110
111
112
113
114
115
116
    def anonymous_project_snippet_abilities(snippet)
      if snippet.public?
        [:read_project_snippet]
      else
        []
      end
    end

Felipe Artur's avatar
Felipe Artur committed
117
118
    def anonymous_user_abilities
      [:read_user] unless restricted_public_level?
Felipe Artur's avatar
Felipe Artur committed
119
120
    end

121
122
123
    def global_abilities(user)
      rules = []
      rules << :create_group if user.can_create_group
124
      rules << :read_users_list
125
      rules
gitlabhq's avatar
gitlabhq committed
126
127
    end

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
128
129
    def project_abilities(user, project)
      rules = []
130
      key = "/user/#{user.id}/project/#{project.id}"
131

132
      RequestStore.store[key] ||= begin
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
133
134
        # Push abilities on the users team role
        rules.push(*project_team_rules(project.team, user))
gitlabhq's avatar
gitlabhq committed
135

Douwe Maan's avatar
Douwe Maan committed
136
137
138
139
140
141
142
        if project.owner == user ||
          (project.group && project.group.has_owner?(user)) ||
          user.admin?

          rules.push(*project_owner_rules)
        end

Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
143
        if project.public? || (project.internal? && !user.external?)
144
          rules.push(*public_project_rules)
145

146
          # Allow to read builds for internal projects
147
          rules << :read_build if project.public_builds?
148
        end
149

150
151
152
        if project.archived?
          rules -= project_archived_rules
        end
153

154
        rules - project_disabled_features_rules(project)
155
      end
156
157
    end

Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
158
159
160
161
162
163
164
165
166
167
168
169
170
    def project_team_rules(team, user)
      # Rules based on role in project
      if team.master?(user)
        project_master_rules
      elsif team.developer?(user)
        project_dev_rules
      elsif team.reporter?(user)
        project_report_rules
      elsif team.guest?(user)
        project_guest_rules
      end
    end

171
    def public_project_rules
172
      @public_project_rules ||= project_guest_rules + [
173
        :download_code,
174
        :fork_project,
Felipe Artur's avatar
Felipe Artur committed
175
        :read_commit_status
176
177
178
      ]
    end

179
    def project_guest_rules
180
      @project_guest_rules ||= [
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
181
182
183
        :read_project,
        :read_wiki,
        :read_issue,
184
        :read_label,
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
185
        :read_milestone,
Andrew8xx8's avatar
Andrew8xx8 committed
186
        :read_project_snippet,
187
        :read_project_member,
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
188
189
        :read_merge_request,
        :read_note,
190
191
        :create_project,
        :create_issue,
Douwe Maan's avatar
Douwe Maan committed
192
193
        :create_note,
        :upload_file
194
195
      ]
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
196

197
    def project_report_rules
198
      @project_report_rules ||= project_guest_rules + [
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
199
        :download_code,
200
        :fork_project,
201
202
203
        :create_project_snippet,
        :update_issue,
        :admin_issue,
204
        :admin_label,
205
        :read_commit_status,
206
        :read_build,
207
        :read_container_registry,
208
209
      ]
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
210

211
    def project_dev_rules
212
      @project_dev_rules ||= project_report_rules + [
213
        :admin_merge_request,
214
        :update_merge_request,
215
216
217
218
        :create_commit_status,
        :update_commit_status,
        :create_build,
        :update_build,
219
220
        :create_merge_request,
        :create_wiki,
221
        :push_code,
222
223
        :create_container_registry,
        :update_container_registry,
224
225
      ]
    end
226

227
    def project_archived_rules
228
      @project_archived_rules ||= [
229
        :create_merge_request,
230
231
        :push_code,
        :push_code_to_protected_branches,
232
        :update_merge_request,
233
234
235
236
        :admin_merge_request
      ]
    end

237
    def project_master_rules
238
      @project_master_rules ||= project_dev_rules + [
239
        :push_code_to_protected_branches,
240
        :update_project_snippet,
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
241
        :admin_milestone,
Andrew8xx8's avatar
Andrew8xx8 committed
242
        :admin_project_snippet,
243
        :admin_project_member,
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
244
245
        :admin_merge_request,
        :admin_note,
246
        :admin_wiki,
247
248
        :admin_project,
        :admin_commit_status,
249
        :admin_build,
250
        :admin_container_registry,
251
252
      ]
    end
gitlabhq's avatar
gitlabhq committed
253

Douwe Maan's avatar
Douwe Maan committed
254
255
    def project_owner_rules
      @project_owner_rules ||= project_master_rules + [
256
        :change_namespace,
257
        :change_visibility_level,
258
        :rename_project,
259
        :remove_project,
260
        :archive_project,
261
        :remove_fork_project,
262
263
        :destroy_merge_request,
        :destroy_issue
264
      ]
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
265
    end
gitlabhq's avatar
gitlabhq committed
266

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
    def project_disabled_features_rules(project)
      rules = []

      unless project.issues_enabled
        rules += named_abilities('issue')
      end

      unless project.merge_requests_enabled
        rules += named_abilities('merge_request')
      end

      unless project.issues_enabled or project.merge_requests_enabled
        rules += named_abilities('label')
        rules += named_abilities('milestone')
      end

      unless project.snippets_enabled
        rules += named_abilities('project_snippet')
      end

      unless project.wiki_enabled
        rules += named_abilities('wiki')
      end

291
292
293
294
      unless project.builds_enabled
        rules += named_abilities('build')
      end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
295
      unless project.container_registry_enabled
296
297
298
        rules += named_abilities('container_registry')
      end

299
300
301
      rules
    end

302
    def group_abilities(user, group)
303
      rules = []
Felipe Artur's avatar
Felipe Artur committed
304
      rules << :read_group if can_read_group?(user, group)
305

Douwe Maan's avatar
Douwe Maan committed
306
      # Only group masters and group owners can create new projects
307
      if group.has_master?(user) || group.has_owner?(user) || user.admin?
308
        rules += [
309
          :create_projects,
Felipe Artur's avatar
Felipe Artur committed
310
          :admin_milestones
311
        ]
312
313
      end

314
      # Only group owner and administrators can admin group
315
      if group.has_owner?(user) || user.admin?
Douwe Maan's avatar
Douwe Maan committed
316
317
318
        rules += [
          :admin_group,
          :admin_namespace,
Felipe Artur's avatar
Felipe Artur committed
319
320
          :admin_group_member,
          :change_visibility_level
Douwe Maan's avatar
Douwe Maan committed
321
        ]
322
      end
323
324
325
326

      rules.flatten
    end

Felipe Artur's avatar
Felipe Artur committed
327
    def can_read_group?(user, group)
Douwe Maan's avatar
Douwe Maan committed
328
329
330
331
332
333
      return true if user.admin?
      return true if group.public?
      return true if group.internal? && !user.external?
      return true if group.users.include?(user)

      GroupProjectsFinder.new(group).execute(user).any?
Felipe Artur's avatar
Felipe Artur committed
334
335
    end

336
    def namespace_abilities(user, namespace)
337
338
      rules = []

339
      # Only namespace owner and administrators can admin it
340
      if namespace.owner == user || user.admin?
Douwe Maan's avatar
Douwe Maan committed
341
342
343
344
        rules += [
          :create_projects,
          :admin_namespace
        ]
345
346
347
348
349
      end

      rules.flatten
    end

350
    [:issue, :merge_request].each do |name|
gitlabhq's avatar
gitlabhq committed
351
      define_method "#{name}_abilities" do |user, subject|
352
353
354
355
        rules = []

        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
          rules += [
gitlabhq's avatar
gitlabhq committed
356
            :"read_#{name}",
357
            :"update_#{name}",
gitlabhq's avatar
gitlabhq committed
358
          ]
359
360
361
        end

        rules += project_abilities(user, subject.project)
362
        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
363
364
365
366
        rules
      end
    end

367
368
    def note_abilities(user, note)
      rules = []
369

370
371
372
373
374
375
376
      if note.author == user
        rules += [
          :read_note,
          :update_note,
          :admin_note
        ]
      end
377

378
379
      if note.respond_to?(:project) && note.project
        rules += project_abilities(user, note.project)
gitlabhq's avatar
gitlabhq committed
380
      end
381
382

      rules
gitlabhq's avatar
gitlabhq committed
383
    end
384

385
386
387
388
389
390
391
392
393
394
395
    def personal_snippet_abilities(user, snippet)
      rules = []

      if snippet.author == user
        rules += [
          :read_personal_snippet,
          :update_personal_snippet,
          :admin_personal_snippet
        ]
      end

Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
396
      if snippet.public? || (snippet.internal? && !user.external?)
397
        rules << :read_personal_snippet
398
399
400
401
402
      end

      rules
    end

403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
    def project_snippet_abilities(user, snippet)
      rules = []

      if snippet.author == user || user.admin?
        rules += [
          :read_project_snippet,
          :update_project_snippet,
          :admin_project_snippet
        ]
      end

      if snippet.public? || (snippet.internal? && !user.external?) || (snippet.private? && snippet.project.team.member?(user))
        rules << :read_project_snippet
      end

      rules
    end

421
    def group_member_abilities(user, subject)
422
423
424
      rules = []
      target_user = subject.user
      group = subject.group
425

Douwe Maan's avatar
Douwe Maan committed
426
427
      unless group.last_owner?(target_user)
        can_manage = group_abilities(user, group).include?(:admin_group_member)
428

429
        if can_manage
Douwe Maan's avatar
Douwe Maan committed
430
431
          rules << :update_group_member
          rules << :destroy_group_member
432
        elsif user == target_user
Douwe Maan's avatar
Douwe Maan committed
433
434
          rules << :destroy_group_member
        end
435
      end
436

437
438
      rules
    end
Ciro Santilli's avatar
Ciro Santilli committed
439

440
441
442
443
444
    def project_member_abilities(user, subject)
      rules = []
      target_user = subject.user
      project = subject.project

Douwe Maan's avatar
Douwe Maan committed
445
446
      unless target_user == project.owner
        can_manage = project_abilities(user, project).include?(:admin_project_member)
447

448
        if can_manage
Douwe Maan's avatar
Douwe Maan committed
449
450
          rules << :update_project_member
          rules << :destroy_project_member
451
        elsif user == target_user
Douwe Maan's avatar
Douwe Maan committed
452
453
          rules << :destroy_project_member
        end
454
      end
Douwe Maan's avatar
Douwe Maan committed
455

456
457
458
      rules
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
459
460
461
462
463
464
465
    def commit_status_abilities(user, subject)
      rules = project_abilities(user, subject.project)
      # If subject is Ci::Build which inherits from CommitStatus filter the abilities
      rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
      rules
    end

466
467
468
    def filter_build_abilities(rules)
      # If we can't read build we should also not have that
      # ability when looking at this in context of commit_status
469
      %w(read create update admin).each do |rule|
470
        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
471
472
473
474
      end
      rules
    end

Felipe Artur's avatar
Felipe Artur committed
475
    def user_abilities
Felipe Artur's avatar
Felipe Artur committed
476
477
478
      [:read_user]
    end

Ciro Santilli's avatar
Ciro Santilli committed
479
480
    def abilities
      @abilities ||= begin
481
482
483
484
        abilities = Six.new
        abilities << self
        abilities
      end
Ciro Santilli's avatar
Ciro Santilli committed
485
    end
486

487
488
489
490
    def external_issue_abilities(user, subject)
      project_abilities(user, subject.project)
    end

491
492
    private

Felipe Artur's avatar
Felipe Artur committed
493
    def restricted_public_level?
Felipe Artur's avatar
Felipe Artur committed
494
      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
Felipe Artur's avatar
Felipe Artur committed
495
496
    end

497
498
499
    def named_abilities(name)
      [
        :"read_#{name}",
500
501
        :"create_#{name}",
        :"update_#{name}",
502
503
504
        :"admin_#{name}"
      ]
    end
505
506
507
508
509
510
511
512
513
514
515
516

    def filter_confidential_issues_abilities(user, issue, rules)
      return rules if user.admin? || !issue.confidential?

      unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id)
        rules.delete(:admin_issue)
        rules.delete(:read_issue)
        rules.delete(:update_issue)
      end

      rules
    end
gitlabhq's avatar
gitlabhq committed
517
  end
gitlabhq's avatar
gitlabhq committed
518
end