Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b42648225b
commit
01600ff2d1
|
|
@ -1,26 +1,46 @@
|
|||
<script>
|
||||
import { GlFormInput, GlFormGroup, GlButton, GlForm } from '@gitlab/ui';
|
||||
import { GlFormInput, GlFormGroup, GlButton, GlForm, GlModal } from '@gitlab/ui';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export const i18n = {
|
||||
currentPassword: __('Current password'),
|
||||
confirmTitle: __('Are you sure?'),
|
||||
confirmWebAuthn: __(
|
||||
'Are you sure? This will invalidate your registered applications and U2F / WebAuthn devices.',
|
||||
'This will invalidate your registered applications and U2F / WebAuthn devices.',
|
||||
),
|
||||
confirm: __('Are you sure? This will invalidate your registered applications and U2F devices.'),
|
||||
confirm: __('This will invalidate your registered applications and U2F devices.'),
|
||||
disableTwoFactor: __('Disable two-factor authentication'),
|
||||
disable: __('Disable'),
|
||||
cancel: __('Cancel'),
|
||||
regenerateRecoveryCodes: __('Regenerate recovery codes'),
|
||||
currentPasswordInvalidFeedback: __('Please enter your current password.'),
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'ManageTwoFactorForm',
|
||||
i18n,
|
||||
modalId: 'manage-two-factor-auth-confirm-modal',
|
||||
modalActions: {
|
||||
primary: {
|
||||
text: i18n.disable,
|
||||
attributes: {
|
||||
variant: 'danger',
|
||||
},
|
||||
},
|
||||
secondary: {
|
||||
text: i18n.cancel,
|
||||
attributes: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
GlForm,
|
||||
GlFormInput,
|
||||
GlFormGroup,
|
||||
GlButton,
|
||||
GlModal,
|
||||
},
|
||||
inject: [
|
||||
'webauthnEnabled',
|
||||
|
|
@ -32,8 +52,11 @@ export default {
|
|||
],
|
||||
data() {
|
||||
return {
|
||||
method: '',
|
||||
action: '#',
|
||||
method: null,
|
||||
action: null,
|
||||
currentPassword: '',
|
||||
currentPasswordState: null,
|
||||
showConfirmModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -46,9 +69,34 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
handleFormSubmit(event) {
|
||||
this.method = event.submitter.dataset.formMethod;
|
||||
this.action = event.submitter.dataset.formAction;
|
||||
submitForm() {
|
||||
this.$refs.form.$el.submit();
|
||||
},
|
||||
async handleSubmitButtonClick({ method, action, confirm = false }) {
|
||||
this.method = method;
|
||||
this.action = action;
|
||||
|
||||
if (this.isCurrentPasswordRequired && this.currentPassword === '') {
|
||||
this.currentPasswordState = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentPasswordState = null;
|
||||
|
||||
if (confirm) {
|
||||
this.showConfirmModal = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for form action and method to be updated
|
||||
await this.$nextTick();
|
||||
|
||||
this.submitForm();
|
||||
},
|
||||
handleModalPrimary() {
|
||||
this.submitForm();
|
||||
},
|
||||
},
|
||||
csrf,
|
||||
|
|
@ -57,10 +105,11 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-form
|
||||
class="gl-display-inline-block"
|
||||
ref="form"
|
||||
class="gl-sm-display-inline-block"
|
||||
method="post"
|
||||
:action="action"
|
||||
@submit="handleFormSubmit($event)"
|
||||
@submit.prevent
|
||||
>
|
||||
<input type="hidden" name="_method" data-testid="test-2fa-method-field" :value="method" />
|
||||
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
|
||||
|
|
@ -69,35 +118,59 @@ export default {
|
|||
v-if="isCurrentPasswordRequired"
|
||||
:label="$options.i18n.currentPassword"
|
||||
label-for="current-password"
|
||||
:state="currentPasswordState"
|
||||
:invalid-feedback="$options.i18n.currentPasswordInvalidFeedback"
|
||||
>
|
||||
<gl-form-input
|
||||
id="current-password"
|
||||
v-model="currentPassword"
|
||||
type="password"
|
||||
name="current_password"
|
||||
required
|
||||
:state="currentPasswordState"
|
||||
data-qa-selector="current_password_field"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<gl-button
|
||||
type="submit"
|
||||
class="btn-danger gl-mr-3 gl-display-inline-block"
|
||||
data-testid="test-2fa-disable-button"
|
||||
variant="danger"
|
||||
:data-confirm="confirmText"
|
||||
:data-form-action="profileTwoFactorAuthPath"
|
||||
:data-form-method="profileTwoFactorAuthMethod"
|
||||
<div class="gl-display-flex gl-flex-wrap">
|
||||
<gl-button
|
||||
type="submit"
|
||||
class="gl-sm-mr-3 gl-w-full gl-sm-w-auto"
|
||||
data-testid="test-2fa-disable-button"
|
||||
variant="danger"
|
||||
@click.prevent="
|
||||
handleSubmitButtonClick({
|
||||
method: profileTwoFactorAuthMethod,
|
||||
action: profileTwoFactorAuthPath,
|
||||
confirm: true,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ $options.i18n.disableTwoFactor }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
type="submit"
|
||||
class="gl-mt-3 gl-sm-mt-0 gl-w-full gl-sm-w-auto"
|
||||
data-testid="test-2fa-regenerate-codes-button"
|
||||
@click.prevent="
|
||||
handleSubmitButtonClick({
|
||||
method: codesProfileTwoFactorAuthMethod,
|
||||
action: codesProfileTwoFactorAuthPath,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ $options.i18n.regenerateRecoveryCodes }}
|
||||
</gl-button>
|
||||
</div>
|
||||
<gl-modal
|
||||
v-model="showConfirmModal"
|
||||
:modal-id="$options.modalId"
|
||||
size="sm"
|
||||
:title="$options.i18n.confirmTitle"
|
||||
:action-primary="$options.modalActions.primary"
|
||||
:action-secondary="$options.modalActions.secondary"
|
||||
@primary="handleModalPrimary"
|
||||
>
|
||||
{{ $options.i18n.disableTwoFactor }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
type="submit"
|
||||
class="gl-display-inline-block"
|
||||
data-testid="test-2fa-regenerate-codes-button"
|
||||
:data-form-action="codesProfileTwoFactorAuthPath"
|
||||
:data-form-method="codesProfileTwoFactorAuthMethod"
|
||||
>
|
||||
{{ $options.i18n.regenerateRecoveryCodes }}
|
||||
</gl-button>
|
||||
{{ confirmText }}
|
||||
</gl-modal>
|
||||
</gl-form>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ as well as these:
|
|||
| Variable | Description |
|
||||
|-|-|
|
||||
| `QA_SCENARIO` | The scenario to run (default `Test::Instance::Image`) |
|
||||
| `QA_TESTS` | The test(s) to run (no default, which means run all the tests in the scenario). Use file paths as you would when running tests via RSpec, e.g., `qa/specs/features/ee/browser_ui` would include all the `EE` UI tests. |
|
||||
| `QA_TESTS` | The test(s) to run (no default, which means run all the tests in the scenario). Use file paths as you would when running tests via RSpec, for example, `qa/specs/features/ee/browser_ui` would include all the `EE` UI tests. |
|
||||
| `QA_RSPEC_TAGS` | The RSpec tags to add (no default) |
|
||||
|
||||
For now [manual jobs with custom variables don't use the same variable
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ This document describes the conventions used at GitLab for writing End-to-end (E
|
|||
|
||||
When clicking in a single link to navigate, use `click_`.
|
||||
|
||||
E.g.:
|
||||
For example:
|
||||
|
||||
```ruby
|
||||
def click_ci_cd_pipelines
|
||||
|
|
@ -33,7 +33,7 @@ From a testing perspective, if we want to check that clicking a link, or a butto
|
|||
|
||||
When interacting with multiple elements to go to a page, use `go_to_`.
|
||||
|
||||
E.g.:
|
||||
For example:
|
||||
|
||||
```ruby
|
||||
def go_to_operations_environments
|
||||
|
|
@ -63,12 +63,12 @@ We follow a simple formula roughly based on Hungarian notation.
|
|||
- `type`: A generic control on the page that can be seen by a user.
|
||||
- `_button`
|
||||
- `_checkbox`
|
||||
- `_container`: an element that includes other elements, but doesn't present visible content itself. E.g., an element that has a third-party editor inside it, but which isn't the editor itself and so doesn't include the editor's content.
|
||||
- `_container`: an element that includes other elements, but doesn't present visible content itself. For example, an element that has a third-party editor inside it, but which isn't the editor itself and so doesn't include the editor's content.
|
||||
- `_content`: any element that contains text, images, or any other content displayed to the user.
|
||||
- `_dropdown`
|
||||
- `_field`: a text input element.
|
||||
- `_link`
|
||||
- `_modal`: a popup modal dialog, e.g., a confirmation prompt.
|
||||
- `_modal`: a popup modal dialog, for example, a confirmation prompt.
|
||||
- `_placeholder`: a temporary element that appears while content is loading. For example, the elements that are displayed instead of discussions while the discussions are being fetched.
|
||||
- `_radio`
|
||||
- `_tab`
|
||||
|
|
@ -116,7 +116,7 @@ we use the name of the page object in [snake_case](https://en.wikipedia.org/wiki
|
|||
(all lowercase, with words separated by an underscore). See good and bad examples below.
|
||||
|
||||
While we prefer to follow the standard in most cases, it is also acceptable to
|
||||
use common abbreviations (e.g., `mr`) or other alternatives, as long as
|
||||
use common abbreviations (for example, `mr`) or other alternatives, as long as
|
||||
the name is not ambiguous. This can include appending `_page` if it helps to
|
||||
avoid confusion or make the code more readable. For example, if a page object is
|
||||
named `New`, it could be confusing to name the block argument `new` because that
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ ensures proper isolation.
|
|||
|
||||
## Testing an `ActiveRecord::Migration` class
|
||||
|
||||
To test an `ActiveRecord::Migration` class (i.e., a
|
||||
To test an `ActiveRecord::Migration` class (for example, a
|
||||
regular migration `db/migrate` or a post-migration `db/post_migrate`), you
|
||||
must load the migration file by using the `require_migration!` helper
|
||||
method because it is not autoloaded by Rails.
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Uploads development documentation
|
||||
|
||||
[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) has special rules for handling uploads.
|
||||
To prevent occupying a Ruby process on I/O operations, we process the upload in workhorse, where is cheaper.
|
||||
We process the upload in Workhorse to prevent occupying a Ruby process on I/O operations and because it is cheaper.
|
||||
This process can also directly upload to object storage.
|
||||
|
||||
## The problem description
|
||||
|
||||
The following graph explains machine boundaries in a scalable GitLab installation. Without any workhorse optimization in place, we can expect incoming requests to follow the numbers on the arrows.
|
||||
The following graph explains machine boundaries in a scalable GitLab installation. Without any Workhorse optimization in place, we can expect incoming requests to follow the numbers on the arrows.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
|
@ -27,10 +27,10 @@ graph TB
|
|||
subgraph "redis cluster"
|
||||
r(persisted redis)
|
||||
end
|
||||
LB-- 1 -->workhorse
|
||||
LB-- 1 -->Workhorse
|
||||
|
||||
subgraph "web or API fleet"
|
||||
workhorse-- 2 -->rails
|
||||
Workhorse-- 2 -->rails
|
||||
end
|
||||
rails-- "3 (write files)" -->nfs
|
||||
rails-- "4 (schedule a job)" -->r
|
||||
|
|
@ -63,12 +63,12 @@ graph TB
|
|||
subgraph "redis cluster"
|
||||
r(persisted redis)
|
||||
end
|
||||
LB-- 1 -->workhorse
|
||||
LB-- 1 -->Workhorse
|
||||
|
||||
subgraph "web or API fleet"
|
||||
workhorse-- "3 (without files)" -->rails
|
||||
Workhorse-- "3 (without files)" -->rails
|
||||
end
|
||||
workhorse -- "2 (write files)" -->nfs
|
||||
Workhorse -- "2 (write files)" -->nfs
|
||||
rails-- "4 (schedule a job)" -->r
|
||||
|
||||
subgraph sidekiq
|
||||
|
|
@ -120,7 +120,7 @@ We have three kinds of file encoding in our uploads:
|
|||
|
||||
1. <i class="fa fa-check-circle"></i> **multipart**: `multipart/form-data` is the most common, a file is encoded as a part of a multipart encoded request.
|
||||
1. <i class="fa fa-check-circle"></i> **body**: some APIs uploads files as the whole request body.
|
||||
1. <i class="fa fa-times-circle"></i> **JSON**: some JSON API uploads files as base64 encoded strings. This requires a change to GitLab Workhorse,
|
||||
1. <i class="fa fa-times-circle"></i> **JSON**: some JSON APIs upload files as base64-encoded strings. This requires a change to GitLab Workhorse,
|
||||
which is tracked [in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/325068).
|
||||
|
||||
## Uploading technologies
|
||||
|
|
@ -131,9 +131,9 @@ GitLab supports 3 kinds of uploading technologies, here follows a brief descript
|
|||
|
||||
### Rack Multipart upload
|
||||
|
||||
This is the default kind of upload, and it's most expensive in terms of resources.
|
||||
This is the default kind of upload, and it's the most expensive in terms of resources.
|
||||
|
||||
In this case, workhorse is unaware of files being uploaded and acts as a regular proxy.
|
||||
In this case, Workhorse is unaware of files being uploaded and acts as a regular proxy.
|
||||
|
||||
When a multipart request reaches the rails application, `Rack::Multipart` leaves behind temporary files in `/tmp` and uses valuable Ruby process time to copy files around.
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ sequenceDiagram
|
|||
|
||||
This is the more advanced acceleration technique we have in place.
|
||||
|
||||
Workhorse asks rails for temporary pre-signed object storage URLs and directly uploads to object storage.
|
||||
Workhorse asks Rails for temporary pre-signed object storage URLs and directly uploads to object storage.
|
||||
|
||||
In this setup, an extra Rails route must be implemented in order to handle authorization. Examples of this can be found in:
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ In this setup, an extra Rails route must be implemented in order to handle autho
|
|||
and [its routes](https://gitlab.com/gitlab-org/gitlab/-/blob/cc723071ad337573e0360a879cbf99bc4fb7adb9/config/routes/git_http.rb#L31-32).
|
||||
- [API endpoints for uploading packages](packages.md#file-uploads).
|
||||
|
||||
This falls back to _disk buffered upload_ when `direct_upload` is disabled inside the [object storage setting](../administration/uploads.md#object-storage-settings).
|
||||
Direct upload falls back to _disk buffered upload_ when `direct_upload` is disabled inside the [object storage setting](../administration/uploads.md#object-storage-settings).
|
||||
The answer to the `/authorize` call contains only a file system path.
|
||||
|
||||
```mermaid
|
||||
|
|
@ -275,7 +275,7 @@ sequenceDiagram
|
|||
|
||||
In this section, we describe how to add a new upload route [accelerated](#uploading-technologies) by Workhorse for [body and multipart](#upload-encodings) encoded uploads.
|
||||
|
||||
Uploads routes belong to one of these categories:
|
||||
Upload routes belong to one of these categories:
|
||||
|
||||
1. Rails controllers: uploads handled by Rails controllers.
|
||||
1. Grape API: uploads handled by a Grape API endpoint.
|
||||
|
|
@ -289,7 +289,7 @@ GraphQL uploads do not support [direct upload](#direct-upload) yet. Depending on
|
|||
For both the Rails controller and Grape API uploads, Workhorse has to be updated in order to get the
|
||||
support for the new upload route.
|
||||
|
||||
1. Open an new issue in the [Workhorse tracker](https://gitlab.com/gitlab-org/gitlab-workhorse/-/issues/new) describing precisely the new upload route:
|
||||
1. Open a new issue in the [Workhorse tracker](https://gitlab.com/gitlab-org/gitlab-workhorse/-/issues/new) describing precisely the new upload route:
|
||||
- The route's URL.
|
||||
- The [upload encoding](#upload-encodings).
|
||||
- If possible, provide a dump of the upload request.
|
||||
|
|
|
|||
|
|
@ -6,12 +6,18 @@ comments: false
|
|||
description: 'Learn how to use and administer GitLab, the most scalable Git-based fully integrated platform for software development.'
|
||||
---
|
||||
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
<!-- MD044/proper-names test disabled after this line to make page compatible with markdownlint-cli 0.29.0. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-markdownlint-tests -->
|
||||
|
||||
<div class="d-none">
|
||||
<h3>Visit <a href="https://docs.gitlab.com/ee/">docs.gitlab.com</a> for the latest version
|
||||
of this help information with enhanced navigation, discoverability, and readability.</h3>
|
||||
</div>
|
||||
<!-- the div above will not display on the docs site but will display on /help -->
|
||||
|
||||
<!-- markdownlint-enable MD044 -->
|
||||
|
||||
# GitLab Docs
|
||||
|
||||
Welcome to [GitLab](https://about.gitlab.com/) documentation.
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ maximum of two directory levels from the repository's root. For example, the
|
|||
`gemnasium-dependency_scanning` job is enabled if a repository contains either `Gemfile`,
|
||||
`api/Gemfile`, or `api/client/Gemfile`, but not if the only supported dependency file is `api/v1/client/Gemfile`.
|
||||
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
<!-- MD044/proper-names test disabled after this line to make page compatible with markdownlint-cli 0.29.0. -->
|
||||
<!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-markdownlint-tests -->
|
||||
|
||||
The following languages and dependency managers are supported:
|
||||
|
||||
<style>
|
||||
|
|
@ -246,6 +250,8 @@ table.supported-languages ul {
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable MD044 -->
|
||||
|
||||
### Notes regarding supported languages and package managers
|
||||
|
||||
1. Although Gradle with Java 8 is supported, there are other issues such that Android project builds are not supported at this time. Please see the backlog issue [Android support for Dependency Scanning (gemnasium-maven)](https://gitlab.com/gitlab-org/gitlab/-/issues/336866) for more details.
|
||||
|
|
|
|||
|
|
@ -13,29 +13,17 @@ the CI/CD pipeline to generate a Pages website.
|
|||
|
||||
Use a `.gitlab-ci.yml` template when you have an existing project that you want to add a Pages site to.
|
||||
|
||||
Your GitLab repository should contain files specific to an SSG, or plain HTML.
|
||||
After you complete these steps, you may need to do additional
|
||||
configuration for the Pages site to generate properly.
|
||||
Your GitLab repository should contain files specific to an SSG, or plain HTML. After you complete
|
||||
these steps, you may have to do additional configuration for the Pages site to generate properly.
|
||||
|
||||
1. On the left sidebar, select **Project information**.
|
||||
1. Click **Set up CI/CD**.
|
||||
|
||||

|
||||
|
||||
If this button is not available, CI/CD is already configured for
|
||||
your project. You may want to browse the `.gitlab-ci.yml` files
|
||||
[in these projects instead](https://gitlab.com/pages).
|
||||
|
||||
1. Click **Set up CI/CD**. If this button is not available, CI/CD is already configured for your project.
|
||||
You may want to browse the `.gitlab-ci.yml` files [in these projects instead](https://gitlab.com/pages).
|
||||
1. From the **Apply a template** list, choose a template for the SSG you're using.
|
||||
You can also choose plain HTML.
|
||||
|
||||

|
||||
|
||||
If you don't find a corresponding template, you can view the
|
||||
You can also choose plain HTML. If you don't find a corresponding template, you can view the
|
||||
[GitLab Pages group of sample projects](https://gitlab.com/pages).
|
||||
These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
|
||||
You can also [learn how to write your own `.gitlab-ci.yml` file for GitLab Pages](pages_from_scratch.md).
|
||||
|
||||
1. Save and commit the `.gitlab-ci.yml` file.
|
||||
|
||||
If everything is configured correctly, the site can take approximately 30 minutes to deploy.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -4585,12 +4585,6 @@ msgstr ""
|
|||
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 / WebAuthn devices."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure? This will invalidate your registered applications and U2F devices."
|
||||
msgstr ""
|
||||
|
||||
msgid "Arrange charts"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25747,6 +25741,9 @@ msgstr ""
|
|||
msgid "Please enter or upload a valid license."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please enter your current password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please fill in a descriptive name for your group."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -35338,6 +35335,12 @@ msgstr ""
|
|||
msgid "This variable can not be masked."
|
||||
msgstr ""
|
||||
|
||||
msgid "This will invalidate your registered applications and U2F / WebAuthn devices."
|
||||
msgstr ""
|
||||
|
||||
msgid "This will invalidate your registered applications and U2F devices."
|
||||
msgstr ""
|
||||
|
||||
msgid "This will redirect you to an external sign in page."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ RSpec.describe 'Two factor auths' do
|
|||
|
||||
click_button 'Disable two-factor authentication'
|
||||
|
||||
page.accept_alert
|
||||
page.within('[role="dialog"]') do
|
||||
click_button 'Disable'
|
||||
end
|
||||
|
||||
expect(page).to have_content('You must provide a valid current password')
|
||||
|
||||
|
|
@ -65,7 +67,9 @@ RSpec.describe 'Two factor auths' do
|
|||
|
||||
click_button 'Disable two-factor authentication'
|
||||
|
||||
page.accept_alert
|
||||
page.within('[role="dialog"]') do
|
||||
click_button 'Disable'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Two-factor authentication has been disabled successfully!')
|
||||
expect(page).to have_content('Enable two-factor authentication')
|
||||
|
|
@ -95,7 +99,9 @@ RSpec.describe 'Two factor auths' do
|
|||
|
||||
click_button 'Disable two-factor authentication'
|
||||
|
||||
page.accept_alert
|
||||
page.within('[role="dialog"]') do
|
||||
click_button 'Disable'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Two-factor authentication has been disabled successfully!')
|
||||
expect(page).to have_content('Enable two-factor authentication')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
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 { GlForm, GlModal } from '@gitlab/ui';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import ManageTwoFactorForm, {
|
||||
i18n,
|
||||
} from '~/authentication/two_factor_auth/components/manage_two_factor_form.vue';
|
||||
|
|
@ -17,100 +16,133 @@ describe('ManageTwoFactorForm', () => {
|
|||
let wrapper;
|
||||
|
||||
const createComponent = (options = {}) => {
|
||||
wrapper = extendedWrapper(
|
||||
mount(ManageTwoFactorForm, {
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
webauthnEnabled: options?.webauthnEnabled ?? false,
|
||||
isCurrentPasswordRequired: options?.currentPasswordRequired ?? true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
wrapper = mountExtended(ManageTwoFactorForm, {
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
webauthnEnabled: options?.webauthnEnabled ?? false,
|
||||
isCurrentPasswordRequired: options?.currentPasswordRequired ?? true,
|
||||
},
|
||||
stubs: {
|
||||
GlModal: stubComponent(GlModal, {
|
||||
template: `
|
||||
<div>
|
||||
<slot name="modal-title"></slot>
|
||||
<slot></slot>
|
||||
<slot name="modal-footer"></slot>
|
||||
</div>`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const queryByText = (text, options) => within(wrapper.element).queryByText(text, options);
|
||||
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');
|
||||
const findConfirmationModal = () => wrapper.findComponent(GlModal);
|
||||
|
||||
const itShowsConfirmationModal = (confirmText) => {
|
||||
it('shows confirmation modal', async () => {
|
||||
await wrapper.findByLabelText('Current password').setValue('foo bar');
|
||||
await findDisableButton().trigger('click');
|
||||
|
||||
expect(findConfirmationModal().props('visible')).toBe(true);
|
||||
expect(findConfirmationModal().html()).toContain(confirmText);
|
||||
});
|
||||
};
|
||||
|
||||
const itShowsValidationMessageIfCurrentPasswordFieldIsEmpty = (findButtonFunction) => {
|
||||
it('shows validation message if `Current password` is empty', async () => {
|
||||
await findButtonFunction().trigger('click');
|
||||
|
||||
expect(wrapper.findByText(i18n.currentPasswordInvalidFeedback).exists()).toBe(true);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
describe('Current password field', () => {
|
||||
it('renders the current password field', () => {
|
||||
expect(queryByLabelText(i18n.currentPassword).tagName).toEqual('INPUT');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when current password is not required', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
currentPasswordRequired: false,
|
||||
describe('`Current password` field', () => {
|
||||
describe('when required', () => {
|
||||
it('renders the current password field', () => {
|
||||
expect(wrapper.findByLabelText(i18n.currentPassword).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render the current password field', () => {
|
||||
expect(queryByLabelText(i18n.currentPassword)).toBe(null);
|
||||
describe('when not required', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
currentPasswordRequired: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render the current password field', () => {
|
||||
expect(wrapper.findByLabelText(i18n.currentPassword).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disable button', () => {
|
||||
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(findDisableButton().attributes('data-confirm')).toBe(i18n.confirm);
|
||||
});
|
||||
describe('when clicked', () => {
|
||||
itShowsValidationMessageIfCurrentPasswordFieldIsEmpty(findDisableButton);
|
||||
|
||||
describe('when webauthnEnabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
webauthnEnabled: true,
|
||||
itShowsConfirmationModal(i18n.confirm);
|
||||
|
||||
describe('when webauthnEnabled', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
webauthnEnabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
itShowsConfirmationModal(i18n.confirmWebAuthn);
|
||||
});
|
||||
|
||||
it('has the right confirm text', () => {
|
||||
expect(findDisableButton().attributes('data-confirm')).toBe(i18n.confirmWebAuthn);
|
||||
it('modifies the form action and method when submitted through the button', async () => {
|
||||
const form = findForm();
|
||||
const methodInput = findMethodInput();
|
||||
const submitSpy = jest.spyOn(form.element, 'submit');
|
||||
|
||||
await wrapper.findByLabelText('Current password').setValue('foo bar');
|
||||
await findDisableButton().trigger('click');
|
||||
|
||||
expect(form.attributes('action')).toBe(defaultProvide.profileTwoFactorAuthPath);
|
||||
expect(methodInput.attributes('value')).toBe(defaultProvide.profileTwoFactorAuthMethod);
|
||||
|
||||
findConfirmationModal().vm.$emit('primary');
|
||||
|
||||
expect(submitSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('modifies the form action and method when submitted through the button', async () => {
|
||||
const form = findForm();
|
||||
const disableButton = findDisableButton().element;
|
||||
const methodInput = findMethodInput();
|
||||
|
||||
await form.vm.$emit('submit', { submitter: disableButton });
|
||||
|
||||
expect(form.attributes('action')).toBe(defaultProvide.profileTwoFactorAuthPath);
|
||||
expect(methodInput.attributes('value')).toBe(defaultProvide.profileTwoFactorAuthMethod);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regenerate recovery codes button', () => {
|
||||
it('renders the button', () => {
|
||||
expect(queryByText(i18n.regenerateRecoveryCodes)).toEqual(expect.any(HTMLElement));
|
||||
expect(findRegenerateCodesButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('modifies the form action and method when submitted through the button', async () => {
|
||||
const form = findForm();
|
||||
const regenerateCodesButton = findRegenerateCodesButton().element;
|
||||
const methodInput = findMethodInput();
|
||||
describe('when clicked', () => {
|
||||
itShowsValidationMessageIfCurrentPasswordFieldIsEmpty(findRegenerateCodesButton);
|
||||
|
||||
await form.vm.$emit('submit', { submitter: regenerateCodesButton });
|
||||
it('modifies the form action and method when submitted through the button', async () => {
|
||||
const form = findForm();
|
||||
const methodInput = findMethodInput();
|
||||
const submitSpy = jest.spyOn(form.element, 'submit');
|
||||
|
||||
expect(form.attributes('action')).toBe(defaultProvide.codesProfileTwoFactorAuthPath);
|
||||
expect(methodInput.attributes('value')).toBe(defaultProvide.codesProfileTwoFactorAuthMethod);
|
||||
await wrapper.findByLabelText('Current password').setValue('foo bar');
|
||||
await findRegenerateCodesButton().trigger('click');
|
||||
|
||||
expect(form.attributes('action')).toBe(defaultProvide.codesProfileTwoFactorAuthPath);
|
||||
expect(methodInput.attributes('value')).toBe(
|
||||
defaultProvide.codesProfileTwoFactorAuthMethod,
|
||||
);
|
||||
expect(submitSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ RSpec.describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::X509::Certificate).to receive_messages(from_files: certificate)
|
||||
allow(Gitlab::Email::Hook::SmimeSignatureInterceptor).to receive(:certificate).and_return(certificate)
|
||||
|
||||
Mail.register_interceptor(described_class)
|
||||
mail.deliver_now
|
||||
|
|
|
|||
|
|
@ -201,9 +201,11 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
describe 'reindex' do
|
||||
let(:reindex) { double('reindex') }
|
||||
let(:indexes) { double('indexes') }
|
||||
let(:databases) { Gitlab::Database.databases }
|
||||
let(:databases_count) { databases.count }
|
||||
|
||||
it 'cleans up any leftover indexes' do
|
||||
expect(Gitlab::Database::Reindexing).to receive(:cleanup_leftovers!)
|
||||
expect(Gitlab::Database::Reindexing).to receive(:cleanup_leftovers!).exactly(databases_count).times
|
||||
|
||||
run_rake_task('gitlab:db:reindex')
|
||||
end
|
||||
|
|
@ -212,8 +214,8 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
it 'executes async index creation prior to any reindexing actions' do
|
||||
stub_feature_flags(database_async_index_creation: true)
|
||||
|
||||
expect(Gitlab::Database::AsyncIndexes).to receive(:create_pending_indexes!).ordered
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).ordered
|
||||
expect(Gitlab::Database::AsyncIndexes).to receive(:create_pending_indexes!).ordered.exactly(databases_count).times
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).ordered.exactly(databases_count).times
|
||||
|
||||
run_rake_task('gitlab:db:reindex')
|
||||
end
|
||||
|
|
@ -231,8 +233,8 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
|
||||
context 'when no index_name is given' do
|
||||
it 'uses all candidate indexes' do
|
||||
expect(Gitlab::Database::PostgresIndex).to receive(:reindexing_support).and_return(indexes)
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes)
|
||||
expect(Gitlab::Database::PostgresIndex).to receive(:reindexing_support).exactly(databases_count).times.and_return(indexes)
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes).exactly(databases_count).times
|
||||
|
||||
run_rake_task('gitlab:db:reindex')
|
||||
end
|
||||
|
|
@ -247,7 +249,8 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
|
||||
it 'calls the index rebuilder with the proper arguments' do
|
||||
allow(indexes).to receive(:where).with(identifier: 'public.foo_idx').and_return([index])
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with([index])
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with([index]).ordered
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes).ordered if databases.many?
|
||||
|
||||
run_rake_task('gitlab:db:reindex', '[public.foo_idx]')
|
||||
end
|
||||
|
|
@ -255,16 +258,17 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
context 'when database name is provided' do
|
||||
it 'calls the index rebuilder with the proper arguments when the database name match' do
|
||||
allow(indexes).to receive(:where).with(identifier: 'public.foo_idx').and_return([index])
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with([index])
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with([index]).ordered
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes).ordered if databases.many?
|
||||
|
||||
run_rake_task('gitlab:db:reindex', '[public.foo_idx,main]')
|
||||
end
|
||||
|
||||
it 'ignores the index and uses all candidate indexes if database name does not match' do
|
||||
expect(Gitlab::Database::PostgresIndex).to receive(:reindexing_support).and_return(indexes)
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes)
|
||||
expect(Gitlab::Database::PostgresIndex).to receive(:reindexing_support).exactly(databases_count).times.and_return(indexes)
|
||||
expect(Gitlab::Database::Reindexing).to receive(:perform).with(indexes).exactly(databases_count).times
|
||||
|
||||
run_rake_task('gitlab:db:reindex', '[public.foo_idx,ci]')
|
||||
run_rake_task('gitlab:db:reindex', '[public.foo_idx,no_such_database]')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ RSpec.describe EmailsOnPushWorker, :mailer do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::X509::Certificate).to receive_messages(from_files: certificate)
|
||||
allow(Gitlab::Email::Hook::SmimeSignatureInterceptor).to receive(:certificate).and_return(certificate)
|
||||
|
||||
Mail.register_interceptor(Gitlab::Email::Hook::SmimeSignatureInterceptor)
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue