ability.rb 15.3 KB
Newer Older
G
gitlabhq 已提交
1
class Ability
A
Andrey Kumanyaev 已提交
2
  class << self
3
    # rubocop: disable Metrics/CyclomaticComplexity
4
    def allowed(user, subject)
5
      return anonymous_abilities(user, subject) if user.nil?
D
Douwe Maan 已提交
6
      return [] unless user.is_a?(User)
7
      return [] if user.blocked?
8

9 10 11 12 13 14 15 16 17 18 19 20
      case subject
      when CommitStatus then commit_status_abilities(user, subject)
      when Project then project_abilities(user, subject)
      when Issue then issue_abilities(user, subject)
      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)
F
Felipe Artur 已提交
21
      when User then user_abilities
22
      when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
23
      when Ci::Runner then runner_abilities(user, subject)
J
James Lopez 已提交
24
      else []
25 26 27
      end.concat(global_abilities(user))
    end

Y
Yorick Peterse 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
    # Given a list of users and a project this method returns the users that can
    # read the given project.
    def users_that_can_read_project(users, project)
      if project.public?
        users
      else
        users.select do |user|
          if user.admin?
            true
          elsif project.internal? && !user.external?
            true
          elsif project.owner == user
            true
          elsif project.team.members.include?(user)
            true
          else
            false
          end
        end
      end
    end

50 51
    # List of possible abilities for anonymous user
    def anonymous_abilities(user, subject)
52
      if subject.is_a?(PersonalSnippet)
53
        anonymous_personal_snippet_abilities(subject)
54
      elsif subject.is_a?(ProjectSnippet)
55
        anonymous_project_snippet_abilities(subject)
56
      elsif subject.is_a?(CommitStatus)
K
Kamil Trzcinski 已提交
57
        anonymous_commit_status_abilities(subject)
58
      elsif subject.is_a?(Project) || subject.respond_to?(:project)
59
        anonymous_project_abilities(subject)
60
      elsif subject.is_a?(Group) || subject.respond_to?(:group)
61
        anonymous_group_abilities(subject)
62
      elsif subject.is_a?(User)
F
Felipe Artur 已提交
63
        anonymous_user_abilities
D
Douwe Maan 已提交
64 65 66
      else
        []
      end
67 68
    end

69
    def anonymous_project_abilities(subject)
D
Douwe Maan 已提交
70
      project = if subject.is_a?(Project)
71 72
                  subject
                else
73
                  subject.project
74 75
                end

76
      if project && project.public?
77
        rules = [
78 79
          :read_project,
          :read_wiki,
80
          :read_label,
81 82
          :read_milestone,
          :read_project_snippet,
83
          :read_project_member,
84 85
          :read_merge_request,
          :read_note,
86
          :read_pipeline,
87
          :read_commit_status,
K
Kamil Trzcinski 已提交
88
          :read_container_image,
89 90
          :download_code
        ]
91

K
Kamil Trzcinski 已提交
92
        # Allow to read builds by anonymous user if guests are allowed
93
        rules << :read_build if project.public_builds?
94

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

98
        rules - project_disabled_features_rules(project)
99
      else
100 101 102
        []
      end
    end
103

K
Kamil Trzcinski 已提交
104 105 106 107 108 109 110
    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

111
    def anonymous_group_abilities(subject)
F
Felipe Artur 已提交
112 113
      rules = []

D
Douwe Maan 已提交
114
      group = if subject.is_a?(Group)
115 116 117 118 119
                subject
              else
                subject.group
              end

F
Felipe Artur 已提交
120
      rules << :read_group if group.public?
F
Felipe Artur 已提交
121 122

      rules
123 124
    end

125
    def anonymous_personal_snippet_abilities(snippet)
126 127 128 129
      if snippet.public?
        [:read_personal_snippet]
      else
        []
130 131 132
      end
    end

133 134 135 136 137 138 139 140
    def anonymous_project_snippet_abilities(snippet)
      if snippet.public?
        [:read_project_snippet]
      else
        []
      end
    end

F
Felipe Artur 已提交
141 142
    def anonymous_user_abilities
      [:read_user] unless restricted_public_level?
