New upstream version 13.2.8
This commit is contained in:
parent
52fc698a56
commit
d0904892e8
117 changed files with 1755 additions and 544 deletions
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -2,6 +2,42 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.2.8 (2020-09-02)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Protect OAuth endpoints from brute force/password stuffing.
|
||||
|
||||
|
||||
## 13.2.7 (2020-09-02)
|
||||
|
||||
### Security (23 changes, 1 of them is from the community)
|
||||
|
||||
- Check validity of project's import_url before mirroring repository.
|
||||
- Show on two-factor authentication setup page groups that are the cause of this requirement.
|
||||
- Prevent interrupted 2FA sign-in from signing-in incorrect user.
|
||||
- Create new 2FA code each time user is entering 2FA setup page.
|
||||
- Remove all sessions but current while enabling 2FA.
|
||||
- Invalidate two factor sign-in when user password changes.
|
||||
- Delete members invites created by users being deleted.
|
||||
- Prevent OmniAuth from rendering arbitrary error messages.
|
||||
- Prevent not-2fa authenticated users that are supposed to use it to consume api via session.
|
||||
- Invalidate remember me when an active session is revoked.
|
||||
- Add rate limit on webhooks testing feature.
|
||||
- Add scope presence validation to OAuth Application creation.
|
||||
- Allow only running job tokens for API authentication.
|
||||
- Prevent Deploy Tokens to read project resources when repository is disabled.
|
||||
- Change conan api to use proper workhorse validation.
|
||||
- Ensure global ID is of Snippet type in GraphQL destroy mutation.
|
||||
- Fix Improper Access Control on Deploy-Key.
|
||||
- Set maximum limit for profile events.
|
||||
- Persist EKS External ID before presenting it to the user.
|
||||
- Prevent project maintainers from editing group badges.
|
||||
- Upgrade jquery to v3.5.
|
||||
- Update websocket-extensions gem to 0.1.5. (Vitor Meireles De Sousa)
|
||||
- Update GitLab Runner Helm Chart to 0.18.3.
|
||||
|
||||
|
||||
## 13.2.6 (2020-08-18)
|
||||
|
||||
- No changes.
|
||||
|
|
|
@ -1 +1 @@
|
|||
13.2.6
|
||||
13.2.8
|
||||
|
|
|
@ -1153,7 +1153,7 @@ GEM
|
|||
railties (>= 3.2.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
websocket-extensions (0.1.5)
|
||||
wikicloth (0.8.1)
|
||||
builder
|
||||
expression_parser
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
13.2.6
|
||||
13.2.8
|
||||
|
|
|
@ -38,8 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController
|
|||
|
||||
def new
|
||||
if params[:provider] == 'aws'
|
||||
@aws_role = current_user.aws_role || Aws::Role.new
|
||||
@aws_role.ensure_role_external_id!
|
||||
@aws_role = Aws::Role.create_or_find_by!(user: current_user)
|
||||
@instance_types = load_instance_types.to_json
|
||||
|
||||
elsif params[:provider] == 'gcp'
|
||||
|
@ -273,7 +272,7 @@ class Clusters::ClustersController < Clusters::BaseController
|
|||
end
|
||||
|
||||
def aws_role_params
|
||||
params.require(:cluster).permit(:role_arn, :role_external_id)
|
||||
params.require(:cluster).permit(:role_arn)
|
||||
end
|
||||
|
||||
def generate_gcp_authorize_url
|
||||
|
|
|
@ -22,6 +22,8 @@ module AuthenticatesWithTwoFactor
|
|||
return handle_locked_user(user) unless user.can?(:log_in)
|
||||
|
||||
session[:otp_user_id] = user.id
|
||||
session[:user_updated_at] = user.updated_at
|
||||
|
||||
setup_u2f_authentication(user)
|
||||
render 'devise/sessions/two_factor'
|
||||
end
|
||||
|
@ -41,6 +43,7 @@ module AuthenticatesWithTwoFactor
|
|||
def authenticate_with_two_factor
|
||||
user = self.resource = find_user
|
||||
return handle_locked_user(user) unless user.can?(:log_in)
|
||||
return handle_changed_user(user) if user_changed?(user)
|
||||
|
||||
if user_params[:otp_attempt].present? && session[:otp_user_id]
|
||||
authenticate_with_two_factor_via_otp(user)
|
||||
|
@ -59,12 +62,14 @@ module AuthenticatesWithTwoFactor
|
|||
|
||||
def clear_two_factor_attempt!
|
||||
session.delete(:otp_user_id)
|
||||
session.delete(:user_updated_at)
|
||||
session.delete(:challenge)
|
||||
end
|
||||
|
||||
def authenticate_with_two_factor_via_otp(user)
|
||||
if valid_otp_attempt?(user)
|
||||
# Remove any lingering user data from login
|
||||
session.delete(:otp_user_id)
|
||||
clear_two_factor_attempt!
|
||||
|
||||
remember_me(user) if user_params[:remember_me] == '1'
|
||||
user.save!
|
||||
|
@ -81,8 +86,7 @@ module AuthenticatesWithTwoFactor
|
|||
def authenticate_with_two_factor_via_u2f(user)
|
||||
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
|
||||
# Remove any lingering user data from login
|
||||
session.delete(:otp_user_id)
|
||||
session.delete(:challenge)
|
||||
clear_two_factor_attempt!
|
||||
|
||||
remember_me(user) if user_params[:remember_me] == '1'
|
||||
sign_in(user, message: :two_factor_authenticated, event: :authentication)
|
||||
|
@ -109,4 +113,18 @@ module AuthenticatesWithTwoFactor
|
|||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def handle_changed_user(user)
|
||||
clear_two_factor_attempt!
|
||||
|
||||
redirect_to new_user_session_path, alert: _('An error occurred. Please sign in again.')
|
||||
end
|
||||
|
||||
# If user has been updated since we validated the password,
|
||||
# the password might have changed.
|
||||
def user_changed?(user)
|
||||
return false unless session[:user_updated_at]
|
||||
|
||||
user.updated_at != session[:user_updated_at]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,12 +29,11 @@ module EnforcesTwoFactorAuthentication
|
|||
end
|
||||
|
||||
def two_factor_authentication_required?
|
||||
Gitlab::CurrentSettings.require_two_factor_authentication? ||
|
||||
current_user.try(:require_two_factor_authentication_from_group?)
|
||||
two_factor_verifier.two_factor_authentication_required?
|
||||
end
|
||||
|
||||
def current_user_requires_two_factor?
|
||||
current_user && !current_user.temp_oauth_email? && !current_user.two_factor_enabled? && !skip_two_factor?
|
||||
two_factor_verifier.current_user_needs_to_setup_two_factor? && !skip_two_factor?
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
@ -43,7 +42,7 @@ module EnforcesTwoFactorAuthentication
|
|||
if Gitlab::CurrentSettings.require_two_factor_authentication?
|
||||
global.call
|
||||
else
|
||||
groups = current_user.expanded_groups_requiring_two_factor_authentication.reorder(name: :asc)
|
||||
groups = current_user.source_groups_of_two_factor_authentication_requirement.reorder(name: :asc)
|
||||
group.call(groups)
|
||||
end
|
||||
end
|
||||
|
@ -51,14 +50,11 @@ module EnforcesTwoFactorAuthentication
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def two_factor_grace_period
|
||||
periods = [Gitlab::CurrentSettings.two_factor_grace_period]
|
||||
periods << current_user.two_factor_grace_period if current_user.try(:require_two_factor_authentication_from_group?)
|
||||
periods.min
|
||||
two_factor_verifier.two_factor_grace_period
|
||||
end
|
||||
|
||||
def two_factor_grace_period_expired?
|
||||
date = current_user.otp_grace_period_started_at
|
||||
date && (date + two_factor_grace_period.hours) < Time.current
|
||||
two_factor_verifier.two_factor_grace_period_expired?
|
||||
end
|
||||
|
||||
def two_factor_skippable?
|
||||
|
@ -70,6 +66,10 @@ module EnforcesTwoFactorAuthentication
|
|||
def skip_two_factor?
|
||||
session[:skip_two_factor] && session[:skip_two_factor] > Time.current
|
||||
end
|
||||
|
||||
def two_factor_verifier
|
||||
@two_factor_verifier ||= Gitlab::Auth::TwoFactorAuthVerifier.new(current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
end
|
||||
|
||||
EnforcesTwoFactorAuthentication.prepend_if_ee('EE::EnforcesTwoFactorAuthentication')
|
||||
|
|
|
@ -17,4 +17,16 @@ module HooksExecution
|
|||
flash[:alert] = "Hook execution failed: #{message}"
|
||||
end
|
||||
end
|
||||
|
||||
def create_rate_limit(key, scope)
|
||||
if rate_limiter.throttled?(key, scope: [scope, current_user])
|
||||
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
|
||||
|
||||
render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
|
||||
end
|
||||
end
|
||||
|
||||
def rate_limiter
|
||||
::Gitlab::ApplicationRateLimiter
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,12 +49,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
redirect_unverified_saml_initiation
|
||||
end
|
||||
|
||||
def omniauth_error
|
||||
@provider = params[:provider]
|
||||
@error = params[:error]
|
||||
render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def cas3
|
||||
ticket = params['ticket']
|
||||
if ticket
|
||||
|
@ -198,9 +192,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
end
|
||||
|
||||
def fail_login(user)
|
||||
error_message = user.errors.full_messages.to_sentence
|
||||
@provider = Gitlab::Auth::OAuth::Provider.label_for(params[:action])
|
||||
@error = user.errors.full_messages.to_sentence
|
||||
|
||||
redirect_to omniauth_error_path(oauth['provider'], error: error_message)
|
||||
render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def fail_auth0_login
|
||||
|
|
|
@ -7,6 +7,7 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController
|
|||
|
||||
def destroy
|
||||
ActiveSession.destroy_with_public_id(current_user, params[:id])
|
||||
current_user.forget_me!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to profile_active_sessions_url, status: :found }
|
||||
|
|
|
@ -4,7 +4,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
skip_before_action :check_two_factor_requirement
|
||||
|
||||
def show
|
||||
unless current_user.otp_secret
|
||||
unless current_user.two_factor_enabled?
|
||||
current_user.otp_secret = User.generate_otp_secret(32)
|
||||
end
|
||||
|
||||
|
@ -38,6 +38,8 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
|
||||
def create
|
||||
if current_user.validate_and_consume_otp!(params[:pin_code])
|
||||
ActiveSession.destroy_all_but_current(current_user, session)
|
||||
|
||||
Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
|
||||
@codes = user.generate_otp_backup_codes!
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ class Projects::HooksController < Projects::ApplicationController
|
|||
# Authorize
|
||||
before_action :authorize_admin_project!
|
||||
before_action :hook_logs, only: :edit
|
||||
before_action -> { create_rate_limit(:project_testing_hook, @project) }, only: :test
|
||||
|
||||
respond_to :html
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ class Projects::TagsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
format.js do
|
||||
render status: :unprocessable_entity
|
||||
render status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ class SessionsController < Devise::SessionsController
|
|||
include Recaptcha::Verify
|
||||
include RendersLdapServers
|
||||
include KnownSignIn
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
skip_before_action :check_two_factor_requirement, only: [:destroy]
|
||||
# replaced with :require_no_authentication_without_flash
|
||||
|
@ -197,12 +198,16 @@ class SessionsController < Devise::SessionsController
|
|||
end
|
||||
|
||||
def find_user
|
||||
if session[:otp_user_id]
|
||||
strong_memoize(:find_user) do
|
||||
if session[:otp_user_id] && user_params[:login]
|
||||
User.by_id_and_login(session[:otp_user_id], user_params[:login]).first
|
||||
elsif session[:otp_user_id]
|
||||
User.find(session[:otp_user_id])
|
||||
elsif user_params[:login]
|
||||
User.by_login(user_params[:login])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stored_redirect_uri
|
||||
@redirect_to ||= stored_location_for(:redirect)
|
||||
|
|
56
app/finders/ci/auth_job_finder.rb
Normal file
56
app/finders/ci/auth_job_finder.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
# 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
|
|
@ -6,6 +6,7 @@
|
|||
# WARNING: does not consider project feature visibility!
|
||||
# - user: The user for which to load the events
|
||||
# - params:
|
||||
# - limit: Number of items that to be returned. Defaults to 20 and limited to 100.
|
||||
# - offset: The page of events to return
|
||||
class UserRecentEventsFinder
|
||||
prepend FinderWithCrossProjectAccess
|
||||
|
@ -16,7 +17,8 @@ class UserRecentEventsFinder
|
|||
|
||||
attr_reader :current_user, :target_user, :params
|
||||
|
||||
LIMIT = 20
|
||||
DEFAULT_LIMIT = 20
|
||||
MAX_LIMIT = 100
|
||||
|
||||
def initialize(current_user, target_user, params = {})
|
||||
@current_user = current_user
|
||||
|
@ -31,7 +33,7 @@ class UserRecentEventsFinder
|
|||
recent_events(params[:offset] || 0)
|
||||
.joins(:project)
|
||||
.with_associations
|
||||
.limit_recent(params[:limit].presence || LIMIT, params[:offset])
|
||||
.limit_recent(limit, params[:offset])
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
@ -59,4 +61,10 @@ class UserRecentEventsFinder
|
|||
def projects
|
||||
target_user.project_interactions.to_sql
|
||||
end
|
||||
|
||||
def limit
|
||||
return DEFAULT_LIMIT unless params[:limit].present?
|
||||
|
||||
[params[:limit].to_i, MAX_LIMIT].min
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module Mutations
|
|||
private
|
||||
|
||||
def find_object(id:)
|
||||
GitlabSchema.object_from_id(id)
|
||||
GitlabSchema.object_from_id(id, expected_type: ::Snippet)
|
||||
end
|
||||
|
||||
def authorized_resource?(snippet)
|
||||
|
|
|
@ -105,6 +105,19 @@ class ActiveSession
|
|||
end
|
||||
end
|
||||
|
||||
def self.destroy_all_but_current(user, current_session)
|
||||
session_ids = not_impersonated(user)
|
||||
session_ids.reject! { |session| session.current?(current_session) } if current_session
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
destroy_sessions(redis, user, session_ids.map(&:session_id)) if session_ids.any?
|
||||
end
|
||||
end
|
||||
|
||||
def self.not_impersonated(user)
|
||||
list(user).reject(&:is_impersonated)
|
||||
end
|
||||
|
||||
def self.key_name(user_id, session_id = '*')
|
||||
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ module Aws
|
|||
validates :role_external_id, uniqueness: true, length: { in: 1..64 }
|
||||
validates :role_arn,
|
||||
length: 1..2048,
|
||||
allow_nil: true,
|
||||
format: {
|
||||
with: Gitlab::Regex.aws_arn_regex,
|
||||
message: Gitlab::Regex.aws_arn_regex_message
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Clusters
|
||||
module Applications
|
||||
class Runner < ApplicationRecord
|
||||
VERSION = '0.18.2'
|
||||
VERSION = '0.18.3'
|
||||
|
||||
self.table_name = 'clusters_applications_runners'
|
||||
|
||||
|
|
|
@ -76,6 +76,8 @@ class Member < ApplicationRecord
|
|||
scope :request, -> { where.not(requested_at: nil) }
|
||||
scope :non_request, -> { where(requested_at: nil) }
|
||||
|
||||
scope :not_accepted_invitations_by_user, -> (user) { invite.where(invite_accepted_at: nil, created_by: user) }
|
||||
|
||||
scope :has_access, -> { active.where('access_level > 0') }
|
||||
|
||||
scope :guests, -> { active.where(access_level: GUEST) }
|
||||
|
|
|
@ -354,6 +354,7 @@ class User < ApplicationRecord
|
|||
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
|
||||
scope :order_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) }
|
||||
scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) }
|
||||
scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) }
|
||||
|
||||
def active_for_authentication?
|
||||
super && can?(:log_in)
|
||||
|
@ -871,6 +872,12 @@ class User < ApplicationRecord
|
|||
all_expanded_groups.where(require_two_factor_authentication: true)
|
||||
end
|
||||
|
||||
def source_groups_of_two_factor_authentication_requirement
|
||||
Gitlab::ObjectHierarchy.new(expanded_groups_requiring_two_factor_authentication)
|
||||
.all_objects
|
||||
.where(id: groups)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def refresh_authorized_projects
|
||||
Users::RefreshAuthorizedProjectsService.new(self).execute
|
||||
|
|
|
@ -11,7 +11,16 @@ module Applications
|
|||
|
||||
# EE would override and use `request` arg
|
||||
def execute(request)
|
||||
Doorkeeper::Application.create(params)
|
||||
@application = Doorkeeper::Application.new(params)
|
||||
|
||||
unless params[:scopes].present?
|
||||
@application.errors.add(:base, _("Scopes can't be blank"))
|
||||
|
||||
return @application
|
||||
end
|
||||
|
||||
@application.save
|
||||
@application
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -132,6 +132,7 @@ module Auth
|
|||
|
||||
def can_access?(requested_project, requested_action)
|
||||
return false unless requested_project.container_registry_enabled?
|
||||
return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED
|
||||
|
||||
case requested_action
|
||||
when 'pull'
|
||||
|
|
|
@ -10,6 +10,9 @@ module Ci
|
|||
elsif job_from_token
|
||||
create_pipeline_from_job(job_from_token)
|
||||
end
|
||||
|
||||
rescue Ci::AuthJobFinder::AuthError => e
|
||||
error(e.message, 401)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -41,8 +44,6 @@ module Ci
|
|||
# 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 error("400 Job has to be running", 400) unless job.running?
|
||||
|
||||
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
|
||||
.execute(:pipeline, ignore_skip_ci: true) do |pipeline|
|
||||
source = job.sourced_pipelines.build(
|
||||
|
@ -64,7 +65,7 @@ module Ci
|
|||
|
||||
def job_from_token
|
||||
strong_memoize(:job) do
|
||||
Ci::Build.find_by_token(params[:token].to_s)
|
||||
Ci::AuthJobFinder.new(token: params[:token].to_s).execute!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ module Clusters
|
|||
|
||||
ERRORS = [
|
||||
ActiveRecord::RecordInvalid,
|
||||
ActiveRecord::RecordNotFound,
|
||||
Clusters::Aws::FetchCredentialsService::MissingRoleError,
|
||||
::Aws::Errors::MissingCredentialsError,
|
||||
::Aws::STS::Errors::ServiceError
|
||||
|
@ -20,7 +21,8 @@ module Clusters
|
|||
end
|
||||
|
||||
def execute
|
||||
@role = create_or_update_role!
|
||||
ensure_role_exists!
|
||||
update_role_arn!
|
||||
|
||||
Response.new(:ok, credentials)
|
||||
rescue *ERRORS
|
||||
|
@ -31,14 +33,12 @@ module Clusters
|
|||
|
||||
attr_reader :role, :params
|
||||
|
||||
def create_or_update_role!
|
||||
if role = user.aws_role
|
||||
role.update!(params)
|
||||
|
||||
role
|
||||
else
|
||||
user.create_aws_role!(params)
|
||||
def ensure_role_exists!
|
||||
@role = ::Aws::Role.find_by_user_id!(user.id)
|
||||
end
|
||||
|
||||
def update_role_arn!
|
||||
role.update!(params)
|
||||
end
|
||||
|
||||
def credentials
|
||||
|
|
|
@ -18,6 +18,7 @@ module Members
|
|||
end
|
||||
|
||||
delete_subresources(member) unless skip_subresources
|
||||
delete_project_invitations_by(member) unless skip_subresources
|
||||
enqueue_delete_todos(member)
|
||||
enqueue_unassign_issuables(member) if unassign_issuables
|
||||
|
||||
|
@ -39,24 +40,48 @@ module Members
|
|||
|
||||
delete_project_members(member)
|
||||
delete_subgroup_members(member)
|
||||
delete_invited_members(member)
|
||||
end
|
||||
|
||||
def delete_project_members(member)
|
||||
groups = member.group.self_and_descendants
|
||||
|
||||
ProjectMember.in_namespaces(groups).with_user(member.user).each do |project_member|
|
||||
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
|
||||
end
|
||||
destroy_project_members(ProjectMember.in_namespaces(groups).with_user(member.user))
|
||||
end
|
||||
|
||||
def delete_subgroup_members(member)
|
||||
groups = member.group.descendants
|
||||
|
||||
GroupMember.of_groups(groups).with_user(member.user).each do |group_member|
|
||||
destroy_group_members(GroupMember.of_groups(groups).with_user(member.user))
|
||||
end
|
||||
|
||||
def delete_invited_members(member)
|
||||
groups = member.group.self_and_descendants
|
||||
|
||||
destroy_group_members(GroupMember.of_groups(groups).not_accepted_invitations_by_user(member.user))
|
||||
|
||||
destroy_project_members(ProjectMember.in_namespaces(groups).not_accepted_invitations_by_user(member.user))
|
||||
end
|
||||
|
||||
def destroy_project_members(members)
|
||||
members.each do |project_member|
|
||||
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_group_members(members)
|
||||
members.each do |group_member|
|
||||
self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_project_invitations_by(member)
|
||||
return unless member.is_a?(ProjectMember) && member.user && member.project
|
||||
|
||||
members_to_delete = member.project.members.not_accepted_invitations_by_user(member.user)
|
||||
destroy_project_members(members_to_delete)
|
||||
end
|
||||
|
||||
def can_destroy_member?(member)
|
||||
can?(current_user, destroy_member_permission(member), member)
|
||||
end
|
||||
|
|
|
@ -7,6 +7,10 @@ module Projects
|
|||
def execute(remote_mirror, tries)
|
||||
return success unless remote_mirror.enabled?
|
||||
|
||||
if Gitlab::UrlBlocker.blocked_url?(CGI.unescape(Gitlab::UrlSanitizer.sanitize(remote_mirror.url)))
|
||||
return error("The remote mirror URL is invalid.")
|
||||
end
|
||||
|
||||
update_mirror(remote_mirror)
|
||||
|
||||
success
|
||||
|
|
|
@ -27,6 +27,6 @@
|
|||
|
||||
- unless is_current_session
|
||||
.float-right
|
||||
= link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
|
||||
= link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
|
||||
%span.sr-only= _('Revoke')
|
||||
= _('Revoke')
|
||||
|
|
|
@ -17,7 +17,7 @@ Doorkeeper.configure do
|
|||
end
|
||||
|
||||
resource_owner_from_credentials do |routes|
|
||||
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password])
|
||||
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password], increment_failed_attempts: true)
|
||||
user unless user.try(:two_factor_enabled?)
|
||||
end
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
|
|||
confirmations: :confirmations }
|
||||
|
||||
devise_scope :user do
|
||||
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
|
||||
get '/users/almost_there' => 'confirmations#almost_there'
|
||||
end
|
||||
|
||||
|
|
15
db/migrate/20200717040735_change_aws_roles_role_arn_null.rb
Normal file
15
db/migrate/20200717040735_change_aws_roles_role_arn_null.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# 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
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddOAuthPathsToProtectedPaths < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
ADD_PROTECTED_PATHS = [
|
||||
'/oauth/authorize',
|
||||
'/oauth/token'
|
||||
].freeze
|
||||
|
||||
EXISTING_DEFAULT_PROTECTED_PATHS = [
|
||||
'/users/password',
|
||||
'/users/sign_in',
|
||||
'/api/v3/session.json',
|
||||
'/api/v3/session',
|
||||
'/api/v4/session.json',
|
||||
'/api/v4/session',
|
||||
'/users',
|
||||
'/users/confirmation',
|
||||
'/unsubscribes/',
|
||||
'/import/github/personal_access_token',
|
||||
'/admin/session'
|
||||
].freeze
|
||||
|
||||
NEW_DEFAULT_PROTECTED_PATHS = (EXISTING_DEFAULT_PROTECTED_PATHS + ADD_PROTECTED_PATHS).freeze
|
||||
|
||||
class ApplicationSetting < ActiveRecord::Base
|
||||
self.table_name = 'application_settings'
|
||||
end
|
||||
|
||||
def up
|
||||
change_column_default :application_settings, :protected_paths, NEW_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
|
||||
missing_paths = ADD_PROTECTED_PATHS - application_setting.protected_paths
|
||||
|
||||
next if missing_paths.empty?
|
||||
|
||||
updated_protected_paths = application_setting.protected_paths + missing_paths
|
||||
application_setting.update!(protected_paths: updated_protected_paths)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_default :application_settings, :protected_paths, EXISTING_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
|
||||
paths_to_remove = application_setting.protected_paths - EXISTING_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
next if paths_to_remove.empty?
|
||||
|
||||
updated_protected_paths = application_setting.protected_paths - paths_to_remove
|
||||
application_setting.update!(protected_paths: updated_protected_paths)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9099,7 +9099,7 @@ CREATE TABLE public.application_settings (
|
|||
throttle_protected_paths_enabled boolean DEFAULT false NOT NULL,
|
||||
throttle_protected_paths_requests_per_period integer DEFAULT 10 NOT NULL,
|
||||
throttle_protected_paths_period_in_seconds integer DEFAULT 60 NOT NULL,
|
||||
protected_paths character varying(255)[] DEFAULT '{/users/password,/users/sign_in,/api/v3/session.json,/api/v3/session,/api/v4/session.json,/api/v4/session,/users,/users/confirmation,/unsubscribes/,/import/github/personal_access_token,/admin/session}'::character varying[],
|
||||
protected_paths character varying(255)[] DEFAULT '{/users/password,/users/sign_in,/api/v3/session.json,/api/v3/session,/api/v4/session.json,/api/v4/session,/users,/users/confirmation,/unsubscribes/,/import/github/personal_access_token,/admin/session,/oauth/authorize,/oauth/token}'::character varying[],
|
||||
throttle_incident_management_notification_enabled boolean DEFAULT false NOT NULL,
|
||||
throttle_incident_management_notification_period_in_seconds integer DEFAULT 3600,
|
||||
throttle_incident_management_notification_per_period integer DEFAULT 3600,
|
||||
|
@ -9433,7 +9433,7 @@ CREATE TABLE public.aws_roles (
|
|||
user_id integer NOT NULL,
|
||||
created_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
|
||||
);
|
||||
|
||||
|
@ -23867,5 +23867,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200715202659
|
||||
20200716044023
|
||||
20200716120419
|
||||
20200717040735
|
||||
20200728182311
|
||||
\.
|
||||
|
||||
|
|
|
@ -169,3 +169,21 @@ successfully, you must replicate their data using some other means.
|
|||
LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).
|
||||
- (*3*): If you are using Object Storage, the replication can be performed by the
|
||||
Object Storage provider if supported. Please see [Geo with Object Storage](object_storage.md)
|
||||
|
||||
### Package file replication behind a feature flag
|
||||
|
||||
Package file replication is behind a feature flag - `geo_self_service_framework_replication`, which is enabled by default.
|
||||
|
||||
For GitLab self-managed instances, GitLab administrators can opt to disable them.
|
||||
|
||||
To disable:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:geo_self_service_framework_replication)
|
||||
```
|
||||
|
||||
To enable:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:geo_self_service_framework_replication)
|
||||
```
|
||||
|
|
|
@ -248,7 +248,7 @@ Example response:
|
|||
### Example with user / group level access **(STARTER)**
|
||||
|
||||
Elements in the `allowed_to_push` / `allowed_to_merge` / `allowed_to_unprotect` array should take the
|
||||
form `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}`. Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md). These access levels allow [more granular control over protected branch access](../user/project/protected_branches.md#restricting-push-and-merge-access-to-certain-users-starter) and were [added to the API in](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3516) in GitLab 10.3 EE.
|
||||
form `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}`. Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md). These access levels allow [more granular control over protected branch access](../user/project/protected_branches.md#restricting-push-and-merge-access-to-certain-users-starter) and were [added to the API in](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/3516) GitLab 10.3 EE.
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/protected_branches?name=*-stable&allowed_to_push%5B%5D%5Buser_id%5D=1"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9388) in [GitLab Silver](https://about.gitlab.com/pricing/) 11.10.
|
||||
|
||||
The SCIM API implements the [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
|
||||
The SCIM API implements [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
|
||||
|
||||
CAUTION: **Caution:**
|
||||
This API is for internal system use for connecting with a SCIM provider. While it can be used directly, it is subject to change without notice.
|
||||
|
|
|
@ -26,7 +26,7 @@ Pipeline jobs in GitLab CI/CD run in parallel, so it's possible that two deploym
|
|||
jobs in two different pipelines attempt to deploy to the same environment at the same
|
||||
time. This is not desired behavior as deployments should happen sequentially.
|
||||
|
||||
You can ensure only one deployment job runs at a time with the [`resource_group` keyword](../yaml/README.md#resource_group) keyword in your `.gitlab-ci.yml`.
|
||||
You can ensure only one deployment job runs at a time with the [`resource_group` keyword](../yaml/README.md#resource_group) in your `.gitlab-ci.yml`.
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ Group SAML SSO helps if you need to allow access via multiple SAML identity prov
|
|||
|
||||
To proceed with configuring Group SAML SSO instead, you'll need to enable the `group_saml` OmniAuth provider. This can be done from:
|
||||
|
||||
- `gitlab.rb` for GitLab [Omnibus installations](#omnibus-installations).
|
||||
- `gitlab.rb` for [Omnibus GitLab installations](#omnibus-installations).
|
||||
- `gitlab/config/gitlab.yml` for [source installations](#source-installations).
|
||||
|
||||
### Limitations
|
||||
|
|
|
@ -29,6 +29,11 @@ exceeds 100, the oldest ones are deleted.
|
|||
1. Use the previous steps to navigate to **Active Sessions**.
|
||||
1. Click on **Revoke** besides a session. The current session cannot be revoked, as this would sign you out of GitLab.
|
||||
|
||||
NOTE: **Note:**
|
||||
When any session is revoked all **Remember me** tokens for all
|
||||
devices will be revoked. See ['Why do I keep getting signed out?'](index.md#why-do-i-keep-getting-signed-out)
|
||||
for more information about the **Remember me** feature.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
|
|
|
@ -255,6 +255,12 @@ to get you a new `_gitlab_session` and keep you signed in through browser restar
|
|||
After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
|
||||
you will be asked to sign in again to verify your identity for security reasons.
|
||||
|
||||
NOTE: **Note:**
|
||||
When any session is signed out, or when a session is revoked
|
||||
via [Active Sessions](active_sessions.md), all **Remember me** tokens are revoked.
|
||||
While other sessions will remain active, the **Remember me** feature will not restore
|
||||
a session if the browser is closed or the existing session expires.
|
||||
|
||||
### Increased sign-in time
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20340) in GitLab 13.1.
|
||||
|
@ -264,7 +270,7 @@ The `remember_user_token` lifetime of a cookie can now extend beyond the deadlin
|
|||
GitLab uses both session and persistent cookies:
|
||||
|
||||
- Session cookie: Session cookies are normally removed at the end of the browser session when the browser is closed. The `_gitlab_session` cookie has no expiration date.
|
||||
- Persistent cookie: The `remember_me_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
|
||||
- Persistent cookie: The `remember_user_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
|
||||
|
||||
By default, the server sets a time-to-live (TTL) of 1-week on any session that is used.
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ as it most likely won't work if you set an [`MX` record](dns_concepts.md#mx-reco
|
|||
|
||||
Subdomains (`subdomain.example.com`) require:
|
||||
|
||||
- A DNS [CNAME record](dns_concepts.md#cname-record) record pointing your subdomain to the Pages server.
|
||||
- A DNS [CNAME record](dns_concepts.md#cname-record) pointing your subdomain to the Pages server.
|
||||
- A DNS [TXT record](dns_concepts.md#txt-record) to verify your domain's ownership.
|
||||
|
||||
| From | DNS Record | To |
|
||||
|
|
10
faraday-middleware-aws-signers-v4/.gitignore
vendored
10
faraday-middleware-aws-signers-v4/.gitignore
vendored
|
@ -1,10 +0,0 @@
|
|||
/.bundle/
|
||||
/.yardoc
|
||||
/Gemfile.lock
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
test.rb
|
|
@ -1,2 +0,0 @@
|
|||
--color
|
||||
--require spec_helper
|
|
@ -1,12 +0,0 @@
|
|||
sudo: false
|
||||
cache: bundler
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.0.0
|
||||
- 2.1.10
|
||||
- 2.2.6
|
||||
- 2.3.3
|
||||
- 2.4.0
|
||||
script:
|
||||
- bundle install
|
||||
- bundle exec rake
|
|
@ -1,4 +0,0 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in faraday_middleware-aws-signers-v4.gemspec
|
||||
gemspec
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Genki Sugawara
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,67 +0,0 @@
|
|||
# FaradayMiddleware::AwsSignersV4
|
||||
|
||||
[Faraday](https://github.com/lostisland/faraday) middleware for AWS Signature Version 4.
|
||||
|
||||
[![Gem Version](https://badge.fury.io/rb/faraday_middleware-aws-signers-v4.svg)](http://badge.fury.io/rb/faraday_middleware-aws-signers-v4)
|
||||
[![Build Status](https://travis-ci.org/winebarrel/faraday_middleware-aws-signers-v4.svg)](https://travis-ci.org/winebarrel/faraday_middleware-aws-signers-v4)
|
||||
[![Coverage Status](https://coveralls.io/repos/winebarrel/faraday_middleware-aws-signers-v4/badge.svg?branch=master&service=github)](https://coveralls.io/github/winebarrel/faraday_middleware-aws-signers-v4?branch=master)
|
||||
|
||||
**Currently developing new gem to support aws-sdk-v3.**
|
||||
|
||||
**see https://github.com/winebarrel/faraday_middleware-aws-sigv4**
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'faraday_middleware-aws-signers-v4'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install faraday_middleware-aws-signers-v4
|
||||
|
||||
## Usage
|
||||
|
||||
```ruby
|
||||
require 'faraday_middleware'
|
||||
require 'faraday_middleware/aws_signers_v4'
|
||||
require 'pp'
|
||||
|
||||
conn = Faraday.new(url: 'https://apigateway.us-east-1.amazonaws.com') do |faraday|
|
||||
faraday.request :aws_signers_v4,
|
||||
credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
|
||||
# If you use the credentials file:
|
||||
#credentials: Aws::SharedCredentials.new.credentials,
|
||||
service_name: 'apigateway',
|
||||
region: 'us-east-1'
|
||||
|
||||
faraday.response :json, :content_type => /\bjson\b/
|
||||
faraday.response :raise_error
|
||||
|
||||
faraday.adapter Faraday.default_adapter
|
||||
end
|
||||
|
||||
res = conn.get '/account'
|
||||
pp res.body
|
||||
# => {"accountUpdate"=>
|
||||
# {"name"=>nil,
|
||||
# "template"=>false,
|
||||
# "templateSkipList"=>nil,
|
||||
# "title"=>nil,
|
||||
# "updateAccountInput"=>nil},
|
||||
# "cloudwatchRoleArn"=>nil,
|
||||
# "self"=>
|
||||
# {"__type"=>
|
||||
# "GetAccountRequest:http://internal.amazon.com/coral/com.amazonaws.backplane.controlplane/",
|
||||
# "name"=>nil,
|
||||
# "template"=>false,
|
||||
# "templateSkipList"=>nil,
|
||||
# "title"=>nil},
|
||||
# "throttleSettings"=>{"burstLimit"=>1000, "rateLimit"=>500.0}}
|
||||
```
|
|
@ -1,6 +0,0 @@
|
|||
require 'bundler/gem_tasks'
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
|
@ -1,14 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "bundler/setup"
|
||||
require "faraday_middleware/aws_signers_v4"
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||
# require "pry"
|
||||
# Pry.start
|
||||
|
||||
require "irb"
|
||||
IRB.start
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
bundle install
|
||||
|
||||
# Do any other automated setup that you need to do here
|
|
@ -1,28 +0,0 @@
|
|||
# coding: utf-8
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = 'faraday_middleware-aws-signers-v4'
|
||||
spec.version = '0.1.9'
|
||||
spec.authors = ['Genki Sugawara']
|
||||
spec.email = ['sgwr_dts@yahoo.co.jp']
|
||||
|
||||
spec.summary = %q{Faraday middleware for AWS Signature Version 4.}
|
||||
spec.description = %q{Faraday middleware for AWS Signature Version 4.}
|
||||
spec.homepage = 'https://github.com/winebarrel/faraday_middleware-aws-signers-v4'
|
||||
spec.license = 'MIT'
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
spec.bindir = 'exe'
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.add_dependency 'faraday', '~> 0.9'
|
||||
spec.add_dependency 'aws-sdk-resources', '>= 2', '< 3'
|
||||
|
||||
spec.add_development_dependency 'bundler'
|
||||
spec.add_development_dependency 'rake'
|
||||
spec.add_development_dependency 'rspec', '>= 3.0.0'
|
||||
spec.add_development_dependency 'timecop'
|
||||
spec.add_development_dependency 'faraday_middleware'
|
||||
spec.add_development_dependency 'coveralls'
|
||||
spec.add_development_dependency 'webmock'
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
require 'faraday_middleware/aws_signers_v4'
|
|
@ -1,8 +0,0 @@
|
|||
require 'aws-sdk-resources'
|
||||
require 'faraday'
|
||||
|
||||
module FaradayMiddleware
|
||||
autoload :AwsSignersV4, 'faraday_middleware/request/aws_signers_v4'
|
||||
|
||||
Faraday::Request.register_middleware :aws_signers_v4 => lambda { AwsSignersV4 }
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
require 'aws-sdk-core/signers/v4'
|
||||
|
||||
module AwsSignersV4Ext
|
||||
def signed_headers(request)
|
||||
super.downcase
|
||||
end
|
||||
end
|
||||
|
||||
class Aws::Signers::V4
|
||||
unless Aws::Signers::V4 < AwsSignersV4Ext
|
||||
prepend AwsSignersV4Ext
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
require 'uri'
|
||||
require 'aws-sdk-resources'
|
||||
|
||||
module URI
|
||||
def self.seahorse_encode_www_form(params)
|
||||
params.map {|key, value|
|
||||
encoded_key = encode_www_form_component(key)
|
||||
|
||||
if value.nil?
|
||||
encoded_key
|
||||
elsif value.respond_to?(:to_ary)
|
||||
value.to_ary.map {|v|
|
||||
if v.nil?
|
||||
# bug?
|
||||
#encoded_key
|
||||
else
|
||||
encoded_key + '=' + Seahorse::Util.uri_escape(v)
|
||||
end
|
||||
}.join('&')
|
||||
else
|
||||
encoded_key + '=' + Seahorse::Util.uri_escape(value)
|
||||
end
|
||||
}.join('&')
|
||||
end
|
||||
end
|
|
@ -1,57 +0,0 @@
|
|||
require 'faraday_middleware/ext/uri_ext'
|
||||
require 'faraday_middleware/aws_signers_v4_ext'
|
||||
|
||||
class FaradayMiddleware::AwsSignersV4 < Faraday::Middleware
|
||||
class Request
|
||||
def initialize(env)
|
||||
@env = env
|
||||
end
|
||||
|
||||
def headers
|
||||
@env.request_headers
|
||||
end
|
||||
|
||||
def body
|
||||
@env.body || ''
|
||||
end
|
||||
|
||||
def endpoint
|
||||
url = @env.url.dup
|
||||
|
||||
# Escape the query string or the request won't sign correctly
|
||||
if url and url.query
|
||||
re_escape_query!(url)
|
||||
end
|
||||
|
||||
url
|
||||
end
|
||||
|
||||
def http_method
|
||||
@env.method.to_s.upcase
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def re_escape_query!(url)
|
||||
params = URI.decode_www_form(url.query)
|
||||
|
||||
if params.any? {|k, v| v =~ / / }
|
||||
url.query = URI.seahorse_encode_www_form(params)
|
||||
end
|
||||
end
|
||||
end # of class Request
|
||||
|
||||
def initialize(app, options = nil)
|
||||
super(app)
|
||||
|
||||
@credentials = options.fetch(:credentials)
|
||||
@service_name = options.fetch(:service_name)
|
||||
@region = options.fetch(:region)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
req = Request.new(env)
|
||||
Aws::Signers::V4.new(@credentials, @service_name, @region).sign(req)
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
|
@ -67,7 +67,7 @@ module API
|
|||
deploy_token_from_request ||
|
||||
find_user_from_bearer_token ||
|
||||
find_user_from_job_token ||
|
||||
find_user_from_warden
|
||||
user_from_warden
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -100,6 +100,25 @@ module API
|
|||
def user_allowed_or_deploy_token?(user)
|
||||
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
|
||||
end
|
||||
|
||||
def user_from_warden
|
||||
user = find_user_from_warden
|
||||
|
||||
return unless user
|
||||
return if two_factor_required_but_not_setup?(user)
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def two_factor_required_but_not_setup?(user)
|
||||
verifier = Gitlab::Auth::TwoFactorAuthVerifier.new(user)
|
||||
|
||||
if verifier.two_factor_authentication_required? && verifier.current_user_needs_to_setup_two_factor?
|
||||
verifier.two_factor_grace_period_expired?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
|
|
@ -109,9 +109,10 @@ module API
|
|||
end
|
||||
put ":id/badges/:badge_id" do
|
||||
source = find_source_if_admin(source_type)
|
||||
badge = find_badge(source)
|
||||
|
||||
badge = ::Badges::UpdateService.new(declared_params(include_missing: false))
|
||||
.execute(find_badge(source))
|
||||
.execute(badge)
|
||||
|
||||
if badge.valid?
|
||||
present_badges(source, badge)
|
||||
|
@ -130,10 +131,6 @@ module API
|
|||
source = find_source_if_admin(source_type)
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,8 @@ module API
|
|||
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_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
|
||||
require_packages_enabled!
|
||||
|
||||
|
@ -233,7 +235,7 @@ module API
|
|||
end
|
||||
|
||||
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
|
||||
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
|
||||
desc 'Download recipe files' do
|
||||
|
@ -248,7 +250,7 @@ module API
|
|||
detail 'This feature was introduced in GitLab 12.6'
|
||||
end
|
||||
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
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
put do
|
||||
|
@ -267,7 +269,7 @@ module API
|
|||
params do
|
||||
requires :conan_package_reference, type: String, desc: 'Conan Package ID'
|
||||
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
|
||||
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
|
||||
desc 'Download package files' do
|
||||
|
@ -290,7 +292,7 @@ module API
|
|||
detail 'This feature was introduced in GitLab 12.6'
|
||||
end
|
||||
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
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
put do
|
||||
|
|
|
@ -6,7 +6,13 @@ module API
|
|||
include ::API::Helpers::MembersHelpers
|
||||
|
||||
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
|
||||
|
||||
def present_badges(source, records, options = {})
|
||||
|
|
|
@ -125,15 +125,15 @@ module API
|
|||
end
|
||||
|
||||
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')
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
::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
|
||||
|
||||
|
@ -204,7 +204,7 @@ module API
|
|||
|
||||
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
|
||||
|
||||
def decode_oauth_token_from_jwt
|
||||
|
|
|
@ -33,7 +33,7 @@ module API
|
|||
|
||||
return unless token
|
||||
|
||||
::Ci::Build.find_by_token(token)
|
||||
::Ci::AuthJobFinder.new(token: token).execute
|
||||
end
|
||||
|
||||
def find_deploy_token_from_http_basic_auth
|
||||
|
|
|
@ -25,11 +25,13 @@ module Gitlab
|
|||
project_repositories_archive: { threshold: 5, interval: 1.minute },
|
||||
project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
|
||||
project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute },
|
||||
project_testing_hook: { threshold: 5, interval: 1.minute },
|
||||
play_pipeline_schedule: { threshold: 1, interval: 1.minute },
|
||||
show_raw_controller: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
|
||||
group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute },
|
||||
group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
|
||||
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }
|
||||
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute },
|
||||
group_testing_hook: { threshold: 5, interval: 1.minute }
|
||||
}.freeze
|
||||
end
|
||||
|
||||
|
|
|
@ -63,7 +63,15 @@ module Gitlab
|
|||
raise Gitlab::Auth::MissingPersonalAccessTokenError
|
||||
end
|
||||
|
||||
def find_with_user_password(login, password)
|
||||
# Find and return a user if the provided password is valid for various
|
||||
# authenticators (OAuth, LDAP, Local Database).
|
||||
#
|
||||
# Specify `increment_failed_attempts: true` to increment Devise `failed_attempts`.
|
||||
# CAUTION: Avoid incrementing failed attempts when authentication falls through
|
||||
# different mechanisms, as in `.find_for_git_client`. This may lead to
|
||||
# unwanted access locks when the value provided for `password` was actually
|
||||
# a PAT, deploy token, etc.
|
||||
def find_with_user_password(login, password, increment_failed_attempts: false)
|
||||
# Avoid resource intensive checks if login credentials are not provided
|
||||
return unless login.present? && password.present?
|
||||
|
||||
|
@ -94,10 +102,14 @@ module Gitlab
|
|||
authenticators.compact!
|
||||
|
||||
# return found user that was authenticated first for given login credentials
|
||||
authenticators.find do |auth|
|
||||
authenticated_user = authenticators.find do |auth|
|
||||
authenticated_user = auth.login(login, password)
|
||||
break authenticated_user if authenticated_user
|
||||
end
|
||||
|
||||
user_auth_attempt!(user, success: !!authenticated_user) if increment_failed_attempts
|
||||
|
||||
authenticated_user
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -220,6 +232,8 @@ module Gitlab
|
|||
|
||||
# Registry access (with jwt) does not have access to project
|
||||
return if project && !token.has_access_to?(project)
|
||||
# When repository is disabled, no resources are accessible via Deploy Token
|
||||
return if project&.repository_access_level == ::ProjectFeature::DISABLED
|
||||
|
||||
scopes = abilities_for_scopes(token.scopes)
|
||||
|
||||
|
@ -353,6 +367,13 @@ module Gitlab
|
|||
def find_build_by_token(token)
|
||||
::Ci::Build.running.find_by_token(token)
|
||||
end
|
||||
|
||||
def user_auth_attempt!(user, success:)
|
||||
return unless user && Gitlab::Database.read_write?
|
||||
return user.unlock_access! if success
|
||||
|
||||
user.increment_failed_attempts!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,9 +68,7 @@ module Gitlab
|
|||
current_request.env[JOB_TOKEN_HEADER].presence
|
||||
return unless token
|
||||
|
||||
job = ::Ci::Build.find_by_token(token)
|
||||
raise UnauthorizedError unless job
|
||||
|
||||
job = find_valid_running_job_by_token!(token)
|
||||
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
job.user
|
||||
|
@ -83,9 +81,7 @@ module Gitlab
|
|||
return unless login.present? && password.present?
|
||||
return unless ::Ci::Build::CI_REGISTRY_USER == login
|
||||
|
||||
job = ::Ci::Build.find_by_token(password)
|
||||
raise UnauthorizedError unless job
|
||||
|
||||
job = find_valid_running_job_by_token!(password)
|
||||
job.user
|
||||
end
|
||||
|
||||
|
@ -169,7 +165,7 @@ module Gitlab
|
|||
token = parsed_oauth_token
|
||||
return unless token
|
||||
|
||||
job = ::Ci::Build.find_by_token(token)
|
||||
job = ::Ci::AuthJobFinder.new(token: token).execute
|
||||
return unless job
|
||||
|
||||
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
@ -294,6 +290,12 @@ module Gitlab
|
|||
def blob_request?
|
||||
current_request.path.include?('/raw/')
|
||||
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
|
||||
|
|
36
lib/gitlab/auth/two_factor_auth_verifier.rb
Normal file
36
lib/gitlab/auth/two_factor_auth_verifier.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Auth
|
||||
class TwoFactorAuthVerifier
|
||||
attr_reader :current_user
|
||||
|
||||
def initialize(current_user)
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def two_factor_authentication_required?
|
||||
Gitlab::CurrentSettings.require_two_factor_authentication? ||
|
||||
current_user&.require_two_factor_authentication_from_group?
|
||||
end
|
||||
|
||||
def current_user_needs_to_setup_two_factor?
|
||||
current_user && !current_user.temp_oauth_email? && !current_user.two_factor_enabled?
|
||||
end
|
||||
|
||||
def two_factor_grace_period
|
||||
periods = [Gitlab::CurrentSettings.two_factor_grace_period]
|
||||
periods << current_user.two_factor_grace_period if current_user&.require_two_factor_authentication_from_group?
|
||||
periods.min
|
||||
end
|
||||
|
||||
def two_factor_grace_period_expired?
|
||||
time = current_user&.otp_grace_period_started_at
|
||||
|
||||
return false unless time
|
||||
|
||||
two_factor_grace_period.hours.since(time) < Time.current
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -91,6 +91,13 @@ module Gitlab
|
|||
Guest.can?(:download_code, project)
|
||||
end
|
||||
|
||||
def deploy_key_can_download_code?
|
||||
authentication_abilities.include?(:download_code) &&
|
||||
deploy_key? &&
|
||||
deploy_key.has_access_to?(container) &&
|
||||
project&.repository_access_level != ::Featurable::DISABLED
|
||||
end
|
||||
|
||||
def user_can_download_code?
|
||||
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
|
||||
end
|
||||
|
@ -235,7 +242,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def check_download_access!
|
||||
passed = deploy_key? ||
|
||||
passed = deploy_key_can_download_code? ||
|
||||
deploy_token? ||
|
||||
user_can_download_code? ||
|
||||
build_can_download_code? ||
|
||||
|
|
|
@ -6,11 +6,6 @@ module Gitlab
|
|||
CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt].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
|
||||
@conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze
|
||||
end
|
||||
|
|
|
@ -2730,6 +2730,9 @@ msgstr ""
|
|||
msgid "An error occurred while validating username"
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred. Please sign in again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -3143,7 +3146,7 @@ msgstr ""
|
|||
msgid "Are you sure? Removing this GPG key does not affect already signed commits."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure? The device will be signed out of GitLab."
|
||||
msgid "Are you sure? The device will be signed out of GitLab and all remember me tokens revoked."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure? This will invalidate your registered applications and U2F devices."
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"ipaddr.js": "^1.9.1",
|
||||
"jed": "^1.1.1",
|
||||
"jest-transform-graphql": "^2.1.0",
|
||||
"jquery": "^3.4.1",
|
||||
"jquery": "^3.5.0",
|
||||
"jquery-ujs": "1.2.2",
|
||||
"jquery.caret": "^0.3.1",
|
||||
"jquery.waitforimages": "^2.2.0",
|
||||
|
|
|
@ -40,7 +40,7 @@ RSpec.describe Admin::ApplicationsController do
|
|||
|
||||
describe 'POST #create' do
|
||||
it 'creates the application' do
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false)
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
|
||||
|
||||
expect do
|
||||
post :create, params: { doorkeeper_application: create_params }
|
||||
|
@ -63,7 +63,7 @@ RSpec.describe Admin::ApplicationsController do
|
|||
|
||||
context 'when the params are for a confidential application' do
|
||||
it 'creates a confidential application' do
|
||||
create_params = attributes_for(:application, confidential: true)
|
||||
create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
|
||||
|
||||
expect do
|
||||
post :create, params: { doorkeeper_application: create_params }
|
||||
|
@ -75,6 +75,18 @@ RSpec.describe Admin::ApplicationsController do
|
|||
expect(application).to have_attributes(create_params.except(:uid, :owner_type))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when scopes are not present' do
|
||||
it 'renders the application form on errors' do
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false)
|
||||
|
||||
expect do
|
||||
post :create, params: { doorkeeper_application: create_params }
|
||||
end.not_to change { Doorkeeper::Application.count }
|
||||
|
||||
expect(response).to render_template :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH #update' do
|
||||
|
|
|
@ -99,7 +99,9 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
def get_new(provider: 'gcp')
|
||||
let(:user) { admin }
|
||||
|
||||
def go(provider: 'gcp')
|
||||
get :new, params: { provider: provider }
|
||||
end
|
||||
|
||||
|
@ -112,7 +114,7 @@ RSpec.describe Admin::ClustersController do
|
|||
|
||||
context 'when selected provider is gke and no valid gcp token exists' do
|
||||
it 'redirects to gcp authorize_url' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(response).to redirect_to(assigns(:authorize_url))
|
||||
end
|
||||
|
@ -125,7 +127,7 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
it 'does not have authorize_url' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(assigns(:authorize_url)).to be_nil
|
||||
end
|
||||
|
@ -137,7 +139,7 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
it 'has new object' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
|
@ -158,16 +160,18 @@ RSpec.describe Admin::ClustersController do
|
|||
|
||||
describe 'functionality for existing cluster' do
|
||||
it 'has new object' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'GET new cluster shared examples'
|
||||
|
||||
describe 'security' do
|
||||
it { expect { get_new }.to be_allowed_for(:admin) }
|
||||
it { expect { get_new }.to be_denied_for(:user) }
|
||||
it { expect { get_new }.to be_denied_for(:external) }
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -424,14 +428,13 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
describe 'POST authorize AWS role for EKS cluster' do
|
||||
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
|
||||
let(:role_external_id) { '12345' }
|
||||
let!(:role) { create(:aws_role, user: admin) }
|
||||
|
||||
let(:role_arn) { 'arn:new-role' }
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
role_arn: role_arn,
|
||||
role_external_id: role_external_id
|
||||
role_arn: role_arn
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -445,28 +448,32 @@ RSpec.describe Admin::ClustersController do
|
|||
.and_return(double(execute: double))
|
||||
end
|
||||
|
||||
it 'creates an Aws::Role record' do
|
||||
expect { go }.to change { Aws::Role.count }
|
||||
it 'updates the associated role with the supplied ARN' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
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
|
||||
expect(role.reload.role_arn).to eq(role_arn)
|
||||
end
|
||||
|
||||
context 'role cannot be created' do
|
||||
context 'supplied role is invalid' do
|
||||
let(:role_arn) { 'invalid-role' }
|
||||
|
||||
it 'does not create a record' do
|
||||
expect { go }.not_to change { Aws::Role.count }
|
||||
it 'does not update the associated role' do
|
||||
expect { go }.not_to change { role.role_arn }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
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_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
|
|
|
@ -239,6 +239,7 @@ RSpec.describe ApplicationController do
|
|||
|
||||
it 'does not redirect if 2FA is not required' do
|
||||
allow(controller).to receive(:two_factor_authentication_required?).and_return(false)
|
||||
allow(controller).to receive(:current_user).and_return(create(:user))
|
||||
|
||||
expect(controller).not_to receive(:redirect_to)
|
||||
|
||||
|
@ -356,13 +357,17 @@ RSpec.describe ApplicationController do
|
|||
let(:user) { create :user, otp_grace_period_started_at: 2.hours.ago }
|
||||
|
||||
it 'returns true if the grace period has expired' do
|
||||
allow(controller).to receive(:two_factor_grace_period).and_return(1)
|
||||
allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier|
|
||||
allow(verifier).to receive(:two_factor_grace_period).and_return(2)
|
||||
end
|
||||
|
||||
expect(subject).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if the grace period is still active' do
|
||||
allow(controller).to receive(:two_factor_grace_period).and_return(3)
|
||||
allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier|
|
||||
allow(verifier).to receive(:two_factor_grace_period).and_return(3)
|
||||
end
|
||||
|
||||
expect(subject).to be_falsey
|
||||
end
|
||||
|
|
|
@ -180,6 +180,8 @@ RSpec.describe Groups::ClustersController do
|
|||
end
|
||||
end
|
||||
|
||||
include_examples 'GET new cluster shared examples'
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(group) }
|
||||
|
@ -493,14 +495,13 @@ RSpec.describe Groups::ClustersController do
|
|||
end
|
||||
|
||||
describe 'POST authorize AWS role for EKS cluster' do
|
||||
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
|
||||
let(:role_external_id) { '12345' }
|
||||
let!(:role) { create(:aws_role, user: user) }
|
||||
|
||||
let(:role_arn) { 'arn:new-role' }
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
role_arn: role_arn,
|
||||
role_external_id: role_external_id
|
||||
role_arn: role_arn
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -514,28 +515,32 @@ RSpec.describe Groups::ClustersController do
|
|||
.and_return(double(execute: double))
|
||||
end
|
||||
|
||||
it 'creates an Aws::Role record' do
|
||||
expect { go }.to change { Aws::Role.count }
|
||||
it 'updates the associated role with the supplied ARN' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
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
|
||||
expect(role.reload.role_arn).to eq(role_arn)
|
||||
end
|
||||
|
||||
context 'role cannot be created' do
|
||||
context 'supplied role is invalid' do
|
||||
let(:role_arn) { 'invalid-role' }
|
||||
|
||||
it 'does not create a record' do
|
||||
expect { go }.not_to change { Aws::Role.count }
|
||||
it 'does not update the associated role' do
|
||||
expect { go }.not_to change { role.role_arn }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
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(:owner).of(group) }
|
||||
it { expect { go }.to be_allowed_for(:maintainer).of(group) }
|
||||
|
|
|
@ -123,7 +123,8 @@ RSpec.describe Oauth::ApplicationsController do
|
|||
invalid_uri_params = {
|
||||
doorkeeper_application: {
|
||||
name: 'foo',
|
||||
redirect_uri: 'javascript://alert()'
|
||||
redirect_uri: 'javascript://alert()',
|
||||
scopes: ['api']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +134,23 @@ RSpec.describe Oauth::ApplicationsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when scopes are not present' do
|
||||
render_views
|
||||
|
||||
it 'shows an error for blank scopes' do
|
||||
invalid_uri_params = {
|
||||
doorkeeper_application: {
|
||||
name: 'foo',
|
||||
redirect_uri: 'http://example.org'
|
||||
}
|
||||
}
|
||||
|
||||
post :create, params: invalid_uri_params
|
||||
|
||||
expect(response.body).to include 'Scopes can't be blank'
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'redirects to login page when the user is not signed in'
|
||||
it_behaves_like 'redirects to 2fa setup page when the user requires it'
|
||||
end
|
||||
|
@ -172,7 +190,8 @@ RSpec.describe Oauth::ApplicationsController do
|
|||
{
|
||||
doorkeeper_application: {
|
||||
name: 'foo',
|
||||
redirect_uri: 'http://example.org'
|
||||
redirect_uri: 'http://example.org',
|
||||
scopes: ['api']
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -40,6 +40,22 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when sign in is not valid' do
|
||||
let(:provider) { :github }
|
||||
let(:extern_uid) { 'my-uid' }
|
||||
|
||||
it 'renders omniauth error page' do
|
||||
allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance|
|
||||
allow(instance).to receive(:valid_sign_in?).and_return(false)
|
||||
end
|
||||
|
||||
post provider
|
||||
|
||||
expect(response).to render_template("errors/omniauth_error")
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is on the last sign in attempt' do
|
||||
let(:extern_uid) { 'my-uid' }
|
||||
|
||||
|
|
23
spec/controllers/profiles/active_sessions_controller_spec.rb
Normal file
23
spec/controllers/profiles/active_sessions_controller_spec.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Profiles::ActiveSessionsController do
|
||||
describe 'DELETE destroy' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'invalidates all remember user tokens' do
|
||||
ActiveSession.set(user, request)
|
||||
session_id = request.session.id.public_id
|
||||
user.remember_me!
|
||||
|
||||
delete :destroy, params: { id: session_id }
|
||||
|
||||
expect(user.reload.remember_created_at).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,10 +14,9 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
it 'generates otp_secret for user' do
|
||||
expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once
|
||||
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once
|
||||
|
||||
get :show
|
||||
get :show # Second hit shouldn't re-generate it
|
||||
end
|
||||
|
||||
it 'assigns qr_code' do
|
||||
|
@ -27,6 +26,14 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
get :show
|
||||
expect(assigns[:qr_code]).to eq code
|
||||
end
|
||||
|
||||
it 'generates a unique otp_secret every time the page is loaded' do
|
||||
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.twice
|
||||
|
||||
2.times do
|
||||
get :show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
|
@ -57,6 +64,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
expect(assigns[:codes]).to match_array %w(a b c)
|
||||
end
|
||||
|
||||
it 'calls to delete other sessions' do
|
||||
expect(ActiveSession).to receive(:destroy_all_but_current)
|
||||
|
||||
go
|
||||
end
|
||||
|
||||
it 'renders create' do
|
||||
go
|
||||
expect(response).to render_template(:create)
|
||||
|
|
|
@ -183,6 +183,8 @@ RSpec.describe Projects::ClustersController do
|
|||
end
|
||||
end
|
||||
|
||||
include_examples 'GET new cluster shared examples'
|
||||
|
||||
describe 'security' do
|
||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||
expect { go }.to be_allowed_for(:admin)
|
||||
|
@ -521,14 +523,13 @@ RSpec.describe Projects::ClustersController do
|
|||
end
|
||||
|
||||
describe 'POST authorize AWS role for EKS cluster' do
|
||||
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
|
||||
let(:role_external_id) { '12345' }
|
||||
let!(:role) { create(:aws_role, user: user) }
|
||||
|
||||
let(:role_arn) { 'arn:new-role' }
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
role_arn: role_arn,
|
||||
role_external_id: role_external_id
|
||||
role_arn: role_arn
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -542,28 +543,32 @@ RSpec.describe Projects::ClustersController do
|
|||
.and_return(double(execute: double))
|
||||
end
|
||||
|
||||
it 'creates an Aws::Role record' do
|
||||
expect { go }.to change { Aws::Role.count }
|
||||
it 'updates the associated role with the supplied ARN' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
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
|
||||
expect(role.reload.role_arn).to eq(role_arn)
|
||||
end
|
||||
|
||||
context 'role cannot be created' do
|
||||
context 'supplied role is invalid' do
|
||||
let(:role_arn) { 'invalid-role' }
|
||||
|
||||
it 'does not create a record' do
|
||||
expect { go }.not_to change { Aws::Role.count }
|
||||
it 'does not update the associated role' do
|
||||
expect { go }.not_to change { role.role_arn }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
expect { go }.to be_allowed_for(:admin)
|
||||
end
|
||||
|
|
|
@ -46,4 +46,26 @@ RSpec.describe Projects::HooksController do
|
|||
expect(ProjectHook.first).to have_attributes(hook_params)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#test' do
|
||||
let(:hook) { create(:project_hook, project: project) }
|
||||
|
||||
context 'when the endpoint receives requests above the limit' do
|
||||
before do
|
||||
allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits)
|
||||
.and_return(project_testing_hook: { threshold: 1, interval: 1.minute })
|
||||
end
|
||||
|
||||
it 'prevents making test requests' do
|
||||
expect_next_instance_of(TestHooks::ProjectService) do |service|
|
||||
expect(service).to receive(:execute).and_return(http_status: 200)
|
||||
end
|
||||
|
||||
2.times { post :test, params: { namespace_id: project.namespace, project_id: project, id: hook } }
|
||||
|
||||
expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.'))
|
||||
expect(response).to have_gitlab_http_status(:too_many_requests)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -288,8 +288,8 @@ RSpec.describe SessionsController do
|
|||
context 'when using two-factor authentication via OTP' do
|
||||
let(:user) { create(:user, :two_factor) }
|
||||
|
||||
def authenticate_2fa(user_params)
|
||||
post(:create, params: { user: user_params }, session: { otp_user_id: user.id })
|
||||
def authenticate_2fa(user_params, otp_user_id: user.id)
|
||||
post(:create, params: { user: user_params }, session: { otp_user_id: otp_user_id })
|
||||
end
|
||||
|
||||
context 'remember_me field' do
|
||||
|
@ -326,8 +326,22 @@ RSpec.describe SessionsController do
|
|||
end
|
||||
end
|
||||
|
||||
# See issue gitlab-org/gitlab#20302.
|
||||
context 'when otp_user_id is stale' do
|
||||
render_views
|
||||
|
||||
it 'favors login over otp_user_id when password is present and does not authenticate the user' do
|
||||
authenticate_2fa(
|
||||
{ login: 'random_username', password: user.password },
|
||||
otp_user_id: user.id
|
||||
)
|
||||
|
||||
expect(response).to set_flash.now[:alert].to /Invalid Login or password/
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# See #14900 issue
|
||||
# See issue gitlab-org/gitlab-foss#14900
|
||||
#
|
||||
context 'when authenticating with login and OTP of another user' do
|
||||
context 'when another user has 2FA enabled' do
|
||||
|
@ -413,18 +427,6 @@ RSpec.describe SessionsController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when another user does not have 2FA enabled' do
|
||||
let(:another_user) { create(:user) }
|
||||
|
||||
it 'does not leak that 2FA is disabled for another user' do
|
||||
authenticate_2fa(login: another_user.username,
|
||||
otp_attempt: 'invalid')
|
||||
|
||||
expect(response).to set_flash.now[:alert]
|
||||
.to /Invalid two-factor code/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe 'admin manage applications' do
|
|||
sign_in(create(:admin))
|
||||
end
|
||||
|
||||
it do
|
||||
it 'creates new oauth application' do
|
||||
visit admin_applications_path
|
||||
|
||||
click_on 'New application'
|
||||
|
@ -16,6 +16,7 @@ RSpec.describe 'admin manage applications' do
|
|||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
check :doorkeeper_application_trusted
|
||||
check :doorkeeper_application_scopes_read_user
|
||||
click_on 'Submit'
|
||||
expect(page).to have_content('Application: test')
|
||||
expect(page).to have_content('Application ID')
|
||||
|
@ -43,4 +44,19 @@ RSpec.describe 'admin manage applications' do
|
|||
end
|
||||
expect(page.find('.oauth-applications')).not_to have_content('test_changed')
|
||||
end
|
||||
|
||||
context 'when scopes are blank' do
|
||||
it 'returns an error' do
|
||||
visit admin_applications_path
|
||||
|
||||
click_on 'New application'
|
||||
expect(page).to have_content('New application')
|
||||
|
||||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
click_on 'Submit'
|
||||
|
||||
expect(page).to have_content("Scopes can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ RSpec.describe 'User manages applications' do
|
|||
|
||||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
check :doorkeeper_application_scopes_read_user
|
||||
click_on 'Save application'
|
||||
|
||||
expect(page).to have_content 'Application: test'
|
||||
|
@ -41,4 +42,16 @@ RSpec.describe 'User manages applications' do
|
|||
end
|
||||
expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
|
||||
end
|
||||
|
||||
context 'when scopes are blank' do
|
||||
it 'returns an error' do
|
||||
expect(page).to have_content 'Add new application'
|
||||
|
||||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
click_on 'Save application'
|
||||
|
||||
expect(page).to have_content("Scopes can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -177,6 +177,14 @@ RSpec.describe 'Login' do
|
|||
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
|
||||
end
|
||||
|
||||
it 'does not allow sign-in if the user password is updated before entering a one-time code' do
|
||||
user.update!(password: 'new_password')
|
||||
|
||||
enter_code(user.current_otp)
|
||||
|
||||
expect(page).to have_content('An error occurred. Please sign in again.')
|
||||
end
|
||||
|
||||
context 'using one-time code' do
|
||||
it 'allows login with valid code' do
|
||||
expect(authentication_metrics)
|
||||
|
@ -232,7 +240,7 @@ RSpec.describe 'Login' do
|
|||
expect(codes.size).to eq 10
|
||||
|
||||
# Ensure the generated codes get saved
|
||||
user.save
|
||||
user.save(touch: false)
|
||||
end
|
||||
|
||||
context 'with valid code' do
|
||||
|
@ -290,7 +298,7 @@ RSpec.describe 'Login' do
|
|||
code = codes.sample
|
||||
expect(user.invalidate_otp_backup_code!(code)).to eq true
|
||||
|
||||
user.save!
|
||||
user.save!(touch: false)
|
||||
expect(user.reload.otp_backup_codes.size).to eq 9
|
||||
|
||||
enter_code(code)
|
||||
|
|
64
spec/finders/ci/auth_job_finder_spec.rb
Normal file
64
spec/finders/ci/auth_job_finder_spec.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
# 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,8 +11,10 @@ RSpec.describe UserRecentEventsFinder do
|
|||
let!(:private_event) { create(:event, project: private_project, author: project_owner) }
|
||||
let!(:internal_event) { create(:event, project: internal_project, author: project_owner) }
|
||||
let!(:public_event) { create(:event, project: public_project, author: project_owner) }
|
||||
let(:limit) { nil }
|
||||
let(:params) { { limit: limit } }
|
||||
|
||||
subject(:finder) { described_class.new(current_user, project_owner) }
|
||||
subject(:finder) { described_class.new(current_user, project_owner, params) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'when profile is public' do
|
||||
|
@ -48,5 +50,38 @@ RSpec.describe UserRecentEventsFinder do
|
|||
expect(events).to include(event_b)
|
||||
end
|
||||
end
|
||||
|
||||
context 'limits' do
|
||||
before do
|
||||
stub_const("#{described_class}::DEFAULT_LIMIT", 1)
|
||||
stub_const("#{described_class}::MAX_LIMIT", 3)
|
||||
end
|
||||
|
||||
context 'when limit is not set' do
|
||||
it 'returns events limited to DEFAULT_LIMIT' do
|
||||
expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when limit is set' do
|
||||
let(:limit) { 2 }
|
||||
|
||||
it 'returns events limited to specified limit' do
|
||||
expect(finder.execute.size).to eq(limit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when limit is set to a number that exceeds maximum limit' do
|
||||
let(:limit) { 4 }
|
||||
|
||||
before do
|
||||
create(:event, project: public_project, author: project_owner)
|
||||
end
|
||||
|
||||
it 'returns events limited to MAX_LIMIT' do
|
||||
expect(finder.execute.size).to eq(described_class::MAX_LIMIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -212,6 +212,55 @@ RSpec.describe GitlabSchema do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.parse_gid' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
|
||||
|
||||
before do
|
||||
test_base = Class.new
|
||||
test_one = Class.new(test_base)
|
||||
test_two = Class.new(test_base)
|
||||
|
||||
stub_const('TestBase', test_base)
|
||||
stub_const('TestOne', test_one)
|
||||
stub_const('TestTwo', test_two)
|
||||
end
|
||||
|
||||
it 'parses the gid' do
|
||||
gid = described_class.parse_gid(global_id)
|
||||
|
||||
expect(gid.model_id).to eq '2147483647'
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
context 'when gid is malformed' do
|
||||
let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { described_class.parse_gid(global_id) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using expected_type' do
|
||||
it 'accepts a single type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestOne)
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'accepts an ancestor type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestBase)
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'rejects an unknown type' do
|
||||
expect { described_class.parse_gid(global_id, expected_type: TestTwo) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid id for TestTwo.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def field_instrumenters
|
||||
described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
|
||||
end
|
||||
|
|
|
@ -45,7 +45,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
|
|||
describe '#find_job_from_http_basic_auth' do
|
||||
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(:headers) { { Authorization: basic_http_auth(username, password) } }
|
||||
|
||||
|
@ -57,6 +57,14 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
|
|||
|
||||
context 'with a valid Authorization header' do
|
||||
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
|
||||
|
||||
context 'with an invalid Authorization header' do
|
||||
|
|
|
@ -36,13 +36,31 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
|
||||
it "return user if token is valid" do
|
||||
context 'with a running job' do
|
||||
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
|
||||
|
||||
context 'with a job that is not running' do
|
||||
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
|
||||
|
||||
describe '#find_user_from_bearer_token' do
|
||||
|
@ -556,7 +574,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
context 'with CI username' do
|
||||
let(:username) { ::Ci::Build::CI_REGISTRY_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
|
||||
set_basic_auth_header(username, nil)
|
||||
|
@ -575,6 +593,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
|
||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
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
|
||||
|
||||
|
@ -585,7 +610,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
|
||||
context 'with a job token' do
|
||||
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
|
||||
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
|
||||
|
@ -640,7 +665,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
end
|
||||
|
||||
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 } }
|
||||
|
||||
subject { find_user_from_job_token }
|
||||
|
@ -665,6 +690,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
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
|
||||
let(:route_authentication_setting) { { job_token_allowed: false } }
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
|||
|
||||
describe '#find_user_from_job_token' do
|
||||
let!(:user) { build(:user) }
|
||||
let!(:job) { build(:ci_build, user: user) }
|
||||
let!(:job) { build(:ci_build, user: user, status: :running) }
|
||||
|
||||
before do
|
||||
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token'
|
||||
|
@ -96,13 +96,18 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
|||
context 'with API requests' do
|
||||
before do
|
||||
env['SCRIPT_NAME'] = '/api/endpoint'
|
||||
expect(::Ci::Build).to receive(:find_by_token).with('token').and_return(job)
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
context 'without API requests' do
|
||||
|
|
112
spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
Normal file
112
spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
subject { described_class.new(user) }
|
||||
|
||||
describe '#two_factor_authentication_required?' do
|
||||
describe 'when it is required on application level' do
|
||||
it 'returns true' do
|
||||
stub_application_setting require_two_factor_authentication: true
|
||||
|
||||
expect(subject.two_factor_authentication_required?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when it is required on group level' do
|
||||
it 'returns true' do
|
||||
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
|
||||
|
||||
expect(subject.two_factor_authentication_required?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when it is not required' do
|
||||
it 'returns false when not required on group level' do
|
||||
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false)
|
||||
|
||||
expect(subject.two_factor_authentication_required?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#current_user_needs_to_setup_two_factor?' do
|
||||
it 'returns false when current_user is nil' do
|
||||
expect(described_class.new(nil).current_user_needs_to_setup_two_factor?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false when current_user does not have temp email' do
|
||||
allow(user).to receive(:two_factor_enabled?).and_return(false)
|
||||
allow(user).to receive(:temp_oauth_email?).and_return(true)
|
||||
|
||||
expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false when current_user has 2fa disabled' do
|
||||
allow(user).to receive(:temp_oauth_email?).and_return(false)
|
||||
allow(user).to receive(:two_factor_enabled?).and_return(true)
|
||||
|
||||
expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true when user requires 2fa authentication' do
|
||||
allow(user).to receive(:two_factor_enabled?).and_return(false)
|
||||
allow(user).to receive(:temp_oauth_email?).and_return(false)
|
||||
|
||||
expect(subject.current_user_needs_to_setup_two_factor?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#two_factor_grace_period' do
|
||||
it 'returns grace period from settings if there is no period from groups' do
|
||||
stub_application_setting two_factor_grace_period: 2
|
||||
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false)
|
||||
|
||||
expect(subject.two_factor_grace_period).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns grace period from groups if there is no period from settings' do
|
||||
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
|
||||
allow(user).to receive(:two_factor_grace_period).and_return(3)
|
||||
|
||||
expect(subject.two_factor_grace_period).to eq(3)
|
||||
end
|
||||
|
||||
it 'returns minimal grace period if there is grace period from settings and from group' do
|
||||
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
|
||||
allow(user).to receive(:two_factor_grace_period).and_return(3)
|
||||
stub_application_setting two_factor_grace_period: 2
|
||||
|
||||
expect(subject.two_factor_grace_period).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#two_factor_grace_period_expired?' do
|
||||
before do
|
||||
allow(user).to receive(:otp_grace_period_started_at).and_return(4.hours.ago)
|
||||
end
|
||||
|
||||
it 'returns true if the grace period has expired' do
|
||||
allow(subject).to receive(:two_factor_grace_period).and_return(2)
|
||||
|
||||
expect(subject.two_factor_grace_period_expired?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if the grace period has not expired' do
|
||||
allow(subject).to receive(:two_factor_grace_period).and_return(6)
|
||||
|
||||
expect(subject.two_factor_grace_period_expired?).to be_falsey
|
||||
end
|
||||
|
||||
context 'when otp_grace_period_started_at is nil' do
|
||||
it 'returns false' do
|
||||
allow(user).to receive(:otp_grace_period_started_at).and_return(nil)
|
||||
|
||||
expect(subject.two_factor_grace_period_expired?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -431,7 +431,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'deploy token with disabled registry' do
|
||||
shared_examples 'deploy token with disabled feature' do
|
||||
context 'when registry disabled' do
|
||||
before do
|
||||
stub_container_registry_config(enabled: false)
|
||||
|
@ -442,6 +442,15 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
.to eq(auth_failure)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository_disabled) }
|
||||
|
||||
it 'fails when login and token are valid' do
|
||||
expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
|
||||
.to eq(auth_failure)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deploy token and user have the same username' do
|
||||
|
@ -594,7 +603,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
it_behaves_like 'registry token scope'
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token with disabled registry'
|
||||
it_behaves_like 'deploy token with disabled feature'
|
||||
end
|
||||
|
||||
context 'when the deploy token has write_registry as a scope' do
|
||||
|
@ -616,7 +625,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
it_behaves_like 'registry token scope'
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token with disabled registry'
|
||||
it_behaves_like 'deploy token with disabled feature'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -671,12 +680,69 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
|
||||
end
|
||||
|
||||
it 'does not find user in locked state' do
|
||||
user.lock_access!
|
||||
|
||||
expect(gl_auth.find_with_user_password(username, password)).not_to eql user
|
||||
end
|
||||
|
||||
it "does not find user in ldap_blocked state" do
|
||||
user.ldap_block
|
||||
|
||||
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
|
||||
end
|
||||
|
||||
context 'with increment_failed_attempts' do
|
||||
wrong_password = 'incorrect_password'
|
||||
|
||||
it 'increments failed_attempts when true and password is incorrect' do
|
||||
expect do
|
||||
gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true)
|
||||
user.reload
|
||||
end.to change(user, :failed_attempts).from(0).to(1)
|
||||
end
|
||||
|
||||
it 'resets failed_attempts when true and password is correct' do
|
||||
user.failed_attempts = 2
|
||||
user.save
|
||||
|
||||
expect do
|
||||
gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
|
||||
user.reload
|
||||
end.to change(user, :failed_attempts).from(2).to(0)
|
||||
end
|
||||
|
||||
it 'does not increment failed_attempts by default' do
|
||||
expect do
|
||||
gl_auth.find_with_user_password(username, wrong_password)
|
||||
user.reload
|
||||
end.not_to change(user, :failed_attempts)
|
||||
end
|
||||
|
||||
context 'when the database is read only' do
|
||||
before do
|
||||
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not increment failed_attempts when true and password is incorrect' do
|
||||
expect do
|
||||
gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true)
|
||||
user.reload
|
||||
end.not_to change(user, :failed_attempts)
|
||||
end
|
||||
|
||||
it 'does not reset failed_attempts when true and password is correct' do
|
||||
user.failed_attempts = 2
|
||||
user.save
|
||||
|
||||
expect do
|
||||
gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
|
||||
user.reload
|
||||
end.not_to change(user, :failed_attempts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with ldap enabled" do
|
||||
before do
|
||||
allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
|
||||
|
|
|
@ -487,31 +487,135 @@ RSpec.describe Gitlab::GitAccess do
|
|||
let(:actor) { key }
|
||||
|
||||
context 'pull code' do
|
||||
context 'when project is authorized' do
|
||||
context 'when project is public' do
|
||||
let(:project) { create(:project, :public, :repository, *options) }
|
||||
|
||||
context 'when deploy key exists in the project' do
|
||||
before do
|
||||
key.projects << project
|
||||
end
|
||||
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when unauthorized' do
|
||||
context 'from public project' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
context 'when the repository is public' do
|
||||
let(:options) { %i[repository_enabled] }
|
||||
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'from internal project' do
|
||||
let(:project) { create(:project, :internal, :repository) }
|
||||
context 'when the repository is private' do
|
||||
let(:options) { %i[repository_private] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_not_found }
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'from private project' do
|
||||
let(:project) { create(:project, :private, :repository) }
|
||||
context 'when the repository is disabled' do
|
||||
let(:options) { %i[repository_disabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_not_found }
|
||||
it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deploy key does not exist in the project' do
|
||||
context 'when the repository is public' do
|
||||
let(:options) { %i[repository_enabled] }
|
||||
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when the repository is private' do
|
||||
let(:options) { %i[repository_private] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
|
||||
end
|
||||
|
||||
context 'when the repository is disabled' do
|
||||
let(:options) { %i[repository_disabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is internal' do
|
||||
let(:project) { create(:project, :internal, :repository, *options) }
|
||||
|
||||
context 'when deploy key exists in the project' do
|
||||
before do
|
||||
key.projects << project
|
||||
end
|
||||
|
||||
context 'when the repository is public' do
|
||||
let(:options) { %i[repository_enabled] }
|
||||
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when the repository is private' do
|
||||
let(:options) { %i[repository_private] }
|
||||
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when the repository is disabled' do
|
||||
let(:options) { %i[repository_disabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deploy key does not exist in the project' do
|
||||
context 'when the repository is public' do
|
||||
let(:options) { %i[repository_enabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
|
||||
end
|
||||
|
||||
context 'when the repository is private' do
|
||||
let(:options) { %i[repository_private] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
|
||||
end
|
||||
|
||||
context 'when the repository is disabled' do
|
||||
let(:options) { %i[repository_disabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let(:project) { create(:project, :private, :repository, *options) }
|
||||
|
||||
context 'when deploy key exists in the project' do
|
||||
before do
|
||||
key.projects << project
|
||||
end
|
||||
|
||||
context 'when the repository is private' do
|
||||
let(:options) { %i[repository_private] }
|
||||
|
||||
it { expect { pull_access_check }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when the repository is disabled' do
|
||||
let(:options) { %i[repository_disabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deploy key does not exist in the project' do
|
||||
context 'when the repository is private' do
|
||||
let(:options) { %i[repository_private] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
|
||||
end
|
||||
|
||||
context 'when the repository is disabled' do
|
||||
let(:options) { %i[repository_disabled] }
|
||||
|
||||
it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -164,15 +164,6 @@ RSpec.describe Gitlab::Regex do
|
|||
it { is_expected.not_to match('foo/bar') }
|
||||
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
|
||||
subject { described_class.conan_package_reference_regex }
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'migrate', '20200728182311_add_o_auth_paths_to_protected_paths.rb')
|
||||
|
||||
RSpec.describe AddOAuthPathsToProtectedPaths do
|
||||
subject(:migration) { described_class.new }
|
||||
|
||||
let(:application_settings) { table(:application_settings) }
|
||||
let(:new_paths) do
|
||||
[
|
||||
'/oauth/authorize',
|
||||
'/oauth/token'
|
||||
]
|
||||
end
|
||||
|
||||
it 'appends new OAuth paths' do
|
||||
application_settings.create!
|
||||
|
||||
protected_paths_before = application_settings.first.protected_paths
|
||||
protected_paths_after = protected_paths_before + new_paths
|
||||
|
||||
expect { migrate! }.to change { application_settings.first.protected_paths }.from(protected_paths_before).to(protected_paths_after)
|
||||
end
|
||||
|
||||
it 'new default includes new paths' do
|
||||
settings_before = application_settings.create!
|
||||
|
||||
expect(settings_before.protected_paths).not_to include(*new_paths)
|
||||
|
||||
migrate!
|
||||
|
||||
application_settings.reset_column_information
|
||||
settings_after = application_settings.create!
|
||||
|
||||
expect(settings_after.protected_paths).to include(*new_paths)
|
||||
end
|
||||
|
||||
it 'does not change the value when the new paths are already included' do
|
||||
application_settings.create!(protected_paths: %w(/users/sign_in /users/password) + new_paths)
|
||||
|
||||
expect { migrate! }.not_to change { application_settings.first.protected_paths }
|
||||
end
|
||||
|
||||
it 'adds one value when the other is already present' do
|
||||
application_settings.create!(protected_paths: %W(/users/sign_in /users/password #{new_paths.first}))
|
||||
|
||||
migrate!
|
||||
|
||||
expect(application_settings.first.protected_paths).to include(new_paths.second)
|
||||
end
|
||||
end
|
|
@ -296,6 +296,59 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.destroy_all_but_current' do
|
||||
it 'gracefully handles a nil session ID' do
|
||||
expect(described_class).not_to receive(:destroy_sessions)
|
||||
|
||||
ActiveSession.destroy_all_but_current(user, nil)
|
||||
end
|
||||
|
||||
context 'with user sessions' do
|
||||
let(:current_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
|
||||
|
||||
before do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set(described_class.key_name(user.id, current_session_id),
|
||||
Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(current_session_id))))
|
||||
redis.set(described_class.key_name(user.id, '59822c7d9fcdfa03725eff41782ad97d'),
|
||||
Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('59822c7d9fcdfa03725eff41782ad97d'))))
|
||||
redis.set(described_class.key_name(9999, '5c8611e4f9c69645ad1a1492f4131358'),
|
||||
Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('5c8611e4f9c69645ad1a1492f4131358'))))
|
||||
redis.sadd(described_class.lookup_key_name(user.id), '59822c7d9fcdfa03725eff41782ad97d')
|
||||
redis.sadd(described_class.lookup_key_name(user.id), current_session_id)
|
||||
redis.sadd(described_class.lookup_key_name(9999), '5c8611e4f9c69645ad1a1492f4131358')
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes the entry associated with the all user sessions but current' do
|
||||
expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(2).to(1)
|
||||
|
||||
expect(ActiveSession.session_ids_for_user(9999).size).to eq(1)
|
||||
end
|
||||
|
||||
it 'removes the lookup entry of deleted sessions' do
|
||||
ActiveSession.destroy_all_but_current(user, request.session)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
expect(redis.smembers(described_class.lookup_key_name(user.id))).to eq [current_session_id]
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not remove impersonated sessions' do
|
||||
impersonated_session_id = '6919a6f1bb119dd7396fadc38fd18eee'
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set(described_class.key_name(user.id, impersonated_session_id),
|
||||
Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(impersonated_session_id), is_impersonated: true)))
|
||||
redis.sadd(described_class.lookup_key_name(user.id), impersonated_session_id)
|
||||
end
|
||||
|
||||
expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(3).to(2)
|
||||
|
||||
expect(ActiveSession.session_ids_for_user(9999).size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.cleanup' do
|
||||
before do
|
||||
stub_const("ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS", 5)
|
||||
|
|
|
@ -29,6 +29,12 @@ RSpec.describe Aws::Role do
|
|||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'ARN is nil' do
|
||||
let(:role_arn) { }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -113,9 +113,10 @@ RSpec.describe Member do
|
|||
end
|
||||
|
||||
describe 'Scopes & finders' do
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
project = create(:project, :public)
|
||||
group = create(:group)
|
||||
@owner_user = create(:user).tap { |u| group.add_owner(u) }
|
||||
@owner = group.members.find_by(user_id: @owner_user.id)
|
||||
|
||||
|
@ -194,6 +195,19 @@ RSpec.describe Member do
|
|||
it { expect(described_class.non_request).to include @accepted_request_member }
|
||||
end
|
||||
|
||||
describe '.not_accepted_invitations_by_user' do
|
||||
let(:invited_by_user) { create(:project_member, :invited, project: project, created_by: @owner_user) }
|
||||
|
||||
before do
|
||||
create(:project_member, :invited, invite_email: 'test@test.com', project: project, created_by: @owner_user, invite_accepted_at: Time.zone.now)
|
||||
create(:project_member, :invited, invite_email: 'test2@test.com', project: project, created_by: @maintainer_user)
|
||||
end
|
||||
|
||||
subject { described_class.not_accepted_invitations_by_user(@owner_user) }
|
||||
|
||||
it { is_expected.to contain_exactly(invited_by_user) }
|
||||
end
|
||||
|
||||
describe '.search_invite_email' do
|
||||
it 'returns only members the matching e-mail' do
|
||||
create(:group_member, :invited)
|
||||
|
|
|
@ -859,6 +859,20 @@ RSpec.describe User do
|
|||
expect(described_class.without_ghosts).to match_array([user1, user2])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.by_id_and_login' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
it 'finds a user regardless of case' do
|
||||
expect(described_class.by_id_and_login(user.id, user.username.upcase))
|
||||
.to contain_exactly(user)
|
||||
end
|
||||
|
||||
it 'finds a user when login is an email address regardless of case' do
|
||||
expect(described_class.by_id_and_login(user.id, user.email.upcase))
|
||||
.to contain_exactly(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Respond to" do
|
||||
|
@ -3544,6 +3558,42 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#source_groups_of_two_factor_authentication_requirement' do
|
||||
let_it_be(:group_not_requiring_2FA) { create :group }
|
||||
let(:user) { create :user }
|
||||
|
||||
before do
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group_not_requiring_2FA.add_user(user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
context 'when user is direct member of group requiring 2FA' do
|
||||
let_it_be(:group) { create :group, require_two_factor_authentication: true }
|
||||
|
||||
it 'returns group requiring 2FA' do
|
||||
expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is member of group which parent requires 2FA' do
|
||||
let_it_be(:parent_group) { create :group, require_two_factor_authentication: true }
|
||||
let_it_be(:group) { create :group, parent: parent_group }
|
||||
|
||||
it 'returns group requiring 2FA' do
|
||||
expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is member of group which child requires 2FA' do
|
||||
let_it_be(:group) { create :group }
|
||||
let_it_be(:child_group) { create :group, require_two_factor_authentication: true, parent: group }
|
||||
|
||||
it 'returns group requiring 2FA' do
|
||||
expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.active' do
|
||||
before do
|
||||
described_class.ghost
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue