diff options
Diffstat (limited to 'app/policies')
-rw-r--r-- | app/policies/base_policy.rb | 116 | ||||
-rw-r--r-- | app/policies/ci/build_policy.rb | 13 | ||||
-rw-r--r-- | app/policies/ci/runner_policy.rb | 13 | ||||
-rw-r--r-- | app/policies/commit_status_policy.rb | 5 | ||||
-rw-r--r-- | app/policies/deployment_policy.rb | 5 | ||||
-rw-r--r-- | app/policies/environment_policy.rb | 5 | ||||
-rw-r--r-- | app/policies/external_issue_policy.rb | 5 | ||||
-rw-r--r-- | app/policies/global_policy.rb | 8 | ||||
-rw-r--r-- | app/policies/group_member_policy.rb | 19 | ||||
-rw-r--r-- | app/policies/group_policy.rb | 45 | ||||
-rw-r--r-- | app/policies/issuable_policy.rb | 14 | ||||
-rw-r--r-- | app/policies/issue_policy.rb | 28 | ||||
-rw-r--r-- | app/policies/merge_request_policy.rb | 3 | ||||
-rw-r--r-- | app/policies/namespace_policy.rb | 10 | ||||
-rw-r--r-- | app/policies/note_policy.rb | 19 | ||||
-rw-r--r-- | app/policies/personal_snippet_policy.rb | 16 | ||||
-rw-r--r-- | app/policies/project_member_policy.rb | 22 | ||||
-rw-r--r-- | app/policies/project_policy.rb | 224 | ||||
-rw-r--r-- | app/policies/project_snippet_policy.rb | 20 | ||||
-rw-r--r-- | app/policies/user_policy.rb | 11 |
20 files changed, 601 insertions, 0 deletions
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..acf36d422d1 --- /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.feature_available?(:issues, user) + cannot!(*named_abilities(:issue)) + end + + unless project.feature_available?(:merge_requests, user) + cannot!(*named_abilities(:merge_request)) + end + + unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user) + cannot!(*named_abilities(:label)) + cannot!(*named_abilities(:milestone)) + end + + unless project.feature_available?(:snippets, user) + cannot!(*named_abilities(:project_snippet)) + end + + unless project.feature_available?(:wiki, user) || project.has_external_wiki? + cannot!(*named_abilities(:wiki)) + end + + unless project.feature_available?(:builds, user) + 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 |