Commit 92d5172a authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/security/gitlab@13-3-stable-ee

parent f4a969f7
...@@ -1199,7 +1199,7 @@ GEM ...@@ -1199,7 +1199,7 @@ GEM
railties (>= 3.2.0) railties (>= 3.2.0)
websocket-driver (0.7.1) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4) websocket-extensions (0.1.5)
wikicloth (0.8.1) wikicloth (0.8.1)
builder builder
expression_parser expression_parser
......
...@@ -38,8 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -38,8 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController
def new def new
if params[:provider] == 'aws' if params[:provider] == 'aws'
@aws_role = current_user.aws_role || Aws::Role.new @aws_role = Aws::Role.create_or_find_by!(user: current_user)
@aws_role.ensure_role_external_id!
@instance_types = load_instance_types.to_json @instance_types = load_instance_types.to_json
elsif params[:provider] == 'gcp' elsif params[:provider] == 'gcp'
...@@ -273,7 +272,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -273,7 +272,7 @@ class Clusters::ClustersController < Clusters::BaseController
end end
def aws_role_params def aws_role_params
params.require(:cluster).permit(:role_arn, :role_external_id) params.require(:cluster).permit(:role_arn)
end end
def generate_gcp_authorize_url def generate_gcp_authorize_url
......
# frozen_string_literal: true
module Ci
class AuthJobFinder
AuthError = Class.new(StandardError)
NotRunningJobError = Class.new(AuthError)
ErasedJobError = Class.new(AuthError)
DeletedProjectError = Class.new(AuthError)
def initialize(token:)
@token = token
end
def execute!
find_job_by_token.tap do |job|
next unless job
validate_job!(job)
end
end
def execute
execute!
rescue AuthError
end
private
attr_reader :token, :require_running, :raise_on_missing
def find_job_by_token
::Ci::Build.find_by_token(token)
end
def validate_job!(job)
validate_running_job!(job)
validate_job_not_erased!(job)
validate_project_presence!(job)
true
end
def validate_running_job!(job)
raise NotRunningJobError, 'Job is not running' unless job.running?
end
def validate_job_not_erased!(job)
raise ErasedJobError, 'Job has been erased!' if job.erased?
end
def validate_project_presence!(job)
if job.project.nil? || job.project.pending_delete?
raise DeletedProjectError, 'Project has been deleted!'
end
end
end
end
...@@ -9,6 +9,7 @@ module Aws ...@@ -9,6 +9,7 @@ module Aws
validates :role_external_id, uniqueness: true, length: { in: 1..64 } validates :role_external_id, uniqueness: true, length: { in: 1..64 }
validates :role_arn, validates :role_arn,
length: 1..2048, length: 1..2048,
allow_nil: true,
format: { format: {
with: Gitlab::Regex.aws_arn_regex, with: Gitlab::Regex.aws_arn_regex,
message: Gitlab::Regex.aws_arn_regex_message message: Gitlab::Regex.aws_arn_regex_message
......
...@@ -10,6 +10,9 @@ module Ci ...@@ -10,6 +10,9 @@ module Ci
elsif job_from_token elsif job_from_token
create_pipeline_from_job(job_from_token) create_pipeline_from_job(job_from_token)
end end
rescue Ci::AuthJobFinder::AuthError => e
error(e.message, 401)
end end
private private
...@@ -41,8 +44,6 @@ module Ci ...@@ -41,8 +44,6 @@ module Ci
# this check is to not leak the presence of the project if user cannot read it # this check is to not leak the presence of the project if user cannot read it
return unless can?(job.user, :read_project, project) return unless can?(job.user, :read_project, project)
return error("400 Job has to be running", 400) unless job.running?
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref]) pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
.execute(:pipeline, ignore_skip_ci: true) do |pipeline| .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
source = job.sourced_pipelines.build( source = job.sourced_pipelines.build(
...@@ -64,7 +65,7 @@ module Ci ...@@ -64,7 +65,7 @@ module Ci
def job_from_token def job_from_token
strong_memoize(:job) do strong_memoize(:job) do
Ci::Build.find_by_token(params[:token].to_s) Ci::AuthJobFinder.new(token: params[:token].to_s).execute!
end end
end end
......
...@@ -9,6 +9,7 @@ module Clusters ...@@ -9,6 +9,7 @@ module Clusters
ERRORS = [ ERRORS = [
ActiveRecord::RecordInvalid, ActiveRecord::RecordInvalid,
ActiveRecord::RecordNotFound,
Clusters::Aws::FetchCredentialsService::MissingRoleError, Clusters::Aws::FetchCredentialsService::MissingRoleError,
::Aws::Errors::MissingCredentialsError, ::Aws::Errors::MissingCredentialsError,
::Aws::STS::Errors::ServiceError ::Aws::STS::Errors::ServiceError
...@@ -20,7 +21,8 @@ module Clusters ...@@ -20,7 +21,8 @@ module Clusters
end end
def execute def execute
@role = create_or_update_role! ensure_role_exists!
update_role_arn!
Response.new(:ok, credentials) Response.new(:ok, credentials)
rescue *ERRORS => e rescue *ERRORS => e
...@@ -33,14 +35,12 @@ module Clusters ...@@ -33,14 +35,12 @@ module Clusters
attr_reader :role, :params attr_reader :role, :params
def create_or_update_role! def ensure_role_exists!
if role = user.aws_role @role = ::Aws::Role.find_by_user_id!(user.id)
role.update!(params) end
role def update_role_arn!
else role.update!(params)
user.create_aws_role!(params)
end
end end
def credentials def credentials
......
---
title: Allow only running job tokens for API authentication
merge_request:
author:
type: security
---
title: Change conan api to use proper workhorse validation
merge_request:
author:
type: security
---
title: Persist EKS External ID before presenting it to the user
merge_request:
author:
type: security
---
title: Prevent project maintainers from editing group badges
merge_request:
author:
type: security
---
title: Update websocket-extensions gem to 0.1.5
merge_request:
author: Vitor Meireles De Sousa
type: security
# frozen_string_literal: true
class ChangeAwsRolesRoleArnNull < ActiveRecord::Migration[6.0]
DOWNTIME = false
EXAMPLE_ARN = 'arn:aws:iam::000000000000:role/example-role'
def up
change_column_null :aws_roles, :role_arn, true
end
def down
# Records may now exist with nulls, so we must fill them with a dummy value
change_column_null :aws_roles, :role_arn, false, EXAMPLE_ARN
end
end
6b8fa09c9700c494eeb5151f43064f1656eaaea804742629b7bd66483e2b04cb
\ No newline at end of file
...@@ -9514,7 +9514,7 @@ CREATE TABLE public.aws_roles ( ...@@ -9514,7 +9514,7 @@ CREATE TABLE public.aws_roles (
user_id integer NOT NULL, user_id integer NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL,
role_arn character varying(2048) NOT NULL, role_arn character varying(2048),
role_external_id character varying(64) NOT NULL role_external_id character varying(64) NOT NULL
); );
......
...@@ -109,9 +109,10 @@ module API ...@@ -109,9 +109,10 @@ module API
end end
put ":id/badges/:badge_id" do put ":id/badges/:badge_id" do
source = find_source_if_admin(source_type) source = find_source_if_admin(source_type)
badge = find_badge(source)
badge = ::Badges::UpdateService.new(declared_params(include_missing: false)) badge = ::Badges::UpdateService.new(declared_params(include_missing: false))
.execute(find_badge(source)) .execute(badge)
if badge.valid? if badge.valid?
present_badges(source, badge) present_badges(source, badge)
...@@ -130,10 +131,6 @@ module API ...@@ -130,10 +131,6 @@ module API
source = find_source_if_admin(source_type) source = find_source_if_admin(source_type)
badge = find_badge(source) badge = find_badge(source)
if badge.is_a?(GroupBadge) && source.is_a?(Project)
error!('To delete a Group badge please use the Group endpoint', 403)
end
destroy_conditionally!(badge) destroy_conditionally!(badge)
end end
end end
......
...@@ -26,6 +26,8 @@ module API ...@@ -26,6 +26,8 @@ module API
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
before do before do
require_packages_enabled! require_packages_enabled!
...@@ -259,7 +261,7 @@ module API ...@@ -259,7 +261,7 @@ module API
end end
params do params do
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
end end
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download recipe files' do desc 'Download recipe files' do
...@@ -277,7 +279,7 @@ module API ...@@ -277,7 +279,7 @@ module API
end end
params do params do
use :workhorse_upload_params requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
...@@ -300,7 +302,7 @@ module API ...@@ -300,7 +302,7 @@ module API
params do params do
requires :conan_package_reference, type: String, desc: 'Conan Package ID' requires :conan_package_reference, type: String, desc: 'Conan Package ID'
requires :package_revision, type: String, desc: 'Conan Package Revision' requires :package_revision, type: String, desc: 'Conan Package Revision'
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
end end
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download package files' do desc 'Download package files' do
...@@ -328,7 +330,7 @@ module API ...@@ -328,7 +330,7 @@ module API
end end
params do params do
use :workhorse_upload_params requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
......
...@@ -6,7 +6,13 @@ module API ...@@ -6,7 +6,13 @@ module API
include ::API::Helpers::MembersHelpers include ::API::Helpers::MembersHelpers
def find_badge(source) def find_badge(source)
source.badges.find(params[:badge_id]) badge_id = params[:badge_id]
if source.is_a?(Project)
source.project_badges.find(badge_id)
else
source.badges.find(badge_id)
end
end end
def present_badges(source, records, options = {}) def present_badges(source, records, options = {})
......
...@@ -133,7 +133,7 @@ module API ...@@ -133,7 +133,7 @@ module API
end end
def track_push_package_event def track_push_package_event
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params['file.size'] > 0 if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
track_event('push_package') track_event('push_package')
end end
end end
...@@ -147,9 +147,9 @@ module API ...@@ -147,9 +147,9 @@ module API
end end
def create_package_file_with_type(file_type, current_package) def create_package_file_with_type(file_type, current_package)
unless params['file.size'] == 0 unless params[:file].size == 0 # rubocop: disable Style/ZeroLengthPredicate
# conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0 # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0
::Packages::Conan::CreatePackageFileService.new(current_package, uploaded_package_file, params.merge(conan_file_type: file_type)).execute ::Packages::Conan::CreatePackageFileService.new(current_package, params[:file], params.merge(conan_file_type: file_type)).execute
end end
end end
...@@ -220,7 +220,7 @@ module API ...@@ -220,7 +220,7 @@ module API
return unless token return unless token
::Ci::Build.find_by_token(token.access_token_id.to_s) ::Ci::AuthJobFinder.new(token: token.access_token_id.to_s).execute
end end
def decode_oauth_token_from_jwt def decode_oauth_token_from_jwt
......
...@@ -23,7 +23,7 @@ module API ...@@ -23,7 +23,7 @@ module API
return unless token return unless token
::Ci::Build.find_by_token(token) ::Ci::AuthJobFinder.new(token: token).execute
end end
def find_deploy_token_from_http_basic_auth def find_deploy_token_from_http_basic_auth
......
...@@ -69,9 +69,7 @@ module Gitlab ...@@ -69,9 +69,7 @@ module Gitlab
current_request.env[JOB_TOKEN_HEADER].presence current_request.env[JOB_TOKEN_HEADER].presence
return unless token return unless token
job = ::Ci::Build.find_by_token(token) job = find_valid_running_job_by_token!(token)
raise UnauthorizedError unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user job.user
...@@ -84,9 +82,7 @@ module Gitlab ...@@ -84,9 +82,7 @@ module Gitlab
return unless login.present? && password.present? return unless login.present? && password.present?
return unless ::Gitlab::Auth::CI_JOB_USER == login return unless ::Gitlab::Auth::CI_JOB_USER == login
job = ::Ci::Build.find_by_token(password) job = find_valid_running_job_by_token!(password)
raise UnauthorizedError unless job
job.user job.user
end end
...@@ -179,7 +175,7 @@ module Gitlab ...@@ -179,7 +175,7 @@ module Gitlab
token = parsed_oauth_token token = parsed_oauth_token
return unless token return unless token
job = ::Ci::Build.find_by_token(token) job = ::Ci::AuthJobFinder.new(token: token).execute
return unless job return unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
...@@ -304,6 +300,12 @@ module Gitlab ...@@ -304,6 +300,12 @@ module Gitlab
def blob_request? def blob_request?
current_request.path.include?('/raw/') current_request.path.include?('/raw/')
end end
def find_valid_running_job_by_token!(token)
::Ci::AuthJobFinder.new(token: token).execute.tap do |job|
raise UnauthorizedError unless job
end
end
end end
end end
end end
......
...@@ -6,11 +6,6 @@ module Gitlab ...@@ -6,11 +6,6 @@ module Gitlab
CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze
CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze
def conan_file_name_regex
@conan_file_name_regex ||=
%r{\A#{(CONAN_RECIPE_FILES + CONAN_PACKAGE_FILES).join("|")}\z}.freeze
end
def conan_package_reference_regex def conan_package_reference_regex
@conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze @conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze
end end
......
...@@ -99,7 +99,9 @@ RSpec.describe Admin::ClustersController do ...@@ -99,7 +99,9 @@ RSpec.describe Admin::ClustersController do
end end
describe 'GET #new' do describe 'GET #new' do
def get_new(provider: 'gcp') let(:user) { admin }
def go(provider: 'gcp')
get :new, params: { provider: provider } get :new, params: { provider: provider }
end end
...@@ -112,7 +114,7 @@ RSpec.describe Admin::ClustersController do ...@@ -112,7 +114,7 @@ RSpec.describe Admin::ClustersController do
context 'when selected provider is gke and no valid gcp token exists' do context 'when selected provider is gke and no valid gcp token exists' do
it 'redirects to gcp authorize_url' do it 'redirects to gcp authorize_url' do
get_new go
expect(response).to redirect_to(assigns(:authorize_url)) expect(response).to redirect_to(assigns(:authorize_url))
end end
...@@ -125,7 +127,7 @@ RSpec.describe Admin::ClustersController do ...@@ -125,7 +127,7 @@ RSpec.describe Admin::ClustersController do
end end
it 'does not have authorize_url' do it 'does not have authorize_url' do
get_new go
expect(assigns(:authorize_url)).to be_nil expect(assigns(:authorize_url)).to be_nil
end end
...@@ -137,7 +139,7 @@ RSpec.describe Admin::ClustersController do ...@@ -137,7 +139,7 @@ RSpec.describe Admin::ClustersController do
end end
it 'has new object' do it 'has new object' do
get_new go
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
end end
...@@ -158,16 +160,18 @@ RSpec.describe Admin::ClustersController do ...@@ -158,16 +160,18 @@ RSpec.describe Admin::ClustersController do
describe 'functionality for existing cluster' do describe 'functionality for existing cluster' do
it 'has new object' do it 'has new object' do
get_new go
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
end end
end end
include_examples 'GET new cluster shared examples'
describe 'security' do describe 'security' do
it { expect { get_new }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { get_new }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:user) }
it { expect { get_new }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }
end end
end end
...@@ -424,14 +428,13 @@ RSpec.describe Admin::ClustersController do ...@@ -424,14 +428,13 @@ RSpec.describe Admin::ClustersController do
end end
describe 'POST authorize AWS role for EKS cluster' do describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } let!(:role) { create(:aws_role, user: admin) }
let(:role_external_id) { '12345' }
let(:role_arn) { 'arn:new-role' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
role_arn: role_arn, role_arn: role_arn
role_external_id: role_external_id
} }
} }
end end
...@@ -445,28 +448,32 @@ RSpec.describe Admin::ClustersController do ...@@ -445,28 +448,32 @@ RSpec.describe Admin::ClustersController do
.and_return(double(execute: double)) .and_return(double(execute: double))
end end
it 'creates an Aws::Role record' do it 'updates the associated role with the supplied ARN' do
expect { go }.to change { Aws::Role.count } go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
role = Aws::Role.last
expect(role.user).to eq admin
expect(role.role_arn).to eq role_arn
expect(role.role_external_id).to eq role_external_id
end end
context 'role cannot be created' do context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' } let(:role_arn) { 'invalid-role' }
it 'does not create a record' do it 'does not update the associated role' do
expect { go }.not_to change { Aws::Role.count } expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
end end
describe 'security' do describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }
......
...@@ -180,6 +180,8 @@ RSpec.describe Groups::ClustersController do ...@@ -180,6 +180,8 @@ RSpec.describe Groups::ClustersController do
end end
end end
include_examples 'GET new cluster shared examples'
describe 'security' do describe 'security' do
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) } it { expect { go }.to be_allowed_for(:owner).of(group) }
...@@ -493,14 +495,13 @@ RSpec.describe Groups::ClustersController do ...@@ -493,14 +495,13 @@ RSpec.describe Groups::ClustersController do
end end
describe 'POST authorize AWS role for EKS cluster' do describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } let!(:role) { create(:aws_role, user: user) }
let(:role_external_id) { '12345' }
let(:role_arn) { 'arn:new-role' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
role_arn: role_arn, role_arn: role_arn
role_external_id: role_external_id
} }
} }
end end
...@@ -514,28 +515,32 @@ RSpec.describe Groups::ClustersController do ...@@ -514,28 +515,32 @@ RSpec.describe Groups::ClustersController do
.and_return(double(execute: double)) .and_return(double(execute: double))
end end
it 'creates an Aws::Role record' do it 'updates the associated role with the supplied ARN' do
expect { go }.to change { Aws::Role.count } go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
role = Aws::Role.last
expect(role.user).to eq user
expect(role.role_arn).to eq role_arn
expect(role.role_external_id).to eq role_external_id
end end
context 'role cannot be created' do context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' } let(:role_arn) { 'invalid-role' }
it 'does not create a record' do it 'does not update the associated role' do
expect { go }.not_to change { Aws::Role.count } expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
end end
describe 'security' do describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) } it { expect { go }.to be_allowed_for(:owner).of(group) }
it { expect { go }.to be_allowed_for(:maintainer).of(group) } it { expect { go }.to be_allowed_for(:maintainer).of(group) }
......
...@@ -183,6 +183,8 @@ RSpec.describe Projects::ClustersController do ...@@ -183,6 +183,8 @@ RSpec.describe Projects::ClustersController do
end end
end end
include_examples 'GET new cluster shared examples'
describe 'security' do describe 'security' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin) expect { go }.to be_allowed_for(:admin)
...@@ -521,14 +523,13 @@ RSpec.describe Projects::ClustersController do ...@@ -521,14 +523,13 @@ RSpec.describe Projects::ClustersController do
end end
describe 'POST authorize AWS role for EKS cluster' do describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } let!(:role) { create(:aws_role, user: user) }
let(:role_external_id) { '12345' }
let(:role_arn) { 'arn:new-role' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
role_arn: role_arn, role_arn: role_arn
role_external_id: role_external_id
} }
} }
end end
...@@ -542,28 +543,32 @@ RSpec.describe Projects::ClustersController do ...@@ -542,28 +543,32 @@ RSpec.describe Projects::ClustersController do
.and_return(double(execute: double)) .and_return(double(execute: double))
end end
it 'creates an Aws::Role record' do it 'updates the associated role with the supplied ARN' do
expect { go }.to change { Aws::Role.count } go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
role = Aws::Role.last
expect(role.user).to eq user
expect(role.role_arn).to eq role_arn
expect(role.role_external_id).to eq role_external_id
end end
context 'role cannot be created' do context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' } let(:role_arn) { 'invalid-role' }
it 'does not create a record' do it 'does not update the associated role' do
expect { go }.not_to change { Aws::Role.count } expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
end end
describe 'security' do describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin) expect { go }.to be_allowed_for(:admin)
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::AuthJobFinder do
let_it_be(:job, reload: true) { create(:ci_build, status: :running) }
let(:token) { job.token }
subject(:finder) do
described_class.new(token: token)
end
describe '#execute!' do
subject(:execute) { finder.execute! }
it { is_expected.to eq(job) }
it 'raises error if the job is not running' do
job.success!
expect { execute }.to raise_error described_class::NotRunningJobError, 'Job is not running'
end
it 'raises error if the job is erased' do
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
expect(job).to receive(:erased?).and_return(true)
expect { execute }.to raise_error described_class::ErasedJobError, 'Job has been erased!'
end
it 'raises error if the the project is missing' do
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
expect(job).to receive(:project).and_return(nil)
expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!'
end
it 'raises error if the the project is being removed' do
project = double(Project)
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
expect(job).to receive(:project).twice.and_return(project)
expect(project).to receive(:pending_delete?).and_return(true)
expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!'
end
context 'with wrong job token' do
let(:token) { 'missing' }
it { is_expected.to be_nil }
end
end
describe '#execute' do
subject(:execute) { finder.execute }
before do
job.success!
end
it { is_expected.to be_nil }
end
end
...@@ -11,7 +11,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do ...@@ -11,7 +11,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
describe '#find_job_from_http_basic_auth' do describe '#find_job_from_http_basic_auth' do
let_it_be(:user) { personal_access_token.user } let_it_be(:user) { personal_access_token.user }
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
let(:password) { job.token } let(:password) { job.token }
let(:headers) { { Authorization: basic_http_auth(username, password) } } let(:headers) { { Authorization: basic_http_auth(username, password) } }
...@@ -23,6 +23,14 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do ...@@ -23,6 +23,14 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
context 'with a valid Authorization header' do context 'with a valid Authorization header' do
it { is_expected.to eq job } it { is_expected.to eq job }
context 'when the job is not running' do
before do
job.update!(status: :failed)
end
it { is_expected.to be nil }
end
end end
context 'with an invalid Authorization header' do context 'with an invalid Authorization header' do
......
...@@ -37,11 +37,29 @@ RSpec.describe Gitlab::Auth::AuthFinders do ...@@ -37,11 +37,29 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
it "return user if token is valid" do context 'with a running job' do
set_token(job.token) before do
job.update!(status: :running)
end
it 'return user if token is valid' do
set_token(job.token)
expect(subject).to eq(user)
expect(@current_authenticated_job).to eq job
end
end
expect(subject).to eq(user) context 'with a job that is not running' do
expect(@current_authenticated_job).to eq job before do
job.update!(status: :failed)
end
it 'returns an Unauthorized exception' do
set_token(job.token)
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end end
end end
end end
...@@ -557,7 +575,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do ...@@ -557,7 +575,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'with CI username' do context 'with CI username' do
let(:username) { ::Gitlab::Auth::CI_JOB_USER } let(:username) { ::Gitlab::Auth::CI_JOB_USER }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build, user: user) } let(:build) { create(:ci_build, user: user, status: :running) }
it 'returns nil without password' do it 'returns nil without password' do
set_basic_auth_header(username, nil) set_basic_auth_header(username, nil)
...@@ -576,6 +594,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do ...@@ -576,6 +594,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
it 'returns exception if the job is not running' do
set_basic_auth_header(username, build.token)
build.success!
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end end
end end
...@@ -586,7 +611,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do ...@@ -586,7 +611,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'with a job token' do context 'with a job token' do
let(:route_authentication_setting) { { job_token_allowed: true } } let(:route_authentication_setting) { { job_token_allowed: true } }
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
before do before do
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}" env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
...@@ -641,7 +666,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do ...@@ -641,7 +666,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end end
describe '#find_user_from_job_token' do describe '#find_user_from_job_token' do
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
let(:route_authentication_setting) { { job_token_allowed: true } } let(:route_authentication_setting) { { job_token_allowed: true } }
subject { find_user_from_job_token } subject { find_user_from_job_token }
...@@ -666,6 +691,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do ...@@ -666,6 +691,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
it 'returns exception if the job is not running' do
set_header(described_class::JOB_TOKEN_HEADER, job.token)
job.success!
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
context 'when route is not allowed to be authenticated' do context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } } let(:route_authentication_setting) { { job_token_allowed: false } }
......
...@@ -88,7 +88,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do ...@@ -88,7 +88,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
describe '#find_user_from_job_token' do describe '#find_user_from_job_token' do
let!(:user) { build(:user) } let!(:user) { build(:user) }
let!(:job) { build(:ci_build, user: user) } let!(:job) { build(:ci_build, user: user, status: :running) }
before do before do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token' env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token'
...@@ -97,13 +97,18 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do ...@@ -97,13 +97,18 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
context 'with API requests' do context 'with API requests' do
before do before do
env['SCRIPT_NAME'] = '/api/endpoint' env['SCRIPT_NAME'] = '/api/endpoint'
expect(::Ci::Build).to receive(:find_by_token).with('token').and_return(job)
end end
it 'tries to find the user' do it 'tries to find the user' do
expect(::Ci::Build).to receive(:find_by_token).and_return(job)
expect(subject.find_sessionless_user([:api])).to eq user expect(subject.find_sessionless_user([:api])).to eq user
end end
it 'returns nil if the job is not running' do
job.status = :success
expect(subject.find_sessionless_user([:api])).to be_blank
end
end end
context 'without API requests' do context 'without API requests' do
......
...@@ -180,15 +180,6 @@ RSpec.describe Gitlab::Regex do ...@@ -180,15 +180,6 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('foo/bar') } it { is_expected.not_to match('foo/bar') }
end end
describe '.conan_file_name_regex' do
subject { described_class.conan_file_name_regex }
it { is_expected.to match('conanfile.py') }
it { is_expected.to match('conan_package.tgz') }
it { is_expected.not_to match('foo.txt') }
it { is_expected.not_to match('!!()()') }
end
describe '.conan_package_reference_regex' do describe '.conan_package_reference_regex' do
subject { described_class.conan_package_reference_regex } subject { described_class.conan_package_reference_regex }
......
...@@ -29,6 +29,12 @@ RSpec.describe Aws::Role do ...@@ -29,6 +29,12 @@ RSpec.describe Aws::Role do
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
context 'ARN is nil' do
let(:role_arn) { }
it { is_expected.to be_truthy }
end
end end
end end
......
...@@ -332,10 +332,32 @@ RSpec.describe API::Badges do ...@@ -332,10 +332,32 @@ RSpec.describe API::Badges do
context 'when deleting a badge' do context 'when deleting a badge' do
context 'and the source is a project' do context 'and the source is a project' do
let(:badge) { project.group.badges.first }
it 'cannot delete badges owned by the project group' do it 'cannot delete badges owned by the project group' do
delete api("/projects/#{project.id}/badges/#{project_group.badges.first.id}", maintainer) expect do
delete api("/projects/#{project.id}/badges/#{badge.id}", maintainer)
expect(response).to have_gitlab_http_status(:not_found)
end.not_to change { badge.reload.persisted? }
end
end
end
context 'when updating a badge' do
context 'and the source is a project' do
let(:badge) { project.group.badges.first }
let(:example_name) { 'BadgeName' }
let(:example_url) { 'http://www.example.com' }
let(:example_url2) { 'http://www.example1.com' }
it 'cannot update badges owned by the project group' do
expect do
put api("/projects/#{project.id}/badges/#{badge.id}", maintainer),
params: { name: example_name, link_url: example_url, image_url: example_url2 }
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:not_found)
end.not_to change { badge.reload.updated_at }
end end
end end
end end
......
...@@ -13,7 +13,7 @@ RSpec.describe API::ConanPackages do ...@@ -13,7 +13,7 @@ RSpec.describe API::ConanPackages do
let(:base_secret) { SecureRandom.base64(64) } let(:base_secret) { SecureRandom.base64(64) }
let(:auth_token) { personal_access_token.token } let(:auth_token) { personal_access_token.token }
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
let(:job_token) { job.token } let(:job_token) { job.token }
let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
...@@ -93,6 +93,14 @@ RSpec.describe API::ConanPackages do ...@@ -93,6 +93,14 @@ RSpec.describe API::ConanPackages do
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'responds with 401 Unauthorized when the job is not running' do
job.update!(status: :failed)
jwt = build_jwt_from_job(job)
get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
expect(response).to have_gitlab_http_status(:unauthorized)
end
context 'packages feature disabled' do context 'packages feature disabled' do
it 'responds with 404 Not Found' do it 'responds with 404 Not Found' do
stub_packages_setting(enabled: false) stub_packages_setting(enabled: false)
...@@ -233,6 +241,18 @@ RSpec.describe API::ConanPackages do ...@@ -233,6 +241,18 @@ RSpec.describe API::ConanPackages do
end end
end end
shared_examples 'rejects invalid file_name' do |invalid_file_name|
let(:file_name) { invalid_file_name }
context 'with invalid file_name' do
it 'returns 400' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
shared_examples 'rejects recipe for invalid project' do shared_examples 'rejects recipe for invalid project' do
context 'with invalid recipe path' do context 'with invalid recipe path' do
let(:recipe_path) { 'aa/bb/not-existing-project/ccc' } let(:recipe_path) { 'aa/bb/not-existing-project/ccc' }
...@@ -685,8 +705,6 @@ RSpec.describe API::ConanPackages do ...@@ -685,8 +705,6 @@ RSpec.describe API::ConanPackages do
context 'without a file from workhorse' do context 'without a file from workhorse' do
let(:params) { { file: nil } } let(:params) { { file: nil } }
it_behaves_like 'package workhorse uploads'
it 'rejects the request' do it 'rejects the request' do
subject subject
...@@ -694,6 +712,10 @@ RSpec.describe API::ConanPackages do ...@@ -694,6 +712,10 @@ RSpec.describe API::ConanPackages do
end end
end end
context 'with a file' do
it_behaves_like 'package workhorse uploads'
end
context 'without a token' do context 'without a token' do
it 'rejects request without a token' do it 'rejects request without a token' do
headers_with_token.delete('HTTP_AUTHORIZATION') headers_with_token.delete('HTTP_AUTHORIZATION')
...@@ -852,16 +874,22 @@ RSpec.describe API::ConanPackages do ...@@ -852,16 +874,22 @@ RSpec.describe API::ConanPackages do
end end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token } let(:file_name) { 'conanfile.py' }
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'workhorse authorization' it_behaves_like 'workhorse authorization'
end end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token } let(:file_name) { 'conaninfo.txt' }
subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'workhorse authorization' it_behaves_like 'workhorse authorization'
end end
...@@ -875,11 +903,13 @@ RSpec.describe API::ConanPackages do ...@@ -875,11 +903,13 @@ RSpec.describe API::ConanPackages do
method: :put, method: :put,
file_key: :file, file_key: :file,
params: params, params: params,
send_rewritten_field: true,
headers: headers_with_token headers: headers_with_token
) )
end end
it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file' it_behaves_like 'uploads a package file'
end end
...@@ -893,12 +923,15 @@ RSpec.describe API::ConanPackages do ...@@ -893,12 +923,15 @@ RSpec.describe API::ConanPackages do
method: :put, method: :put,
file_key: :file, file_key: :file,
params: params, params: params,
headers: headers_with_token headers: headers_with_token,
send_rewritten_field: true
) )
end end
it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'uploads a package file' it_behaves_like 'uploads a package file'
context 'tracking the conan_package.tgz upload' do context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY } let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
......
...@@ -11,7 +11,7 @@ RSpec.describe API::GoProxy do ...@@ -11,7 +11,7 @@ RSpec.describe API::GoProxy do
let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" } let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user } let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user }
let_it_be(:job) { create :ci_build, user: user } let_it_be(:job) { create :ci_build, user: user, status: :running }
let_it_be(:pa_token) { create :personal_access_token, user: user } let_it_be(:pa_token) { create :personal_access_token, user: user }
let_it_be(:modules) do let_it_be(:modules) do
...@@ -393,6 +393,13 @@ RSpec.describe API::GoProxy do ...@@ -393,6 +393,13 @@ RSpec.describe API::GoProxy do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'returns unauthorized with a failed job token' do
job.update!(status: :failed)
get_resource(oauth_access_token: job)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'returns unauthorized with no authentication' do it 'returns unauthorized with no authentication' do
get_resource get_resource
......
...@@ -12,7 +12,7 @@ RSpec.describe API::MavenPackages do ...@@ -12,7 +12,7 @@ RSpec.describe API::MavenPackages do
let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first } let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first }
let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first } let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job) { create(:ci_build, user: user) } let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
...@@ -102,11 +102,25 @@ RSpec.describe API::MavenPackages do ...@@ -102,11 +102,25 @@ RSpec.describe API::MavenPackages do
end end
shared_examples 'downloads with a job token' do shared_examples 'downloads with a job token' do
it 'allows download with job token' do context 'with a running job' do
download_file(package_file.file_name, job_token: job.token) it 'allows download with job token' do
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream') expect(response.media_type).to eq('application/octet-stream')
end
end
context 'with a finished job' do
before do
job.update!(status: :failed)
end
it 'returns unauthorized error' do
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end end
end end
...@@ -557,13 +571,20 @@ RSpec.describe API::MavenPackages do ...@@ -557,13 +571,20 @@ RSpec.describe API::MavenPackages do
expect(jar_file.file_name).to eq(file_upload.original_filename) expect(jar_file.file_name).to eq(file_upload.original_filename)
end end
it 'allows upload with job token' do it 'allows upload with running job token' do
upload_file(params.merge(job_token: job.token)) upload_file(params.merge(job_token: job.token))
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
end end
it 'rejects upload without running job token' do
job.update!(status: :failed)
upload_file(params.merge(job_token: job.token))
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'allows upload with deploy token' do it 'allows upload with deploy token' do
upload_file(params, headers_with_deploy_token) upload_file(params, headers_with_deploy_token)
......
...@@ -12,7 +12,7 @@ RSpec.describe API::NpmPackages do ...@@ -12,7 +12,7 @@ RSpec.describe API::NpmPackages do
let_it_be(:package, reload: true) { create(:npm_package, project: project) } let_it_be(:package, reload: true) { create(:npm_package, project: project) }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) } let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job) { create(:ci_build, user: user) } let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
...@@ -27,12 +27,19 @@ RSpec.describe API::NpmPackages do ...@@ -27,12 +27,19 @@ RSpec.describe API::NpmPackages do
expect_a_valid_package_response expect_a_valid_package_response
end end
it 'returns the package info with job token' do it 'returns the package info with running job token' do
get_package_with_job_token(package) get_package_with_job_token(package)
expect_a_valid_package_response expect_a_valid_package_response
end end
it 'denies request without running job token' do
job.update!(status: :success)
get_package_with_job_token(package)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'denies request without oauth token' do it 'denies request without oauth token' do
get_package(package) get_package(package)
......
...@@ -80,7 +80,7 @@ RSpec.describe API::NugetPackages do ...@@ -80,7 +80,7 @@ RSpec.describe API::NugetPackages do
end end
with_them do with_them do
let(:job) { user_token ? create(:ci_build, project: project, user: user) : double(token: 'wrong') } let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') }
let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) } let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) }
subject { get api(url), headers: headers } subject { get api(url), headers: headers }
......
...@@ -671,12 +671,20 @@ RSpec.describe API::Releases do ...@@ -671,12 +671,20 @@ RSpec.describe API::Releases do
end end
context 'when a valid token is provided' do context 'when a valid token is provided' do
it 'creates the release' do it 'creates the release for a running job' do
job.update!(status: :running)
post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token) post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token)
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
expect(project.releases.last.description).to eq('Another nice release') expect(project.releases.last.description).to eq('Another nice release')
end end
it 'returns an :unauthorized error for a completed job' do
job.success!
post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end end
end end
......
...@@ -72,7 +72,7 @@ RSpec.describe API::Terraform::State do ...@@ -72,7 +72,7 @@ RSpec.describe API::Terraform::State do
let(:auth_header) { job_basic_auth_header(job) } let(:auth_header) { job_basic_auth_header(job) }
context 'with maintainer permissions' do context 'with maintainer permissions' do
let(:job) { create(:ci_build, project: project, user: maintainer) } let(:job) { create(:ci_build, status: :running, project: project, user: maintainer) }
it 'returns terraform state belonging to a project of given state name' do it 'returns terraform state belonging to a project of given state name' do
request request
...@@ -81,6 +81,13 @@ RSpec.describe API::Terraform::State do ...@@ -81,6 +81,13 @@ RSpec.describe API::Terraform::State do
expect(response.body).to eq(state.file.read) expect(response.body).to eq(state.file.read)
end end
it 'returns unauthorized if the the job is not running' do
job.update!(status: :failed)
request
expect(response).to have_gitlab_http_status(:unauthorized)
end
context 'for a project that does not exist' do context 'for a project that does not exist' do
let(:project_id) { '0000' } let(:project_id) { '0000' }
...@@ -93,7 +100,7 @@ RSpec.describe API::Terraform::State do ...@@ -93,7 +100,7 @@ RSpec.describe API::Terraform::State do
end end
context 'with developer permissions' do context 'with developer permissions' do
let(:job) { create(:ci_build, project: project, user: developer) } let(:job) { create(:ci_build, status: :running, project: project, user: developer) }
it 'returns terraform state belonging to a project of given state name' do it 'returns terraform state belonging to a project of given state name' do
request request
......
...@@ -106,9 +106,23 @@ RSpec.describe Ci::PipelineTriggerService do ...@@ -106,9 +106,23 @@ RSpec.describe Ci::PipelineTriggerService do
let(:params) { { token: job.token, ref: 'master', variables: nil } } let(:params) { { token: job.token, ref: 'master', variables: nil } }
let(:job) { create(:ci_build, :success, pipeline: pipeline, user: user) } let(:job) { create(:ci_build, :success, pipeline: pipeline, user: user) }
it 'does nothing' do it 'does nothing', :aggregate_failures do
expect { result }.not_to change { Ci::Pipeline.count }
expect(result[:message]).to eq('Job is not running')
expect(result[:http_status]).to eq(401)
end
end
context 'when job does not have a project' do
let(:params) { { token: job.token, ref: 'master', variables: nil } }
let(:job) { create(:ci_build, status: :running, pipeline: pipeline, user: user) }
it 'does nothing', :aggregate_failures do
job.update!(project: nil)
expect { result }.not_to change { Ci::Pipeline.count } expect { result }.not_to change { Ci::Pipeline.count }
expect(result[:message]).to eq('400 Job has to be running') expect(result[:message]).to eq('Project has been deleted!')
expect(result[:http_status]).to eq(401)
end end
end end
......
...@@ -3,47 +3,34 @@ ...@@ -3,47 +3,34 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Clusters::Aws::AuthorizeRoleService do RSpec.describe Clusters::Aws::AuthorizeRoleService do
let(:user) { create(:user) } subject { described_class.new(user, params: params).execute }
let(:role) { create(:aws_role) }
let(:user) { role.user }
let(:credentials) { instance_double(Aws::Credentials) } let(:credentials) { instance_double(Aws::Credentials) }
let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) } let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) }
let(:role_arn) { 'arn:my-role' }
let(:params) do let(:params) do
params = ActionController::Parameters.new({ params = ActionController::Parameters.new({
cluster: { cluster: {
role_arn: 'arn:my-role', role_arn: role_arn
role_external_id: 'external-id'
} }
}) })
params.require(:cluster).permit(:role_arn, :role_external_id) params.require(:cluster).permit(:role_arn)
end end
subject { described_class.new(user, params: params).execute }
before do before do
allow(Clusters::Aws::FetchCredentialsService).to receive(:new) allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
.with(instance_of(Aws::Role)).and_return(credentials_service) .with(instance_of(Aws::Role)).and_return(credentials_service)
end end
context 'role does not exist' do context 'role exists' do
it 'creates an Aws::Role record and returns a set of credentials' do
expect(user).to receive(:create_aws_role!)
.with(params).and_call_original
expect(subject.status).to eq(:ok)
expect(subject.body).to eq(credentials)
end
end
context 'role already exists' do
let(:role) { create(:aws_role, user: user) }
it 'updates the existing Aws::Role record and returns a set of credentials' do it 'updates the existing Aws::Role record and returns a set of credentials' do
expect(role).to receive(:update!)
.with(params).and_call_original
expect(subject.status).to eq(:ok) expect(subject.status).to eq(:ok)
expect(subject.body).to eq(credentials) expect(subject.body).to eq(credentials)
expect(role.reload.role_arn).to eq(role_arn)
end end
end end
...@@ -61,11 +48,14 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do ...@@ -61,11 +48,14 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do
end end
end end
context 'cannot create role' do context 'role does not exist' do
before do let(:user) { create(:user) }
allow(user).to receive(:create_aws_role!)
.and_raise(ActiveRecord::RecordInvalid.new(user)) include_examples 'bad request'
end end
context 'supplied ARN is invalid' do
let(:role_arn) { 'invalid' }
include_examples 'bad request' include_examples 'bad request'
end end
......
# frozen_string_literal: true
RSpec.shared_examples 'GET new cluster shared examples' do
describe 'EKS cluster' do
context 'user already has an associated AWS role' do
let!(:role) { create(:aws_role, user: user) }
it 'does not create an Aws::Role record' do
expect { go(provider: 'aws') }.not_to change { Aws::Role.count }
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:aws_role)).to eq(role)
end
end
context 'user does not have an associated AWS role' do
it 'creates an Aws::Role record' do
expect { go(provider: 'aws') }.to change { Aws::Role.count }
expect(response).to have_gitlab_http_status(:ok)
role = assigns(:aws_role)
expect(role.user).to eq(user)
expect(role.role_arn).to be_nil
expect(role.role_external_id).to be_present
end
end
end
end
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment