BigW Consortium Gitlab

ability.rb 16 KB
Newer Older
gitlabhq committed
1
class Ability
Andrey Kumanyaev committed
2
  class << self
3
    # rubocop: disable Metrics/CyclomaticComplexity
4
    def allowed(user, subject)
5
      return anonymous_abilities(user, subject) if user.nil?
Douwe Maan committed
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)
Felipe Artur committed
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)
James Lopez committed
24
      else []
25 26 27
      end.concat(global_abilities(user))
    end

Yorick Peterse committed
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 52 53 54 55 56 57 58 59
    # Returns an Array of Issues that can be read by the given user.
    #
    # issues - The issues to reduce down to those readable by the user.
    # user - The User for which to check the issues
    def issues_readable_by_user(issues, user = nil)
      return issues if user && user.admin?

      issues.select { |issue| issue.visible_to_user?(user) }
    end

60 61
    # List of possible abilities for anonymous user
    def anonymous_abilities(user, subject)
62
      if subject.is_a?(PersonalSnippet)
63
        anonymous_personal_snippet_abilities(subject)
64
      elsif subject.is_a?(ProjectSnippet)
65
        anonymous_project_snippet_abilities(subject)
66
      elsif subject.is_a?(CommitStatus)
Kamil Trzcinski committed
67
        anonymous_commit_status_abilities(subject)
68
      elsif subject.is_a?(Project) || subject.respond_to?(:project)
69
        anonymous_project_abilities(subject)
70
      elsif subject.is_a?(Group) || subject.respond_to?(:group)
71
        anonymous_group_abilities(subject)
72
      elsif subject.is_a?(User)
Felipe Artur committed
73
        anonymous_user_abilities
Douwe Maan committed
74 75 76
      else
        []
      end
77 78
    end

79
    def anonymous_project_abilities(subject)
Douwe Maan committed
80
      project = if subject.is_a?(Project)
81 82
                  subject
                else
83
                  subject.project
84 85
                end

86
      if project && project.public?
87
        rules = [
88 89
          :read_project,
          :read_wiki,
90
          :read_label,
91 92
          :read_milestone,
          :read_project_snippet,
93
          :read_project_member,
94 95
          :read_merge_request,
          :read_note,
96
          :read_pipeline,
97
          :read_commit_status,
98
          :read_container_image,
99 100
          :download_code
        ]
101

Kamil Trzcinski committed
102
        # Allow to read builds by anonymous user if guests are allowed
103
        rules << :read_build if project.public_builds?
104

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

108
        rules - project_disabled_features_rules(project)
109
      else
110 111 112
        []
      end
    end
113

Kamil Trzcinski committed
114 115 116 117 118 119 120
    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

121
    def anonymous_group_abilities(subject)
122 123
      rules = []

Douwe Maan committed
124
      group = if subject.is_a?(Group)
125 126 127 128 129
                subject
              else
                subject.group
              end

130
      rules << :read_group if group.public?
131 132

      rules
133 134
    end

135
    def anonymous_personal_snippet_abilities(snippet)
136 137 138 139
      if snippet.public?
        [:read_personal_snippet]
      else
        []
140 141 142
      end
    end

143 144 145 146 147 148 149 150
    def anonymous_project_snippet_abilities(snippet)
      if snippet.public?
        [:read_project_snippet]
      else
        []
      end
    end

Felipe Artur committed
151 152
    def anonymous_user_abilities
      [:read_user] unless restricted_public_level?
153 154
    end

155 156 157
    def global_abilities(user)
      rules = []
      rules << :create_group if user.can_create_group
158
      rules << :read_users_list
159
      rules
gitlabhq committed
160 161
    end

Andrey Kumanyaev committed
162 163
    def project_abilities(user, project)
      rules = []
164
      key = "/user/#{user.id}/project/#{project.id}"
165

166
      RequestStore.store[key] ||= begin
Zeger-Jan van de Weg committed
167
        # Push abilities on the users team role
168
        rules.push(*project_team_rules(project.team, user))
gitlabhq committed
169

170 171 172
        owner = user.admin? ||
                project.owner == user ||
                (project.group && project.group.has_owner?(user))
173

174
        if owner
175 176 177
          rules.push(*project_owner_rules)
        end

Zeger-Jan van de Weg committed
178
        if project.public? || (project.internal? && !user.external?)
179
          rules.push(*public_project_rules)
180

181
          # Allow to read builds for internal projects
182
          rules << :read_build if project.public_builds?
183

184
          unless owner || project.team.member?(user) || project_group_member?(project, user)
185
            rules << :request_access if project.request_access_enabled
186
          end
187
        end
188

189 190 191
        if project.archived?
          rules -= project_archived_rules
        end