F
Felipe Artur 已提交
143 144
    end

145 146 147
    def global_abilities(user)
      rules = []
      rules << :create_group if user.can_create_group
148
      rules << :read_users_list
149
      rules
G
gitlabhq 已提交
150 151
    end

A
Andrey Kumanyaev 已提交
152 153
    def project_abilities(user, project)
      rules = []
S
skv-headless 已提交
154
      key = "/user/#{user.id}/project/#{project.id}"
155

S
skv-headless 已提交
156
      RequestStore.store[key] ||= begin
Z
Zeger-Jan van de Weg 已提交
157
        # Push abilities on the users team role
R
Rémy Coutable 已提交
158
        rules.push(*project_team_rules(project.team, user))
G
gitlabhq 已提交
159

160 161 162
        owner = user.admin? ||
                project.owner == user ||
                (project.group && project.group.has_owner?(user))
D
Douwe Maan 已提交
163

164
        if owner
D
Douwe Maan 已提交
165 166 167
          rules.push(*project_owner_rules)
        end

Z
Zeger-Jan van de Weg 已提交
168
        if project.public? || (project.internal? && !user.external?)
169
          rules.push(*public_project_rules)
170

171
          # Allow to read builds for internal projects
172
          rules << :read_build if project.public_builds?
173

174
          unless owner || project.team.member?(user) || project_group_member?(project, user)
175
            rules << :request_access if project.request_access_enabled
176
          end
S
skv-headless 已提交
177
        end
178

S
skv-headless 已提交
179 180 181
        if project.archived?
          rules -= project_archived_rules
        end
182

183
        rules - project_disabled_features_rules(project)
184
      end
185 186
    end

Z
Zeger-Jan van de Weg 已提交
187 188 189 190 191 192 193 194 195 196
    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
R
Rémy Coutable 已提交
197 198
      else
        []
Z
Zeger-Jan van de Weg 已提交
199 200 201
      end
    end

202
    def public_project_rules
J
Jason Lee 已提交
203
      @public_project_rules ||= project_guest_rules + [
204
        :download_code,
205
        :fork_project,
K
Kamil Trzcinski 已提交
206
        :read_commit_status,
207 208
        :read_pipeline,
        :read_container_image
209 210 211
      ]
    end

212
    def project_guest_rules
J
Jason Lee 已提交
213
      @project_guest_rules ||= [
A
Andrey Kumanyaev 已提交
214 215 216
        :read_project,
        :read_wiki,
        :read_issue,
217
        :read_label,
A
Andrey Kumanyaev 已提交
218
        :read_milestone,
A
Andrew8xx8 已提交
219
        :read_project_snippet,
220
        :read_project_member,
A
Andrey Kumanyaev 已提交
221 222
        :read_merge_request,
        :read_note,
223 224
        :create_project,
        :create_issue,
D
Douwe Maan 已提交
225 226
        :create_note,
        :upload_file
227 228
      ]
    end
D
Dmitriy Zaporozhets 已提交
229

230
    def project_report_rules
J
Jason Lee 已提交
231
      @project_report_rules ||= project_guest_rules + [
A
Andrey Kumanyaev 已提交
232
        :download_code,
233
        :fork_project,
234 235 236
        :create_project_snippet,
        :update_issue,
        :admin_issue,
237
        :admin_label,
238
        :read_commit_status,
239
        :read_build,
K
Kamil Trzcinski 已提交
240
        :read_container_image,
K
WIP  
Kamil Trzcinski 已提交
241
        :read_pipeline,
242 243
        :read_environment,
        :read_deployment
244 245
      ]
    end
D
Dmitriy Zaporozhets 已提交
246

247
    def project_dev_rules
J
Jason Lee 已提交
248
      @project_dev_rules ||= project_report_rules + [
249
        :admin_merge_request,
250
        :update_merge_request,
251 252 253 254
        :create_commit_status,
        :update_commit_status,
        :create_build,
        :update_build,
K
WIP  
Kamil Trzcinski 已提交
255 256
        :create_pipeline,
        :update_pipeline,
257 258
        :create_merge_request,
        :create_wiki,
259
        :push_code,
K
Kamil Trzcinski 已提交
260 261
        :create_container_image,
        :update_container_image,
262
        :create_environment,
263
        :create_deployment
264 265
      ]
    end
