Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e2d4c85dec
commit
8e35232810
|
@ -761,7 +761,7 @@
|
|||
changes: *code-qa-patterns
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- <<: *if-master-refs
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
allow_failure: true
|
||||
|
||||
.review:rules:danger:
|
||||
|
|
|
@ -18,20 +18,6 @@ Capybara/CurrentPathExpectation:
|
|||
Layout/ArgumentAlignment:
|
||||
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
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
|
@ -101,14 +87,6 @@ Layout/SpaceInsideParens:
|
|||
Lint/MissingCopEnableDirective:
|
||||
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
|
||||
# Cop supports --auto-correct.
|
||||
Lint/RedundantCopDisableDirective:
|
||||
|
@ -229,15 +207,6 @@ RSpec/ExpectChange:
|
|||
RSpec/ExpectInHook:
|
||||
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
|
||||
# Cop supports --auto-correct.
|
||||
RSpec/LetBeforeExamples:
|
||||
|
@ -600,17 +569,6 @@ Style/EmptyLambdaParameter:
|
|||
- 'app/models/ci/build.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
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
|
|
|
@ -1197,7 +1197,7 @@ GEM
|
|||
railties (>= 3.2.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
websocket-extensions (0.1.5)
|
||||
wikicloth (0.8.1)
|
||||
builder
|
||||
expression_parser
|
||||
|
|
|
@ -85,6 +85,11 @@ export const conditions = flattenDeep(
|
|||
tokenKey: 'assignee',
|
||||
value: __('Any'),
|
||||
},
|
||||
{
|
||||
url: 'author_username=support-bot',
|
||||
tokenKey: 'author',
|
||||
value: 'support-bot',
|
||||
},
|
||||
{
|
||||
url: 'milestone_title=None',
|
||||
tokenKey: 'milestone',
|
||||
|
|
|
@ -38,8 +38,7 @@ class Clusters::ClustersController < Clusters::BaseController
|
|||
|
||||
def new
|
||||
if params[:provider] == 'aws'
|
||||
@aws_role = current_user.aws_role || Aws::Role.new
|
||||
@aws_role.ensure_role_external_id!
|
||||
@aws_role = Aws::Role.create_or_find_by!(user: current_user)
|
||||
@instance_types = load_instance_types.to_json
|
||||
|
||||
elsif params[:provider] == 'gcp'
|
||||
|
@ -274,7 +273,7 @@ class Clusters::ClustersController < Clusters::BaseController
|
|||
end
|
||||
|
||||
def aws_role_params
|
||||
params.require(:cluster).permit(:role_arn, :role_external_id)
|
||||
params.require(:cluster).permit(:role_arn)
|
||||
end
|
||||
|
||||
def generate_gcp_authorize_url
|
||||
|
|
|
@ -22,6 +22,8 @@ module AuthenticatesWithTwoFactor
|
|||
return handle_locked_user(user) unless user.can?(:log_in)
|
||||
|
||||
session[:otp_user_id] = user.id
|
||||
session[:user_updated_at] = user.updated_at
|
||||
|
||||
setup_u2f_authentication(user)
|
||||
render 'devise/sessions/two_factor'
|
||||
end
|
||||
|
@ -39,6 +41,7 @@ module AuthenticatesWithTwoFactor
|
|||
def authenticate_with_two_factor
|
||||
user = self.resource = find_user
|
||||
return handle_locked_user(user) unless user.can?(:log_in)
|
||||
return handle_changed_user(user) if user_changed?(user)
|
||||
|
||||
if user_params[:otp_attempt].present? && session[:otp_user_id]
|
||||
authenticate_with_two_factor_via_otp(user)
|
||||
|
@ -63,12 +66,14 @@ module AuthenticatesWithTwoFactor
|
|||
|
||||
def clear_two_factor_attempt!
|
||||
session.delete(:otp_user_id)
|
||||
session.delete(:user_updated_at)
|
||||
session.delete(:challenge)
|
||||
end
|
||||
|
||||
def authenticate_with_two_factor_via_otp(user)
|
||||
if valid_otp_attempt?(user)
|
||||
# Remove any lingering user data from login
|
||||
session.delete(:otp_user_id)
|
||||
clear_two_factor_attempt!
|
||||
|
||||
remember_me(user) if user_params[:remember_me] == '1'
|
||||
user.save!
|
||||
|
@ -85,8 +90,7 @@ module AuthenticatesWithTwoFactor
|
|||
def authenticate_with_two_factor_via_u2f(user)
|
||||
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
|
||||
# Remove any lingering user data from login
|
||||
session.delete(:otp_user_id)
|
||||
session.delete(:challenge)
|
||||
clear_two_factor_attempt!
|
||||
|
||||
remember_me(user) if user_params[:remember_me] == '1'
|
||||
sign_in(user, message: :two_factor_authenticated, event: :authentication)
|
||||
|
@ -113,4 +117,18 @@ module AuthenticatesWithTwoFactor
|
|||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def handle_changed_user(user)
|
||||
clear_two_factor_attempt!
|
||||
|
||||
redirect_to new_user_session_path, alert: _('An error occurred. Please sign in again.')
|
||||
end
|
||||
|
||||
# If user has been updated since we validated the password,
|
||||
# the password might have changed.
|
||||
def user_changed?(user)
|
||||
return false unless session[:user_updated_at]
|
||||
|
||||
user.updated_at != session[:user_updated_at]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,12 +29,11 @@ module EnforcesTwoFactorAuthentication
|
|||
end
|
||||
|
||||
def two_factor_authentication_required?
|
||||
Gitlab::CurrentSettings.require_two_factor_authentication? ||
|
||||
current_user.try(:require_two_factor_authentication_from_group?)
|
||||
two_factor_verifier.two_factor_authentication_required?
|
||||
end
|
||||
|
||||
def current_user_requires_two_factor?
|
||||
current_user && !current_user.temp_oauth_email? && !current_user.two_factor_enabled? && !skip_two_factor?
|
||||
two_factor_verifier.current_user_needs_to_setup_two_factor? && !skip_two_factor?
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
@ -43,7 +42,7 @@ module EnforcesTwoFactorAuthentication
|
|||
if Gitlab::CurrentSettings.require_two_factor_authentication?
|
||||
global.call
|
||||
else
|
||||
groups = current_user.expanded_groups_requiring_two_factor_authentication.reorder(name: :asc)
|
||||
groups = current_user.source_groups_of_two_factor_authentication_requirement.reorder(name: :asc)
|
||||
group.call(groups)
|
||||
end
|
||||
end
|
||||
|
@ -51,14 +50,11 @@ module EnforcesTwoFactorAuthentication
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def two_factor_grace_period
|
||||
periods = [Gitlab::CurrentSettings.two_factor_grace_period]
|
||||
periods << current_user.two_factor_grace_period if current_user.try(:require_two_factor_authentication_from_group?)
|
||||
periods.min
|
||||
two_factor_verifier.two_factor_grace_period
|
||||
end
|
||||
|
||||
def two_factor_grace_period_expired?
|
||||
date = current_user.otp_grace_period_started_at
|
||||
date && (date + two_factor_grace_period.hours) < Time.current
|
||||
two_factor_verifier.two_factor_grace_period_expired?
|
||||
end
|
||||
|
||||
def two_factor_skippable?
|
||||
|
@ -70,6 +66,10 @@ module EnforcesTwoFactorAuthentication
|
|||
def skip_two_factor?
|
||||
session[:skip_two_factor] && session[:skip_two_factor] > Time.current
|
||||
end
|
||||
|
||||
def two_factor_verifier
|
||||
@two_factor_verifier ||= Gitlab::Auth::TwoFactorAuthVerifier.new(current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
end
|
||||
|
||||
EnforcesTwoFactorAuthentication.prepend_if_ee('EE::EnforcesTwoFactorAuthentication')
|
||||
|
|
|
@ -50,12 +50,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
redirect_unverified_saml_initiation
|
||||
end
|
||||
|
||||
def omniauth_error
|
||||
@provider = params[:provider]
|
||||
@error = params[:error]
|
||||
render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def cas3
|
||||
ticket = params['ticket']
|
||||
if ticket
|
||||
|
@ -205,9 +199,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
def fail_login(user)
|
||||
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
|
||||
|
||||
def fail_auth0_login
|
||||
|
|
|
@ -7,6 +7,7 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController
|
|||
|
||||
def destroy
|
||||
ActiveSession.destroy_with_public_id(current_user, params[:id])
|
||||
current_user.forget_me!
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to profile_active_sessions_url, status: :found }
|
||||
|
|
|
@ -4,7 +4,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
skip_before_action :check_two_factor_requirement
|
||||
|
||||
def show
|
||||
unless current_user.otp_secret
|
||||
unless current_user.two_factor_enabled?
|
||||
current_user.otp_secret = User.generate_otp_secret(32)
|
||||
end
|
||||
|
||||
|
@ -38,6 +38,8 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
|
||||
def create
|
||||
if current_user.validate_and_consume_otp!(params[:pin_code])
|
||||
ActiveSession.destroy_all_but_current(current_user, session)
|
||||
|
||||
Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
|
||||
@codes = user.generate_otp_backup_codes!
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ class SessionsController < Devise::SessionsController
|
|||
include Recaptcha::Verify
|
||||
include RendersLdapServers
|
||||
include KnownSignIn
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
skip_before_action :check_two_factor_requirement, only: [:destroy]
|
||||
skip_before_action :check_password_expiration, only: [:destroy]
|
||||
|
@ -199,10 +200,14 @@ class SessionsController < Devise::SessionsController
|
|||
end
|
||||
|
||||
def find_user
|
||||
if session[:otp_user_id]
|
||||
User.find(session[:otp_user_id])
|
||||
elsif user_params[:login]
|
||||
User.by_login(user_params[:login])
|
||||
strong_memoize(:find_user) do
|
||||
if session[:otp_user_id] && user_params[:login]
|
||||
User.by_id_and_login(session[:otp_user_id], user_params[:login]).first
|
||||
elsif session[:otp_user_id]
|
||||
User.find(session[:otp_user_id])
|
||||
elsif user_params[:login]
|
||||
User.by_login(user_params[:login])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
module Ci
|
||||
class AuthJobFinder
|
||||
AuthError = Class.new(StandardError)
|
||||
NotRunningJobError = Class.new(AuthError)
|
||||
ErasedJobError = Class.new(AuthError)
|
||||
DeletedProjectError = Class.new(AuthError)
|
||||
|
||||
def initialize(token:)
|
||||
@token = token
|
||||
end
|
||||
|
||||
def execute!
|
||||
find_job_by_token.tap do |job|
|
||||
next unless job
|
||||
|
||||
validate_job!(job)
|
||||
end
|
||||
end
|
||||
|
||||
def execute
|
||||
execute!
|
||||
rescue AuthError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :token, :require_running, :raise_on_missing
|
||||
|
||||
def find_job_by_token
|
||||
::Ci::Build.find_by_token(token)
|
||||
end
|
||||
|
||||
def validate_job!(job)
|
||||
validate_running_job!(job)
|
||||
validate_job_not_erased!(job)
|
||||
validate_project_presence!(job)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def validate_running_job!(job)
|
||||
raise NotRunningJobError, 'Job is not running' unless job.running?
|
||||
end
|
||||
|
||||
def validate_job_not_erased!(job)
|
||||
raise ErasedJobError, 'Job has been erased!' if job.erased?
|
||||
end
|
||||
|
||||
def validate_project_presence!(job)
|
||||
if job.project.nil? || job.project.pending_delete?
|
||||
raise DeletedProjectError, 'Project has been deleted!'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@
|
|||
# WARNING: does not consider project feature visibility!
|
||||
# - user: The user for which to load the events
|
||||
# - params:
|
||||
# - limit: Number of items that to be returned. Defaults to 20 and limited to 100.
|
||||
# - offset: The page of events to return
|
||||
class UserRecentEventsFinder
|
||||
prepend FinderWithCrossProjectAccess
|
||||
|
@ -16,7 +17,8 @@ class UserRecentEventsFinder
|
|||
|
||||
attr_reader :current_user, :target_user, :params
|
||||
|
||||
LIMIT = 20
|
||||
DEFAULT_LIMIT = 20
|
||||
MAX_LIMIT = 100
|
||||
|
||||
def initialize(current_user, target_user, params = {})
|
||||
@current_user = current_user
|
||||
|
@ -31,7 +33,7 @@ class UserRecentEventsFinder
|
|||
recent_events(params[:offset] || 0)
|
||||
.joins(:project)
|
||||
.with_associations
|
||||
.limit_recent(params[:limit].presence || LIMIT, params[:offset])
|
||||
.limit_recent(limit, params[:offset])
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
@ -59,4 +61,10 @@ class UserRecentEventsFinder
|
|||
def projects
|
||||
target_user.project_interactions.to_sql
|
||||
end
|
||||
|
||||
def limit
|
||||
return DEFAULT_LIMIT unless params[:limit].present?
|
||||
|
||||
[params[:limit].to_i, MAX_LIMIT].min
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Mutations
|
|||
|
||||
argument :board_id, ::Types::GlobalIDType[::Board],
|
||||
required: true,
|
||||
description: 'The Global ID of the issue board to mutate'
|
||||
description: 'Global ID of the issue board to mutate'
|
||||
|
||||
field :list,
|
||||
Types::BoardListType,
|
||||
|
|
|
@ -12,7 +12,7 @@ module Mutations
|
|||
|
||||
argument :label_id, ::Types::GlobalIDType[::Label],
|
||||
required: false,
|
||||
description: 'ID of an existing label'
|
||||
description: 'Global ID of an existing label'
|
||||
|
||||
def ready?(**args)
|
||||
if args.slice(*mutually_exclusive_args).size != 1
|
||||
|
@ -39,6 +39,7 @@ module Mutations
|
|||
|
||||
private
|
||||
|
||||
# Overridden in EE
|
||||
def authorize_list_type_resource!(board, params)
|
||||
return unless params[:label_id]
|
||||
|
||||
|
@ -57,13 +58,15 @@ module Mutations
|
|||
create_list_service.execute(board)
|
||||
end
|
||||
|
||||
# Overridden in EE
|
||||
def create_list_params(args)
|
||||
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
|
||||
end
|
||||
|
||||
# Overridden in EE
|
||||
def mutually_exclusive_args
|
||||
[:backlog, :label_id]
|
||||
end
|
||||
|
@ -71,3 +74,5 @@ module Mutations
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Mutations::Boards::Lists::Create.prepend_if_ee('::EE::Mutations::Boards::Lists::Create')
|
||||
|
|
|
@ -11,7 +11,7 @@ module Mutations
|
|||
private
|
||||
|
||||
def find_object(id:)
|
||||
GitlabSchema.object_from_id(id)
|
||||
GitlabSchema.object_from_id(id, expected_type: ::Snippet)
|
||||
end
|
||||
|
||||
def authorized_resource?(snippet)
|
||||
|
|
|
@ -105,6 +105,19 @@ class ActiveSession
|
|||
end
|
||||
end
|
||||
|
||||
def self.destroy_all_but_current(user, current_session)
|
||||
session_ids = not_impersonated(user)
|
||||
session_ids.reject! { |session| session.current?(current_session) } if current_session
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
destroy_sessions(redis, user, session_ids.map(&:session_id)) if session_ids.any?
|
||||
end
|
||||
end
|
||||
|
||||
def self.not_impersonated(user)
|
||||
list(user).reject(&:is_impersonated)
|
||||
end
|
||||
|
||||
def self.key_name(user_id, session_id = '*')
|
||||
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ module Aws
|
|||
validates :role_external_id, uniqueness: true, length: { in: 1..64 }
|
||||
validates :role_arn,
|
||||
length: 1..2048,
|
||||
allow_nil: true,
|
||||
format: {
|
||||
with: Gitlab::Regex.aws_arn_regex,
|
||||
message: Gitlab::Regex.aws_arn_regex_message
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Clusters
|
||||
module Applications
|
||||
class Runner < ApplicationRecord
|
||||
VERSION = '0.20.0'
|
||||
VERSION = '0.20.1'
|
||||
|
||||
self.table_name = 'clusters_applications_runners'
|
||||
|
||||
|
|
|
@ -13,14 +13,12 @@ module DiscussionOnDiff
|
|||
:diff_line,
|
||||
:active?,
|
||||
:created_at_diff?,
|
||||
|
||||
to: :first_note
|
||||
|
||||
delegate :file_path,
|
||||
:blob,
|
||||
:highlighted_diff_lines,
|
||||
:diff_lines,
|
||||
|
||||
to: :diff_file,
|
||||
allow_nil: true
|
||||
end
|
||||
|
|
|
@ -31,7 +31,6 @@ module ResolvableDiscussion
|
|||
delegate :resolved_at,
|
||||
:resolved_by,
|
||||
:resolved_by_push?,
|
||||
|
||||
to: :last_resolved_note,
|
||||
allow_nil: true
|
||||
end
|
||||
|
|
|
@ -16,7 +16,6 @@ class DiffDiscussion < Discussion
|
|||
:diff_note_positions,
|
||||
:on_text?,
|
||||
:on_image?,
|
||||
|
||||
to: :first_note
|
||||
|
||||
def legacy_diff_discussion?
|
||||
|
|
|
@ -24,7 +24,6 @@ class Discussion
|
|||
:system_note_with_references_visible_for?,
|
||||
:resource_parent,
|
||||
:save,
|
||||
|
||||
to: :first_note
|
||||
|
||||
def declarative_policy_delegate
|
||||
|
|
|
@ -76,6 +76,8 @@ class Member < ApplicationRecord
|
|||
scope :request, -> { where.not(requested_at: nil) }
|
||||
scope :non_request, -> { where(requested_at: nil) }
|
||||
|
||||
scope :not_accepted_invitations_by_user, -> (user) { invite.where(invite_accepted_at: nil, created_by: user) }
|
||||
|
||||
scope :has_access, -> { active.where('access_level > 0') }
|
||||
|
||||
scope :guests, -> { active.where(access_level: GUEST) }
|
||||
|
|
|
@ -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_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) }
|
||||
scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) }
|
||||
scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) }
|
||||
|
||||
def preferred_language
|
||||
read_attribute('preferred_language') ||
|
||||
|
@ -887,6 +888,12 @@ class User < ApplicationRecord
|
|||
all_expanded_groups.where(require_two_factor_authentication: true)
|
||||
end
|
||||
|
||||
def source_groups_of_two_factor_authentication_requirement
|
||||
Gitlab::ObjectHierarchy.new(expanded_groups_requiring_two_factor_authentication)
|
||||
.all_objects
|
||||
.where(id: groups)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def refresh_authorized_projects
|
||||
Users::RefreshAuthorizedProjectsService.new(self).execute
|
||||
|
|
|
@ -11,7 +11,16 @@ module Applications
|
|||
|
||||
# EE would override and use `request` arg
|
||||
def execute(request)
|
||||
Doorkeeper::Application.create(params)
|
||||
@application = Doorkeeper::Application.new(params)
|
||||
|
||||
unless params[:scopes].present?
|
||||
@application.errors.add(:base, _("Scopes can't be blank"))
|
||||
|
||||
return @application
|
||||
end
|
||||
|
||||
@application.save
|
||||
@application
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -132,6 +132,7 @@ module Auth
|
|||
|
||||
def can_access?(requested_project, requested_action)
|
||||
return false unless requested_project.container_registry_enabled?
|
||||
return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED
|
||||
|
||||
case requested_action
|
||||
when 'pull'
|
||||
|
|
|
@ -26,7 +26,7 @@ module Branches
|
|||
message: 'Failed to remove branch',
|
||||
http_status: 400)
|
||||
end
|
||||
rescue Gitlab::Git::PreReceiveError => ex
|
||||
rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError => ex
|
||||
ServiceResponse.error(message: ex.message, http_status: 400)
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ module Ci
|
|||
elsif job_from_token
|
||||
create_pipeline_from_job(job_from_token)
|
||||
end
|
||||
|
||||
rescue Ci::AuthJobFinder::AuthError => e
|
||||
error(e.message, 401)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -41,8 +44,6 @@ module Ci
|
|||
# this check is to not leak the presence of the project if user cannot read it
|
||||
return unless can?(job.user, :read_project, project)
|
||||
|
||||
return error("400 Job has to be running", 400) unless job.running?
|
||||
|
||||
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
|
||||
.execute(:pipeline, ignore_skip_ci: true) do |pipeline|
|
||||
source = job.sourced_pipelines.build(
|
||||
|
@ -64,7 +65,7 @@ module Ci
|
|||
|
||||
def job_from_token
|
||||
strong_memoize(:job) do
|
||||
Ci::Build.find_by_token(params[:token].to_s)
|
||||
Ci::AuthJobFinder.new(token: params[:token].to_s).execute!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ module Clusters
|
|||
|
||||
ERRORS = [
|
||||
ActiveRecord::RecordInvalid,
|
||||
ActiveRecord::RecordNotFound,
|
||||
Clusters::Aws::FetchCredentialsService::MissingRoleError,
|
||||
::Aws::Errors::MissingCredentialsError,
|
||||
::Aws::STS::Errors::ServiceError
|
||||
|
@ -20,7 +21,8 @@ module Clusters
|
|||
end
|
||||
|
||||
def execute
|
||||
@role = create_or_update_role!
|
||||
ensure_role_exists!
|
||||
update_role_arn!
|
||||
|
||||
Response.new(:ok, credentials)
|
||||
rescue *ERRORS => e
|
||||
|
@ -33,14 +35,12 @@ module Clusters
|
|||
|
||||
attr_reader :role, :params
|
||||
|
||||
def create_or_update_role!
|
||||
if role = user.aws_role
|
||||
role.update!(params)
|
||||
def ensure_role_exists!
|
||||
@role = ::Aws::Role.find_by_user_id!(user.id)
|
||||
end
|
||||
|
||||
role
|
||||
else
|
||||
user.create_aws_role!(params)
|
||||
end
|
||||
def update_role_arn!
|
||||
role.update!(params)
|
||||
end
|
||||
|
||||
def credentials
|
||||
|
|
|
@ -18,6 +18,7 @@ module Members
|
|||
end
|
||||
|
||||
delete_subresources(member) unless skip_subresources
|
||||
delete_project_invitations_by(member) unless skip_subresources
|
||||
enqueue_delete_todos(member)
|
||||
enqueue_unassign_issuables(member) if unassign_issuables
|
||||
|
||||
|
@ -39,24 +40,48 @@ module Members
|
|||
|
||||
delete_project_members(member)
|
||||
delete_subgroup_members(member)
|
||||
delete_invited_members(member)
|
||||
end
|
||||
|
||||
def delete_project_members(member)
|
||||
groups = member.group.self_and_descendants
|
||||
|
||||
ProjectMember.in_namespaces(groups).with_user(member.user).each do |project_member|
|
||||
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
|
||||
end
|
||||
destroy_project_members(ProjectMember.in_namespaces(groups).with_user(member.user))
|
||||
end
|
||||
|
||||
def delete_subgroup_members(member)
|
||||
groups = member.group.descendants
|
||||
|
||||
GroupMember.of_groups(groups).with_user(member.user).each do |group_member|
|
||||
destroy_group_members(GroupMember.of_groups(groups).with_user(member.user))
|
||||
end
|
||||
|
||||
def delete_invited_members(member)
|
||||
groups = member.group.self_and_descendants
|
||||
|
||||
destroy_group_members(GroupMember.of_groups(groups).not_accepted_invitations_by_user(member.user))
|
||||
|
||||
destroy_project_members(ProjectMember.in_namespaces(groups).not_accepted_invitations_by_user(member.user))
|
||||
end
|
||||
|
||||
def destroy_project_members(members)
|
||||
members.each do |project_member|
|
||||
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_group_members(members)
|
||||
members.each do |group_member|
|
||||
self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_project_invitations_by(member)
|
||||
return unless member.is_a?(ProjectMember) && member.user && member.project
|
||||
|
||||
members_to_delete = member.project.members.not_accepted_invitations_by_user(member.user)
|
||||
destroy_project_members(members_to_delete)
|
||||
end
|
||||
|
||||
def can_destroy_member?(member)
|
||||
can?(current_user, destroy_member_permission(member), member)
|
||||
end
|
||||
|
|
|
@ -7,6 +7,10 @@ module Projects
|
|||
def execute(remote_mirror, tries)
|
||||
return success unless remote_mirror.enabled?
|
||||
|
||||
if Gitlab::UrlBlocker.blocked_url?(CGI.unescape(Gitlab::UrlSanitizer.sanitize(remote_mirror.url)))
|
||||
return error("The remote mirror URL is invalid.")
|
||||
end
|
||||
|
||||
update_mirror(remote_mirror)
|
||||
|
||||
success
|
||||
|
|
|
@ -27,6 +27,6 @@
|
|||
|
||||
- unless is_current_session
|
||||
.float-right
|
||||
= link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
|
||||
= link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
|
||||
%span.sr-only= _('Revoke')
|
||||
= _('Revoke')
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
- 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) } }
|
||||
.top-area
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix unfinished merge by Merge Train process
|
||||
merge_request: 41106
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix the filtered search bar to work in the service desk issue list
|
||||
merge_request: 40797
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Track downloads of group code coverage CSV in snowplow
|
||||
merge_request: 40754
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Layout/EmptyLinesAroundArguments cop
|
||||
merge_request: 41086
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Style/EmptyLiteral cop
|
||||
merge_request: 41110
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix RSpec/ItBehavesLike cop
|
||||
merge_request: 41111
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Lint/NonDeterministicRequireOrder cop
|
||||
merge_request: 41098
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Protect OAuth endpoints from brute force/password stuffing
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update GitLab Runner Helm Chart to 0.20.1
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -17,7 +17,7 @@ Doorkeeper.configure do
|
|||
end
|
||||
|
||||
resource_owner_from_credentials do |routes|
|
||||
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password])
|
||||
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password], increment_failed_attempts: true)
|
||||
user unless user.try(:two_factor_enabled?)
|
||||
end
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks,
|
|||
confirmations: :confirmations }
|
||||
|
||||
devise_scope :user do
|
||||
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
|
||||
get '/users/almost_there' => 'confirmations#almost_there'
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeAwsRolesRoleArnNull < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
EXAMPLE_ARN = 'arn:aws:iam::000000000000:role/example-role'
|
||||
|
||||
def up
|
||||
change_column_null :aws_roles, :role_arn, true
|
||||
end
|
||||
|
||||
def down
|
||||
# Records may now exist with nulls, so we must fill them with a dummy value
|
||||
change_column_null :aws_roles, :role_arn, false, EXAMPLE_ARN
|
||||
end
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddOAuthPathsToProtectedPaths < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
ADD_PROTECTED_PATHS = [
|
||||
'/oauth/authorize',
|
||||
'/oauth/token'
|
||||
].freeze
|
||||
|
||||
EXISTING_DEFAULT_PROTECTED_PATHS = [
|
||||
'/users/password',
|
||||
'/users/sign_in',
|
||||
'/api/v3/session.json',
|
||||
'/api/v3/session',
|
||||
'/api/v4/session.json',
|
||||
'/api/v4/session',
|
||||
'/users',
|
||||
'/users/confirmation',
|
||||
'/unsubscribes/',
|
||||
'/import/github/personal_access_token',
|
||||
'/admin/session'
|
||||
].freeze
|
||||
|
||||
NEW_DEFAULT_PROTECTED_PATHS = (EXISTING_DEFAULT_PROTECTED_PATHS + ADD_PROTECTED_PATHS).freeze
|
||||
|
||||
class ApplicationSetting < ActiveRecord::Base
|
||||
self.table_name = 'application_settings'
|
||||
end
|
||||
|
||||
def up
|
||||
change_column_default :application_settings, :protected_paths, NEW_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
|
||||
missing_paths = ADD_PROTECTED_PATHS - application_setting.protected_paths
|
||||
|
||||
next if missing_paths.empty?
|
||||
|
||||
updated_protected_paths = application_setting.protected_paths + missing_paths
|
||||
application_setting.update!(protected_paths: updated_protected_paths)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_default :application_settings, :protected_paths, EXISTING_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
|
||||
paths_to_remove = application_setting.protected_paths - EXISTING_DEFAULT_PROTECTED_PATHS
|
||||
|
||||
next if paths_to_remove.empty?
|
||||
|
||||
updated_protected_paths = application_setting.protected_paths - paths_to_remove
|
||||
application_setting.update!(protected_paths: updated_protected_paths)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
6b8fa09c9700c494eeb5151f43064f1656eaaea804742629b7bd66483e2b04cb
|
|
@ -0,0 +1 @@
|
|||
2aab4599404312ddcc5bc9af11b0a21dfd6aa8aa10d4b4b5086a93ce1ffe77b6
|
|
@ -9190,7 +9190,7 @@ CREATE TABLE public.application_settings (
|
|||
throttle_protected_paths_enabled boolean DEFAULT false NOT NULL,
|
||||
throttle_protected_paths_requests_per_period integer DEFAULT 10 NOT NULL,
|
||||
throttle_protected_paths_period_in_seconds integer DEFAULT 60 NOT NULL,
|
||||
protected_paths character varying(255)[] DEFAULT '{/users/password,/users/sign_in,/api/v3/session.json,/api/v3/session,/api/v4/session.json,/api/v4/session,/users,/users/confirmation,/unsubscribes/,/import/github/personal_access_token,/admin/session}'::character varying[],
|
||||
protected_paths character varying(255)[] DEFAULT '{/users/password,/users/sign_in,/api/v3/session.json,/api/v3/session,/api/v4/session.json,/api/v4/session,/users,/users/confirmation,/unsubscribes/,/import/github/personal_access_token,/admin/session,/oauth/authorize,/oauth/token}'::character varying[],
|
||||
throttle_incident_management_notification_enabled boolean DEFAULT false NOT NULL,
|
||||
throttle_incident_management_notification_period_in_seconds integer DEFAULT 3600,
|
||||
throttle_incident_management_notification_per_period integer DEFAULT 3600,
|
||||
|
@ -9557,7 +9557,7 @@ CREATE TABLE public.aws_roles (
|
|||
user_id integer NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
role_arn character varying(2048) NOT NULL,
|
||||
role_arn character varying(2048),
|
||||
role_external_id character varying(64) NOT NULL
|
||||
);
|
||||
|
||||
|
|
|
@ -1309,13 +1309,18 @@ type BoardListConnection {
|
|||
Autogenerated input type of BoardListCreate
|
||||
"""
|
||||
input BoardListCreateInput {
|
||||
"""
|
||||
Global ID of an existing user
|
||||
"""
|
||||
assigneeId: UserID
|
||||
|
||||
"""
|
||||
Create the backlog list
|
||||
"""
|
||||
backlog: Boolean
|
||||
|
||||
"""
|
||||
The Global ID of the issue board to mutate
|
||||
Global ID of the issue board to mutate
|
||||
"""
|
||||
boardId: BoardID!
|
||||
|
||||
|
@ -1325,9 +1330,14 @@ input BoardListCreateInput {
|
|||
clientMutationId: String
|
||||
|
||||
"""
|
||||
ID of an existing label
|
||||
Global ID of an existing label
|
||||
"""
|
||||
labelId: LabelID
|
||||
|
||||
"""
|
||||
Global ID of an existing milestone
|
||||
"""
|
||||
milestoneId: MilestoneID
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -17210,6 +17220,11 @@ type UserEdge {
|
|||
node: User
|
||||
}
|
||||
|
||||
"""
|
||||
Identifier of User
|
||||
"""
|
||||
scalar UserID
|
||||
|
||||
type UserPermissions {
|
||||
"""
|
||||
Indicates the user can perform `create_snippet` on this resource
|
||||
|
|
|
@ -3505,7 +3505,7 @@
|
|||
"inputFields": [
|
||||
{
|
||||
"name": "boardId",
|
||||
"description": "The Global ID of the issue board to mutate",
|
||||
"description": "Global ID of the issue board to mutate",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
|
@ -3529,7 +3529,7 @@
|
|||
},
|
||||
{
|
||||
"name": "labelId",
|
||||
"description": "ID of an existing label",
|
||||
"description": "Global ID of an existing label",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "LabelID",
|
||||
|
@ -3537,6 +3537,26 @@
|
|||
},
|
||||
"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",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
|
@ -50478,6 +50498,16 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "UserID",
|
||||
"description": "Identifier of User",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "UserPermissions",
|
||||
|
|
|
@ -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
|
||||
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
|
||||
following approach:
|
||||
**Before you remove arguments from the `perform_async` and `perform` methods.**, deprecate them. The
|
||||
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
|
||||
argument as deprecated
|
||||
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.
|
||||
argument as deprecated in the coming minor release. (Release M)
|
||||
|
||||
In the following example, if you want to remove `arg2`, first set a `nil` default value,
|
||||
and then update locations where `ExampleWorker.perform_async` is called.
|
||||
```ruby
|
||||
class ExampleWorker
|
||||
# Keep arg2 parameter for backwards compatibility.
|
||||
def perform(object_id, arg1, arg2 = nil)
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
class ExampleWorker
|
||||
def perform(object_id, arg1, arg2 = nil)
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
1. One minor release later, stop using the argument in `perform_async`. (Release M+1)
|
||||
|
||||
```ruby
|
||||
ExampleWorker.perform_async(object_id, arg1)
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
##### Multi-step deployment
|
||||
|
||||
This approach requires multiple merge requests and for the first merge request
|
||||
to be merged and deployed before additional changes are merged.
|
||||
This approach requires multiple releases.
|
||||
|
||||
1. In an initial merge request, add the argument to the worker with a default
|
||||
value:
|
||||
1. Add the argument to the worker with a default value (Release M).
|
||||
|
||||
```ruby
|
||||
class ExampleWorker
|
||||
|
@ -660,16 +669,28 @@ to be merged and deployed before additional changes are merged.
|
|||
end
|
||||
```
|
||||
|
||||
1. Merge and deploy the worker with the new argument.
|
||||
1. In a further merge request, update `ExampleWorker.perform_async` calls to
|
||||
use the new argument.
|
||||
1. Add the new argument to all the invocations of the worker (Release M+1).
|
||||
|
||||
```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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
class ExampleWorker
|
||||
|
|
|
@ -165,7 +165,7 @@ instances](#indexing-large-instances) below.
|
|||
|
||||
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.
|
||||
1. Configure the [Elasticsearch settings](#elasticsearch-configuration) for
|
||||
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 >
|
||||
Integrations > Elasticsearch** and click **Save changes**.
|
||||
General > Elasticsearch** and click **Save changes**.
|
||||
1. Click **Index all projects**.
|
||||
1. Click **Check progress** in the confirmation message to see the status of
|
||||
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
|
||||
**Admin Area > Settings > Integrations > Elasticsearch** and click **Save
|
||||
**Admin Area > Settings > General > Elasticsearch** and click **Save
|
||||
changes**.
|
||||
|
||||
### Elasticsearch configuration
|
||||
|
@ -251,7 +251,7 @@ from the Elasticsearch index as expected.
|
|||
|
||||
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**
|
||||
and **Search with Elasticsearch enabled**.
|
||||
1. Click **Save changes** for the changes to take effect.
|
||||
|
@ -439,7 +439,7 @@ used by the GitLab Elasticsearch integration.
|
|||
|
||||
### 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.
|
||||
|
||||
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:
|
||||
|
||||
```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 PRIMARY_INDEX="gitlab-production"
|
||||
export SECONDARY_INDEX="gitlab-production-$(date +%s)"
|
||||
|
@ -556,14 +556,14 @@ To trigger the re-index from `primary` index:
|
|||
|
||||
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
|
||||
|
||||
> - [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.
|
||||
|
||||
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:**
|
||||
Reindexing can be a lengthy process depending on the size of your Elasticsearch cluster.
|
||||
|
|
|
@ -29,6 +29,11 @@ exceeds 100, the oldest ones are deleted.
|
|||
1. Use the previous steps to navigate to **Active Sessions**.
|
||||
1. Click on **Revoke** besides a session. The current session cannot be revoked, as this would sign you out of GitLab.
|
||||
|
||||
NOTE: **Note:**
|
||||
When any session is revoked all **Remember me** tokens for all
|
||||
devices will be revoked. See ['Why do I keep getting signed out?'](index.md#why-do-i-keep-getting-signed-out)
|
||||
for more information about the **Remember me** feature.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
|
|
|
@ -255,6 +255,12 @@ to get you a new `_gitlab_session` and keep you signed in through browser restar
|
|||
After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
|
||||
you 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
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20340) in GitLab 13.1.
|
||||
|
@ -264,7 +270,7 @@ The `remember_user_token` lifetime of a cookie can now extend beyond the deadlin
|
|||
GitLab uses both session and persistent cookies:
|
||||
|
||||
- Session cookie: Session cookies are normally removed at the end of the browser session when the browser is closed. The `_gitlab_session` cookie has no expiration date.
|
||||
- Persistent cookie: The `remember_me_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
|
||||
- Persistent cookie: The `remember_user_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
|
||||
|
||||
By default, the server sets a time-to-live (TTL) of 1-week on any session that is used.
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ module API
|
|||
deploy_token_from_request ||
|
||||
find_user_from_bearer_token ||
|
||||
find_user_from_job_token ||
|
||||
find_user_from_warden
|
||||
user_from_warden
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -103,6 +103,25 @@ module API
|
|||
def user_allowed_or_deploy_token?(user)
|
||||
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
|
||||
end
|
||||
|
||||
def user_from_warden
|
||||
user = find_user_from_warden
|
||||
|
||||
return unless user
|
||||
return if two_factor_required_but_not_setup?(user)
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def two_factor_required_but_not_setup?(user)
|
||||
verifier = Gitlab::Auth::TwoFactorAuthVerifier.new(user)
|
||||
|
||||
if verifier.two_factor_authentication_required? && verifier.current_user_needs_to_setup_two_factor?
|
||||
verifier.two_factor_grace_period_expired?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
|
|
@ -109,9 +109,10 @@ module API
|
|||
end
|
||||
put ":id/badges/:badge_id" do
|
||||
source = find_source_if_admin(source_type)
|
||||
badge = find_badge(source)
|
||||
|
||||
badge = ::Badges::UpdateService.new(declared_params(include_missing: false))
|
||||
.execute(find_badge(source))
|
||||
.execute(badge)
|
||||
|
||||
if badge.valid?
|
||||
present_badges(source, badge)
|
||||
|
@ -130,10 +131,6 @@ module API
|
|||
source = find_source_if_admin(source_type)
|
||||
badge = find_badge(source)
|
||||
|
||||
if badge.is_a?(GroupBadge) && source.is_a?(Project)
|
||||
error!('To delete a Group badge please use the Group endpoint', 403)
|
||||
end
|
||||
|
||||
destroy_conditionally!(badge)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,8 @@ module API
|
|||
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
|
||||
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
|
||||
|
||||
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
|
||||
|
||||
before do
|
||||
require_packages_enabled!
|
||||
|
||||
|
@ -259,7 +261,7 @@ module API
|
|||
end
|
||||
|
||||
params do
|
||||
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex
|
||||
requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
|
||||
end
|
||||
namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do
|
||||
desc 'Download recipe files' do
|
||||
|
@ -277,7 +279,7 @@ module API
|
|||
end
|
||||
|
||||
params do
|
||||
use :workhorse_upload_params
|
||||
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
|
||||
end
|
||||
|
||||
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
|
||||
|
@ -300,7 +302,7 @@ module API
|
|||
params do
|
||||
requires :conan_package_reference, type: String, desc: 'Conan Package ID'
|
||||
requires :package_revision, type: String, desc: 'Conan Package Revision'
|
||||
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.conan_file_name_regex
|
||||
requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
|
||||
end
|
||||
namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do
|
||||
desc 'Download package files' do
|
||||
|
@ -328,7 +330,7 @@ module API
|
|||
end
|
||||
|
||||
params do
|
||||
use :workhorse_upload_params
|
||||
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
|
||||
end
|
||||
|
||||
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
|
||||
|
|
|
@ -6,7 +6,13 @@ module API
|
|||
include ::API::Helpers::MembersHelpers
|
||||
|
||||
def find_badge(source)
|
||||
source.badges.find(params[:badge_id])
|
||||
badge_id = params[:badge_id]
|
||||
|
||||
if source.is_a?(Project)
|
||||
source.project_badges.find(badge_id)
|
||||
else
|
||||
source.badges.find(badge_id)
|
||||
end
|
||||
end
|
||||
|
||||
def present_badges(source, records, options = {})
|
||||
|
|
|
@ -134,7 +134,7 @@ module API
|
|||
end
|
||||
|
||||
def track_push_package_event
|
||||
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params['file.size'] > 0
|
||||
if params[:file_name] == ::Packages::Conan::FileMetadatum::PACKAGE_BINARY && params[:file].size > 0 # rubocop: disable Style/ZeroLengthPredicate
|
||||
track_event('push_package')
|
||||
end
|
||||
end
|
||||
|
@ -148,9 +148,9 @@ module API
|
|||
end
|
||||
|
||||
def create_package_file_with_type(file_type, current_package)
|
||||
unless params['file.size'] == 0
|
||||
unless params[:file].size == 0 # rubocop: disable Style/ZeroLengthPredicate
|
||||
# conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0
|
||||
::Packages::Conan::CreatePackageFileService.new(current_package, uploaded_package_file, params.merge(conan_file_type: file_type)).execute
|
||||
::Packages::Conan::CreatePackageFileService.new(current_package, params[:file], params.merge(conan_file_type: file_type)).execute
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -222,7 +222,7 @@ module API
|
|||
|
||||
return unless token
|
||||
|
||||
::Ci::Build.find_by_token(token.access_token_id.to_s)
|
||||
::Ci::AuthJobFinder.new(token: token.access_token_id.to_s).execute
|
||||
end
|
||||
|
||||
def decode_oauth_token_from_jwt
|
||||
|
|
|
@ -23,7 +23,7 @@ module API
|
|||
|
||||
return unless token
|
||||
|
||||
::Ci::Build.find_by_token(token)
|
||||
::Ci::AuthJobFinder.new(token: token).execute
|
||||
end
|
||||
|
||||
def find_deploy_token_from_http_basic_auth
|
||||
|
|
|
@ -7,7 +7,6 @@ module Banzai
|
|||
@filters ||= FilterArray[
|
||||
Filter::MarkdownFilter,
|
||||
Filter::BroadcastMessageSanitizationFilter,
|
||||
|
||||
Filter::EmojiFilter,
|
||||
Filter::ColorFilter,
|
||||
Filter::AutolinkFilter,
|
||||
|
|
|
@ -12,14 +12,11 @@ module Banzai
|
|||
def self.filters
|
||||
@filters ||= FilterArray[
|
||||
Filter::PlantumlFilter,
|
||||
|
||||
# Must always be before the SanitizationFilter to prevent XSS attacks
|
||||
Filter::SpacedLinkFilter,
|
||||
|
||||
Filter::SanitizationFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
Filter::SyntaxHighlightFilter,
|
||||
|
||||
Filter::MathFilter,
|
||||
Filter::ColorFilter,
|
||||
Filter::MermaidFilter,
|
||||
|
@ -34,13 +31,10 @@ module Banzai
|
|||
Filter::ExternalLinkFilter,
|
||||
Filter::SuggestionFilter,
|
||||
Filter::FootnoteFilter,
|
||||
|
||||
*reference_filters,
|
||||
|
||||
Filter::EmojiFilter,
|
||||
Filter::TaskListFilter,
|
||||
Filter::InlineDiffFilter,
|
||||
|
||||
Filter::SetDirectionFilter
|
||||
]
|
||||
end
|
||||
|
|
|
@ -8,11 +8,9 @@ module Banzai
|
|||
Filter::HtmlEntityFilter,
|
||||
Filter::SanitizationFilter,
|
||||
Filter::AssetProxyFilter,
|
||||
|
||||
Filter::EmojiFilter,
|
||||
Filter::AutolinkFilter,
|
||||
Filter::ExternalLinkFilter,
|
||||
|
||||
*reference_filters
|
||||
]
|
||||
end
|
||||
|
|
|
@ -65,7 +65,15 @@ module Gitlab
|
|||
raise Gitlab::Auth::MissingPersonalAccessTokenError
|
||||
end
|
||||
|
||||
def find_with_user_password(login, password)
|
||||
# Find and return a user if the provided password is valid for various
|
||||
# authenticators (OAuth, LDAP, Local Database).
|
||||
#
|
||||
# Specify `increment_failed_attempts: true` to increment Devise `failed_attempts`.
|
||||
# CAUTION: Avoid incrementing failed attempts when authentication falls through
|
||||
# different mechanisms, as in `.find_for_git_client`. This may lead to
|
||||
# unwanted access locks when the value provided for `password` was actually
|
||||
# a PAT, deploy token, etc.
|
||||
def find_with_user_password(login, password, increment_failed_attempts: false)
|
||||
# Avoid resource intensive checks if login credentials are not provided
|
||||
return unless login.present? && password.present?
|
||||
|
||||
|
@ -96,10 +104,14 @@ module Gitlab
|
|||
authenticators.compact!
|
||||
|
||||
# return found user that was authenticated first for given login credentials
|
||||
authenticators.find do |auth|
|
||||
authenticated_user = authenticators.find do |auth|
|
||||
authenticated_user = auth.login(login, password)
|
||||
break authenticated_user if authenticated_user
|
||||
end
|
||||
|
||||
user_auth_attempt!(user, success: !!authenticated_user) if increment_failed_attempts
|
||||
|
||||
authenticated_user
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -222,6 +234,8 @@ module Gitlab
|
|||
|
||||
# Registry access (with jwt) does not have access to project
|
||||
return if project && !token.has_access_to?(project)
|
||||
# When repository is disabled, no resources are accessible via Deploy Token
|
||||
return if project&.repository_access_level == ::ProjectFeature::DISABLED
|
||||
|
||||
scopes = abilities_for_scopes(token.scopes)
|
||||
|
||||
|
@ -355,6 +369,13 @@ module Gitlab
|
|||
def find_build_by_token(token)
|
||||
::Ci::Build.running.find_by_token(token)
|
||||
end
|
||||
|
||||
def user_auth_attempt!(user, success:)
|
||||
return unless user && Gitlab::Database.read_write?
|
||||
return user.unlock_access! if success
|
||||
|
||||
user.increment_failed_attempts!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -69,9 +69,7 @@ module Gitlab
|
|||
current_request.env[JOB_TOKEN_HEADER].presence
|
||||
return unless token
|
||||
|
||||
job = ::Ci::Build.find_by_token(token)
|
||||
raise UnauthorizedError unless job
|
||||
|
||||
job = find_valid_running_job_by_token!(token)
|
||||
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
job.user
|
||||
|
@ -84,9 +82,7 @@ module Gitlab
|
|||
return unless login.present? && password.present?
|
||||
return unless ::Gitlab::Auth::CI_JOB_USER == login
|
||||
|
||||
job = ::Ci::Build.find_by_token(password)
|
||||
raise UnauthorizedError unless job
|
||||
|
||||
job = find_valid_running_job_by_token!(password)
|
||||
job.user
|
||||
end
|
||||
|
||||
|
@ -179,7 +175,7 @@ module Gitlab
|
|||
token = parsed_oauth_token
|
||||
return unless token
|
||||
|
||||
job = ::Ci::Build.find_by_token(token)
|
||||
job = ::Ci::AuthJobFinder.new(token: token).execute
|
||||
return unless job
|
||||
|
||||
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
@ -304,6 +300,12 @@ module Gitlab
|
|||
def blob_request?
|
||||
current_request.path.include?('/raw/')
|
||||
end
|
||||
|
||||
def find_valid_running_job_by_token!(token)
|
||||
::Ci::AuthJobFinder.new(token: token).execute.tap do |job|
|
||||
raise UnauthorizedError unless job
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -39,7 +39,7 @@ module Gitlab
|
|||
|
||||
def user_map
|
||||
@user_map ||= begin
|
||||
user_map = Hash.new
|
||||
user_map = {}
|
||||
import_data = project.import_data.try(:data)
|
||||
stored_user_map = import_data['user_map'] if import_data
|
||||
user_map.update(stored_user_map) if stored_user_map
|
||||
|
|
|
@ -37,7 +37,7 @@ module Gitlab
|
|||
@byte_count = 0
|
||||
@overflow = false
|
||||
@empty = true
|
||||
@array = Array.new
|
||||
@array = []
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
|
|
|
@ -98,6 +98,13 @@ module Gitlab
|
|||
Guest.can?(download_ability, container)
|
||||
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?
|
||||
authentication_abilities.include?(:download_code) &&
|
||||
user_access.can_do_action?(download_ability)
|
||||
|
@ -257,7 +264,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def check_download_access!
|
||||
passed = deploy_key? ||
|
||||
passed = deploy_key_can_download_code? ||
|
||||
deploy_token? ||
|
||||
user_can_download_code? ||
|
||||
build_can_download_code? ||
|
||||
|
|
|
@ -450,7 +450,7 @@ module Gitlab
|
|||
|
||||
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
|
||||
Gitlab::SafeRequestStore[:stack_counter][stack_string] = count + 1
|
||||
|
|
|
@ -6,11 +6,6 @@ module Gitlab
|
|||
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
|
||||
|
||||
def conan_file_name_regex
|
||||
@conan_file_name_regex ||=
|
||||
%r{\A#{(CONAN_RECIPE_FILES + CONAN_PACKAGE_FILES).join("|")}\z}.freeze
|
||||
end
|
||||
|
||||
def conan_package_reference_regex
|
||||
@conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze
|
||||
end
|
||||
|
|
|
@ -2873,6 +2873,9 @@ msgstr ""
|
|||
msgid "An error occurred while validating username"
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred. Please sign in again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -3310,7 +3313,7 @@ msgstr ""
|
|||
msgid "Are you sure? Removing this GPG key does not affect already signed commits."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure? The device will be signed out of GitLab."
|
||||
msgid "Are you sure? The device will be signed out of GitLab and all remember me tokens revoked."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure? This will invalidate your registered applications and U2F devices."
|
||||
|
@ -17212,6 +17215,9 @@ msgstr ""
|
|||
msgid "Only project members will be imported. Group members will be skipped."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -19135,6 +19141,9 @@ msgstr ""
|
|||
msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Project was not found or you do not have permission to add this project to Security Dashboards."
|
||||
msgstr ""
|
||||
|
||||
msgid "Project: %{name}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -24689,6 +24698,9 @@ msgstr ""
|
|||
msgid "The project can be accessed without any authentication."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.161.0",
|
||||
"@gitlab/ui": "20.16.0",
|
||||
"@gitlab/ui": "20.17.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-1",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
|
|
|
@ -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
|
||||
|
||||
Dir[::File.join(__dir__, "support/helpers/*.rb")].each { |f| require f }
|
||||
Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].each { |f| require f }
|
||||
Dir[::File.join(__dir__, "support/shared_examples/*.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")].sort.each { |f| require f }
|
||||
Dir[::File.join(__dir__, "support/shared_examples/*.rb")].sort.each { |f| require f }
|
||||
|
||||
RSpec.configure do |config|
|
||||
QA::Specs::Helpers::Quarantine.configure_rspec
|
||||
|
|
|
@ -40,7 +40,7 @@ RSpec.describe Admin::ApplicationsController do
|
|||
|
||||
describe 'POST #create' do
|
||||
it 'creates the application' do
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false)
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
|
||||
|
||||
expect do
|
||||
post :create, params: { doorkeeper_application: create_params }
|
||||
|
@ -63,7 +63,7 @@ RSpec.describe Admin::ApplicationsController do
|
|||
|
||||
context 'when the params are for a confidential application' do
|
||||
it 'creates a confidential application' do
|
||||
create_params = attributes_for(:application, confidential: true)
|
||||
create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
|
||||
|
||||
expect do
|
||||
post :create, params: { doorkeeper_application: create_params }
|
||||
|
@ -75,6 +75,18 @@ RSpec.describe Admin::ApplicationsController do
|
|||
expect(application).to have_attributes(create_params.except(:uid, :owner_type))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when scopes are not present' do
|
||||
it 'renders the application form on errors' do
|
||||
create_params = attributes_for(:application, trusted: true, confidential: false)
|
||||
|
||||
expect do
|
||||
post :create, params: { doorkeeper_application: create_params }
|
||||
end.not_to change { Doorkeeper::Application.count }
|
||||
|
||||
expect(response).to render_template :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH #update' do
|
||||
|
|
|
@ -99,7 +99,9 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
describe 'GET #new' do
|
||||
def get_new(provider: 'gcp')
|
||||
let(:user) { admin }
|
||||
|
||||
def go(provider: 'gcp')
|
||||
get :new, params: { provider: provider }
|
||||
end
|
||||
|
||||
|
@ -112,7 +114,7 @@ RSpec.describe Admin::ClustersController do
|
|||
|
||||
context 'when selected provider is gke and no valid gcp token exists' do
|
||||
it 'redirects to gcp authorize_url' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(response).to redirect_to(assigns(:authorize_url))
|
||||
end
|
||||
|
@ -125,7 +127,7 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
it 'does not have authorize_url' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(assigns(:authorize_url)).to be_nil
|
||||
end
|
||||
|
@ -137,7 +139,7 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
it 'has new object' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
|
@ -158,16 +160,18 @@ RSpec.describe Admin::ClustersController do
|
|||
|
||||
describe 'functionality for existing cluster' do
|
||||
it 'has new object' do
|
||||
get_new
|
||||
go
|
||||
|
||||
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'GET new cluster shared examples'
|
||||
|
||||
describe 'security' do
|
||||
it { expect { get_new }.to be_allowed_for(:admin) }
|
||||
it { expect { get_new }.to be_denied_for(:user) }
|
||||
it { expect { get_new }.to be_denied_for(:external) }
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -424,14 +428,13 @@ RSpec.describe Admin::ClustersController do
|
|||
end
|
||||
|
||||
describe 'POST authorize AWS role for EKS cluster' do
|
||||
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
|
||||
let(:role_external_id) { '12345' }
|
||||
let!(:role) { create(:aws_role, user: admin) }
|
||||
|
||||
let(:role_arn) { 'arn:new-role' }
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
role_arn: role_arn,
|
||||
role_external_id: role_external_id
|
||||
role_arn: role_arn
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -445,28 +448,32 @@ RSpec.describe Admin::ClustersController do
|
|||
.and_return(double(execute: double))
|
||||
end
|
||||
|
||||
it 'creates an Aws::Role record' do
|
||||
expect { go }.to change { Aws::Role.count }
|
||||
it 'updates the associated role with the supplied ARN' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
role = Aws::Role.last
|
||||
expect(role.user).to eq admin
|
||||
expect(role.role_arn).to eq role_arn
|
||||
expect(role.role_external_id).to eq role_external_id
|
||||
expect(role.reload.role_arn).to eq(role_arn)
|
||||
end
|
||||
|
||||
context 'role cannot be created' do
|
||||
context 'supplied role is invalid' do
|
||||
let(:role_arn) { 'invalid-role' }
|
||||
|
||||
it 'does not create a record' do
|
||||
expect { go }.not_to change { Aws::Role.count }
|
||||
it 'does not update the associated role' do
|
||||
expect { go }.not_to change { role.role_arn }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
before do
|
||||
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
|
||||
response = double(status: :ok, body: double)
|
||||
|
||||
allow(service).to receive(:execute).and_return(response)
|
||||
end
|
||||
end
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
|
|
|
@ -229,6 +229,7 @@ RSpec.describe ApplicationController do
|
|||
|
||||
it 'does not redirect if 2FA is not required' do
|
||||
allow(controller).to receive(:two_factor_authentication_required?).and_return(false)
|
||||
allow(controller).to receive(:current_user).and_return(create(:user))
|
||||
|
||||
expect(controller).not_to receive(:redirect_to)
|
||||
|
||||
|
@ -346,13 +347,17 @@ RSpec.describe ApplicationController do
|
|||
let(:user) { create :user, otp_grace_period_started_at: 2.hours.ago }
|
||||
|
||||
it 'returns true if the grace period has expired' do
|
||||
allow(controller).to receive(:two_factor_grace_period).and_return(1)
|
||||
allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier|
|
||||
allow(verifier).to receive(:two_factor_grace_period).and_return(2)
|
||||
end
|
||||
|
||||
expect(subject).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if the grace period is still active' do
|
||||
allow(controller).to receive(:two_factor_grace_period).and_return(3)
|
||||
allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier|
|
||||
allow(verifier).to receive(:two_factor_grace_period).and_return(3)
|
||||
end
|
||||
|
||||
expect(subject).to be_falsey
|
||||
end
|
||||
|
|
|
@ -180,6 +180,8 @@ RSpec.describe Groups::ClustersController do
|
|||
end
|
||||
end
|
||||
|
||||
include_examples 'GET new cluster shared examples'
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(group) }
|
||||
|
@ -493,14 +495,13 @@ RSpec.describe Groups::ClustersController do
|
|||
end
|
||||
|
||||
describe 'POST authorize AWS role for EKS cluster' do
|
||||
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
|
||||
let(:role_external_id) { '12345' }
|
||||
let!(:role) { create(:aws_role, user: user) }
|
||||
|
||||
let(:role_arn) { 'arn:new-role' }
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
role_arn: role_arn,
|
||||
role_external_id: role_external_id
|
||||
role_arn: role_arn
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -514,28 +515,32 @@ RSpec.describe Groups::ClustersController do
|
|||
.and_return(double(execute: double))
|
||||
end
|
||||
|
||||
it 'creates an Aws::Role record' do
|
||||
expect { go }.to change { Aws::Role.count }
|
||||
it 'updates the associated role with the supplied ARN' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
role = Aws::Role.last
|
||||
expect(role.user).to eq user
|
||||
expect(role.role_arn).to eq role_arn
|
||||
expect(role.role_external_id).to eq role_external_id
|
||||
expect(role.reload.role_arn).to eq(role_arn)
|
||||
end
|
||||
|
||||
context 'role cannot be created' do
|
||||
context 'supplied role is invalid' do
|
||||
let(:role_arn) { 'invalid-role' }
|
||||
|
||||
it 'does not create a record' do
|
||||
expect { go }.not_to change { Aws::Role.count }
|
||||
it 'does not update the associated role' do
|
||||
expect { go }.not_to change { role.role_arn }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
before do
|
||||
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
|
||||
response = double(status: :ok, body: double)
|
||||
|
||||
allow(service).to receive(:execute).and_return(response)
|
||||
end
|
||||
end
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(group) }
|
||||
it { expect { go }.to be_allowed_for(:maintainer).of(group) }
|
||||
|
|
|
@ -123,7 +123,8 @@ RSpec.describe Oauth::ApplicationsController do
|
|||
invalid_uri_params = {
|
||||
doorkeeper_application: {
|
||||
name: 'foo',
|
||||
redirect_uri: 'javascript://alert()'
|
||||
redirect_uri: 'javascript://alert()',
|
||||
scopes: ['api']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +134,23 @@ RSpec.describe Oauth::ApplicationsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when scopes are not present' do
|
||||
render_views
|
||||
|
||||
it 'shows an error for blank scopes' do
|
||||
invalid_uri_params = {
|
||||
doorkeeper_application: {
|
||||
name: 'foo',
|
||||
redirect_uri: 'http://example.org'
|
||||
}
|
||||
}
|
||||
|
||||
post :create, params: invalid_uri_params
|
||||
|
||||
expect(response.body).to include 'Scopes can't be blank'
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'redirects to login page when the user is not signed in'
|
||||
it_behaves_like 'redirects to 2fa setup page when the user requires it'
|
||||
end
|
||||
|
@ -172,7 +190,8 @@ RSpec.describe Oauth::ApplicationsController do
|
|||
{
|
||||
doorkeeper_application: {
|
||||
name: 'foo',
|
||||
redirect_uri: 'http://example.org'
|
||||
redirect_uri: 'http://example.org',
|
||||
scopes: ['api']
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -40,6 +40,22 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when sign in is not valid' do
|
||||
let(:provider) { :github }
|
||||
let(:extern_uid) { 'my-uid' }
|
||||
|
||||
it 'renders omniauth error page' do
|
||||
allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance|
|
||||
allow(instance).to receive(:valid_sign_in?).and_return(false)
|
||||
end
|
||||
|
||||
post provider
|
||||
|
||||
expect(response).to render_template("errors/omniauth_error")
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is on the last sign in attempt' do
|
||||
let(:extern_uid) { 'my-uid' }
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Profiles::ActiveSessionsController do
|
||||
describe 'DELETE destroy' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'invalidates all remember user tokens' do
|
||||
ActiveSession.set(user, request)
|
||||
session_id = request.session.id.public_id
|
||||
user.remember_me!
|
||||
|
||||
delete :destroy, params: { id: session_id }
|
||||
|
||||
expect(user.reload.remember_created_at).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,10 +14,9 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
it 'generates otp_secret for user' do
|
||||
expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once
|
||||
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once
|
||||
|
||||
get :show
|
||||
get :show # Second hit shouldn't re-generate it
|
||||
end
|
||||
|
||||
it 'assigns qr_code' do
|
||||
|
@ -27,6 +26,14 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
get :show
|
||||
expect(assigns[:qr_code]).to eq code
|
||||
end
|
||||
|
||||
it 'generates a unique otp_secret every time the page is loaded' do
|
||||
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.twice
|
||||
|
||||
2.times do
|
||||
get :show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
|
@ -57,6 +64,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
expect(assigns[:codes]).to match_array %w(a b c)
|
||||
end
|
||||
|
||||
it 'calls to delete other sessions' do
|
||||
expect(ActiveSession).to receive(:destroy_all_but_current)
|
||||
|
||||
go
|
||||
end
|
||||
|
||||
it 'renders create' do
|
||||
go
|
||||
expect(response).to render_template(:create)
|
||||
|
|
|
@ -183,6 +183,8 @@ RSpec.describe Projects::ClustersController do
|
|||
end
|
||||
end
|
||||
|
||||
include_examples 'GET new cluster shared examples'
|
||||
|
||||
describe 'security' do
|
||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||
expect { go }.to be_allowed_for(:admin)
|
||||
|
@ -521,14 +523,13 @@ RSpec.describe Projects::ClustersController do
|
|||
end
|
||||
|
||||
describe 'POST authorize AWS role for EKS cluster' do
|
||||
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
|
||||
let(:role_external_id) { '12345' }
|
||||
let!(:role) { create(:aws_role, user: user) }
|
||||
|
||||
let(:role_arn) { 'arn:new-role' }
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
role_arn: role_arn,
|
||||
role_external_id: role_external_id
|
||||
role_arn: role_arn
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -542,28 +543,32 @@ RSpec.describe Projects::ClustersController do
|
|||
.and_return(double(execute: double))
|
||||
end
|
||||
|
||||
it 'creates an Aws::Role record' do
|
||||
expect { go }.to change { Aws::Role.count }
|
||||
it 'updates the associated role with the supplied ARN' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
role = Aws::Role.last
|
||||
expect(role.user).to eq user
|
||||
expect(role.role_arn).to eq role_arn
|
||||
expect(role.role_external_id).to eq role_external_id
|
||||
expect(role.reload.role_arn).to eq(role_arn)
|
||||
end
|
||||
|
||||
context 'role cannot be created' do
|
||||
context 'supplied role is invalid' do
|
||||
let(:role_arn) { 'invalid-role' }
|
||||
|
||||
it 'does not create a record' do
|
||||
expect { go }.not_to change { Aws::Role.count }
|
||||
it 'does not update the associated role' do
|
||||
expect { go }.not_to change { role.role_arn }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
before do
|
||||
allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service|
|
||||
response = double(status: :ok, body: double)
|
||||
|
||||
allow(service).to receive(:execute).and_return(response)
|
||||
end
|
||||
end
|
||||
|
||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||
expect { go }.to be_allowed_for(:admin)
|
||||
end
|
||||
|
|
|
@ -149,9 +149,15 @@ RSpec.describe SearchController do
|
|||
expect(assigns[:search_objects].first).to eq note
|
||||
end
|
||||
|
||||
it_behaves_like 'tracking unique hll events', :show do
|
||||
let(:request_params) { { scope: 'projects', search: 'term' } }
|
||||
let(:target_id) { 'i_search_total' }
|
||||
context 'unique users tracking' do
|
||||
before do
|
||||
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
|
||||
|
||||
context 'on restricted projects' do
|
||||
|
|
|
@ -261,8 +261,8 @@ RSpec.describe SessionsController do
|
|||
context 'when using two-factor authentication via OTP' do
|
||||
let(:user) { create(:user, :two_factor) }
|
||||
|
||||
def authenticate_2fa(user_params)
|
||||
post(:create, params: { user: user_params }, session: { otp_user_id: user.id })
|
||||
def authenticate_2fa(user_params, otp_user_id: user.id)
|
||||
post(:create, params: { user: user_params }, session: { otp_user_id: otp_user_id })
|
||||
end
|
||||
|
||||
context 'remember_me field' do
|
||||
|
@ -299,8 +299,22 @@ RSpec.describe SessionsController do
|
|||
end
|
||||
end
|
||||
|
||||
# See issue gitlab-org/gitlab#20302.
|
||||
context 'when otp_user_id is stale' do
|
||||
render_views
|
||||
|
||||
it 'favors login over otp_user_id when password is present and does not authenticate the user' do
|
||||
authenticate_2fa(
|
||||
{ login: 'random_username', password: user.password },
|
||||
otp_user_id: user.id
|
||||
)
|
||||
|
||||
expect(response).to set_flash.now[:alert].to /Invalid Login or password/
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# See #14900 issue
|
||||
# See issue gitlab-org/gitlab-foss#14900
|
||||
#
|
||||
context 'when authenticating with login and OTP of another user' do
|
||||
context 'when another user has 2FA enabled' do
|
||||
|
@ -386,18 +400,6 @@ RSpec.describe SessionsController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when another user does not have 2FA enabled' do
|
||||
let(:another_user) { create(:user) }
|
||||
|
||||
it 'does not leak that 2FA is disabled for another user' do
|
||||
authenticate_2fa(login: another_user.username,
|
||||
otp_attempt: 'invalid')
|
||||
|
||||
expect(response).to set_flash.now[:alert]
|
||||
.to /Invalid two-factor code/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe 'admin manage applications' do
|
|||
sign_in(create(:admin))
|
||||
end
|
||||
|
||||
it do
|
||||
it 'creates new oauth application' do
|
||||
visit admin_applications_path
|
||||
|
||||
click_on 'New application'
|
||||
|
@ -16,6 +16,7 @@ RSpec.describe 'admin manage applications' do
|
|||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
check :doorkeeper_application_trusted
|
||||
check :doorkeeper_application_scopes_read_user
|
||||
click_on 'Submit'
|
||||
expect(page).to have_content('Application: test')
|
||||
expect(page).to have_content('Application ID')
|
||||
|
@ -43,4 +44,19 @@ RSpec.describe 'admin manage applications' do
|
|||
end
|
||||
expect(page.find('.oauth-applications')).not_to have_content('test_changed')
|
||||
end
|
||||
|
||||
context 'when scopes are blank' do
|
||||
it 'returns an error' do
|
||||
visit admin_applications_path
|
||||
|
||||
click_on 'New application'
|
||||
expect(page).to have_content('New application')
|
||||
|
||||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
click_on 'Submit'
|
||||
|
||||
expect(page).to have_content("Scopes can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -109,6 +109,14 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
|
|||
find('.input-token .filtered-search').native.send_key(:backspace)
|
||||
expect(page).to have_selector('.js-visual-token', count: 1)
|
||||
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
|
||||
|
|
|
@ -30,13 +30,11 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'works', :aggregate_failures do
|
||||
verify(
|
||||
'nesting',
|
||||
|
||||
'> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**'
|
||||
)
|
||||
|
||||
verify(
|
||||
'a real world example from the gitlab-ce README',
|
||||
|
||||
<<~GFM
|
||||
# GitLab
|
||||
|
||||
|
@ -103,19 +101,16 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'InlineDiffFilter',
|
||||
|
||||
'{-Deleted text-}',
|
||||
'{+Added text+}'
|
||||
)
|
||||
|
||||
verify(
|
||||
'TaskListFilter',
|
||||
|
||||
<<~GFM,
|
||||
* [ ] Unchecked task
|
||||
* [x] Checked task
|
||||
GFM
|
||||
|
||||
<<~GFM
|
||||
1. [ ] Unchecked ordered task
|
||||
1. [x] Checked ordered task
|
||||
|
@ -124,7 +119,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'ReferenceFilter',
|
||||
|
||||
# issue reference
|
||||
@feat.issue.to_reference,
|
||||
# full issue reference
|
||||
|
@ -141,13 +135,11 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'AutolinkFilter',
|
||||
|
||||
'https://example.com'
|
||||
)
|
||||
|
||||
verify(
|
||||
'TableOfContentsFilter',
|
||||
|
||||
<<~GFM,
|
||||
[[_TOC_]]
|
||||
|
||||
|
@ -155,64 +147,53 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
## Heading 2
|
||||
GFM
|
||||
|
||||
pipeline: :wiki,
|
||||
wiki: @project.wiki
|
||||
)
|
||||
|
||||
verify(
|
||||
'EmojiFilter',
|
||||
|
||||
':thumbsup:'
|
||||
)
|
||||
|
||||
verify(
|
||||
'ImageLinkFilter',
|
||||
|
||||
'![Image](https://example.com/image.png)'
|
||||
)
|
||||
|
||||
verify_media_with_partial_path(
|
||||
'[test.txt](/uploads/a123/image.txt)',
|
||||
|
||||
project_media_uri(@project, '/uploads/a123/image.txt')
|
||||
)
|
||||
|
||||
verify_media_with_partial_path(
|
||||
'![Image](/uploads/a123/image.png)',
|
||||
|
||||
project_media_uri(@project, '/uploads/a123/image.png')
|
||||
)
|
||||
|
||||
verify(
|
||||
'VideoLinkFilter',
|
||||
|
||||
'![Video](https://example.com/video.mp4)'
|
||||
)
|
||||
|
||||
verify_media_with_partial_path(
|
||||
'![Video](/uploads/a123/video.mp4)',
|
||||
|
||||
project_media_uri(@project, '/uploads/a123/video.mp4')
|
||||
)
|
||||
|
||||
verify(
|
||||
'AudioLinkFilter',
|
||||
|
||||
'![Audio](https://example.com/audio.wav)'
|
||||
)
|
||||
|
||||
verify_media_with_partial_path(
|
||||
'![Audio](/uploads/a123/audio.wav)',
|
||||
|
||||
project_media_uri(@project, '/uploads/a123/audio.wav')
|
||||
)
|
||||
|
||||
verify(
|
||||
'MathFilter: math as converted from GFM to HTML',
|
||||
|
||||
'$`c = \pm\sqrt{a^2 + b^2}`$',
|
||||
|
||||
# math block
|
||||
<<~GFM
|
||||
```math
|
||||
|
@ -334,7 +315,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'MermaidFilter: mermaid as converted from GFM to HTML',
|
||||
|
||||
<<~GFM
|
||||
```mermaid
|
||||
graph TD;
|
||||
|
@ -429,7 +409,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'SuggestionFilter: suggestion as converted from GFM to HTML',
|
||||
|
||||
<<~GFM
|
||||
```suggestion
|
||||
New
|
||||
|
@ -491,7 +470,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'SanitizationFilter',
|
||||
|
||||
<<~GFM
|
||||
<sub>sub</sub>
|
||||
|
||||
|
@ -527,13 +505,11 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'SanitizationFilter',
|
||||
|
||||
<<~GFM,
|
||||
```
|
||||
Plain text
|
||||
```
|
||||
GFM
|
||||
|
||||
<<~GFM,
|
||||
```ruby
|
||||
def foo
|
||||
|
@ -541,7 +517,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
end
|
||||
```
|
||||
GFM
|
||||
|
||||
<<~GFM
|
||||
Foo
|
||||
|
||||
|
@ -553,27 +528,19 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
verify(
|
||||
'MarkdownFilter',
|
||||
|
||||
"Line with two spaces at the end \nto insert a linebreak",
|
||||
|
||||
'`code`',
|
||||
'`` code with ` ticks ``',
|
||||
|
||||
'> Quote',
|
||||
|
||||
# multiline quote
|
||||
<<~GFM,
|
||||
> Multiline Quote
|
||||
>
|
||||
> With multiple paragraphs
|
||||
GFM
|
||||
|
||||
'![Image](https://example.com/image.png)',
|
||||
|
||||
'# Heading with no anchor link',
|
||||
|
||||
'[Link](https://example.com)',
|
||||
|
||||
<<~GFM,
|
||||
* List item
|
||||
* List item 2
|
||||
|
@ -598,7 +565,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
> Blockquote
|
||||
GFM
|
||||
|
||||
<<~GFM,
|
||||
1. Ordered list item
|
||||
1. Ordered list item 2
|
||||
|
@ -623,22 +589,16 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
|
||||
---
|
||||
GFM
|
||||
|
||||
'# Heading',
|
||||
'## Heading',
|
||||
'### Heading',
|
||||
'#### Heading',
|
||||
'##### Heading',
|
||||
'###### Heading',
|
||||
|
||||
'**Bold**',
|
||||
|
||||
'*Italics*',
|
||||
|
||||
'~~Strikethrough~~',
|
||||
|
||||
'---',
|
||||
|
||||
# table
|
||||
<<~GFM,
|
||||
| Centered | Right | Left |
|
||||
|
@ -696,9 +656,7 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no',
|
||||
|
||||
'`RuntimeError`',
|
||||
|
||||
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
|
||||
)
|
||||
end
|
||||
|
@ -708,9 +666,7 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]',
|
||||
|
||||
'`raise RuntimeError, "System commands must be given as an array of strings"`',
|
||||
|
||||
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
|
||||
)
|
||||
end
|
||||
|
@ -720,14 +676,12 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as a code block' do
|
||||
verify(
|
||||
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
|
||||
|
||||
<<~GFM,
|
||||
```ruby
|
||||
raise RuntimeError, "System commands must be given as an array of strings"
|
||||
end
|
||||
```
|
||||
GFM
|
||||
|
||||
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
|
||||
)
|
||||
end
|
||||
|
@ -755,7 +709,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as a code block' do
|
||||
verify(
|
||||
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
|
||||
|
||||
<<~GFM,
|
||||
```ruby
|
||||
unless cmd.is_a?(Array)
|
||||
|
@ -763,7 +716,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
end
|
||||
```
|
||||
GFM
|
||||
|
||||
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].left-side'
|
||||
)
|
||||
end
|
||||
|
@ -773,7 +725,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as a code block' do
|
||||
verify(
|
||||
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
|
||||
|
||||
<<~GFM,
|
||||
```ruby
|
||||
unless cmd.is_a?(Array)
|
||||
|
@ -781,7 +732,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
end
|
||||
```
|
||||
GFM
|
||||
|
||||
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].right-side'
|
||||
)
|
||||
end
|
||||
|
@ -799,7 +749,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'.line[id="LC9"] .no',
|
||||
|
||||
'`RuntimeError`'
|
||||
)
|
||||
end
|
||||
|
@ -809,7 +758,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'.line[id="LC9"]',
|
||||
|
||||
'`raise RuntimeError, "System commands must be given as an array of strings"`'
|
||||
)
|
||||
end
|
||||
|
@ -819,7 +767,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as a code block' do
|
||||
verify(
|
||||
'.line[id="LC9"], .line[id="LC10"]',
|
||||
|
||||
<<~GFM
|
||||
```ruby
|
||||
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
|
||||
verify(
|
||||
'.line[id="LC27"] .nl',
|
||||
|
||||
'`"bio"`'
|
||||
)
|
||||
end
|
||||
|
@ -851,7 +797,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'.line[id="LC27"]',
|
||||
|
||||
'`"bio": null,`'
|
||||
)
|
||||
end
|
||||
|
@ -861,7 +806,6 @@ RSpec.describe 'Copy as GFM', :js do
|
|||
it 'copies as a code block with the correct language' do
|
||||
verify(
|
||||
'.line[id="LC27"], .line[id="LC28"]',
|
||||
|
||||
<<~GFM
|
||||
```json
|
||||
"bio": null,
|
||||
|
|
|
@ -15,6 +15,7 @@ RSpec.describe 'User manages applications' do
|
|||
|
||||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
check :doorkeeper_application_scopes_read_user
|
||||
click_on 'Save application'
|
||||
|
||||
expect(page).to have_content 'Application: test'
|
||||
|
@ -41,4 +42,16 @@ RSpec.describe 'User manages applications' do
|
|||
end
|
||||
expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
|
||||
end
|
||||
|
||||
context 'when scopes are blank' do
|
||||
it 'returns an error' do
|
||||
expect(page).to have_content 'Add new application'
|
||||
|
||||
fill_in :doorkeeper_application_name, with: 'test'
|
||||
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
|
||||
click_on 'Save application'
|
||||
|
||||
expect(page).to have_content("Scopes can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -177,6 +177,14 @@ RSpec.describe 'Login' do
|
|||
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
|
||||
end
|
||||
|
||||
it 'does not allow sign-in if the user password is updated before entering a one-time code' do
|
||||
user.update!(password: 'new_password')
|
||||
|
||||
enter_code(user.current_otp)
|
||||
|
||||
expect(page).to have_content('An error occurred. Please sign in again.')
|
||||
end
|
||||
|
||||
context 'using one-time code' do
|
||||
it 'allows login with valid code' do
|
||||
expect(authentication_metrics)
|
||||
|
@ -232,7 +240,7 @@ RSpec.describe 'Login' do
|
|||
expect(codes.size).to eq 10
|
||||
|
||||
# Ensure the generated codes get saved
|
||||
user.save
|
||||
user.save(touch: false)
|
||||
end
|
||||
|
||||
context 'with valid code' do
|
||||
|
@ -290,7 +298,7 @@ RSpec.describe 'Login' do
|
|||
code = codes.sample
|
||||
expect(user.invalidate_otp_backup_code!(code)).to eq true
|
||||
|
||||
user.save!
|
||||
user.save!(touch: false)
|
||||
expect(user.reload.otp_backup_codes.size).to eq 9
|
||||
|
||||
enter_code(code)
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::AuthJobFinder do
|
||||
let_it_be(:job, reload: true) { create(:ci_build, status: :running) }
|
||||
|
||||
let(:token) { job.token }
|
||||
|
||||
subject(:finder) do
|
||||
described_class.new(token: token)
|
||||
end
|
||||
|
||||
describe '#execute!' do
|
||||
subject(:execute) { finder.execute! }
|
||||
|
||||
it { is_expected.to eq(job) }
|
||||
|
||||
it 'raises error if the job is not running' do
|
||||
job.success!
|
||||
|
||||
expect { execute }.to raise_error described_class::NotRunningJobError, 'Job is not running'
|
||||
end
|
||||
|
||||
it 'raises error if the job is erased' do
|
||||
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
|
||||
expect(job).to receive(:erased?).and_return(true)
|
||||
|
||||
expect { execute }.to raise_error described_class::ErasedJobError, 'Job has been erased!'
|
||||
end
|
||||
|
||||
it 'raises error if the the project is missing' do
|
||||
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
|
||||
expect(job).to receive(:project).and_return(nil)
|
||||
|
||||
expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!'
|
||||
end
|
||||
|
||||
it 'raises error if the the project is being removed' do
|
||||
project = double(Project)
|
||||
|
||||
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
|
||||
expect(job).to receive(:project).twice.and_return(project)
|
||||
expect(project).to receive(:pending_delete?).and_return(true)
|
||||
|
||||
expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!'
|
||||
end
|
||||
|
||||
context 'with wrong job token' do
|
||||
let(:token) { 'missing' }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:execute) { finder.execute }
|
||||
|
||||
before do
|
||||
job.success!
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
|
@ -11,8 +11,10 @@ RSpec.describe UserRecentEventsFinder do
|
|||
let!(:private_event) { create(:event, project: private_project, author: project_owner) }
|
||||
let!(:internal_event) { create(:event, project: internal_project, author: project_owner) }
|
||||
let!(:public_event) { create(:event, project: public_project, author: project_owner) }
|
||||
let(:limit) { nil }
|
||||
let(:params) { { limit: limit } }
|
||||
|
||||
subject(:finder) { described_class.new(current_user, project_owner) }
|
||||
subject(:finder) { described_class.new(current_user, project_owner, params) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'when profile is public' do
|
||||
|
@ -48,5 +50,38 @@ RSpec.describe UserRecentEventsFinder do
|
|||
expect(events).to include(event_b)
|
||||
end
|
||||
end
|
||||
|
||||
context 'limits' do
|
||||
before do
|
||||
stub_const("#{described_class}::DEFAULT_LIMIT", 1)
|
||||
stub_const("#{described_class}::MAX_LIMIT", 3)
|
||||
end
|
||||
|
||||
context 'when limit is not set' do
|
||||
it 'returns events limited to DEFAULT_LIMIT' do
|
||||
expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when limit is set' do
|
||||
let(:limit) { 2 }
|
||||
|
||||
it 'returns events limited to specified limit' do
|
||||
expect(finder.execute.size).to eq(limit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when limit is set to a number that exceeds maximum limit' do
|
||||
let(:limit) { 4 }
|
||||
|
||||
before do
|
||||
create(:event, project: public_project, author: project_owner)
|
||||
end
|
||||
|
||||
it 'returns events limited to MAX_LIMIT' do
|
||||
expect(finder.execute.size).to eq(described_class::MAX_LIMIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -212,6 +212,55 @@ RSpec.describe GitlabSchema do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.parse_gid' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
|
||||
|
||||
before do
|
||||
test_base = Class.new
|
||||
test_one = Class.new(test_base)
|
||||
test_two = Class.new(test_base)
|
||||
|
||||
stub_const('TestBase', test_base)
|
||||
stub_const('TestOne', test_one)
|
||||
stub_const('TestTwo', test_two)
|
||||
end
|
||||
|
||||
it 'parses the gid' do
|
||||
gid = described_class.parse_gid(global_id)
|
||||
|
||||
expect(gid.model_id).to eq '2147483647'
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
context 'when gid is malformed' do
|
||||
let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { described_class.parse_gid(global_id) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab ID.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using expected_type' do
|
||||
it 'accepts a single type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestOne)
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'accepts an ancestor type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestBase)
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'rejects an unknown type' do
|
||||
expect { described_class.parse_gid(global_id, expected_type: TestTwo) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestTwo.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def field_instrumenters
|
||||
described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
|
||||
end
|
||||
|
|
|
@ -24,14 +24,12 @@ RSpec.describe Mutations::Boards::Lists::Create do
|
|||
describe '#ready?' do
|
||||
it 'raises an error if required arguments are missing' do
|
||||
expect { mutation.ready?({ board_id: 'some id' }) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError,
|
||||
'one and only one of backlog or labelId is required')
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
|
||||
end
|
||||
|
||||
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' }) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError,
|
||||
'one and only one of backlog or labelId is required')
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
|
||||
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.position).to eq 0
|
||||
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
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ RSpec.describe MergeRequestsHelper do
|
|||
|
||||
describe '#tab_link_for' do
|
||||
let(:merge_request) { create(:merge_request, :simple) }
|
||||
let(:options) { Hash.new }
|
||||
let(:options) { {} }
|
||||
|
||||
subject { tab_link_for(merge_request, :show, options) { 'Discussion' } }
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
|
|||
|
||||
describe '#find_job_from_http_basic_auth' do
|
||||
let_it_be(:user) { personal_access_token.user }
|
||||
let(:job) { create(:ci_build, user: user) }
|
||||
let(:job) { create(:ci_build, user: user, status: :running) }
|
||||
let(:password) { job.token }
|
||||
|
||||
subject { helper.find_job_from_http_basic_auth }
|
||||
|
@ -60,6 +60,16 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
describe '#find_deploy_token_from_http_basic_auth' do
|
||||
|
|
|
@ -37,11 +37,29 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
|
||||
it "return user if token is valid" do
|
||||
set_token(job.token)
|
||||
context 'with a running job' do
|
||||
before do
|
||||
job.update!(status: :running)
|
||||
end
|
||||
|
||||
expect(subject).to eq(user)
|
||||
expect(@current_authenticated_job).to eq job
|
||||
it 'return user if token is valid' do
|
||||
set_token(job.token)
|
||||
|
||||
expect(subject).to eq(user)
|
||||
expect(@current_authenticated_job).to eq job
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a job that is not running' do
|
||||
before do
|
||||
job.update!(status: :failed)
|
||||
end
|
||||
|
||||
it 'returns an Unauthorized exception' do
|
||||
set_token(job.token)
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -557,7 +575,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
context 'with CI username' do
|
||||
let(:username) { ::Gitlab::Auth::CI_JOB_USER }
|
||||
let(:user) { create(:user) }
|
||||
let(:build) { create(:ci_build, user: user) }
|
||||
let(:build) { create(:ci_build, user: user, status: :running) }
|
||||
|
||||
it 'returns nil without password' do
|
||||
set_basic_auth_header(username, nil)
|
||||
|
@ -576,6 +594,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
|
||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
|
||||
it 'returns exception if the job is not running' do
|
||||
set_basic_auth_header(username, build.token)
|
||||
build.success!
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -586,7 +611,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
|
||||
context 'with a job token' do
|
||||
let(:route_authentication_setting) { { job_token_allowed: true } }
|
||||
let(:job) { create(:ci_build, user: user) }
|
||||
let(:job) { create(:ci_build, user: user, status: :running) }
|
||||
|
||||
before do
|
||||
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
|
||||
|
@ -641,7 +666,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
end
|
||||
|
||||
describe '#find_user_from_job_token' do
|
||||
let(:job) { create(:ci_build, user: user) }
|
||||
let(:job) { create(:ci_build, user: user, status: :running) }
|
||||
let(:route_authentication_setting) { { job_token_allowed: true } }
|
||||
|
||||
subject { find_user_from_job_token }
|
||||
|
@ -666,6 +691,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do
|
|||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
|
||||
it 'returns exception if the job is not running' do
|
||||
set_header(described_class::JOB_TOKEN_HEADER, job.token)
|
||||
job.success!
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
|
||||
context 'when route is not allowed to be authenticated' do
|
||||
let(:route_authentication_setting) { { job_token_allowed: false } }
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
|||
|
||||
describe '#find_user_from_job_token' do
|
||||
let!(:user) { build(:user) }
|
||||
let!(:job) { build(:ci_build, user: user) }
|
||||
let!(:job) { build(:ci_build, user: user, status: :running) }
|
||||
|
||||
before do
|
||||
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token'
|
||||
|
@ -97,13 +97,18 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
|||
context 'with API requests' do
|
||||
before do
|
||||
env['SCRIPT_NAME'] = '/api/endpoint'
|
||||
expect(::Ci::Build).to receive(:find_by_token).with('token').and_return(job)
|
||||
end
|
||||
|
||||
it 'tries to find the user' do
|
||||
expect(::Ci::Build).to receive(:find_by_token).and_return(job)
|
||||
|
||||
expect(subject.find_sessionless_user([:api])).to eq user
|
||||
end
|
||||
|
||||
it 'returns nil if the job is not running' do
|
||||
job.status = :success
|
||||
|
||||
expect(subject.find_sessionless_user([:api])).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context 'without API requests' do
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue