Commit 25ef3a96 authored by Stan Hu's avatar Stan Hu

Merge branch 'thomas-nilsson-irfu/gitlab-ce-thomas-nilsson-irfu-master-patch-13137' into 'master'

Allow masking if 8 or more characters in base64

See merge request gitlab-org/gitlab-ce!29143
parents 2320e445 6a90249b
...@@ -27,15 +27,24 @@ function generateErrorBoxContent(errors) { ...@@ -27,15 +27,24 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page // Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList { export default class AjaxVariableList {
constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) { constructor({
container,
saveButton,
errorBox,
formField = 'variables',
saveEndpoint,
maskableRegex,
}) {
this.container = container; this.container = container;
this.saveButton = saveButton; this.saveButton = saveButton;
this.errorBox = errorBox; this.errorBox = errorBox;
this.saveEndpoint = saveEndpoint; this.saveEndpoint = saveEndpoint;
this.maskableRegex = maskableRegex;
this.variableList = new VariableList({ this.variableList = new VariableList({
container: this.container, container: this.container,
formField, formField,
maskableRegex,
}); });
this.bindEvents(); this.bindEvents();
......
...@@ -16,9 +16,10 @@ function createEnvironmentItem(value) { ...@@ -16,9 +16,10 @@ function createEnvironmentItem(value) {
} }
export default class VariableList { export default class VariableList {
constructor({ container, formField }) { constructor({ container, formField, maskableRegex }) {
this.$container = $(container); this.$container = $(container);
this.formField = formField; this.formField = formField;
this.maskableRegex = new RegExp(maskableRegex);
this.environmentDropdownMap = new WeakMap(); this.environmentDropdownMap = new WeakMap();
this.inputMap = { this.inputMap = {
...@@ -196,9 +197,8 @@ export default class VariableList { ...@@ -196,9 +197,8 @@ export default class VariableList {
validateMaskability($row) { validateMaskability($row) {
const invalidInputClass = 'gl-field-error-outline'; const invalidInputClass = 'gl-field-error-outline';
const maskableRegex = /^\w{8,}$/; // Eight or more alphanumeric characters plus underscores
const variableValue = $row.find(this.inputMap.secret_value.selector).val(); const variableValue = $row.find(this.inputMap.secret_value.selector).val();
const isValueMaskable = maskableRegex.test(variableValue) || variableValue === ''; const isValueMaskable = this.maskableRegex.test(variableValue) || variableValue === '';
const isMaskedChecked = $row.find(this.inputMap.masked.selector).val() === 'true'; const isMaskedChecked = $row.find(this.inputMap.masked.selector).val() === 'true';
// Show a validation error if the user wants to mask an unmaskable variable value // Show a validation error if the user wants to mask an unmaskable variable value
......
...@@ -12,5 +12,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -12,5 +12,6 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'), saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'), errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint, saveEndpoint: variableListEl.dataset.saveEndpoint,
maskableRegex: variableListEl.dataset.maskableRegex,
}); });
}); });
...@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'), saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'), errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint, saveEndpoint: variableListEl.dataset.saveEndpoint,
maskableRegex: variableListEl.dataset.maskableRegex,
}); });
// hide extra auto devops settings based checkbox state // hide extra auto devops settings based checkbox state
......
...@@ -27,4 +27,8 @@ module CiVariablesHelper ...@@ -27,4 +27,8 @@ module CiVariablesHelper
%w(File file) %w(File file)
] ]
end end
def ci_variable_maskable_regex
Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
end
end end
...@@ -7,9 +7,9 @@ module Maskable ...@@ -7,9 +7,9 @@ module Maskable
# * No escape characters # * No escape characters
# * No variables # * No variables
# * No spaces # * No spaces
# * Minimal length of 8 characters # * Minimal length of 8 characters from the Base64 alphabets (RFC4648)
# * Absolutely no fun is allowed # * Absolutely no fun is allowed
REGEX = /\A\w{8,}\z/.freeze REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze
included do included do
validates :masked, inclusion: { in: [true, false] } validates :masked, inclusion: { in: [true, false] }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } = s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.row .row
.col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } } .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint, maskable_regex: ci_variable_maskable_regex } }
.hide.alert.alert-danger.js-ci-variable-error-box .hide.alert.alert-danger.js-ci-variable-error-box
%ul.ci-variable-list %ul.ci-variable-list
......
---
title: Allow masking if 8 or more characters in base64.
merge_request: 29143
author: thomas-nilsson-irfu
type: changed
...@@ -92,7 +92,7 @@ This means that the value of the variable will be hidden in job logs, ...@@ -92,7 +92,7 @@ This means that the value of the variable will be hidden in job logs,
though it must match certain requirements to do so: though it must match certain requirements to do so:
- The value must be in a single line. - The value must be in a single line.
- The value must contain only letters, numbers, or underscores. - The value must only consist of characters from the Base64 alphabet, defined in [RFC4648](https://tools.ietf.org/html/rfc4648).
- The value must be at least 8 characters long. - The value must be at least 8 characters long.
- The value must not use variables. - The value must not use variables.
......
...@@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => { ...@@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => {
saveButton, saveButton,
errorBox, errorBox,
saveEndpoint: container.dataset.saveEndpoint, saveEndpoint: container.dataset.saveEndpoint,
maskableRegex: container.dataset.maskableRegex,
}); });
spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough(); spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough();
...@@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => { ...@@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => {
expect(row.dataset.isPersisted).toEqual('true'); expect(row.dataset.isPersisted).toEqual('true');
}); });
}); });
describe('maskableRegex', () => {
it('takes in the regex provided by the data attribute', () => {
expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
});
});
}); });
...@@ -150,6 +150,65 @@ describe('VariableList', () => { ...@@ -150,6 +150,65 @@ describe('VariableList', () => {
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
describe('validateMaskability', () => {
let $row;
const maskingErrorElement = '.js-row:last-child .masking-validation-error';
beforeEach(() => {
$row = $wrapper.find('.js-row:last-child');
$row.find('.ci-variable-masked-item .js-project-feature-toggle').click();
});
it('has a regex provided via a data attribute', () => {
expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
});
it('allows values that are 8 characters long', done => {
$row.find('.js-ci-variable-input-value').val('looooong');
getSetTimeoutPromise()
.then(() => {
expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
})
.then(done)
.catch(done.fail);
});
it('rejects values that are shorter than 8 characters', done => {
$row.find('.js-ci-variable-input-value').val('short');
getSetTimeoutPromise()
.then(() => {
expect($wrapper.find(maskingErrorElement)).toBeVisible();
})
.then(done)
.catch(done.fail);
});
it('allows values with base 64 characters', done => {
$row.find('.js-ci-variable-input-value').val('abcABC123_+=/-');
getSetTimeoutPromise()
.then(() => {
expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
})
.then(done)
.catch(done.fail);
});
it('rejects values with other special characters', done => {
$row.find('.js-ci-variable-input-value').val('1234567$');
getSetTimeoutPromise()
.then(() => {
expect($wrapper.find(maskingErrorElement)).toBeVisible();
})
.then(done)
.catch(done.fail);
});
});
}); });
describe('toggleEnableRow method', () => { describe('toggleEnableRow method', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment