Add latest changes from gitlab-org/gitlab@master

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,8 @@ module AuthenticatesWithTwoFactor
return handle_locked_user(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id
session[:user_updated_at] = user.updated_at
setup_u2f_authentication(user)
render 'devise/sessions/two_factor'
end
@ -39,6 +41,7 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor
user = self.resource = find_user
return handle_locked_user(user) unless user.can?(:log_in)
return handle_changed_user(user) if user_changed?(user)
if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
@ -63,12 +66,14 @@ module AuthenticatesWithTwoFactor
def clear_two_factor_attempt!
session.delete(:otp_user_id)
session.delete(:user_updated_at)
session.delete(:challenge)
end
def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user)
# Remove any lingering user data from login
session.delete(:otp_user_id)
clear_two_factor_attempt!
remember_me(user) if user_params[:remember_me] == '1'
user.save!
@ -85,8 +90,7 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor_via_u2f(user)
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
# Remove any lingering user data from login
session.delete(:otp_user_id)
session.delete(:challenge)
clear_two_factor_attempt!
remember_me(user) if user_params[:remember_me] == '1'
sign_in(user, message: :two_factor_authenticated, event: :authentication)
@ -113,4 +117,18 @@ module AuthenticatesWithTwoFactor
end
end
# rubocop: enable CodeReuse/ActiveRecord
def handle_changed_user(user)
clear_two_factor_attempt!
redirect_to new_user_session_path, alert: _('An error occurred. Please sign in again.')
end
# If user has been updated since we validated the password,
# the password might have changed.
def user_changed?(user)
return false unless session[:user_updated_at]
user.updated_at != session[:user_updated_at]
end
end

View File

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

View File

@ -50,12 +50,6 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_unverified_saml_initiation
end
def omniauth_error
@provider = params[:provider]
@error = params[:error]
render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
end
def cas3
ticket = params['ticket']
if ticket
@ -205,9 +199,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def fail_login(user)
log_failed_login(user.username, oauth['provider'])
error_message = user.errors.full_messages.to_sentence
@provider = Gitlab::Auth::OAuth::Provider.label_for(params[:action])
@error = user.errors.full_messages.to_sentence
redirect_to omniauth_error_path(oauth['provider'], error: error_message)
render 'errors/omniauth_error', layout: "oauth_error", status: :unprocessable_entity
end
def fail_auth0_login

View File

@ -7,6 +7,7 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController
def destroy
ActiveSession.destroy_with_public_id(current_user, params[:id])
current_user.forget_me!
respond_to do |format|
format.html { redirect_to profile_active_sessions_url, status: :found }

View File

@ -4,7 +4,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_two_factor_requirement
def show
unless current_user.otp_secret
unless current_user.two_factor_enabled?
current_user.otp_secret = User.generate_otp_secret(32)
end
@ -38,6 +38,8 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create
if current_user.validate_and_consume_otp!(params[:pin_code])
ActiveSession.destroy_all_but_current(current_user, session)
Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
@codes = user.generate_otp_backup_codes!
end

View File

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

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
module Ci
class AuthJobFinder
AuthError = Class.new(StandardError)
NotRunningJobError = Class.new(AuthError)
ErasedJobError = Class.new(AuthError)
DeletedProjectError = Class.new(AuthError)
def initialize(token:)
@token = token
end
def execute!
find_job_by_token.tap do |job|
next unless job
validate_job!(job)
end
end
def execute
execute!
rescue AuthError
end
private
attr_reader :token, :require_running, :raise_on_missing
def find_job_by_token
::Ci::Build.find_by_token(token)
end
def validate_job!(job)
validate_running_job!(job)
validate_job_not_erased!(job)
validate_project_presence!(job)
true
end
def validate_running_job!(job)
raise NotRunningJobError, 'Job is not running' unless job.running?
end
def validate_job_not_erased!(job)
raise ErasedJobError, 'Job has been erased!' if job.erased?
end
def validate_project_presence!(job)
if job.project.nil? || job.project.pending_delete?
raise DeletedProjectError, 'Project has been deleted!'
end
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