266

267
    def project_archived_rules
J
Jason Lee 已提交
268
      @project_archived_rules ||= [
269
        :create_merge_request,
270 271
        :push_code,
        :push_code_to_protected_branches,
272
        :update_merge_request,
273 274 275 276
        :admin_merge_request
      ]
    end

277
    def project_master_rules
J
Jason Lee 已提交
278
      @project_master_rules ||= project_dev_rules + [
279
        :push_code_to_protected_branches,
280
        :update_project_snippet,
K
Kamil Trzcinski 已提交
281
        :update_environment,
282
        :update_deployment,
A
Andrey Kumanyaev 已提交
283
        :admin_milestone,
A
Andrew8xx8 已提交
284
        :admin_project_snippet,
285
        :admin_project_member,
A
Andrey Kumanyaev 已提交
286 287
        :admin_merge_request,
        :admin_note,
288
        :admin_wiki,
289 290
        :admin_project,
        :admin_commit_status,
K
WIP  
Kamil Trzcinski 已提交
291
        :admin_build,
K
Kamil Trzcinski 已提交
292
        :admin_container_image,
293 294 295
        :admin_pipeline,
        :admin_environment,
        :admin_deployment
296 297
      ]
    end
G
gitlabhq 已提交
298

D
Douwe Maan 已提交
299 300
    def project_owner_rules
      @project_owner_rules ||= project_master_rules + [
301
        :change_namespace,
302
        :change_visibility_level,
303
        :rename_project,
304
        :remove_project,
305
        :archive_project,
306
        :remove_fork_project,
307 308
        :destroy_merge_request,
        :destroy_issue
309
      ]
A
Andrey Kumanyaev 已提交
310
    end
G
gitlabhq 已提交
311

312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    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

336 337
      unless project.builds_enabled
        rules += named_abilities('build')
K
WIP  
Kamil Trzcinski 已提交
338
        rules += named_abilities('pipeline')
339 340
        rules += named_abilities('environment')
        rules += named_abilities('deployment')
341 342
      end

343
      unless project.container_registry_enabled
K
Kamil Trzcinski 已提交
344
        rules += named_abilities('container_image')
345 346
      end

347 348 349
      rules
    end

350
    def group_abilities(user, group)
351
      rules = []
F
Felipe Artur 已提交
352
      rules << :read_group if can_read_group?(user, group)
353

354 355
      owner = user.admin? || group.has_owner?(user)
      master = owner || group.has_master?(user)
356

D
Douwe Maan 已提交
357
      # Only group masters and group owners can create new projects
358
      if master
359
        rules += [
360
          :create_projects,
F
Felipe Artur 已提交
361
          :admin_milestones
362
        ]
363 364
      end

365
      # Only group owner and administrators can admin group
366
      if owner
D
Douwe Maan 已提交
367 368 369
        rules += [
          :admin_group,
          :admin_namespace,
F
Felipe Artur 已提交
370 371
          :admin_group_member,
          :change_visibility_level
D
Douwe Maan 已提交
372
        ]
373
      end
374

R
Rémy Coutable 已提交
375 376
      if group.public? || (group.internal? && !user.external?)
        rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
377 378
      end

379 380 381
      rules.flatten
    end

F
Felipe Artur 已提交
382
    def can_read_group?(user, group)
D
Douwe Maan 已提交
383 384 385 386 387 388
      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?
F
Felipe Artur 已提交
389 390
    end

391
    def namespace_abilities(user, namespace)
392 393
      rules = []

394
      # Only namespace owner and administrators can admin it
395
      if namespace.owner == user || user.admin?
D
Douwe Maan 已提交
396 397 398 399
        rules += [
          :create_projects,
          :admin_namespace
        ]
400 401 402 403 404
      end

      rules.flatten
    end

405
    [:issue, :merge_request].each do |name|
G
gitlabhq 已提交
406
      define_method "#{name}_abilities" do |user, subject|
