diff options
-rw-r--r-- | app/controllers/jwt_controller.rb | 26 | ||||
-rw-r--r-- | app/controllers/projects/git_http_client_controller.rb | 8 | ||||
-rw-r--r-- | app/controllers/projects/git_http_controller.rb | 2 | ||||
-rw-r--r-- | app/helpers/lfs_helper.rb | 16 | ||||
-rw-r--r-- | app/models/ci/build.rb | 13 | ||||
-rw-r--r-- | app/models/project.rb | 6 | ||||
-rw-r--r-- | app/policies/project_policy.rb | 17 | ||||
-rw-r--r-- | app/services/auth/container_registry_authentication_service.rb | 34 | ||||
-rw-r--r-- | db/migrate/20160808085531_add_token_to_build.rb | 10 | ||||
-rw-r--r-- | db/migrate/20160808085602_add_index_for_build_token.rb | 12 | ||||
-rw-r--r-- | db/schema.rb | 3 | ||||
-rw-r--r-- | lib/api/internal.rb | 14 | ||||
-rw-r--r-- | lib/ci/api/helpers.rb | 14 | ||||
-rw-r--r-- | lib/gitlab/auth.rb | 108 | ||||
-rw-r--r-- | lib/gitlab/git_access.rb | 19 | ||||
-rw-r--r-- | spec/lib/gitlab/auth_spec.rb | 76 | ||||
-rw-r--r-- | spec/lib/gitlab/git_access_spec.rb | 101 |
17 files changed, 363 insertions, 116 deletions
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 66ebdcc37a7..ed5d28e0d2c 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -11,7 +11,10 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - result = service.new(@project, @user, auth_params).execute + @@authentication_result ||= Gitlab::Auth.Result.new + + result = service.new(@authentication_result.project, @authentication_result.user, auth_params). + execute(capabilities: @authentication_result.capabilities || []) render json: result, status: result[:http_status] end @@ -20,30 +23,13 @@ class JwtController < ApplicationController def authenticate_project_or_user authenticate_with_http_basic do |login, password| - # if it's possible we first try to authenticate project with login and password - @project = authenticate_project(login, password) - return if @project - - @user = authenticate_user(login, password) - return if @user + @authentication_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) - render_403 + render_403 unless @authentication_result.succeeded? end end def auth_params params.permit(:service, :scope, :account, :client_id) end - - def authenticate_project(login, password) - if login == 'gitlab-ci-token' - Project.with_builds_enabled.find_by(runners_token: password) - end - end - - def authenticate_user(login, password) - user = Gitlab::Auth.find_with_user_password(login, password) - Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login) - user - end end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index f5ce63fdfed..6870102c296 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :user + attr_reader :user, :capabilities # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -34,6 +34,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController @user = auth_result.user end + @capabilities = auth_result.capabilities || [] + if ci? || user return # Allow access end @@ -118,6 +120,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController @ci.present? end + def has_capability?(capability) + @capabilities.include?(capability) + end + def verify_workhorse_api! Gitlab::Workhorse.verify_api_request!(request.headers) end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 9805705c4e3..89afaaed510 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access - @access ||= Gitlab::GitAccess.new(user, project, 'http') + @access ||= Gitlab::GitAccess.new(user, project, 'http', capabilities: capabilities) end def access_check diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 5d82abfca79..a2359d94443 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,13 +25,25 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || (user && user.can?(:download_code, project)) + project.public? || ci? || user_can_download_code? || build_can_download_code? + end + + def user_can_download_code? + has_capability?(:download_code) && user && user.can?(:download_code, project) + end + + def build_can_download_code? + has_capability?(:build_download_code) && user && user.can?(:build_download_code, project) end def lfs_upload_access? return false unless project.lfs_enabled? - user && user.can?(:push_code, project) + privileged_user_can_push_code? + end + + def privileged_user_can_push_code? + has_capability?(:push_code) && user && user.can?(:push_code, project) end def render_lfs_forbidden diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index fb16bc06d71..29788b8285d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,5 +1,7 @@ module Ci class Build < CommitStatus + include TokenAuthenticatable + belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :erased_by, class_name: 'User' @@ -23,7 +25,10 @@ module Ci acts_as_taggable + add_authentication_token_field :token + before_save :update_artifacts_size, if: :artifacts_file_changed? + before_save :ensure_token before_destroy { project } after_create :execute_hooks @@ -173,7 +178,7 @@ module Ci end def repo_url - auth = "gitlab-ci-token:#{token}@" + auth = "gitlab-ci-token:#{ensure_token}@" project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| prefix + auth end @@ -341,12 +346,8 @@ module Ci ) end - def token - project.runners_token - end - def valid_token?(token) - project.valid_runners_token?(token) + self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) end def has_tags? diff --git a/app/models/project.rb b/app/models/project.rb index f3f3ffbbd28..16ca2b688c8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1138,12 +1138,6 @@ class Project < ActiveRecord::Base self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end - # TODO (ayufan): For now we use runners_token (backward compatibility) - # In 8.4 every build will have its own individual token valid for time of build - def valid_build_token?(token) - self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) - end - def build_coverage_enabled? build_coverage_regex.present? end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index acf36d422d1..ce686af2ade 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -64,6 +64,12 @@ class ProjectPolicy < BasePolicy can! :read_deployment end + # Permissions given when an user is direct member of a group + 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 @@ -109,6 +115,8 @@ class ProjectPolicy < BasePolicy can! :read_commit_status can! :read_pipeline can! :read_container_image + can! :build_download_code + can! :build_read_container_image end def owner_access! @@ -130,10 +138,11 @@ class ProjectPolicy < BasePolicy 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 + guest_access! if access >= Gitlab::Access::GUEST + reporter_access! if access >= Gitlab::Access::REPORTER + team_member_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! diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 6072123b851..ba0b60abfe4 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -4,7 +4,9 @@ module Auth AUDIENCE = 'container_registry' - def execute + def execute(capabilities: capabilities) + @capabilities = capabilities + return error('not found', 404) unless registry.enabled unless current_user || project @@ -74,9 +76,9 @@ module Auth case requested_action when 'pull' - requested_project == project || can?(current_user, :read_container_image, requested_project) + build_can_pull?(requested_project) || user_can_pull?(requested_project) when 'push' - requested_project == project || can?(current_user, :create_container_image, requested_project) + build_can_push?(requested_project) || user_can_push?(requested_project) else false end @@ -85,5 +87,31 @@ module Auth def registry Gitlab.config.registry end + + private + + def build_can_pull?(requested_project) + # Build can: + # 1. pull from it's own project (for ex. a build) + # 2. read images from dependent projects if creator of build is a team member + @capabilities.include?(:build_read_container_image) && + (requested_project == project || can?(current_user, :build_read_container_image, requested_project)) + end + + def user_can_pull?(requested_project) + @capabilities.include?(:read_container_image) && + can?(current_user, :read_container_image, requested_project) + end + + def build_can_push?(requested_project) + # Build can push only to project to from which he originates + @capabilities.include?(:build_create_container_image) && + requested_project == project + end + + def user_can_push?(requested_project) + @capabilities.include?(:create_container_image) && + can?(current_user, :create_container_image, requested_project) + end end end diff --git a/db/migrate/20160808085531_add_token_to_build.rb b/db/migrate/20160808085531_add_token_to_build.rb new file mode 100644 index 00000000000..3ed2a103ae3 --- /dev/null +++ b/db/migrate/20160808085531_add_token_to_build.rb @@ -0,0 +1,10 @@ +class AddTokenToBuild < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + add_column :ci_builds, :token, :string + end +end diff --git a/db/migrate/20160808085602_add_index_for_build_token.rb b/db/migrate/20160808085602_add_index_for_build_token.rb new file mode 100644 index 00000000000..10ef42afce1 --- /dev/null +++ b/db/migrate/20160808085602_add_index_for_build_token.rb @@ -0,0 +1,12 @@ +class AddIndexForBuildToken < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_concurrent_index :ci_builds, :token, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 61873e38113..af00094c6b6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,6 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. + ActiveRecord::Schema.define(version: 20160913162434) do # These are extensions that must be enabled in order to support this database @@ -181,6 +182,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do t.string "when" t.text "yaml_variables" t.datetime "queued_at" + t.string "token" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -192,6 +194,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree + add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree create_table "ci_commits", force: :cascade do |t| t.integer "project_id" diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 6e6efece7c4..2ec94570506 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -51,9 +51,9 @@ module API access = if wiki? - Gitlab::GitAccessWiki.new(actor, project, protocol) + Gitlab::GitAccessWiki.new(actor, project, protocol, capabilities: ssh_capabilities) else - Gitlab::GitAccess.new(actor, project, protocol) + Gitlab::GitAccess.new(actor, project, protocol, capabilities: ssh_capabilities) end access_status = access.check(params[:action], params[:changes]) @@ -130,6 +130,16 @@ module API { success: true, recovery_codes: codes } end + + private + + def ssh_capabilities + [ + :read_project, + :download_code, + :push_code + ] + end end end end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index ba80c89df78..3dfebc0cac1 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -14,12 +14,20 @@ module Ci end def authenticate_build_token!(build) - token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s - forbidden! unless token && build.valid_token?(token) + forbidden! unless build_token_valid? end def runner_registration_token_valid? - params[:token] == current_application_settings.runners_registration_token + ActiveSupport::SecurityUtils.variable_size_secure_compare( + params[:token], + current_application_settings.runners_registration_token) + end + + def build_token_valid? + token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s + + # We require to also check `runners_token` to maintain compatibility with old version of runners + token && (build.valid_token?(token) || build.project.valid_runners_token?(token)) end def update_runner_last_contact(save: true) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 91f0270818a..7af9bb9a326 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,21 +1,23 @@ module Gitlab module Auth - Result = Struct.new(:user, :type) + Result = Struct.new(:user, :project, :type, :capabilities) do + def succeeded? + user.present? || [:ci].include?(type) + end + end class << self def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - result = Result.new - - if valid_ci_request?(login, password, project) - result.type = :ci - else - result = populate_result(login, password) - end + result = service_access_token_check(login, password, project) || + build_access_token_check(login, password) || + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + personal_access_token_check(login, password) || + Result.new - success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) - rate_limit!(ip, success: success, login: login) + rate_limit!(ip, success: result.succeeded?, login: login) result end @@ -57,44 +59,36 @@ module Gitlab private - def valid_ci_request?(login, password, project) + def service_access_token_check(login, password, project) matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login) - return false unless project && matched_login.present? + return unless project && matched_login.present? underscored_service = matched_login['service'].underscore - if underscored_service == 'gitlab_ci' - project && project.valid_build_token?(password) - elsif Service.available_services_names.include?(underscored_service) + if Service.available_services_names.include?(underscored_service) # We treat underscored_service as a trusted input because it is included # in the Service.available_services_names whitelist. service = project.public_send("#{underscored_service}_service") - service && service.activated? && service.valid_token?(password) - end - end - - def populate_result(login, password) - result = - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || - personal_access_token_check(login, password) - - if result - result.type = nil unless result.user - - if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap - result.type = :missing_personal_token + if service && service.activated? && service.valid_token?(password) + Result.new(nil, project, :ci, build_capabilities) end end - - result || Result.new end def user_with_password_for_git(login, password) user = find_with_user_password(login, password) - Result.new(user, :gitlab_or_ldap) if user + return unless user + + type = + if user.two_factor_enabled? + :missing_personal_token + else + :gitlab_or_ldap + end + + Result.new(user, nil, type, full_capabilities) end def oauth_access_token_check(login, password) @@ -102,7 +96,7 @@ module Gitlab token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - Result.new(user, :oauth) + Result.new(user, nil, :oauth, read_capabilities) end end end @@ -111,9 +105,53 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, :personal_token) if user == validation + if user && user == validation + Result.new(user, nil, :personal_token, full_capabilities) + end + end + end + + def build_access_token_check(login, password) + return unless login == 'gitlab-ci-token' + return unless password + + build = Ci::Build.running.find_by_token(password) + return unless build + + if build.user + # If user is assigned to build, use restricted credentials of user + Result.new(build.user, build.project, :build, build_capabilities) + else + # Otherwise use generic CI credentials (backward compatibility) + Result.new(nil, build.project, :ci, build_capabilities) end end + + private + + def build_capabilities + [ + :read_project, + :build_download_code, + :build_read_container_image, + :build_create_container_image + ] + end + + def read_capabilities + [ + :read_project, + :download_code, + :read_container_image + ] + end + + def full_capabilities + read_capabilities + [ + :push_code, + :update_container_image + ] + end end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 1882eb8d050..63b707db814 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,12 +5,13 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :actor, :project, :protocol, :user_access + attr_reader :actor, :project, :protocol, :user_access, :capabilities - def initialize(actor, project, protocol) + def initialize(actor, project, protocol, capabilities: capabilities) @actor = actor @project = project @protocol = protocol + @capabilities = capabilities @user_access = UserAccess.new(user, project: project) end @@ -60,14 +61,26 @@ module Gitlab end def user_download_access_check - unless user_access.can_do_action?(:download_code) + unless user_can_download_code? || build_can_download_code? return build_status_object(false, "You are not allowed to download code from this project.") end build_status_object(true) end + def user_can_download_code? + capabilities.include?(:download_code) && user_access.can_do_action?(:download_code) + end + + def build_can_download_code? + capabilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) + end + def user_push_access_check(changes) + unless capabilities.include?(:push_code) + return build_status_object(false, "You are not allowed to upload code for this project.") + end + if changes.blank? return build_status_object(true) end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 7c23e02d05a..b665517bbb0 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -4,15 +4,51 @@ describe Gitlab::Auth, lib: true do let(:gl_auth) { described_class } describe 'find_for_git_client' do - it 'recognizes CI' do - token = '123' + context 'build token' do + subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') } + + context 'for running build' do + let!(:build) { create(:ci_build, :running) } + let(:project) { build.project } + + before do + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token') + end + + it 'recognises user-less build' do + expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_capabilities)) + end + + it 'recognises user token' do + build.update(user: create(:user)) + + expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_capabilities)) + end + end + + context 'for non-running build' do + let!(:build) { create(:ci_build, :pending) } + let(:project) { build.project } + + before do + expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token') + end + + it 'denies authentication' do + expect(subject).to eq(Gitlab::Auth::Result.new) + end + end + end + + it 'recognizes other ci services' do project = create(:empty_project) - project.update_attributes(runners_token: token) + project.create_drone_ci_service(active: true) + project.drone_ci_service.update(token: 'token') ip = 'ip' - expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token') - expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci)) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'drone-ci-token') + expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_capabilities)) end it 'recognizes master passwords' do @@ -20,7 +56,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap)) + expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_capabilities)) end it 'recognizes OAuth tokens' do @@ -30,7 +66,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2') - expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth)) + expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_capabilities)) end it 'returns double nil for invalid credentials' do @@ -92,4 +128,30 @@ describe Gitlab::Auth, lib: true do end end end + + private + + def build_capabilities + [ + :read_project, + :build_download_code, + :build_read_container_image, + :build_create_container_image + ] + end + + def read_capabilities + [ + :read_project, + :download_code, + :read_container_image + ] + end + + def full_capabilities + read_capabilities + [ + :push_code, + :update_container_image + ] + end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index f12c9a370f7..77dce676cdb 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,10 +1,17 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do - let(:access) { Gitlab::GitAccess.new(actor, project, 'web') } + let(:access) { Gitlab::GitAccess.new(actor, project, 'web', capabilities: capabilities) } let(:project) { create(:project) } let(:user) { create(:user) } let(:actor) { user } + let(:capabilities) do + [ + :read_project, + :download_code, + :push_code + ] + end describe '#check with single protocols allowed' do def disable_protocol(protocol) @@ -111,6 +118,36 @@ describe Gitlab::GitAccess, lib: true do end end end + + describe 'build capabilities permissions' do + let(:capabilities) { build_capabilities } + + describe 'reporter user' do + before { project.team << [user, :reporter] } + + context 'pull code' do + it { expect(subject).to be_allowed } + end + end + + describe 'admin user' do + let(:user) { create(:admin) } + + context 'when member of the project' do + before { project.team << [user, :reporter] } + + context 'pull code' do + it { expect(subject).to be_allowed } + end + end + + context 'when is not member of the project' do + context 'pull code' do + it { expect(subject).not_to be_allowed } + end + end + end + end end describe 'push_access_check' do @@ -281,40 +318,58 @@ describe Gitlab::GitAccess, lib: true do admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) end end + end - describe 'deploy key permissions' do - let(:key) { create(:deploy_key) } - let(:actor) { key } + shared_examples 'can not push code' do + subject { access.check('git-receive-pack', '_any') } - context 'push code' do - subject { access.check('git-receive-pack', '_any') } + context 'when project is authorized' do + before { key.projects << project } - context 'when project is authorized' do - before { key.projects << project } + it { expect(subject).not_to be_allowed } + end + + context 'when unauthorized' do + context 'to public project' do + let(:project) { create(:project, :public) } it { expect(subject).not_to be_allowed } end - context 'when unauthorized' do - context 'to public project' do - let(:project) { create(:project, :public) } - - it { expect(subject).not_to be_allowed } - end - - context 'to internal project' do - let(:project) { create(:project, :internal) } + context 'to internal project' do + let(:project) { create(:project, :internal) } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } + end - context 'to private project' do - let(:project) { create(:project, :internal) } + context 'to private project' do + let(:project) { create(:project, :internal) } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } end end end + + describe 'build capabilities permissions' do + let(:capabilities) { build_capabilities } + + it_behaves_like 'cannot push code' + end + + describe 'deploy key permissions' do + let(:key) { create(:deploy_key) } + let(:actor) { key } + + it_behaves_like 'cannot push code' + end + + private + + def build_capabilities + [ + :read_project, + :build_download_code + ] + end end |