BigW Consortium Gitlab

ability.rb 16.2 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
      abilities_by_subject_class(user: user, subject: subject)
    end

    def abilities_by_subject_class(user:, subject:)
13 14 15 16 17 18 19 20 21 22 23 24
      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
25
      when User then user_abilities
26
      when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
27
      when Ci::Runner then runner_abilities(user, subject)
James Lopez committed
28
      else []
29 30 31
      end.concat(global_abilities(user))
    end

Yorick Peterse committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    # 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

54 55 56 57 58 59 60 61 62 63
    # 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

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

83
    def anonymous_project_abilities(subject)
Douwe Maan committed
84
      project = if subject.is_a?(Project)
85 86
                  subject
                else
87
                  subject.project
88 89
                end

90
      if project && project.public?
91
        rules = [
92
          :read_project,
93
          :read_board,
94
          :read_list,
95
          :read_wiki,
96
          :read_label,
97 98
          :read_milestone,
          :read_project_snippet,
99
          :read_project_member,
100 101
          :read_merge_request,
          :read_note,
102
          :read_pipeline,
103
          :read_commit_status,
104
          :read_container_image,
105 106
          :download_code
        ]
107

Kamil Trzcinski committed
108
        # Allow to read builds by anonymous user if guests are allowed
109
        rules << :read_build if project.public_builds?
110

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

114
        rules - project_disabled_features_rules(project)
115
      else
116 117 118
        []
      end
    end
119

Kamil Trzcinski committed
120 121 122 123 124 125 126
    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

127
    def anonymous_group_abilities(subject)
128 129
      rules = []

Douwe Maan committed
130
      group = if subject.is_a?(Group)
131 132 133 134 135
                subject
              else
                subject.group
              end

136
      rules << :read_group if group.public?
137 138

      rules
139 140
    end

141
    def anonymous_personal_snippet_abilities(snippet)
142 143 144 145
      if snippet.public?
        [:read_personal_snippet]
      else
        []
146 147 148
      end
    end

149 150 151 152 153 154 155 156
    def anonymous_project_snippet_abilities(snippet)
      if snippet.public?
        [:read_project_snippet]
      else
        []
      end
    end

Felipe Artur committed
157 158
    def anonymous_user_abilities
      [:read_user] unless restricted_public_level?
159 160
    end

161 162 163
    def global_abilities(user)
      rules = []
      rules << :create_group if user.can_create_group
164
      rules << :read_users_list
165
      rules
gitlabhq committed
166 167
    end

Andrey Kumanyaev committed
168 169
    def project_abilities(user, project)
      rules = []
170
      key = "/user/#{user.id}/project/#{project.id}"
171

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

176 177 178
        owner = user.admin? ||
                project.owner == user ||
                (project.group && project.group.has_owner?(user))
179

180
        if owner
181 182 183
          rules.push(*project_owner_rules)
        end

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

187
          # Allow to read builds for internal projects
188
          rules << :read_build if project.public_builds?
189

190
          unless owner || project.team.member?(user) || project_group_member?(project, user)
191
            rules << :request_access if project.request_access_enabled
192
          end
193
        end
194

195 196 197
        if project.archived?
          rules -= project_archived_rules
        end
198

199
        rules - project_disabled_features_rules(project)
200
      end
201 202
    end

Zeger-Jan van de Weg committed
203 204 205 206 207 208 209 210 211 212
    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
213 214
      else
        []
Zeger-Jan van de Weg committed
215 216 217
      end
    end

218
    def public_project_rules
219
      @public_project_rules ||= project_guest_rules + [
220
        :download_code,
221
        :fork_project,
222
        :read_commit_status,
223 224
        :read_pipeline,
        :read_container_image
225 226 227
      ]
    end

228
    def project_guest_rules
229
      @project_guest_rules ||= [
Andrey Kumanyaev committed
230 231 232
        :read_project,
        :read_wiki,
        :read_issue,
233
        :read_board,
234
        :read_list,
235
        :read_label,
Andrey Kumanyaev committed
236
        :read_milestone,
Andrew8xx8 committed
237
        :read_project_snippet,
238
        :read_project_member,
Andrey Kumanyaev committed
239 240
        :read_merge_request,
        :read_note,
241 242
        :create_project,
        :create_issue,
Douwe Maan committed
243 244
        :create_note,
        :upload_file
245 246
      ]
    end
