Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-02 15:10:54 +00:00
parent e2d4c85dec
commit 8e35232810
136 changed files with 1951 additions and 448 deletions

View File

@ -761,7 +761,7 @@
changes: *code-qa-patterns changes: *code-qa-patterns
when: manual when: manual
allow_failure: true allow_failure: true
- <<: *if-master-refs - <<: *if-dot-com-gitlab-org-schedule
allow_failure: true allow_failure: true
.review:rules:danger: .review:rules:danger:

View File

@ -18,20 +18,6 @@ Capybara/CurrentPathExpectation:
Layout/ArgumentAlignment: Layout/ArgumentAlignment:
Enabled: false Enabled: false
# Offense count: 72
# Cop supports --auto-correct.
Layout/EmptyLinesAroundArguments:
Exclude:
- 'app/models/concerns/discussion_on_diff.rb'
- 'app/models/concerns/resolvable_discussion.rb'
- 'app/models/diff_discussion.rb'
- 'app/models/discussion.rb'
- 'ee/spec/models/geo/project_registry_spec.rb'
- 'lib/banzai/pipeline/broadcast_message_pipeline.rb'
- 'lib/banzai/pipeline/gfm_pipeline.rb'
- 'lib/banzai/pipeline/single_line_pipeline.rb'
- 'spec/features/markdown/copy_as_gfm_spec.rb'
# Offense count: 413 # Offense count: 413
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth. # Configuration parameters: EnforcedStyle, IndentationWidth.
@ -101,14 +87,6 @@ Layout/SpaceInsideParens:
Lint/MissingCopEnableDirective: Lint/MissingCopEnableDirective:
Enabled: false Enabled: false
# Offense count: 11
# Cop supports --auto-correct.
Lint/NonDeterministicRequireOrder:
Exclude:
- 'ee/spec/spec_helper.rb'
- 'qa/spec/spec_helper.rb'
- 'spec/spec_helper.rb'
# Offense count: 27 # Offense count: 27
# Cop supports --auto-correct. # Cop supports --auto-correct.
Lint/RedundantCopDisableDirective: Lint/RedundantCopDisableDirective:
@ -229,15 +207,6 @@ RSpec/ExpectChange:
RSpec/ExpectInHook: RSpec/ExpectInHook:
Enabled: false Enabled: false
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: it_behaves_like, it_should_behave_like
RSpec/ItBehavesLike:
Exclude:
- 'spec/lib/gitlab/git/commit_spec.rb'
- 'spec/services/notification_service_spec.rb'
# Offense count: 68 # Offense count: 68
# Cop supports --auto-correct. # Cop supports --auto-correct.
RSpec/LetBeforeExamples: RSpec/LetBeforeExamples:
@ -600,17 +569,6 @@ Style/EmptyLambdaParameter:
- 'app/models/ci/build.rb' - 'app/models/ci/build.rb'
- 'app/models/ci/runner.rb' - 'app/models/ci/runner.rb'
# Offense count: 7
# Cop supports --auto-correct.
Style/EmptyLiteral:
Exclude:
- 'lib/gitlab/fogbugz_import/importer.rb'
- 'lib/gitlab/git/diff_collection.rb'
- 'lib/gitlab/gitaly_client.rb'
- 'spec/helpers/merge_requests_helper_spec.rb'
- 'spec/lib/gitlab/workhorse_spec.rb'
- 'spec/requests/api/jobs_spec.rb'
# Offense count: 170 # Offense count: 170
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.

View File

@ -1197,7 +1197,7 @@ GEM
railties (>= 3.2.0) railties (>= 3.2.0)
websocket-driver (0.7.1) websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4) websocket-extensions (0.1.5)
wikicloth (0.8.1) wikicloth (0.8.1)
builder builder
expression_parser expression_parser

View File

@ -85,6 +85,11 @@ export const conditions = flattenDeep(
tokenKey: 'assignee', tokenKey: 'assignee',
value: __('Any'), value: __('Any'),
}, },
{
url: 'author_username=support-bot',
tokenKey: 'author',
value: 'support-bot',
},
{ {
url: 'milestone_title=None', url: 'milestone_title=None',
tokenKey: 'milestone', tokenKey: 'milestone',

View File

@ -38,8 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController
def new def new
if params[:provider] == 'aws' if params[:provider] == 'aws'
@aws_role = current_user.aws_role || Aws::Role.new @aws_role = Aws::Role.create_or_find_by!(user: current_user)
@aws_role.ensure_role_external_id!
@instance_types = load_instance_types.to_json @instance_types = load_instance_types.to_json
elsif params[:provider] == 'gcp' elsif params[:provider] == 'gcp'
@ -274,7 +273,7 @@ class Clusters::ClustersController < Clusters::BaseController
end end
def aws_role_params def aws_role_params
params.require(:cluster).permit(:role_arn, :role_external_id) params.require(:cluster).permit(:role_arn)
end end
def generate_gcp_authorize_url def generate_gcp_authorize_url

View File

@ -22,6 +22,8 @@ module AuthenticatesWithTwoFactor
return handle_locked_user(user) unless user.can?(:log_in) return handle_locked_user(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id session[:otp_user_id] = user.id
session[:user_updated_at] = user.updated_at
setup_u2f_authentication(user) setup_u2f_authentication(user)
render 'devise/sessions/two_factor' render 'devise/sessions/two_factor'
end end
@ -39,6 +41,7 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor def authenticate_with_two_factor
user = self.resource = find_user user = self.resource = find_user
return handle_locked_user(user) unless user.can?(:log_in) 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] if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user) authenticate_with_two_factor_via_otp(user)
@ -63,12 +66,14 @@ module AuthenticatesWithTwoFactor
def clear_two_factor_attempt! def clear_two_factor_attempt!
session.delete(:otp_user_id) session.delete(:otp_user_id)
session.delete(:user_updated_at)
session.delete(:challenge)
end end
def authenticate_with_two_factor_via_otp(user) def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user) if valid_otp_attempt?(user)
# Remove any lingering user data from login # 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' remember_me(user) if user_params[:remember_me] == '1'
user.save! user.save!
@ -85,8 +90,7 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor_via_u2f(user) def authenticate_with_two_factor_via_u2f(user)
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge]) if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
# Remove any lingering user data from login # Remove any lingering user data from login
session.delete(:otp_user_id) clear_two_factor_attempt!
session.delete(:challenge)
remember_me(user) if user_params[:remember_me] == '1' remember_me(user) if user_params[:remember_me] == '1'
sign_in(user, message: :two_factor_authenticated, event: :authentication) sign_in(user, message: :two_factor_authenticated, event: :authentication)
@ -113,4 +117,18 @@ module AuthenticatesWithTwoFactor
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # 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 end

View File

@ -29,12 +29,11 @@ module EnforcesTwoFactorAuthentication
end end
def two_factor_authentication_required? def two_factor_authentication_required?
Gitlab::CurrentSettings.require_two_factor_authentication? || two_factor_verifier.two_factor_authentication_required?
current_user.try(:require_two_factor_authentication_from_group?)
end end
def current_user_requires_two_factor? 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 end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
@ -43,7 +42,7 @@ module EnforcesTwoFactorAuthentication
if Gitlab::CurrentSettings.require_two_factor_authentication? if Gitlab::CurrentSettings.require_two_factor_authentication?
global.call global.call
else 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) group.call(groups)
end end
end end
@ -51,14 +50,11 @@ module EnforcesTwoFactorAuthentication
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def two_factor_grace_period def two_factor_grace_period
periods = [Gitlab::CurrentSettings.two_factor_grace_period] two_factor_verifier.two_factor_grace_period
periods << current_user.two_factor_grace_period if current_user.try(:require_two_factor_authentication_from_group?)
periods.min
end end
def two_factor_grace_period_expired? def two_factor_grace_period_expired?
date = current_user.otp_grace_period_started_at two_factor_verifier.two_factor_grace_period_expired?
date && (date + two_factor_grace_period.hours) < Time.current
end end
def two_factor_skippable? def two_factor_skippable?
@ -70,6 +66,10 @@ module EnforcesTwoFactorAuthentication
def skip_two_factor? def skip_two_factor?
session[:skip_two_factor] && session[:skip_two_factor] > Time.current session[:skip_two_factor] && session[:skip_two_factor] > Time.current
end end
def two_factor_verifier
@two_factor_verifier ||= Gitlab::Auth::TwoFactorAuthVerifier.new(current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end end
EnforcesTwoFactorAuthentication.prepend_if_ee('EE::EnforcesTwoFactorAuthentication') EnforcesTwoFactorAuthentication.prepend_if_ee('EE::EnforcesTwoFactorAuthentication')

View File

@ -50,12 +50,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_unverified_saml_initiation redirect_unverified_saml_initiation
end end
def omniauth_error
@provider = params[:provider]
@error = params[:error]
render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
end
def cas3 def cas3
ticket = params['ticket'] ticket = params['ticket']
if ticket if ticket
@ -205,9 +199,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def fail_login(user) def fail_login(user)
log_failed_login(user.username, oauth['provider']) log_failed_login(user.username, oauth['provider'])
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 end
def fail_auth0_login def fail_auth0_login

View File

@ -7,6 +7,7 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController
def destroy def destroy
ActiveSession.destroy_with_public_id(current_user, params[:id]) ActiveSession.destroy_with_public_id(current_user, params[:id])
current_user.forget_me!
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_active_sessions_url, status: :found } 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 skip_before_action :check_two_factor_requirement
def show def show
unless current_user.otp_secret unless current_user.two_factor_enabled?
current_user.otp_secret = User.generate_otp_secret(32) current_user.otp_secret = User.generate_otp_secret(32)
end end
@ -38,6 +38,8 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create def create
if current_user.validate_and_consume_otp!(params[:pin_code]) 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| Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
@codes = user.generate_otp_backup_codes! @codes = user.generate_otp_backup_codes!
end end

View File

