summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Speicher <robert@gitlab.com>2016-08-31 20:53:40 +0000
committerRobert Speicher <robert@gitlab.com>2016-08-31 20:53:40 +0000
commite71cd7a300017cf85e16de3b1c68fdb25c3a4b4d (patch)
treed38382dae7c95938510bae75080c3816df7544a0
parent177cc4e4cbde21e8b56a9f3e0104d6319d79e6cc (diff)
parentb105dc791df07bab0d5349c63cb73c7b3ee8212c (diff)
downloadgitlab-ce-e71cd7a300017cf85e16de3b1c68fdb25c3a4b4d.tar.gz
Merge branch 'refactor/add-policies' into 'master'
Refactor ability.rb into Policies ## What does this MR do? Factors out `ability.rb` into a new abstraction - the "policy" (stored in `app/policies`). A policy is a class named `#{class_name}Policy` (looked up automatically as needed) that implements `rules` as follows: ``` ruby class ThingPolicy < BasePolicy def rules @user # this is a user to determine abilities for, optionally nil in the anonymous case @subject # this is the subject of the ability, guaranteed to be an instance of `Thing` can! :some_ability # grant the :some_ability permission cannot! :some_ability # ensure that :some_ability is not allowed. this overrides any `can!` that is called before or after delegate! @subject.other_thing # merge the abilities (can!) and prohibitions (cannot!) from `@subject.other_thing` can? :some_ability # test whether, so far, :some_ability is allowed end def anonymous_rules # optional. if not implemented `rules` is called where `@user` is nil. otherwise this method is called when `@user` is nil. end end ``` See merge request !5796
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock2
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/namespaces_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/mailers/base_mailer.rb2
-rw-r--r--app/models/ability.rb587
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/user.rb6
-rw-r--r--app/policies/base_policy.rb116
-rw-r--r--app/policies/ci/build_policy.rb13
-rw-r--r--app/policies/ci/runner_policy.rb13
-rw-r--r--app/policies/commit_status_policy.rb5
-rw-r--r--app/policies/deployment_policy.rb5
-rw-r--r--app/policies/environment_policy.rb5
-rw-r--r--app/policies/external_issue_policy.rb5
-rw-r--r--app/policies/global_policy.rb8
-rw-r--r--app/policies/group_member_policy.rb19
-rw-r--r--app/policies/group_policy.rb45
-rw-r--r--app/policies/issuable_policy.rb14
-rw-r--r--app/policies/issue_policy.rb28
-rw-r--r--app/policies/merge_request_policy.rb3
-rw-r--r--app/policies/namespace_policy.rb10
-rw-r--r--app/policies/note_policy.rb19
-rw-r--r--app/policies/personal_snippet_policy.rb16
-rw-r--r--app/policies/project_member_policy.rb22
-rw-r--r--app/policies/project_policy.rb224
-rw-r--r--app/policies/project_snippet_policy.rb20
-rw-r--r--app/policies/user_policy.rb11
-rw-r--r--app/services/base_service.rb6
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb12
-rw-r--r--lib/banzai/reference_parser/base_parser.rb2
-rw-r--r--spec/controllers/projects/boards/issues_controller_spec.rb4
-rw-r--r--spec/controllers/projects/boards/lists_controller_spec.rb4
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb4
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb8
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb10
-rw-r--r--spec/models/ability_spec.rb68
-rw-r--r--spec/models/members/project_member_spec.rb7
-rw-r--r--spec/models/note_spec.rb20
-rw-r--r--spec/models/project_security_spec.rb112
-rw-r--r--spec/policies/project_policy_spec.rb36
45 files changed, 690 insertions, 824 deletions
diff --git a/Gemfile b/Gemfile
index 194379dd687..96841013815 100644
--- a/Gemfile
+++ b/Gemfile
@@ -97,9 +97,6 @@ gem 'fog-rackspace', '~> 0.1.1'
# for aws storage
gem 'unf', '~> 0.1.4'
-# Authorization
-gem 'six', '~> 0.2.0'
-
# Seed data
gem 'seed-fu', '~> 2.3.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0c28975060c..1d0fcfd3c3a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -683,7 +683,6 @@ GEM
rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
- six (0.2.0)
slack-notifier (1.2.1)
slop (3.6.0)
spinach (0.8.10)
@@ -954,7 +953,6 @@ DEPENDENCIES
sidekiq-cron (~> 0.4.0)
simplecov (= 0.12.0)
sinatra (~> 1.4.4)
- six (~> 0.2.0)
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
spinach-rerun-reporter (~> 0.0.2)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ebc2a4651ba..bd4ba384b29 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
- helper_method :abilities, :can?, :current_application_settings
+ helper_method :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
@@ -97,12 +97,8 @@ class ApplicationController < ActionController::Base
current_application_settings.after_sign_out_path.presence || new_user_session_path
end
- def abilities
- Ability.abilities
- end
-
def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
+ Ability.allowed?(object, action, subject)
end
def access_denied!
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index 5a94dcb0dbd..83eec1bf4a2 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user
redirect_to user_path(user)
- elsif group && can?(current_user, :read_group, namespace)
+ elsif group && can?(current_user, :read_group, group)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 33daac0399e..60996b181f2 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -64,7 +64,7 @@ class IssuableFinder
if project?
@project = Project.find(params[:project_id])
- unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index 06b3e8a9502..a93a63bdb9b 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -83,7 +83,7 @@ class TodosFinder
if project?
@project = Project.find(params[:project_id])
- unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ unless Ability.allowed?(current_user, :read_project, @project)
@project = nil
end
else
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 8b83bbd93b7..61a574d3dc0 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -9,7 +9,7 @@ class BaseMailer < ActionMailer::Base
default reply_to: Proc.new { default_reply_to_address.format }
def can?
- Ability.abilities.allowed?(current_user, action, subject)
+ Ability.allowed?(current_user, action, subject)
end
private
diff --git a/app/models/ability.rb b/app/models/ability.rb
index c1df4a865f6..fa8f8bc3a5f 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,34 +1,5 @@
class Ability
class << self
- # rubocop: disable Metrics/CyclomaticComplexity
- def allowed(user, subject)
- return anonymous_abilities(user, subject) if user.nil?
- return [] unless user.is_a?(User)
- return [] if user.blocked?
-
- abilities_by_subject_class(user: user, subject: subject)
- end
-
- def abilities_by_subject_class(user:, subject:)
- 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)
- when User then user_abilities
- when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project)
- when Ci::Runner then runner_abilities(user, subject)
- else []
- end.concat(global_abilities(user))
- end
-
# 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)
@@ -61,359 +32,7 @@ class Ability
issues.select { |issue| issue.visible_to_user?(user) }
end
- # List of possible abilities for anonymous user
- def anonymous_abilities(user, subject)
- if subject.is_a?(PersonalSnippet)
- anonymous_personal_snippet_abilities(subject)
- elsif subject.is_a?(ProjectSnippet)
- anonymous_project_snippet_abilities(subject)
- elsif subject.is_a?(CommitStatus)
- anonymous_commit_status_abilities(subject)
- elsif subject.is_a?(Project) || subject.respond_to?(:project)
- anonymous_project_abilities(subject)
- elsif subject.is_a?(Group) || subject.respond_to?(:group)
- anonymous_group_abilities(subject)
- elsif subject.is_a?(User)
- anonymous_user_abilities
- else
- []
- end
- end
-
- def anonymous_project_abilities(subject)
- project = if subject.is_a?(Project)
- subject
- else
- subject.project
- end
-
- if project && project.public?
- rules = [
- :read_project,
- :read_board,
- :read_list,
- :read_wiki,
- :read_label,
- :read_milestone,
- :read_project_snippet,
- :read_project_member,
- :read_merge_request,
- :read_note,
- :read_pipeline,
- :read_commit_status,
- :read_container_image,
- :download_code
- ]
-
- # Allow to read builds by anonymous user if guests are allowed
- rules << :read_build if project.public_builds?
-
- # Allow to read issues by anonymous user if issue is not confidential
- rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
-
- rules - project_disabled_features_rules(project)
- else
- []
- end
- end
-
- 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
-
- def anonymous_group_abilities(subject)
- rules = []
-
- group = if subject.is_a?(Group)
- subject
- else
- subject.group
- end
-
- rules << :read_group if group.public?
-
- rules
- end
-
- def anonymous_personal_snippet_abilities(snippet)
- if snippet.public?
- [:read_personal_snippet]
- else
- []
- end
- end
-
- def anonymous_project_snippet_abilities(snippet)
- if snippet.public?
- [:read_project_snippet]
- else
- []
- end
- end
-
- def anonymous_user_abilities
- [:read_user] unless restricted_public_level?
- end
-
- def global_abilities(user)
- rules = []
- rules << :create_group if user.can_create_group
- rules << :read_users_list
- rules
- end
-
- def project_abilities(user, project)
- key = "/user/#{user.id}/project/#{project.id}"
-
- if RequestStore.active?
- RequestStore.store[key] ||= uncached_project_abilities(user, project)
- else
- uncached_project_abilities(user, project)
- end
- end
-
- def uncached_project_abilities(user, project)
- rules = []
- # Push abilities on the users team role
- rules.push(*project_team_rules(project.team, user))
-
- owner = user.admin? ||
- project.owner == user ||
- (project.group && project.group.has_owner?(user))
-
- if owner
- rules.push(*project_owner_rules)
- end
-
- if project.public? || (project.internal? && !user.external?)
- rules.push(*public_project_rules)
-
- # Allow to read builds for internal projects
- rules << :read_build if project.public_builds?
-
- unless owner || project.team.member?(user) || project_group_member?(project, user)
- rules << :request_access if project.request_access_enabled
- end
- end
-
- if project.archived?
- rules -= project_archived_rules
- end
-
- (rules - project_disabled_features_rules(project)).uniq
- end
-
- 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
- else
- []
- end
- end
-
- def public_project_rules
- @public_project_rules ||= project_guest_rules + [
- :download_code,
- :fork_project,
- :read_commit_status,
- :read_pipeline,
- :read_container_image
- ]
- end
-
- def project_guest_rules
- @project_guest_rules ||= [
- :read_project,
- :read_wiki,
- :read_issue,
- :read_board,
- :read_list,
- :read_label,
- :read_milestone,
- :read_project_snippet,
- :read_project_member,
- :read_merge_request,
- :read_note,
- :create_project,
- :create_issue,
- :create_note,
- :upload_file
- ]
- end
-
- def project_report_rules
- @project_report_rules ||= project_guest_rules + [
- :download_code,
- :fork_project,
- :create_project_snippet,
- :update_issue,
- :admin_issue,
- :admin_label,
- :admin_list,
- :read_commit_status,
- :read_build,
- :read_container_image,
- :read_pipeline,
- :read_environment,
- :read_deployment
- ]
- end
-
- def project_dev_rules
- @project_dev_rules ||= project_report_rules + [
- :admin_merge_request,
- :update_merge_request,
- :create_commit_status,
- :update_commit_status,
- :create_build,
- :update_build,
- :create_pipeline,
- :update_pipeline,
- :create_merge_request,
- :create_wiki,
- :push_code,
- :resolve_note,
- :create_container_image,
- :update_container_image,
- :create_environment,
- :create_deployment
- ]
- end
-
- def project_archived_rules
- @project_archived_rules ||= [
- :create_merge_request,
- :push_code,
- :push_code_to_protected_branches,
- :update_merge_request,
- :admin_merge_request
- ]
- end
-
- def project_master_rules
- @project_master_rules ||= project_dev_rules + [
- :push_code_to_protected_branches,
- :update_project_snippet,
- :update_environment,
- :update_deployment,
- :admin_milestone,
- :admin_project_snippet,
- :admin_project_member,
- :admin_merge_request,
- :admin_note,
- :admin_wiki,
- :admin_project,
- :admin_commit_status,
- :admin_build,
- :admin_container_image,
- :admin_pipeline,
- :admin_environment,
- :admin_deployment
- ]
- end
-
- def project_owner_rules
- @project_owner_rules ||= project_master_rules + [
- :change_namespace,
- :change_visibility_level,
- :rename_project,
- :remove_project,
- :archive_project,
- :remove_fork_project,
- :destroy_merge_request,
- :destroy_issue
- ]
- end
-
- 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.has_wiki?
- rules += named_abilities('wiki')
- end
-
- unless project.builds_enabled
- rules += named_abilities('build')
- rules += named_abilities('pipeline')
- rules += named_abilities('environment')
- rules += named_abilities('deployment')
- end
-
- unless project.container_registry_enabled
- rules += named_abilities('container_image')
- end
-
- rules
- end
-
- def group_abilities(user, group)
- rules = []
- rules << :read_group if can_read_group?(user, group)
-
- owner = user.admin? || group.has_owner?(user)
- master = owner || group.has_master?(user)
-
- # Only group masters and group owners can create new projects
- if master
- rules += [
- :create_projects,
- :admin_milestones
- ]
- end
-
- # Only group owner and administrators can admin group
- if owner
- rules += [
- :admin_group,
- :admin_namespace,
- :admin_group_member,
- :change_visibility_level
- ]
- end
-
- if group.public? || (group.internal? && !user.external?)
- rules << :request_access if group.request_access_enabled && group.users.exclude?(user)
- end
-
- rules.flatten
- end
-
- def can_read_group?(user, group)
- 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?
- end
-
+ # TODO: make this private and use the actual abilities stuff for this
def can_edit_note?(user, note)
return false if !note.editable? || !user.present?
return true if note.author == user || user.admin?
@@ -426,207 +45,23 @@ class Ability
end
end
- def namespace_abilities(user, namespace)
- rules = []
-
- # Only namespace owner and administrators can admin it
- if namespace.owner == user || user.admin?
- rules += [
- :create_projects,
- :admin_namespace
- ]
- end
-
- rules.flatten
- end
-
- [:issue, :merge_request].each do |name|
- define_method "#{name}_abilities" do |user, subject|
- rules = []
-
- if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
- rules += [
- :"read_#{name}",
- :"update_#{name}",
- ]
- end
-
- rules += project_abilities(user, subject.project)
- rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
- rules
- end
- end
-
- def note_abilities(user, note)
- rules = []
-
- if note.author == user
- rules += [
- :read_note,
- :update_note,
- :admin_note,
- :resolve_note
- ]
- end
-
- if note.respond_to?(:project) && note.project
- rules += project_abilities(user, note.project)
- end
-
- if note.for_merge_request? && note.noteable.author == user
- rules << :resolve_note
- end
-
- rules
- end
-
- def personal_snippet_abilities(user, snippet)
- rules = []
-
- if snippet.author == user
- rules += [
- :read_personal_snippet,
- :update_personal_snippet,
- :admin_personal_snippet
- ]
- end
-
- if snippet.public? || (snippet.internal? && !user.external?)
- rules << :read_personal_snippet
- end
-
- rules
+ def allowed?(user, action, subject)
+ allowed(user, subject).include?(action)
end
- 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
-
- def group_member_abilities(user, subject)
- rules = []
- target_user = subject.user
- group = subject.group
-
- unless group.last_owner?(target_user)
- can_manage = group_abilities(user, group).include?(:admin_group_member)
-
- if can_manage
- rules << :update_group_member
- rules << :destroy_group_member
- elsif user == target_user
- rules << :destroy_group_member
- end
- end
-
- rules
- end
-
- def project_member_abilities(user, subject)
- rules = []
- target_user = subject.user
- project = subject.project
-
- unless target_user == project.owner
- can_manage = project_abilities(user, project).include?(:admin_project_member)
-
- if can_manage
- rules << :update_project_member
- rules << :destroy_project_member
- elsif user == target_user
- rules << :destroy_project_member
- end
- end
-
- rules
- end
-
- 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
-
- 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
- %w(read create update admin).each do |rule|
- rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
- end
- rules
- end
-
- 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
+ def allowed(user, subject)
+ return uncached_allowed(user, subject) unless RequestStore.active?
- def user_abilities
- [:read_user]
- end
-
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << self
- abilities
- end
+ user_key = user ? user.id : 'anonymous'
+ subject_key = subject ? "#{subject.class.name}/#{subject.id}" : 'global'
+ key = "/ability/#{user_key}/#{subject_key}"
+ RequestStore[key] ||= uncached_allowed(user, subject).freeze
end
private
- def restricted_public_level?
- current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
- end
-
- def named_abilities(name)
- [
- :"read_#{name}",
- :"create_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
- end
-
- 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, Gitlab::Access::REPORTER)
- rules.delete(:admin_issue)
- rules.delete(:read_issue)
- rules.delete(:update_issue)
- end
-
- rules
- end
-
- def project_group_member?(project, user)
- project.group &&
- (
- project.group.members.exists?(user_id: user.id) ||
- project.group.requesters.exists?(user_id: user.id)
- )
+ def uncached_allowed(user, subject)
+ BasePolicy.class_for(subject).abilities(user, subject)
end
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index fd736d12359..a0b7b0dc2b5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -65,7 +65,7 @@ class Event < ActiveRecord::Base
elsif created_project?
true
elsif issue? || issue_note?
- Ability.abilities.allowed?(user, :read_issue, note? ? note_target : target)
+ Ability.allowed?(user, :read_issue, note? ? note_target : target)
else
((merge_request? || note?) && target.present?) || milestone?
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 3600fde866c..b0b1313f94a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -411,7 +411,7 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
- Ability.abilities.allowed?(current_user, :push_code, source_project) &&
+ Ability.allowed?(current_user, :push_code, source_project) &&
diff_head_commit == source_branch_head
end
diff --git a/app/models/user.rb b/app/models/user.rb
index ad3cfbc03e4..8f5958333d7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -460,16 +460,12 @@ class User < ActiveRecord::Base
can?(:create_group, nil)
end
- def abilities
- Ability.abilities
- end
-
def can_select_namespace?
several_namespaces? || admin
end
def can?(action, subject)
- abilities.allowed?(self, action, subject)
+ Ability.allowed?(self, action, subject)
end
def first_name
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
new file mode 100644
index 00000000000..118c100ca11
--- /dev/null
+++ b/app/policies/base_policy.rb
@@ -0,0 +1,116 @@
+class BasePolicy
+ class RuleSet
+ attr_reader :can_set, :cannot_set
+ def initialize(can_set, cannot_set)
+ @can_set = can_set
+ @cannot_set = cannot_set
+ end
+
+ def size
+ to_set.size
+ end
+
+ def self.empty
+ new(Set.new, Set.new)
+ end
+
+ def can?(ability)
+ @can_set.include?(ability) && !@cannot_set.include?(ability)
+ end
+
+ def include?(ability)
+ can?(ability)
+ end
+
+ def to_set
+ @can_set - @cannot_set
+ end
+
+ def merge(other)
+ @can_set.merge(other.can_set)
+ @cannot_set.merge(other.cannot_set)
+ end
+
+ def can!(*abilities)
+ @can_set.merge(abilities)
+ end
+
+ def cannot!(*abilities)
+ @cannot_set.merge(abilities)
+ end
+
+ def freeze
+ @can_set.freeze
+ @cannot_set.freeze
+ super
+ end
+ end
+
+ def self.abilities(user, subject)
+ new(user, subject).abilities
+ end
+
+ def self.class_for(subject)
+ return GlobalPolicy if subject.nil?
+
+ subject.class.ancestors.each do |klass|
+ next unless klass.name
+
+ begin
+ policy_class = "#{klass.name}Policy".constantize
+
+ # NOTE: the < operator here tests whether policy_class
+ # inherits from BasePolicy
+ return policy_class if policy_class < BasePolicy
+ rescue NameError
+ nil
+ end
+ end
+
+ raise "no policy for #{subject.class.name}"
+ end
+
+ attr_reader :user, :subject
+ def initialize(user, subject)
+ @user = user
+ @subject = subject
+ end
+
+ def abilities
+ return RuleSet.empty if @user && @user.blocked?
+ return anonymous_abilities if @user.nil?
+ collect_rules { rules }
+ end
+
+ def anonymous_abilities
+ collect_rules { anonymous_rules }
+ end
+
+ def anonymous_rules
+ rules
+ end
+
+ def delegate!(new_subject)
+ @rule_set.merge(Ability.allowed(@user, new_subject))
+ end
+
+ def can?(rule)
+ @rule_set.can?(rule)
+ end
+
+ def can!(*rules)
+ @rule_set.can!(*rules)
+ end
+
+ def cannot!(*rules)
+ @rule_set.cannot!(*rules)
+ end
+
+ private
+
+ def collect_rules(&b)
+ @rule_set = RuleSet.empty
+ yield
+ @rule_set
+ end
+end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
new file mode 100644
index 00000000000..2232e231cf8
--- /dev/null
+++ b/app/policies/ci/build_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+ class BuildPolicy < CommitStatusPolicy
+ def rules
+ super
+
+ # If we can't read build we should also not have that
+ # ability when looking at this in context of commit_status
+ %w(read create update admin).each do |rule|
+ cannot! :"#{rule}_commit_status" unless can? :"#{rule}_build"
+ end
+ end
+ end
+end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
new file mode 100644
index 00000000000..7edd383530d
--- /dev/null
+++ b/app/policies/ci/runner_policy.rb
@@ -0,0 +1,13 @@
+module Ci
+ class RunnerPolicy < BasePolicy
+ def rules
+ return unless @user
+
+ can! :assign_runner if @user.is_admin?
+
+ return if @subject.is_shared? || @subject.locked?
+
+ can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
+ end
+ end
+end
diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb
new file mode 100644
index 00000000000..593df738328
--- /dev/null
+++ b/app/policies/commit_status_policy.rb
@@ -0,0 +1,5 @@
+class CommitStatusPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
new file mode 100644
index 00000000000..163d070ff90
--- /dev/null
+++ b/app/policies/deployment_policy.rb
@@ -0,0 +1,5 @@
+class DeploymentPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
new file mode 100644
index 00000000000..f4219569161
--- /dev/null
+++ b/app/policies/environment_policy.rb
@@ -0,0 +1,5 @@
+class EnvironmentPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb
new file mode 100644
index 00000000000..d9e28bd107a
--- /dev/null
+++ b/app/policies/external_issue_policy.rb
@@ -0,0 +1,5 @@
+class ExternalIssuePolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
new file mode 100644
index 00000000000..3c2fbe6b56b
--- /dev/null
+++ b/app/policies/global_policy.rb
@@ -0,0 +1,8 @@
+class GlobalPolicy < BasePolicy
+ def rules
+ return unless @user
+
+ can! :create_group if @user.can_create_group
+ can! :read_users_list
+ end
+end
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
new file mode 100644
index 00000000000..62335527654
--- /dev/null
+++ b/app/policies/group_member_policy.rb
@@ -0,0 +1,19 @@
+class GroupMemberPolicy < BasePolicy
+ def rules
+ return unless @user
+
+ target_user = @subject.user
+ group = @subject.group
+
+ return if group.last_owner?(target_user)
+
+ can_manage = Ability.allowed?(@user, :admin_group_member, group)
+
+ if can_manage
+ can! :update_group_member
+ can! :destroy_group_member
+ elsif @user == target_user
+ can! :destroy_group_member
+ end
+ end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
new file mode 100644
index 00000000000..97ff6233968
--- /dev/null
+++ b/app/policies/group_policy.rb
@@ -0,0 +1,45 @@
+class GroupPolicy < BasePolicy
+ def rules
+ can! :read_group if @subject.public?
+ return unless @user
+
+ globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
+ member = @subject.users.include?(@user)
+ owner = @user.admin? || @subject.has_owner?(@user)
+ master = owner || @subject.has_master?(@user)
+
+ can_read = false
+ can_read ||= globally_viewable
+ can_read ||= member
+ can_read ||= @user.admin?
+ can_read ||= GroupProjectsFinder.new(@subject).execute(@user).any?
+ can! :read_group if can_read
+
+ # Only group masters and group owners can create new projects
+ if master
+ can! :create_projects
+ can! :admin_milestones
+ end
+
+ # Only group owner and administrators can admin group
+ if owner
+ can! :admin_group
+ can! :admin_namespace
+ can! :admin_group_member
+ can! :change_visibility_level
+ end
+
+ if globally_viewable && @subject.request_access_enabled && !member
+ can! :request_access
+ end
+ end
+
+ def can_read_group?
+ return true if @subject.public?
+ return true if @user.admin?
+ return true if @subject.internal? && !@user.external?
+ return true if @subject.users.include?(@user)
+
+ GroupProjectsFinder.new(@subject).execute(@user).any?
+ end
+end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
new file mode 100644
index 00000000000..c253f9a9399
--- /dev/null
+++ b/app/policies/issuable_policy.rb
@@ -0,0 +1,14 @@
+class IssuablePolicy < BasePolicy
+ def action_name
+ @subject.class.name.underscore
+ end
+
+ def rules
+ if @user && (@subject.author == @user || @subject.assignee == @user)
+ can! :"read_#{action_name}"
+ can! :"update_#{action_name}"
+ end
+
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
new file mode 100644
index 00000000000..bd1811a3c54
--- /dev/null
+++ b/app/policies/issue_policy.rb
@@ -0,0 +1,28 @@
+class IssuePolicy < IssuablePolicy
+ def issue
+ @subject
+ end
+
+ def rules
+ super
+
+ if @subject.confidential? && !can_read_confidential?
+ cannot! :read_issue
+ cannot! :admin_issue
+ cannot! :update_issue
+ cannot! :read_issue
+ end
+ end
+
+ private
+
+ def can_read_confidential?
+ return false unless @user
+ return true if @user.admin?
+ return true if @subject.author == @user
+ return true if @subject.assignee == @user
+ return true if @subject.project.team.member?(@user, Gitlab::Access::REPORTER)
+
+ false
+ end
+end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
new file mode 100644
index 00000000000..bc3afc626fb
--- /dev/null
+++ b/app/policies/merge_request_policy.rb
@@ -0,0 +1,3 @@
+class MergeRequestPolicy < IssuablePolicy
+ # pass
+end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
new file mode 100644
index 00000000000..29bb357e00a
--- /dev/null
+++ b/app/policies/namespace_policy.rb
@@ -0,0 +1,10 @@
+class NamespacePolicy < BasePolicy
+ def rules
+ return unless @user
+
+ if @subject.owner == @user || @user.admin?
+ can! :create_projects
+ can! :admin_namespace
+ end
+ end
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
new file mode 100644
index 00000000000..83847466ee2
--- /dev/null
+++ b/app/policies/note_policy.rb
@@ -0,0 +1,19 @@
+class NotePolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+
+ return unless @user
+
+ if @subject.author == @user
+ can! :read_note
+ can! :update_note
+ can! :admin_note
+ can! :resolve_note
+ end
+
+ if @subject.for_merge_request? &&
+ @subject.noteable.author == @user
+ can! :resolve_note
+ end
+ end
+end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
new file mode 100644
index 00000000000..46c5aa1a5be
--- /dev/null
+++ b/app/policies/personal_snippet_policy.rb
@@ -0,0 +1,16 @@
+class PersonalSnippetPolicy < BasePolicy
+ def rules
+ can! :read_personal_snippet if @subject.public?
+ return unless @user
+
+ if @subject.author == @user
+ can! :read_personal_snippet
+ can! :update_personal_snippet
+ can! :admin_personal_snippet
+ end
+
+ if @subject.internal? && !@user.external?
+ can! :read_personal_snippet
+ end
+ end
+end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
new file mode 100644
index 00000000000..1c038dddd4b
--- /dev/null
+++ b/app/policies/project_member_policy.rb
@@ -0,0 +1,22 @@
+class ProjectMemberPolicy < BasePolicy
+ def rules
+ # anonymous users have no abilities here
+ return unless @user
+
+ target_user = @subject.user
+ project = @subject.project
+
+ return if target_user == project.owner
+
+ can_manage = Ability.allowed?(@user, :admin_project_member, project)
+
+ if can_manage
+ can! :update_project_member
+ can! :destroy_project_member
+ end
+
+ if @user == target_user
+ can! :destroy_project_member
+ end
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
new file mode 100644
index 00000000000..15a9f2f0dca
--- /dev/null
+++ b/app/policies/project_policy.rb
@@ -0,0 +1,224 @@
+class ProjectPolicy < BasePolicy
+ def rules
+ team_access!(user)
+
+ owner = user.admin? ||
+ project.owner == user ||
+ (project.group && project.group.has_owner?(user))
+
+ owner_access! if owner
+
+ if project.public? || (project.internal? && !user.external?)
+ guest_access!
+ public_access!
+
+ # Allow to read builds for internal projects
+ can! :read_build if project.public_builds?
+
+ if project.request_access_enabled &&
+ !(owner || project.team.member?(user) || project_group_member?(user))
+ can! :request_access
+ end
+ end
+
+ archived_access! if project.archived?
+
+ disabled_features!
+ end
+
+ def project
+ @subject
+ end
+
+ def guest_access!
+ can! :read_project
+ can! :read_board
+ can! :read_list
+ can! :read_wiki
+ can! :read_issue
+ can! :read_label
+ can! :read_milestone
+ can! :read_project_snippet
+ can! :read_project_member
+ can! :read_merge_request
+ can! :read_note
+ can! :create_project
+ can! :create_issue
+ can! :create_note
+ can! :upload_file
+ end
+
+ def reporter_access!
+ can! :download_code
+ can! :fork_project
+ can! :create_project_snippet
+ can! :update_issue
+ can! :admin_issue
+ can! :admin_label
+ can! :admin_list
+ can! :read_commit_status
+ can! :read_build
+ can! :read_container_image
+ can! :read_pipeline
+ can! :read_environment
+ can! :read_deployment
+ end
+
+ def developer_access!
+ can! :admin_merge_request
+ can! :update_merge_request
+ can! :create_commit_status
+ can! :update_commit_status
+ can! :create_build
+ can! :update_build
+ can! :create_pipeline
+ can! :update_pipeline
+ can! :create_merge_request
+ can! :create_wiki
+ can! :push_code
+ can! :resolve_note
+ can! :create_container_image
+ can! :update_container_image
+ can! :create_environment
+ can! :create_deployment
+ end
+
+ def master_access!
+ can! :push_code_to_protected_branches
+ can! :update_project_snippet
+ can! :update_environment
+ can! :update_deployment
+ can! :admin_milestone
+ can! :admin_project_snippet
+ can! :admin_project_member
+ can! :admin_merge_request
+ can! :admin_note
+ can! :admin_wiki
+ can! :admin_project
+ can! :admin_commit_status
+ can! :admin_build
+ can! :admin_container_image
+ can! :admin_pipeline
+ can! :admin_environment
+ can! :admin_deployment
+ end
+
+ def public_access!
+ can! :download_code
+ can! :fork_project
+ can! :read_commit_status
+ can! :read_pipeline
+ can! :read_container_image
+ end
+
+ def owner_access!
+ guest_access!
+ reporter_access!
+ developer_access!
+ master_access!
+ can! :change_namespace
+ can! :change_visibility_level
+ can! :rename_project
+ can! :remove_project
+ can! :archive_project
+ can! :remove_fork_project
+ can! :destroy_merge_request
+ can! :destroy_issue
+ end
+
+ # Push abilities on the users team role
+ def team_access!(user)
+ access = project.team.max_member_access(user.id)
+
+ guest_access! if access >= Gitlab::Access::GUEST
+ reporter_access! if access >= Gitlab::Access::REPORTER
+ developer_access! if access >= Gitlab::Access::DEVELOPER
+ master_access! if access >= Gitlab::Access::MASTER
+ end
+
+ def archived_access!
+ cannot! :create_merge_request
+ cannot! :push_code
+ cannot! :push_code_to_protected_branches
+ cannot! :update_merge_request
+ cannot! :admin_merge_request
+ end
+
+ def disabled_features!
+ unless project.issues_enabled
+ cannot!(*named_abilities(:issue))
+ end
+
+ unless project.merge_requests_enabled
+ cannot!(*named_abilities(:merge_request))
+ end
+
+ unless project.issues_enabled || project.merge_requests_enabled
+ cannot!(*named_abilities(:label))
+ cannot!(*named_abilities(:milestone))
+ end
+
+ unless project.snippets_enabled
+ cannot!(*named_abilities(:project_snippet))
+ end
+
+ unless project.has_wiki?
+ cannot!(*named_abilities(:wiki))
+ end
+
+ unless project.builds_enabled
+ cannot!(*named_abilities(:build))
+ cannot!(*named_abilities(:pipeline))
+ cannot!(*named_abilities(:environment))
+ cannot!(*named_abilities(:deployment))
+ end
+
+ unless project.container_registry_enabled
+ cannot!(*named_abilities(:container_image))
+ end
+ end
+
+ def anonymous_rules
+ return unless project.public?
+
+ can! :read_project
+ can! :read_board
+ can! :read_list
+ can! :read_wiki
+ can! :read_label
+ can! :read_milestone
+ can! :read_project_snippet
+ can! :read_project_member
+ can! :read_merge_request
+ can! :read_note
+ can! :read_pipeline
+ can! :read_commit_status
+ can! :read_container_image
+ can! :download_code
+
+ # NOTE: may be overridden by IssuePolicy
+ can! :read_issue
+
+ # Allow to read builds by anonymous user if guests are allowed
+ can! :read_build if project.public_builds?
+
+ disabled_features!
+ end
+
+ def project_group_member?(user)
+ project.group &&
+ (
+ project.group.members.exists?(user_id: user.id) ||
+ project.group.requesters.exists?(user_id: user.id)
+ )
+ end
+
+ def named_abilities(name)
+ [
+ :"read_#{name}",
+ :"create_#{name}",
+ :"update_#{name}",
+ :"admin_#{name}"
+ ]
+ end
+end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
new file mode 100644
index 00000000000..57acccfafd9
--- /dev/null
+++ b/app/policies/project_snippet_policy.rb
@@ -0,0 +1,20 @@
+class ProjectSnippetPolicy < BasePolicy
+ def rules
+ can! :read_project_snippet if @subject.public?
+ return unless @user
+
+ if @user && @subject.author == @user || @user.admin?
+ can! :read_project_snippet
+ can! :update_project_snippet
+ can! :admin_project_snippet
+ end
+
+ if @subject.internal? && !@user.external?
+ can! :read_project_snippet
+ end
+
+ if @subject.private? && @subject.project.team.member?(@user)
+ can! :read_project_snippet
+ end
+ end
+end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
new file mode 100644
index 00000000000..03a2499e263
--- /dev/null
+++ b/app/policies/user_policy.rb
@@ -0,0 +1,11 @@
+class UserPolicy < BasePolicy
+ include Gitlab::CurrentSettings
+
+ def rules
+ can! :read_user if @user || !restricted_public_level?
+ end
+
+ def restricted_public_level?
+ current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
+ end
+end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 0d55ba5a981..0c208150fb8 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -7,12 +7,8 @@ class BaseService
@project, @current_user, @params = project, user, params.dup
end
- def abilities
- Ability.abilities
- end
-
def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
+ Ability.allowed?(object, action, subject)
end
def notification_service
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9d8b8d737a9..f981ec0dbfe 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -30,7 +30,7 @@ module API
# Example Request:
# POST /groups
post do
- authorize! :create_group, current_user
+ authorize! :create_group
required_attributes! [:name, :path]
attrs = attributes_for_keys [:name, :path, :description, :visibility_level]
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index da4b1bf9902..6a20ba95a79 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -129,7 +129,7 @@ module API
forbidden! unless current_user.is_admin?
end
- def authorize!(action, subject)
+ def authorize!(action, subject = nil)
forbidden! unless can?(current_user, action, subject)
end
@@ -148,7 +148,7 @@ module API
end
def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
+ Ability.allowed?(object, action, subject)
end
# Checks the occurrences of required attributes, each attribute must be present in the params hash
@@ -408,14 +408,6 @@ module API
links.join(', ')
end
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
- end
-
def secret_token
File.read(Gitlab.config.gitlab_shell.secret_file).chomp
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 6cf218aaa0d..e8e03e4a98f 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -211,7 +211,7 @@ module Banzai
end
def can?(user, permission, subject)
- Ability.abilities.allowed?(user, permission, subject)
+ Ability.allowed?(user, permission, subject)
end
def find_projects_for_hash_keys(hash)
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb
index d0ad5e26dbd..2896636db5a 100644
--- a/spec/controllers/projects/boards/issues_controller_spec.rb
+++ b/spec/controllers/projects/boards/issues_controller_spec.rb
@@ -41,8 +41,8 @@ describe Projects::Boards::IssuesController do
context 'with unauthorized user' do
before do
- allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
- allow(Ability.abilities).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
+ allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
+ allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
end
it 'returns a successful 403 response' do
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb
index 261f35f28ed..d687dea3c3b 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/projects/boards/lists_controller_spec.rb
@@ -35,8 +35,8 @@ describe Projects::Boards::ListsController do
context 'with unauthorized user' do
before do
- allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
- allow(Ability.abilities).to receive(:allowed?).with(user, :read_list, project).and_return(false)
+ allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
+ allow(Ability).to receive(:allowed?).with(user, :read_list, project).and_return(false)
end
it 'returns a forbidden 403 response' do
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 75a6d39e82c..6f6e608e1f3 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -23,8 +23,8 @@ describe Projects::BoardsController do
context 'with unauthorized user' do
before do
- allow(Ability.abilities).to receive(:allowed?).with(user, :read_project, project).and_return(true)
- allow(Ability.abilities).to receive(:allowed?).with(user, :read_board, project).and_return(false)
+ allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
+ allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false)
end
it 'returns a successful 404 response' do
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index ac9c66e2663..9095d2b1345 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'returns the nodes if the attribute value equals the current project ID' do
link['data-project'] = project.id.to_s
- expect(Ability.abilities).not_to receive(:allowed?)
+ expect(Ability).not_to receive(:allowed?)
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
@@ -39,7 +39,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
link['data-project'] = other_project.id.to_s
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(true)
@@ -57,7 +57,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
link['data-project'] = other_project.id.to_s
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(false)
@@ -221,7 +221,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'delegates the permissions check to the Ability class' do
user = double(:user)
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, project)
subject.can?(user, :read_project, project)
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 9a82891297d..4e7f82a6e09 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -82,7 +82,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
it 'returns the nodes if the user can read the group' do
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_group, group).
and_return(true)
@@ -90,7 +90,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
end
it 'returns an empty Array if the user can not read the group' do
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_group, group).
and_return(false)
@@ -103,7 +103,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
it 'returns the nodes if the attribute value equals the current project ID' do
link['data-project'] = project.id.to_s
- expect(Ability.abilities).not_to receive(:allowed?)
+ expect(Ability).not_to receive(:allowed?)
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
@@ -113,7 +113,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
link['data-project'] = other_project.id.to_s
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(true)
@@ -125,7 +125,7 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
link['data-project'] = other_project.id.to_s
- expect(Ability.abilities).to receive(:allowed?).
+ expect(Ability).to receive(:allowed?).
with(user, :read_project, other_project).
and_return(false)
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index c50ca38bdd9..b05510342bc 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -171,70 +171,6 @@ describe Ability, lib: true do
end
end
- shared_examples_for ".project_abilities" do |enable_request_store|
- before do
- RequestStore.begin! if enable_request_store
- end
-
- after do
- if enable_request_store
- RequestStore.end!
- RequestStore.clear!
- end
- end
-
- describe '.project_abilities' do
- let!(:project) { create(:empty_project, :public) }
- let!(:user) { create(:user) }
-
- it 'returns permissions for admin user' do
- admin = create(:admin)
-
- results = described_class.project_abilities(admin, project)
-
- expect(results.count).to eq(68)
- end
-
- it 'returns permissions for an owner' do
- results = described_class.project_abilities(project.owner, project)
-
- expect(results.count).to eq(68)
- end
-
- it 'returns permissions for a master' do
- project.team << [user, :master]
-
- results = described_class.project_abilities(user, project)
-
- expect(results.count).to eq(60)
- end
-
- it 'returns permissions for a developer' do
- project.team << [user, :developer]
-
- results = described_class.project_abilities(user, project)
-
- expect(results.count).to eq(44)
- end
-
- it 'returns permissions for a guest' do
- project.team << [user, :guest]
-
- results = described_class.project_abilities(user, project)
-
- expect(results.count).to eq(21)
- end
- end
- end
-
- describe '.project_abilities with RequestStore' do
- it_behaves_like ".project_abilities", true
- end
-
- describe '.project_abilities without RequestStore' do
- it_behaves_like ".project_abilities", false
- end
-
describe '.issues_readable_by_user' do
context 'with an admin user' do
it 'returns all given issues' do
@@ -286,12 +222,12 @@ describe Ability, lib: true do
describe '.project_disabled_features_rules' do
let(:project) { build(:project) }
- subject { described_class.project_disabled_features_rules(project) }
+ subject { described_class.allowed(project.owner, project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
expect(project).to receive(:has_wiki?).and_return(false)
- expect(subject).to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
+ expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
end
end
end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 913d74645a7..be57957b569 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -71,9 +71,6 @@ describe ProjectMember, models: true do
describe :import_team do
before do
- @abilities = Six.new
- @abilities << Ability
-
@project_1 = create :project
@project_2 = create :project
@@ -92,8 +89,8 @@ describe ProjectMember, models: true do
it { expect(@project_2.users).to include(@user_1) }
it { expect(@project_2.users).to include(@user_2) }
- it { expect(@abilities.allowed?(@user_1, :create_project, @project_2)).to be_truthy }
- it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
+ it { expect(Ability.allowed?(@user_1, :create_project, @project_2)).to be_truthy }
+ it { expect(Ability.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
end
describe 'project 1 should not be changed' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 9e8ae07e0b2..e6b6e7c0634 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -85,8 +85,6 @@ describe Note, models: true do
@u1 = create(:user)
@u2 = create(:user)
@u3 = create(:user)
- @abilities = Six.new
- @abilities << Ability
end
describe 'read' do
@@ -95,9 +93,9 @@ describe Note, models: true do
@p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST)
end
- it { expect(@abilities.allowed?(@u1, :read_note, @p1)).to be_falsey }
- it { expect(@abilities.allowed?(@u2, :read_note, @p1)).to be_truthy }
- it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey }
+ it { expect(Ability.allowed?(@u1, :read_note, @p1)).to be_falsey }
+ it { expect(Ability.allowed?(@u2, :read_note, @p1)).to be_truthy }
+ it { expect(Ability.allowed?(@u3, :read_note, @p1)).to be_falsey }
end
describe 'write' do
@@ -106,9 +104,9 @@ describe Note, models: true do
@p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER)
end
- it { expect(@abilities.allowed?(@u1, :create_note, @p1)).to be_falsey }
- it { expect(@abilities.allowed?(@u2, :create_note, @p1)).to be_truthy }
- it { expect(@abilities.allowed?(@u3, :create_note, @p1)).to be_falsey }
+ it { expect(Ability.allowed?(@u1, :create_note, @p1)).to be_falsey }
+ it { expect(Ability.allowed?(@u2, :create_note, @p1)).to be_truthy }
+ it { expect(Ability.allowed?(@u3, :create_note, @p1)).to be_falsey }
end
describe 'admin' do
@@ -118,9 +116,9 @@ describe Note, models: true do
@p2.project_members.create(user: @u3, access_level: ProjectMember::MASTER)
end
- it { expect(@abilities.allowed?(@u1, :admin_note, @p1)).to be_falsey }
- it { expect(@abilities.allowed?(@u2, :admin_note, @p1)).to be_truthy }
- it { expect(@abilities.allowed?(@u3, :admin_note, @p1)).to be_falsey }
+ it { expect(Ability.allowed?(@u1, :admin_note, @p1)).to be_falsey }
+ it { expect(Ability.allowed?(@u2, :admin_note, @p1)).to be_truthy }
+ it { expect(Ability.allowed?(@u3, :admin_note, @p1)).to be_falsey }
end
end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
deleted file mode 100644
index 36379074ea0..00000000000
--- a/spec/models/project_security_spec.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-require 'spec_helper'
-
-describe Project, models: true do
- describe 'authorization' do
- before do
- @p1 = create(:project)
-
- @u1 = create(:user)
- @u2 = create(:user)
- @u3 = create(:user)
- @u4 = @p1.owner
-
- @abilities = Six.new
- @abilities << Ability
- end
-
- let(:guest_actions) { Ability.project_guest_rules }
- let(:report_actions) { Ability.project_report_rules }
- let(:dev_actions) { Ability.project_dev_rules }
- let(:master_actions) { Ability.project_master_rules }
- let(:owner_actions) { Ability.project_owner_rules }
-
- describe "Non member rules" do
- it "denies for non-project users any actions" do
- owner_actions.each do |action|
- expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey
- end
- end
- end
-
- describe "Guest Rules" do
- before do
- @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::GUEST)
- end
-
- it "allows for project user any guest actions" do
- guest_actions.each do |action|
- expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
- end
- end
- end
-
- describe "Report Rules" do
- before do
- @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
- end
-
- it "allows for project user any report actions" do
- report_actions.each do |action|
- expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
- end
- end
- end
-
- describe "Developer Rules" do
- before do
- @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
- @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::DEVELOPER)
- end
-
- it "denies for developer master-specific actions" do
- [dev_actions - report_actions].each do |action|
- expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
- end
- end
-
- it "allows for project user any dev actions" do
- dev_actions.each do |action|
- expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
- end
- end
- end
-
- describe "Master Rules" do
- before do
- @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
- @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
- end
-
- it "denies for developer master-specific actions" do
- [master_actions - dev_actions].each do |action|
- expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
- end
- end
-
- it "allows for project user any master actions" do
- master_actions.each do |action|
- expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
- end
- end
- end
-
- describe "Owner Rules" do
- before do
- @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER)
- @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
- end
-
- it "denies for masters admin-specific actions" do
- [owner_actions - master_actions].each do |action|
- expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
- end
- end
-
- it "allows for project owner any admin actions" do
- owner_actions.each do |action|
- expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy
- end
- end
- end
- end
-end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
new file mode 100644
index 00000000000..eda1cafd65e
--- /dev/null
+++ b/spec/policies/project_policy_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe ProjectPolicy, models: true do
+ let(:project) { create(:empty_project, :public) }
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:dev) { create(:user) }
+ let(:master) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:admin) { create(:admin) }
+
+ let(:users_ordered_by_permissions) do
+ [nil, guest, reporter, dev, master, owner, admin]
+ end
+
+ let(:users_permissions) do
+ users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size }
+ end
+
+ before do
+ project.team << [guest, :guest]
+ project.team << [master, :master]
+ project.team << [dev, :developer]
+ project.team << [reporter, :reporter]
+
+ group = create(:group)
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::MASTER)
+ group.add_owner(owner)
+ end
+
+ it 'returns increasing permissions for each level' do
+ expect(users_permissions).to eq(users_permissions.sort.uniq)
+ end
+end