407 408 409 410
        rules = []

        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
          rules += [
G
gitlabhq 已提交
411
            :"read_#{name}",
412
            :"update_#{name}",
G
gitlabhq 已提交
413
          ]
414 415 416
        end

        rules += project_abilities(user, subject.project)
417
        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
418 419 420 421
        rules
      end
    end

422 423
    def note_abilities(user, note)
      rules = []
424

425 426 427 428 429 430 431
      if note.author == user
        rules += [
          :read_note,
          :update_note,
          :admin_note
        ]
      end
432

433 434
      if note.respond_to?(:project) && note.project
        rules += project_abilities(user, note.project)
G
gitlabhq 已提交
435
      end
436 437

      rules
G
gitlabhq 已提交
438
    end
439

440 441 442 443 444 445 446 447 448 449 450
    def personal_snippet_abilities(user, snippet)
      rules = []

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

Z
Zeger-Jan van de Weg 已提交
451
      if snippet.public? || (snippet.internal? && !user.external?)
J
Jason Lee 已提交
452
        rules << :read_personal_snippet
453 454 455 456 457
      end

      rules
    end

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
    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

476
    def group_member_abilities(user, subject)
477 478 479
      rules = []
      target_user = subject.user
      group = subject.group
480

D
Douwe Maan 已提交
481 482
      unless group.last_owner?(target_user)
        can_manage = group_abilities(user, group).include?(:admin_group_member)
483

484
        if can_manage
D
Douwe Maan 已提交
485 486
          rules << :update_group_member
          rules << :destroy_group_member
487
        elsif user == target_user
D
Douwe Maan 已提交
488 489
          rules << :destroy_group_member
        end
490
      end
491

492 493
      rules
    end
C
Ciro Santilli 已提交
494

495 496 497 498 499
    def project_member_abilities(user, subject)
      rules = []
      target_user = subject.user
      project = subject.project

500
      unless target_user == project.owner
D
Douwe Maan 已提交
501
        can_manage = project_abilities(user, project).include?(:admin_project_member)
502

503
        if can_manage
D
Douwe Maan 已提交
504 505
          rules << :update_project_member
          rules << :destroy_project_member
506
        elsif user == target_user
D
Douwe Maan 已提交
507 508
          rules << :destroy_project_member
        end
509
      end
D
Douwe Maan 已提交
510

511 512 513
      rules
    end

K
Kamil Trzcinski 已提交
514 515 516 517 518 519 520
    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

521 522 523
    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
524
      %w(read create update admin).each do |rule|
525
        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
526 527 528 529
      end
      rules
    end

530 531 532 533 534 535 536 537 538 539 540 541
    def runner_abilities(user, runner)
      if user.is_admin?
        [:assign_runner]
      elsif runner.is_shared? || runner.locked?
        []
      elsif user.ci_authorized_runners.include?(runner)
        [:assign_runner]
      else
        []
      end
    end

F
Felipe Artur 已提交
542
    def user_abilities
F
Felipe Artur 已提交
543 544 545
      [:read_user]
    end

C
Ciro Santilli 已提交
546 547
    def abilities
      @abilities ||= begin
548 549 550 551
        abilities = Six.new
        abilities << self
        abilities
      end
C
Ciro Santilli 已提交
552
    end
553 554 555

    private

F
Felipe Artur 已提交
556
    def restricted_public_level?
F
Felipe Artur 已提交
557
      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
F
Felipe Artur 已提交
558 559
    end

560 561 562
    def named_abilities(name)
      [
        :"read_#{name}",
563 564
        :"create_#{name}",
        :"update_#{name}",
565 566 567
        :"admin_#{name}"
      ]
    end
568 569 570 571

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

572
      unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
573 574 575 576 577 578 579
        rules.delete(:admin_issue)
        rules.delete(:read_issue)
        rules.delete(:update_issue)
      end

      rules
    end
580 581 582 583 584 585 586 587

    def project_group_member?(project, user)
      project.group &&
      (
        project.group.members.exists?(user_id: user.id) ||
        project.group.requesters.exists?(user_id: user.id)
      )
    end
G
gitlabhq 已提交
588
  end
G
gitlabhq 已提交
589
end