@ -8,6 +8,7 @@ class SessionsController < Devise::SessionsController
include Recaptcha::Verify include Recaptcha::Verify
include RendersLdapServers include RendersLdapServers
include KnownSignIn include KnownSignIn
include Gitlab::Utils::StrongMemoize
skip_before_action :check_two_factor_requirement, only: [:destroy] skip_before_action :check_two_factor_requirement, only: [:destroy]
skip_before_action :check_password_expiration, only: [:destroy] skip_before_action :check_password_expiration, only: [:destroy]
@ -199,10 +200,14 @@ class SessionsController < Devise::SessionsController
end end
def find_user def find_user
if session[:otp_user_id] strong_memoize(:find_user) do
User.find(session[:otp_user_id]) if session[:otp_user_id] && user_params[:login]
elsif user_params[:login] User.by_id_and_login(session[:otp_user_id], user_params[:login]).first
User.by_login(user_params[:login]) elsif session[:otp_user_id]
User.find(session[:otp_user_id])
elsif user_params[:login]
User.by_login(user_params[:login])
end
end end
end end

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! # WARNING: does not consider project feature visibility!
# - user: The user for which to load the events # - user: The user for which to load the events
# - params: # - params:
# - limit: Number of items that to be returned. Defaults to 20 and limited to 100.
# - offset: The page of events to return # - offset: The page of events to return
class UserRecentEventsFinder class UserRecentEventsFinder
prepend FinderWithCrossProjectAccess prepend FinderWithCrossProjectAccess
@ -16,7 +17,8 @@ class UserRecentEventsFinder
attr_reader :current_user, :target_user, :params attr_reader :current_user, :target_user, :params
LIMIT = 20 DEFAULT_LIMIT = 20
MAX_LIMIT = 100
def initialize(current_user, target_user, params = {}) def initialize(current_user, target_user, params = {})
@current_user = current_user @current_user = current_user
@ -31,7 +33,7 @@ class UserRecentEventsFinder
recent_events(params[:offset] || 0) recent_events(params[:offset] || 0)
.joins(:project) .joins(:project)
.with_associations .with_associations
.limit_recent(params[:limit].presence || LIMIT, params[:offset]) .limit_recent(limit, params[:offset])
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
@ -59,4 +61,10 @@ class UserRecentEventsFinder
def projects def projects
target_user.project_interactions.to_sql target_user.project_interactions.to_sql
end end
def limit
return DEFAULT_LIMIT unless params[:limit].present?
[params[:limit].to_i, MAX_LIMIT].min
end
end end

View File

@ -8,7 +8,7 @@ module Mutations
argument :board_id, ::Types::GlobalIDType[::Board], argument :board_id, ::Types::GlobalIDType[::Board],
required: true, required: true,
description: 'The Global ID of the issue board to mutate' description: 'Global ID of the issue board to mutate'
field :list, field :list,
Types::BoardListType, Types::BoardListType,

View File

@ -12,7 +12,7 @@ module Mutations
argument :label_id, ::Types::GlobalIDType[::Label], argument :label_id, ::Types::GlobalIDType[::Label],
required: false, required: false,
description: 'ID of an existing label' description: 'Global ID of an existing label'
def ready?(**args) def ready?(**args)
if args.slice(*mutually_exclusive_args).size != 1 if args.slice(*mutually_exclusive_args).size != 1
@ -39,6 +39,7 @@ module Mutations
private private
# Overridden in EE
def authorize_list_type_resource!(board, params) def authorize_list_type_resource!(board, params)
return unless params[:label_id] return unless params[:label_id]
@ -57,13 +58,15 @@ module Mutations
create_list_service.execute(board) create_list_service.execute(board)
end end
# Overridden in EE
def create_list_params(args) def create_list_params(args)
params = args.slice(*mutually_exclusive_args).with_indifferent_access params = args.slice(*mutually_exclusive_args).with_indifferent_access
params[:label_id] = GitlabSchema.parse_gid(params[:label_id]).model_id if params[:label_id] params[:label_id] &&= ::GitlabSchema.parse_gid(params[:label_id], expected_type: ::Label).model_id
params params
end end
# Overridden in EE
def mutually_exclusive_args def mutually_exclusive_args
[:backlog, :label_id] [:backlog, :label_id]
end end
@ -71,3 +74,5 @@ module Mutations
end end
end end
end end
Mutations::Boards::Lists::Create.prepend_if_ee('::EE::Mutations::Boards::Lists::Create')

View File

@ -11,7 +11,7 @@ module Mutations
private private
def find_object(id:) def find_object(id:)
GitlabSchema.object_from_id(id) GitlabSchema.object_from_id(id, expected_type: ::Snippet)
end end
def authorized_resource?(snippet) def authorized_resource?(snippet)

View File

@ -105,6 +105,19 @@ class ActiveSession
end end
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 = '*') def self.key_name(user_id, session_id = '*')
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}" "#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
end end

View File

@ -9,6 +9,7 @@ module Aws
validates :role_external_id, uniqueness: true, length: { in: 1..64 } validates :role_external_id, uniqueness: true, length: { in: 1..64 }
validates :role_arn, validates :role_arn,
length: 1..2048, length: 1..2048,
allow_nil: true,
format: { format: {
with: Gitlab::Regex.aws_arn_regex, with: Gitlab::Regex.aws_arn_regex,
message: Gitlab::Regex.aws_arn_regex_message message: Gitlab::Regex.aws_arn_regex_message

View File

@ -3,7 +3,7 @@
module Clusters module Clusters
module Applications module Applications
class Runner < ApplicationRecord class Runner < ApplicationRecord
VERSION = '0.20.0' VERSION = '0.20.1'
self.table_name = 'clusters_applications_runners' self.table_name = 'clusters_applications_runners'

View File

@ -13,14 +13,12 @@ module DiscussionOnDiff
:diff_line, :diff_line,
:active?, :active?,
:created_at_diff?, :created_at_diff?,
to: :first_note to: :first_note
delegate :file_path, delegate :file_path,
:blob, :blob,
:highlighted_diff_lines, :highlighted_diff_lines,
:diff_lines, :diff_lines,
to: :diff_file, to: :diff_file,
allow_nil: true allow_nil: true
end end

View File

@ -31,7 +31,6 @@ module ResolvableDiscussion
delegate :resolved_at, delegate :resolved_at,
:resolved_by, :resolved_by,
:resolved_by_push?, :resolved_by_push?,
to: :last_resolved_note, to: :last_resolved_note,
allow_nil: true allow_nil: true
end end

View File

@ -16,7 +16,6 @@ class DiffDiscussion < Discussion
:diff_note_positions, :diff_note_positions,
:on_text?, :on_text?,
:on_image?, :on_image?,
to: :first_note to: :first_note
def legacy_diff_discussion? def legacy_diff_discussion?

View File

@ -24,7 +24,6 @@ class Discussion
:system_note_with_references_visible_for?, :system_note_with_references_visible_for?,
:resource_parent, :resource_parent,
:save, :save,
to: :first_note to: :first_note
def declarative_policy_delegate def declarative_policy_delegate

View File

@ -76,6 +76,8 @@ class Member < ApplicationRecord
scope :request, -> { where.not(requested_at: nil) } scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(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 :has_access, -> { active.where('access_level > 0') }
scope :guests, -> { active.where(access_level: GUEST) } scope :guests, -> { active.where(access_level: GUEST) }

View File

@ -364,6 +364,7 @@ class User < ApplicationRecord
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } 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_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 :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 preferred_language def preferred_language
read_attribute('preferred_language') || read_attribute('preferred_language') ||
@ -887,6 +888,12 @@ class User < ApplicationRecord
all_expanded_groups.where(require_two_factor_authentication: true) all_expanded_groups.where(require_two_factor_authentication: true)
end 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 # rubocop: disable CodeReuse/ServiceClass
def refresh_authorized_projects def refresh_authorized_projects
Users::RefreshAuthorizedProjectsService.new(self).execute Users::RefreshAuthorizedProjectsService.new(self).execute

View File

@ -11,7 +11,16 @@ module Applications
# EE would override and use `request` arg # EE would override and use `request` arg
def execute(request) 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 end
end end

View File

@ -132,6 +132,7 @@ module Auth
def can_access?(requested_project, requested_action) def can_access?(requested_project, requested_action)
return false unless requested_project.container_registry_enabled? return false unless requested_project.container_registry_enabled?
return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED
case requested_action case requested_action
when 'pull' when 'pull'

View File

@ -26,7 +26,7 @@ module Branches
message: 'Failed to remove branch', message: 'Failed to remove branch',
http_status: 400) http_status: 400)
end end
rescue Gitlab::Git::PreReceiveError => ex rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError => ex
ServiceResponse.error(message: ex.message, http_status: 400) ServiceResponse.error(message: ex.message, http_status: 400)
end end

View File