Dmitriy Zaporozhets committed
247

248
    def project_report_rules
249
      @project_report_rules ||= project_guest_rules + [
Andrey Kumanyaev committed
250
        :download_code,
251
        :fork_project,
252 253 254
        :create_project_snippet,
        :update_issue,
        :admin_issue,
255
        :admin_label,
256
        :admin_list,
257
        :read_commit_status,
258
        :read_build,
259
        :read_container_image,
Kamil Trzcinski committed
260
        :read_pipeline,
261 262
        :read_environment,
        :read_deployment
263 264
      ]
    end
Dmitriy Zaporozhets committed
265

266
    def project_dev_rules
267
      @project_dev_rules ||= project_report_rules + [
268
        :admin_merge_request,
269
        :update_merge_request,
270 271 272 273
        :create_commit_status,
        :update_commit_status,
        :create_build,
        :update_build,
Kamil Trzcinski committed
274 275
        :create_pipeline,
        :update_pipeline,
276 277
        :create_merge_request,
        :create_wiki,
278
        :push_code,
279 280
        :create_container_image,
        :update_container_image,
281
        :create_environment,
282
        :create_deployment
283 284
      ]
    end
285

286
    def project_archived_rules
287
      @project_archived_rules ||= [
288
        :create_merge_request,
289 290
        :push_code,
        :push_code_to_protected_branches,
291
        :update_merge_request,
292 293 294 295
        :admin_merge_request
      ]
    end

296
    def project_master_rules
297
      @project_master_rules ||= project_dev_rules + [
298
        :push_code_to_protected_branches,
299
        :update_project_snippet,
300
        :update_environment,
301
        :update_deployment,
Andrey Kumanyaev committed
302
        :admin_milestone,
Andrew8xx8 committed
303
        :admin_project_snippet,
304
        :admin_project_member,
Andrey Kumanyaev committed
305 306
        :admin_merge_request,
        :admin_note,
307
        :admin_wiki,
308 309
        :admin_project,
        :admin_commit_status,
Kamil Trzcinski committed
310
        :admin_build,
311
        :admin_container_image,
312 313 314
        :admin_pipeline,
        :admin_environment,
        :admin_deployment
315 316
      ]
    end
gitlabhq committed
317

318 319
    def project_owner_rules
      @project_owner_rules ||= project_master_rules + [
320
        :change_namespace,
321
        :change_visibility_level,
322
        :rename_project,
323
        :remove_project,
324
        :archive_project,
325
        :remove_fork_project,
326 327
        :destroy_merge_request,
        :destroy_issue
328
      ]
Andrey Kumanyaev committed
329
    end
gitlabhq committed
330

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
    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

355 356
      unless project.builds_enabled
        rules += named_abilities('build')
Kamil Trzcinski committed
357
        rules += named_abilities('pipeline')
358 359
        rules += named_abilities('environment')
        rules += named_abilities('deployment')
360 361
      end

362
      unless project.container_registry_enabled
363
        rules += named_abilities('container_image')
364 365
      end

366 367 368
      rules
    end

369
    def group_abilities(user, group)
370
      rules = []
Felipe Artur committed
371
      rules << :read_group if can_read_group?(user, group)
372

373 374
      owner = user.admin? || group.has_owner?(user)
      master = owner || group.has_master?(user)
375

376
      # Only group masters and group owners can create new projects
377
      if master
378
        rules += [
379
          :create_projects,
Felipe Artur committed
380
          :admin_milestones
381
        ]
382 383
      end

384
      # Only group owner and administrators can admin group
385
      if owner
Douwe Maan committed
386 387 388
        rules += [
          :admin_group,
          :admin_namespace,
Felipe Artur committed
389 390
          :admin_group_member,
          :change_visibility_level
Douwe Maan committed
391
        ]
392
      end
393

Rémy Coutable committed
394 395
      if group.public? || (group.internal? && !user.external?)
        rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
396 397
      end

