BigW Consortium Gitlab

ability.rb 14.2 KB
Newer Older
gitlabhq committed
1
class Ability
Andrey Kumanyaev committed
2
  class << self
3
    def allowed(user, subject)
4
      return anonymous_abilities(user, subject) if user.nil?
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 committed
21
      when User then user_abilities
James Lopez committed
22
      else []
23 24 25
      end.concat(global_abilities(user))
    end

Yorick Peterse committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
    # 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

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

67
    def anonymous_project_abilities(subject)
Douwe Maan committed
68
      project = if subject.is_a?(Project)
69 70
                  subject
                else
71
                  subject.project
72 73
                end

74
      if project && project.public?
75
        rules = [
76 77
          :read_project,
          :read_wiki,
78
          :read_label,
79 80
          :read_milestone,
          :read_project_snippet,
81
          :read_project_member,
82 83
          :read_merge_request,
          :read_note,
84
          :read_pipeline,
85
          :read_commit_status,
86
          :read_container_image,
87 88
          :download_code
        ]
89

Kamil Trzcinski committed
90
        # Allow to read builds by anonymous user if guests are allowed
91
        rules << :read_build if project.public_builds?
92

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

96
        rules - project_disabled_features_rules(project)
97
      else
98 99 100
        []
      end
    end
101

Kamil Trzcinski committed
102 103 104 105 106 107 108
    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

109
    def anonymous_group_abilities(subject)
110 111
      rules = []

Douwe Maan committed
112
      group = if subject.is_a?(Group)
113 114 115 116 117
                subject
              else
                subject.group
              end

118
      rules << :read_group if group.public?
119 120

      rules
121 122
    end

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

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

Felipe Artur committed
139 140
    def anonymous_user_abilities
      [:read_user] unless restricted_public_level?
141 142
    end

143 144 145
    def global_abilities(user)
      rules = []
      rules << :create_group if user.can_create_group
146
      rules << :read_users_list
147
      rules
gitlabhq committed
148 149
    end

Andrey Kumanyaev committed
150 151
    def project_abilities(user, project)
      rules = []
152
      key = "/user/#{user.id}/project/#{project.id}"
153

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

158 159 160 161 162 163 164
        if project.owner == user ||
          (project.group && project.group.has_owner?(user)) ||
          user.admin?

          rules.push(*project_owner_rules)
        end

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

168
          # Allow to read builds for internal projects
169
          rules << :read_build if project.public_builds?
170
        end
171

172 173 174
        if project.archived?
          rules -= project_archived_rules
        end
175

176
        rules - project_disabled_features_rules(project)
177
      end
178 179
    end

Zeger-Jan van de Weg committed
180 181 182 183 184 185 186 187 188 189
    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
190 191
      else
        []
Zeger-Jan van de Weg committed
192 193 194
      end
    end

195
    def public_project_rules
196
      @public_project_rules ||= project_guest_rules + [
197
        :download_code,
198
        :fork_project,
Felipe Artur committed
199
        :read_commit_status
200 201 202
      ]
    end

203
    def project_guest_rules
204
      @project_guest_rules ||= [
Andrey Kumanyaev committed
205 206 207
        :read_project,
        :read_wiki,
        :read_issue,
208
        :read_label,
Andrey Kumanyaev committed
209
        :read_milestone,
Andrew8xx8 committed
210
        :read_project_snippet,
211
        :read_project_member,
Andrey Kumanyaev committed
212 213
        :read_merge_request,
        :read_note,
214 215
        :create_project,
        :create_issue,
Douwe Maan committed
216 217
        :create_note,
        :upload_file
218 219
      ]
    end
Dmitriy Zaporozhets committed
220

221
    def project_report_rules
222
      @project_report_rules ||= project_guest_rules + [
Andrey Kumanyaev committed
223
        :download_code,
224
        :fork_project,
225 226 227
        :create_project_snippet,
        :update_issue,
        :admin_issue,
228
        :admin_label,
229
        :read_commit_status,
230
        :read_build,
231
        :read_container_image,
Kamil Trzcinski committed
232
        :read_pipeline,
233 234
      ]
    end
Dmitriy Zaporozhets committed
235

236
    def project_dev_rules
237
      @project_dev_rules ||= project_report_rules + [
238
        :admin_merge_request,
239
        :update_merge_request,
240 241 242 243
        :create_commit_status,
        :update_commit_status,
        :create_build,
        :update_build,
Kamil Trzcinski committed
244 245
        :create_pipeline,
        :update_pipeline,
246 247
        :create_merge_request,
        :create_wiki,
248
        :push_code,
249 250
        :create_container_image,
        :update_container_image,
251 252
      ]
    end
253

254
    def project_archived_rules
255
      @project_archived_rules ||= [
256
        :create_merge_request,
257 258
        :push_code,
        :push_code_to_protected_branches,
259
        :update_merge_request,
260 261 262 263
        :admin_merge_request
      ]
    end

264
    def project_master_rules
265
      @project_master_rules ||= project_dev_rules + [
266
        :push_code_to_protected_branches,
267
        :update_project_snippet,
Andrey Kumanyaev committed
268
        :admin_milestone,
Andrew8xx8 committed
269
        :admin_project_snippet,
270
        :admin_project_member,
Andrey Kumanyaev committed
271 272
        :admin_merge_request,
        :admin_note,
273
        :admin_wiki,
274 275
        :admin_project,
        :admin_commit_status,
Kamil Trzcinski committed
276
        :admin_build,
277
        :admin_container_image,
Kamil Trzcinski committed
278
        :admin_pipeline
279 280
      ]
    end
gitlabhq committed
281