@ -10,6 +10,9 @@ module Ci
elsif job_from_token elsif job_from_token
create_pipeline_from_job(job_from_token) create_pipeline_from_job(job_from_token)
end end
rescue Ci::AuthJobFinder::AuthError => e
error(e.message, 401)
end end
private private
@ -41,8 +44,6 @@ module Ci
# this check is to not leak the presence of the project if user cannot read it # this check is to not leak the presence of the project if user cannot read it
return unless can?(job.user, :read_project, project) return unless can?(job.user, :read_project, project)
return error("400 Job has to be running", 400) unless job.running?
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref]) pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
.execute(:pipeline, ignore_skip_ci: true) do |pipeline| .execute(:pipeline, ignore_skip_ci: true) do |pipeline|
source = job.sourced_pipelines.build( source = job.sourced_pipelines.build(
@ -64,7 +65,7 @@ module Ci
def job_from_token def job_from_token
strong_memoize(:job) do strong_memoize(:job) do
Ci::Build.find_by_token(params[:token].to_s) Ci::AuthJobFinder.new(token: params[:token].to_s).execute!
end end
end end

View File

@ -9,6 +9,7 @@ module Clusters
ERRORS = [ ERRORS = [
ActiveRecord::RecordInvalid, ActiveRecord::RecordInvalid,
ActiveRecord::RecordNotFound,
Clusters::Aws::FetchCredentialsService::MissingRoleError, Clusters::Aws::FetchCredentialsService::MissingRoleError,
::Aws::Errors::MissingCredentialsError, ::Aws::Errors::MissingCredentialsError,
::Aws::STS::Errors::ServiceError ::Aws::STS::Errors::ServiceError
@ -20,7 +21,8 @@ module Clusters
end end
def execute def execute
@role = create_or_update_role! ensure_role_exists!
update_role_arn!
Response.new(:ok, credentials) Response.new(:ok, credentials)
rescue *ERRORS => e rescue *ERRORS => e
@ -33,14 +35,12 @@ module Clusters
attr_reader :role, :params attr_reader :role, :params
def create_or_update_role! def ensure_role_exists!
if role = user.aws_role @role = ::Aws::Role.find_by_user_id!(user.id)
role.update!(params) end
role def update_role_arn!
else role.update!(params)
user.create_aws_role!(params)
end
end end
def credentials def credentials

View File

@ -18,6 +18,7 @@ module Members
end end
delete_subresources(member) unless skip_subresources delete_subresources(member) unless skip_subresources
delete_project_invitations_by(member) unless skip_subresources
enqueue_delete_todos(member) enqueue_delete_todos(member)
enqueue_unassign_issuables(member) if unassign_issuables enqueue_unassign_issuables(member) if unassign_issuables
@ -39,24 +40,48 @@ module Members
delete_project_members(member) delete_project_members(member)
delete_subgroup_members(member) delete_subgroup_members(member)
delete_invited_members(member)
end end
def delete_project_members(member) def delete_project_members(member)
groups = member.group.self_and_descendants groups = member.group.self_and_descendants
ProjectMember.in_namespaces(groups).with_user(member.user).each do |project_member| destroy_project_members(ProjectMember.in_namespaces(groups).with_user(member.user))
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
end
end end
def delete_subgroup_members(member) def delete_subgroup_members(member)
groups = member.group.descendants 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) self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
end end
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) def can_destroy_member?(member)
can?(current_user, destroy_member_permission(member), member) can?(current_user, destroy_member_permission(member), member)
end end

View File

@ -7,6 +7,10 @@ module Projects
def execute(remote_mirror, tries) def execute(remote_mirror, tries)
return success unless remote_mirror.enabled? 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) update_mirror(remote_mirror)
success success

View File

@ -27,6 +27,6 @@
- unless is_current_session - unless is_current_session
.float-right .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') %span.sr-only= _('Revoke')
= _('Revoke') = _('Revoke')

View File

@ -7,7 +7,7 @@
- support_bot_attrs = { service_desk_enabled: @project.service_desk_enabled?, **UserSerializer.new.represent(User.support_bot) }.to_json - support_bot_attrs = { service_desk_enabled: @project.service_desk_enabled?, **UserSerializer.new.represent(User.support_bot) }.to_json
- data_endpoint = "#{expose_path(api_v4_projects_issues_path(id: @project.id))}?author_id=#{User.support_bot.id}" - data_endpoint = "#{expose_path(api_v4_projects_issues_path(id: @project.id))}?author_username=#{User.support_bot.username}"
%div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs, service_desk_meta: service_desk_meta(@project) } } %div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs, service_desk_meta: service_desk_meta(@project) } }
.top-area .top-area

View File

@ -0,0 +1,5 @@
---
title: Fix unfinished merge by Merge Train process
merge_request: 41106
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix the filtered search bar to work in the service desk issue list
merge_request: 40797
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Track downloads of group code coverage CSV in snowplow
merge_request: 40754
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix Layout/EmptyLinesAroundArguments cop
merge_request: 41086
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix Style/EmptyLiteral cop
merge_request: 41110
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix RSpec/ItBehavesLike cop
merge_request: 41111
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix Lint/NonDeterministicRequireOrder cop
merge_request: 41098
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Protect OAuth endpoints from brute force/password stuffing
merge_request:
author:
type: security

View File

@ -0,0 +1,5 @@
---
title: Update GitLab Runner Helm Chart to 0.20.1
merge_request:
author:
type: security

View File

@ -17,7 +17,7 @@ Doorkeeper.configure do
end end
resource_owner_from_credentials do |routes| 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?) user unless user.try(:two_factor_enabled?)
end end

View File

@ -25,7 +25,6 @@ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
confirmations: :confirmations } confirmations: :confirmations }
devise_scope :user do devise_scope :user do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
get '/users/almost_there' => 'confirmations#almost_there' get '/users/almost_there' => 'confirmations#almost_there'
end 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

@ -0,0 +1 @@
6b8fa09c9700c494eeb5151f43064f1656eaaea804742629b7bd66483e2b04cb

View File

@ -0,0 +1 @@
2aab4599404312ddcc5bc9af11b0a21dfd6aa8aa10d4b4b5086a93ce1ffe77b6

View File

@ -9190,7 +9190,7 @@ CREATE TABLE public.application_settings (
throttle_protected_paths_enabled boolean DEFAULT false NOT NULL, throttle_protected_paths_enabled boolean DEFAULT false NOT NULL,
throttle_protected_paths_requests_per_period integer DEFAULT 10 NOT NULL, throttle_protected_paths_requests_per_period integer DEFAULT 10 NOT NULL,
throttle_protected_paths_period_in_seconds integer DEFAULT 60 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_enabled boolean DEFAULT false NOT NULL,
throttle_incident_management_notification_period_in_seconds integer DEFAULT 3600, throttle_incident_management_notification_period_in_seconds integer DEFAULT 3600,
throttle_incident_management_notification_per_period integer DEFAULT 3600, throttle_incident_management_notification_per_period integer DEFAULT 3600,
@ -9557,7 +9557,7 @@ CREATE TABLE public.aws_roles (
user_id integer NOT NULL, user_id integer NOT NULL,
created_at timestamp with time zone NOT NULL, created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL,
role_arn character varying(2048) NOT NULL, role_arn character varying(2048),
role_external_id character varying(64) NOT NULL role_external_id character varying(64) NOT NULL
); );

View File