192

193
        rules - project_disabled_features_rules(project)
194
      end
195 196
    end

Zeger-Jan van de Weg committed
197 198 199 200 201 202 203 204 205 206
    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
207 208
      else
        []
Zeger-Jan van de Weg committed
209 210 211
      end
    end

212
    def public_project_rules
213
      @public_project_rules ||= project_guest_rules + [
214
        :download_code,
215
        :fork_project,
216
        :read_commit_status,
217 218
        :read_pipeline,
        :read_container_image
219 220 221
      ]
    end

222
    def project_guest_rules
223
      @project_guest_rules ||= [
Andrey Kumanyaev committed
224 225 226
        :read_project,
        :read_wiki,
        :read_issue,
227
        :read_label,
Andrey Kumanyaev committed
228
        :read_milestone,
Andrew8xx8 committed
229
        :read_project_snippet,
230
        :read_project_member,
Andrey Kumanyaev committed
231 232
        :read_merge_request,
        :read_note,
233 234
        :create_project,
        :create_issue,
Douwe Maan committed
235 236
        :create_note,
        :upload_file
237 238
      ]
    end
Dmitriy Zaporozhets committed
239

240
    def project_report_rules
241
      @project_report_rules ||= project_guest_rules + [
Andrey Kumanyaev committed
242
        :download_code,
243
        :fork_project,
244 245 246
        :create_project_snippet,
        :update_issue,
        :admin_issue,
247
        :admin_label,
248
        :read_commit_status,
249
        :read_build,
250
        :read_container_image,
Kamil Trzcinski committed
251
        :read_pipeline,
252 253
        :read_environment,
        :read_deployment
254 255
      ]
    end
Dmitriy Zaporozhets committed
256

257
    def project_dev_rules
258
      @project_dev_rules ||= project_report_rules + [
259
        :admin_merge_request,
260
        :update_merge_request,
261 262 263 264
        :create_commit_status,
        :update_commit_status,
        :create_build,
        :update_build,
Kamil Trzcinski committed
265 266
        :create_pipeline,
        :update_pipeline,
267 268
        :create_merge_request,
        :create_wiki,
269
        :push_code,
270 271
        :create_container_image,
        :update_container_image,
272
        :create_environment,
273
        :create_deployment
274 275
      ]
    end
276

277
    def project_archived_rules
278
      @project_archived_rules ||= [
279
        :create_merge_request,
280 281
        :push_code,
        :push_code_to_protected_branches,
282
        :update_merge_request,
283 284 285 286
        :admin_merge_request
      ]
    end

287
    def project_master_rules
288
      @project_master_rules ||= project_dev_rules + [
289
        :push_code_to_protected_branches,
290
        :update_project_snippet,
291
        :update_environment,
292
        :update_deployment,
Andrey Kumanyaev committed
293
        :admin_milestone,
Andrew8xx8 committed
294
        :admin_project_snippet,
295
        :admin_project_member,
Andrey Kumanyaev committed
296 297
        :admin_merge_request,
        :admin_note,
298
        :admin_wiki,
299 300
        :admin_project,
        :admin_commit_status,
Kamil Trzcinski committed
301
        :admin_build,
302
        :admin_container_image,
303 304 305
        :admin_pipeline,
        :admin_environment,
        :admin_deployment
306 307
      ]
    end
gitlabhq committed
308

309 310
    def project_owner_rules
      @project_owner_rules ||= project_master_rules + [
311
        :change_namespace,
312
        :change_visibility_level,
313
        :rename_project,
314
        :remove_project,
315
        :archive_project,
316
        :remove_fork_project,
317 318
        :destroy_merge_request,
        :destroy_issue
319
      ]
Andrey Kumanyaev committed
320
    end
gitlabhq committed
321

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
    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

346 347
      unless project.builds_enabled
        rules += named_abilities('build')
Kamil Trzcinski committed
348
        rules += named_abilities('pipeline')
349 350
        rules += named_abilities('environment')
        rules += named_abilities('deployment')
351 352
      end

353
      unless project.container_registry_enabled
354
        rules += named_abilities('container_image')
355 356
      end

357 358 359
      rules
    end

360
    def group_abilities(user, group)
361
      rules = []
Felipe Artur committed
362
      rules << :read_group if can_read_group?(user, group)
363

364 365
      owner = user.admin? || group.has_owner?(user)
      master = owner || group.has_master?(user)
366

367
      # Only group masters and group owners can create new projects
368
      if master
369
        rules += [
370
          :create_projects,
Felipe Artur committed
371
          :admin_milestones
372
        ]
373 374
      end

375
      # Only group owner and administrators can admin group
376
      if owner
Douwe Maan committed
377 378 379
        rules += [
          :admin_group,
          :admin_namespace,
Felipe Artur committed
380 381
          :admin_group_member,
          :change_visibility_level
Douwe Maan committed
382
        ]
383
      end
384

Rémy Coutable committed
385 386
      if group.public? || (group.internal? && !user.external?)
        rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
387 388
      end

389 390 391
      rules.flatten
    end

Felipe Artur committed
392
    def can_read_group?(user, group)
Douwe Maan committed
393 394 395 396 397 398
      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 committed
399 400
    end

401
    def can_edit_note?(user, note)
Stan Hu committed
402 403
      return false if !note.editable? || !user.present?
      return true if note.author == user || user.admin?
404 405 406 407 408 409 410 411 412

      if note.project
        max_access_level = note.project.team.max_member_access(user.id)
        max_access_level >= Gitlab::Access::MASTER
      else
        false
      end
    end

413
    def namespace_abilities(user, namespace)
414 415
      rules = []

416
      # Only namespace owner and administrators can admin it
417
      if namespace.owner == user || user.admin?
Douwe Maan committed
418 419 420 421
        rules += [
          :create_projects,
          :admin_namespace
        ]
422 423 424 425 426
      end

      rules.flatten
    end

427
    [:issue, :merge_request].each do |name|
gitlabhq committed
428
      define_method "#{name}_abilities" do |user, subject|
429 430 431 432
        rules = []

        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
          rules += [
gitlabhq committed
433
            :"read_#{name}",
434
            :"update_#{name}",
gitlabhq committed
435
          ]
436 437 438
        end

        rules += project_abilities(user, subject.project)
439
        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
440 441 442 443
        rules
      end
    end

444 445
    def note_abilities(user, note)
      rules = []
446

447 448 449 450 451 452 453
      if note.author == user
        rules += [
          :read_note,
          :update_note,
          :admin_note
        ]
      end
454

455 456
      if note.respond_to?(:project) && note.project
        rules += project_abilities(user, note.project)
gitlabhq committed
457
      end
458 459

      rules
gitlabhq committed
460
    end
461

462 463 464 465 466 467 468 469 470 471 472
    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 committed
473
      if snippet.public? || (snippet.internal? && !user.external?)
474
        rules << :read_personal_snippet
475 476 477 478 479
      end

      rules
    end

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    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

498
    def group_member_abilities(user, subject)
499 500 501
      rules = []
      target_user = subject.user
      group = subject.group
502

Douwe Maan committed
503 504
      unless group.last_owner?(target_user)
        can_manage = group_abilities(user, group).include?(:admin_group_member)
505

506
        if can_manage
Douwe Maan committed
507 508
          rules << :update_group_member
          rules << :destroy_group_member
509
        elsif user == target_user
Douwe Maan committed
510 511
          rules << :destroy_group_member
        end
512
      end
513

514 515
      rules
    end
Ciro Santilli committed
516

517 518 519 520 521
    def project_member_abilities(user, subject)
      rules = []
      target_user = subject.user
      project = subject.project

522
      unless target_user == project.owner
Douwe Maan committed
523
        can_manage = project_abilities(user, project).include?(:admin_project_member)
524

525
        if can_manage
Douwe Maan committed
526 527
          rules << :update_project_member
          rules << :destroy_project_member
528
        elsif user == target_user
Douwe Maan committed
529 530
          rules << :destroy_project_member
        end
531
      end
Douwe Maan committed
532

533 534 535
      rules
    end

Kamil Trzcinski committed
536 537 538 539 540 541 542
    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

543 544 545
    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
546
      %w(read create update admin).each do |rule|
547
        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
548 549 550 551
      end
      rules
    end

552 553 554 555 556 557 558 559 560 561 562 563
    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

Felipe Artur committed
564
    def user_abilities
565 566 567
      [:read_user]
    end

Ciro Santilli committed
568 569
    def abilities
      @abilities ||= begin
570 571 572 573
        abilities = Six.new
        abilities << self
        abilities
      end
Ciro Santilli committed
574
    end
575 576 577

    private

Felipe Artur committed
578
    def restricted_public_level?
579
      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
Felipe Artur committed
580 581
    end

582 583 584
    def named_abilities(name)
      [
        :"read_#{name}",
585 586
        :"create_#{name}",
        :"update_#{name}",
587 588 589
        :"admin_#{name}"
      ]
    end
590 591 592 593

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

594
      unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
595 596 597 598 599 600 601
        rules.delete(:admin_issue)
        rules.delete(:read_issue)
        rules.delete(:update_issue)
      end

      rules
    end
602 603 604 605 606 607 608 609

    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
gitlabhq committed
610
  end
gitlabhq committed
611
end