Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2017-07-04 00:15:27 +0300
committerLin Jen-Shin <godfat@godfat.org>2017-07-04 00:15:27 +0300
commit39573c6dde39de2345f100586c2c10f74187f6c1 (patch)
treeb98c5d4b2e211397450dad6009bf97584f772ce5 /app/policies
parent23bfd8c13c803f4efdb9eaf8e6e3c1ffd17640e8 (diff)
parent049d4baed0f3532359feb729c5f0938d3d4518ef (diff)
Merge remote-tracking branch 'upstream/master' into 30634-protected-pipeline
* upstream/master: (119 commits) Speed up operations performed by gitlab-shell Change the force flag to a keyword argument add image - issue boards - moving card copyedit == ee !2296 Reset @full_path to nil when cache expires Replace existing runner links with icons and tooltips, move into btn-group. add margin between captcha and register button Eagerly create a milestone that is used in a feature spec Adjust readme repo width Resolve "Issue Board -> "Remove from board" button when viewing an issue gives js error and fails" Set force_remove_source_branch default to false. Fix rubocop offenses Make entrypoint and command keys to be array of strings Add issuable-list class to shared mr/issue lists to fix new responsive layout New navigation breadcrumbs Restore timeago translations in renderTimeago. Fix curl example paths (missing the 'files' segment) Automatically hide sidebar on smaller screens Fix typo in IssuesFinder comment Make Project#ensure_repository force create a repo ...
Diffstat (limited to 'app/policies')
-rw-r--r--app/policies/base_policy.rb132
-rw-r--r--app/policies/ci/build_policy.rb29
-rw-r--r--app/policies/ci/pipeline_policy.rb23
-rw-r--r--app/policies/ci/runner_policy.rb15
-rw-r--r--app/policies/ci/trigger_policy.rb21
-rw-r--r--app/policies/commit_status_policy.rb6
-rw-r--r--app/policies/deploy_key_policy.rb14
-rw-r--r--app/policies/deployment_policy.rb4
-rw-r--r--app/policies/environment_policy.rb16
-rw-r--r--app/policies/external_issue_policy.rb4
-rw-r--r--app/policies/global_policy.rb46
-rw-r--r--app/policies/group_label_policy.rb4
-rw-r--r--app/policies/group_member_policy.rb29
-rw-r--r--app/policies/group_policy.rb96
-rw-r--r--app/policies/issuable_policy.rb19
-rw-r--r--app/policies/issue_policy.rb26
-rw-r--r--app/policies/namespace_policy.rb12
-rw-r--r--app/policies/nil_policy.rb3
-rw-r--r--app/policies/note_policy.rb31
-rw-r--r--app/policies/personal_snippet_policy.rb41
-rw-r--r--app/policies/project_label_policy.rb4
-rw-r--r--app/policies/project_member_policy.rb26
-rw-r--r--app/policies/project_policy.rb574
-rw-r--r--app/policies/project_snippet_policy.rb64
-rw-r--r--app/policies/user_policy.rb25
25 files changed, 607 insertions, 657 deletions
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 623424c63e0..191c2e78a08 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -1,127 +1,13 @@
-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
+require_dependency 'declarative_policy'
- delegate :size, to: :to_set
+class BasePolicy < DeclarativePolicy::Base
+ desc "User is an instance admin"
+ with_options scope: :user, score: 0
+ condition(:admin) { @user&.admin? }
- def self.empty
- new(Set.new, Set.new)
- end
+ with_options scope: :user, score: 0
+ condition(:external_user) { @user.nil? || @user.external? }
- def self.none
- empty.freeze
- 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 == :global
- raise ArgumentError, 'no policy for nil' if subject.nil?
-
- if subject.class.try(:presenter?)
- subject = subject.subject
- end
-
- 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.none 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 rules
- raise NotImplementedError
- 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
+ with_options scope: :user, score: 0
+ condition(:can_create_group) { @user&.can_create_group }
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 85245528602..129ed756477 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,30 +1,11 @@
module Ci
class BuildPolicy < CommitStatusPolicy
- alias_method :build, :subject
-
- 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
-
- if can?(:update_build) && !can_user_update?
- cannot! :update_build
- end
+ condition(:user_cannot_update) do
+ !::Gitlab::UserAccess
+ .new(@user, project: @subject.project)
+ .can_push_or_merge_to_branch?(@subject.ref)
end
- private
-
- def can_user_update?
- user_access.can_push_or_merge_to_branch?(build.ref)
- end
-
- def user_access
- @user_access ||= ::Gitlab::UserAccess
- .new(user, project: build.project)
- end
+ rule { user_cannot_update }.prevent :update_build
end
end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index e71cc358353..73b5a40c7fc 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -1,24 +1,13 @@
module Ci
class PipelinePolicy < BasePolicy
- alias_method :pipeline, :subject
+ delegate { pipeline.project }
- def rules
- delegate! pipeline.project
-
- if can?(:update_pipeline) && !can_user_update?
- cannot! :update_pipeline
- end
+ condition(:user_cannot_update) do
+ !::Gitlab::UserAccess
+ .new(@user, project: @subject.project)
+ .can_push_or_merge_to_branch?(@subject.ref)
end
- private
-
- def can_user_update?
- user_access.can_push_or_merge_to_branch?(pipeline.ref)
- end
-
- def user_access
- @user_access ||= ::Gitlab::UserAccess
- .new(user, project: pipeline.project)
- end
+ rule { user_cannot_update }.prevent :update_pipeline
end
end
diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 416d93ffe63..7dff8470e23 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -1,13 +1,16 @@
module Ci
class RunnerPolicy < BasePolicy
- def rules
- return unless @user
+ with_options scope: :subject, score: 0
+ condition(:shared) { @subject.is_shared? }
- can! :assign_runner if @user.admin?
+ with_options scope: :subject, score: 0
+ condition(:locked, scope: :subject) { @subject.locked? }
- return if @subject.is_shared? || @subject.locked?
+ condition(:authorized_runner) { @user.ci_authorized_runners.include?(@subject) }
- can! :assign_runner if @user.ci_authorized_runners.include?(@subject)
- end
+ rule { anonymous }.prevent_all
+ rule { admin | authorized_runner }.enable :assign_runner
+ rule { ~admin & shared }.prevent :assign_runner
+ rule { ~admin & locked }.prevent :assign_runner
end
end
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
index c90c9ac0583..5592ac30812 100644
--- a/app/policies/ci/trigger_policy.rb
+++ b/app/policies/ci/trigger_policy.rb
@@ -1,13 +1,16 @@
module Ci
class TriggerPolicy < BasePolicy
- def rules
- delegate! @subject.project
-
- if can?(:admin_build)
- can! :admin_trigger if @subject.owner.blank? ||
- @subject.owner == @user
- can! :manage_trigger
- end
- end
+ delegate { @subject.project }
+
+ with_options scope: :subject, score: 0
+ condition(:legacy) { @subject.legacy? }
+
+ with_score 0
+ condition(:is_owner) { @user && @subject.owner_id == @user.id }
+
+ rule { ~can?(:admin_build) }.prevent :admin_trigger
+ rule { legacy | is_owner }.enable :admin_trigger
+
+ rule { can?(:admin_build) }.enable :manage_trigger
end
end
diff --git a/app/policies/commit_status_policy.rb b/app/policies/commit_status_policy.rb
index 593df738328..24b2a4cc7fd 100644
--- a/app/policies/commit_status_policy.rb
+++ b/app/policies/commit_status_policy.rb
@@ -1,5 +1,7 @@
class CommitStatusPolicy < BasePolicy
- def rules
- delegate! @subject.project
+ delegate { @subject.project }
+
+ %w[read create update admin].each do |action|
+ rule { ~can?(:"#{action}_commit_status") }.prevent :"#{action}_build"
end
end
diff --git a/app/policies/deploy_key_policy.rb b/app/policies/deploy_key_policy.rb
index ebab213e6be..62a22a59be6 100644
--- a/app/policies/deploy_key_policy.rb
+++ b/app/policies/deploy_key_policy.rb
@@ -1,11 +1,11 @@
class DeployKeyPolicy < BasePolicy
- def rules
- return unless @user
+ with_options scope: :subject, score: 0
+ condition(:private_deploy_key) { @subject.private? }
- can! :update_deploy_key if @user.admin?
+ condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
- if @subject.private? && @user.project_deploy_keys.exists?(id: @subject.id)
- can! :update_deploy_key
- end
- end
+ rule { anonymous }.prevent_all
+
+ rule { admin }.enable :update_deploy_key
+ rule { private_deploy_key & has_deploy_key }.enable :update_deploy_key
end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
index 163d070ff90..62b63b9f87b 100644
--- a/app/policies/deployment_policy.rb
+++ b/app/policies/deployment_policy.rb
@@ -1,5 +1,3 @@
class DeploymentPolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb
index 2fa15e64562..375a5535359 100644
--- a/app/policies/environment_policy.rb
+++ b/app/policies/environment_policy.rb
@@ -1,17 +1,9 @@
class EnvironmentPolicy < BasePolicy
- alias_method :environment, :subject
+ delegate { @subject.project }
- def rules
- delegate! environment.project
-
- if can?(:create_deployment) && environment.stop_action?
- can! :stop_environment if can_play_stop_action?
- end
+ condition(:stop_action_allowed) do
+ @subject.stop_action? && can?(:update_build, @subject.stop_action)
end
- private
-
- def can_play_stop_action?
- Ability.allowed?(user, :update_build, environment.stop_action)
- end
+ rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment
end
diff --git a/app/policies/external_issue_policy.rb b/app/policies/external_issue_policy.rb
index d9e28bd107a..e031b38078c 100644
--- a/app/policies/external_issue_policy.rb
+++ b/app/policies/external_issue_policy.rb
@@ -1,5 +1,3 @@
class ExternalIssuePolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 2683aaad981..535faa922dd 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -1,16 +1,40 @@
class GlobalPolicy < BasePolicy
- def rules
- return unless @user
+ desc "User is blocked"
+ with_options scope: :user, score: 0
+ condition(:blocked) { @user.blocked? }
- can! :create_group if @user.can_create_group
- can! :read_users_list
+ desc "User is an internal user"
+ with_options scope: :user, score: 0
+ condition(:internal) { @user.internal? }
- unless @user.blocked? || @user.internal?
- can! :log_in unless @user.access_locked?
- can! :access_api
- can! :access_git
- can! :receive_notifications
- can! :use_quick_actions
- end
+ desc "User's access has been locked"
+ with_options scope: :user, score: 0
+ condition(:access_locked) { @user.access_locked? }
+
+ rule { anonymous }.prevent_all
+
+ rule { default }.policy do
+ enable :read_users_list
+ enable :log_in
+ enable :access_api
+ enable :access_git
+ enable :receive_notifications
+ enable :use_quick_actions
+ end
+
+ rule { blocked | internal }.policy do
+ prevent :log_in
+ prevent :access_api
+ prevent :access_git
+ prevent :receive_notifications
+ prevent :use_quick_actions
+ end
+
+ rule { can_create_group }.policy do
+ enable :create_group
+ end
+
+ rule { access_locked }.policy do
+ prevent :log_in
end
end
diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb
index 7b34aa182eb..e3dd3296699 100644
--- a/app/policies/group_label_policy.rb
+++ b/app/policies/group_label_policy.rb
@@ -1,5 +1,3 @@
class GroupLabelPolicy < BasePolicy
- def rules
- delegate! @subject.group
- end
+ delegate { @subject.group }
end
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
index 5a3fe814b77..23dd0d7cd23 100644
--- a/app/policies/group_member_policy.rb
+++ b/app/policies/group_member_policy.rb
@@ -1,25 +1,22 @@
class GroupMemberPolicy < BasePolicy
- def rules
- return unless @user
+ delegate :group
- target_user = @subject.user
- group = @subject.group
+ with_scope :subject
+ condition(:last_owner) { @subject.group.last_owner?(@subject.user) }
- return if group.last_owner?(target_user)
+ desc "Membership is users' own"
+ with_score 0
+ condition(:is_target_user) { @user && @subject.user_id == @user.id }
- can_manage = Ability.allowed?(@user, :admin_group_member, group)
+ rule { anonymous }.prevent_all
+ rule { last_owner }.prevent_all
- if can_manage
- can! :update_group_member
- can! :destroy_group_member
- elsif @user == target_user
- can! :destroy_group_member
- end
-
- additional_rules!
+ rule { can?(:admin_group_member) }.policy do
+ enable :update_group_member
+ enable :destroy_group_member
end
- def additional_rules!
- # This is meant to be overriden in EE
+ rule { is_target_user }.policy do
+ enable :destroy_group_member
end
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index fb07298c6c2..dcb37416ca3 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -1,50 +1,58 @@
class GroupPolicy < BasePolicy
- def rules
- can! :read_group if @subject.public?
- return unless @user
-
- globally_viewable = @subject.public? || (@subject.internal? && !@user.external?)
- access_level = @subject.max_member_access_for_user(@user)
- owner = access_level >= GroupMember::OWNER
- master = access_level >= GroupMember::MASTER
- reporter = access_level >= GroupMember::REPORTER
-
- can_read = false
- can_read ||= globally_viewable
- can_read ||= access_level >= GroupMember::GUEST
- can_read ||= GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
- can! :read_group if can_read
-
- if reporter
- can! :admin_label
- end
-
- # 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
- can! :create_subgroup if @user.can_create_group
- end
-
- if globally_viewable && @subject.request_access_enabled && access_level == GroupMember::NO_ACCESS
- can! :request_access
- end
- end
+ desc "Group is public"
+ with_options scope: :subject, score: 0
+ condition(:public_group) { @subject.public? }
+
+ with_score 0
+ condition(:logged_in_viewable) { @user && @subject.internal? && !@user.external? }
+
+ condition(:has_access) { access_level != GroupMember::NO_ACCESS }
- 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)
+ condition(:guest) { access_level >= GroupMember::GUEST }
+ condition(:owner) { access_level >= GroupMember::OWNER }
+ condition(:master) { access_level >= GroupMember::MASTER }
+ condition(:reporter) { access_level >= GroupMember::REPORTER }
+ condition(:has_projects) do
GroupProjectsFinder.new(group: @subject, current_user: @user).execute.any?
end
+
+ with_options scope: :subject, score: 0
+ condition(:request_access_enabled) { @subject.request_access_enabled }
+
+ rule { public_group } .enable :read_group
+ rule { logged_in_viewable }.enable :read_group
+ rule { guest } .enable :read_group
+ rule { admin } .enable :read_group
+ rule { has_projects } .enable :read_group
+
+ rule { reporter }.enable :admin_label
+
+ rule { master }.policy do
+ enable :create_projects
+ enable :admin_milestones
+ end
+
+ rule { owner }.policy do
+ enable :admin_group
+ enable :admin_namespace
+ enable :admin_group_member
+ enable :change_visibility_level
+ end
+
+ rule { owner & can_create_group }.enable :create_subgroup
+
+ rule { public_group | logged_in_viewable }.enable :view_globally
+
+ rule { default }.enable(:request_access)
+
+ rule { ~request_access_enabled }.prevent :request_access
+ rule { ~can?(:view_globally) }.prevent :request_access
+ rule { has_access }.prevent :request_access
+
+ def access_level
+ return GroupMember::NO_ACCESS if @user.nil?
+
+ @access_level ||= @subject.max_member_access_for_user(@user)
+ end
end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 9501e499507..daf6fa9e18a 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -1,14 +1,15 @@
class IssuablePolicy < BasePolicy
- def action_name
- @subject.class.name.underscore
- end
+ delegate { @subject.project }
- def rules
- if @user && @subject.assignee_or_author?(@user)
- can! :"read_#{action_name}"
- can! :"update_#{action_name}"
- end
+ desc "User is the assignee or author"
+ condition(:assignee_or_author) do
+ @user && @subject.assignee_or_author?(@user)
+ end
- delegate! @subject.project
+ rule { assignee_or_author }.policy do
+ enable :read_issue
+ enable :update_issue
+ enable :read_merge_request
+ enable :update_merge_request
end
end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 88f3179c6ff..bd2d417b2a8 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -3,25 +3,17 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
- def issue
- @subject
+ desc "User can read confidential issues"
+ condition(:can_read_confidential) do
+ @user && IssueCollection.new([@subject]).visible_to(@user).any?
end
- def rules
- super
+ desc "Issue is confidential"
+ condition(:confidential, scope: :subject) { @subject.confidential? }
- if @subject.confidential? && !can_read_confidential?
- cannot! :read_issue
- cannot! :update_issue
- cannot! :admin_issue
- end
- end
-
- private
-
- def can_read_confidential?
- return false unless @user
-
- IssueCollection.new([@subject]).visible_to(@user).any?
+ rule { confidential & ~can_read_confidential }.policy do
+ prevent :read_issue
+ prevent :update_issue
+ prevent :admin_issue
end
end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
index 29bb357e00a..85b67f0a237 100644
--- a/app/policies/namespace_policy.rb
+++ b/app/policies/namespace_policy.rb
@@ -1,10 +1,10 @@
class NamespacePolicy < BasePolicy
- def rules
- return unless @user
+ rule { anonymous }.prevent_all
- if @subject.owner == @user || @user.admin?
- can! :create_projects
- can! :admin_namespace
- end
+ condition(:owner) { @subject.owner == @user }
+
+ rule { owner | admin }.policy do
+ enable :create_projects
+ enable :admin_namespace
end
end
diff --git a/app/policies/nil_policy.rb b/app/policies/nil_policy.rb
new file mode 100644
index 00000000000..13f46ba60f0
--- /dev/null
+++ b/app/policies/nil_policy.rb
@@ -0,0 +1,3 @@
+class NilPolicy < BasePolicy
+ rule { default }.prevent_all
+end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index 5326061bd07..20cd51cfb99 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -1,19 +1,24 @@
class NotePolicy < BasePolicy
- def rules
- delegate! @subject.project
+ delegate { @subject.project }
- return unless @user
+ condition(:is_author) { @user && @subject.author == @user }
+ condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
+ condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
- if @subject.author == @user
- can! :read_note
- can! :update_note
- can! :admin_note
- can! :resolve_note
- end
+ condition(:editable, scope: :subject) { @subject.editable? }
- if @subject.for_merge_request? &&
- @subject.noteable.author == @user
- can! :resolve_note
- end
+ rule { ~editable | anonymous }.prevent :edit_note
+ rule { is_author | admin }.enable :edit_note
+ rule { can?(:master_access) }.enable :edit_note
+
+ rule { is_author }.policy do
+ enable :read_note
+ enable :update_note
+ enable :admin_note
+ enable :resolve_note
+ end
+
+ rule { for_merge_request & is_noteable_author }.policy do
+ enable :resolve_note
end
end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index e1e5336da8c..cac0530b9f7 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -1,27 +1,28 @@
class PersonalSnippetPolicy < BasePolicy
- def rules
- can! :read_personal_snippet if @subject.public?
- return unless @user
+ condition(:public_snippet, scope: :subject) { @subject.public? }
+ condition(:is_author) { @user && @subject.author == @user }
+ condition(:internal_snippet, scope: :subject) { @subject.internal? }
- if @subject.public?
- can! :comment_personal_snippet
- end
+ rule { public_snippet }.policy do
+ enable :read_personal_snippet
+ enable :comment_personal_snippet
+ end
- if @subject.author == @user
- can! :read_personal_snippet
- can! :update_personal_snippet
- can! :destroy_personal_snippet
- can! :admin_personal_snippet
- can! :comment_personal_snippet
- end
+ rule { is_author }.policy do
+ enable :read_personal_snippet
+ enable :update_personal_snippet
+ enable :destroy_personal_snippet
+ enable :admin_personal_snippet
+ enable :comment_personal_snippet
+ end
- unless @user.external?
- can! :create_personal_snippet
- end
+ rule { ~anonymous }.enable :create_personal_snippet
+ rule { external_user }.prevent :create_personal_snippet
- if @subject.internal? && !@user.external?
- can! :read_personal_snippet
- can! :comment_personal_snippet
- end
+ rule { internal_snippet & ~external_user }.policy do
+ enable :read_personal_snippet
+ enable :comment_personal_snippet
end
+
+ rule { anonymous }.prevent :comment_personal_snippet
end
diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb
index b12b4c5166b..2d0f021118b 100644
--- a/app/policies/project_label_policy.rb
+++ b/app/policies/project_label_policy.rb
@@ -1,5 +1,3 @@
class ProjectLabelPolicy < BasePolicy
- def rules
- delegate! @subject.project
- end
+ delegate { @subject.project }
end
diff --git a/app/policies/project_member_policy.rb b/app/policies/project_member_policy.rb
index 1c038dddd4b..9aedb620be9 100644
--- a/app/policies/project_member_policy.rb
+++ b/app/policies/project_member_policy.rb
@@ -1,22 +1,16 @@
class ProjectMemberPolicy < BasePolicy
- def rules
- # anonymous users have no abilities here
- return unless @user
+ delegate { @subject.project }
- target_user = @subject.user
- project = @subject.project
+ condition(:target_is_owner, scope: :subject) { @subject.user == @subject.project.owner }
+ condition(:target_is_self) { @user && @subject.user == @user }
- return if target_user == project.owner
+ rule { anonymous }.prevent_all
+ rule { target_is_owner }.prevent_all
- 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
+ rule { can?(:admin_project_member) }.policy do
+ enable :update_project_member
+ enable :destroy_project_member
end
+
+ rule { target_is_self }.enable :destroy_project_member
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 47518dddb61..7cbca63fab4 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -1,297 +1,353 @@
class ProjectPolicy < BasePolicy
- def rules
- team_access!(user)
+ def self.create_read_update_admin(name)
+ [
+ :"create_#{name}",
+ :"read_#{name}",
+ :"update_#{name}",
+ :"admin_#{name}"
+ ]
+ end
- owner_access! if user.admin? || owner?
- team_member_owner_access! if owner?
+ desc "User is a project owner"
+ condition :owner do
+ @user && project.owner == @user || (project.group && project.group.has_owner?(@user))
+ end
- if project.public? || (project.internal? && !user.external?)
- guest_access!
- public_access!
- can! :request_access if access_requestable?
- end
+ desc "Project has public builds enabled"
+ condition(:public_builds, scope: :subject) { project.public_builds? }
+
+ # For guest access we use #is_team_member? so we can use
+ # project.members, which gets cached in subject scope.
+ # This is safe because team_access_level is guaranteed
+ # by ProjectAuthorization's validation to be at minimum
+ # GUEST
+ desc "User has guest access"
+ condition(:guest) { is_team_member? }
- archived_access! if project.archived?
+ desc "User has reporter access"
+ condition(:reporter) { team_access_level >= Gitlab::Access::REPORTER }
- disabled_features!
+ desc "User has developer access"
+ condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER }
+
+ desc "User has master access"
+ condition(:master) { team_access_level >= Gitlab::Access::MASTER }
+
+ desc "Project is public"
+ condition(:public_project, scope: :subject) { project.public? }
+
+ desc "Project is visible to internal users"
+ condition(:internal_access) do
+ project.internal? && !user.external?
end
- def project
- @subject
+ desc "User is a member of the group"
+ condition(:group_member, scope: :subject) { project_group_member? }
+
+ desc "Project is archived"
+ condition(:archived, scope: :subject) { project.archived? }
+
+ condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
+
+ desc "Container registry is disabled"
+ condition(:container_registry_disabled, scope: :subject) do
+ !project.container_registry_enabled
end
- def owner?
- return @owner if defined?(@owner)
-
- @owner = project.owner == user ||
- (project.group && project.group.has_owner?(user))
- 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_note
- can! :create_project
- can! :create_issue
- can! :create_note
- can! :upload_file
- can! :read_cycle_analytics
-
- if project.public_builds?
- can! :read_pipeline
- can! :read_pipeline_schedule
- can! :read_build
- end
+ desc "Project has an external wiki"
+ condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
+
+ desc "Project has request access enabled"
+ condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
+
+ features = %w[
+ merge_requests
+ issues
+ repository
+ snippets
+ wiki
+ builds
+ ]
+
+ features.each do |f|
+ # these are scored high because they are unlikely
+ desc "Project has #{f} disabled"
+ condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
end
- def reporter_access!
- can! :download_code
- can! :download_wiki_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_pipeline_schedule
- can! :read_environment
- can! :read_deployment
- can! :read_merge_request
- end
-
- # Permissions given when an user is team member of a project
- def team_member_reporter_access!
- can! :build_download_code
- can! :build_read_container_image
- 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_pipeline_schedule
- can! :update_pipeline_schedule
- 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! :delete_protected_branch
- can! :update_project_snippet
- can! :update_environment
- can! :update_deployment
- can! :admin_milestone
- can! :admin_project_snippet
- can! :admin_project_member
- 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_pipeline_schedule
- can! :admin_environment
- can! :admin_deployment
- can! :admin_pages
- can! :read_pages
- can! :update_pages
- end
-
- def public_access!
- can! :download_code
- can! :fork_project
- can! :read_commit_status
- can! :read_pipeline
- can! :read_pipeline_schedule
- can! :read_container_image
- can! :build_download_code
- can! :build_read_container_image
- can! :read_merge_request
- 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
- can! :remove_pages
- end
-
- def team_member_owner_access!
- team_member_reporter_access!
- end
-
- # Push abilities on the users team role
- def team_access!(user)
- access = project.team.max_member_access(user.id)
-
- return if access < Gitlab::Access::GUEST
- guest_access!
-
- return if access < Gitlab::Access::REPORTER
- reporter_access!
- team_member_reporter_access!
-
- return if access < Gitlab::Access::DEVELOPER
- developer_access!
-
- return if access < Gitlab::Access::MASTER
- master_access!
- end
-
- def archived_access!
- cannot! :create_merge_request
- cannot! :push_code
- cannot! :delete_protected_branch
- cannot! :update_merge_request
- cannot! :admin_merge_request
- end
-
- def disabled_features!
- repository_enabled = project.feature_available?(:repository, user)
-
- block_issues_abilities
-
- unless project.feature_available?(:merge_requests, user) && repository_enabled
- cannot!(*named_abilities(:merge_request))
- end
+ rule { guest }.enable :guest_access
+ rule { reporter }.enable :reporter_access
+ rule { developer }.enable :developer_access
+ rule { master }.enable :master_access
+
+ rule { owner | admin }.policy do
+ enable :guest_access
+ enable :reporter_access
+ enable :developer_access
+ enable :master_access
+
+ enable :change_namespace
+ enable :change_visibility_level
+ enable :rename_project
+ enable :remove_project
+ enable :archive_project
+ enable :remove_fork_project
+ enable :destroy_merge_request
+ enable :destroy_issue
+ enable :remove_pages
+ end
- unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
- cannot!(*named_abilities(:label))
- cannot!(*named_abilities(:milestone))
- end
+ rule { owner | reporter }.policy do
+ enable :build_download_code
+ enable :build_read_container_image
+ end
- unless project.feature_available?(:snippets, user)
- cannot!(*named_abilities(:project_snippet))
- end
+ rule { can?(:guest_access) }.policy do
+ enable :read_project
+ enable :read_board
+ enable :read_list
+ enable :read_wiki
+ enable :read_issue
+ enable :read_label
+ enable :read_milestone
+ enable :read_project_snippet
+ enable :read_project_member
+ enable :read_note
+ enable :create_project
+ enable :create_issue
+ enable :create_note
+ enable :upload_file
+ enable :read_cycle_analytics
+ enable :read_project_snippet
+ end
- unless project.feature_available?(:wiki, user) || project.has_external_wiki?
- cannot!(*named_abilities(:wiki))
- cannot!(:download_wiki_code)
- end
+ rule { can?(:reporter_access) }.policy do
+ enable :download_code
+ enable :download_wiki_code
+ enable :fork_project
+ enable :create_project_snippet
+ enable :update_issue
+ enable :admin_issue
+ enable :admin_label
+ enable :admin_list
+ enable :read_commit_status
+ enable :read_build
+ enable :read_container_image
+ enable :read_pipeline
+ enable :read_pipeline_schedule
+ enable :read_environment
+ enable :read_deployment
+ enable :read_merge_request
+ end
- unless project.feature_available?(:builds, user) && repository_enabled
- cannot!(*named_abilities(:build))
- cannot!(*named_abilities(:pipeline) - [:read_pipeline])
- cannot!(*named_abilities(:pipeline_schedule))
- cannot!(*named_abilities(:environment))
- cannot!(*named_abilities(:deployment))
- end
+ rule { (~anonymous & public_project) | internal_access }.policy do
+ enable :public_user_access
+ end
- unless repository_enabled
- cannot! :push_code
- cannot! :delete_protected_branch
- cannot! :download_code
- cannot! :fork_project
- cannot! :read_commit_status
- end
+ rule { can?(:public_user_access) }.policy do
+ enable :guest_access
+ enable :request_access
+ end
- unless project.container_registry_enabled
- cannot!(*named_abilities(:container_image))
- end
+ rule { owner | admin | guest | group_member }.prevent :request_access
+ rule { ~request_access_enabled }.prevent :request_access
+
+ rule { can?(:developer_access) }.policy do
+ enable :admin_merge_request
+ enable :update_merge_request
+ enable :create_commit_status
+ enable :update_commit_status
+ enable :create_build
+ enable :update_build
+ enable :create_pipeline
+ enable :update_pipeline
+ enable :create_pipeline_schedule
+ enable :update_pipeline_schedule
+ enable :create_merge_request
+ enable :create_wiki
+ enable :push_code
+ enable :resolve_note
+ enable :create_container_image
+ enable :update_container_image
+ enable :create_environment
+ enable :create_deployment
end
- def anonymous_rules
- return unless project.public?
+ rule { can?(:master_access) }.policy do
+ enable :delete_protected_branch
+ enable :update_project_snippet
+ enable :update_environment
+ enable :update_deployment
+ enable :admin_milestone
+ enable :admin_project_snippet
+ enable :admin_project_member
+ enable :admin_note
+ enable :admin_wiki
+ enable :admin_project
+ enable :admin_commit_status
+ enable :admin_build
+ enable :admin_container_image
+ enable :admin_pipeline
+ enable :admin_pipeline_schedule
+ enable :admin_environment
+ enable :admin_deployment
+ enable :admin_pages
+ enable :read_pages
+ enable :update_pages
+ end
- base_readonly_access!
+ rule { can?(:public_user_access) }.policy do
+ enable :public_access
- # Allow to read builds by anonymous user if guests are allowed
- can! :read_build if project.public_builds?
+ enable :fork_project
+ enable :build_download_code
+ enable :build_read_container_image
+ end
- disabled_features!
+ rule { archived }.policy do
+ prevent :create_merge_request
+ prevent :push_code
+ prevent :delete_protected_branch
+ prevent :update_merge_request
+ prevent :admin_merge_request
end
- def block_issues_abilities
- unless project.feature_available?(:issues, user)
- cannot! :read_issue if project.default_issues_tracker?
- cannot! :create_issue
- cannot! :update_issue
- cannot! :admin_issue
- end
+ rule { merge_requests_disabled | repository_disabled }.policy do
+ prevent(*create_read_update_admin(:merge_request))
end
- def named_abilities(name)
- [
- :"read_#{name}",
- :"create_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
+ rule { issues_disabled & merge_requests_disabled }.policy do
+ prevent(*create_read_update_admin(:label))
+ prevent(*create_read_update_admin(:milestone))
+ end
+
+ rule { snippets_disabled }.policy do
+ prevent(*create_read_update_admin(:project_snippet))
+ end
+
+ rule { wiki_disabled & ~has_external_wiki }.policy do
+ prevent(*create_read_update_admin(:wiki))
+ prevent(:download_wiki_code)
+ end
+
+ rule { builds_disabled | repository_disabled }.policy do
+ prevent(*create_read_update_admin(:build))
+ prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
+ prevent(*create_read_update_admin(:pipeline_schedule))
+ prevent(*create_read_update_admin(:environment))
+ prevent(*create_read_update_admin(:deployment))
+ end
+
+ rule { repository_disabled }.policy do
+ prevent :push_code
+ prevent :push_code_to_protected_branches
+ prevent :download_code
+ prevent :fork_project
+ prevent :read_commit_status
+ end
+
+ rule { container_registry_disabled }.policy do
+ prevent(*create_read_update_admin(:container_image))
+ end
+
+ rule { anonymous & ~public_project }.prevent_all
+ rule { public_project }.enable(:public_access)
+
+ rule { can?(:public_access) }.policy do
+ enable :read_project
+ enable :read_board
+ enable :read_list
+ enable :read_wiki
+ enable :read_label
+ enable :read_milestone
+ enable :read_project_snippet
+ enable :read_project_member
+ enable :read_merge_request
+ enable :read_note
+ enable :read_pipeline
+ enable :read_pipeline_schedule
+ enable :read_commit_status
+ enable :read_container_image
+ enable :download_code
+ enable :download_wiki_code
+ enable :read_cycle_analytics
+
+ # NOTE: may be overridden by IssuePolicy
+ enable :read_issue
+ end
+
+ rule { public_builds }.policy do
+ enable :read_build
+ end
+
+ rule { public_builds & can?(:guest_access) }.policy do
+ enable :read_pipeline
+ enable :read_pipeline_schedule
+ end
+
+ rule { issues_disabled }.policy do
+ prevent :create_issue
+ prevent :update_issue
+ prevent :admin_issue
+ end
+
+ rule { issues_disabled & default_issues_tracker }.policy do
+ prevent :read_issue
end
private
- def project_group_member?(user)
+ def is_team_member?
+ return false if @user.nil?
+
+ greedy_load_subject = false
+
+ # when scoping by subject, we want to be greedy
+ # and load *all* the members with one query.
+ greedy_load_subject ||= DeclarativePolicy.preferred_scope == :subject
+
+ # in this case we're likely to have loaded #members already
+ # anyways, and #member? would fail with an error
+ greedy_load_subject ||= !@user.persisted?
+
+ if greedy_load_subject
+ project.team.members.include?(user)
+ else
+ # otherwise we just make a specific query for
+ # this particular user.
+ team_access_level >= Gitlab::Access::GUEST
+ end
+ end
+
+ def project_group_member?
+ return false if @user.nil?
+
project.group &&
(
- project.group.members_with_parents.exists?(user_id: user.id) ||
- project.group.requesters.exists?(user_id: user.id)
+ project.group.members_with_parents.exists?(user_id: @user.id) ||
+ project.group.requesters.exists?(user_id: @user.id)
)
end
- def access_requestable?
- project.request_access_enabled &&
- !owner? &&
- !user.admin? &&
- !project.team.member?(user) &&
- !project_group_member?(user)
- end
-
- # A base set of abilities for read-only users, which
- # is then augmented as necessary for anonymous and other
- # read-only users.
- def base_readonly_access!
- 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_pipeline_schedule
- can! :read_commit_status
- can! :read_container_image
- can! :download_code
- can! :download_wiki_code
- can! :read_cycle_analytics
+ def team_access_level
+ return -1 if @user.nil?
- # NOTE: may be overridden by IssuePolicy
- can! :read_issue
+ # NOTE: max_member_access has its own cache
+ project.team.max_member_access(@user.id)
+ end
+
+ def feature_available?(feature)
+ case project.project_feature.access_level(feature)
+ when ProjectFeature::DISABLED
+ false
+ when ProjectFeature::PRIVATE
+ guest? || admin?
+ else
+ true
+ end
+ end
+
+ def project
+ @subject
end
end
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index bc5c4f32f79..dd270643bbf 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -1,25 +1,45 @@
class ProjectSnippetPolicy < BasePolicy
- def rules
- # We have to check both project feature visibility and a snippet visibility and take the stricter one
- # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
- return unless @subject.project.feature_available?(:snippets, @user)
- return unless Ability.allowed?(@user, :read_project, @subject.project)
-
- 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.project.team.member?(@user)
- can! :read_project_snippet
- end
+ delegate :project
+
+ desc "Snippet is public"
+ condition(:public_snippet, scope: :subject) { @subject.public? }
+ condition(:private_snippet, scope: :subject) { @subject.private? }
+ condition(:public_project, scope: :subject) { @subject.project.public? }
+
+ condition(:is_author) { @user && @subject.author == @user }
+
+ condition(:internal, scope: :subject) { @subject.internal? }
+
+ # We have to check both project feature visibility and a snippet visibility and take the stricter one
+ # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
+ rule { ~can?(:read_project) }.policy do
+ prevent :read_project_snippet
+ prevent :update_project_snippet
+ prevent :admin_project_snippet
+ end
+
+ # we have to use this complicated prevent because the delegated project policy
+ # is overly greedy in allowing :read_project_snippet, since it doesn't have any
+ # information about the snippet. However, :read_project_snippet on the *project*
+ # is used to hide/show various snippet-related controls, so we can't just move
+ # all of the handling here.
+ rule do
+ all?(private_snippet | (internal & external_user),
+ ~project.guest,
+ ~admin,
+ ~is_author)
+ end.prevent :read_project_snippet
+
+ rule { internal & ~is_author & ~admin }.policy do
+ prevent :update_project_snippet
+ prevent :admin_project_snippet
+ end
+
+ rule { public_snippet }.enable :read_project_snippet
+
+ rule { is_author | admin }.policy do
+ enable :read_project_snippet
+ enable :update_project_snippet
+ enable :admin_project_snippet
end
end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 229846e368c..0181ddf85e0 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -1,19 +1,20 @@
class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
- def rules
- can! :read_user if @user || !restricted_public_level?
+ desc "The application is restricted from public visibility"
+ condition(:restricted_public_level, scope: :global) do
+ current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
+ end
- if @user
- if @user.admin? || @subject == @user
- can! :destroy_user
- end
+ desc "The current user is the user in question"
+ condition(:user_is_self, score: 0) { @subject == @user }
- cannot! :destroy_user if @subject.ghost?
- end
- end
+ desc "This is the ghost user"
+ condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? }
- def restricted_public_level?
- current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
- end
+ rule { ~restricted_public_level }.enable :read_user
+ rule { ~anonymous }.enable :read_user
+
+ rule { user_is_self | admin }.enable :destroy_user
+ rule { subject_ghost }.prevent :destroy_user
end