@ -1309,13 +1309,18 @@ type BoardListConnection {
Autogenerated input type of BoardListCreate Autogenerated input type of BoardListCreate
""" """
input BoardListCreateInput { input BoardListCreateInput {
"""
Global ID of an existing user
"""
assigneeId: UserID
""" """
Create the backlog list Create the backlog list
""" """
backlog: Boolean backlog: Boolean
""" """
The Global ID of the issue board to mutate Global ID of the issue board to mutate
""" """
boardId: BoardID! boardId: BoardID!
@ -1325,9 +1330,14 @@ input BoardListCreateInput {
clientMutationId: String clientMutationId: String
""" """
ID of an existing label Global ID of an existing label
""" """
labelId: LabelID labelId: LabelID
"""
Global ID of an existing milestone
"""
milestoneId: MilestoneID
} }
""" """
@ -17210,6 +17220,11 @@ type UserEdge {
node: User node: User
} }
"""
Identifier of User
"""
scalar UserID
type UserPermissions { type UserPermissions {
""" """
Indicates the user can perform `create_snippet` on this resource Indicates the user can perform `create_snippet` on this resource

View File

@ -3505,7 +3505,7 @@
"inputFields": [ "inputFields": [
{ {
"name": "boardId", "name": "boardId",
"description": "The Global ID of the issue board to mutate", "description": "Global ID of the issue board to mutate",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
@ -3529,7 +3529,7 @@
}, },
{ {
"name": "labelId", "name": "labelId",
"description": "ID of an existing label", "description": "Global ID of an existing label",
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "LabelID", "name": "LabelID",
@ -3537,6 +3537,26 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "milestoneId",
"description": "Global ID of an existing milestone",
"type": {
"kind": "SCALAR",
"name": "MilestoneID",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeId",
"description": "Global ID of an existing user",
"type": {
"kind": "SCALAR",
"name": "UserID",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "clientMutationId", "name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.", "description": "A unique identifier for the client performing the mutation.",
@ -50478,6 +50498,16 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "SCALAR",
"name": "UserID",
"description": "Identifier of User",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "UserPermissions", "name": "UserPermissions",

View File

@ -615,42 +615,51 @@ Jobs need to be backward and forward compatible between consecutive versions
of the application. Adding or removing an argument may cause problems of the application. Adding or removing an argument may cause problems
during deployment before all Rails and Sidekiq nodes have the updated code. during deployment before all Rails and Sidekiq nodes have the updated code.
#### Remove an argument #### Deprecate and remove an argument
**Do not remove arguments from the `perform` function.**. Instead, use the **Before you remove arguments from the `perform_async` and `perform` methods.**, deprecate them. The
following approach: following example deprecates and then removes `arg2` from the `perform_async` method:
1. Provide a default value (usually `nil`) and use a comment to mark the 1. Provide a default value (usually `nil`) and use a comment to mark the
argument as deprecated argument as deprecated in the coming minor release. (Release M)
1. Stop using the argument in `perform_async`.
1. Ignore the value in the worker class, but do not remove it until the next
major release.
In the following example, if you want to remove `arg2`, first set a `nil` default value, ```ruby
and then update locations where `ExampleWorker.perform_async` is called. class ExampleWorker
# Keep arg2 parameter for backwards compatibility.
def perform(object_id, arg1, arg2 = nil)
# ...
end
end
```
```ruby 1. One minor release later, stop using the argument in `perform_async`. (Release M+1)
class ExampleWorker
def perform(object_id, arg1, arg2 = nil) ```ruby
# ... ExampleWorker.perform_async(object_id, arg1)
end ```
end
``` 1. At the next major release, remove the value from the worker class. (Next major release)
```ruby
class ExampleWorker
def perform(object_id, arg1)
# ...
end
end
```
#### Add an argument #### Add an argument
There are two options for safely adding new arguments to Sidekiq workers: There are two options for safely adding new arguments to Sidekiq workers:
1. Set up a [multi-step deployment](#multi-step-deployment) in which the new argument is first added to the worker 1. Set up a [multi-step deployment](#multi-step-deployment) in which the new argument is first added to the worker.
1. Use a [parameter hash](#parameter-hash) for additional arguments. This is perhaps the most flexible option. 1. Use a [parameter hash](#parameter-hash) for additional arguments. This is perhaps the most flexible option.
##### Multi-step deployment ##### Multi-step deployment
This approach requires multiple merge requests and for the first merge request This approach requires multiple releases.
to be merged and deployed before additional changes are merged.
1. In an initial merge request, add the argument to the worker with a default 1. Add the argument to the worker with a default value (Release M).
value:
```ruby ```ruby
class ExampleWorker class ExampleWorker
@ -660,16 +669,28 @@ to be merged and deployed before additional changes are merged.
end end
``` ```
1. Merge and deploy the worker with the new argument. 1. Add the new argument to all the invocations of the worker (Release M+1).
1. In a further merge request, update `ExampleWorker.perform_async` calls to
use the new argument. ```ruby
ExampleWorker.perform_async(object_id, new_arg)
```
1. Remove the default value (Release M+2).
```ruby
class ExampleWorker
def perform(object_id, new_arg)
# ...
end
end
```
##### Parameter hash ##### Parameter hash
This approach will not require multiple deployments if an existing worker already This approach will not require multiple releases if an existing worker already
utilizes a parameter hash. utilizes a parameter hash.
1. Use a parameter hash in the worker to allow for future flexibility: 1. Use a parameter hash in the worker to allow future flexibility.
```ruby ```ruby
class ExampleWorker class ExampleWorker

View File

@ -165,7 +165,7 @@ instances](#indexing-large-instances) below.
In order to enable Elasticsearch, you need to have admin access in GitLab. In order to enable Elasticsearch, you need to have admin access in GitLab.
1. Navigate to **Admin Area** (wrench icon), then **Settings > Integrations** 1. Navigate to **Admin Area** (wrench icon), then **Settings > General**
and expand the **Elasticsearch** section. and expand the **Elasticsearch** section.
1. Configure the [Elasticsearch settings](#elasticsearch-configuration) for 1. Configure the [Elasticsearch settings](#elasticsearch-configuration) for
your Elasticsearch cluster. Do not enable **Elasticsearch indexing** or your Elasticsearch cluster. Do not enable **Elasticsearch indexing** or
@ -183,7 +183,7 @@ In order to enable Elasticsearch, you need to have admin access in GitLab.
``` ```
1. Now enable `Elasticsearch indexing` in **Admin Area > Settings > 1. Now enable `Elasticsearch indexing` in **Admin Area > Settings >
Integrations > Elasticsearch** and click **Save changes**. General > Elasticsearch** and click **Save changes**.
1. Click **Index all projects**. 1. Click **Index all projects**.
1. Click **Check progress** in the confirmation message to see the status of 1. Click **Check progress** in the confirmation message to see the status of
the background jobs. the background jobs.
@ -198,7 +198,7 @@ In order to enable Elasticsearch, you need to have admin access in GitLab.
``` ```
1. After the indexing has completed, enable **Search with Elasticsearch** in 1. After the indexing has completed, enable **Search with Elasticsearch** in
**Admin Area > Settings > Integrations > Elasticsearch** and click **Save **Admin Area > Settings > General > Elasticsearch** and click **Save
changes**. changes**.
### Elasticsearch configuration ### Elasticsearch configuration
@ -251,7 +251,7 @@ from the Elasticsearch index as expected.
To disable the Elasticsearch integration: To disable the Elasticsearch integration:
1. Navigate to the **Admin Area** (wrench icon), then **Settings > Integrations**. 1. Navigate to the **Admin Area** (wrench icon), then **Settings > General**.
1. Expand the **Elasticsearch** section and uncheck **Elasticsearch indexing** 1. Expand the **Elasticsearch** section and uncheck **Elasticsearch indexing**
and **Search with Elasticsearch enabled**. and **Search with Elasticsearch enabled**.
1. Click **Save changes** for the changes to take effect. 1. Click **Save changes** for the changes to take effect.
@ -439,7 +439,7 @@ used by the GitLab Elasticsearch integration.
### Pause the indexing ### Pause the indexing
In the **Admin Area > Integration > Elasticsearch** section, select the In the **Admin Area > Settings > General > Elasticsearch** section, select the
**Pause Elasticsearch Indexing** setting, and then save your change. **Pause Elasticsearch Indexing** setting, and then save your change.
With this, all updates that should happen on your Elasticsearch index will be With this, all updates that should happen on your Elasticsearch index will be
@ -455,7 +455,7 @@ This process involves several shell commands and curl invocations, so a good
initial setup will help for later: initial setup will help for later:
```shell ```shell
# You can find this value under Admin Area > Integration > Elasticsearch > URL # You can find this value under Admin Area > Settings > General > Elasticsearch > URL
export CLUSTER_URL="http://localhost:9200" export CLUSTER_URL="http://localhost:9200"
export PRIMARY_INDEX="gitlab-production" export PRIMARY_INDEX="gitlab-production"
export SECONDARY_INDEX="gitlab-production-$(date +%s)" export SECONDARY_INDEX="gitlab-production-$(date +%s)"
@ -556,14 +556,14 @@ To trigger the re-index from `primary` index:
1. Unpause the indexing 1. Unpause the indexing
Under **Admin Area > Integration > Elasticsearch**, uncheck the **Pause Elasticsearch Indexing** setting and save. Under **Admin Area > Settings > General > Elasticsearch**, uncheck the **Pause Elasticsearch Indexing** setting and save.
### Trigger the reindex via the Elasticsearch administration ### Trigger the reindex via the Elasticsearch administration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
> - A scheduled index deletion and the ability to cancel it was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38914) in GitLab Starter 13.3. > - A scheduled index deletion and the ability to cancel it was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38914) in GitLab Starter 13.3.
Under **Admin Area > Integration > Elasticsearch zero-downtime reindexing**, click on **Trigger cluster reindexing**. Under **Admin Area > Settings > General > Elasticsearch > Elasticsearch zero-downtime reindexing**, click on **Trigger cluster reindexing**.
NOTE: **Note:** NOTE: **Note:**
Reindexing can be a lengthy process depending on the size of your Elasticsearch cluster. Reindexing can be a lengthy process depending on the size of your Elasticsearch cluster.

View File

@ -29,6 +29,11 @@ exceeds 100, the oldest ones are deleted.
1. Use the previous steps to navigate to **Active Sessions**. 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. 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 <!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues 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, After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
you are asked to sign in again to verify your identity for security reasons. you are 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 ### Increased sign-in time
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20340) in GitLab 13.1. > [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: 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. - 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. By default, the server sets a time-to-live (TTL) of 1-week on any session that is used.

View File

@ -69,7 +69,7 @@ module API
deploy_token_from_request || deploy_token_from_request ||
find_user_from_bearer_token || find_user_from_bearer_token ||
find_user_from_job_token || find_user_from_job_token ||
find_user_from_warden user_from_warden
end end
end end
@ -103,6 +103,25 @@ module API
def user_allowed_or_deploy_token?(user) def user_allowed_or_deploy_token?(user)
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken) Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
end 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 end
class_methods do class_methods do

View File

@ -109,9 +109,10 @@ module API
end end
put ":id/badges/:badge_id" do put ":id/badges/:badge_id" do
source = find_source_if_admin(source_type) source = find_source_if_admin(source_type)
badge = find_badge(source)
badge = ::Badges::UpdateService.new(declared_params(include_missing: false)) badge = ::Badges::UpdateService.new(declared_params(include_missing: false))
.execute(find_badge(source)) .execute(badge)
if badge.valid? if badge.valid?
present_badges(source, badge) present_badges(source, badge)
@ -130,10 +131,6 @@ module API
source = find_source_if_admin(source_type) source = find_source_if_admin(source_type)
badge = find_badge(source) badge = find_badge(source)
if badge.is_a?(GroupBadge) && source.is_a?(Project)
error!('To delete a Group badge please use the Group endpoint', 403)
end
destroy_conditionally!(badge) destroy_conditionally!(badge)
end end
end end

View File

@ -26,6 +26,8 @@ module API
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
before do before do
require_packages_enabled! require_packages_enabled!
@ -259,7 +261,7 @@ module API
end end
params do params do
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
end end
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download recipe files' do desc 'Download recipe files' do
@ -277,7 +279,7 @@ module API
end end
params do params do
use :workhorse_upload_params requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
@ -300,7 +302,7 @@ module API
params do params do
requires :conan_package_reference, type: String, desc: 'Conan Package ID' requires :conan_package_reference, type: String, desc: 'Conan Package ID'
requires :package_revision, type: String, desc: 'Conan Package Revision' requires :package_revision, type: String, desc: 'Conan Package Revision'
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
end end
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
desc 'Download package files' do desc 'Download package files' do
@ -328,7 +330,7 @@ module API
end end
params do params do
use :workhorse_upload_params requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true

View File

@ -6,7 +6,13 @@ module API
include ::API::Helpers::MembersHelpers include ::API::Helpers::MembersHelpers
def find_badge(source) def find_badge(source)
source.badges.find(params[:badge_id]) badge_id = params[:badge_id]
if source.is_a?(Project)
source.project_badges.find(badge_id)
else
source.badges.find(badge_id)
end
end end
def present_badges(source, records, options = {}) def present_badges(source, records, options = {})

View File

@ -134,7 +134,7 @@ module API
end end
def track_push_package_event def track_push_package_event
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params['file.size'] > 0 if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
track_event('push_package') track_event('push_package')
end end
end end
@ -148,9 +148,9 @@ module API
end end
def create_package_file_with_type(file_type, current_package) def create_package_file_with_type(file_type, current_package)
unless params['file.size'] == 0 unless params[:file].size == 0 # rubocop: disable Style/ZeroLengthPredicate
# conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0 # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0
::Packages::Conan::CreatePackageFileService.new(current_package, uploaded_package_file, params.merge(conan_file_type: file_type)).execute ::Packages::Conan::CreatePackageFileService.new(current_package, params[:file], params.merge(conan_file_type: file_type)).execute
end end
end end
@ -222,7 +222,7 @@ module API
return unless token return unless token
::Ci::Build.find_by_token(token.access_token_id.to_s) ::Ci::AuthJobFinder.new(token: token.access_token_id.to_s).execute
end end
def decode_oauth_token_from_jwt def decode_oauth_token_from_jwt

View File

@ -23,7 +23,7 @@ module API
return unless token return unless token
::Ci::Build.find_by_token(token) ::Ci::AuthJobFinder.new(token: token).execute
end end
def find_deploy_token_from_http_basic_auth def find_deploy_token_from_http_basic_auth

View File

@ -7,7 +7,6 @@ module Banzai
@filters ||= FilterArray[ @filters ||= FilterArray[
Filter::MarkdownFilter, Filter::MarkdownFilter,
Filter::BroadcastMessageSanitizationFilter, Filter::BroadcastMessageSanitizationFilter,
Filter::EmojiFilter, Filter::EmojiFilter,
Filter::ColorFilter, Filter::ColorFilter,
Filter::AutolinkFilter, Filter::AutolinkFilter,

View File

@ -12,14 +12,11 @@ module Banzai
def self.filters def self.filters
@filters ||= FilterArray[ @filters ||= FilterArray[
Filter::PlantumlFilter, Filter::PlantumlFilter,
# Must always be before the SanitizationFilter to prevent XSS attacks # Must always be before the SanitizationFilter to prevent XSS attacks
Filter::SpacedLinkFilter, Filter::SpacedLinkFilter,
Filter::SanitizationFilter, Filter::SanitizationFilter,
Filter::AssetProxyFilter, Filter::AssetProxyFilter,
Filter::SyntaxHighlightFilter, Filter::SyntaxHighlightFilter,
Filter::MathFilter, Filter::MathFilter,
Filter::ColorFilter, Filter::ColorFilter,
Filter::MermaidFilter, Filter::MermaidFilter,
@ -34,13 +31,10 @@ module Banzai
Filter::ExternalLinkFilter, Filter::ExternalLinkFilter,
Filter::SuggestionFilter, Filter::SuggestionFilter,
Filter::FootnoteFilter, Filter::FootnoteFilter,
*reference_filters, *reference_filters,
Filter::EmojiFilter, Filter::EmojiFilter,
Filter::TaskListFilter, Filter::TaskListFilter,
Filter::InlineDiffFilter, Filter::InlineDiffFilter,
Filter::SetDirectionFilter Filter::SetDirectionFilter
] ]
end end

View File

@ -8,11 +8,9 @@ module Banzai
Filter::HtmlEntityFilter, Filter::HtmlEntityFilter,
Filter::SanitizationFilter, Filter::SanitizationFilter,
Filter::AssetProxyFilter, Filter::AssetProxyFilter,
Filter::EmojiFilter, Filter::EmojiFilter,
Filter::AutolinkFilter, Filter::AutolinkFilter,
Filter::ExternalLinkFilter, Filter::ExternalLinkFilter,
*reference_filters *reference_filters
] ]
end end

View File

@ -65,7 +65,15 @@ module Gitlab
raise Gitlab::Auth::MissingPersonalAccessTokenError raise Gitlab::Auth::MissingPersonalAccessTokenError
end 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 # Avoid resource intensive checks if login credentials are not provided
return unless login.present? && password.present? return unless login.present? && password.present?
@ -96,10 +104,14 @@ module Gitlab
authenticators.compact! authenticators.compact!
# return found user that was authenticated first for given login credentials # 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) authenticated_user = auth.login(login, password)
break authenticated_user if authenticated_user break authenticated_user if authenticated_user
end end
user_auth_attempt!(user, success: !!authenticated_user) if increment_failed_attempts
authenticated_user
end end
end end
@ -222,6 +234,8 @@ module Gitlab
# Registry access (with jwt) does not have access to project # Registry access (with jwt) does not have access to project
return if project && !token.has_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) scopes = abilities_for_scopes(token.scopes)
@ -355,6 +369,13 @@ module Gitlab
def find_build_by_token(token) def find_build_by_token(token)
::Ci::Build.running.find_by_token(token) ::Ci::Build.running.find_by_token(token)
end 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 end
end end

View File

@ -69,9 +69,7 @@ module Gitlab
current_request.env[JOB_TOKEN_HEADER].presence current_request.env[JOB_TOKEN_HEADER].presence
return unless token return unless token
job = ::Ci::Build.find_by_token(token) job = find_valid_running_job_by_token!(token)
raise UnauthorizedError unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user job.user
@ -84,9 +82,7 @@ module Gitlab
return unless login.present? && password.present? return unless login.present? && password.present?
return unless ::Gitlab::Auth::CI_JOB_USER == login return unless ::Gitlab::Auth::CI_JOB_USER == login
job = ::Ci::Build.find_by_token(password) job = find_valid_running_job_by_token!(password)
raise UnauthorizedError unless job
job.user job.user
end end
@ -179,7 +175,7 @@ module Gitlab
token = parsed_oauth_token token = parsed_oauth_token
return unless token return unless token
job = ::Ci::Build.find_by_token(token) job = ::Ci::AuthJobFinder.new(token: token).execute
return unless job return unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
@ -304,6 +300,12 @@ module Gitlab
def blob_request? def blob_request?
current_request.path.include?('/raw/') current_request.path.include?('/raw/')
end end
def find_valid_running_job_by_token!(token)
::Ci::AuthJobFinder.new(token: token).execute.tap do |job|
raise UnauthorizedError unless job
end
end
end end
end end
end end

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

@ -39,7 +39,7 @@ module Gitlab
def user_map def user_map
@user_map ||= begin @user_map ||= begin
user_map = Hash.new user_map = {}
import_data = project.import_data.try(:data) import_data = project.import_data.try(:data)
stored_user_map = import_data['user_map'] if import_data stored_user_map = import_data['user_map'] if import_data
user_map.update(stored_user_map) if stored_user_map user_map.update(stored_user_map) if stored_user_map

View File

@ -37,7 +37,7 @@ module Gitlab
@byte_count = 0 @byte_count = 0
@overflow = false @overflow = false
@empty = true @empty = true
@array = Array.new @array = []
end end
def each(&block) def each(&block)

View File

@ -98,6 +98,13 @@ module Gitlab
Guest.can?(download_ability, container) Guest.can?(download_ability, container)
end end
def deploy_key_can_download_code?
authentication_abilities.include?(:download_code) &&
deploy_key? &&
deploy_key.has_access_to?(container) &&
(project? && project&.repository_access_level != ::Featurable::DISABLED)
end
def user_can_download_code? def user_can_download_code?
authentication_abilities.include?(:download_code) && authentication_abilities.include?(:download_code) &&
user_access.can_do_action?(download_ability) user_access.can_do_action?(download_ability)
@ -257,7 +264,7 @@ module Gitlab
end end
def check_download_access! def check_download_access!
passed = deploy_key? || passed = deploy_key_can_download_code? ||
deploy_token? || deploy_token? ||
user_can_download_code? || user_can_download_code? ||
build_can_download_code? || build_can_download_code? ||

View File

@ -450,7 +450,7 @@ module Gitlab
stack_string = Gitlab::BacktraceCleaner.clean_backtrace(caller).drop(1).join("\n") stack_string = Gitlab::BacktraceCleaner.clean_backtrace(caller).drop(1).join("\n")
Gitlab::SafeRequestStore[:stack_counter] ||= Hash.new Gitlab::SafeRequestStore[:stack_counter] ||= {}
count = Gitlab::SafeRequestStore[:stack_counter][stack_string] || 0 count = Gitlab::SafeRequestStore[:stack_counter][stack_string] || 0
Gitlab::SafeRequestStore[:stack_counter][stack_string] = count + 1 Gitlab::SafeRequestStore[:stack_counter][stack_string] = count + 1

View File

@ -6,11 +6,6 @@ module Gitlab
CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze
CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze
def conan_file_name_regex
@conan_file_name_regex ||=
%r{\A#{(CONAN_RECIPE_FILES + CONAN_PACKAGE_FILES).join("|")}\z}.freeze
end
def conan_package_reference_regex def conan_package_reference_regex
@conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze @conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze
end end

View File

@ -2873,6 +2873,9 @@ msgstr ""
msgid "An error occurred while validating username" msgid "An error occurred while validating username"
msgstr "" msgstr ""
msgid "An error occurred. Please sign in again."
msgstr ""
msgid "An error occurred. Please try again." msgid "An error occurred. Please try again."
msgstr "" msgstr ""
@ -3310,7 +3313,7 @@ msgstr ""
msgid "Are you sure? Removing this GPG key does not affect already signed commits." msgid "Are you sure? Removing this GPG key does not affect already signed commits."
msgstr "" 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 "" msgstr ""
msgid "Are you sure? This will invalidate your registered applications and U2F devices." msgid "Are you sure? This will invalidate your registered applications and U2F devices."
@ -17212,6 +17215,9 @@ msgstr ""
msgid "Only project members will be imported. Group members will be skipped." msgid "Only project members will be imported. Group members will be skipped."
msgstr "" msgstr ""
msgid "Only projects created under a Gold license are available in Security Dashboards."
msgstr ""
msgid "Only verified users with an email address in any of these domains can be added to the group." msgid "Only verified users with an email address in any of these domains can be added to the group."
msgstr "" msgstr ""
@ -19135,6 +19141,9 @@ msgstr ""
msgid "Project visibility level will be changed to match namespace rules when transferring to a group." msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr "" msgstr ""
msgid "Project was not found or you do not have permission to add this project to Security Dashboards."
msgstr ""
msgid "Project: %{name}" msgid "Project: %{name}"
msgstr "" msgstr ""
@ -24689,6 +24698,9 @@ msgstr ""
msgid "The project can be accessed without any authentication." msgid "The project can be accessed without any authentication."
msgstr "" msgstr ""
msgid "The project has already been added to your dashboard."
msgstr ""
msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user." msgid "The project is accessible only by members of the project. Access must be granted explicitly to each user."
msgstr "" msgstr ""

View File

@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1", "@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5", "@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.161.0", "@gitlab/svgs": "1.161.0",
"@gitlab/ui": "20.16.0", "@gitlab/ui": "20.17.0",
"@gitlab/visual-review-tools": "1.6.1", "@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1", "@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2", "@sentry/browser": "^5.10.2",

View File

@ -13,9 +13,9 @@ QA::Runtime::Browser.configure!
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes
Dir[::File.join(__dir__, "support/helpers/*.rb")].each { |f| require f } Dir[::File.join(__dir__, "support/helpers/*.rb")].sort.each { |f| require f }
Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].each { |f| require f } Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].sort.each { |f| require f }
Dir[::File.join(__dir__, "support/shared_examples/*.rb")].each { |f| require f } Dir[::File.join(__dir__, "support/shared_examples/*.rb")].sort.each { |f| require f }
RSpec.configure do |config| RSpec.configure do |config|
QA::Specs::Helpers::Quarantine.configure_rspec QA::Specs::Helpers::Quarantine.configure_rspec

View File

@ -40,7 +40,7 @@ RSpec.describe Admin::ApplicationsController do
describe 'POST #create' do describe 'POST #create' do
it 'creates the application' 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 expect do
post :create, params: { doorkeeper_application: create_params } 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 context 'when the params are for a confidential application' do
it 'creates 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 expect do
post :create, params: { doorkeeper_application: create_params } 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)) expect(application).to have_attributes(create_params.except(:uid, :owner_type))
end end
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 end
describe 'PATCH #update' do describe 'PATCH #update' do

View File

@ -99,7 +99,9 @@ RSpec.describe Admin::ClustersController do
end end
describe 'GET #new' do describe 'GET #new' do
def get_new(provider: 'gcp') let(:user) { admin }
def go(provider: 'gcp')
get :new, params: { provider: provider } get :new, params: { provider: provider }
end end
@ -112,7 +114,7 @@ RSpec.describe Admin::ClustersController do
context 'when selected provider is gke and no valid gcp token exists' do context 'when selected provider is gke and no valid gcp token exists' do
it 'redirects to gcp authorize_url' do it 'redirects to gcp authorize_url' do
get_new go
expect(response).to redirect_to(assigns(:authorize_url)) expect(response).to redirect_to(assigns(:authorize_url))
end end
@ -125,7 +127,7 @@ RSpec.describe Admin::ClustersController do
end end
it 'does not have authorize_url' do it 'does not have authorize_url' do
get_new go
expect(assigns(:authorize_url)).to be_nil expect(assigns(:authorize_url)).to be_nil
end end
@ -137,7 +139,7 @@ RSpec.describe Admin::ClustersController do
end end
it 'has new object' do it 'has new object' do
get_new go
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
end end
@ -158,16 +160,18 @@ RSpec.describe Admin::ClustersController do
describe 'functionality for existing cluster' do describe 'functionality for existing cluster' do
it 'has new object' do it 'has new object' do
get_new go
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
end end
end end
include_examples 'GET new cluster shared examples'
describe 'security' do describe 'security' do
it { expect { get_new }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { get_new }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:user) }
it { expect { get_new }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }
end end
end end
@ -424,14 +428,13 @@ RSpec.describe Admin::ClustersController do
end end
describe 'POST authorize AWS role for EKS cluster' do describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } let!(:role) { create(:aws_role, user: admin) }
let(:role_external_id) { '12345' }
let(:role_arn) { 'arn:new-role' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
role_arn: role_arn, role_arn: role_arn
role_external_id: role_external_id
} }
} }
end end
@ -445,28 +448,32 @@ RSpec.describe Admin::ClustersController do
.and_return(double(execute: double)) .and_return(double(execute: double))
end end
it 'creates an Aws::Role record' do it 'updates the associated role with the supplied ARN' do
expect { go }.to change { Aws::Role.count } go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
role = Aws::Role.last
expect(role.user).to eq admin
expect(role.role_arn).to eq role_arn
expect(role.role_external_id).to eq role_external_id
end end
context 'role cannot be created' do context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' } let(:role_arn) { 'invalid-role' }
it 'does not create a record' do it 'does not update the associated role' do
expect { go }.not_to change { Aws::Role.count } expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
end end
describe 'security' do describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }

