New upstream version 13.2.8

This commit is contained in:
Pirate Praveen 2020-09-03 11:15:55 +05:30
parent 86a86a9f9b
commit 0c019fe1e6
117 changed files with 1755 additions and 544 deletions

View file

@ -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.

View file

@ -1 +1 @@
13.2.6
13.2.8

View file

@ -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

View file

@ -1 +1 @@
13.2.6
13.2.8

View file

@ -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

View file

@ -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

View file

@ -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')

View file

@ -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

View file

@ -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

View file

@ -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 }

View file

@ -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

View file

@ -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

View file

@ -92,7 +92,7 @@ class Projects::TagsController < Projects::ApplicationController
end
format.js do
render status: :unprocessable_entity
render status: :ok
end
end
end

View file

@ -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)

View 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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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) }

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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')

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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
\.

View file

@ -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)
```

View file

@ -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"

View file

@ -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.

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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 |

View file

@ -1,10 +0,0 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
test.rb

View file

@ -1,2 +0,0 @@
--color
--require spec_helper

View file

@ -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

View file

@ -1,4 +0,0 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in faraday_middleware-aws-signers-v4.gemspec
gemspec

View file

@ -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.

View file

@ -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}}
```

View file

@ -1,6 +0,0 @@
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task :default => :spec

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
require 'faraday_middleware/aws_signers_v4'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 = {})

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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? ||

View file

@ -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

View file

@ -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."

View file

@ -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",

View file

@ -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

View file

@ -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) }

View file

@ -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

View file

@ -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) }

View file

@ -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&#39;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

View file

@ -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' }

View 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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 } }

View file

@ -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

View 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

View file

@ -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)

View file

@ -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

View file

@ -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 }

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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