Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml2
-rw-r--r--.rubocop_todo.yml42
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js5
-rw-r--r--app/controllers/clusters/clusters_controller.rb5
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb24
-rw-r--r--app/controllers/concerns/enforces_two_factor_authentication.rb18
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb11
-rw-r--r--app/controllers/profiles/active_sessions_controller.rb1
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb4
-rw-r--r--app/controllers/sessions_controller.rb13
-rw-r--r--app/finders/ci/auth_job_finder.rb56
-rw-r--r--app/finders/user_recent_events_finder.rb12
-rw-r--r--app/graphql/mutations/boards/lists/base.rb2
-rw-r--r--app/graphql/mutations/boards/lists/create.rb9
-rw-r--r--app/graphql/mutations/snippets/base.rb2
-rw-r--r--app/models/active_session.rb13
-rw-r--r--app/models/aws/role.rb1
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/concerns/discussion_on_diff.rb2
-rw-r--r--app/models/concerns/resolvable_discussion.rb1
-rw-r--r--app/models/diff_discussion.rb1
-rw-r--r--app/models/discussion.rb1
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/user.rb7
-rw-r--r--app/services/applications/create_service.rb11
-rw-r--r--app/services/auth/container_registry_authentication_service.rb1
-rw-r--r--app/services/branches/delete_service.rb2
-rw-r--r--app/services/ci/pipeline_trigger_service.rb7
-rw-r--r--app/services/clusters/aws/authorize_role_service.rb16
-rw-r--r--app/services/members/destroy_service.rb33
-rw-r--r--app/services/projects/update_remote_mirror_service.rb4
-rw-r--r--app/views/profiles/active_sessions/_active_session.html.haml2
-rw-r--r--app/views/projects/issues/service_desk.html.haml2
-rw-r--r--changelogs/unreleased/217908-handle-git-errors.yml5
-rw-r--r--changelogs/unreleased/235440-search-query-is-added-without-query-parameter-name-in-the-service-.yml5
-rw-r--r--changelogs/unreleased/eb-track-group-coverage-csv-download.yml5
-rw-r--r--changelogs/unreleased/empty-lines-cop-fix.yml5
-rw-r--r--changelogs/unreleased/empty-literal-cop.yml5
-rw-r--r--changelogs/unreleased/it-behaves-cop.yml5
-rw-r--r--changelogs/unreleased/non-deter-cop.yml5
-rw-r--r--changelogs/unreleased/security-205-dblessing-oauth-token-brute-force.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-20-1.yml5
-rw-r--r--config/initializers/doorkeeper.rb2
-rw-r--r--config/routes/user.rb1
-rw-r--r--db/migrate/20200717040735_change_aws_roles_role_arn_null.rb15
-rw-r--r--db/migrate/20200728182311_add_o_auth_paths_to_protected_paths.rb62
-rw-r--r--db/schema_migrations/202007170407351
-rw-r--r--db/schema_migrations/202007281823111
-rw-r--r--db/structure.sql4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql19
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json34
-rw-r--r--doc/development/sidekiq_style_guide.md73
-rw-r--r--doc/integration/elasticsearch.md16
-rw-r--r--doc/user/profile/active_sessions.md5
-rw-r--r--doc/user/profile/index.md8
-rw-r--r--lib/api/api_guard.rb21
-rw-r--r--lib/api/badges.rb7
-rw-r--r--lib/api/conan_packages.rb10
-rw-r--r--lib/api/helpers/badges_helpers.rb8
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb8
-rw-r--r--lib/api/helpers/packages_manager_clients_helpers.rb2
-rw-r--r--lib/banzai/pipeline/broadcast_message_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb6
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb2
-rw-r--r--lib/gitlab/auth.rb25
-rw-r--r--lib/gitlab/auth/auth_finders.rb16
-rw-r--r--lib/gitlab/auth/two_factor_auth_verifier.rb36
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb2
-rw-r--r--lib/gitlab/git/diff_collection.rb2
-rw-r--r--lib/gitlab/git_access.rb9
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/regex.rb5
-rw-r--r--locale/gitlab.pot14
-rw-r--r--package.json2
-rw-r--r--qa/spec/spec_helper.rb6
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb16
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb51
-rw-r--r--spec/controllers/application_controller_spec.rb9
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb33
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb23
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb16
-rw-r--r--spec/controllers/profiles/active_sessions_controller_spec.rb23
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb17
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb33
-rw-r--r--spec/controllers/search_controller_spec.rb12
-rw-r--r--spec/controllers/sessions_controller_spec.rb32
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb18
-rw-r--r--spec/features/issues/service_desk_spec.rb8
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb56
-rw-r--r--spec/features/profiles/user_manages_applications_spec.rb13
-rw-r--r--spec/features/users/login_spec.rb12
-rw-r--r--spec/finders/ci/auth_job_finder_spec.rb64
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb37
-rw-r--r--spec/graphql/gitlab_schema_spec.rb49
-rw-r--r--spec/graphql/mutations/boards/lists/create_spec.rb15
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb12
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb46
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb11
-rw-r--r--spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb112
-rw-r--r--spec/lib/gitlab/auth_spec.rb72
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb8
-rw-r--r--spec/lib/gitlab/git_access_spec.rb132
-rw-r--r--spec/lib/gitlab/regex_spec.rb9
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb52
-rw-r--r--spec/models/active_session_spec.rb53
-rw-r--r--spec/models/aws/role_spec.rb6
-rw-r--r--spec/models/member_spec.rb13
-rw-r--r--spec/models/user_spec.rb50
-rw-r--r--spec/requests/api/badges_spec.rb26
-rw-r--r--spec/requests/api/conan_packages_spec.rb45
-rw-r--r--spec/requests/api/generic_packages_spec.rb2
-rw-r--r--spec/requests/api/go_proxy_spec.rb9
-rw-r--r--spec/requests/api/graphql/mutations/boards/lists/create_spec.rb54
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb25
-rw-r--r--spec/requests/api/helpers_spec.rb21
-rw-r--r--spec/requests/api/jobs_spec.rb6
-rw-r--r--spec/requests/api/maven_packages_spec.rb33
-rw-r--r--spec/requests/api/npm_packages_spec.rb11
-rw-r--r--spec/requests/api/nuget_packages_spec.rb2
-rw-r--r--spec/requests/api/releases_spec.rb10
-rw-r--r--spec/requests/api/terraform/state_spec.rb11
-rw-r--r--spec/services/applications/create_service_spec.rb19
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb13
-rw-r--r--spec/services/branches/delete_service_spec.rb15
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb18
-rw-r--r--spec/services/clusters/aws/authorize_role_service_spec.rb44
-rw-r--r--spec/services/members/destroy_service_spec.rb40
-rw-r--r--spec/services/notification_service_spec.rb10
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb33
-rw-r--r--spec/spec_helper.rb8
-rw-r--r--spec/support/helpers/stub_object_storage.rb2
-rw-r--r--spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb29
-rw-r--r--yarn.lock8
136 files changed, 1950 insertions, 447 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 47a1e8341b7..aedcd4f71a0 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -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:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index d01b3fb91da..6c46ad5dffc 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -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.
diff --git a/Gemfile.lock b/Gemfile.lock
index ae678d2321b..53ede64312b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index 9bea7aa7b04..d2ac80fa190 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -86,6 +86,11 @@ export const conditions = flattenDeep(
value: __('Any'),
},
{
+ url: 'author_username=support-bot',
+ tokenKey: 'author',
+ value: 'support-bot',
+ },
+ {
url: 'milestone_title=None',
tokenKey: 'milestone',
value: __('None'),
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index b6d5eb7e5c0..7006c23321c 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -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
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 4b4bcc8d37e..2cc51c65c26 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -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
diff --git a/app/controllers/concerns/enforces_two_factor_authentication.rb b/app/controllers/concerns/enforces_two_factor_authentication.rb
index f1dd46648f1..02082f81598 100644
--- a/app/controllers/concerns/enforces_two_factor_authentication.rb
+++ b/app/controllers/concerns/enforces_two_factor_authentication.rb
@@ -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')
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 6a393405e4d..a558b01f0c6 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -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
diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb
index f409193aefc..d9ec3195fd1 100644
--- a/app/controllers/profiles/active_sessions_controller.rb
+++ b/app/controllers/profiles/active_sessions_controller.rb
@@ -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 }
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index a88c5ca4fa1..f600c34ca75 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -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
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index baf7a05f8ba..5da2bafbcb3 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -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
diff --git a/app/finders/ci/auth_job_finder.rb b/app/finders/ci/auth_job_finder.rb
new file mode 100644
index 00000000000..aee7dd16341
--- /dev/null
+++ b/app/finders/ci/auth_job_finder.rb
@@ -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
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 3f2e813d381..f376b26ab9c 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -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
diff --git a/app/graphql/mutations/boards/lists/base.rb b/app/graphql/mutations/boards/lists/base.rb
index 34b271ba3b8..d244d6bf8dd 100644
--- a/app/graphql/mutations/boards/lists/base.rb
+++ b/app/graphql/mutations/boards/lists/base.rb
@@ -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,
diff --git a/app/graphql/mutations/boards/lists/create.rb b/app/graphql/mutations/boards/lists/create.rb
index 4f545709ee9..3fe1052315f 100644
--- a/app/graphql/mutations/boards/lists/create.rb
+++ b/app/graphql/mutations/boards/lists/create.rb
@@ -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')
diff --git a/app/graphql/mutations/snippets/base.rb b/app/graphql/mutations/snippets/base.rb
index c8cc721b2e0..023f876d035 100644
--- a/app/graphql/mutations/snippets/base.rb
+++ b/app/graphql/mutations/snippets/base.rb
@@ -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)
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index be07c221f32..4908290e06b 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -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
diff --git a/app/models/aws/role.rb b/app/models/aws/role.rb
index 54132be749d..7d34665082d 100644
--- a/app/models/aws/role.rb
+++ b/app/models/aws/role.rb
@@ -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
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index f33d54544ca..4983de83800 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -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'
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index 8542c48f366..40891073738 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -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
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index 5174ae05d15..3e1e5faee54 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -31,7 +31,6 @@ module ResolvableDiscussion
delegate :resolved_at,
:resolved_by,
:resolved_by_push?,
-
to: :last_resolved_note,
allow_nil: true
end
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index f9e2f00b9f3..6806008d676 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -16,7 +16,6 @@ class DiffDiscussion < Discussion
:diff_note_positions,
:on_text?,
:on_image?,
-
to: :first_note
def legacy_diff_discussion?
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index adcb2217d85..793cdb5dece 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -24,7 +24,6 @@ class Discussion
:system_note_with_references_visible_for?,
:resource_parent,
:save,
-
to: :first_note
def declarative_policy_delegate
diff --git a/app/models/member.rb b/app/models/member.rb
index a27076b49d7..1913df61614 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -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) }
diff --git a/app/models/user.rb b/app/models/user.rb
index 355a174ba9a..7574f0865a0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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
diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb
index 500db1e172a..92500fbc254 100644
--- a/app/services/applications/create_service.rb
+++ b/app/services/applications/create_service.rb
@@ -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
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 44a434f4402..831a25a637e 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -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'
diff --git a/app/services/branches/delete_service.rb b/app/services/branches/delete_service.rb
index 9bd5b343448..6efbdd161a1 100644
--- a/app/services/branches/delete_service.rb
+++ b/app/services/branches/delete_service.rb
@@ -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
diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb
index 37b9b4c362c..d9f41b7040e 100644
--- a/app/services/ci/pipeline_trigger_service.rb
+++ b/app/services/ci/pipeline_trigger_service.rb
@@ -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
diff --git a/app/services/clusters/aws/authorize_role_service.rb b/app/services/clusters/aws/authorize_role_service.rb
index fb620f77b9f..2712a4b05bb 100644
--- a/app/services/clusters/aws/authorize_role_service.rb
+++ b/app/services/clusters/aws/authorize_role_service.rb
@@ -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
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index fdd43260521..8cad065e6cc 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -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
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index fe2610f89fb..7961f689259 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -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
diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml
index 9ae75fe6b8e..c4b3fa80d75 100644
--- a/app/views/profiles/active_sessions/_active_session.html.haml
+++ b/app/views/profiles/active_sessions/_active_session.html.haml
@@ -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')
diff --git a/app/views/projects/issues/service_desk.html.haml b/app/views/projects/issues/service_desk.html.haml
index bd260bdf143..65580a94cd0 100644
--- a/app/views/projects/issues/service_desk.html.haml
+++ b/app/views/projects/issues/service_desk.html.haml
@@ -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
diff --git a/changelogs/unreleased/217908-handle-git-errors.yml b/changelogs/unreleased/217908-handle-git-errors.yml
new file mode 100644
index 00000000000..bb6c4918428
--- /dev/null
+++ b/changelogs/unreleased/217908-handle-git-errors.yml
@@ -0,0 +1,5 @@
+---
+title: Fix unfinished merge by Merge Train process
+merge_request: 41106
+author:
+type: fixed
diff --git a/changelogs/unreleased/235440-search-query-is-added-without-query-parameter-name-in-the-service-.yml b/changelogs/unreleased/235440-search-query-is-added-without-query-parameter-name-in-the-service-.yml
new file mode 100644
index 00000000000..09b4181ee32
--- /dev/null
+++ b/changelogs/unreleased/235440-search-query-is-added-without-query-parameter-name-in-the-service-.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the filtered search bar to work in the service desk issue list
+merge_request: 40797
+author:
+type: fixed
diff --git a/changelogs/unreleased/eb-track-group-coverage-csv-download.yml b/changelogs/unreleased/eb-track-group-coverage-csv-download.yml
new file mode 100644
index 00000000000..603d01bad2c
--- /dev/null
+++ b/changelogs/unreleased/eb-track-group-coverage-csv-download.yml
@@ -0,0 +1,5 @@
+---
+title: Track downloads of group code coverage CSV in snowplow
+merge_request: 40754
+author:
+type: added
diff --git a/changelogs/unreleased/empty-lines-cop-fix.yml b/changelogs/unreleased/empty-lines-cop-fix.yml
new file mode 100644
index 00000000000..f5ed1955266
--- /dev/null
+++ b/changelogs/unreleased/empty-lines-cop-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Layout/EmptyLinesAroundArguments cop
+merge_request: 41086
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/empty-literal-cop.yml b/changelogs/unreleased/empty-literal-cop.yml
new file mode 100644
index 00000000000..1d5b4857193
--- /dev/null
+++ b/changelogs/unreleased/empty-literal-cop.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Style/EmptyLiteral cop
+merge_request: 41110
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/it-behaves-cop.yml b/changelogs/unreleased/it-behaves-cop.yml
new file mode 100644
index 00000000000..95bb412c55a
--- /dev/null
+++ b/changelogs/unreleased/it-behaves-cop.yml
@@ -0,0 +1,5 @@
+---
+title: Fix RSpec/ItBehavesLike cop
+merge_request: 41111
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/non-deter-cop.yml b/changelogs/unreleased/non-deter-cop.yml
new file mode 100644
index 00000000000..18cd89ccceb
--- /dev/null
+++ b/changelogs/unreleased/non-deter-cop.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Lint/NonDeterministicRequireOrder cop
+merge_request: 41098
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/security-205-dblessing-oauth-token-brute-force.yml b/changelogs/unreleased/security-205-dblessing-oauth-token-brute-force.yml
new file mode 100644
index 00000000000..e53ef7ba09a
--- /dev/null
+++ b/changelogs/unreleased/security-205-dblessing-oauth-token-brute-force.yml
@@ -0,0 +1,5 @@
+---
+title: Protect OAuth endpoints from brute force/password stuffing
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-20-1.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-20-1.yml
new file mode 100644
index 00000000000..67cddaa4539
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-20-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.20.1
+merge_request:
+author:
+type: security
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 76e29fb6c02..ad0b0c2008f 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -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
diff --git a/config/routes/user.rb b/config/routes/user.rb
index 3bf13908d39..c7a5a56d9ed 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -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
diff --git a/db/migrate/20200717040735_change_aws_roles_role_arn_null.rb b/db/migrate/20200717040735_change_aws_roles_role_arn_null.rb
new file mode 100644
index 00000000000..707cfe96a7c
--- /dev/null
+++ b/db/migrate/20200717040735_change_aws_roles_role_arn_null.rb
@@ -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
diff --git a/db/migrate/20200728182311_add_o_auth_paths_to_protected_paths.rb b/db/migrate/20200728182311_add_o_auth_paths_to_protected_paths.rb
new file mode 100644
index 00000000000..7a5af0135fa
--- /dev/null
+++ b/db/migrate/20200728182311_add_o_auth_paths_to_protected_paths.rb
@@ -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
diff --git a/db/schema_migrations/20200717040735 b/db/schema_migrations/20200717040735
new file mode 100644
index 00000000000..6bfa1dd7261
--- /dev/null
+++ b/db/schema_migrations/20200717040735
@@ -0,0 +1 @@
+6b8fa09c9700c494eeb5151f43064f1656eaaea804742629b7bd66483e2b04cb \ No newline at end of file
diff --git a/db/schema_migrations/20200728182311 b/db/schema_migrations/20200728182311
new file mode 100644
index 00000000000..6bb5a869513
--- /dev/null
+++ b/db/schema_migrations/20200728182311
@@ -0,0 +1 @@
+2aab4599404312ddcc5bc9af11b0a21dfd6aa8aa10d4b4b5086a93ce1ffe77b6 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 61a9dadba08..97d74287694 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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
);
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index f66a897c1c2..6d73c0d594a 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -1310,12 +1310,17 @@ 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
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index ecf3cb3e38c..7a58ee5d54b 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -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",
@@ -3538,6 +3538,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.",
"type": {
@@ -50479,6 +50499,16 @@
"possibleTypes": null
},
{
+ "kind": "SCALAR",
+ "name": "UserID",
+ "description": "Identifier of User",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "UserPermissions",
"description": null,
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index c5dfc5731e6..fdea27f43ca 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -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
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index daf7a8f6703..4d0ed035ad5 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -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.
diff --git a/doc/user/profile/active_sessions.md b/doc/user/profile/active_sessions.md
index 4dbb11b581d..a5b15a7880c 100644
--- a/doc/user/profile/active_sessions.md
+++ b/doc/user/profile/active_sessions.md
@@ -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
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index b6ef6d7fdb7..894494da513 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -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.
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 59978962b1d..46b69214877 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -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
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index f6cd3f83ff3..f9728ffc446 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -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
diff --git a/lib/api/conan_packages.rb b/lib/api/conan_packages.rb
index c98812c16ce..cf91d8ac74a 100644
--- a/lib/api/conan_packages.rb
+++ b/lib/api/conan_packages.rb
@@ -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
diff --git a/lib/api/helpers/badges_helpers.rb b/lib/api/helpers/badges_helpers.rb
index 46ce5b4e7b5..f402c603c87 100644
--- a/lib/api/helpers/badges_helpers.rb
+++ b/lib/api/helpers/badges_helpers.rb
@@ -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 = {})
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index 69c10440283..65769d7d8ec 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -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
diff --git a/lib/api/helpers/packages_manager_clients_helpers.rb b/lib/api/helpers/packages_manager_clients_helpers.rb
index 2c0598ae7dc..a30396bf07a 100644
--- a/lib/api/helpers/packages_manager_clients_helpers.rb
+++ b/lib/api/helpers/packages_manager_clients_helpers.rb
@@ -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
diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb
index e31795e673c..27118269bd0 100644
--- a/lib/banzai/pipeline/broadcast_message_pipeline.rb
+++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb
@@ -7,7 +7,6 @@ module Banzai
@filters ||= FilterArray[
Filter::MarkdownFilter,
Filter::BroadcastMessageSanitizationFilter,
-
Filter::EmojiFilter,
Filter::ColorFilter,
Filter::AutolinkFilter,
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 10ac813ea15..0d6f36cd6e9 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -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
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 7fe13100ec2..b06cb907025 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -8,11 +8,9 @@ module Banzai
Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
Filter::AssetProxyFilter,
-
Filter::EmojiFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
-
*reference_filters
]
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 332d0bc1478..ece4946383d 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -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
diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb
index f3d0c053880..ccf52bae9a5 100644
--- a/lib/gitlab/auth/auth_finders.rb
+++ b/lib/gitlab/auth/auth_finders.rb
@@ -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
diff --git a/lib/gitlab/auth/two_factor_auth_verifier.rb b/lib/gitlab/auth/two_factor_auth_verifier.rb
new file mode 100644
index 00000000000..86552ef1267
--- /dev/null
+++ b/lib/gitlab/auth/two_factor_auth_verifier.rb
@@ -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
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 2c53f9b026d..bd5d2e53180 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -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
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 46896961867..e6121d688ba 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -37,7 +37,7 @@ module Gitlab
@byte_count = 0
@overflow = false
@empty = true
- @array = Array.new
+ @array = []
end
def each(&block)
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index f3b53a2ba0b..b67b3a37440 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -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? ||
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 131c00db612..e1324530412 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -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
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 0f52d8ab95d..06e8a9ec649 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 06a802bcac4..469e959ad54 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/package.json b/package.json
index a794d41db92..70c88e901f1 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index b3251c739a9..81730c3ab13 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -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
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
index 732d20666cb..6c423097e70 100644
--- a/spec/controllers/admin/applications_controller_spec.rb
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -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
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index 2e0ee671d3f..d2a569a9d48 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -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) }
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 357044a144c..f8d4690e9ce 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -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
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 1593e1290c4..81d5bc7770f 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -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) }
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index 0a7975b8c1b..f21ef324884 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -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
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index dce996b977d..3f7f0c55f38 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -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' }
diff --git a/spec/controllers/profiles/active_sessions_controller_spec.rb b/spec/controllers/profiles/active_sessions_controller_spec.rb
new file mode 100644
index 00000000000..f54f69d853d
--- /dev/null
+++ b/spec/controllers/profiles/active_sessions_controller_spec.rb
@@ -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
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index e6839f54c5d..59eb33f4bc6 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -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)
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index da4faad2a39..51a451570c5 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -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
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index d66a0246c2d..4d325f55fb0 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -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
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 7754fac2fb2..acec1cf22fd 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -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
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index 3f3d71e842c..7a9a6f2ccb8 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -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
diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb
index 666fbe84fae..1512d539dec 100644
--- a/spec/features/issues/service_desk_spec.rb
+++ b/spec/features/issues/service_desk_spec.rb
@@ -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
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index 57362ed2d54..fbf4e531db1 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -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,
diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb
index d65365db880..22eed748c00 100644
--- a/spec/features/profiles/user_manages_applications_spec.rb
+++ b/spec/features/profiles/user_manages_applications_spec.rb
@@ -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
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index a2fd1caf6a3..853c381fe6b 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -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)
diff --git a/spec/finders/ci/auth_job_finder_spec.rb b/spec/finders/ci/auth_job_finder_spec.rb
new file mode 100644
index 00000000000..6cd58f5cd01
--- /dev/null
+++ b/spec/finders/ci/auth_job_finder_spec.rb
@@ -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
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index 559d1004b4b..ddba9b595a4 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -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
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 5d6aa863994..4db12643069 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -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
diff --git a/spec/graphql/mutations/boards/lists/create_spec.rb b/spec/graphql/mutations/boards/lists/create_spec.rb
index 1a881ac81e8..b1fe9911c7b 100644
--- a/spec/graphql/mutations/boards/lists/create_spec.rb
+++ b/spec/graphql/mutations/boards/lists/create_spec.rb
@@ -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
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index fcb9efa39d5..153dc19335b 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -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' } }
diff --git a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
index 70a95d7ba7b..c19b7b63eec 100644
--- a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
+++ b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index a73ac0b34af..1ac8ebe1369 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -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
+
+ 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
- expect(subject).to eq(user)
- expect(@current_authenticated_job).to eq job
+ 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 } }
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index ef83321cc0e..b89ceb37076 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
new file mode 100644
index 00000000000..f906870195a
--- /dev/null
+++ b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do
+ let(:user) { create(:user) }
+
+ subject { described_class.new(user) }
+
+ describe '#two_factor_authentication_required?' do
+ describe 'when it is required on application level' do
+ it 'returns true' do
+ stub_application_setting require_two_factor_authentication: true
+
+ expect(subject.two_factor_authentication_required?).to be_truthy
+ end
+ end
+
+ describe 'when it is required on group level' do
+ it 'returns true' do
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
+
+ expect(subject.two_factor_authentication_required?).to be_truthy
+ end
+ end
+
+ describe 'when it is not required' do
+ it 'returns false when not required on group level' do
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false)
+
+ expect(subject.two_factor_authentication_required?).to be_falsey
+ end
+ end
+ end
+
+ describe '#current_user_needs_to_setup_two_factor?' do
+ it 'returns false when current_user is nil' do
+ expect(described_class.new(nil).current_user_needs_to_setup_two_factor?).to be_falsey
+ end
+
+ it 'returns false when current_user does not have temp email' do
+ allow(user).to receive(:two_factor_enabled?).and_return(false)
+ allow(user).to receive(:temp_oauth_email?).and_return(true)
+
+ expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey
+ end
+
+ it 'returns false when current_user has 2fa disabled' do
+ allow(user).to receive(:temp_oauth_email?).and_return(false)
+ allow(user).to receive(:two_factor_enabled?).and_return(true)
+
+ expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey
+ end
+
+ it 'returns true when user requires 2fa authentication' do
+ allow(user).to receive(:two_factor_enabled?).and_return(false)
+ allow(user).to receive(:temp_oauth_email?).and_return(false)
+
+ expect(subject.current_user_needs_to_setup_two_factor?).to be_truthy
+ end
+ end
+
+ describe '#two_factor_grace_period' do
+ it 'returns grace period from settings if there is no period from groups' do
+ stub_application_setting two_factor_grace_period: 2
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false)
+
+ expect(subject.two_factor_grace_period).to eq(2)
+ end
+
+ it 'returns grace period from groups if there is no period from settings' do
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
+ allow(user).to receive(:two_factor_grace_period).and_return(3)
+
+ expect(subject.two_factor_grace_period).to eq(3)
+ end
+
+ it 'returns minimal grace period if there is grace period from settings and from group' do
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
+ allow(user).to receive(:two_factor_grace_period).and_return(3)
+ stub_application_setting two_factor_grace_period: 2
+
+ expect(subject.two_factor_grace_period).to eq(2)
+ end
+ end
+
+ describe '#two_factor_grace_period_expired?' do
+ before do
+ allow(user).to receive(:otp_grace_period_started_at).and_return(4.hours.ago)
+ end
+
+ it 'returns true if the grace period has expired' do
+ allow(subject).to receive(:two_factor_grace_period).and_return(2)
+
+ expect(subject.two_factor_grace_period_expired?).to be_truthy
+ end
+
+ it 'returns false if the grace period has not expired' do
+ allow(subject).to receive(:two_factor_grace_period).and_return(6)
+
+ expect(subject.two_factor_grace_period_expired?).to be_falsey
+ end
+
+ context 'when otp_grace_period_started_at is nil' do
+ it 'returns false' do
+ allow(user).to receive(:otp_grace_period_started_at).and_return(nil)
+
+ expect(subject.two_factor_grace_period_expired?).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index dcaaa8d4188..b6a8ac31074 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -441,7 +441,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
end
- shared_examples 'deploy token with disabled registry' do
+ shared_examples 'deploy token with disabled feature' do
context 'when registry disabled' do
before do
stub_container_registry_config(enabled: false)
@@ -452,6 +452,15 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
.to eq(auth_failure)
end
end
+
+ context 'when repository is disabled' do
+ let(:project) { create(:project, :repository_disabled) }
+
+ it 'fails when login and token are valid' do
+ expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
end
context 'when deploy token and user have the same username' do
@@ -604,7 +613,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it_behaves_like 'registry token scope'
end
- it_behaves_like 'deploy token with disabled registry'
+ it_behaves_like 'deploy token with disabled feature'
end
context 'when the deploy token has write_registry as a scope' do
@@ -626,7 +635,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it_behaves_like 'registry token scope'
end
- it_behaves_like 'deploy token with disabled registry'
+ it_behaves_like 'deploy token with disabled feature'
end
end
end
@@ -682,12 +691,69 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end
+ it 'does not find user in locked state' do
+ user.lock_access!
+
+ expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ end
+
it "does not find user in ldap_blocked state" do
user.ldap_block
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end
+ context 'with increment_failed_attempts' do
+ wrong_password = 'incorrect_password'
+
+ it 'increments failed_attempts when true and password is incorrect' do
+ expect do
+ gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true)
+ user.reload
+ end.to change(user, :failed_attempts).from(0).to(1)
+ end
+
+ it 'resets failed_attempts when true and password is correct' do
+ user.failed_attempts = 2
+ user.save
+
+ expect do
+ gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
+ user.reload
+ end.to change(user, :failed_attempts).from(2).to(0)
+ end
+
+ it 'does not increment failed_attempts by default' do
+ expect do
+ gl_auth.find_with_user_password(username, wrong_password)
+ user.reload
+ end.not_to change(user, :failed_attempts)
+ end
+
+ context 'when the database is read only' do
+ before do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it 'does not increment failed_attempts when true and password is incorrect' do
+ expect do
+ gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true)
+ user.reload
+ end.not_to change(user, :failed_attempts)
+ end
+
+ it 'does not reset failed_attempts when true and password is correct' do
+ user.failed_attempts = 2
+ user.save
+
+ expect do
+ gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
+ user.reload
+ end.not_to change(user, :failed_attempts)
+ end
+ end
+ end
+
context "with ldap enabled" do
before do
allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 491437856d4..8961cdcae7d 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -192,7 +192,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
end
describe '.find with Gitaly enabled' do
- it_should_behave_like '.find'
+ it_behaves_like '.find'
end
describe '.find with Rugged enabled', :enable_rugged do
@@ -204,7 +204,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
described_class.find(repository, SeedRepo::Commit::ID)
end
- it_should_behave_like '.find'
+ it_behaves_like '.find'
end
describe '.last_for_path' do
@@ -474,7 +474,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
end
describe '.batch_by_oid with Gitaly enabled' do
- it_should_behave_like '.batch_by_oid'
+ it_behaves_like '.batch_by_oid'
context 'when oids is empty' do
it 'makes no Gitaly request' do
@@ -486,7 +486,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
end
describe '.batch_by_oid with Rugged enabled', :enable_rugged do
- it_should_behave_like '.batch_by_oid'
+ it_behaves_like '.batch_by_oid'
it 'calls out to the Rugged implementation' do
allow_next_instance_of(Rugged) do |instance|
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 8153886a2ab..85567ab2e55 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -472,31 +472,135 @@ RSpec.describe Gitlab::GitAccess do
let(:actor) { key }
context 'pull code' do
- context 'when project is authorized' do
- before do
- key.projects << project
+ context 'when project is public' do
+ let(:project) { create(:project, :public, :repository, *options) }
+
+ context 'when deploy key exists in the project' do
+ before do
+ key.projects << project
+ end
+
+ context 'when the repository is public' do
+ let(:options) { %i[repository_enabled] }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when the repository is private' do
+ let(:options) { %i[repository_private] }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when the repository is disabled' do
+ let(:options) { %i[repository_disabled] }
+
+ it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
+ end
end
- it { expect { pull_access_check }.not_to raise_error }
+ context 'when deploy key does not exist in the project' do
+ context 'when the repository is public' do
+ let(:options) { %i[repository_enabled] }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when the repository is private' do
+ let(:options) { %i[repository_private] }
+
+ it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
+ end
+
+ context 'when the repository is disabled' do
+ let(:options) { %i[repository_disabled] }
+
+ it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
+ end
+ end
end
- context 'when unauthorized' do
- context 'from public project' do
- let(:project) { create(:project, :public, :repository) }
+ context 'when project is internal' do
+ let(:project) { create(:project, :internal, :repository, *options) }
- it { expect { pull_access_check }.not_to raise_error }
+ context 'when deploy key exists in the project' do
+ before do
+ key.projects << project
+ end
+
+ context 'when the repository is public' do
+ let(:options) { %i[repository_enabled] }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when the repository is private' do
+ let(:options) { %i[repository_private] }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when the repository is disabled' do
+ let(:options) { %i[repository_disabled] }
+
+ it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
+ end
end
- context 'from internal project' do
- let(:project) { create(:project, :internal, :repository) }
+ context 'when deploy key does not exist in the project' do
+ context 'when the repository is public' do
+ let(:options) { %i[repository_enabled] }
- it { expect { pull_access_check }.to raise_not_found }
+ it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
+ end
+
+ context 'when the repository is private' do
+ let(:options) { %i[repository_private] }
+
+ it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
+ end
+
+ context 'when the repository is disabled' do
+ let(:options) { %i[repository_disabled] }
+
+ it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
+ end
end
+ end
- context 'from private project' do
- let(:project) { create(:project, :private, :repository) }
+ context 'when project is private' do
+ let(:project) { create(:project, :private, :repository, *options) }
- it { expect { pull_access_check }.to raise_not_found }
+ context 'when deploy key exists in the project' do
+ before do
+ key.projects << project
+ end
+
+ context 'when the repository is private' do
+ let(:options) { %i[repository_private] }
+
+ it { expect { pull_access_check }.not_to raise_error }
+ end
+
+ context 'when the repository is disabled' do
+ let(:options) { %i[repository_disabled] }
+
+ it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') }
+ end
+ end
+
+ context 'when deploy key does not exist in the project' do
+ context 'when the repository is private' do
+ let(:options) { %i[repository_private] }
+
+ it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
+ end
+
+ context 'when the repository is disabled' do
+ let(:options) { %i[repository_disabled] }
+
+ it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') }
+ end
end
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index f736f19ca9a..88c3315150b 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -195,15 +195,6 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('foo/bar') }
end
- describe '.conan_file_name_regex' do
- subject { described_class.conan_file_name_regex }
-
- it { is_expected.to match('conanfile.py') }
- it { is_expected.to match('conan_package.tgz') }
- it { is_expected.not_to match('foo.txt') }
- it { is_expected.not_to match('!!()()') }
- end
-
describe '.conan_package_reference_regex' do
subject { described_class.conan_package_reference_regex }
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index cac2c410132..e9733851590 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Workhorse do
context "when the repository doesn't have an archive file path" do
before do
- allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
+ allow(project.repository).to receive(:archive_metadata).and_return({})
end
it "raises an error" do
diff --git a/spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb b/spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb
new file mode 100644
index 00000000000..e12519e15b8
--- /dev/null
+++ b/spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20200728182311_add_o_auth_paths_to_protected_paths.rb')
+
+RSpec.describe AddOAuthPathsToProtectedPaths do
+ subject(:migration) { described_class.new }
+
+ let(:application_settings) { table(:application_settings) }
+ let(:new_paths) do
+ [
+ '/oauth/authorize',
+ '/oauth/token'
+ ]
+ end
+
+ it 'appends new OAuth paths' do
+ application_settings.create!
+
+ protected_paths_before = application_settings.first.protected_paths
+ protected_paths_after = protected_paths_before + new_paths
+
+ expect { migrate! }.to change { application_settings.first.protected_paths }.from(protected_paths_before).to(protected_paths_after)
+ end
+
+ it 'new default includes new paths' do
+ settings_before = application_settings.create!
+
+ expect(settings_before.protected_paths).not_to include(*new_paths)
+
+ migrate!
+
+ application_settings.reset_column_information
+ settings_after = application_settings.create!
+
+ expect(settings_after.protected_paths).to include(*new_paths)
+ end
+
+ it 'does not change the value when the new paths are already included' do
+ application_settings.create!(protected_paths: %w(/users/sign_in /users/password) + new_paths)
+
+ expect { migrate! }.not_to change { application_settings.first.protected_paths }
+ end
+
+ it 'adds one value when the other is already present' do
+ application_settings.create!(protected_paths: %W(/users/sign_in /users/password #{new_paths.first}))
+
+ migrate!
+
+ expect(application_settings.first.protected_paths).to include(new_paths.second)
+ end
+end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 24b47be3c69..de39c8c7c5c 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -296,6 +296,59 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
end
end
+ describe '.destroy_all_but_current' do
+ it 'gracefully handles a nil session ID' do
+ expect(described_class).not_to receive(:destroy_sessions)
+
+ ActiveSession.destroy_all_but_current(user, nil)
+ end
+
+ context 'with user sessions' do
+ let(:current_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
+
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(described_class.key_name(user.id, current_session_id),
+ Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(current_session_id))))
+ redis.set(described_class.key_name(user.id, '59822c7d9fcdfa03725eff41782ad97d'),
+ Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('59822c7d9fcdfa03725eff41782ad97d'))))
+ redis.set(described_class.key_name(9999, '5c8611e4f9c69645ad1a1492f4131358'),
+ Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('5c8611e4f9c69645ad1a1492f4131358'))))
+ redis.sadd(described_class.lookup_key_name(user.id), '59822c7d9fcdfa03725eff41782ad97d')
+ redis.sadd(described_class.lookup_key_name(user.id), current_session_id)
+ redis.sadd(described_class.lookup_key_name(9999), '5c8611e4f9c69645ad1a1492f4131358')
+ end
+ end
+
+ it 'removes the entry associated with the all user sessions but current' do
+ expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(2).to(1)
+
+ expect(ActiveSession.session_ids_for_user(9999).size).to eq(1)
+ end
+
+ it 'removes the lookup entry of deleted sessions' do
+ ActiveSession.destroy_all_but_current(user, request.session)
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers(described_class.lookup_key_name(user.id))).to eq [current_session_id]
+ end
+ end
+
+ it 'does not remove impersonated sessions' do
+ impersonated_session_id = '6919a6f1bb119dd7396fadc38fd18eee'
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(described_class.key_name(user.id, impersonated_session_id),
+ Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(impersonated_session_id), is_impersonated: true)))
+ redis.sadd(described_class.lookup_key_name(user.id), impersonated_session_id)
+ end
+
+ expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(3).to(2)
+
+ expect(ActiveSession.session_ids_for_user(9999).size).to eq(1)
+ end
+ end
+ end
+
describe '.cleanup' do
before do
stub_const("ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS", 5)
diff --git a/spec/models/aws/role_spec.rb b/spec/models/aws/role_spec.rb
index 612868f6eb0..ee93c9d6fad 100644
--- a/spec/models/aws/role_spec.rb
+++ b/spec/models/aws/role_spec.rb
@@ -29,6 +29,12 @@ RSpec.describe Aws::Role do
it { is_expected.to be_truthy }
end
+
+ context 'ARN is nil' do
+ let(:role_arn) { }
+
+ it { is_expected.to be_truthy }
+ end
end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 8da164e041c..b83c769284a 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -195,6 +195,19 @@ RSpec.describe Member do
it { expect(described_class.non_request).to include @accepted_request_member }
end
+ describe '.not_accepted_invitations_by_user' do
+ let(:invited_by_user) { create(:project_member, :invited, project: project, created_by: @owner_user) }
+
+ before do
+ create(:project_member, :invited, invite_email: 'test@test.com', project: project, created_by: @owner_user, invite_accepted_at: Time.zone.now)
+ create(:project_member, :invited, invite_email: 'test2@test.com', project: project, created_by: @maintainer_user)
+ end
+
+ subject { described_class.not_accepted_invitations_by_user(@owner_user) }
+
+ it { is_expected.to contain_exactly(invited_by_user) }
+ end
+
describe '.search_invite_email' do
it 'returns only members the matching e-mail' do
create(:group_member, :invited)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 98f1c5bb2cc..74a36daa727 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -895,6 +895,20 @@ RSpec.describe User do
expect(described_class.without_ghosts).to match_array([user1, user2])
end
end
+
+ describe '.by_id_and_login' do
+ let_it_be(:user) { create(:user) }
+
+ it 'finds a user regardless of case' do
+ expect(described_class.by_id_and_login(user.id, user.username.upcase))
+ .to contain_exactly(user)
+ end
+
+ it 'finds a user when login is an email address regardless of case' do
+ expect(described_class.by_id_and_login(user.id, user.email.upcase))
+ .to contain_exactly(user)
+ end
+ end
end
describe "Respond to" do
@@ -3580,6 +3594,42 @@ RSpec.describe User do
end
end
+ describe '#source_groups_of_two_factor_authentication_requirement' do
+ let_it_be(:group_not_requiring_2FA) { create :group }
+ let(:user) { create :user }
+
+ before do
+ group.add_user(user, GroupMember::OWNER)
+ group_not_requiring_2FA.add_user(user, GroupMember::OWNER)
+ end
+
+ context 'when user is direct member of group requiring 2FA' do
+ let_it_be(:group) { create :group, require_two_factor_authentication: true }
+
+ it 'returns group requiring 2FA' do
+ expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group)
+ end
+ end
+
+ context 'when user is member of group which parent requires 2FA' do
+ let_it_be(:parent_group) { create :group, require_two_factor_authentication: true }
+ let_it_be(:group) { create :group, parent: parent_group }
+
+ it 'returns group requiring 2FA' do
+ expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group)
+ end
+ end
+
+ context 'when user is member of group which child requires 2FA' do
+ let_it_be(:group) { create :group }
+ let_it_be(:child_group) { create :group, require_two_factor_authentication: true, parent: group }
+
+ it 'returns group requiring 2FA' do
+ expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group)
+ end
+ end
+ end
+
describe '.active' do
before do
described_class.ghost
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index 99d224cb8e9..d8a345a79b0 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -332,10 +332,32 @@ RSpec.describe API::Badges do
context 'when deleting a badge' do
context 'and the source is a project' do
+ let(:badge) { project.group.badges.first }
+
it 'cannot delete badges owned by the project group' do
- delete api("/projects/#{project.id}/badges/#{project_group.badges.first.id}", maintainer)
+ expect do
+ delete api("/projects/#{project.id}/badges/#{badge.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end.not_to change { badge.reload.persisted? }
+ end
+ end
+ end
+
+ context 'when updating a badge' do
+ context 'and the source is a project' do
+ let(:badge) { project.group.badges.first }
+ let(:example_name) { 'BadgeName' }
+ let(:example_url) { 'http://www.example.com' }
+ let(:example_url2) { 'http://www.example1.com' }
+
+ it 'cannot update badges owned by the project group' do
+ expect do
+ put api("/projects/#{project.id}/badges/#{badge.id}", maintainer),
+ params: { name: example_name, link_url: example_url, image_url: example_url2 }
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end.not_to change { badge.reload.updated_at }
end
end
end
diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb
index 58c360d055b..d1ddaa277fc 100644
--- a/spec/requests/api/conan_packages_spec.rb
+++ b/spec/requests/api/conan_packages_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe API::ConanPackages do
let(:base_secret) { SecureRandom.base64(64) }
let(:auth_token) { personal_access_token.token }
- let(:job) { create(:ci_build, user: user) }
+ let(:job) { create(:ci_build, user: user, status: :running) }
let(:job_token) { job.token }
let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
@@ -94,6 +94,14 @@ RSpec.describe API::ConanPackages do
expect(response).to have_gitlab_http_status(:unauthorized)
end
+ it 'responds with 401 Unauthorized when the job is not running' do
+ job.update!(status: :failed)
+ jwt = build_jwt_from_job(job)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
context 'packages feature disabled' do
it 'responds with 404 Not Found' do
stub_packages_setting(enabled: false)
@@ -234,6 +242,18 @@ RSpec.describe API::ConanPackages do
end
end
+ shared_examples 'rejects invalid file_name' do |invalid_file_name|
+ let(:file_name) { invalid_file_name }
+
+ context 'with invalid file_name' do
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
shared_examples 'rejects recipe for invalid project' do
context 'with invalid recipe path' do
let(:recipe_path) { 'aa/bb/not-existing-project/ccc' }
@@ -717,8 +737,6 @@ RSpec.describe API::ConanPackages do
context 'without a file from workhorse' do
let(:params) { { file: nil } }
- it_behaves_like 'package workhorse uploads'
-
it 'rejects the request' do
subject
@@ -726,6 +744,10 @@ RSpec.describe API::ConanPackages do
end
end
+ context 'with a file' do
+ it_behaves_like 'package workhorse uploads'
+ end
+
context 'without a token' do
it 'rejects request without a token' do
headers_with_token.delete('HTTP_AUTHORIZATION')
@@ -884,16 +906,22 @@ RSpec.describe API::ConanPackages do
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
- subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token }
+ let(:file_name) { 'conanfile.py' }
+
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'workhorse authorization'
end
describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
- subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token }
+ let(:file_name) { 'conaninfo.txt' }
+
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token }
it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'workhorse authorization'
end
@@ -907,11 +935,13 @@ RSpec.describe API::ConanPackages do
method: :put,
file_key: :file,
params: params,
+ send_rewritten_field: true,
headers: headers_with_token
)
end
it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack'
it_behaves_like 'uploads a package file'
end
@@ -925,12 +955,15 @@ RSpec.describe API::ConanPackages do
method: :put,
file_key: :file,
params: params,
- headers: headers_with_token
+ headers: headers_with_token,
+ send_rewritten_field: true
)
end
it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest'
it_behaves_like 'uploads a package file'
+
context 'tracking the conan_package.tgz upload' do
let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb
index e08637629cc..ed852fe75c7 100644
--- a/spec/requests/api/generic_packages_spec.rb
+++ b/spec/requests/api/generic_packages_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe API::GenericPackages do
context 'authenticating using job token' do
it 'responds with 200 OK when valid job token is provided' do
- job_token = create(:ci_build, user: user).token
+ job_token = create(:ci_build, :running, user: user).token
ping(job_token: job_token)
diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb
index 2d7e319b0be..9d422ebbce2 100644
--- a/spec/requests/api/go_proxy_spec.rb
+++ b/spec/requests/api/go_proxy_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe API::GoProxy do
let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user }
- let_it_be(:job) { create :ci_build, user: user }
+ let_it_be(:job) { create :ci_build, user: user, status: :running }
let_it_be(:pa_token) { create :personal_access_token, user: user }
let_it_be(:modules) do
@@ -393,6 +393,13 @@ RSpec.describe API::GoProxy do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'returns unauthorized with a failed job token' do
+ job.update!(status: :failed)
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
it 'returns unauthorized with no authentication' do
get_resource
diff --git a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb
new file mode 100644
index 00000000000..328f4fb7b6e
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create a label or backlog board list' do
+ include GraphqlHelpers
+
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:board) { create(:board, group: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:dev_label) do
+ create(:group_label, title: 'Development', color: '#FFAABB', group: group)
+ end
+
+ let(:current_user) { user }
+ let(:mutation) { graphql_mutation(:board_list_create, input) }
+ let(:mutation_response) { graphql_mutation_response(:board_list_create) }
+
+ context 'the user is not allowed to read board lists' do
+ let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to admin board lists' do
+ before do
+ group.add_reporter(current_user)
+ end
+
+ describe 'backlog list' do
+ let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
+
+ it 'creates the list' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['list'])
+ .to include('position' => nil, 'listType' => 'backlog')
+ end
+ end
+
+ describe 'label list' do
+ let(:input) { { board_id: board.to_global_id.to_s, label_id: dev_label.to_global_id.to_s } }
+
+ it 'creates the list' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['list'])
+ .to include('position' => 0, 'listType' => 'label', 'label' => include('title' => 'Development'))
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index 8ade72635af..b71f87d2702 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -46,6 +46,31 @@ RSpec.describe 'Destroying a Snippet' do
expect(mutation_response).to have_key('snippet')
expect(mutation_response['snippet']).to be_nil
end
+
+ context 'when a bad gid is given' do
+ let!(:project) { create(:project, :private) }
+ let!(:snippet) { create(:project_snippet, :private, project: project, author: create(:user)) }
+ let!(:snippet_gid) { project.to_gid.to_s }
+
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors)
+ .to include(a_hash_including('message' => "#{snippet_gid} is not a valid ID for Snippet."))
+ end
+
+ it 'does not destroy the Snippet' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Snippet.count }
+ end
+
+ it 'does not destroy the Project' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Project.count }
+ end
+ end
end
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 090ff3c1b5a..9c0ea14e3e3 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -85,6 +85,27 @@ RSpec.describe API::Helpers do
end
it { is_expected.to eq(user) }
+
+ context 'when user should have 2fa enabled' do
+ before do
+ allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
+ allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier|
+ allow(verifier).to receive(:two_factor_grace_period_expired?).and_return(true)
+ end
+ end
+
+ context 'when 2fa is not enabled' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when 2fa is enabled' do
+ before do
+ allow(user).to receive(:two_factor_enabled?).and_return(true)
+ end
+
+ it { is_expected.to eq(user) }
+ end
+ end
end
context "PUT request" do
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 6555a055678..f90e04bbd2e 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe API::Jobs do
end
describe 'GET /projects/:id/jobs' do
- let(:query) { Hash.new }
+ let(:query) { {} }
before do |example|
unless example.metadata[:skip_before_request]
@@ -167,7 +167,7 @@ RSpec.describe API::Jobs do
end
describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do
- let(:query) { Hash.new }
+ let(:query) { {} }
before do |example|
unless example.metadata[:skip_before_request]
@@ -296,7 +296,7 @@ RSpec.describe API::Jobs do
project: downstream_pipeline.project)
end
- let(:query) { Hash.new }
+ let(:query) { {} }
before do |example|
unless example.metadata[:skip_before_request]
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index 99588694bab..04d1d2cf1e8 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe API::MavenPackages do
let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first }
let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
- let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
@@ -102,11 +102,25 @@ RSpec.describe API::MavenPackages do
end
shared_examples 'downloads with a job token' do
- it 'allows download with job token' do
- download_file(package_file.file_name, job_token: job.token)
+ context 'with a running job' do
+ it 'allows download with job token' do
+ download_file(package_file.file_name, job_token: job.token)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.media_type).to eq('application/octet-stream')
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ context 'with a finished job' do
+ before do
+ job.update!(status: :failed)
+ end
+
+ it 'returns unauthorized error' do
+ download_file(package_file.file_name, job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
end
@@ -569,13 +583,20 @@ RSpec.describe API::MavenPackages do
expect(jar_file.file_name).to eq(file_upload.original_filename)
end
- it 'allows upload with job token' do
+ it 'allows upload with running job token' do
upload_file(params.merge(job_token: job.token))
expect(response).to have_gitlab_http_status(:ok)
expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
end
+ it 'rejects upload without running job token' do
+ job.update!(status: :failed)
+ upload_file(params.merge(job_token: job.token))
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
it 'allows upload with deploy token' do
upload_file(params, headers_with_deploy_token)
diff --git a/spec/requests/api/npm_packages_spec.rb b/spec/requests/api/npm_packages_spec.rb
index 94647123df0..108ea84b7e6 100644
--- a/spec/requests/api/npm_packages_spec.rb
+++ b/spec/requests/api/npm_packages_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe API::NpmPackages do
let_it_be(:package, reload: true) { create(:npm_package, project: project) }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
- let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
@@ -27,12 +27,19 @@ RSpec.describe API::NpmPackages do
expect_a_valid_package_response
end
- it 'returns the package info with job token' do
+ it 'returns the package info with running job token' do
get_package_with_job_token(package)
expect_a_valid_package_response
end
+ it 'denies request without running job token' do
+ job.update!(status: :success)
+ get_package_with_job_token(package)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
it 'denies request without oauth token' do
get_package(package)
diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb
index 37170592b60..62f244c433b 100644
--- a/spec/requests/api/nuget_packages_spec.rb
+++ b/spec/requests/api/nuget_packages_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe API::NugetPackages do
end
with_them do
- let(:job) { user_token ? create(:ci_build, project: project, user: user) : double(token: 'wrong') }
+ let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') }
let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) }
subject { get api(url), headers: headers }
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index a9a92a4d3cd..779ae983886 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -671,12 +671,20 @@ RSpec.describe API::Releases do
end
context 'when a valid token is provided' do
- it 'creates the release' do
+ it 'creates the release for a running job' do
+ job.update!(status: :running)
post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token)
expect(response).to have_gitlab_http_status(:created)
expect(project.releases.last.description).to eq('Another nice release')
end
+
+ it 'returns an :unauthorized error for a completed job' do
+ job.success!
+ post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
end
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index c6cba39314b..c47a12456c3 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe API::Terraform::State do
let(:auth_header) { job_basic_auth_header(job) }
context 'with maintainer permissions' do
- let(:job) { create(:ci_build, project: project, user: maintainer) }
+ let(:job) { create(:ci_build, status: :running, project: project, user: maintainer) }
it 'returns terraform state belonging to a project of given state name' do
request
@@ -81,6 +81,13 @@ RSpec.describe API::Terraform::State do
expect(response.body).to eq(state.file.read)
end
+ it 'returns unauthorized if the the job is not running' do
+ job.update!(status: :failed)
+ request
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
context 'for a project that does not exist' do
let(:project_id) { '0000' }
@@ -93,7 +100,7 @@ RSpec.describe API::Terraform::State do
end
context 'with developer permissions' do
- let(:job) { create(:ci_build, project: project, user: developer) }
+ let(:job) { create(:ci_build, status: :running, project: project, user: developer) }
it 'returns terraform state belonging to a project of given state name' do
request
diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb
index 58ac723ee55..8b8beb057a9 100644
--- a/spec/services/applications/create_service_spec.rb
+++ b/spec/services/applications/create_service_spec.rb
@@ -6,9 +6,24 @@ RSpec.describe ::Applications::CreateService do
include TestRequestHelpers
let(:user) { create(:user) }
- let(:params) { attributes_for(:application) }
subject { described_class.new(user, params) }
- it { expect { subject.execute(test_request) }.to change { Doorkeeper::Application.count }.by(1) }
+ context 'when scopes are present' do
+ let(:params) { attributes_for(:application, scopes: ['read_user']) }
+
+ it { expect { subject.execute(test_request) }.to change { Doorkeeper::Application.count }.by(1) }
+ end
+
+ context 'when scopes are missing' do
+ let(:params) { attributes_for(:application) }
+
+ it { expect { subject.execute(test_request) }.not_to change { Doorkeeper::Application.count } }
+
+ it 'includes blank scopes error message' do
+ application = subject.execute(test_request)
+
+ expect(application.errors.full_messages).to include "Scopes can't be blank"
+ end
+ end
end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 8d58c4b27e1..bc85f4f0087 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -654,6 +654,19 @@ RSpec.describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
end
+
+ context 'for project that disables repository' do
+ let(:project) { create(:project, :public, :repository_disabled) }
+
+ context 'disallow when pulling' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:pull"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+ end
end
context 'registry catalog browsing authorized as admin' do
diff --git a/spec/services/branches/delete_service_spec.rb b/spec/services/branches/delete_service_spec.rb
index f1e7c9340b1..291431c1723 100644
--- a/spec/services/branches/delete_service_spec.rb
+++ b/spec/services/branches/delete_service_spec.rb
@@ -37,6 +37,21 @@ RSpec.describe Branches::DeleteService do
end
it_behaves_like 'a deleted branch', 'feature'
+
+ context 'when Gitlab::Git::CommandError is raised' do
+ before do
+ allow(repository).to receive(:rm_branch) do
+ raise Gitlab::Git::CommandError.new('Could not update patch')
+ end
+ end
+
+ it 'handles and returns error' do
+ result = service.execute('feature')
+
+ expect(result.status).to eq(:error)
+ expect(result.message).to eq('Could not update patch')
+ end
+ end
end
context 'when user does not have access to push to repository' do
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index 18fab9623ec..ac077e3c30e 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -106,9 +106,23 @@ RSpec.describe Ci::PipelineTriggerService do
let(:params) { { token: job.token, ref: 'master', variables: nil } }
let(:job) { create(:ci_build, :success, pipeline: pipeline, user: user) }
- it 'does nothing' do
+ it 'does nothing', :aggregate_failures do
+ expect { result }.not_to change { Ci::Pipeline.count }
+ expect(result[:message]).to eq('Job is not running')
+ expect(result[:http_status]).to eq(401)
+ end
+ end
+
+ context 'when job does not have a project' do
+ let(:params) { { token: job.token, ref: 'master', variables: nil } }
+ let(:job) { create(:ci_build, status: :running, pipeline: pipeline, user: user) }
+
+ it 'does nothing', :aggregate_failures do
+ job.update!(project: nil)
+
expect { result }.not_to change { Ci::Pipeline.count }
- expect(result[:message]).to eq('400 Job has to be running')
+ expect(result[:message]).to eq('Project has been deleted!')
+ expect(result[:http_status]).to eq(401)
end
end
diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb
index 3d12400a47b..5b47cf0ecde 100644
--- a/spec/services/clusters/aws/authorize_role_service_spec.rb
+++ b/spec/services/clusters/aws/authorize_role_service_spec.rb
@@ -3,47 +3,34 @@
require 'spec_helper'
RSpec.describe Clusters::Aws::AuthorizeRoleService do
- let(:user) { create(:user) }
+ subject { described_class.new(user, params: params).execute }
+
+ let(:role) { create(:aws_role) }
+ let(:user) { role.user }
let(:credentials) { instance_double(Aws::Credentials) }
let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) }
+ let(:role_arn) { 'arn:my-role' }
let(:params) do
params = ActionController::Parameters.new({
cluster: {
- role_arn: 'arn:my-role',
- role_external_id: 'external-id'
+ role_arn: role_arn
}
})
- params.require(:cluster).permit(:role_arn, :role_external_id)
+ params.require(:cluster).permit(:role_arn)
end
- subject { described_class.new(user, params: params).execute }
-
before do
allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
.with(instance_of(Aws::Role)).and_return(credentials_service)
end
- context 'role does not exist' do
- it 'creates an Aws::Role record and returns a set of credentials' do
- expect(user).to receive(:create_aws_role!)
- .with(params).and_call_original
-
- expect(subject.status).to eq(:ok)
- expect(subject.body).to eq(credentials)
- end
- end
-
- context 'role already exists' do
- let(:role) { create(:aws_role, user: user) }
-
+ context 'role exists' do
it 'updates the existing Aws::Role record and returns a set of credentials' do
- expect(role).to receive(:update!)
- .with(params).and_call_original
-
expect(subject.status).to eq(:ok)
expect(subject.body).to eq(credentials)
+ expect(role.reload.role_arn).to eq(role_arn)
end
end
@@ -61,11 +48,14 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do
end
end
- context 'cannot create role' do
- before do
- allow(user).to receive(:create_aws_role!)
- .and_raise(ActiveRecord::RecordInvalid.new(user))
- end
+ context 'role does not exist' do
+ let(:user) { create(:user) }
+
+ include_examples 'bad request'
+ end
+
+ context 'supplied ARN is invalid' do
+ let(:role_arn) { 'invalid' }
include_examples 'bad request'
end
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 13e7b4c1006..5c90f1f54ea 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -292,6 +292,10 @@ RSpec.describe Members::DestroyService do
before do
create(:group_member, :developer, group: subsubgroup, user: member_user)
+ create(:project_member, :invited, project: group_project, created_by: member_user)
+ create(:group_member, :invited, group: group, created_by: member_user)
+ create(:project_member, :invited, project: subsubproject, created_by: member_user)
+ create(:group_member, :invited, group: subgroup, created_by: member_user)
subsubproject.add_developer(member_user)
control_project.add_maintainer(user)
@@ -325,5 +329,41 @@ RSpec.describe Members::DestroyService do
it 'does not remove the user from the control project' do
expect(control_project.members.map(&:user)).to include(user)
end
+
+ it 'removes group members invited by deleted user' do
+ expect(group.members.not_accepted_invitations_by_user(member_user)).to be_empty
+ end
+
+ it 'removes project members invited by deleted user' do
+ expect(group_project.members.not_accepted_invitations_by_user(member_user)).to be_empty
+ end
+
+ it 'removes subgroup members invited by deleted user' do
+ expect(subgroup.members.not_accepted_invitations_by_user(member_user)).to be_empty
+ end
+
+ it 'removes subproject members invited by deleted user' do
+ expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).to be_empty
+ end
+ end
+
+ context 'deletion of invitations created by deleted project member' do
+ let(:user) { project.owner }
+ let(:member_user) { create(:user) }
+ let(:opts) { {} }
+
+ let(:project) { create(:project) }
+
+ before do
+ create(:project_member, :invited, project: project, created_by: member_user)
+
+ project_member = create(:project_member, :maintainer, user: member_user, project: project)
+
+ described_class.new(user).execute(project_member, opts)
+ end
+
+ it 'removes project members invited by deleted user' do
+ expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty
+ end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 47bc6bcd989..87d6f99e8aa 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -151,9 +151,9 @@ RSpec.describe NotificationService, :mailer do
end
shared_examples_for 'participating notifications' do
- it_should_behave_like 'participating by note notification'
- it_should_behave_like 'participating by author notification'
- it_should_behave_like 'participating by assignee notification'
+ it_behaves_like 'participating by note notification'
+ it_behaves_like 'participating by author notification'
+ it_behaves_like 'participating by assignee notification'
end
describe '#async' do
@@ -1682,13 +1682,13 @@ RSpec.describe NotificationService, :mailer do
end
context 'participating' do
- it_should_behave_like 'participating by assignee notification' do
+ it_behaves_like 'participating by assignee notification' do
let(:participant) { create(:user, username: 'user-participant')}
let(:issuable) { merge_request }
let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) }
end
- it_should_behave_like 'participating by note notification' do
+ it_behaves_like 'participating by note notification' do
let(:participant) { create(:user, username: 'user-participant')}
let(:issuable) { merge_request }
let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) }
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index 09244db8010..a452f88c75f 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -56,6 +56,39 @@ RSpec.describe Projects::UpdateRemoteMirrorService do
expect(remote_mirror.last_error).to include('Badly broken')
end
+ context 'when the URL is blocked' do
+ before do
+ allow(Gitlab::UrlBlocker).to receive(:blocked_url?).and_return(true)
+ end
+
+ it 'fails and returns error status' do
+ expect(execute!).to eq(status: :error, message: 'The remote mirror URL is invalid.')
+ end
+ end
+
+ context "when given URLs containing escaped elements" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:url, :result_status) do
+ "https://user:0a%23@test.example.com/project.git" | :success
+ "https://git.example.com:1%2F%2F@source.developers.google.com/project.git" | :success
+ CGI.escape("git://localhost:1234/some-path?some-query=some-val\#@example.com/") | :error
+ CGI.escape(CGI.escape("https://user:0a%23@test.example.com/project.git")) | :error
+ end
+
+ with_them do
+ before do
+ allow(remote_mirror).to receive(:url).and_return(url)
+ end
+
+ it "returns expected status" do
+ result = execute!
+
+ expect(result[:status]).to eq(result_status)
+ end
+ end
+ end
+
context 'when the update fails because of a `Gitlab::Git::CommandError`' do
before do
allow(remote_mirror).to receive(:update_repository)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 395930c8cbc..06ec97d65b2 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -47,10 +47,10 @@ require_relative('../ee/spec/spec_helper') if Gitlab.ee?
require Rails.root.join("spec/support/helpers/git_helpers.rb")
# Then the rest
-Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f }
-Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
-Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
-Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
+Dir[Rails.root.join("spec/support/helpers/*.rb")].sort.each { |f| require f }
+Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].sort.each { |f| require f }
+Dir[Rails.root.join("spec/support/shared_examples/*.rb")].sort.each { |f| require f }
+Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f }
quality_level = Quality::TestLevel.new
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index 0486eccf48c..476b7d34ee5 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -21,7 +21,7 @@ module StubObjectStorage
proxy_download: false,
background_upload: false,
direct_upload: false
- )
+ )
allow(config).to receive(:enabled) { enabled }
allow(config).to receive(:proxy_download) { proxy_download }
allow(config).to receive(:background_upload) { background_upload }
diff --git a/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb b/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb
new file mode 100644
index 00000000000..aa17e72d08e
--- /dev/null
+++ b/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'GET new cluster shared examples' do
+ describe 'EKS cluster' do
+ context 'user already has an associated AWS role' do
+ let!(:role) { create(:aws_role, user: user) }
+
+ it 'does not create an Aws::Role record' do
+ expect { go(provider: 'aws') }.not_to change { Aws::Role.count }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(assigns(:aws_role)).to eq(role)
+ end
+ end
+
+ context 'user does not have an associated AWS role' do
+ it 'creates an Aws::Role record' do
+ expect { go(provider: 'aws') }.to change { Aws::Role.count }
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ role = assigns(:aws_role)
+ expect(role.user).to eq(user)
+ expect(role.role_arn).to be_nil
+ expect(role.role_external_id).to be_present
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 62102a3cbec..50f38b57dc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.161.0.tgz#661e8d19862dfba0e4c558e2eb6d64b402c1453e"
integrity sha512-qsbboEICn08ZoEoAX/TuYygsFaXlzsCY+CfmdOzqvJbOdfHhVXmrJBxd2hP2qqjTZm2PkbRRmn+03+ce1jvatQ==
-"@gitlab/ui@20.16.0":
- version "20.16.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.16.0.tgz#45bad5500e6f47bc4bebb95a17b446f254f9ee21"
- integrity sha512-k1+X8RgBXknCu40dEWTjE0Sd9jzGmpoPWPlgmoo28tE1vewhRmCOa8aL0tAcm6VNg5av5ZEqoQEJvgFKxLQB1Q==
+"@gitlab/ui@20.17.0":
+ version "20.17.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.17.0.tgz#5a148c778247efc08df5c093acf5e82069823970"
+ integrity sha512-yDZ+B9/rq7t6OqIHMxPk120KzAO7xM1JKlE9ahkDyijC7lohQy9ygQGFlvVHo1Bic4Y/IW7nzeeZErjzJ3PPtA==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"