View File

@ -229,6 +229,7 @@ RSpec.describe ApplicationController do
it 'does not redirect if 2FA is not required' 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(:two_factor_authentication_required?).and_return(false)
allow(controller).to receive(:current_user).and_return(create(:user))
expect(controller).not_to receive(:redirect_to) expect(controller).not_to receive(:redirect_to)
@ -346,13 +347,17 @@ RSpec.describe ApplicationController do
let(:user) { create :user, otp_grace_period_started_at: 2.hours.ago } let(:user) { create :user, otp_grace_period_started_at: 2.hours.ago }
it 'returns true if the grace period has expired' do 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 expect(subject).to be_truthy
end end
it 'returns false if the grace period is still active' do 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 expect(subject).to be_falsey
end end

View File

@ -180,6 +180,8 @@ RSpec.describe Groups::ClustersController do
end end
end end
include_examples 'GET new cluster shared examples'
describe 'security' do describe 'security' do
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) } it { expect { go }.to be_allowed_for(:owner).of(group) }
@ -493,14 +495,13 @@ RSpec.describe Groups::ClustersController do
end end
describe 'POST authorize AWS role for EKS cluster' do describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } let!(:role) { create(:aws_role, user: user) }
let(:role_external_id) { '12345' }
let(:role_arn) { 'arn:new-role' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
role_arn: role_arn, role_arn: role_arn
role_external_id: role_external_id
} }
} }
end end
@ -514,28 +515,32 @@ RSpec.describe Groups::ClustersController do
.and_return(double(execute: double)) .and_return(double(execute: double))
end end
it 'creates an Aws::Role record' do it 'updates the associated role with the supplied ARN' do
expect { go }.to change { Aws::Role.count } go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
role = Aws::Role.last
expect(role.user).to eq user
expect(role.role_arn).to eq role_arn
expect(role.role_external_id).to eq role_external_id
end end
context 'role cannot be created' do context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' } let(:role_arn) { 'invalid-role' }
it 'does not create a record' do it 'does not update the associated role' do
expect { go }.not_to change { Aws::Role.count } expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
end end
describe 'security' do describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) } it { expect { go }.to be_allowed_for(:owner).of(group) }
it { expect { go }.to be_allowed_for(:maintainer).of(group) } it { expect { go }.to be_allowed_for(:maintainer).of(group) }

