diff options
-rw-r--r-- | app/controllers/jwt_controller.rb | 2 | ||||
-rw-r--r-- | lib/api/internal.rb | 14 | ||||
-rw-r--r-- | lib/gitlab/auth.rb | 20 | ||||
-rw-r--r-- | spec/lib/gitlab/auth_spec.rb | 76 | ||||
-rw-r--r-- | spec/lib/gitlab/git_access_spec.rb | 101 |
5 files changed, 168 insertions, 45 deletions
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 7bf534d8732..ed5d28e0d2c 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -25,7 +25,7 @@ class JwtController < ApplicationController authenticate_with_http_basic do |login, password| @authentication_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) - render_403 unless @authentication_result.success? + render_403 unless @authentication_result.succeeded? end end 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/gitlab/auth.rb b/lib/gitlab/auth.rb index 6a55c50c3f3..7af9bb9a326 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,14 +1,8 @@ module Gitlab module Auth - class Result - attr_reader :user, :project, :type, :capabilities - - def initialize?(user = nil, project = nil, type = nil, capabilities = nil) - @user, @project, @type, @capabilities = user, project, type, capabilities - end - - def success? - user.present? || [:ci, :missing_personal_token].include?(type) + Result = Struct.new(:user, :project, :type, :capabilities) do + def succeeded? + user.present? || [:ci].include?(type) end end @@ -23,7 +17,7 @@ module Gitlab personal_access_token_check(login, password) || Result.new - rate_limit!(ip, success: result.success?, login: login) + rate_limit!(ip, success: result.succeeded?, login: login) result end @@ -94,7 +88,7 @@ module Gitlab :gitlab_or_ldap end - Result.new(user, type, nil, full_capabilities) + Result.new(user, nil, type, full_capabilities) end def oauth_access_token_check(login, password) @@ -111,7 +105,9 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, nil, :personal_token, full_capabilities) if user == validation + if user && user == validation + Result.new(user, nil, :personal_token, full_capabilities) + end end 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 |