@ -105,6 +105,19 @@ class ActiveSession
end
end
def self.destroy_all_but_current(user, current_session)
session_ids = not_impersonated(user)
session_ids.reject! { |session| session.current?(current_session) } if current_session
Gitlab::Redis::SharedState.with do |redis|
destroy_sessions(redis, user, session_ids.map(&:session_id)) if session_ids.any?
end
end
def self.not_impersonated(user)
list(user).reject(&:is_impersonated)
end
def self.key_name(user_id, session_id = '*')
"#{Gitlab::Redis::SharedState::USER_SESSIONS_NAMESPACE}:#{user_id}:#{session_id}"
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,6 +76,8 @@ class Member < ApplicationRecord
scope :request, -> { where.not(requested_at: nil) }
scope :non_request, -> { where(requested_at: nil) }
scope :not_accepted_invitations_by_user, -> (user) { invite.where(invite_accepted_at: nil, created_by: user) }
scope :has_access, -> { active.where('access_level > 0') }
scope :guests, -> { active.where(access_level: GUEST) }

View File

@ -364,6 +364,7 @@ class User < ApplicationRecord
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
scope :order_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) }
scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) }
scope :by_id_and_login, ->(id, login) { where(id: id).where('username = LOWER(:login) OR email = LOWER(:login)', login: login) }
def preferred_language
read_attribute('preferred_language') ||
@ -887,6 +888,12 @@ class User < ApplicationRecord
all_expanded_groups.where(require_two_factor_authentication: true)
end
def source_groups_of_two_factor_authentication_requirement
Gitlab::ObjectHierarchy.new(expanded_groups_requiring_two_factor_authentication)
.all_objects
.where(id: groups)
end
# rubocop: disable CodeReuse/ServiceClass
def refresh_authorized_projects
Users::RefreshAuthorizedProjectsService.new(self).execute

View File

@ -11,7 +11,16 @@ module Applications
# EE would override and use `request` arg
def execute(request)
Doorkeeper::Application.create(params)
@application = Doorkeeper::Application.new(params)
unless params[:scopes].present?
@application.errors.add(:base, _("Scopes can't be blank"))
return @application
end
@application.save
@application
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ module Members
end
delete_subresources(member) unless skip_subresources
delete_project_invitations_by(member) unless skip_subresources
enqueue_delete_todos(member)
enqueue_unassign_issuables(member) if unassign_issuables
@ -39,24 +40,48 @@ module Members
delete_project_members(member)
delete_subgroup_members(member)
delete_invited_members(member)
end
def delete_project_members(member)
groups = member.group.self_and_descendants
ProjectMember.in_namespaces(groups).with_user(member.user).each do |project_member|
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
end
destroy_project_members(ProjectMember.in_namespaces(groups).with_user(member.user))
end
def delete_subgroup_members(member)
groups = member.group.descendants
GroupMember.of_groups(groups).with_user(member.user).each do |group_member|
destroy_group_members(GroupMember.of_groups(groups).with_user(member.user))
end
def delete_invited_members(member)
groups = member.group.self_and_descendants
destroy_group_members(GroupMember.of_groups(groups).not_accepted_invitations_by_user(member.user))
destroy_project_members(ProjectMember.in_namespaces(groups).not_accepted_invitations_by_user(member.user))
end
def destroy_project_members(members)
members.each do |project_member|
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
end
end
def destroy_group_members(members)
members.each do |group_member|
self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
end
end
def delete_project_invitations_by(member)
return unless member.is_a?(ProjectMember) && member.user && member.project
members_to_delete = member.project.members.not_accepted_invitations_by_user(member.user)
destroy_project_members(members_to_delete)
end
def can_destroy_member?(member)
can?(current_user, destroy_member_permission(member), member)
end

View File

@ -7,6 +7,10 @@ module Projects
def execute(remote_mirror, tries)
return success unless remote_mirror.enabled?
if Gitlab::UrlBlocker.blocked_url?(CGI.unescape(Gitlab::UrlSanitizer.sanitize(remote_mirror.url)))
return error("The remote mirror URL is invalid.")
end
update_mirror(remote_mirror)
success

View File

@ -27,6 +27,6 @@
- unless is_current_session
.float-right
= link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
= link_to profile_active_session_path(active_session.public_id), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') }, method: :delete, class: "btn btn-danger gl-ml-3" do
%span.sr-only= _('Revoke')
= _('Revoke')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ Doorkeeper.configure do
end
resource_owner_from_credentials do |routes|
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password])
user = Gitlab::Auth.find_with_user_password(params[:username], params[:password], increment_failed_attempts: true)
user unless user.try(:two_factor_enabled?)
end

View File

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

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class ChangeAwsRolesRoleArnNull < ActiveRecord::Migration[6.0]
DOWNTIME = false
EXAMPLE_ARN = 'arn:aws:iam::000000000000:role/example-role'
def up
change_column_null :aws_roles, :role_arn, true
end
def down
# Records may now exist with nulls, so we must fill them with a dummy value
change_column_null :aws_roles, :role_arn, false, EXAMPLE_ARN
end
end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
class AddOAuthPathsToProtectedPaths < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
ADD_PROTECTED_PATHS = [
'/oauth/authorize',
'/oauth/token'
].freeze
EXISTING_DEFAULT_PROTECTED_PATHS = [
'/users/password',
'/users/sign_in',
'/api/v3/session.json',
'/api/v3/session',
'/api/v4/session.json',
'/api/v4/session',
'/users',
'/users/confirmation',
'/unsubscribes/',
'/import/github/personal_access_token',
'/admin/session'
].freeze
NEW_DEFAULT_PROTECTED_PATHS = (EXISTING_DEFAULT_PROTECTED_PATHS + ADD_PROTECTED_PATHS).freeze
class ApplicationSetting < ActiveRecord::Base
self.table_name = 'application_settings'
end
def up
change_column_default :application_settings, :protected_paths, NEW_DEFAULT_PROTECTED_PATHS
ApplicationSetting.reset_column_information
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
missing_paths = ADD_PROTECTED_PATHS - application_setting.protected_paths
next if missing_paths.empty?
updated_protected_paths = application_setting.protected_paths + missing_paths
application_setting.update!(protected_paths: updated_protected_paths)
end
end
def down
change_column_default :application_settings, :protected_paths, EXISTING_DEFAULT_PROTECTED_PATHS
ApplicationSetting.reset_column_information
ApplicationSetting.where.not(protected_paths: nil).each do |application_setting|
paths_to_remove = application_setting.protected_paths - EXISTING_DEFAULT_PROTECTED_PATHS
next if paths_to_remove.empty?
updated_protected_paths = application_setting.protected_paths - paths_to_remove
application_setting.update!(protected_paths: updated_protected_paths)
end
end
end

View File

@ -0,0 +1 @@
6b8fa09c9700c494eeb5151f43064f1656eaaea804742629b7bd66483e2b04cb

View File

@ -0,0 +1 @@
2aab4599404312ddcc5bc9af11b0a21dfd6aa8aa10d4b4b5086a93ce1ffe77b6

View File

@ -9190,7 +9190,7 @@ CREATE TABLE public.application_settings (
throttle_protected_paths_enabled boolean DEFAULT false NOT NULL,
throttle_protected_paths_requests_per_period integer DEFAULT 10 NOT NULL,
throttle_protected_paths_period_in_seconds integer DEFAULT 60 NOT NULL,
protected_paths character varying(255)[] DEFAULT '{/users/password,/users/sign_in,/api/v3/session.json,/api/v3/session,/api/v4/session.json,/api/v4/session,/users,/users/confirmation,/unsubscribes/,/import/github/personal_access_token,/admin/session}'::character varying[],
protected_paths character varying(255)[] DEFAULT '{/users/password,/users/sign_in,/api/v3/session.json,/api/v3/session,/api/v4/session.json,/api/v4/session,/users,/users/confirmation,/unsubscribes/,/import/github/personal_access_token,/admin/session,/oauth/authorize,/oauth/token}'::character varying[],
throttle_incident_management_notification_enabled boolean DEFAULT false NOT NULL,
throttle_incident_management_notification_period_in_seconds integer DEFAULT 3600,
throttle_incident_management_notification_per_period integer DEFAULT 3600,
@ -9557,7 +9557,7 @@ CREATE TABLE public.aws_roles (
user_id integer NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
role_arn character varying(2048) NOT NULL,
role_arn character varying(2048),
role_external_id character varying(64) NOT NULL
);

View File

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

View File

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

View File

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

View File

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

View File

@ -29,6 +29,11 @@ exceeds 100, the oldest ones are deleted.
1. Use the previous steps to navigate to **Active Sessions**.
1. Click on **Revoke** besides a session. The current session cannot be revoked, as this would sign you out of GitLab.
NOTE: **Note:**
When any session is revoked all **Remember me** tokens for all
devices will be revoked. See ['Why do I keep getting signed out?'](index.md#why-do-i-keep-getting-signed-out)
for more information about the **Remember me** feature.
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -255,6 +255,12 @@ to get you a new `_gitlab_session` and keep you signed in through browser restar
After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
you are asked to sign in again to verify your identity for security reasons.
NOTE: **Note:**
When any session is signed out, or when a session is revoked
via [Active Sessions](active_sessions.md), all **Remember me** tokens are revoked.
While other sessions will remain active, the **Remember me** feature will not restore
a session if the browser is closed or the existing session expires.
### Increased sign-in time
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20340) in GitLab 13.1.
@ -264,7 +270,7 @@ The `remember_user_token` lifetime of a cookie can now extend beyond the deadlin
GitLab uses both session and persistent cookies:
- Session cookie: Session cookies are normally removed at the end of the browser session when the browser is closed. The `_gitlab_session` cookie has no expiration date.
- Persistent cookie: The `remember_me_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
- Persistent cookie: The `remember_user_token` is a cookie with an expiration date of two weeks. GitLab activates this cookie if you click Remember Me when you sign in.
By default, the server sets a time-to-live (TTL) of 1-week on any session that is used.

View File

@ -69,7 +69,7 @@ module API
deploy_token_from_request ||
find_user_from_bearer_token ||
find_user_from_job_token ||
find_user_from_warden
user_from_warden
end
end
@ -103,6 +103,25 @@ module API
def user_allowed_or_deploy_token?(user)
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
end
def user_from_warden
user = find_user_from_warden
return unless user
return if two_factor_required_but_not_setup?(user)
user
end
def two_factor_required_but_not_setup?(user)
verifier = Gitlab::Auth::TwoFactorAuthVerifier.new(user)
if verifier.two_factor_authentication_required? && verifier.current_user_needs_to_setup_two_factor?
verifier.two_factor_grace_period_expired?
else
false
end
end
end
class_methods do

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -65,7 +65,15 @@ module Gitlab
raise Gitlab::Auth::MissingPersonalAccessTokenError
end
def find_with_user_password(login, password)
# Find and return a user if the provided password is valid for various
# authenticators (OAuth, LDAP, Local Database).
#
# Specify `increment_failed_attempts: true` to increment Devise `failed_attempts`.
# CAUTION: Avoid incrementing failed attempts when authentication falls through
# different mechanisms, as in `.find_for_git_client`. This may lead to
# unwanted access locks when the value provided for `password` was actually
# a PAT, deploy token, etc.
def find_with_user_password(login, password, increment_failed_attempts: false)
# Avoid resource intensive checks if login credentials are not provided
return unless login.present? && password.present?
@ -96,10 +104,14 @@ module Gitlab
authenticators.compact!
# return found user that was authenticated first for given login credentials
authenticators.find do |auth|
authenticated_user = authenticators.find do |auth|
authenticated_user = auth.login(login, password)
break authenticated_user if authenticated_user
end
user_auth_attempt!(user, success: !!authenticated_user) if increment_failed_attempts
authenticated_user
end
end
@ -222,6 +234,8 @@ module Gitlab
# Registry access (with jwt) does not have access to project
return if project && !token.has_access_to?(project)
# When repository is disabled, no resources are accessible via Deploy Token
return if project&.repository_access_level == ::ProjectFeature::DISABLED
scopes = abilities_for_scopes(token.scopes)
@ -355,6 +369,13 @@ module Gitlab
def find_build_by_token(token)
::Ci::Build.running.find_by_token(token)
end
def user_auth_attempt!(user, success:)
return unless user && Gitlab::Database.read_write?
return user.unlock_access! if success
user.increment_failed_attempts!
end
end
end
end

View File

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

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Gitlab
module Auth
class TwoFactorAuthVerifier
attr_reader :current_user
def initialize(current_user)
@current_user = current_user
end
def two_factor_authentication_required?
Gitlab::CurrentSettings.require_two_factor_authentication? ||
current_user&.require_two_factor_authentication_from_group?
end
def current_user_needs_to_setup_two_factor?
current_user && !current_user.temp_oauth_email? && !current_user.two_factor_enabled?
end
def two_factor_grace_period
periods = [Gitlab::CurrentSettings.two_factor_grace_period]
periods << current_user.two_factor_grace_period if current_user&.require_two_factor_authentication_from_group?
periods.min
end
def two_factor_grace_period_expired?
time = current_user&.otp_grace_period_started_at
return false unless time
two_factor_grace_period.hours.since(time) < Time.current
end
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -40,7 +40,7 @@ RSpec.describe Admin::ApplicationsController do
describe 'POST #create' do
it 'creates the application' do
create_params = attributes_for(:application, trusted: true, confidential: false)
create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
expect do
post :create, params: { doorkeeper_application: create_params }
@ -63,7 +63,7 @@ RSpec.describe Admin::ApplicationsController do
context 'when the params are for a confidential application' do
it 'creates a confidential application' do
create_params = attributes_for(:application, confidential: true)
create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
expect do
post :create, params: { doorkeeper_application: create_params }
@ -75,6 +75,18 @@ RSpec.describe Admin::ApplicationsController do
expect(application).to have_attributes(create_params.except(:uid, :owner_type))
end
end
context 'when scopes are not present' do
it 'renders the application form on errors' do
create_params = attributes_for(:application, trusted: true, confidential: false)
expect do
post :create, params: { doorkeeper_application: create_params }
end.not_to change { Doorkeeper::Application.count }
expect(response).to render_template :new
end
end
end
describe 'PATCH #update' do

View File

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

View File

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

View File

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

View File

@ -123,7 +123,8 @@ RSpec.describe Oauth::ApplicationsController do
invalid_uri_params = {
doorkeeper_application: {
name: 'foo',
redirect_uri: 'javascript://alert()'
redirect_uri: 'javascript://alert()',
scopes: ['api']
}
}
@ -133,6 +134,23 @@ RSpec.describe Oauth::ApplicationsController do
end
end
context 'when scopes are not present' do
render_views
it 'shows an error for blank scopes' do
invalid_uri_params = {
doorkeeper_application: {
name: 'foo',
redirect_uri: 'http://example.org'
}
}
post :create, params: invalid_uri_params
expect(response.body).to include 'Scopes can&#39;t be blank'
end
end
it_behaves_like 'redirects to login page when the user is not signed in'
it_behaves_like 'redirects to 2fa setup page when the user requires it'
end
@ -172,7 +190,8 @@ RSpec.describe Oauth::ApplicationsController do
{
doorkeeper_application: {
name: 'foo',
redirect_uri: 'http://example.org'
redirect_uri: 'http://example.org',
scopes: ['api']
}
}
end

View File

@ -40,6 +40,22 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
end
end
context 'when sign in is not valid' do
let(:provider) { :github }
let(:extern_uid) { 'my-uid' }
it 'renders omniauth error page' do
allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance|
allow(instance).to receive(:valid_sign_in?).and_return(false)
end
post provider
expect(response).to render_template("errors/omniauth_error")
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
context 'when the user is on the last sign in attempt' do
let(:extern_uid) { 'my-uid' }

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Profiles::ActiveSessionsController do
describe 'DELETE destroy' do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
it 'invalidates all remember user tokens' do
ActiveSession.set(user, request)
session_id = request.session.id.public_id
user.remember_me!
delete :destroy, params: { id: session_id }
expect(user.reload.remember_created_at).to be_nil
end
end
end

View File

@ -14,10 +14,9 @@ RSpec.describe Profiles::TwoFactorAuthsController do
let(:user) { create(:user) }
it 'generates otp_secret for user' do
expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once
get :show
get :show # Second hit shouldn't re-generate it
end
it 'assigns qr_code' do
@ -27,6 +26,14 @@ RSpec.describe Profiles::TwoFactorAuthsController do
get :show
expect(assigns[:qr_code]).to eq code
end
it 'generates a unique otp_secret every time the page is loaded' do
expect(User).to receive(:generate_otp_secret).with(32).and_call_original.twice
2.times do
get :show
end
end
end
describe 'POST create' do
@ -57,6 +64,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do
expect(assigns[:codes]).to match_array %w(a b c)
end
it 'calls to delete other sessions' do
expect(ActiveSession).to receive(:destroy_all_but_current)
go
end
it 'renders create' do
go
expect(response).to render_template(:create)

View File

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

View File

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

View File

@ -261,8 +261,8 @@ RSpec.describe SessionsController do
context 'when using two-factor authentication via OTP' do
let(:user) { create(:user, :two_factor) }
def authenticate_2fa(user_params)
post(:create, params: { user: user_params }, session: { otp_user_id: user.id })
def authenticate_2fa(user_params, otp_user_id: user.id)
post(:create, params: { user: user_params }, session: { otp_user_id: otp_user_id })
end
context 'remember_me field' do
@ -299,8 +299,22 @@ RSpec.describe SessionsController do
end
end
# See issue gitlab-org/gitlab#20302.
context 'when otp_user_id is stale' do
render_views
it 'favors login over otp_user_id when password is present and does not authenticate the user' do
authenticate_2fa(
{ login: 'random_username', password: user.password },
otp_user_id: user.id
)
expect(response).to set_flash.now[:alert].to /Invalid Login or password/
end
end
##
# See #14900 issue
# See issue gitlab-org/gitlab-foss#14900
#
context 'when authenticating with login and OTP of another user' do
context 'when another user has 2FA enabled' do
@ -386,18 +400,6 @@ RSpec.describe SessionsController do
end
end
end
context 'when another user does not have 2FA enabled' do
let(:another_user) { create(:user) }
it 'does not leak that 2FA is disabled for another user' do
authenticate_2fa(login: another_user.username,
otp_attempt: 'invalid')
expect(response).to set_flash.now[:alert]
.to /Invalid two-factor code/
end
end
end
end

View File

@ -7,7 +7,7 @@ RSpec.describe 'admin manage applications' do
sign_in(create(:admin))
end
it do
it 'creates new oauth application' do
visit admin_applications_path
click_on 'New application'
@ -16,6 +16,7 @@ RSpec.describe 'admin manage applications' do
fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
check :doorkeeper_application_trusted
check :doorkeeper_application_scopes_read_user
click_on 'Submit'
expect(page).to have_content('Application: test')
expect(page).to have_content('Application ID')
@ -43,4 +44,19 @@ RSpec.describe 'admin manage applications' do
end
expect(page.find('.oauth-applications')).not_to have_content('test_changed')
end
context 'when scopes are blank' do
it 'returns an error' do
visit admin_applications_path
click_on 'New application'
expect(page).to have_content('New application')
fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
click_on 'Submit'
expect(page).to have_content("Scopes can't be blank")
end
end
end

View File

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

View File

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

View File

@ -15,6 +15,7 @@ RSpec.describe 'User manages applications' do
fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
check :doorkeeper_application_scopes_read_user
click_on 'Save application'
expect(page).to have_content 'Application: test'
@ -41,4 +42,16 @@ RSpec.describe 'User manages applications' do
end
expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
end
context 'when scopes are blank' do
it 'returns an error' do
expect(page).to have_content 'Add new application'
fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
click_on 'Save application'
expect(page).to have_content("Scopes can't be blank")
end
end
end

View File

@ -177,6 +177,14 @@ RSpec.describe 'Login' do
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
it 'does not allow sign-in if the user password is updated before entering a one-time code' do
user.update!(password: 'new_password')
enter_code(user.current_otp)
expect(page).to have_content('An error occurred. Please sign in again.')
end
context 'using one-time code' do
it 'allows login with valid code' do
expect(authentication_metrics)
@ -232,7 +240,7 @@ RSpec.describe 'Login' do
expect(codes.size).to eq 10
# Ensure the generated codes get saved
user.save
user.save(touch: false)
end
context 'with valid code' do
@ -290,7 +298,7 @@ RSpec.describe 'Login' do
code = codes.sample
expect(user.invalidate_otp_backup_code!(code)).to eq true
user.save!
user.save!(touch: false)
expect(user.reload.otp_backup_codes.size).to eq 9
enter_code(code)

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::AuthJobFinder do
let_it_be(:job, reload: true) { create(:ci_build, status: :running) }
let(:token) { job.token }
subject(:finder) do
described_class.new(token: token)
end
describe '#execute!' do
subject(:execute) { finder.execute! }
it { is_expected.to eq(job) }
it 'raises error if the job is not running' do
job.success!
expect { execute }.to raise_error described_class::NotRunningJobError, 'Job is not running'
end
it 'raises error if the job is erased' do
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
expect(job).to receive(:erased?).and_return(true)
expect { execute }.to raise_error described_class::ErasedJobError, 'Job has been erased!'
end
it 'raises error if the the project is missing' do
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
expect(job).to receive(:project).and_return(nil)
expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!'
end
it 'raises error if the the project is being removed' do
project = double(Project)
expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job)
expect(job).to receive(:project).twice.and_return(project)
expect(project).to receive(:pending_delete?).and_return(true)
expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!'
end
context 'with wrong job token' do
let(:token) { 'missing' }
it { is_expected.to be_nil }
end
end
describe '#execute' do
subject(:execute) { finder.execute }
before do
job.success!
end
it { is_expected.to be_nil }
end
end

View File

@ -11,8 +11,10 @@ RSpec.describe UserRecentEventsFinder do
let!(:private_event) { create(:event, project: private_project, author: project_owner) }
let!(:internal_event) { create(:event, project: internal_project, author: project_owner) }
let!(:public_event) { create(:event, project: public_project, author: project_owner) }
let(:limit) { nil }
let(:params) { { limit: limit } }
subject(:finder) { described_class.new(current_user, project_owner) }
subject(:finder) { described_class.new(current_user, project_owner, params) }
describe '#execute' do
context 'when profile is public' do
@ -48,5 +50,38 @@ RSpec.describe UserRecentEventsFinder do
expect(events).to include(event_b)
end
end
context 'limits' do
before do
stub_const("#{described_class}::DEFAULT_LIMIT", 1)
stub_const("#{described_class}::MAX_LIMIT", 3)
end
context 'when limit is not set' do
it 'returns events limited to DEFAULT_LIMIT' do
expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT)
end
end
context 'when limit is set' do
let(:limit) { 2 }
it 'returns events limited to specified limit' do
expect(finder.execute.size).to eq(limit)
end
end
context 'when limit is set to a number that exceeds maximum limit' do
let(:limit) { 4 }
before do
create(:event, project: public_project, author: project_owner)
end
it 'returns events limited to MAX_LIMIT' do
expect(finder.execute.size).to eq(described_class::MAX_LIMIT)
end
end
end
end
end

View File

@ -212,6 +212,55 @@ RSpec.describe GitlabSchema do
end
end
describe '.parse_gid' do
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
before do
test_base = Class.new
test_one = Class.new(test_base)
test_two = Class.new(test_base)
stub_const('TestBase', test_base)
stub_const('TestOne', test_one)
stub_const('TestTwo', test_two)
end
it 'parses the gid' do
gid = described_class.parse_gid(global_id)
expect(gid.model_id).to eq '2147483647'
expect(gid.model_class).to eq TestOne
end
context 'when gid is malformed' do
let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' }
it 'raises an error' do
expect { described_class.parse_gid(global_id) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab ID.")
end
end
context 'when using expected_type' do
it 'accepts a single type' do
gid = described_class.parse_gid(global_id, expected_type: TestOne)
expect(gid.model_class).to eq TestOne
end
it 'accepts an ancestor type' do
gid = described_class.parse_gid(global_id, expected_type: TestBase)
expect(gid.model_class).to eq TestOne
end
it 'rejects an unknown type' do
expect { described_class.parse_gid(global_id, expected_type: TestTwo) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestTwo.")
end
end
end
def field_instrumenters
described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
end

View File

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

View File

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

View File

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

View File

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

View File

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

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