View File

@ -123,7 +123,8 @@ RSpec.describe Oauth::ApplicationsController do
invalid_uri_params = { invalid_uri_params = {
doorkeeper_application: { doorkeeper_application: {
name: 'foo', name: 'foo',
redirect_uri: 'javascript://alert()' redirect_uri: 'javascript://alert()',
scopes: ['api']
} }
} }
@ -133,6 +134,23 @@ RSpec.describe Oauth::ApplicationsController do
end end
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 login page when the user is not signed in'
it_behaves_like 'redirects to 2fa setup page when the user requires it' it_behaves_like 'redirects to 2fa setup page when the user requires it'
end end
@ -172,7 +190,8 @@ RSpec.describe Oauth::ApplicationsController do
{ {
doorkeeper_application: { doorkeeper_application: {
name: 'foo', name: 'foo',
redirect_uri: 'http://example.org' redirect_uri: 'http://example.org',
scopes: ['api']
} }
} }
end end

View File

@ -40,6 +40,22 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
end end
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 context 'when the user is on the last sign in attempt' do
let(:extern_uid) { 'my-uid' } 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) } let(:user) { create(:user) }
it 'generates otp_secret for user' do 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
get :show # Second hit shouldn't re-generate it
end end
it 'assigns qr_code' do it 'assigns qr_code' do
@ -27,6 +26,14 @@ RSpec.describe Profiles::TwoFactorAuthsController do
get :show get :show
expect(assigns[:qr_code]).to eq code expect(assigns[:qr_code]).to eq code
end 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 end
describe 'POST create' do describe 'POST create' do
@ -57,6 +64,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do
expect(assigns[:codes]).to match_array %w(a b c) expect(assigns[:codes]).to match_array %w(a b c)
end end
it 'calls to delete other sessions' do
expect(ActiveSession).to receive(:destroy_all_but_current)
go
end
it 'renders create' do it 'renders create' do
go go
expect(response).to render_template(:create) expect(response).to render_template(:create)

View File

@ -183,6 +183,8 @@ RSpec.describe Projects::ClustersController do
end end
end end
include_examples 'GET new cluster shared examples'
describe 'security' do describe 'security' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin) expect { go }.to be_allowed_for(:admin)
@ -521,14 +523,13 @@ RSpec.describe Projects::ClustersController do
end end
describe 'POST authorize AWS role for EKS cluster' do describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } let!(:role) { create(:aws_role, user: user) }
let(:role_external_id) { '12345' }
let(:role_arn) { 'arn:new-role' }
let(:params) do let(:params) do
{ {
cluster: { cluster: {
role_arn: role_arn, role_arn: role_arn
role_external_id: role_external_id
} }
} }
end end
@ -542,28 +543,32 @@ RSpec.describe Projects::ClustersController do
.and_return(double(execute: double)) .and_return(double(execute: double))
end end
it 'creates an Aws::Role record' do it 'updates the associated role with the supplied ARN' do
expect { go }.to change { Aws::Role.count } go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(role.reload.role_arn).to eq(role_arn)
role = Aws::Role.last
expect(role.user).to eq user
expect(role.role_arn).to eq role_arn
expect(role.role_external_id).to eq role_external_id
end end
context 'role cannot be created' do context 'supplied role is invalid' do
let(:role_arn) { 'invalid-role' } let(:role_arn) { 'invalid-role' }
it 'does not create a record' do it 'does not update the associated role' do
expect { go }.not_to change { Aws::Role.count } expect { go }.not_to change { role.role_arn }
expect(response).to have_gitlab_http_status(:unprocessable_entity) expect(response).to have_gitlab_http_status(:unprocessable_entity)
end end
end end
describe 'security' do describe 'security' do
before do
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
response = double(status: :ok, body: double)
allow(service).to receive(:execute).and_return(response)
end
end
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin) expect { go }.to be_allowed_for(:admin)
end end

