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