Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ace0df53d3
commit
2ddcd634fc
49 changed files with 580 additions and 102 deletions
|
@ -306,7 +306,7 @@ export default () => {
|
|||
|
||||
const issueBoardsModal = document.getElementById('js-add-issues-btn');
|
||||
|
||||
if (issueBoardsModal) {
|
||||
if (issueBoardsModal && gon.features.addIssuesButton) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: issueBoardsModal,
|
||||
|
|
|
@ -33,7 +33,14 @@ export default {
|
|||
mixins: [glFeatureFlagsMixin()],
|
||||
computed: {
|
||||
...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
|
||||
...mapState(['defaultState', 'override', 'isSaving', 'isTesting', 'isResetting']),
|
||||
...mapState([
|
||||
'defaultState',
|
||||
'customState',
|
||||
'override',
|
||||
'isSaving',
|
||||
'isTesting',
|
||||
'isResetting',
|
||||
]),
|
||||
isEditable() {
|
||||
return this.propsSource.editable;
|
||||
},
|
||||
|
@ -42,8 +49,8 @@ export default {
|
|||
},
|
||||
isInstanceOrGroupLevel() {
|
||||
return (
|
||||
this.propsSource.integrationLevel === integrationLevels.INSTANCE ||
|
||||
this.propsSource.integrationLevel === integrationLevels.GROUP
|
||||
this.customState.integrationLevel === integrationLevels.INSTANCE ||
|
||||
this.customState.integrationLevel === integrationLevels.GROUP
|
||||
);
|
||||
},
|
||||
showJiraIssuesFields() {
|
||||
|
|
|
@ -4,7 +4,7 @@ import ActionComponent from './action_component.vue';
|
|||
import JobNameComponent from './job_name_component.vue';
|
||||
import { sprintf } from '~/locale';
|
||||
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
|
||||
import { accessors } from './accessors';
|
||||
import { accessValue } from './accessors';
|
||||
import { REST } from './constants';
|
||||
|
||||
/**
|
||||
|
@ -79,10 +79,10 @@ export default {
|
|||
return this.dropdownLength === 1 ? 'viewport' : 'scrollParent';
|
||||
},
|
||||
detailsPath() {
|
||||
return this.status[accessors[this.dataMethod].detailsPath];
|
||||
return accessValue(this.dataMethod, 'detailsPath', this.status);
|
||||
},
|
||||
hasDetails() {
|
||||
return this.status[accessors[this.dataMethod].hasDetails];
|
||||
return accessValue(this.dataMethod, 'hasDetails', this.status);
|
||||
},
|
||||
status() {
|
||||
return this.job && this.job.status ? this.job.status : {};
|
||||
|
|
|
@ -5,7 +5,7 @@ import JobItem from './job_item.vue';
|
|||
import JobGroupDropdown from './job_group_dropdown.vue';
|
||||
import ActionComponent from './action_component.vue';
|
||||
import { GRAPHQL } from './constants';
|
||||
import { accessors } from './accessors';
|
||||
import { accessValue } from './accessors';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -39,7 +39,6 @@ export default {
|
|||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
accessors,
|
||||
titleClasses: [
|
||||
'gl-font-weight-bold',
|
||||
'gl-pipeline-job-width',
|
||||
|
@ -56,8 +55,8 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
getAccessor(property) {
|
||||
return accessors[GRAPHQL][property];
|
||||
getGroupId(group) {
|
||||
return accessValue(GRAPHQL, 'groupId', group);
|
||||
},
|
||||
groupId(group) {
|
||||
return `ci-badge-${escape(group.name)}`;
|
||||
|
@ -87,7 +86,7 @@ export default {
|
|||
<div
|
||||
v-for="group in groups"
|
||||
:id="groupId(group)"
|
||||
:key="group[getAccessor('groupId')]"
|
||||
:key="getGroupId(group)"
|
||||
data-testid="stage-column-group"
|
||||
class="gl-relative gl-mb-3 gl-white-space-normal gl-pipeline-job-width"
|
||||
>
|
||||
|
|
|
@ -229,6 +229,7 @@ export default {
|
|||
v-if="pipeline.cancelable"
|
||||
:loading="isCanceling"
|
||||
:disabled="isCanceling"
|
||||
class="gl-ml-3"
|
||||
variant="danger"
|
||||
data-testid="cancelPipeline"
|
||||
@click="cancelPipeline()"
|
||||
|
|
|
@ -30,6 +30,10 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
customEmailEnabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
selectedTemplate: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -140,6 +144,7 @@ export default {
|
|||
:is-enabled="isEnabled"
|
||||
:incoming-email="incomingEmail"
|
||||
:custom-email="updatedCustomEmail"
|
||||
:custom-email-enabled="customEmailEnabled"
|
||||
:initial-selected-template="selectedTemplate"
|
||||
:initial-outgoing-name="outgoingName"
|
||||
:initial-project-key="projectKey"
|
||||
|
|
|
@ -31,6 +31,10 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
customEmailEnabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
initialSelectedTemplate: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -69,7 +73,7 @@ export default {
|
|||
return [''].concat(this.templates);
|
||||
},
|
||||
hasProjectKeySupport() {
|
||||
return Boolean(this.glFeatures.serviceDeskCustomAddress);
|
||||
return Boolean(this.customEmailEnabled);
|
||||
},
|
||||
email() {
|
||||
return this.customEmail || this.incomingEmail;
|
||||
|
|
|
@ -18,6 +18,7 @@ export default () => {
|
|||
endpoint: dataset.endpoint,
|
||||
incomingEmail: dataset.incomingEmail,
|
||||
customEmail: dataset.customEmail,
|
||||
customEmailEnabled: parseBoolean(dataset.customEmailEnabled),
|
||||
selectedTemplate: dataset.selectedTemplate,
|
||||
outgoingName: dataset.outgoingName,
|
||||
projectKey: dataset.projectKey,
|
||||
|
@ -31,6 +32,7 @@ export default () => {
|
|||
endpoint: this.endpoint,
|
||||
incomingEmail: this.incomingEmail,
|
||||
customEmail: this.customEmail,
|
||||
customEmailEnabled: this.customEmailEnabled,
|
||||
selectedTemplate: this.selectedTemplate,
|
||||
outgoingName: this.outgoingName,
|
||||
projectKey: this.projectKey,
|
||||
|
|
|
@ -129,17 +129,6 @@
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
// Move to Gitlab UI
|
||||
.gl-font-weight-100 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.gl-active-text-decoration-none:active,
|
||||
.gl-focus-text-decoration-none:focus,
|
||||
.gl-hover-text-decoration-none:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// These are single-value classes to use with utility-class style CSS
|
||||
// but to still access this variable. Do not add other styles.
|
||||
.gl-pipeline-min-h {
|
||||
|
@ -150,10 +139,6 @@
|
|||
width: 186px;
|
||||
}
|
||||
|
||||
.gl-pipeline-title-width {
|
||||
width: 176px;
|
||||
}
|
||||
|
||||
.gl-build-content {
|
||||
@include build-content();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
before_action :check_issues_available!
|
||||
before_action :authorize_read_board!, only: [:index, :show]
|
||||
before_action :assign_endpoint_vars
|
||||
before_action do
|
||||
push_frontend_feature_flag(:add_issues_button)
|
||||
end
|
||||
|
||||
feature_category :boards
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ class ProjectsController < Projects::ApplicationController
|
|||
before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
|
||||
|
||||
before_action only: [:edit] do
|
||||
push_frontend_feature_flag(:service_desk_custom_address, @project)
|
||||
push_frontend_feature_flag(:approval_suggestions, @project, default_enabled: true)
|
||||
end
|
||||
|
||||
|
|
|
@ -2507,8 +2507,7 @@ class Project < ApplicationRecord
|
|||
end
|
||||
|
||||
def service_desk_custom_address
|
||||
return unless ::Gitlab::ServiceDeskEmail.enabled?
|
||||
return unless ::Feature.enabled?(:service_desk_custom_address, self)
|
||||
return unless service_desk_custom_address_enabled?
|
||||
|
||||
key = service_desk_setting&.project_key
|
||||
return unless key.present?
|
||||
|
@ -2516,6 +2515,10 @@ class Project < ApplicationRecord
|
|||
::Gitlab::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
|
||||
end
|
||||
|
||||
def service_desk_custom_address_enabled?
|
||||
::Gitlab::ServiceDeskEmail.enabled? && ::Feature.enabled?(:service_desk_custom_address, self)
|
||||
end
|
||||
|
||||
def root_namespace
|
||||
if namespace.has_parent?
|
||||
namespace.root_ancestor
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
enabled: "#{@project.service_desk_enabled}",
|
||||
incoming_email: (@project.service_desk_incoming_address if @project.service_desk_enabled),
|
||||
custom_email: (@project.service_desk_custom_address if @project.service_desk_enabled),
|
||||
custom_email_enabled: "#{@project.service_desk_custom_address_enabled?}",
|
||||
selected_template: "#{@project.service_desk_setting&.issue_template_key}",
|
||||
outgoing_name: "#{@project.service_desk_setting&.outgoing_name}",
|
||||
project_key: "#{@project.service_desk_setting&.project_key}",
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
|
||||
- else
|
||||
%span.token-never-expires-label= _('Never')
|
||||
%td= token.scopes.present? ? token.scopes.join(', ') : html_escape_once(_('<no scopes selected>')).html_safe
|
||||
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
|
||||
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
|
||||
- else
|
||||
.settings-message.text-center
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
In #{distance_of_time_in_words_to_now(token.expires_at)}
|
||||
- else
|
||||
%span.token-never-expires-label= _('Never')
|
||||
%td= token.scopes.present? ? token.scopes.join(", ") : html_escape_once(_('<no scopes selected>')).html_safe
|
||||
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
|
||||
%td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger float-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"}
|
||||
= render 'shared/deploy_tokens/revoke_modal', token: token, group_or_project: group_or_project
|
||||
- else
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove brackets in no scopes selected message in access and deploy tokens lists
|
||||
merge_request: 47628
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove `Add Issues` button and a related modal
|
||||
merge_request: 47898
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix styling of various dropdowns
|
||||
merge_request: 48800
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix confirmation modal showing on project integration
|
||||
merge_request: 48720
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix spacing between buttons on pipeline header
|
||||
merge_request: 48660
|
||||
author:
|
||||
type: fixed
|
8
config/feature_flags/development/add_issues_button.yml
Normal file
8
config/feature_flags/development/add_issues_button.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: add_issues_button
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47898
|
||||
rollout_issue_url:
|
||||
milestone: '13.6'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: false
|
|
@ -1042,6 +1042,10 @@ production: &base
|
|||
shared:
|
||||
# path: /mnt/gitlab # Default: shared
|
||||
|
||||
# Encrypted Settings configuration
|
||||
encrypted_settings:
|
||||
# path: /mnt/gitlab/encrypted_settings # Default: shared/encrypted_settings
|
||||
|
||||
# Gitaly settings
|
||||
gitaly:
|
||||
# Path to the directory containing Gitaly client executables.
|
||||
|
|
|
@ -34,6 +34,9 @@ def create_tokens
|
|||
openid_connect_signing_key: generate_new_rsa_private_key
|
||||
}
|
||||
|
||||
# encrypted_settings_key_base is optional for now
|
||||
defaults[:encrypted_settings_key_base] = generate_new_secure_token if ENV['GITLAB_GENERATE_ENCRYPTED_SETTINGS_KEY_BASE']
|
||||
|
||||
missing_secrets = set_missing_keys(defaults)
|
||||
write_secrets_yml(missing_secrets) unless missing_secrets.empty?
|
||||
|
||||
|
|
|
@ -3,6 +3,13 @@ require_relative '../object_store_settings'
|
|||
require_relative '../smime_signature_settings'
|
||||
|
||||
# Default settings
|
||||
Settings['shared'] ||= Settingslogic.new({})
|
||||
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
|
||||
|
||||
Settings['encrypted_settings'] ||= Settingslogic.new({})
|
||||
Settings.encrypted_settings['path'] ||= File.join(Settings.shared['path'], "encrypted_settings")
|
||||
Settings.encrypted_settings['path'] = Settings.absolute(Settings.encrypted_settings['path'])
|
||||
|
||||
Settings['ldap'] ||= Settingslogic.new({})
|
||||
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
|
||||
Settings.ldap['prevent_ldap_sign_in'] = false if Settings.ldap['prevent_ldap_sign_in'].blank?
|
||||
|
@ -140,9 +147,6 @@ if Gitlab.ee? && Rails.env.test? && !saml_provider_enabled
|
|||
Settings.omniauth.providers << Settingslogic.new({ 'name' => 'group_saml' })
|
||||
end
|
||||
|
||||
Settings['shared'] ||= Settingslogic.new({})
|
||||
Settings.shared['path'] = Settings.absolute(Settings.shared['path'] || "shared")
|
||||
|
||||
Settings['issues_tracker'] ||= {}
|
||||
|
||||
#
|
||||
|
|
|
@ -152,6 +152,14 @@ class Settings < Settingslogic
|
|||
Gitlab::Application.secrets.db_key_base
|
||||
end
|
||||
|
||||
def encrypted(path)
|
||||
Gitlab::EncryptedConfiguration.new(
|
||||
content_path: path,
|
||||
base_key: Gitlab::Application.secrets.encrypted_settings_key_base,
|
||||
previous_keys: Gitlab::Application.secrets.rotated_encrypted_settings_key_base || []
|
||||
)
|
||||
end
|
||||
|
||||
def load_dynamic_cron_schedules!
|
||||
cron_jobs['gitlab_usage_ping_worker']['cron'] ||= cron_for_usage_ping
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ This page is a development guide for application secrets.
|
|||
| `otp_key_base` | The base key for One Time Passwords, described in [User management](../raketasks/user_management.md#rotate-two-factor-authentication-encryption-key) |
|
||||
|`db_key_base` | The base key to encrypt the data for `attr_encrypted` columns |
|
||||
|`openid_connect_signing_key` | The singing key for OpenID Connect |
|
||||
| `encrypted_settings_key_base` | The base key to encrypt settings files with |
|
||||
|
||||
## Where the secrets are stored
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ description: 'Writing styles, markup, formatting, and other standards for GitLab
|
|||
|
||||
# Documentation Style Guide
|
||||
|
||||
This document defines the standards for GitLab's documentation content and
|
||||
files.
|
||||
This document defines the standards for GitLab documentation.
|
||||
|
||||
For broader information about the documentation, see the [Documentation guidelines](../index.md).
|
||||
|
||||
|
@ -241,16 +240,16 @@ to update.
|
|||
|
||||
Put files for a specific product area into the related folder:
|
||||
|
||||
| Directory | What belongs here |
|
||||
|:----------------------|:----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `doc/user/` | User related documentation. Anything that can be done within the GitLab user interface goes here, including usage of the `/admin` interface. |
|
||||
| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed by using GitLab's interface exist under `doc/user/admin_area/`. |
|
||||
| `doc/api/` | API related documentation. |
|
||||
| Directory | What belongs here |
|
||||
|:----------------------|:------------------|
|
||||
| `doc/user/` | User related documentation. Anything that can be done within the GitLab user interface goes here, including usage of the `/admin` interface. |
|
||||
| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. Administrator settings in the GitLab user interface are under `doc/user/admin_area/`. |
|
||||
| `doc/api/` | API-related documentation. |
|
||||
| `doc/development/` | Documentation related to the development of GitLab, whether contributing code or documentation. Related process and style guides should go here. |
|
||||
| `doc/legal/` | Legal documents about contributing to GitLab. |
|
||||
| `doc/install/` | Contains instructions for installing GitLab. |
|
||||
| `doc/update/` | Contains instructions for updating GitLab. |
|
||||
| `doc/topics/` | Indexes per topic (`doc/topics/topic_name/index.md`): all resources for that topic. |
|
||||
| `doc/legal/` | Legal documents about contributing to GitLab. |
|
||||
| `doc/install/` | Contains instructions for installing GitLab. |
|
||||
| `doc/update/` | Contains instructions for updating GitLab. |
|
||||
| `doc/topics/` | Indexes per topic (`doc/topics/topic_name/index.md`): all resources for that topic. |
|
||||
|
||||
### Work with directories and files
|
||||
|
||||
|
@ -277,10 +276,10 @@ Refer to the following items when working with directories and files:
|
|||
Every page you would navigate under `/profile` should have its own document,
|
||||
for example, `account.md`, `applications.md`, or `emails.md`.
|
||||
- `doc/user/dashboard/` should contain all dashboard related documentation.
|
||||
- `doc/user/admin_area/` should contain all admin related documentation
|
||||
describing what can be achieved by accessing GitLab's admin interface
|
||||
(_not to be confused with `doc/administration` where server access is
|
||||
required_).
|
||||
- `doc/user/admin_area/` should contain all administrator-related
|
||||
documentation describing what can be achieved by accessing the GitLab
|
||||
administrator interface (not to be confused with `doc/administration` where
|
||||
server access is required).
|
||||
- Every category under `/admin/application_settings/` should have its
|
||||
own document located at `doc/user/admin_area/settings/`. For example,
|
||||
the **Visibility and Access Controls** category should have a document
|
||||
|
@ -567,9 +566,9 @@ tenses, words, and phrases:
|
|||
- Avoid using the word *currently* when talking about the product or its
|
||||
features. The documentation describes the product as it is, and not as it
|
||||
will be at some indeterminate point in the future.
|
||||
- Avoid the using the word *scalability* with increasing GitLab's performance
|
||||
for additional users. Using the words *scale* or *scaling* in other cases is
|
||||
acceptable, but references to increasing GitLab's performance for additional
|
||||
- Avoid using the word scalability when talking about increasing GitLab
|
||||
performance for additional users. The words scale or scaling are sometimes
|
||||
acceptable, but references to increasing GitLab performance for additional
|
||||
users should direct readers to the GitLab
|
||||
[reference architectures](../../../administration/reference_architectures/index.md)
|
||||
page.
|
||||
|
@ -577,8 +576,8 @@ tenses, words, and phrases:
|
|||
direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md)
|
||||
for information about configuring GitLab to have the performance needed for
|
||||
additional users over time.
|
||||
- Don't use profanity or obscenities. Doing so may negatively affect other
|
||||
users and contributors, which is contrary to GitLab's value of
|
||||
- Don't use profanity or obscenities. Doing so may negatively affect other users
|
||||
and contributors, which is contrary to the GitLab value of
|
||||
[Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion).
|
||||
- Avoid the use of [racially-insensitive terminology or phrases](https://www.marketplace.org/2020/06/17/tech-companies-update-language-to-avoid-offensive-terms/). For example:
|
||||
- Use *primary* and *secondary* for database and server relationships.
|
||||
|
@ -982,9 +981,9 @@ Important:
|
|||
tutorials, presentations, StackOverflow posts, and other sources.
|
||||
- Do not link to `h1` headings.
|
||||
|
||||
Note that, with Kramdown, it is possible to add a custom ID to an HTML element
|
||||
with Markdown markup, but they _do not_ work in GitLab's `/help`. Therefore,
|
||||
do not use this option until further notice.
|
||||
Note that with Kramdown, it's possible to add a custom ID to an HTML element
|
||||
with Markdown markup, but they don't work in `/help`. Because of this, don't use
|
||||
this option.
|
||||
|
||||
## Links
|
||||
|
||||
|
@ -1268,7 +1267,7 @@ request.
|
|||
|
||||
## Videos
|
||||
|
||||
Adding GitLab's existing YouTube video tutorials to the documentation is highly
|
||||
Adding GitLab YouTube video tutorials to the documentation is highly
|
||||
encouraged, unless the video is outdated. Videos should not replace
|
||||
documentation, but complement or illustrate it. If content in a video is
|
||||
fundamental to a feature and its key use cases, but this is not adequately
|
||||
|
@ -1297,7 +1296,7 @@ You can link any up-to-date video that's useful to the GitLab user.
|
|||
The [GitLab documentation site](https://docs.gitlab.com) supports embedded
|
||||
videos.
|
||||
|
||||
You can only embed videos from [GitLab's official YouTube account](https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg).
|
||||
You can embed videos from [the official YouTube account for GitLab](https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg) only.
|
||||
For videos from other sources, [link](#link-to-video) them instead.
|
||||
|
||||
In most cases, it is better to [link to video](#link-to-video) instead, because
|
||||
|
@ -1341,9 +1340,9 @@ This is how it renders on the GitLab documentation site:
|
|||
> - The `figure` tag is required for semantic SEO and the `video_container`
|
||||
class is necessary to make sure the video is responsive and displays on
|
||||
different mobile devices.
|
||||
> - The `<div class="video-fallback">` is a fallback necessary for GitLab's
|
||||
`/help`, as GitLab's Markdown processor does not support iframes. It's hidden on
|
||||
the documentation site, but will be displayed on GitLab's `/help`.
|
||||
> - The `<div class="video-fallback">` is a fallback necessary for
|
||||
`/help`, because the GitLab Markdown processor doesn't support iframes. It's
|
||||
hidden on the documentation site, but is displayed by `/help`.
|
||||
|
||||
## Code blocks
|
||||
|
||||
|
@ -1659,8 +1658,8 @@ elements:
|
|||
To help users be aware of recent product improvements or additions, we add
|
||||
GitLab version information to our documentation.
|
||||
|
||||
The GitLab Technical Writing team determines which versions of GitLab's
|
||||
documentation to display on this site based on GitLab's
|
||||
The GitLab Technical Writing team determines which versions of
|
||||
documentation to display on this site based on the GitLab
|
||||
[Statement of Support](https://about.gitlab.com/support/statement-of-support.html#we-support-the-current-major-version-and-the-two-previous-major-versions).
|
||||
|
||||
### View older GitLab documentation versions
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
|
@ -433,7 +433,13 @@ To remove a list from an issue board:
|
|||
1. Select **Remove list**. A confirmation dialog appears.
|
||||
1. Select **OK**.
|
||||
|
||||
### Add issues to a list
|
||||
### Add issues to a list **(CORE ONLY)**
|
||||
|
||||
> - Feature flag [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47898) in GitLab 13.7.
|
||||
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com.
|
||||
> - It's recommended for production use.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-adding-issues-to-the-list). **(CORE ONLY)**
|
||||
|
||||
You can add issues to a list in a project issue board by clicking the **Add issues** button
|
||||
in the top right corner of the issue board. This opens up a modal
|
||||
|
@ -453,7 +459,23 @@ the list by filtering by the following:
|
|||
- Release
|
||||
- Weight
|
||||
|
||||
![Bulk adding issues to lists](img/issue_boards_add_issues_modal_v13_6.png)
|
||||
#### Enable or disable adding issues to the list **(CORE ONLY)**
|
||||
|
||||
Adding issues to the list is deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can enable it.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:add_issues_button)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:add_issues_button)
|
||||
```
|
||||
|
||||
### Remove an issue from a list
|
||||
|
||||
|
|
|
@ -254,18 +254,18 @@ NOTE: **Note:**
|
|||
Although the Code Climate spec supports more properties, those are ignored by
|
||||
GitLab.
|
||||
|
||||
## Code Quality reports **(STARTER)**
|
||||
## Code Quality reports
|
||||
|
||||
Once the Code Quality job has completed:
|
||||
After the Code Quality job completes:
|
||||
|
||||
- The full list of code quality violations generated by a pipeline is shown in the
|
||||
Code Quality tab of the Pipeline Details page.
|
||||
- Potential changes to code quality are shown directly in the merge request.
|
||||
The Code Quality widget in the merge request compares the reports from the base and head of the branch,
|
||||
then lists any violations that are resolved or created when the branch is merged.
|
||||
- The full JSON report is available as a
|
||||
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
|
||||
for the `code_quality` job.
|
||||
- The full list of code quality violations generated by a pipeline is shown in the
|
||||
Code Quality tab of the Pipeline Details page. **(STARTER)**
|
||||
|
||||
### Generating an HTML report
|
||||
|
||||
|
|
|
@ -79,6 +79,16 @@ To create a new release through the GitLab UI:
|
|||
[release notes](#release-notes-description), or [assets links](#links).
|
||||
1. Click **Create release**.
|
||||
|
||||
### Create release from GitLab CI
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19298) in GitLab 12.7.
|
||||
|
||||
You can [create a release directly from the GitLab CI pipeline](../../../ci/yaml/README.md#release)
|
||||
by using a `release` node in the job definition.
|
||||
|
||||
The release is created only if the job processes without error. If the Rails API returns an error
|
||||
during release creation, the release job fails.
|
||||
|
||||
### Schedule a future release
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/38105) in GitLab 12.1.
|
||||
|
|
121
lib/gitlab/encrypted_configuration.rb
Normal file
121
lib/gitlab/encrypted_configuration.rb
Normal file
|
@ -0,0 +1,121 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class EncryptedConfiguration
|
||||
delegate :[], :fetch, to: :config
|
||||
delegate_missing_to :options
|
||||
attr_reader :content_path, :key, :previous_keys
|
||||
|
||||
CIPHER = "aes-256-gcm"
|
||||
SALT = "GitLabEncryptedConfigSalt"
|
||||
|
||||
class MissingKeyError < RuntimeError
|
||||
def initialize(msg = "Missing encryption key to encrypt/decrypt file with.")
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidConfigError < RuntimeError
|
||||
def initialize(msg = "Content was not a valid yml config file")
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def self.generate_key(base_key)
|
||||
# Because the salt is static, we want uniqueness to be coming from the base_key
|
||||
# Error if the base_key is empty or suspiciously short
|
||||
raise 'Base key too small' if base_key.blank? || base_key.length < 16
|
||||
|
||||
ActiveSupport::KeyGenerator.new(base_key).generate_key(SALT, ActiveSupport::MessageEncryptor.key_len(CIPHER))
|
||||
end
|
||||
|
||||
def initialize(content_path: nil, base_key: nil, previous_keys: [])
|
||||
@content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path } if content_path
|
||||
@key = self.class.generate_key(base_key) if base_key
|
||||
@previous_keys = previous_keys
|
||||
end
|
||||
|
||||
def active?
|
||||
content_path&.exist?
|
||||
end
|
||||
|
||||
def read
|
||||
if active?
|
||||
decrypt(content_path.binread)
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def write(contents)
|
||||
# ensure contents are valid to deserialize before write
|
||||
deserialize(contents)
|
||||
|
||||
temp_file = Tempfile.new(File.basename(content_path), File.dirname(content_path))
|
||||
File.open(temp_file.path, 'wb') do |file|
|
||||
file.write(encrypt(contents))
|
||||
end
|
||||
FileUtils.mv(temp_file.path, content_path)
|
||||
ensure
|
||||
temp_file&.unlink
|
||||
end
|
||||
|
||||
def config
|
||||
return @config if @config
|
||||
|
||||
contents = deserialize(read)
|
||||
|
||||
raise InvalidConfigError.new unless contents.is_a?(Hash)
|
||||
|
||||
@config = contents.deep_symbolize_keys
|
||||
end
|
||||
|
||||
def change(&block)
|
||||
writing(read, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def writing(contents)
|
||||
updated_contents = yield contents
|
||||
|
||||
write(updated_contents) if updated_contents != contents
|
||||
end
|
||||
|
||||
def encrypt(contents)
|
||||
handle_missing_key!
|
||||
encryptor.encrypt_and_sign(contents)
|
||||
end
|
||||
|
||||
def decrypt(contents)
|
||||
handle_missing_key!
|
||||
encryptor.decrypt_and_verify(contents)
|
||||
end
|
||||
|
||||
def encryptor
|
||||
return @encryptor if @encryptor
|
||||
|
||||
@encryptor = ActiveSupport::MessageEncryptor.new(key, cipher: CIPHER)
|
||||
|
||||
# Allow fallback to previous keys
|
||||
@previous_keys.each do |key|
|
||||
@encryptor.rotate(self.class.generate_key(key))
|
||||
end
|
||||
|
||||
@encryptor
|
||||
end
|
||||
|
||||
def options
|
||||
# Allows top level keys to be referenced using dot syntax
|
||||
@options ||= ActiveSupport::InheritableOptions.new(config)
|
||||
end
|
||||
|
||||
def deserialize(contents)
|
||||
YAML.safe_load(contents, permitted_classes: [Symbol]).presence || {}
|
||||
end
|
||||
|
||||
def handle_missing_key!
|
||||
raise MissingKeyError.new if @key.nil?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,8 +6,8 @@ namespace :gitlab do
|
|||
|
||||
result = User.where(id: group.direct_and_indirect_users_with_inactive.select(:id)).update_all(projects_limit: 0, can_create_group: false)
|
||||
ids_count = group.direct_and_indirect_users_with_inactive.count
|
||||
puts "Done".green if result == ids_count
|
||||
puts "Something went wrong".red if result != ids_count
|
||||
puts "Done".color(:green) if result == ids_count
|
||||
puts "Something went wrong".color(:red) if result != ids_count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -896,9 +896,6 @@ msgstr ""
|
|||
msgid "< 1 hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "<no scopes selected>"
|
||||
msgstr ""
|
||||
|
||||
msgid "'%{data}' at %{location} does not match format: %{format}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -32767,6 +32764,9 @@ msgstr ""
|
|||
msgid "no one can merge"
|
||||
msgstr ""
|
||||
|
||||
msgid "no scopes selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "none"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.175.0",
|
||||
"@gitlab/ui": "24.3.0",
|
||||
"@gitlab/ui": "24.3.1",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-3",
|
||||
"@rails/ujs": "^6.0.3-2",
|
||||
|
|
|
@ -134,4 +134,20 @@ RSpec.describe Settings do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.encrypted' do
|
||||
before do
|
||||
allow(Gitlab::Application.secrets).to receive(:encryped_settings_key_base).and_return(SecureRandom.hex(64))
|
||||
end
|
||||
|
||||
it 'defaults to using the encrypted_settings_key_base for the key' do
|
||||
expect(Gitlab::EncryptedConfiguration).to receive(:new).with(hash_including(base_key: Gitlab::Application.secrets.encrypted_settings_key_base))
|
||||
Settings.encrypted('tmp/tests/test.enc')
|
||||
end
|
||||
|
||||
it 'returns empty encrypted config when a key has not been set' do
|
||||
allow(Gitlab::Application.secrets).to receive(:encrypted_settings_key_base).and_return(nil)
|
||||
expect(Settings.encrypted('tmp/tests/test.enc').read).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,6 +37,10 @@ RSpec.describe 'Issue Boards add issue modal', :js do
|
|||
end
|
||||
|
||||
context 'modal interaction' do
|
||||
before do
|
||||
stub_feature_flags(add_issues_button: true)
|
||||
end
|
||||
|
||||
it 'opens modal' do
|
||||
click_button('Add issues')
|
||||
|
||||
|
@ -72,6 +76,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do
|
|||
|
||||
context 'issues list' do
|
||||
before do
|
||||
stub_feature_flags(add_issues_button: true)
|
||||
click_button('Add issues')
|
||||
|
||||
wait_for_requests
|
||||
|
|
|
@ -14,20 +14,57 @@ RSpec.describe 'Service Desk Setting', :js do
|
|||
allow_any_instance_of(Project).to receive(:present).with(current_user: user).and_return(presenter)
|
||||
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
|
||||
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
|
||||
|
||||
visit edit_project_path(project)
|
||||
end
|
||||
|
||||
it 'shows activation checkbox' do
|
||||
visit edit_project_path(project)
|
||||
|
||||
expect(page).to have_selector("#service-desk-checkbox")
|
||||
end
|
||||
|
||||
it 'shows incoming email after activating' do
|
||||
find("#service-desk-checkbox").click
|
||||
wait_for_requests
|
||||
project.reload
|
||||
expect(project.service_desk_enabled).to be_truthy
|
||||
expect(project.service_desk_address).to be_present
|
||||
expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_address)
|
||||
context 'when service_desk_email is disabled' do
|
||||
before do
|
||||
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
|
||||
|
||||
visit edit_project_path(project)
|
||||
end
|
||||
|
||||
it 'shows incoming email but not project name suffix after activating' do
|
||||
find("#service-desk-checkbox").click
|
||||
|
||||
wait_for_requests
|
||||
|
||||
project.reload
|
||||
expect(project.service_desk_enabled).to be_truthy
|
||||
expect(project.service_desk_address).to be_present
|
||||
expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_incoming_address)
|
||||
expect(page).not_to have_selector('#service-desk-project-suffix')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service_desk_email is enabled' do
|
||||
before do
|
||||
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?) { true }
|
||||
allow(::Gitlab::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' }
|
||||
|
||||
visit edit_project_path(project)
|
||||
end
|
||||
|
||||
it 'allows setting of custom address suffix' do
|
||||
find("#service-desk-checkbox").click
|
||||
wait_for_requests
|
||||
|
||||
project.reload
|
||||
expect(find('[data-testid="incoming-email"]').value).to eq(project.service_desk_incoming_address)
|
||||
|
||||
page.within '#js-service-desk' do
|
||||
fill_in('service-desk-project-suffix', with: 'foo')
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(find('[data-testid="incoming-email"]').value).to eq('address-suffix@example.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
|
|||
menu-class="dropdown-menu-large"
|
||||
>
|
||||
<button
|
||||
class="btn btn-danger btn-md gl-button split-content-button btn-danger-secondary"
|
||||
class="btn btn-danger btn-md gl-button split-content-button"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -27,7 +27,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
class="btn dropdown-toggle btn-danger btn-md gl-button gl-dropdown-toggle btn-danger-secondary dropdown-toggle-split"
|
||||
class="btn dropdown-toggle btn-danger btn-md gl-button gl-dropdown-toggle dropdown-toggle-split"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
|
||||
<gl-dropdown-stub
|
||||
category="tertiary"
|
||||
category="primary"
|
||||
headertext=""
|
||||
issueiid=""
|
||||
projectpath=""
|
||||
|
@ -42,7 +42,7 @@ exports[`Design management design version dropdown component renders design vers
|
|||
|
||||
exports[`Design management design version dropdown component renders design version list 1`] = `
|
||||
<gl-dropdown-stub
|
||||
category="tertiary"
|
||||
category="primary"
|
||||
headertext=""
|
||||
issueiid=""
|
||||
projectpath=""
|
||||
|
|
|
@ -47,7 +47,7 @@ exports[`Alert integration settings form default state should match the default
|
|||
|
||||
<gl-dropdown-stub
|
||||
block="true"
|
||||
category="tertiary"
|
||||
category="primary"
|
||||
data-qa-selector="incident_templates_dropdown"
|
||||
headertext=""
|
||||
id="alert-integration-settings-issue-template"
|
||||
|
|
|
@ -86,7 +86,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary"
|
||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -201,7 +201,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary"
|
||||
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
|
|
@ -33,7 +33,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
|
|||
class="mb-2 pr-2 d-flex d-sm-block"
|
||||
>
|
||||
<gl-dropdown-stub
|
||||
category="tertiary"
|
||||
category="primary"
|
||||
class="flex-grow-1"
|
||||
data-qa-selector="environments_dropdown"
|
||||
headertext=""
|
||||
|
|
|
@ -10,7 +10,7 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
|
|||
<!---->
|
||||
|
||||
<gl-dropdown-stub
|
||||
category="tertiary"
|
||||
category="primary"
|
||||
headertext=""
|
||||
size="medium"
|
||||
text="rspec"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`SplitButton renders actionItems 1`] = `
|
||||
<gl-dropdown-stub
|
||||
category="tertiary"
|
||||
category="primary"
|
||||
headertext=""
|
||||
menu-class=""
|
||||
size="medium"
|
||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe 'create_tokens' do
|
|||
|
||||
describe 'ensure acknowledged secrets in any installations' do
|
||||
let(:acknowledged_secrets) do
|
||||
%w[secret_key_base otp_key_base db_key_base openid_connect_signing_key]
|
||||
%w[secret_key_base otp_key_base db_key_base openid_connect_signing_key encrypted_settings_key_base rotated_encrypted_settings_key_base]
|
||||
end
|
||||
|
||||
it 'does not allow to add a new secret without a proper handling' do
|
||||
|
@ -90,6 +90,7 @@ RSpec.describe 'create_tokens' do
|
|||
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
|
||||
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
|
||||
expect(new_secrets['openid_connect_signing_key']).to eq(secrets.openid_connect_signing_key)
|
||||
expect(new_secrets['encrypted_settings_key_base']).to eq(secrets.encrypted_settings_key_base)
|
||||
end
|
||||
|
||||
create_tokens
|
||||
|
@ -106,6 +107,7 @@ RSpec.describe 'create_tokens' do
|
|||
before do
|
||||
secrets.db_key_base = 'db_key_base'
|
||||
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
|
||||
secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
|
||||
|
||||
allow(File).to receive(:exist?).with('.secret').and_return(true)
|
||||
stub_file_read('.secret', content: 'file_key')
|
||||
|
@ -158,6 +160,7 @@ RSpec.describe 'create_tokens' do
|
|||
expect(secrets.otp_key_base).to eq('otp_key_base')
|
||||
expect(secrets.db_key_base).to eq('db_key_base')
|
||||
expect(secrets.openid_connect_signing_key).to eq('openid_connect_signing_key')
|
||||
expect(secrets.encrypted_settings_key_base).to eq('encrypted_settings_key_base')
|
||||
end
|
||||
|
||||
it 'deletes the .secret file' do
|
||||
|
@ -208,12 +211,34 @@ RSpec.describe 'create_tokens' do
|
|||
create_tokens
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rotated_encrypted_settings_key_base does not exist' do
|
||||
before do
|
||||
secrets.secret_key_base = 'secret_key_base'
|
||||
secrets.otp_key_base = 'otp_key_base'
|
||||
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
|
||||
secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
|
||||
end
|
||||
|
||||
it 'does not warn about the missing secrets' do
|
||||
expect(self).not_to receive(:warn_missing_secret).with('rotated_encrypted_settings_key_base')
|
||||
|
||||
create_tokens
|
||||
end
|
||||
|
||||
it 'does not update secrets.yml' do
|
||||
expect(File).not_to receive(:write)
|
||||
|
||||
create_tokens
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when db_key_base is blank but exists in secrets.yml' do
|
||||
before do
|
||||
secrets.otp_key_base = 'otp_key_base'
|
||||
secrets.secret_key_base = 'secret_key_base'
|
||||
secrets.encrypted_settings_key_base = 'encrypted_settings_key_base'
|
||||
yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
|
||||
|
||||
allow(File).to receive(:exist?).with('.secret').and_return(false)
|
||||
|
|
145
spec/lib/gitlab/encrypted_configuration_spec.rb
Normal file
145
spec/lib/gitlab/encrypted_configuration_spec.rb
Normal file
|
@ -0,0 +1,145 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Gitlab::EncryptedConfiguration do
|
||||
subject(:configuration) { described_class.new }
|
||||
|
||||
let!(:config_tmp_dir) { Dir.mktmpdir('config-') }
|
||||
|
||||
after do
|
||||
FileUtils.rm_f(config_tmp_dir)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'accepts all args as optional fields' do
|
||||
expect { configuration }.not_to raise_exception
|
||||
|
||||
expect(configuration.key).to be_nil
|
||||
expect(configuration.previous_keys).to be_empty
|
||||
end
|
||||
|
||||
it 'generates 32 byte key when provided a larger base key' do
|
||||
configuration = described_class.new(base_key: 'A' * 64)
|
||||
|
||||
expect(configuration.key.bytesize).to eq 32
|
||||
end
|
||||
|
||||
it 'generates 32 byte key when provided a smaller base key' do
|
||||
configuration = described_class.new(base_key: 'A' * 16)
|
||||
|
||||
expect(configuration.key.bytesize).to eq 32
|
||||
end
|
||||
|
||||
it 'throws an error when the base key is too small' do
|
||||
expect { described_class.new(base_key: 'A' * 12) }.to raise_error 'Base key too small'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided a config file but no key' do
|
||||
let(:config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
|
||||
|
||||
it 'throws an error when writing without a key' do
|
||||
expect { described_class.new(content_path: config_path).write('test') }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
|
||||
end
|
||||
|
||||
it 'throws an error when reading without a key' do
|
||||
config = described_class.new(content_path: config_path)
|
||||
File.write(config_path, 'test')
|
||||
expect { config.read }.to raise_error Gitlab::EncryptedConfiguration::MissingKeyError
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided key and config file' do
|
||||
let(:credentials_config_path) { File.join(config_tmp_dir, 'credentials.yml.enc') }
|
||||
let(:credentials_key) { SecureRandom.hex(64) }
|
||||
|
||||
describe '#write' do
|
||||
it 'encrypts the file using the provided key' do
|
||||
encryptor = ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(credentials_key), cipher: 'aes-256-gcm')
|
||||
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||
|
||||
config.write('sample-content')
|
||||
expect(encryptor.decrypt_and_verify(File.read(credentials_config_path))).to eq('sample-content')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#read' do
|
||||
it 'reads yaml configuration' do
|
||||
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||
|
||||
config.write({ foo: { bar: true } }.to_yaml)
|
||||
expect(config[:foo][:bar]).to be true
|
||||
end
|
||||
|
||||
it 'allows referencing top level keys via dot syntax' do
|
||||
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||
|
||||
config.write({ foo: { bar: true } }.to_yaml)
|
||||
expect(config.foo[:bar]).to be true
|
||||
end
|
||||
|
||||
it 'throws a custom error when referencing an invalid key map config' do
|
||||
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||
|
||||
config.write("stringcontent")
|
||||
expect { config[:foo] }.to raise_error Gitlab::EncryptedConfiguration::InvalidConfigError
|
||||
end
|
||||
end
|
||||
|
||||
describe '#change' do
|
||||
it 'changes yaml configuration' do
|
||||
config = described_class.new(content_path: credentials_config_path, base_key: credentials_key)
|
||||
|
||||
config.write({ foo: { bar: true } }.to_yaml)
|
||||
config.change do |unencrypted_contents|
|
||||
contents = YAML.safe_load(unencrypted_contents, permitted_classes: [Symbol])
|
||||
contents.merge(beef: "stew").to_yaml
|
||||
end
|
||||
expect(config.foo[:bar]).to be true
|
||||
expect(config.beef).to eq('stew')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided previous_keys for rotation' do
|
||||
let(:credential_key_original) { SecureRandom.hex(64) }
|
||||
let(:credential_key_latest) { SecureRandom.hex(64) }
|
||||
let(:config_path_original) { File.join(config_tmp_dir, 'credentials-orig.yml.enc') }
|
||||
let(:config_path_latest) { File.join(config_tmp_dir, 'credentials-latest.yml.enc') }
|
||||
|
||||
def encryptor(key)
|
||||
ActiveSupport::MessageEncryptor.new(Gitlab::EncryptedConfiguration.generate_key(key), cipher: 'aes-256-gcm')
|
||||
end
|
||||
|
||||
describe '#write' do
|
||||
it 'rotates the key when provided a new key' do
|
||||
config1 = described_class.new(content_path: config_path_original, base_key: credential_key_original)
|
||||
config1.write('sample-content1')
|
||||
|
||||
config2 = described_class.new(content_path: config_path_latest, base_key: credential_key_latest, previous_keys: [credential_key_original])
|
||||
config2.write('sample-content2')
|
||||
|
||||
original_key_encryptor = encryptor(credential_key_original) # can read with the initial key
|
||||
latest_key_encryptor = encryptor(credential_key_latest) # can read with the new key
|
||||
both_key_encryptor = encryptor(credential_key_latest) # can read with either key
|
||||
both_key_encryptor.rotate(Gitlab::EncryptedConfiguration.generate_key(credential_key_original))
|
||||
|
||||
expect(original_key_encryptor.decrypt_and_verify(File.read(config_path_original))).to eq('sample-content1')
|
||||
expect(both_key_encryptor.decrypt_and_verify(File.read(config_path_original))).to eq('sample-content1')
|
||||
expect(latest_key_encryptor.decrypt_and_verify(File.read(config_path_latest))).to eq('sample-content2')
|
||||
expect(both_key_encryptor.decrypt_and_verify(File.read(config_path_latest))).to eq('sample-content2')
|
||||
expect { original_key_encryptor.decrypt_and_verify(File.read(config_path_latest)) }.to raise_error(ActiveSupport::MessageEncryptor::InvalidMessage)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#read' do
|
||||
it 'supports reading using rotated config' do
|
||||
described_class.new(content_path: config_path_original, base_key: credential_key_original).write({ foo: { bar: true } }.to_yaml)
|
||||
|
||||
config = described_class.new(content_path: config_path_original, base_key: credential_key_latest, previous_keys: [credential_key_original])
|
||||
expect(config[:foo][:bar]).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1535,6 +1535,42 @@ RSpec.describe Project, factory_default: :keep do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.service_desk_custom_address_enabled?' do
|
||||
let_it_be(:project) { create(:project, service_desk_enabled: true) }
|
||||
|
||||
subject(:address_enabled) { project.service_desk_custom_address_enabled? }
|
||||
|
||||
context 'when service_desk_email is enabled' do
|
||||
before do
|
||||
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(address_enabled).to be_truthy
|
||||
end
|
||||
|
||||
context 'when service_desk_custom_address flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(service_desk_custom_address: false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(address_enabled).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service_desk_email is disabled' do
|
||||
before do
|
||||
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false when service_desk_email is disabled' do
|
||||
expect(address_enabled).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.find_by_service_desk_project_key' do
|
||||
it 'returns the correct project' do
|
||||
project1 = create(:project)
|
||||
|
|
|
@ -866,10 +866,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.175.0.tgz#734f341784af1cd1d62d160a17bcdfb61ff7b04d"
|
||||
integrity sha512-gXpc87TGSXIzfAr4QER1Qw1v3P47pBO6BXkma52blgwXVmcFNe3nhQzqsqt66wKNzrIrk3lAcB4GUyPHbPVXpg==
|
||||
|
||||
"@gitlab/ui@24.3.0":
|
||||
version "24.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.3.0.tgz#6e3f7d6e198d12faad046ba819ffc56aa520d2ea"
|
||||
integrity sha512-jfDMEEUrJUC8OoQrtpeupomfbiIX0AnQ5SYaugVwkNocRQ/dWwFf1OwbnKh3BNRNUS2581Vt1yjZb32CO8+GiA==
|
||||
"@gitlab/ui@24.3.1":
|
||||
version "24.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-24.3.1.tgz#3bc40a1e33326fa89cf25d29afa1628c0ab59684"
|
||||
integrity sha512-m2/Q/vuWrDt5ZGcdbf7VB6h152VtDA+BeVZu1T8RxzQGzgCJ9XnRqb8wTvW4KOXzdbsY/iqCHPRYhZKVrnakqg==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
Loading…
Reference in a new issue