Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-30 21:09:16 +00:00
parent ace0df53d3
commit 2ddcd634fc
49 changed files with 580 additions and 102 deletions

View File

@ -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,

View File

@ -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() {

View File

@ -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 : {};

View File

@ -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"
>

View File

@ -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()"

View File

@ -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"

View File

@ -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;

View File

@ -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,

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}",

View File

@ -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(_('&lt;no scopes selected&gt;')).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

View File

@ -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(_('&lt;no scopes selected&gt;')).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

View File

@ -0,0 +1,5 @@
---
title: Remove brackets in no scopes selected message in access and deploy tokens lists
merge_request: 47628
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove `Add Issues` button and a related modal
merge_request: 47898
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix styling of various dropdowns
merge_request: 48800
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix confirmation modal showing on project integration
merge_request: 48720
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix spacing between buttons on pipeline header
merge_request: 48660
author:
type: fixed

View 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

View File

@ -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.

View File

@ -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?

View File

@ -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'] ||= {}
#

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View 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

View File

@ -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

View File

@ -896,9 +896,6 @@ msgstr ""
msgid "&lt; 1 hour"
msgstr ""
msgid "&lt;no scopes selected&gt;"
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 ""

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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=""

View File

@ -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"

View File

@ -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"
>
<!---->

View File

@ -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=""

View File

@ -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"

View File

@ -2,7 +2,7 @@
exports[`SplitButton renders actionItems 1`] = `
<gl-dropdown-stub
category="tertiary"
category="primary"
headertext=""
menu-class=""
size="medium"

View File

@ -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)

View 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

View File

@ -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)

View File

@ -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"