282 283
    def project_owner_rules
      @project_owner_rules ||= project_master_rules + [
284
        :change_namespace,
285
        :change_visibility_level,
286
        :rename_project,
287
        :remove_project,
288
        :archive_project,
289
        :remove_fork_project,
290 291
        :destroy_merge_request,
        :destroy_issue
292
      ]
Andrey Kumanyaev committed
293
    end
gitlabhq committed
294

295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    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

319 320
      unless project.builds_enabled
        rules += named_abilities('build')
Kamil Trzcinski committed
321
        rules += named_abilities('pipeline')
322 323
      end

324
      unless project.container_registry_enabled
325
        rules += named_abilities('container_image')
326 327
      end

328 329 330
      rules
    end

331
    def group_abilities(user, group)
332
      rules = []
Felipe Artur committed
333
      rules << :read_group if can_read_group?(user, group)
334

335
      # Only group masters and group owners can create new projects
336
      if group.has_master?(user) || group.has_owner?(user) || user.admin?
337
        rules += [
338
          :create_projects,
Felipe Artur committed
339
          :admin_milestones
340
        ]
341 342
      end

343
      # Only group owner and administrators can admin group
344
      if group.has_owner?(user) || user.admin?
Douwe Maan committed
345 346 347
        rules += [
          :admin_group,
          :admin_namespace,
Felipe Artur committed
348 349
          :admin_group_member,
          :change_visibility_level
Douwe Maan committed
350
        ]
351
      end
352 353 354 355

      rules.flatten
    end

Felipe Artur committed
356
    def can_read_group?(user, group)
Douwe Maan committed
357 358 359 360 361 362
      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
363 364
    end

365
    def namespace_abilities(user, namespace)
366 367
      rules = []

368
      # Only namespace owner and administrators can admin it
369
      if namespace.owner == user || user.admin?
Douwe Maan committed
370 371 372 373
        rules += [
          :create_projects,
          :admin_namespace
        ]
374 375 376 377 378
      end

      rules.flatten
    end

379
    [:issue, :merge_request].each do |name|
gitlabhq committed
380
      define_method "#{name}_abilities" do |user, subject|
381 382 383 384
        rules = []

        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
          rules += [
gitlabhq committed
385
            :"read_#{name}",
386
            :"update_#{name}",
gitlabhq committed
387
          ]
388 389 390
        end

        rules += project_abilities(user, subject.project)
391
        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
392 393 394 395
        rules
      end
    end

396 397
    def note_abilities(user, note)
      rules = []
398

399 400 401 402 403 404 405
      if note.author == user
        rules += [
          :read_note,
          :update_note,
          :admin_note
        ]
      end
406

407 408
      if note.respond_to?(:project) && note.project
        rules += project_abilities(user, note.project)
gitlabhq committed
409
      end
410 411

      rules
gitlabhq committed
412
    end
413

414 415 416 417 418 419 420 421 422 423 424
    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
425
      if snippet.public? || (snippet.internal? && !user.external?)
426
        rules << :read_personal_snippet
427 428 429 430 431
      end

      rules
    end

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    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

450
    def group_member_abilities(user, subject)
451 452 453
      rules = []
      target_user = subject.user
      group = subject.group
454

Douwe Maan committed
455 456
      unless group.last_owner?(target_user)
        can_manage = group_abilities(user, group).include?(:admin_group_member)
457

458
        if can_manage
Douwe Maan committed
459 460
          rules << :update_group_member
          rules << :destroy_group_member
461
        elsif user == target_user
Douwe Maan committed
462
          rules << :destroy_group_member
463 464
        elsif subject.request? && user == subject.created_by
          rules << :destroy_group_member
Douwe Maan committed
465
        end
466
      end
467

468 469
      rules
    end
Ciro Santilli committed
470

471 472 473 474 475
    def project_member_abilities(user, subject)
      rules = []
      target_user = subject.user
      project = subject.project

Douwe Maan committed
476 477
      unless target_user == project.owner
        can_manage = project_abilities(user, project).include?(:admin_project_member)
478

479
        if can_manage
Douwe Maan committed
480 481
          rules << :update_project_member
          rules << :destroy_project_member
482
        elsif user == target_user
Douwe Maan committed
483
          rules << :destroy_project_member
484 485
        elsif subject.request? && user == subject.created_by
          rules << :destroy_project_member
Douwe Maan committed
486
        end
487
      end
Douwe Maan committed
488

489 490 491
      rules
    end

Kamil Trzcinski committed
492 493 494 495 496 497 498
    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

499 500 501
    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
502
      %w(read create update admin).each do |rule|
503
        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
504 505 506 507
      end
      rules
    end

Felipe Artur committed
508
    def user_abilities
509 510 511
      [:read_user]
    end

Ciro Santilli committed
512 513
    def abilities
      @abilities ||= begin
514 515 516 517
        abilities = Six.new
        abilities << self
        abilities
      end
Ciro Santilli committed
518
    end
519

520 521 522 523
    def external_issue_abilities(user, subject)
      project_abilities(user, subject.project)
    end

524 525
    private

Felipe Artur committed
526
    def restricted_public_level?
527
      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
Felipe Artur committed
528 529
    end

530 531 532
    def named_abilities(name)
      [
        :"read_#{name}",
533 534
        :"create_#{name}",
        :"update_#{name}",
535 536 537
        :"admin_#{name}"
      ]
    end
538 539 540 541

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

542
      unless issue.author == user || issue.assignee == user || issue.project.team.member?(user, Gitlab::Access::REPORTER)
543 544 545 546 547 548 549
        rules.delete(:admin_issue)
        rules.delete(:read_issue)
        rules.delete(:update_issue)
      end

      rules
    end
gitlabhq committed
550
  end
gitlabhq committed
551
end