398 399 400
      rules.flatten
    end

Felipe Artur committed
401
    def can_read_group?(user, group)
Douwe Maan committed
402 403 404 405 406 407
      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
408 409
    end

410
    def can_edit_note?(user, note)
Stan Hu committed
411 412
      return false if !note.editable? || !user.present?
      return true if note.author == user || user.admin?
413 414 415 416 417 418 419 420 421

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

422
    def namespace_abilities(user, namespace)
423 424
      rules = []

425
      # Only namespace owner and administrators can admin it
426
      if namespace.owner == user || user.admin?
Douwe Maan committed
427 428 429 430
        rules += [
          :create_projects,
          :admin_namespace
        ]
431 432 433 434 435
      end

      rules.flatten
    end

436
    [:issue, :merge_request].each do |name|
gitlabhq committed
437
      define_method "#{name}_abilities" do |user, subject|
438 439 440 441
        rules = []

        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
          rules += [
gitlabhq committed
442
            :"read_#{name}",
443
            :"update_#{name}",
gitlabhq committed
444
          ]
445 446 447
        end

        rules += project_abilities(user, subject.project)
448
        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
449 450 451 452
        rules
      end
    end

453 454
    def note_abilities(user, note)
      rules = []
455

456 457 458 459 460 461 462
      if note.author == user
        rules += [
          :read_note,
          :update_note,
          :admin_note
        ]
      end
463

464 465
      if note.respond_to?(:project) && note.project
        rules += project_abilities(user, note.project)
gitlabhq committed
466
      end
467 468

      rules
gitlabhq committed
469
    end
470

471 472 473 474 475 476 477 478 479 480 481
    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
482
      if snippet.public? || (snippet.internal? && !user.external?)
483
        rules << :read_personal_snippet
484 485 486 487 488
      end

      rules
    end

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
    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

507
    def group_member_abilities(user, subject)
508 509 510
      rules = []
      target_user = subject.user
      group = subject.group
511

Douwe Maan committed
512 513
      unless group.last_owner?(target_user)
        can_manage = group_abilities(user, group).include?(:admin_group_member)
514

515
        if can_manage
Douwe Maan committed
516 517
          rules << :update_group_member
          rules << :destroy_group_member
518
        elsif user == target_user
Douwe Maan committed
519 520
          rules << :destroy_group_member
        end
521
      end
522

523 524
      rules
    end
Ciro Santilli committed
525

526 527 528 529 530
    def project_member_abilities(user, subject)
      rules = []
      target_user = subject.user
      project = subject.project

531
      unless target_user == project.owner
Douwe Maan committed
532
        can_manage = project_abilities(user, project).include?(:admin_project_member)
533

534
        if can_manage
Douwe Maan committed
535 536
          rules << :update_project_member
          rules << :destroy_project_member
537
        elsif user == target_user
Douwe Maan committed
538 539
          rules << :destroy_project_member
        end
540
      end
Douwe Maan committed
541

542 543 544
      rules
    end

Kamil Trzcinski committed
545 546 547 548 549 550 551
    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

552 553 554
    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
555
      %w(read create update admin).each do |rule|
556
        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
557 558 559 560
      end
      rules
    end

561 562 563 564 565 566 567 568 569 570 571 572
    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
573
    def user_abilities
574 575 576
      [:read_user]
    end

Ciro Santilli committed
577 578
    def abilities
      @abilities ||= begin
579 580 581 582
        abilities = Six.new
        abilities << self
        abilities
      end
Ciro Santilli committed
583
    end
584 585 586

    private

Felipe Artur committed
587
    def restricted_public_level?
588
      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
Felipe Artur committed
589 590
    end

591 592 593
    def named_abilities(name)
      [
        :"read_#{name}",
594 595
        :"create_#{name}",
        :"update_#{name}",
596 597 598
        :"admin_#{name}"
      ]
    end
599 600 601 602

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

603
      unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
604 605 606 607 608 609 610
        rules.delete(:admin_issue)
        rules.delete(:read_issue)
        rules.delete(:update_issue)
      end

      rules
    end
611 612 613 614 615 616 617 618

    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
619
  end
gitlabhq committed
620
end