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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-06 09:09:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-06 09:09:43 +0300
commitb6fd4f66153660e126eae62ff7eb2cfa761eb47c (patch)
treeef32fd51aea8347220dff9a3753d958b5e3cf1c7
parent81e0e55a182eb01ad174fb2b50913eec48c52ca7 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/build-images.gitlab-ci.yml3
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue7
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/index.js5
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb8
-rw-r--r--app/controllers/projects/cluster_agents_controller.rb19
-rw-r--r--app/helpers/projects/cluster_agents_helper.rb10
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml13
-rw-r--r--app/views/projects/cluster_agents/show.html.haml4
-rw-r--r--doc/ci/jobs/ci_job_token.md53
-rw-r--r--doc/ci/pipelines/img/multi_project_pipeline_graph.pngbin10671 -> 0 bytes
-rw-r--r--doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.pngbin0 -> 30119 bytes
-rw-r--r--doc/ci/pipelines/img/parent_pipeline_graph_expanded_v12_6.pngbin96087 -> 0 bytes
-rw-r--r--doc/ci/pipelines/img/parent_pipeline_graph_expanded_v14_3.pngbin0 -> 40079 bytes
-rw-r--r--doc/ci/pipelines/multi_project_pipelines.md2
-rw-r--r--doc/ci/pipelines/parent_child_pipelines.md2
-rw-r--r--doc/ssh/index.md4
-rw-r--r--package.json2
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb34
-rw-r--r--spec/features/profiles/two_factor_auths_spec.rb59
-rw-r--r--spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap99
-rw-r--r--spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js80
-rw-r--r--spec/helpers/projects/cluster_agents_helper_spec.rb21
-rw-r--r--spec/requests/projects/cluster_agents_controller_spec.rb40
-rw-r--r--yarn.lock8
25 files changed, 301 insertions, 174 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d67137704dc..d0a0c7b2414 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -93,6 +93,8 @@ variables:
# For the default QA image, we use $CI_COMMIT_SHA as tag since it's always available and we override it for specific workflow.rules (see above)
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA}"
+ # Default latest tag for particular branch
+ QA_IMAGE_BRANCH: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
# Preparing custom clone path to reduce space used by all random forks
# on GitLab.com's Shared Runners. Our main forks - especially the security
diff --git a/.gitlab/ci/build-images.gitlab-ci.yml b/.gitlab/ci/build-images.gitlab-ci.yml
index 0169f017063..6a222d8937f 100644
--- a/.gitlab/ci/build-images.gitlab-ci.yml
+++ b/.gitlab/ci/build-images.gitlab-ci.yml
@@ -28,7 +28,8 @@ build-qa-image:
script:
- !reference [.base-image-build, script]
- echo $QA_IMAGE
- - /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
+ - echo $QA_IMAGE_BRANCH
+ - /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --destination=${QA_IMAGE_BRANCH} --cache=true
# This image is used by:
# - The `CNG` pipelines (via the `review-build-cng` job): https://gitlab.com/gitlab-org/build/CNG/-/blob/cfc67136d711e1c8c409bf8e57427a644393da2f/.gitlab-ci.yml#L335
diff --git a/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue b/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue
index 280c222c380..0b748f18cb2 100644
--- a/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue
+++ b/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue
@@ -24,6 +24,7 @@ export default {
},
inject: [
'webauthnEnabled',
+ 'isCurrentPasswordRequired',
'profileTwoFactorAuthPath',
'profileTwoFactorAuthMethod',
'codesProfileTwoFactorAuthPath',
@@ -64,7 +65,11 @@ export default {
<input type="hidden" name="_method" data-testid="test-2fa-method-field" :value="method" />
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
- <gl-form-group :label="$options.i18n.currentPassword" label-for="current-password">
+ <gl-form-group
+ v-if="isCurrentPasswordRequired"
+ :label="$options.i18n.currentPassword"
+ label-for="current-password"
+ >
<gl-form-input
id="current-password"
type="password"
diff --git a/app/assets/javascripts/authentication/two_factor_auth/index.js b/app/assets/javascripts/authentication/two_factor_auth/index.js
index f663c0705e6..7d21c19ac4c 100644
--- a/app/assets/javascripts/authentication/two_factor_auth/index.js
+++ b/app/assets/javascripts/authentication/two_factor_auth/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
import { updateHistory, removeParams } from '~/lib/utils/url_utility';
import ManageTwoFactorForm from './components/manage_two_factor_form.vue';
import RecoveryCodes from './components/recovery_codes.vue';
@@ -13,16 +14,20 @@ export const initManageTwoFactorForm = () => {
const {
webauthnEnabled = false,
+ currentPasswordRequired,
profileTwoFactorAuthPath = '',
profileTwoFactorAuthMethod = '',
codesProfileTwoFactorAuthPath = '',
codesProfileTwoFactorAuthMethod = '',
} = el.dataset;
+ const isCurrentPasswordRequired = parseBoolean(currentPasswordRequired);
+
return new Vue({
el,
provide: {
webauthnEnabled,
+ isCurrentPasswordRequired,
profileTwoFactorAuthPath,
profileTwoFactorAuthMethod,
codesProfileTwoFactorAuthPath,
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 3e397684ffe..e0b5d6be155 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -3,7 +3,9 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_two_factor_requirement
before_action :ensure_verified_primary_email, only: [:show, :create]
- before_action :validate_current_password, only: [:create, :codes, :destroy]
+ before_action :validate_current_password, only: [:create, :codes, :destroy], if: :current_password_required?
+
+ helper_method :current_password_required?
before_action do
push_frontend_feature_flag(:webauthn)
@@ -144,6 +146,10 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
redirect_to profile_two_factor_auth_path, alert: _('You must provide a valid current password')
end
+ def current_password_required?
+ !current_user.password_automatically_set?
+ end
+
def build_qr_code
uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host)
RQRCode.render_qrcode(uri, :svg, level: :m, unit: 3)
diff --git a/app/controllers/projects/cluster_agents_controller.rb b/app/controllers/projects/cluster_agents_controller.rb
new file mode 100644
index 00000000000..e7fbe93131d
--- /dev/null
+++ b/app/controllers/projects/cluster_agents_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class Projects::ClusterAgentsController < Projects::ApplicationController
+ before_action :authorize_can_read_cluster_agent!
+
+ feature_category :kubernetes_management
+
+ def show
+ @agent_name = params[:name]
+ end
+
+ private
+
+ def authorize_can_read_cluster_agent!
+ return if can?(current_user, :admin_cluster, project)
+
+ access_denied!
+ end
+end
diff --git a/app/helpers/projects/cluster_agents_helper.rb b/app/helpers/projects/cluster_agents_helper.rb
new file mode 100644
index 00000000000..20fa721cc3b
--- /dev/null
+++ b/app/helpers/projects/cluster_agents_helper.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Projects::ClusterAgentsHelper
+ def js_cluster_agent_details_data(agent_name, project)
+ {
+ agent_name: agent_name,
+ project_path: project.full_path
+ }
+ end
+end
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index d1d6b6301b8..bd3cb7e60f0 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -17,7 +17,7 @@
= _("You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.")
%p
= _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.')
- .js-manage-two-factor-form{ data: { webauthn_enabled: webauthn_enabled, profile_two_factor_auth_path: profile_two_factor_auth_path, profile_two_factor_auth_method: 'delete', codes_profile_two_factor_auth_path: codes_profile_two_factor_auth_path, codes_profile_two_factor_auth_method: 'post' } }
+ .js-manage-two-factor-form{ data: { webauthn_enabled: webauthn_enabled, current_password_required: current_password_required?.to_s, profile_two_factor_auth_path: profile_two_factor_auth_path, profile_two_factor_auth_method: 'delete', codes_profile_two_factor_auth_path: codes_profile_two_factor_auth_path, codes_profile_two_factor_auth_method: 'post' } }
- else
%p
@@ -47,11 +47,12 @@
.form-group
= label_tag :pin_code, _('Pin code'), class: "label-bold"
= text_field_tag :pin_code, nil, class: "form-control gl-form-input", required: true, data: { qa_selector: 'pin_code_field' }
- .form-group
- = label_tag :current_password, _('Current password'), class: 'label-bold'
- = password_field_tag :current_password, nil, required: true, class: 'form-control gl-form-input', data: { qa_selector: 'current_password_field' }
- %p.form-text.text-muted
- = _('Your current password is required to register a two-factor authenticator app.')
+ - if current_password_required?
+ .form-group
+ = label_tag :current_password, _('Current password'), class: 'label-bold'
+ = password_field_tag :current_password, nil, required: true, class: 'form-control gl-form-input', data: { qa_selector: 'current_password_field' }
+ %p.form-text.text-muted
+ = _('Your current password is required to register a two-factor authenticator app.')
.gl-mt-3
= submit_tag _('Register with two-factor app'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'register_2fa_app_button' }
diff --git a/app/views/projects/cluster_agents/show.html.haml b/app/views/projects/cluster_agents/show.html.haml
new file mode 100644
index 00000000000..a2d3426d99c
--- /dev/null
+++ b/app/views/projects/cluster_agents/show.html.haml
@@ -0,0 +1,4 @@
+- add_to_breadcrumbs _('Kubernetes'), project_clusters_path(@project)
+- page_title @agent_name
+
+#js-cluster-agent-details{ data: js_cluster_agent_details_data(@agent_name, @project) }
diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md
index 1cb37e3d650..91b8bcfd337 100644
--- a/doc/ci/jobs/ci_job_token.md
+++ b/doc/ci/jobs/ci_job_token.md
@@ -23,8 +23,8 @@ You can use a GitLab CI/CD job token to authenticate with specific API endpoints
- [Release creation](../../api/releases/index.md#create-a-release).
- [Terraform plan](../../user/infrastructure/index.md).
-The token has the same permissions to access the API as the user that triggers the
-pipeline. Therefore, this user must be assigned to [a role that has the required privileges](../../user/permissions.md#gitlab-cicd-permissions).
+The token has the same permissions to access the API as the user that executes the
+job. Therefore, this user must be assigned to [a role that has the required privileges](../../user/permissions.md#gitlab-cicd-permissions).
The token is valid only while the pipeline job runs. After the job finishes, you can't
use the token anymore.
@@ -89,7 +89,7 @@ to make an API request to a private project `B`, then `B` must be added to the a
If project `B` is public or internal, it doesn't need to be added to the allowlist.
The job token scope is only for controlling access to private projects.
-To enable and configure the job token scope limit:
+### Configure the job token scope limit
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > CI/CD**.
@@ -162,3 +162,50 @@ build_submodule:
```
Read more about the [jobs artifacts API](../../api/job_artifacts.md#download-the-artifacts-archive).
+
+## Troubleshooting
+
+CI job token failures are usually shown as responses like `404 Not Found` or similar:
+
+- Unauthorized Git clone:
+
+ ```plaintext
+ $ git clone https://gitlab-ci-token:$CI_JOB_TOKEN@gitlab.com/fabiopitino/test2.git
+
+ Cloning into 'test2'...
+ remote: The project you were looking for could not be found or you don't have permission to view it.
+ fatal: repository 'https://gitlab-ci-token:[MASKED]@gitlab.com/<namespace>/<project>.git/' not found
+ ```
+
+- Unauthorized package download:
+
+ ```plaintext
+ $ wget --header="JOB-TOKEN: $CI_JOB_TOKEN" ${CI_API_V4_URL}/projects/1234/packages/generic/my_package/0.0.1/file.txt
+
+ --2021-09-23 11:00:13-- https://gitlab.com/api/v4/projects/1234/packages/generic/my_package/0.0.1/file.txt
+ Resolving gitlab.com (gitlab.com)... 172.65.251.78, 2606:4700:90:0:f22e:fbec:5bed:a9b9
+ Connecting to gitlab.com (gitlab.com)|172.65.251.78|:443... connected.
+ HTTP request sent, awaiting response... 404 Not Found
+ 2021-09-23 11:00:13 ERROR 404: Not Found.
+ ```
+
+- Unauthorized API request:
+
+ ```plaintext
+ $ curl --verbose --request POST --form "token=$CI_JOB_TOKEN" --form ref=master "https://gitlab.com/api/v4/projects/1234/trigger/pipeline"
+
+ < HTTP/2 404
+ < date: Thu, 23 Sep 2021 11:00:12 GMT
+ {"message":"404 Not Found"}
+ < content-type: application/json
+ ```
+
+While troubleshooting CI/CD job token authentication issues, be aware that:
+
+- When the [CI/CD job token limit](#limit-gitlab-cicd-job-token-access) is enabled,
+ and the job token is being used to access a different project:
+ - The user that executes the job must be a member of the project that is being accessed.
+ - The user must have the [permissions](../../user/permissions.md) to perform the action.
+ - The target project must be [allowlisted for the job token scope limit](#configure-the-job-token-scope-limit).
+- The CI job token becomes invalid if the job is no longer running, has been erased,
+ or if the project is in the process of being deleted.
diff --git a/doc/ci/pipelines/img/multi_project_pipeline_graph.png b/doc/ci/pipelines/img/multi_project_pipeline_graph.png
deleted file mode 100644
index 723a455cb4a..00000000000
--- a/doc/ci/pipelines/img/multi_project_pipeline_graph.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.png b/doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.png
new file mode 100644
index 00000000000..aadf8bb0979
--- /dev/null
+++ b/doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.png
Binary files differ
diff --git a/doc/ci/pipelines/img/parent_pipeline_graph_expanded_v12_6.png b/doc/ci/pipelines/img/parent_pipeline_graph_expanded_v12_6.png
deleted file mode 100644
index db18cc201fc..00000000000
--- a/doc/ci/pipelines/img/parent_pipeline_graph_expanded_v12_6.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/pipelines/img/parent_pipeline_graph_expanded_v14_3.png b/doc/ci/pipelines/img/parent_pipeline_graph_expanded_v14_3.png
new file mode 100644
index 00000000000..206e4eeec05
--- /dev/null
+++ b/doc/ci/pipelines/img/parent_pipeline_graph_expanded_v14_3.png
Binary files differ
diff --git a/doc/ci/pipelines/multi_project_pipelines.md b/doc/ci/pipelines/multi_project_pipelines.md
index 8390e85d57b..afb0a75e504 100644
--- a/doc/ci/pipelines/multi_project_pipelines.md
+++ b/doc/ci/pipelines/multi_project_pipelines.md
@@ -321,7 +321,7 @@ downstream projects. On self-managed instances, an administrator can change this
When you configure GitLab CI/CD for your project, you can visualize the stages of your
[jobs](index.md#configure-a-pipeline) on a [pipeline graph](index.md#visualize-pipelines).
-![Multi-project pipeline graph](img/multi_project_pipeline_graph.png)
+![Multi-project pipeline graph](img/multi_project_pipeline_graph_v14_3.png)
In the merge request, on the **Pipelines** tab, multi-project pipeline mini-graphs are displayed.
They expand and are shown adjacent to each other when hovering (or tapping on touchscreen devices).
diff --git a/doc/ci/pipelines/parent_child_pipelines.md b/doc/ci/pipelines/parent_child_pipelines.md
index 71f778d81b3..46a4ff775c4 100644
--- a/doc/ci/pipelines/parent_child_pipelines.md
+++ b/doc/ci/pipelines/parent_child_pipelines.md
@@ -23,7 +23,7 @@ Additionally, sometimes the behavior of a pipeline needs to be more dynamic. The
to choose to start sub-pipelines (or not) is a powerful ability, especially if the
YAML is dynamically generated.
-![Parent pipeline graph expanded](img/parent_pipeline_graph_expanded_v12_6.png)
+![Parent pipeline graph expanded](img/parent_pipeline_graph_expanded_v14_3.png)
Similarly to [multi-project pipelines](multi_project_pipelines.md), a pipeline can trigger a
set of concurrently running child pipelines, but within the same project:
diff --git a/doc/ssh/index.md b/doc/ssh/index.md
index 94c157697ce..6f886309640 100644
--- a/doc/ssh/index.md
+++ b/doc/ssh/index.md
@@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: howto, reference
---
-# GitLab and SSH keys
+# GitLab and SSH keys **(FREE)**
Git is a distributed version control system, which means you can work locally,
then share or "push" your changes to a server. In this case, the server is GitLab.
@@ -213,7 +213,7 @@ To use SSH with GitLab, copy your public key to your GitLab account.
which starts with `ssh-ed25519` or `ssh-rsa`, and may end with a comment.
1. In the **Title** box, type a description, like `Work Laptop` or
`Home Workstation`.
-1. Optional. In the **Expires at** box, select an expiration date. (Introduced in [GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/36243).)
+1. Optional. In the **Expires at** box, select an expiration date. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36243) in GitLab 12.9.)
In:
- GitLab 13.12 and earlier, the expiration date is informational only. It doesn't prevent
you from using the key. Administrators can view expiration dates and use them for
diff --git a/package.json b/package.json
index b55981ce95e..0ebe0245078 100644
--- a/package.json
+++ b/package.json
@@ -113,7 +113,7 @@
"codesandbox-api": "0.0.23",
"compression-webpack-plugin": "^5.0.2",
"copy-webpack-plugin": "^6.4.1",
- "core-js": "^3.18.1",
+ "core-js": "^3.18.2",
"cron-validator": "^1.1.1",
"cropper": "^2.3.0",
"css-loader": "^2.1.1",
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index 0af04e58903..e57bd5be937 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -31,11 +31,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do
shared_examples 'user must enter a valid current password' do
let(:current_password) { '123' }
+ let(:redirect_path) { profile_two_factor_auth_path }
it 'requires the current password', :aggregate_failures do
go
- expect(response).to redirect_to(profile_two_factor_auth_path)
+ expect(response).to redirect_to(redirect_path)
expect(flash[:alert]).to eq(_('You must provide a valid current password'))
end
@@ -48,6 +49,19 @@ RSpec.describe Profiles::TwoFactorAuthsController do
expect(user.reload).to be_access_locked
end
end
+
+ context 'when user authenticates with an external service' do
+ before do
+ allow(user).to receive(:password_automatically_set?).and_return(true)
+ end
+
+ it 'does not require the current password', :aggregate_failures do
+ go
+
+ expect(response).not_to redirect_to(redirect_path)
+ expect(flash[:alert]).to be_nil
+ end
+ end
end
describe 'GET show' do
@@ -188,7 +202,9 @@ RSpec.describe Profiles::TwoFactorAuthsController do
end
describe 'DELETE destroy' do
- subject { delete :destroy, params: { current_password: current_password } }
+ def go
+ delete :destroy, params: { current_password: current_password }
+ end
let(:current_password) { user.password }
@@ -196,40 +212,38 @@ RSpec.describe Profiles::TwoFactorAuthsController do
let_it_be_with_reload(:user) { create(:user, :two_factor) }
it 'disables two factor' do
- subject
+ go
expect(user.reload.two_factor_enabled?).to eq(false)
end
it 'redirects to profile_account_path' do
- subject
+ go
expect(response).to redirect_to(profile_account_path)
end
it 'displays a notice on success' do
- subject
+ go
expect(flash[:notice])
.to eq _('Two-factor authentication has been disabled successfully!')
end
- it_behaves_like 'user must enter a valid current password' do
- let(:go) { delete :destroy, params: { current_password: current_password } }
- end
+ it_behaves_like 'user must enter a valid current password'
end
context 'for a user that does not have 2FA enabled' do
let_it_be_with_reload(:user) { create(:user) }
it 'redirects to profile_account_path' do
- subject
+ go
expect(response).to redirect_to(profile_account_path)
end
it 'displays an alert on failure' do
- subject
+ go
expect(flash[:alert])
.to eq _('Two-factor authentication is not enabled for this user')
diff --git a/spec/features/profiles/two_factor_auths_spec.rb b/spec/features/profiles/two_factor_auths_spec.rb
index e1feca5031a..3f5789e119a 100644
--- a/spec/features/profiles/two_factor_auths_spec.rb
+++ b/spec/features/profiles/two_factor_auths_spec.rb
@@ -5,20 +5,16 @@ require 'spec_helper'
RSpec.describe 'Two factor auths' do
context 'when signed in' do
before do
- allow(Gitlab).to receive(:com?) { true }
+ sign_in(user)
end
context 'when user has two-factor authentication disabled' do
- let(:user) { create(:user ) }
-
- before do
- sign_in(user)
- end
+ let_it_be(:user) { create(:user ) }
it 'requires the current password to set up two factor authentication', :js do
visit profile_two_factor_auth_path
- register_2fa(user.reload.current_otp, '123')
+ register_2fa(user.current_otp, '123')
expect(page).to have_content('You must provide a valid current password')
@@ -31,14 +27,28 @@ RSpec.describe 'Two factor auths' do
expect(page).to have_content('Status: Enabled')
end
- end
- context 'when user has two-factor authentication enabled' do
- let(:user) { create(:user, :two_factor) }
+ context 'when user authenticates with an external service' do
+ let_it_be(:user) { create(:omniauth_user) }
+
+ it 'does not require the current password to set up two factor authentication', :js do
+ visit profile_two_factor_auth_path
- before do
- sign_in(user)
+ fill_in 'pin_code', with: user.current_otp
+ click_button 'Register with two-factor app'
+
+ expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.')
+
+ click_button 'Copy codes'
+ click_link 'Proceed'
+
+ expect(page).to have_content('Status: Enabled')
+ end
end
+ end
+
+ context 'when user has two-factor authentication enabled' do
+ let_it_be(:user) { create(:user, :two_factor) }
it 'requires the current_password to disable two-factor authentication', :js do
visit profile_two_factor_auth_path
@@ -61,7 +71,7 @@ RSpec.describe 'Two factor auths' do
expect(page).to have_content('Enable two-factor authentication')
end
- it 'requires the current_password to regernate recovery codes', :js do
+ it 'requires the current_password to regenerate recovery codes', :js do
visit profile_two_factor_auth_path
fill_in 'current_password', with: '123'
@@ -76,6 +86,29 @@ RSpec.describe 'Two factor auths' do
expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.')
end
+
+ context 'when user authenticates with an external service' do
+ let_it_be(:user) { create(:omniauth_user, :two_factor) }
+
+ it 'does not require the current_password to disable two-factor authentication', :js do
+ visit profile_two_factor_auth_path
+
+ click_button 'Disable two-factor authentication'
+
+ page.accept_alert
+
+ expect(page).to have_content('Two-factor authentication has been disabled successfully!')
+ expect(page).to have_content('Enable two-factor authentication')
+ end
+
+ it 'does not require the current_password to regenerate recovery codes', :js do
+ visit profile_two_factor_auth_path
+
+ click_button 'Regenerate recovery codes'
+
+ expect(page).to have_content('Please copy, download, or print your recovery codes before proceeding.')
+ end
+ end
end
def register_2fa(pin, password)
diff --git a/spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap b/spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap
deleted file mode 100644
index 3fe0e570a54..00000000000
--- a/spec/frontend/authentication/two_factor_auth/components/__snapshots__/manage_two_factor_form_spec.js.snap
+++ /dev/null
@@ -1,99 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ManageTwoFactorForm Disable button renders the component correctly 1`] = `
-VueWrapper {
- "_emitted": Object {},
- "_emittedByOrder": Array [],
- "isFunctionalComponent": undefined,
-}
-`;
-
-exports[`ManageTwoFactorForm Disable button renders the component correctly 2`] = `
-<form
- action="#"
- class="gl-display-inline-block"
- method="post"
->
- <input
- data-testid="test-2fa-method-field"
- name="_method"
- type="hidden"
- />
-
- <input
- name="authenticity_token"
- type="hidden"
- />
-
- <div
- class="form-group gl-form-group"
- id="__BVID__15"
- role="group"
- >
- <label
- class="d-block col-form-label"
- for="current-password"
- id="__BVID__15__BV_label_"
- >
- Current password
- </label>
- <div
- class="bv-no-focus-ring"
- >
- <input
- aria-required="true"
- class="gl-form-input form-control"
- data-qa-selector="current_password_field"
- id="current-password"
- name="current_password"
- required="required"
- type="password"
- />
- <!---->
- <!---->
- <!---->
- </div>
- </div>
-
- <button
- class="btn btn-danger gl-mr-3 gl-display-inline-block btn-danger btn-md gl-button"
- data-confirm="Are you sure? This will invalidate your registered applications and U2F devices."
- data-form-action="2fa_auth_path"
- data-form-method="2fa_auth_method"
- data-testid="test-2fa-disable-button"
- type="submit"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Disable two-factor authentication
-
- </span>
- </button>
-
- <button
- class="btn gl-display-inline-block btn-default btn-md gl-button"
- data-form-action="2fa_codes_path"
- data-form-method="2fa_codes_method"
- data-testid="test-2fa-regenerate-codes-button"
- type="submit"
- >
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Regenerate recovery codes
-
- </span>
- </button>
-</form>
-`;
diff --git a/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js b/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js
index 384579c6876..870375318e3 100644
--- a/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js
+++ b/spec/frontend/authentication/two_factor_auth/components/manage_two_factor_form_spec.js
@@ -1,10 +1,18 @@
import { within } from '@testing-library/dom';
+import { GlForm } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ManageTwoFactorForm, {
i18n,
} from '~/authentication/two_factor_auth/components/manage_two_factor_form.vue';
+const defaultProvide = {
+ profileTwoFactorAuthPath: '2fa_auth_path',
+ profileTwoFactorAuthMethod: '2fa_auth_method',
+ codesProfileTwoFactorAuthPath: '2fa_codes_path',
+ codesProfileTwoFactorAuthMethod: '2fa_codes_method',
+};
+
describe('ManageTwoFactorForm', () => {
let wrapper;
@@ -12,11 +20,9 @@ describe('ManageTwoFactorForm', () => {
wrapper = extendedWrapper(
mount(ManageTwoFactorForm, {
provide: {
- webauthnEnabled: options?.webauthnEnabled || false,
- profileTwoFactorAuthPath: '2fa_auth_path',
- profileTwoFactorAuthMethod: '2fa_auth_method',
- codesProfileTwoFactorAuthPath: '2fa_codes_path',
- codesProfileTwoFactorAuthMethod: '2fa_codes_method',
+ ...defaultProvide,
+ webauthnEnabled: options?.webauthnEnabled ?? false,
+ isCurrentPasswordRequired: options?.currentPasswordRequired ?? true,
},
}),
);
@@ -26,6 +32,11 @@ describe('ManageTwoFactorForm', () => {
const queryByLabelText = (text, options) =>
within(wrapper.element).queryByLabelText(text, options);
+ const findForm = () => wrapper.findComponent(GlForm);
+ const findMethodInput = () => wrapper.findByTestId('test-2fa-method-field');
+ const findDisableButton = () => wrapper.findByTestId('test-2fa-disable-button');
+ const findRegenerateCodesButton = () => wrapper.findByTestId('test-2fa-regenerate-codes-button');
+
beforeEach(() => {
createComponent();
});
@@ -36,16 +47,30 @@ describe('ManageTwoFactorForm', () => {
});
});
+ describe('when current password is not required', () => {
+ beforeEach(() => {
+ createComponent({
+ currentPasswordRequired: false,
+ });
+ });
+
+ it('does not render the current password field', () => {
+ expect(queryByLabelText(i18n.currentPassword)).toBe(null);
+ });
+ });
+
describe('Disable button', () => {
- it('renders the component correctly', () => {
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.element).toMatchSnapshot();
+ it('renders the component with correct attributes', () => {
+ expect(findDisableButton().exists()).toBe(true);
+ expect(findDisableButton().attributes()).toMatchObject({
+ 'data-confirm': i18n.confirm,
+ 'data-form-action': defaultProvide.profileTwoFactorAuthPath,
+ 'data-form-method': defaultProvide.profileTwoFactorAuthMethod,
+ });
});
it('has the right confirm text', () => {
- expect(wrapper.findByTestId('test-2fa-disable-button').element.dataset.confirm).toEqual(
- i18n.confirm,
- );
+ expect(findDisableButton().attributes('data-confirm')).toBe(i18n.confirm);
});
describe('when webauthnEnabled', () => {
@@ -56,23 +81,19 @@ describe('ManageTwoFactorForm', () => {
});
it('has the right confirm text', () => {
- expect(wrapper.findByTestId('test-2fa-disable-button').element.dataset.confirm).toEqual(
- i18n.confirmWebAuthn,
- );
+ expect(findDisableButton().attributes('data-confirm')).toBe(i18n.confirmWebAuthn);
});
});
it('modifies the form action and method when submitted through the button', async () => {
- const form = wrapper.find('form');
- const disableButton = wrapper.findByTestId('test-2fa-disable-button').element;
- const methodInput = wrapper.findByTestId('test-2fa-method-field').element;
+ const form = findForm();
+ const disableButton = findDisableButton().element;
+ const methodInput = findMethodInput();
- form.trigger('submit', { submitter: disableButton });
+ await form.vm.$emit('submit', { submitter: disableButton });
- await wrapper.vm.$nextTick();
-
- expect(form.element.getAttribute('action')).toEqual('2fa_auth_path');
- expect(methodInput.getAttribute('value')).toEqual('2fa_auth_method');
+ expect(form.attributes('action')).toBe(defaultProvide.profileTwoFactorAuthPath);
+ expect(methodInput.attributes('value')).toBe(defaultProvide.profileTwoFactorAuthMethod);
});
});
@@ -82,17 +103,14 @@ describe('ManageTwoFactorForm', () => {
});
it('modifies the form action and method when submitted through the button', async () => {
- const form = wrapper.find('form');
- const regenerateCodesButton = wrapper.findByTestId('test-2fa-regenerate-codes-button')
- .element;
- const methodInput = wrapper.findByTestId('test-2fa-method-field').element;
-
- form.trigger('submit', { submitter: regenerateCodesButton });
+ const form = findForm();
+ const regenerateCodesButton = findRegenerateCodesButton().element;
+ const methodInput = findMethodInput();
- await wrapper.vm.$nextTick();
+ await form.vm.$emit('submit', { submitter: regenerateCodesButton });
- expect(form.element.getAttribute('action')).toEqual('2fa_codes_path');
- expect(methodInput.getAttribute('value')).toEqual('2fa_codes_method');
+ expect(form.attributes('action')).toBe(defaultProvide.codesProfileTwoFactorAuthPath);
+ expect(methodInput.attributes('value')).toBe(defaultProvide.codesProfileTwoFactorAuthMethod);
});
});
});
diff --git a/spec/helpers/projects/cluster_agents_helper_spec.rb b/spec/helpers/projects/cluster_agents_helper_spec.rb
new file mode 100644
index 00000000000..2935a74586b
--- /dev/null
+++ b/spec/helpers/projects/cluster_agents_helper_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ClusterAgentsHelper do
+ describe '#js_cluster_agent_details_data' do
+ let_it_be(:project) { create(:project) }
+
+ let(:agent_name) { 'agent-name' }
+
+ subject { helper.js_cluster_agent_details_data(agent_name, project) }
+
+ it 'returns name' do
+ expect(subject[:agent_name]).to eq(agent_name)
+ end
+
+ it 'returns project path' do
+ expect(subject[:project_path]).to eq(project.full_path)
+ end
+ end
+end
diff --git a/spec/requests/projects/cluster_agents_controller_spec.rb b/spec/requests/projects/cluster_agents_controller_spec.rb
new file mode 100644
index 00000000000..e4c4f537699
--- /dev/null
+++ b/spec/requests/projects/cluster_agents_controller_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ClusterAgentsController do
+ let_it_be(:cluster_agent) { create(:cluster_agent) }
+
+ let(:project) { cluster_agent.project }
+
+ describe 'GET #show' do
+ subject { get project_cluster_agent_path(project, cluster_agent.name) }
+
+ context 'when user is unauthorized' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ subject
+ end
+
+ it 'shows 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ subject
+ end
+
+ it 'renders content' do
+ expect(response).to be_successful
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index dd43540d846..0214d4be47b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3579,10 +3579,10 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
-core-js@^3.18.1:
- version "3.18.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.18.1.tgz#289d4be2ce0085d40fc1244c0b1a54c00454622f"
- integrity sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==
+core-js@^3.18.2:
+ version "3.18.2"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.18.2.tgz#63a551e8a29f305cd4123754846e65896619ba5b"
+ integrity sha512-zNhPOUoSgoizoSQFdX1MeZO16ORRb9FFQLts8gSYbZU5FcgXhp24iMWMxnOQo5uIaIG7/6FA/IqJPwev1o9ZXQ==
core-js@~2.3.0:
version "2.3.0"