ability.rb 13 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)
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 65
          :download_code
        ]
66

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

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

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

Kamil Trzcinski's avatar
Kamil Trzcinski committed
79 80 81 82 83 84 85
    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

86
    def anonymous_group_abilities(subject)
87 88
      rules = []

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

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

      rules
98 99
    end

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

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

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

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

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

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

135 136 137 138 139 140 141
        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
142
        if project.public? || (project.internal? && !user.external?)
143
          rules.push(*public_project_rules)
144

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

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

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

Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
157 158
    def project_team_rules(team, user)
      # Rules based on role in project
159
      if team.master?(user)
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
160 161 162 163 164 165 166 167 168 169
        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

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

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

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

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

223
    def project_archived_rules
224
      @project_archived_rules ||= [
225
        :create_merge_request,
226 227
        :push_code,
        :push_code_to_protected_branches,
228
        :update_merge_request,
229 230 231 232
        :admin_merge_request
      ]
    end

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

249 250
    def project_owner_rules
      @project_owner_rules ||= project_master_rules + [
251
        :change_namespace,
252
        :change_visibility_level,
253
        :rename_project,
254
        :remove_project,
255
        :archive_project,
256
        :remove_fork_project,
257 258
        :destroy_merge_request,
        :destroy_issue
259
      ]
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
260
    end
gitlabhq's avatar
gitlabhq committed
261

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
    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

286 287 288 289
      unless project.builds_enabled
        rules += named_abilities('build')
      end

290 291 292
      rules
    end

293
    def group_abilities(user, group)
294
      rules = []
Felipe Artur's avatar
Felipe Artur committed
295
      rules << :read_group if can_read_group?(user, group)
296

297
      # Only group masters and group owners can create new projects
298
      if group.has_master?(user) || group.has_owner?(user) || user.admin?
299
        rules += [
300
          :create_projects,
Felipe Artur's avatar
Felipe Artur committed
301
          :admin_milestones
302
        ]
303 304
      end

305
      # Only group owner and administrators can admin group
306
      if group.has_owner?(user) || user.admin?
Douwe Maan's avatar
Douwe Maan committed
307 308 309
        rules += [
          :admin_group,
          :admin_namespace,
Felipe Artur's avatar
Felipe Artur committed
310 311
          :admin_group_member,
          :change_visibility_level
Douwe Maan's avatar
Douwe Maan committed
312
        ]
313
      end
314 315 316 317

      rules.flatten
    end

Felipe Artur's avatar
Felipe Artur committed
318
    def can_read_group?(user, group)
Douwe Maan's avatar
Douwe Maan committed
319 320 321 322 323 324
      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
325 326
    end

327
    def namespace_abilities(user, namespace)
328 329
      rules = []

330
      # Only namespace owner and administrators can admin it
331
      if namespace.owner == user || user.admin?
Douwe Maan's avatar
Douwe Maan committed
332 333 334 335
        rules += [
          :create_projects,
          :admin_namespace
        ]
336 337 338 339 340
      end

      rules.flatten
    end

341
    [:issue, :merge_request].each do |name|
gitlabhq's avatar
gitlabhq committed
342
      define_method "#{name}_abilities" do |user, subject|
343 344 345 346
        rules = []

        if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
          rules += [
gitlabhq's avatar
gitlabhq committed
347
            :"read_#{name}",
348
            :"update_#{name}",
gitlabhq's avatar
gitlabhq committed
349
          ]
350 351 352
        end

        rules += project_abilities(user, subject.project)
353
        rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
354 355 356 357
        rules
      end
    end

358 359
    def note_abilities(user, note)
      rules = []
360

361 362 363 364 365 366 367
      if note.author == user
        rules += [
          :read_note,
          :update_note,
          :admin_note
        ]
      end
368

369 370
      if note.respond_to?(:project) && note.project
        rules += project_abilities(user, note.project)
gitlabhq's avatar
gitlabhq committed
371
      end
372 373

      rules
gitlabhq's avatar
gitlabhq committed
374
    end
375

376 377 378 379 380 381 382 383 384 385 386
    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
387
      if snippet.public? || (snippet.internal? && !user.external?)
388
        rules << :read_personal_snippet
389 390 391 392 393
      end

      rules
    end

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    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

412
    def group_member_abilities(user, subject)
413 414 415
      rules = []
      target_user = subject.user
      group = subject.group
416

Douwe Maan's avatar
Douwe Maan committed
417 418
      unless group.last_owner?(target_user)
        can_manage = group_abilities(user, group).include?(:admin_group_member)
419

420
        if can_manage
Douwe Maan's avatar
Douwe Maan committed
421 422
          rules << :update_group_member
          rules << :destroy_group_member
423
        elsif user == target_user
Douwe Maan's avatar
Douwe Maan committed
424 425
          rules << :destroy_group_member
        end
426
      end
427

428 429
      rules
    end
Ciro Santilli's avatar
Ciro Santilli committed
430

431 432 433 434 435
    def project_member_abilities(user, subject)
      rules = []
      target_user = subject.user
      project = subject.project

Douwe Maan's avatar
Douwe Maan committed
436 437
      unless target_user == project.owner
        can_manage = project_abilities(user, project).include?(:admin_project_member)
438

439
        if can_manage
Douwe Maan's avatar
Douwe Maan committed
440 441
          rules << :update_project_member
          rules << :destroy_project_member
442
        elsif user == target_user
Douwe Maan's avatar
Douwe Maan committed
443 444
          rules << :destroy_project_member
        end
445
      end
Douwe Maan's avatar
Douwe Maan committed
446

447 448 449
      rules
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
450 451 452 453 454 455 456
    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

457 458 459
    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
460
      %w(read create update admin).each do |rule|
461
        rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
462 463 464 465
      end
      rules
    end

Felipe Artur's avatar
Felipe Artur committed
466
    def user_abilities
467 468 469
      [:read_user]
    end

Ciro Santilli's avatar
Ciro Santilli committed
470 471
    def abilities
      @abilities ||= begin
472 473 474 475
        abilities = Six.new
        abilities << self
        abilities
      end
Ciro Santilli's avatar
Ciro Santilli committed
476
    end
477

478 479 480 481
    def external_issue_abilities(user, subject)
      project_abilities(user, subject.project)
    end

482 483
    private

Felipe Artur's avatar
Felipe Artur committed
484
    def restricted_public_level?
Felipe Artur's avatar
Felipe Artur committed
485
      current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
Felipe Artur's avatar
Felipe Artur committed
486 487
    end

488 489 490
    def named_abilities(name)
      [
        :"read_#{name}",
491 492
        :"create_#{name}",
        :"update_#{name}",
493 494 495
        :"admin_#{name}"
      ]
    end
496 497 498 499 500 501 502 503 504 505 506 507

    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
508
  end
gitlabhq's avatar
gitlabhq committed
509
end