View File

@ -149,9 +149,15 @@ RSpec.describe SearchController do
expect(assigns[:search_objects].first).to eq note expect(assigns[:search_objects].first).to eq note
end end
it_behaves_like 'tracking unique hll events', :show do context 'unique users tracking' do
let(:request_params) { { scope: 'projects', search: 'term' } } before do
let(:target_id) { 'i_search_total' } allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
end
it_behaves_like 'tracking unique hll events', :show do
let(:request_params) { { scope: 'projects', search: 'term' } }
let(:target_id) { 'i_search_total' }
end
end end
context 'on restricted projects' do context 'on restricted projects' do

View File

@ -261,8 +261,8 @@ RSpec.describe SessionsController do
context 'when using two-factor authentication via OTP' do context 'when using two-factor authentication via OTP' do
let(:user) { create(:user, :two_factor) } let(:user) { create(:user, :two_factor) }
def authenticate_2fa(user_params) def authenticate_2fa(user_params, otp_user_id: user.id)
post(:create, params: { user: user_params }, session: { otp_user_id: user.id }) post(:create, params: { user: user_params }, session: { otp_user_id: otp_user_id })
end end
context 'remember_me field' do context 'remember_me field' do
@ -299,8 +299,22 @@ RSpec.describe SessionsController do
end end
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 authenticating with login and OTP of another user' do
context 'when another user has 2FA enabled' do context 'when another user has 2FA enabled' do
@ -386,18 +400,6 @@ RSpec.describe SessionsController do
end end
end 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
end end

View File

@ -7,7 +7,7 @@ RSpec.describe 'admin manage applications' do
sign_in(create(:admin)) sign_in(create(:admin))
end end
it do it 'creates new oauth application' do
visit admin_applications_path visit admin_applications_path
click_on 'New application' 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_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
check :doorkeeper_application_trusted check :doorkeeper_application_trusted
check :doorkeeper_application_scopes_read_user
click_on 'Submit' click_on 'Submit'
expect(page).to have_content('Application: test') expect(page).to have_content('Application: test')
expect(page).to have_content('Application ID') expect(page).to have_content('Application ID')
@ -43,4 +44,19 @@ RSpec.describe 'admin manage applications' do
end end
expect(page.find('.oauth-applications')).not_to have_content('test_changed') expect(page.find('.oauth-applications')).not_to have_content('test_changed')
end 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 end

View File

@ -109,6 +109,14 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
find('.input-token .filtered-search').native.send_key(:backspace) find('.input-token .filtered-search').native.send_key(:backspace)
expect(page).to have_selector('.js-visual-token', count: 1) expect(page).to have_selector('.js-visual-token', count: 1)
end end
it 'support bot author token has been properly added' do
within('.filtered-search-token') do
expect(page).to have_selector('.name', count: 1, visible: false)
expect(page).to have_selector('.operator', count: 1, visible: false)
expect(page).to have_selector('.value-container', count: 1, visible: false)
end
end
end end
end end
end end

View File

