diff options
Diffstat (limited to 'app/models/ability.rb')
-rw-r--r-- | app/models/ability.rb | 587 |
1 files changed, 11 insertions, 576 deletions
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 |