Commit fe084819 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'per-build-token-without-lfs' into 'master'

Make CI to use the permission of the user who is trigger the build

This is continuation of https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5735, but with removed all LFS code that is added by: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6043.

This also incorporates most of LFS code added in !6043 to simplify further merge.

See merge request !6409
parents 1e72de66 135be3ca
......@@ -22,6 +22,7 @@ v 8.12.0 (unreleased)
- Instructions for enabling Git packfile bitmaps !6104
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
- Fix pagination on user snippets page
- Run CI builds with the permissions of users !5735
- Fix sorting of issues in API
- Sort project variables by key. !6275 (Diego Souza)
- Ensure specs on sorting of issues in API are deterministic on MySQL
......
......@@ -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.actor, auth_params).
execute(authentication_abilities: @authentication_result.authentication_abilities)
render json: result, status: result[:http_status]
end
......@@ -20,30 +23,23 @@ 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, project: nil, ip: request.ip)
render_403
render_403 unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
end
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end
def auth_params
params.permit(:service, :scope, :account, :client_id)
def render_missing_personal_token
render plain: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
status: 401
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
def auth_params
params.permit(:service, :scope, :account, :client_id)
end
end
......@@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
render json: @build.to_json(methods: :trace_html)
render json: {
id: @build.id,
status: @build.status,
trace_html: @build.trace_html
}
end
end
end
......
......@@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
attr_reader :user
attr_reader :authentication_result
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
alias_method :user, :actor
# Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token
......@@ -15,32 +19,25 @@ class Projects::GitHttpClientController < Projects::ApplicationController
private
def authenticate_user
@authentication_result = Gitlab::Auth::Result.new
if project && project.public? && download_request?
return # Allow access
end
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && download_request?
@ci = true
elsif auth_result.type == :oauth && !download_request?
# Not allowed
elsif auth_result.type == :missing_personal_token
render_missing_personal_token
return # Render above denied access, nothing left to do
else
@user = auth_result.user
end
if ci? || user
if handle_basic_authentication(login, password)
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user
user = find_kerberos_user
if user
@authentication_result = Gitlab::Auth::Result.new(
user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities)
send_final_spnego_response
return # Allow access
end
......@@ -48,6 +45,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end
def basic_auth_provided?
......@@ -114,8 +113,39 @@ class Projects::GitHttpClientController < Projects::ApplicationController
render plain: 'Not Found', status: :not_found
end
def handle_basic_authentication(login, password)
@authentication_result = Gitlab::Auth.find_for_git_client(
login, password, project: project, ip: request.ip)
return false unless @authentication_result.success?
if download_request?
authentication_has_download_access?
else
authentication_has_upload_access?
end
end
def ci?
@ci.present?
authentication_result.ci? &&
authentication_project &&
authentication_project == project
end
def authentication_has_download_access?
has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
end
def authentication_has_upload_access?
has_authentication_ability?(:push_code)
end
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
def authentication_project
authentication_result.project
end
def verify_workhorse_api!
......
......@@ -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', authentication_abilities: authentication_abilities)
end
def access_check
......
......@@ -25,13 +25,21 @@ 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_authentication_ability?(:download_code) && can?(user, :download_code, project)
end
def build_can_download_code?
has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project)
end
def lfs_upload_access?
return false unless project.lfs_enabled?
user && user.can?(:push_code, project)
has_authentication_ability?(:push_code) && can?(user, :push_code, project)
end
def render_lfs_forbidden
......
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
......@@ -38,6 +43,7 @@ module Ci
new_build.status = 'pending'
new_build.runner_id = nil
new_build.trigger_request_id = nil
new_build.token = nil
new_build.save
end
......@@ -176,7 +182,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
......@@ -238,12 +244,7 @@ module Ci
end
def trace
trace = raw_trace
if project && trace.present? && project.runners_token.present?
trace.gsub(project.runners_token, 'xxxxxx')
else
trace
end
hide_secrets(raw_trace)
end
def trace_length
......@@ -256,6 +257,7 @@ module Ci
def trace=(trace)
recreate_trace_dir
trace = hide_secrets(trace)
File.write(path_to_trace, trace)
end
......@@ -269,6 +271,8 @@ module Ci
def append_trace(trace_part, offset)
recreate_trace_dir
trace_part = hide_secrets(trace_part)
File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
File.open(path_to_trace, 'ab') do |f|
f.write(trace_part)
......@@ -344,12 +348,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?
......@@ -491,5 +491,11 @@ module Ci
pipeline.config_processor.build_attributes(name)
end
def hide_secrets(trace)
trace = Ci::MaskSecret.mask(trace, project.runners_token) if project
trace = Ci::MaskSecret.mask(trace, token)
trace
end
end
end
......@@ -1137,12 +1137,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
......
......@@ -64,6 +64,12 @@ class ProjectPolicy < BasePolicy
can! :read_deployment
end
# Permissions given when an user is team member of a project
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!
......
......@@ -4,7 +4,9 @@ module Auth
AUDIENCE = 'container_registry'
def execute
def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities || []
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)
requested_project.public? || 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,29 @@ module Auth
def registry
Gitlab.config.registry
end
def build_can_pull?(requested_project)
# Build can:
# 1. pull from its own project (for ex. a build)
# 2. read images from dependent projects if creator of build is a team member
@authentication_abilities.include?(:build_read_container_image) &&
(requested_project == project || can?(current_user, :build_read_container_image, requested_project))
end
def user_can_pull?(requested_project)
@authentication_abilities.include?(:read_container_image) &&
can?(current_user, :read_container_image, requested_project)
end
def build_can_push?(requested_project)
# Build can push only to the project from which it originates
@authentication_abilities.include?(:build_create_container_image) &&
requested_project == project
end
def user_can_push?(requested_project)
@authentication_abilities.include?(:create_container_image) &&
can?(current_user, :create_container_image, requested_project)
end
end
end
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
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
......@@ -181,6 +181,7 @@ ActiveRecord::Schema.define(version: 20160913212128) 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 +193,7 @@ ActiveRecord::Schema.define(version: 20160913212128) 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"
......
......@@ -35,6 +35,14 @@ module API
Project.find_with_namespace(project_path)
end
end
def ssh_authentication_abilities
[
:read_project,
:download_code,
:push_code
]
end
end
post "/allowed" do
......@@ -51,9 +59,9 @@ module API
access =
if wiki?
Gitlab::GitAccessWiki.new(actor, project, protocol)
Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
else
Gitlab::GitAccess.new(actor, project, protocol)
Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
end
access_status = access.check(params[:action], params[:changes])
......
......@@ -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?(build)
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?(build)
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)
......
module Ci::MaskSecret
class << self
def mask(value, token)
return value unless value.present? && token.present?
value.gsub(token, 'x' * token.length)
end
end
end
module Gitlab
module Auth
Result = Struct.new(:user, :type)
class MissingPersonalTokenError < StandardError; 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
result =
service_request_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) ||
Gitlab::Auth::Result.new
if valid_ci_request?(login, password, project)
result.type = :ci
else
result = populate_result(login, password)
end
rate_limit!(ip, success: result.success?, login: login)
success = result.user.present? || [:ci, :missing_personal_token].include?(result.type)
rate_limit!(ip, success: success, login: login)
result
end
......@@ -57,44 +57,31 @@ module Gitlab
private
def valid_ci_request?(login, password, project)
def service_request_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)
Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
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
raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
end
def oauth_access_token_check(login, password)
......@@ -102,7 +89,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)
Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)
end
end
end
......@@ -111,9 +98,52 @@ 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
Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation
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
return unless build.project.builds_enabled?
if build.user
# If user is assigned to build, use restricted credentials of user
Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
else
# Otherwise use generic CI credentials (backward compatibility)
Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)
end
end
public
def build_authentication_abilities
[
:read_project,
:build_download_code,
:build_read_container_image,
:build_create_container_image
]
end
def read_authentication_abilities
[
:read_project,
:download_code,
:read_container_image
]
end
def full_authentication_abilities
read_authentication_abilities + [
:push_code,
:create_container_image
]
end
end
end
end
module Gitlab
module Auth
Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
def ci?
type == :ci
end
def success?
actor.present? || type == :ci
end
end
end
end
......@@ -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, :authentication_abilities
def initialize(actor, project, protocol)
def initialize(actor, project, protocol, authentication_abilities:)
@actor = actor
@project = project
@protocol = protocol
@authentication_abilities = authentication_abilities
@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?
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
end
def build_can_download_code?
authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
end
def user_push_access_check(changes)
unless authentication_abilities.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
......
require 'spec_helper'
describe Ci::MaskSecret, lib: true do
subject { described_class }
describe '#mask' do
it 'masks exact number of characters' do
expect(subject.mask('token', 'oke')).to eq('txxxn')
end
it 'masks multiple occurrences' do
expect(subject.mask('token token token', 'oke')).to eq('txxxn txxxn txxxn')
end
it 'does not mask if not found' do
expect(subject.mask('token', 'not')).to eq('token')
end
end
end
......@@ -4,15 +4,53 @@ 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_authentication_abilities))
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_authentication_abilities))
end
end
(HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
context "for #{build_status} build" do
let!(:build) { create(:ci_build, status: build_status) }
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
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_authentication_abilities))
end
it 'recognizes master passwords' do
......@@ -20,7 +58,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_authentication_abilities))
end
it 'recognizes OAuth tokens' do
......@@ -30,7 +68,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_authentication_abilities))
end
it 'returns double nil for invalid credentials' do
......@@ -92,4 +130,30 @@ describe Gitlab::Auth, lib: true do
end
end
end
private
def build_authentication_abilities
[
:read_project,
:build_download_code,
:build_read_container_image,
:build_create_container_image
]
end
def read_authentication_abilities
[
:read_project,
:download_code,
:read_container_image
]
end
def full_authentication_abilities
read_authentication_abilities + [
:push_code,
:create_container_image
]
end
end
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', authentication_abilities: authentication_abilities) }
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:actor) { user }
let(:authentication_abilities) do
[
:read_project,
:download_code,
:push_code
]
end
describe '#check with single protocols allowed' do
def disable_protocol(protocol)
......@@ -15,7 +22,7 @@ describe Gitlab::GitAccess, lib: true do
context 'ssh disabled' do
before do
disable_protocol('ssh')
@acc = Gitlab::GitAccess.new(actor, project, 'ssh')
@acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities)
end
it 'blocks ssh git push' do
......@@ -30,7 +37,7 @@ describe Gitlab::GitAccess, lib: true do
context 'http disabled' do
before do
disable_protocol('http')
@acc = Gitlab::GitAccess.new(actor, project, 'http')
@acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities)
end
it 'blocks http push' do
......@@ -111,6 +118,36 @@ describe Gitlab::GitAccess, lib: true do
end
end
end
describe 'build authentication_abilities permissions' do
let(:authentication_abilities) { build_authentication_abilities }
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
......@@ -283,38 +320,71 @@ describe Gitlab::GitAccess, lib: true do
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 'when project is authorized' do
before { authorize }
context 'push code' do
subject { access.check('git-receive-pack', '_any') }
it { expect(subject).not_to be_allowed }
end
context 'when project is authorized' do
before { key.projects << project }
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) }
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 internal 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
context 'to private project' do
let(:project) { create(:project, :internal) }
describe 'build authentication abilities' do
let(:authentication_abilities) { build_authentication_abilities }
it { expect(subject).not_to be_allowed }
end
it_behaves_like 'can not push code' do
def authorize
project.team << [user, :reporter]
end
end
end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
let(:actor) { key }
it_behaves_like 'can not push code' do
def authorize
key.projects << project
end
end
end
private
def build_authentication_abilities
[
:read_project,
:build_download_code
]
end
def full_authentication_abilities
[
:read_project,
:download_code,
:push_code
]
end
end
require 'spec_helper'
describe Gitlab::GitAccessWiki, lib: true do
let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') }
let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:authentication_abilities) do
[
:read_project,
:download_code,
:push_code
]
end
describe 'push_allowed?' do
before do
......
......@@ -88,9 +88,7 @@ describe Ci::Build, models: true do
end
describe '#trace' do
subject { build.trace_html }
it { is_expected.to be_empty }
it { expect(build.trace).to be_nil }
context 'when build.trace contains text' do
let(:text) { 'example output' }
......@@ -98,16 +96,80 @@ describe Ci::Build, models: true do
build.trace = text
end
it { is_expected.to include(text) }
it { expect(subject.length).to be >= text.length }
it { expect(build.trace).to eq(text) }
end
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.update(trace: token)
build.project.update(runners_token: token)
end
it { expect(build.trace).not_to include(token) }
it { expect(build.raw_trace).to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(trace: token)
build.update(token: token)
end
it { expect(build.trace).not_to include(token) }
it { expect(build.raw_trace).to include(token) }
end
end
describe '#raw_trace' do
subject { build.raw_trace }
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.update(trace: token)
end
it { is_expected.not_to include(token) }
end
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.update(token: token)
build.update(trace: token)
end
it { is_expected.not_to include(token) }
end
end
context '#append_trace' do
subject { build.trace_html }
context 'when build.trace hides runners token' do
let(:token) { 'my_secret_token' }
before do
build.project.update(runners_token: token)
build.append_trace(token, 0)
end
it { is_expected.not_to include(token) }
end
context 'when build.trace hides token' do
context 'when build.trace hides build token' do
let(:token) { 'my_secret_token' }
before do
build.project.update_attributes(runners_token: token)
build.update_attributes(trace: token)
build.update(token: token)
build.append_trace(token, 0)
end
it { is_expected.not_to include(token) }
......
......@@ -8,7 +8,7 @@ describe Ci::Build, models: true do
it 'obfuscates project runners token' do
allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}")
expect(build.trace).to eq("Test: xxxxxx")
expect(build.trace).to eq("Test: xxxxxxxxxxxxxxxxxxxx")
end
it 'empty project runners token' do
......
......@@ -254,7 +254,8 @@ describe Ci::API::API do
let(:get_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
let(:token) { build.token }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) }
before { build.run! }
......@@ -262,6 +263,7 @@ describe Ci::API::API do
context "should authorize posting artifact to running build" do
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response["TempPath"]).not_to be_nil
......@@ -269,6 +271,15 @@ describe Ci::API::API do
it "using token as header" do
post authorize_url, {}, headers_with_token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response["TempPath"]).not_to be_nil
end
it "using runners token" do
post authorize_url, { token: build.project.runners_token }, headers
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(json_response["TempPath"]).not_to be_nil
......@@ -276,7 +287,9 @@ describe Ci::API::API do
it "reject requests that did not go through gitlab-workhorse" do
headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
post authorize_url, { token: build.token }, headers
expect(response).to have_http_status(500)
end
end
......@@ -284,13 +297,17 @@ describe Ci::API::API do
context "should fail to post too large artifact" do
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.token, filesize: 100 }, headers
expect(response).to have_http_status(413)
end
it "using token as header" do
stub_application_setting(max_artifacts_size: 0)
post authorize_url, { filesize: 100 }, headers_with_token
expect(response).to have_http_status(413)
end
end
......@@ -358,6 +375,16 @@ describe Ci::API::API do
it_behaves_like 'successful artifacts upload'
end
context 'when using runners token' do
let(:token) { build.project.runners_token }
before do
upload_artifacts(file_upload, headers_with_token)
end
it_behaves_like 'successful artifacts upload'
end
end
context 'posts artifacts file and metadata file' do
......@@ -497,19 +524,40 @@ describe Ci::API::API do
before do
delete delete_url, token: build.token
build.reload
end
it 'removes build artifacts' do
expect(response).to have_http_status(200)
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
expect(build.artifacts_size).to be_nil
shared_examples 'having removable artifacts' do
it 'removes build artifacts' do
build.reload
expect(response).to have_http_status(200)
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
expect(build.artifacts_size).to be_nil
end
end
context 'when using build token' do
before do
delete delete_url, token: build.token
end
it_behaves_like 'having removable artifacts'
end
context 'when using runnners token' do
before do
delete delete_url, token: build.project.runners_token
end
it_behaves_like 'having removable artifacts'
end
end
describe 'GET /builds/:id/artifacts' do
before { get get_url, token: build.token }
before do
get get_url, token: token
end
context 'build has artifacts' do
let(:build) { create(:ci_build, :artifacts) }
......@@ -518,13 +566,29 @@ describe Ci::API::API do
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
it 'downloads artifact' do
expect(response).to have_http_status(200)
expect(response.headers).to include download_headers
shared_examples 'having downloadable artifacts' do
it 'download artifacts' do
expect(response).to have_http_status(200)
expect(response.headers).to include download_headers
end
end
context 'when using build token' do
let(:token) { build.token }
it_behaves_like 'having downloadable artifacts'
end
context 'when using runnners token' do
let(:token) { build.project.runners_token }
it_behaves_like 'having downloadable artifacts'
end
end
context 'build does not has artifacts' do
let(:token) { build.token }
it 'responds with not found' do
expect(response).to have_http_status(404)
end
......
......@@ -300,25 +300,79 @@ describe 'Git HTTP requests', lib: true do
end
context "when a gitlab ci token is provided" do
let(:token) { 123 }
let(:project) { FactoryGirl.create :empty_project }
let(:build) { create(:ci_build, :running) }
let(:project) { build.project }
let(:other_project) { create(:empty_project) }
before do
project.update_attributes(runners_token: token)
project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
end
it "downloads get status 200" do
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
context 'when build created by system is authenticated' do
it "downloads get status 200" do
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it "uploads get status 401 (no project existence information leak)" do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(401)
end
it "downloads from other project get status 404" do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(404)
end
end
it "uploads get status 401 (no project existence information leak)" do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
context 'and build created by' do
before do
build.update(user: user)
project.team << [user, :reporter]
end
expect(response).to have_http_status(401)
shared_examples 'can download code only from own projects' do
it 'downloads get status 200' do
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it 'uploads get status 403' do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(401)
end
end
context 'administrator' do
let(:user) { create(:admin) }
it_behaves_like 'can download code only from own projects'
it 'downloads from other project get status 403' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(403)
end
end
context 'regular user' do
let(:user) { create(:user) }
it_behaves_like 'can download code only from own projects'
it 'downloads from other project get status 404' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(404)
end
end
end
end
end
......
......@@ -22,11 +22,13 @@ describe JwtController do
context 'when using authorized request' do
context 'using CI token' do
let(:project) { create(:empty_project, runners_token: 'token') }
let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } }
let(:build) { create(:ci_build, :running) }
let(:project) { build.project }
let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } }
context 'project with enabled CI' do
subject! { get '/jwt/auth', parameters, headers }
it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
end
......@@ -43,13 +45,31 @@ describe JwtController do
context 'using User login' do
let(:user) { create(:user) }
let(:headers) { { authorization: credentials('user', 'password') } }
before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) }
let(:headers) { { authorization: credentials(user.username, user.password) } }
subject! { get '/jwt/auth', parameters, headers }
it { expect(service_class).to have_received(:new).with(nil, user, parameters) }
context 'when user has 2FA enabled' do
let(:user) { create(:user, :two_factor) }
context 'without personal token' do
it 'rejects the authorization attempt' do
expect(response).to have_http_status(401)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end
end
context 'with personal token' do
let(:access_token) { create(:personal_access_token, user: user) }
let(:headers) { { authorization: credentials(user.username, access_token.token) } }
it 'rejects the authorization attempt' do
expect(response).to have_http_status(200)
end
end
end
end
context 'using invalid login' do
......
......@@ -14,6 +14,7 @@ describe 'Git LFS API and storage' do
end
let(:authorization) { }
let(:sendfile) { }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:sample_oid) { lfs_object.oid }
let(:sample_size) { lfs_object.size }
......@@ -244,14 +245,63 @@ describe 'Git LFS API and storage' do
end
end
context 'when CI is authorized' do
context 'when build is authorized as' do
let(:authorization) { authorize_ci_project }
let(:update_permissions) do
project.lfs_objects << lfs_object
shared_examples 'can download LFS only from own projects' do
context 'for own project' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:update_permissions) do
project.team << [user, :reporter]
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end
context 'for other project' do
let(:other_project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it 'rejects downloading code' do
expect(response).to have_http_status(other_project_status)
end
end
end
context 'administrator' do
let(:user) { create(:admin) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 403, because administrator does have normally access
let(:other_project_status) { 403 }
end
end
context 'regular user' do
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
end
it_behaves_like 'responds with a file'
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it_behaves_like 'can download LFS only from own projects' do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
end
end
end
......@@ -431,10 +481,62 @@ describe 'Git LFS API and storage' do
end
end
context 'when CI is authorized' do
context 'when build is authorized as' do
let(:authorization) { authorize_ci_project }
it_behaves_like 'an authorized requests'
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
shared_examples 'can download LFS only from own projects' do
context 'for own project' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:update_user_permissions) do
project.team << [user, :reporter]
end
it_behaves_like 'an authorized requests'
end
context 'for other project' do
let(:other_project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
it 'rejects downloading code' do
expect(response).to have_http_status(other_project_status)
end
end
end
context 'administrator' do
let(:user) { create(:admin) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 403, because administrator does have normally access
let(:other_project_status) { 403 }
end
end
context 'regular user' do
let(:user) { create(:user) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
end
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it_behaves_like 'can download LFS only from own projects' do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
end
end
context 'when user is not authenticated' do
......@@ -583,11 +685,37 @@ describe 'Git LFS API and storage' do
end
end
context 'when CI is authorized' do
context 'when build is authorized' do
let(:authorization) { authorize_ci_project }
it 'responds with 401' do
expect(response).to have_http_status(401)
context 'build has an user' do
let(:user) { create(:user) }
context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
context 'tries to push to other project' do
let(:other_project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
end
......@@ -609,14 +737,6 @@ describe 'Git LFS API and storage' do
end
end
end
context 'when CI is authorized' do
let(:authorization) { authorize_ci_project }
it 'responds with status 403' do
expect(response).to have_http_status(401)
end
end
end
describe 'unsupported' do
......@@ -779,10 +899,51 @@ describe 'Git LFS API and storage' do
end
end
context 'when CI is authenticated' do
context 'when build is authorized' do
let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized'
context 'build has an user' do
let(:user) { create(:user) }
context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
before do
project.team << [user, :developer]
put_authorize
end
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
context 'tries to push to other project' do
let(:other_project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
before do
put_authorize
end
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
before do
put_authorize
end
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
context 'for unauthenticated' do
......@@ -839,10 +1000,42 @@ describe 'Git LFS API and storage' do
end
end
context 'when CI is authenticated' do
context 'when build is authorized' do
let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized'
before do
put_authorize
end
context 'build has an user' do
let(:user) { create(:user) }
context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
context 'tries to push to other project' do
let(:other_project) { create(:empty_project) }
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it 'responds with 401' do
expect(response).to have_http_status(401)
end
end
end
context 'for unauthenticated' do
......@@ -897,7 +1090,7 @@ describe 'Git LFS API and storage' do
end
def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token)
end
def authorize_user
......
......@@ -6,8 +6,14 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
let(:current_params) { {} }
let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
let(:payload) { JWT.decode(subject[:token], rsa_key).first }
let(:authentication_abilities) do
[
:read_container_image,
:create_container_image
]
end
subject { described_class.new(current_project, current_user, current_params).execute }
subject { described_class.new(current_project, current_user, current_params).execute(authentication_abilities: authentication_abilities) }
before do
allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
......@@ -189,13 +195,22 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end
end
context 'project authorization' do
context 'build authorized as user' do
let(:current_project) { create(:empty_project) }
let(:current_user) { create(:user) }
let(:authentication_abilities) do
[
:build_read_container_image,
:build_create_container_image
]
end
context 'allow to use scope-less authentication' do
it_behaves_like 'a valid token'
before do
current_project.team << [current_user, :developer]
end
it_behaves_like 'a valid token'
context 'allow to pull and push images' do
let(:current_params) do
{ scope: "repository:#{current_project.path_with_namespace}:pull,push" }
......@@ -214,12 +229,44 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
context 'allow for public' do
let(:project) { create(:empty_project, :public) }
it_behaves_like 'a pullable'
end
context 'disallow for private' do
shared_examples 'pullable for being team member' do
context 'when you are not member' do
it_behaves_like 'an inaccessible'
end
context 'when you are member' do
before do
project.team << [current_user, :developer]
end
it_behaves_like 'a pullable'
end
end
context 'for private' do
let(:project) { create(:empty_project, :private) }
it_behaves_like 'an inaccessible'
it_behaves_like 'pullable for being team member'
context 'when you are admin' do
let(:current_user) { create(:admin) }
context 'when you are not member' do
it_behaves_like 'an inaccessible'
end
context 'when you are member' do
before do
project.team << [current_user, :developer]
end
it_behaves_like 'a pullable'
end
end
end
end
......@@ -230,6 +277,11 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
context 'disallow for all' do
let(:project) { create(:empty_project, :public) }
before do
project.team << [current_user, :developer]
end
it_behaves_like 'an inaccessible'
end
end
......
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