@ -30,13 +30,11 @@ RSpec.describe 'Copy as GFM', :js do
it 'works', :aggregate_failures do it 'works', :aggregate_failures do
verify( verify(
'nesting', 'nesting',
'> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**' '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
) )
verify( verify(
'a real world example from the gitlab-ce README', 'a real world example from the gitlab-ce README',
<<~GFM <<~GFM
# GitLab # GitLab
@ -103,19 +101,16 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'InlineDiffFilter', 'InlineDiffFilter',
'{-Deleted text-}', '{-Deleted text-}',
'{+Added text+}' '{+Added text+}'
) )
verify( verify(
'TaskListFilter', 'TaskListFilter',
<<~GFM, <<~GFM,
* [ ] Unchecked task * [ ] Unchecked task
* [x] Checked task * [x] Checked task
GFM GFM
<<~GFM <<~GFM
1. [ ] Unchecked ordered task 1. [ ] Unchecked ordered task
1. [x] Checked ordered task 1. [x] Checked ordered task
@ -124,7 +119,6 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'ReferenceFilter', 'ReferenceFilter',
# issue reference # issue reference
@feat.issue.to_reference, @feat.issue.to_reference,
# full issue reference # full issue reference
@ -141,13 +135,11 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'AutolinkFilter', 'AutolinkFilter',
'https://example.com' 'https://example.com'
) )
verify( verify(
'TableOfContentsFilter', 'TableOfContentsFilter',
<<~GFM, <<~GFM,
[[_TOC_]] [[_TOC_]]
@ -155,64 +147,53 @@ RSpec.describe 'Copy as GFM', :js do
## Heading 2 ## Heading 2
GFM GFM
pipeline: :wiki, pipeline: :wiki,
wiki: @project.wiki wiki: @project.wiki
) )
verify( verify(
'EmojiFilter', 'EmojiFilter',
':thumbsup:' ':thumbsup:'
) )
verify( verify(
'ImageLinkFilter', 'ImageLinkFilter',
'![Image](https://example.com/image.png)' '![Image](https://example.com/image.png)'
) )
verify_media_with_partial_path( verify_media_with_partial_path(
'[test.txt](/uploads/a123/image.txt)', '[test.txt](/uploads/a123/image.txt)',
project_media_uri(@project, '/uploads/a123/image.txt') project_media_uri(@project, '/uploads/a123/image.txt')
) )
verify_media_with_partial_path( verify_media_with_partial_path(
'![Image](/uploads/a123/image.png)', '![Image](/uploads/a123/image.png)',
project_media_uri(@project, '/uploads/a123/image.png') project_media_uri(@project, '/uploads/a123/image.png')
) )
verify( verify(
'VideoLinkFilter', 'VideoLinkFilter',
'![Video](https://example.com/video.mp4)' '![Video](https://example.com/video.mp4)'
) )
verify_media_with_partial_path( verify_media_with_partial_path(
'![Video](/uploads/a123/video.mp4)', '![Video](/uploads/a123/video.mp4)',
project_media_uri(@project, '/uploads/a123/video.mp4') project_media_uri(@project, '/uploads/a123/video.mp4')
) )
verify( verify(
'AudioLinkFilter', 'AudioLinkFilter',
'![Audio](https://example.com/audio.wav)' '![Audio](https://example.com/audio.wav)'
) )
verify_media_with_partial_path( verify_media_with_partial_path(
'![Audio](/uploads/a123/audio.wav)', '![Audio](/uploads/a123/audio.wav)',
project_media_uri(@project, '/uploads/a123/audio.wav') project_media_uri(@project, '/uploads/a123/audio.wav')
) )
verify( verify(
'MathFilter: math as converted from GFM to HTML', 'MathFilter: math as converted from GFM to HTML',
'$`c = \pm\sqrt{a^2 + b^2}`$', '$`c = \pm\sqrt{a^2 + b^2}`$',
# math block # math block
<<~GFM <<~GFM
```math ```math
@ -334,7 +315,6 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'MermaidFilter: mermaid as converted from GFM to HTML', 'MermaidFilter: mermaid as converted from GFM to HTML',
<<~GFM <<~GFM
```mermaid ```mermaid
graph TD; graph TD;
@ -429,7 +409,6 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'SuggestionFilter: suggestion as converted from GFM to HTML', 'SuggestionFilter: suggestion as converted from GFM to HTML',
<<~GFM <<~GFM
```suggestion ```suggestion
New New
@ -491,7 +470,6 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'SanitizationFilter', 'SanitizationFilter',
<<~GFM <<~GFM
<sub>sub</sub> <sub>sub</sub>
@ -527,13 +505,11 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'SanitizationFilter', 'SanitizationFilter',
<<~GFM, <<~GFM,
``` ```
Plain text Plain text
``` ```
GFM GFM
<<~GFM, <<~GFM,
```ruby ```ruby
def foo def foo
@ -541,7 +517,6 @@ RSpec.describe 'Copy as GFM', :js do
end end
``` ```
GFM GFM
<<~GFM <<~GFM
Foo Foo
@ -553,27 +528,19 @@ RSpec.describe 'Copy as GFM', :js do
verify( verify(
'MarkdownFilter', 'MarkdownFilter',
"Line with two spaces at the end \nto insert a linebreak", "Line with two spaces at the end \nto insert a linebreak",
'`code`', '`code`',
'`` code with ` ticks ``', '`` code with ` ticks ``',
'> Quote', '> Quote',
# multiline quote # multiline quote
<<~GFM, <<~GFM,
> Multiline Quote > Multiline Quote
> >
> With multiple paragraphs > With multiple paragraphs
GFM GFM
'![Image](https://example.com/image.png)', '![Image](https://example.com/image.png)',
'# Heading with no anchor link', '# Heading with no anchor link',
'[Link](https://example.com)', '[Link](https://example.com)',
<<~GFM, <<~GFM,
* List item * List item
* List item 2 * List item 2
@ -598,7 +565,6 @@ RSpec.describe 'Copy as GFM', :js do
> Blockquote > Blockquote
GFM GFM
<<~GFM, <<~GFM,
1. Ordered list item 1. Ordered list item
1. Ordered list item 2 1. Ordered list item 2
@ -623,22 +589,16 @@ RSpec.describe 'Copy as GFM', :js do
--- ---
GFM GFM
'# Heading', '# Heading',
'## Heading', '## Heading',
'### Heading', '### Heading',
'#### Heading', '#### Heading',
'##### Heading', '##### Heading',
'###### Heading', '###### Heading',
'**Bold**', '**Bold**',
'*Italics*', '*Italics*',
'~~Strikethrough~~', '~~Strikethrough~~',
'---', '---',
# table # table
<<~GFM, <<~GFM,
| Centered | Right | Left | | Centered | Right | Left |
@ -696,9 +656,7 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no', '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no',
'`RuntimeError`', '`RuntimeError`',
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
) )
end end
@ -708,9 +666,7 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]', '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]',
'`raise RuntimeError, "System commands must be given as an array of strings"`', '`raise RuntimeError, "System commands must be given as an array of strings"`',
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
) )
end end
@ -720,14 +676,12 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as a code block' do it 'copies as a code block' do
verify( verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
<<~GFM, <<~GFM,
```ruby ```ruby
raise RuntimeError, "System commands must be given as an array of strings" raise RuntimeError, "System commands must be given as an array of strings"
end end
``` ```
GFM GFM
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
) )
end end
@ -755,7 +709,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as a code block' do it 'copies as a code block' do
verify( verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
<<~GFM, <<~GFM,
```ruby ```ruby
unless cmd.is_a?(Array) unless cmd.is_a?(Array)
@ -763,7 +716,6 @@ RSpec.describe 'Copy as GFM', :js do
end end
``` ```
GFM GFM
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].left-side' target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].left-side'
) )
end end
@ -773,7 +725,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as a code block' do it 'copies as a code block' do
verify( verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
<<~GFM, <<~GFM,
```ruby ```ruby
unless cmd.is_a?(Array) unless cmd.is_a?(Array)
@ -781,7 +732,6 @@ RSpec.describe 'Copy as GFM', :js do
end end
``` ```
GFM GFM
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].right-side' target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].right-side'
) )
end end
@ -799,7 +749,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'.line[id="LC9"] .no', '.line[id="LC9"] .no',
'`RuntimeError`' '`RuntimeError`'
) )
end end
@ -809,7 +758,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'.line[id="LC9"]', '.line[id="LC9"]',
'`raise RuntimeError, "System commands must be given as an array of strings"`' '`raise RuntimeError, "System commands must be given as an array of strings"`'
) )
end end
@ -819,7 +767,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as a code block' do it 'copies as a code block' do
verify( verify(
'.line[id="LC9"], .line[id="LC10"]', '.line[id="LC9"], .line[id="LC10"]',
<<~GFM <<~GFM
```ruby ```ruby
raise RuntimeError, "System commands must be given as an array of strings" raise RuntimeError, "System commands must be given as an array of strings"
@ -841,7 +788,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'.line[id="LC27"] .nl', '.line[id="LC27"] .nl',
'`"bio"`' '`"bio"`'
) )
end end
@ -851,7 +797,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as inline code' do it 'copies as inline code' do
verify( verify(
'.line[id="LC27"]', '.line[id="LC27"]',
'`"bio": null,`' '`"bio": null,`'
) )
end end
@ -861,7 +806,6 @@ RSpec.describe 'Copy as GFM', :js do
it 'copies as a code block with the correct language' do it 'copies as a code block with the correct language' do
verify( verify(
'.line[id="LC27"], .line[id="LC28"]', '.line[id="LC27"], .line[id="LC28"]',
<<~GFM <<~GFM
```json ```json
"bio": null, "bio": null,

View File

@ -15,6 +15,7 @@ RSpec.describe 'User manages applications' do
fill_in :doorkeeper_application_name, with: 'test' fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
check :doorkeeper_application_scopes_read_user
click_on 'Save application' click_on 'Save application'
expect(page).to have_content 'Application: test' expect(page).to have_content 'Application: test'
@ -41,4 +42,16 @@ RSpec.describe 'User manages applications' do
end end
expect(page.find('.oauth-applications')).not_to have_content 'test_changed' expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
end 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 end

View File

@ -177,6 +177,14 @@ RSpec.describe 'Login' do
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end 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 context 'using one-time code' do
it 'allows login with valid code' do it 'allows login with valid code' do
expect(authentication_metrics) expect(authentication_metrics)
@ -232,7 +240,7 @@ RSpec.describe 'Login' do
expect(codes.size).to eq 10 expect(codes.size).to eq 10
# Ensure the generated codes get saved # Ensure the generated codes get saved
user.save user.save(touch: false)
end end
context 'with valid code' do context 'with valid code' do
@ -290,7 +298,7 @@ RSpec.describe 'Login' do
code = codes.sample code = codes.sample
expect(user.invalidate_otp_backup_code!(code)).to eq true 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 expect(user.reload.otp_backup_codes.size).to eq 9
enter_code(code) 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!(:private_event) { create(:event, project: private_project, author: project_owner) }
let!(:internal_event) { create(:event, project: internal_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!(: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 describe '#execute' do
context 'when profile is public' do context 'when profile is public' do
@ -48,5 +50,38 @@ RSpec.describe UserRecentEventsFinder do
expect(events).to include(event_b) expect(events).to include(event_b)
end end
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
end end

View File

@ -212,6 +212,55 @@ RSpec.describe GitlabSchema do
end end
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 def field_instrumenters
described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins] described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
end end

View File

@ -24,14 +24,12 @@ RSpec.describe Mutations::Boards::Lists::Create do
describe '#ready?' do describe '#ready?' do
it 'raises an error if required arguments are missing' do it 'raises an error if required arguments are missing' do
expect { mutation.ready?({ board_id: 'some id' }) } expect { mutation.ready?({ board_id: 'some id' }) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
'one and only one of backlog or labelId is required')
end end
it 'raises an error if too many required arguments are specified' do it 'raises an error if too many required arguments are specified' do
expect { mutation.ready?({ board_id: 'some id', backlog: true, label_id: 'some label' }) } expect { mutation.ready?({ board_id: 'some id', backlog: true, label_id: 'some label' }) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
'one and only one of backlog or labelId is required')
end end
end end
@ -66,6 +64,15 @@ RSpec.describe Mutations::Boards::Lists::Create do
expect(new_list.title).to eq dev_label.title expect(new_list.title).to eq dev_label.title
expect(new_list.position).to eq 0 expect(new_list.position).to eq 0
end end
context 'when label not found' do
let(:list_create_params) { { label_id: "gid://gitlab/Label/#{non_existing_record_id}" } }
it 'raises an error' do
expect { subject }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Label not found!')
end
end
end end
end end

View File

@ -75,7 +75,7 @@ RSpec.describe MergeRequestsHelper do
describe '#tab_link_for' do describe '#tab_link_for' do
let(:merge_request) { create(:merge_request, :simple) } let(:merge_request) { create(:merge_request, :simple) }
let(:options) { Hash.new } let(:options) { {} }
subject { tab_link_for(merge_request, :show, options) { 'Discussion' } } subject { tab_link_for(merge_request, :show, options) { 'Discussion' } }

View File

@ -50,7 +50,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
describe '#find_job_from_http_basic_auth' do describe '#find_job_from_http_basic_auth' do
let_it_be(:user) { personal_access_token.user } let_it_be(:user) { personal_access_token.user }
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
let(:password) { job.token } let(:password) { job.token }
subject { helper.find_job_from_http_basic_auth } subject { helper.find_job_from_http_basic_auth }
@ -60,6 +60,16 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
end end
it_behaves_like 'invalid auth header' it_behaves_like 'invalid auth header'
context 'when the job is not running' do
before do
job.update!(status: :failed)
end
it_behaves_like 'valid auth header' do
let(:expected_result) { nil }
end
end
end end
describe '#find_deploy_token_from_http_basic_auth' do describe '#find_deploy_token_from_http_basic_auth' do

View File

@ -37,11 +37,29 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
it "return user if token is valid" do context 'with a running job' do
set_token(job.token) before do
job.update!(status: :running)
end
expect(subject).to eq(user) it 'return user if token is valid' do
expect(@current_authenticated_job).to eq job 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 end
end end
@ -557,7 +575,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'with CI username' do context 'with CI username' do
let(:username) { ::Gitlab::Auth::CI_JOB_USER } let(:username) { ::Gitlab::Auth::CI_JOB_USER }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:build) { create(:ci_build, user: user) } let(:build) { create(:ci_build, user: user, status: :running) }
it 'returns nil without password' do it 'returns nil without password' do
set_basic_auth_header(username, nil) set_basic_auth_header(username, nil)
@ -576,6 +594,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
it 'returns exception if the job is not running' do
set_basic_auth_header(username, build.token)
build.success!
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end end
end end
@ -586,7 +611,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'with a job token' do context 'with a job token' do
let(:route_authentication_setting) { { job_token_allowed: true } } let(:route_authentication_setting) { { job_token_allowed: true } }
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
before do before do
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}" env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
@ -641,7 +666,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end end
describe '#find_user_from_job_token' do describe '#find_user_from_job_token' do
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user, status: :running) }
let(:route_authentication_setting) { { job_token_allowed: true } } let(:route_authentication_setting) { { job_token_allowed: true } }
subject { find_user_from_job_token } subject { find_user_from_job_token }
@ -666,6 +691,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
it 'returns exception if the job is not running' do
set_header(described_class::JOB_TOKEN_HEADER, job.token)
job.success!
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
context 'when route is not allowed to be authenticated' do context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } } let(:route_authentication_setting) { { job_token_allowed: false } }

View File

@ -88,7 +88,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
describe '#find_user_from_job_token' do describe '#find_user_from_job_token' do
let!(:user) { build(:user) } let!(:user) { build(:user) }
let!(:job) { build(:ci_build, user: user) } let!(:job) { build(:ci_build, user: user, status: :running) }
before do before do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token' env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token'
@ -97,13 +97,18 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
context 'with API requests' do context 'with API requests' do
before do before do
env['SCRIPT_NAME'] = '/api/endpoint' env['SCRIPT_NAME'] = '/api/endpoint'
expect(::Ci::Build).to receive(:find_by_token).with('token').and_return(job)
end end
it 'tries to find the user' do it 'tries to find the user' do
expect(::Ci::Build).to receive(:find_by_token).and_return(job)
expect(subject.find_sessionless_user([:api])).to eq user expect(subject.find_sessionless_user([:api])).to eq user
end end
it 'returns nil if the job is not running' do
job.status = :success
expect(subject.find_sessionless_user([:api])).to be_blank
end
end end
context 'without API requests' do context 'without API requests' do

Some files were not shown because too many files have changed in this diff Show More