New upstream version 14.7.7+ds1
This commit is contained in:
parent
0d67b01f61
commit
c611f599cf
124 changed files with 1804 additions and 417 deletions
|
@ -13,6 +13,9 @@
|
||||||
.if-jh: &if-jh
|
.if-jh: &if-jh
|
||||||
if: '$CI_PROJECT_PATH =~ /^gitlab-(jh|cn)\/.*/'
|
if: '$CI_PROJECT_PATH =~ /^gitlab-(jh|cn)\/.*/'
|
||||||
|
|
||||||
|
.if-force-ci: &if-force-ci
|
||||||
|
if: '$FORCE_GITLAB_CI'
|
||||||
|
|
||||||
.if-default-refs: &if-default-refs
|
.if-default-refs: &if-default-refs
|
||||||
if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
|
if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
|
||||||
|
|
||||||
|
@ -485,6 +488,7 @@
|
||||||
- <<: *if-dot-com-gitlab-org-default-branch
|
- <<: *if-dot-com-gitlab-org-default-branch
|
||||||
changes: *code-qa-patterns
|
changes: *code-qa-patterns
|
||||||
- <<: *if-dot-com-gitlab-org-schedule
|
- <<: *if-dot-com-gitlab-org-schedule
|
||||||
|
- <<: *if-force-ci
|
||||||
|
|
||||||
.build-images:rules:build-assets-image:
|
.build-images:rules:build-assets-image:
|
||||||
rules:
|
rules:
|
||||||
|
@ -781,6 +785,9 @@
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
- <<: *if-dot-com-gitlab-org-schedule
|
- <<: *if-dot-com-gitlab-org-schedule
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
- <<: *if-force-ci
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
.qa:rules:package-and-qa:feature-flags:
|
.qa:rules:package-and-qa:feature-flags:
|
||||||
rules:
|
rules:
|
||||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -2,6 +2,53 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 14.7.7 (2022-03-31)
|
||||||
|
|
||||||
|
### Security (21 changes)
|
||||||
|
|
||||||
|
- [Update to commonmarker 0.23.4](gitlab-org/security/gitlab@eb4b231173c86901f93b5b7781716b1f7706dad1) ([merge request](gitlab-org/security/gitlab!2283))
|
||||||
|
- [Revert merge request approval groups behavior](gitlab-org/security/gitlab@08e3ecced649f6ad241db6de7050b1502f7bef21) ([merge request](gitlab-org/security/gitlab!2333))
|
||||||
|
- [Disallow login if password matches a fixed list](gitlab-org/security/gitlab@02a69ab32da1ac67d855de3ee388d0bd2bb6586e) ([merge request](gitlab-org/security/gitlab!2359))
|
||||||
|
- [Update devise-two-factor to 4.0.2](gitlab-org/security/gitlab@c9fde96c7780f5b883cd1ac63d7ac3d5f4d78dc6) ([merge request](gitlab-org/security/gitlab!2351))
|
||||||
|
- [Limit the number of tags associated with a CI runner](gitlab-org/security/gitlab@00124d5f8ba0d7437d1f6f19b029754bf481185b) ([merge request](gitlab-org/security/gitlab!2305))
|
||||||
|
- [GitLab Pages Security Updates for 14.9](gitlab-org/security/gitlab@d335917e233658fa9d4452053469c3582ef38368) ([merge request](gitlab-org/security/gitlab!2325))
|
||||||
|
- [Upgrade swagger-ui dependency](gitlab-org/security/gitlab@7a8ce32f70fd0338817705651ee0dbe0a277d5f1) ([merge request](gitlab-org/security/gitlab!2338))
|
||||||
|
- [Modify release link format check to avoid regex if string is too long](gitlab-org/security/gitlab@e18dc2be245bca7e192c8536d1ba7de2ad798c43) ([merge request](gitlab-org/security/gitlab!2244))
|
||||||
|
- [Masks variables in error messages](gitlab-org/security/gitlab@1706c5cf9b939a6ab0682db7b8945feb851a3f8b) ([merge request](gitlab-org/security/gitlab!2292))
|
||||||
|
- [Escape user provided string to prevent XSS](gitlab-org/security/gitlab@c57edf9ab52810d455e41d71bad4e4d12c098cad) ([merge request](gitlab-org/security/gitlab!2315))
|
||||||
|
- [Monkey patch of RDoc to prevent Ruby segfault](gitlab-org/security/gitlab@f9e5597d1864d03bf1f0103787becbc84886968d) ([merge request](gitlab-org/security/gitlab!2233))
|
||||||
|
- [Project import maps members' created_by_id users based on source user ID](gitlab-org/security/gitlab@3ea1e477e0596f15e040f42b59fa86953d057128) ([merge request](gitlab-org/security/gitlab!2239))
|
||||||
|
- [Redact InvalidURIError error messages](gitlab-org/security/gitlab@a42ede835e32f44b68c1affe78a7ee48332bb30a) ([merge request](gitlab-org/security/gitlab!2297))
|
||||||
|
- [Fix access for approval rules API](gitlab-org/security/gitlab@b8c3997763d1e041dc2b82e464a99a5b2f15a798) ([merge request](gitlab-org/security/gitlab!2324))
|
||||||
|
- [Fix kroki exploit](gitlab-org/security/gitlab@ad123e33510103af4fb00378ef1fc8dae4cacb21) ([merge request](gitlab-org/security/gitlab!2278))
|
||||||
|
- [Fix blind SSRF when looking up SSH host keys for mirroring](gitlab-org/security/gitlab@0209f44cb4876f0a9ef13d4c8875a95a0cda1e2f) ([merge request](gitlab-org/security/gitlab!2311))
|
||||||
|
- [Escape original content in reference redactor](gitlab-org/security/gitlab@f63861d8fe7b2b8d161162063e7995782cbfada8) ([merge request](gitlab-org/security/gitlab!2319))
|
||||||
|
- [Security fix for CI/CD analytics visibility](gitlab-org/security/gitlab@fea6a4ff80862f9dba493405d03d82cf129e8854) ([merge request](gitlab-org/security/gitlab!2274))
|
||||||
|
- [Latest commit exposed through fork of a private project](gitlab-org/security/gitlab@b573cea38cdce020e5f25fb9de60e0e506c87a9b) ([merge request](gitlab-org/security/gitlab!2272))
|
||||||
|
- [Fix Asana integration restricted branch filter](gitlab-org/security/gitlab@56e2d9ae3de4f587d2c8a5aa111c2922553d6b7b) ([merge request](gitlab-org/security/gitlab!2214))
|
||||||
|
- [Revert "JH need more complex passwords"](gitlab-org/security/gitlab@2419522b02700ce98e0c4d6e7bfd4d28b6464506) ([merge request](gitlab-org/security/gitlab!2354))
|
||||||
|
|
||||||
|
## 14.7.6 (2022-03-24)
|
||||||
|
|
||||||
|
### Added (1 change)
|
||||||
|
|
||||||
|
- [Detect and fix artifacts with backfilled expire_at](gitlab-org/gitlab@92938348905581798fa669051a61c107d082d908) ([merge request](gitlab-org/gitlab!83054))
|
||||||
|
|
||||||
|
### Changed (2 changes)
|
||||||
|
|
||||||
|
- [Enable feature flags to resume artifact removal on self-managed](gitlab-org/gitlab@45e4aba7099e0b6963674d192dc87edfe9ff8cdb) ([merge request](gitlab-org/gitlab!83054))
|
||||||
|
- [Remove runners token prefix feature flags](gitlab-org/gitlab@d57e7e1966cac500ba830dca7843cb315a34a4e4) ([merge request](gitlab-org/gitlab!82121))
|
||||||
|
|
||||||
|
## 14.7.5 (2022-03-09)
|
||||||
|
|
||||||
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- [Ensure cleanup job artifacts task does not include pipeline artifacts](gitlab-org/gitlab@7b5e91bc78c46109e48537b20239d4ab649a971a) ([merge request](gitlab-org/gitlab!82430))
|
||||||
|
|
||||||
|
### Other (1 change)
|
||||||
|
|
||||||
|
- [Change to truncate table before adding finding_link_url_idx](gitlab-org/gitlab@6411ec61f40cb8648cea24ed26c1d69c8b910891) ([merge request](gitlab-org/gitlab!82430))
|
||||||
|
|
||||||
## 14.7.4 (2022-02-25)
|
## 14.7.4 (2022-02-25)
|
||||||
|
|
||||||
### Security (8 changes)
|
### Security (8 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
14.7.4
|
14.7.7
|
|
@ -1 +1 @@
|
||||||
1.51.0
|
1.51.1
|
||||||
|
|
4
Gemfile
4
Gemfile
|
@ -65,7 +65,7 @@ gem 'akismet', '~> 3.0'
|
||||||
gem 'invisible_captcha', '~> 1.1.0'
|
gem 'invisible_captcha', '~> 1.1.0'
|
||||||
|
|
||||||
# Two-factor authentication
|
# Two-factor authentication
|
||||||
gem 'devise-two-factor', '~> 4.0.0'
|
gem 'devise-two-factor', '~> 4.0.2'
|
||||||
gem 'rqrcode-rails3', '~> 0.1.7'
|
gem 'rqrcode-rails3', '~> 0.1.7'
|
||||||
gem 'attr_encrypted', '~> 3.1.0'
|
gem 'attr_encrypted', '~> 3.1.0'
|
||||||
gem 'u2f', '~> 0.2.1'
|
gem 'u2f', '~> 0.2.1'
|
||||||
|
@ -153,7 +153,7 @@ gem 'html-pipeline', '~> 2.13.2'
|
||||||
gem 'deckar01-task_list', '2.3.1'
|
gem 'deckar01-task_list', '2.3.1'
|
||||||
gem 'gitlab-markup', '~> 1.8.0'
|
gem 'gitlab-markup', '~> 1.8.0'
|
||||||
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
|
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
|
||||||
gem 'commonmarker', '~> 0.23.2'
|
gem 'commonmarker', '~> 0.23.4'
|
||||||
gem 'kramdown', '~> 2.3.1'
|
gem 'kramdown', '~> 2.3.1'
|
||||||
gem 'RedCloth', '~> 4.3.2'
|
gem 'RedCloth', '~> 4.3.2'
|
||||||
gem 'rdoc', '~> 6.3.2'
|
gem 'rdoc', '~> 6.3.2'
|
||||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -200,7 +200,7 @@ GEM
|
||||||
open4 (~> 1.3)
|
open4 (~> 1.3)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commonmarker (0.23.2)
|
commonmarker (0.23.4)
|
||||||
concurrent-ruby (1.1.9)
|
concurrent-ruby (1.1.9)
|
||||||
connection_pool (2.2.2)
|
connection_pool (2.2.2)
|
||||||
contracts (0.11.0)
|
contracts (0.11.0)
|
||||||
|
@ -265,11 +265,11 @@ GEM
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (4.0.0)
|
devise-two-factor (4.0.2)
|
||||||
activesupport (< 6.2)
|
activesupport (< 7.1)
|
||||||
attr_encrypted (>= 1.3, < 4, != 2)
|
attr_encrypted (>= 1.3, < 4, != 2)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
railties (< 6.2)
|
railties (< 7.1)
|
||||||
rotp (~> 6.0)
|
rotp (~> 6.0)
|
||||||
diff-lcs (1.4.4)
|
diff-lcs (1.4.4)
|
||||||
diff_match_patch (0.1.0)
|
diff_match_patch (0.1.0)
|
||||||
|
@ -1421,7 +1421,7 @@ DEPENDENCIES
|
||||||
capybara-screenshot (~> 1.0.22)
|
capybara-screenshot (~> 1.0.22)
|
||||||
carrierwave (~> 1.3)
|
carrierwave (~> 1.3)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
commonmarker (~> 0.23.2)
|
commonmarker (~> 0.23.4)
|
||||||
concurrent-ruby (~> 1.1)
|
concurrent-ruby (~> 1.1)
|
||||||
connection_pool (~> 2.0)
|
connection_pool (~> 2.0)
|
||||||
countries (~> 3.0)
|
countries (~> 3.0)
|
||||||
|
@ -1435,7 +1435,7 @@ DEPENDENCIES
|
||||||
derailed_benchmarks
|
derailed_benchmarks
|
||||||
device_detector
|
device_detector
|
||||||
devise (~> 4.7.2)
|
devise (~> 4.7.2)
|
||||||
devise-two-factor (~> 4.0.0)
|
devise-two-factor (~> 4.0.2)
|
||||||
diff_match_patch (~> 0.1.0)
|
diff_match_patch (~> 0.1.0)
|
||||||
diffy (~> 3.3)
|
diffy (~> 3.3)
|
||||||
discordrb-webhooks (~> 3.4)
|
discordrb-webhooks (~> 3.4)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
14.7.4
|
14.7.7
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script>
|
||||||
|
import { GlAlert } from '@gitlab/ui';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
i18n: {
|
||||||
|
bodyText: __('Warning: Displaying this diagram might cause performance issues on this page.'),
|
||||||
|
buttonText: __('Display'),
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
GlAlert,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<gl-alert
|
||||||
|
:primary-button-text="$options.i18n.buttonText"
|
||||||
|
variant="warning"
|
||||||
|
@dismiss="$emit('closeAlert')"
|
||||||
|
@primaryAction="$emit('showImage')"
|
||||||
|
>
|
||||||
|
{{ $options.i18n.bodyText }}
|
||||||
|
</gl-alert>
|
||||||
|
</template>
|
|
@ -1,3 +1,19 @@
|
||||||
// https://prosemirror.net/docs/ref/#model.ParseRule.priority
|
// https://prosemirror.net/docs/ref/#model.ParseRule.priority
|
||||||
export const DEFAULT_PARSE_RULE_PRIORITY = 50;
|
export const DEFAULT_PARSE_RULE_PRIORITY = 50;
|
||||||
export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY;
|
export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY;
|
||||||
|
|
||||||
|
export const unrestrictedPages = [
|
||||||
|
// Group wiki
|
||||||
|
'groups:wikis:show',
|
||||||
|
'groups:wikis:edit',
|
||||||
|
'groups:wikis:create',
|
||||||
|
|
||||||
|
// Project wiki
|
||||||
|
'projects:wikis:show',
|
||||||
|
'projects:wikis:edit',
|
||||||
|
'projects:wikis:create',
|
||||||
|
|
||||||
|
// Project files
|
||||||
|
'projects:show',
|
||||||
|
'projects:blob:show',
|
||||||
|
];
|
||||||
|
|
|
@ -2,6 +2,7 @@ import $ from 'jquery';
|
||||||
import syntaxHighlight from '~/syntax_highlight';
|
import syntaxHighlight from '~/syntax_highlight';
|
||||||
import initUserPopovers from '../../user_popovers';
|
import initUserPopovers from '../../user_popovers';
|
||||||
import highlightCurrentUser from './highlight_current_user';
|
import highlightCurrentUser from './highlight_current_user';
|
||||||
|
import { renderKroki } from './render_kroki';
|
||||||
import renderMath from './render_math';
|
import renderMath from './render_math';
|
||||||
import renderMermaid from './render_mermaid';
|
import renderMermaid from './render_mermaid';
|
||||||
import renderSandboxedMermaid from './render_sandboxed_mermaid';
|
import renderSandboxedMermaid from './render_sandboxed_mermaid';
|
||||||
|
@ -13,6 +14,7 @@ import renderMetrics from './render_metrics';
|
||||||
//
|
//
|
||||||
$.fn.renderGFM = function renderGFM() {
|
$.fn.renderGFM = function renderGFM() {
|
||||||
syntaxHighlight(this.find('.js-syntax-highlight').get());
|
syntaxHighlight(this.find('.js-syntax-highlight').get());
|
||||||
|
renderKroki(this.find('.js-render-kroki[hidden]').get());
|
||||||
renderMath(this.find('.js-render-math'));
|
renderMath(this.find('.js-render-math'));
|
||||||
if (gon.features?.sandboxedMermaid) {
|
if (gon.features?.sandboxedMermaid) {
|
||||||
renderSandboxedMermaid(this.find('.js-render-mermaid'));
|
renderSandboxedMermaid(this.find('.js-render-mermaid'));
|
||||||
|
|
63
app/assets/javascripts/behaviors/markdown/render_kroki.js
Normal file
63
app/assets/javascripts/behaviors/markdown/render_kroki.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import DiagramPerformanceWarning from '../components/diagram_performance_warning.vue';
|
||||||
|
import { unrestrictedPages } from './constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create alert element.
|
||||||
|
*
|
||||||
|
* @param {Element} krokiImage Kroki `img` element
|
||||||
|
* @return {Element} Alert element
|
||||||
|
*/
|
||||||
|
function createAlert(krokiImage) {
|
||||||
|
const app = new Vue({
|
||||||
|
el: document.createElement('div'),
|
||||||
|
name: 'DiagramPerformanceWarningRoot',
|
||||||
|
render(createElement) {
|
||||||
|
return createElement(DiagramPerformanceWarning, {
|
||||||
|
on: {
|
||||||
|
closeAlert() {
|
||||||
|
app.$destroy();
|
||||||
|
app.$el.remove();
|
||||||
|
},
|
||||||
|
showImage() {
|
||||||
|
krokiImage.removeAttribute('hidden');
|
||||||
|
app.$destroy();
|
||||||
|
app.$el.remove();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.$el;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add warning alert to hidden Kroki images,
|
||||||
|
* or show Kroki image if on an unrestricted page.
|
||||||
|
*
|
||||||
|
* Kroki images are given a hidden attribute by the
|
||||||
|
* backend when the original markdown source is large.
|
||||||
|
*
|
||||||
|
* @param {Array<Element>} krokiImages Array of hidden Kroki `img` elements
|
||||||
|
*/
|
||||||
|
export function renderKroki(krokiImages) {
|
||||||
|
const pageName = document.querySelector('body').dataset.page;
|
||||||
|
const isUnrestrictedPage = unrestrictedPages.includes(pageName);
|
||||||
|
|
||||||
|
krokiImages.forEach((krokiImage) => {
|
||||||
|
if (isUnrestrictedPage) {
|
||||||
|
krokiImage.removeAttribute('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = krokiImage.closest('.js-markdown-code');
|
||||||
|
|
||||||
|
// A single Kroki image is processed multiple times for some reason,
|
||||||
|
// so this condition ensures we only create one alert per Kroki image
|
||||||
|
if (!parent.hasAttribute('data-kroki-processed')) {
|
||||||
|
parent.setAttribute('data-kroki-processed', 'true');
|
||||||
|
parent.after(createAlert(krokiImage));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { once, countBy } from 'lodash';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import { darkModeEnabled } from '~/lib/utils/color_utils';
|
import { darkModeEnabled } from '~/lib/utils/color_utils';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
|
import { unrestrictedPages } from './constants';
|
||||||
|
|
||||||
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
||||||
// `js-render-mermaid` class.
|
// `js-render-mermaid` class.
|
||||||
|
@ -30,24 +31,6 @@ let renderedMermaidBlocks = 0;
|
||||||
|
|
||||||
let mermaidModule = {};
|
let mermaidModule = {};
|
||||||
|
|
||||||
// Whitelist pages where we won't impose any restrictions
|
|
||||||
// on mermaid rendering
|
|
||||||
const WHITELISTED_PAGES = [
|
|
||||||
// Group wiki
|
|
||||||
'groups:wikis:show',
|
|
||||||
'groups:wikis:edit',
|
|
||||||
'groups:wikis:create',
|
|
||||||
|
|
||||||
// Project wiki
|
|
||||||
'projects:wikis:show',
|
|
||||||
'projects:wikis:edit',
|
|
||||||
'projects:wikis:create',
|
|
||||||
|
|
||||||
// Project files
|
|
||||||
'projects:show',
|
|
||||||
'projects:blob:show',
|
|
||||||
];
|
|
||||||
|
|
||||||
export function initMermaid(mermaid) {
|
export function initMermaid(mermaid) {
|
||||||
let theme = 'neutral';
|
let theme = 'neutral';
|
||||||
|
|
||||||
|
@ -163,7 +146,7 @@ function renderMermaids($els) {
|
||||||
* up the entire thread and causing a DoS.
|
* up the entire thread and causing a DoS.
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
!WHITELISTED_PAGES.includes(pageName) &&
|
!unrestrictedPages.includes(pageName) &&
|
||||||
((source && source.length > MAX_CHAR_LIMIT) ||
|
((source && source.length > MAX_CHAR_LIMIT) ||
|
||||||
renderedChars > MAX_CHAR_LIMIT ||
|
renderedChars > MAX_CHAR_LIMIT ||
|
||||||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from '~/lib/utils/url_utility';
|
} from '~/lib/utils/url_utility';
|
||||||
import { darkModeEnabled } from '~/lib/utils/color_utils';
|
import { darkModeEnabled } from '~/lib/utils/color_utils';
|
||||||
import { setAttributes } from '~/lib/utils/dom_utils';
|
import { setAttributes } from '~/lib/utils/dom_utils';
|
||||||
|
import { unrestrictedPages } from './constants';
|
||||||
|
|
||||||
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
||||||
// `js-render-mermaid` class.
|
// `js-render-mermaid` class.
|
||||||
|
@ -36,23 +37,6 @@ const BUFFER_IFRAME_HEIGHT = 10;
|
||||||
const elsProcessingMap = new WeakMap();
|
const elsProcessingMap = new WeakMap();
|
||||||
let renderedMermaidBlocks = 0;
|
let renderedMermaidBlocks = 0;
|
||||||
|
|
||||||
// Pages without any restrictions on mermaid rendering
|
|
||||||
const PAGES_WITHOUT_RESTRICTIONS = [
|
|
||||||
// Group wiki
|
|
||||||
'groups:wikis:show',
|
|
||||||
'groups:wikis:edit',
|
|
||||||
'groups:wikis:create',
|
|
||||||
|
|
||||||
// Project wiki
|
|
||||||
'projects:wikis:show',
|
|
||||||
'projects:wikis:edit',
|
|
||||||
'projects:wikis:create',
|
|
||||||
|
|
||||||
// Project files
|
|
||||||
'projects:show',
|
|
||||||
'projects:blob:show',
|
|
||||||
];
|
|
||||||
|
|
||||||
function shouldLazyLoadMermaidBlock(source) {
|
function shouldLazyLoadMermaidBlock(source) {
|
||||||
/**
|
/**
|
||||||
* If source contains `&`, which means that it might
|
* If source contains `&`, which means that it might
|
||||||
|
@ -149,7 +133,7 @@ function renderMermaids($els) {
|
||||||
* up the entire thread and causing a DoS.
|
* up the entire thread and causing a DoS.
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
!PAGES_WITHOUT_RESTRICTIONS.includes(pageName) &&
|
!unrestrictedPages.includes(pageName) &&
|
||||||
((source && source.length > MAX_CHAR_LIMIT) ||
|
((source && source.length > MAX_CHAR_LIMIT) ||
|
||||||
renderedChars > MAX_CHAR_LIMIT ||
|
renderedChars > MAX_CHAR_LIMIT ||
|
||||||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
|
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
@ -8,14 +7,10 @@ export default () => {
|
||||||
|
|
||||||
Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')])
|
Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Temporary fix to prevent an XSS attack due to "useUnsafeMarkdown"
|
|
||||||
// Once we upgrade Swagger to "4.0.0", we can safely remove this as it will be deprecated
|
|
||||||
// Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/339696
|
|
||||||
updateHistory({ url: removeParams(['useUnsafeMarkdown']), replace: true });
|
|
||||||
SwaggerUIBundle({
|
SwaggerUIBundle({
|
||||||
url: el.dataset.endpoint,
|
url: el.dataset.endpoint,
|
||||||
dom_id: '#js-openapi-viewer',
|
dom_id: '#js-openapi-viewer',
|
||||||
useUnsafeMarkdown: false,
|
deepLinking: true,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
|
@ -81,7 +81,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
||||||
def branch_to
|
def branch_to
|
||||||
@target_project = selected_target_project
|
@target_project = selected_target_project
|
||||||
|
|
||||||
if @target_project && params[:ref].present?
|
if @target_project && params[:ref].present? && Ability.allowed?(current_user, :create_merge_request_in, @target_project)
|
||||||
@ref = params[:ref]
|
@ref = params[:ref]
|
||||||
@commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
|
@commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,6 +65,8 @@ module Ci
|
||||||
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
||||||
MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze
|
MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze
|
||||||
|
|
||||||
|
TAG_LIST_MAX_LENGTH = 50
|
||||||
|
|
||||||
has_many :builds
|
has_many :builds
|
||||||
has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||||
has_many :projects, through: :runner_projects
|
has_many :projects, through: :runner_projects
|
||||||
|
@ -508,6 +510,11 @@ module Ci
|
||||||
errors.add(:tags_list,
|
errors.add(:tags_list,
|
||||||
'can not be empty when runner is not allowed to pick untagged jobs')
|
'can not be empty when runner is not allowed to pick untagged jobs')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if tag_list_changed? && tag_list.count > TAG_LIST_MAX_LENGTH
|
||||||
|
errors.add(:tags_list,
|
||||||
|
"Too many tags specified. Please limit the number of tags to #{TAG_LIST_MAX_LENGTH}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: remove this method once feature flag ci_runners_short_circuit_assignable_for
|
# TODO: remove this method once feature flag ci_runners_short_circuit_assignable_for
|
||||||
|
|
14
app/models/concerns/runners_token_prefixable.rb
Normal file
14
app/models/concerns/runners_token_prefixable.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RunnersTokenPrefixable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# Prefix for runners_token which can be used to invalidate existing tokens.
|
||||||
|
# The value chosen here is GR (for Gitlab Runner) combined with the rotation
|
||||||
|
# date (20220225) decimal to hex encoded.
|
||||||
|
RUNNERS_TOKEN_PREFIX = 'GR1348941'
|
||||||
|
|
||||||
|
def runners_token_prefix
|
||||||
|
RUNNERS_TOKEN_PREFIX
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,14 +19,10 @@ class Group < Namespace
|
||||||
include BulkMemberAccessLoad
|
include BulkMemberAccessLoad
|
||||||
include ChronicDurationAttribute
|
include ChronicDurationAttribute
|
||||||
include RunnerTokenExpirationInterval
|
include RunnerTokenExpirationInterval
|
||||||
|
include RunnersTokenPrefixable
|
||||||
|
|
||||||
extend ::Gitlab::Utils::Override
|
extend ::Gitlab::Utils::Override
|
||||||
|
|
||||||
# Prefix for runners_token which can be used to invalidate existing tokens.
|
|
||||||
# The value chosen here is GR (for Gitlab Runner) combined with the rotation
|
|
||||||
# date (20220225) decimal to hex encoded.
|
|
||||||
RUNNERS_TOKEN_PREFIX = 'GR1348941'
|
|
||||||
|
|
||||||
def self.sti_name
|
def self.sti_name
|
||||||
'Group'
|
'Group'
|
||||||
end
|
end
|
||||||
|
@ -124,7 +120,7 @@ class Group < Namespace
|
||||||
|
|
||||||
add_authentication_token_field :runners_token,
|
add_authentication_token_field :runners_token,
|
||||||
encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
|
encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
|
||||||
prefix: ->(instance) { instance.runners_token_prefix }
|
prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
|
||||||
|
|
||||||
after_create :post_create_hook
|
after_create :post_create_hook
|
||||||
after_destroy :post_destroy_hook
|
after_destroy :post_destroy_hook
|
||||||
|
@ -678,10 +674,6 @@ class Group < Namespace
|
||||||
ensure_runners_token!
|
ensure_runners_token!
|
||||||
end
|
end
|
||||||
|
|
||||||
def runners_token_prefix
|
|
||||||
Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : ''
|
|
||||||
end
|
|
||||||
|
|
||||||
override :format_runners_token
|
override :format_runners_token
|
||||||
def format_runners_token(token)
|
def format_runners_token(token)
|
||||||
"#{runners_token_prefix}#{token}"
|
"#{runners_token_prefix}#{token}"
|
||||||
|
|
|
@ -61,12 +61,9 @@ module Integrations
|
||||||
def execute(data)
|
def execute(data)
|
||||||
return unless supported_events.include?(data[:object_kind])
|
return unless supported_events.include?(data[:object_kind])
|
||||||
|
|
||||||
# check the branch restriction is poplulated and branch is not included
|
|
||||||
branch = Gitlab::Git.ref_name(data[:ref])
|
branch = Gitlab::Git.ref_name(data[:ref])
|
||||||
branch_restriction = restrict_to_branch.to_s
|
|
||||||
if branch_restriction.present? && branch_restriction.index(branch).nil?
|
return unless branch_allowed?(branch)
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
user = data[:user_name]
|
user = data[:user_name]
|
||||||
project_name = project.full_name
|
project_name = project.full_name
|
||||||
|
@ -105,5 +102,13 @@ module Integrations
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def branch_allowed?(branch_name)
|
||||||
|
return true if restrict_to_branch.blank?
|
||||||
|
|
||||||
|
restrict_to_branch.to_s.gsub(/\s+/, '').split(',').include?(branch_name)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,7 @@ class Project < ApplicationRecord
|
||||||
include GitlabRoutingHelper
|
include GitlabRoutingHelper
|
||||||
include BulkMemberAccessLoad
|
include BulkMemberAccessLoad
|
||||||
include RunnerTokenExpirationInterval
|
include RunnerTokenExpirationInterval
|
||||||
|
include RunnersTokenPrefixable
|
||||||
|
|
||||||
extend Gitlab::Cache::RequestCache
|
extend Gitlab::Cache::RequestCache
|
||||||
extend Gitlab::Utils::Override
|
extend Gitlab::Utils::Override
|
||||||
|
@ -74,11 +75,6 @@ class Project < ApplicationRecord
|
||||||
|
|
||||||
GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze
|
GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze
|
||||||
|
|
||||||
# Prefix for runners_token which can be used to invalidate existing tokens.
|
|
||||||
# The value chosen here is GR (for Gitlab Runner) combined with the rotation
|
|
||||||
# date (20220225) decimal to hex encoded.
|
|
||||||
RUNNERS_TOKEN_PREFIX = 'GR1348941'
|
|
||||||
|
|
||||||
cache_markdown_field :description, pipeline: :description
|
cache_markdown_field :description, pipeline: :description
|
||||||
|
|
||||||
default_value_for :packages_enabled, true
|
default_value_for :packages_enabled, true
|
||||||
|
@ -101,7 +97,7 @@ class Project < ApplicationRecord
|
||||||
|
|
||||||
add_authentication_token_field :runners_token,
|
add_authentication_token_field :runners_token,
|
||||||
encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
|
encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
|
||||||
prefix: ->(instance) { instance.runners_token_prefix }
|
prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
|
||||||
|
|
||||||
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
|
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
|
||||||
|
|
||||||
|
@ -1847,10 +1843,6 @@ class Project < ApplicationRecord
|
||||||
ensure_runners_token!
|
ensure_runners_token!
|
||||||
end
|
end
|
||||||
|
|
||||||
def runners_token_prefix
|
|
||||||
Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : ''
|
|
||||||
end
|
|
||||||
|
|
||||||
override :format_runners_token
|
override :format_runners_token
|
||||||
def format_runners_token(token)
|
def format_runners_token(token)
|
||||||
"#{runners_token_prefix}#{token}"
|
"#{runners_token_prefix}#{token}"
|
||||||
|
|
|
@ -9,10 +9,20 @@ module Releases
|
||||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
|
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
|
||||||
# Regex modified to prevent catastrophic backtracking
|
# Regex modified to prevent catastrophic backtracking
|
||||||
FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze
|
FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze
|
||||||
|
FILEPATH_MAX_LENGTH = 128
|
||||||
|
|
||||||
validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
|
validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
|
||||||
validates :name, presence: true, uniqueness: { scope: :release }
|
validates :name, presence: true, uniqueness: { scope: :release }
|
||||||
validates :filepath, uniqueness: { scope: :release }, format: { with: FILEPATH_REGEX }, allow_blank: true, length: { maximum: 128 }
|
validates :filepath, uniqueness: { scope: :release }, allow_blank: true
|
||||||
|
validate :filepath_format_valid?
|
||||||
|
|
||||||
|
# we use a custom validator here to prevent running the regex if the string is too long
|
||||||
|
# see https://gitlab.com/gitlab-org/gitlab/-/issues/273771
|
||||||
|
def filepath_format_valid?
|
||||||
|
return if filepath.nil? # valid use case
|
||||||
|
return errors.add(:filepath, "is too long (maximum is #{FILEPATH_MAX_LENGTH} characters)") if filepath.length > FILEPATH_MAX_LENGTH
|
||||||
|
return errors.add(:filepath, 'is in an invalid format') unless FILEPATH_REGEX.match? filepath
|
||||||
|
end
|
||||||
|
|
||||||
scope :sorted, -> { order(created_at: :desc) }
|
scope :sorted, -> { order(created_at: :desc) }
|
||||||
|
|
||||||
|
|
|
@ -46,11 +46,11 @@ class SshHostKey
|
||||||
.select(&:valid?)
|
.select(&:valid?)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :project, :url, :compare_host_keys
|
attr_reader :project, :url, :ip, :compare_host_keys
|
||||||
|
|
||||||
def initialize(project:, url:, compare_host_keys: nil)
|
def initialize(project:, url:, compare_host_keys: nil)
|
||||||
@project = project
|
@project = project
|
||||||
@url = normalize_url(url)
|
@url, @ip = normalize_url(url)
|
||||||
@compare_host_keys = compare_host_keys
|
@compare_host_keys = compare_host_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -90,9 +90,11 @@ class SshHostKey
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_reactive_cache
|
def calculate_reactive_cache
|
||||||
|
input = [ip, url.hostname].compact.join(' ')
|
||||||
|
|
||||||
known_hosts, errors, status =
|
known_hosts, errors, status =
|
||||||
Open3.popen3({}, *%W[ssh-keyscan -T 5 -p #{url.port} -f-]) do |stdin, stdout, stderr, wait_thr|
|
Open3.popen3({}, *%W[ssh-keyscan -T 5 -p #{url.port} -f-]) do |stdin, stdout, stderr, wait_thr|
|
||||||
stdin.puts(url.host)
|
stdin.puts(input)
|
||||||
stdin.close
|
stdin.close
|
||||||
|
|
||||||
[
|
[
|
||||||
|
@ -127,11 +129,31 @@ class SshHostKey
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_url(url)
|
def normalize_url(url)
|
||||||
full_url = ::Addressable::URI.parse(url)
|
url, real_hostname = Gitlab::UrlBlocker.validate!(
|
||||||
raise ArgumentError, "Invalid URL" unless full_url&.scheme == 'ssh'
|
url,
|
||||||
|
schemes: %w[ssh],
|
||||||
|
allow_localhost: allow_local_requests?,
|
||||||
|
allow_local_network: allow_local_requests?,
|
||||||
|
dns_rebind_protection: Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
|
||||||
|
)
|
||||||
|
|
||||||
Addressable::URI.parse("ssh://#{full_url.host}:#{full_url.inferred_port}")
|
# When DNS rebinding protection is required, the hostname is replaced by the
|
||||||
rescue Addressable::URI::InvalidURIError
|
# resolved IP. However, `url` is used in `id`, so we can't change it. Track
|
||||||
|
# the resolved IP separately instead.
|
||||||
|
if real_hostname
|
||||||
|
ip = url.hostname
|
||||||
|
url.hostname = real_hostname
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ensure ssh://foo and ssh://foo:22 share the same cache
|
||||||
|
url.port = url.inferred_port
|
||||||
|
|
||||||
|
[url, ip]
|
||||||
|
rescue Gitlab::UrlBlocker::BlockedUrlError
|
||||||
raise ArgumentError, "Invalid URL"
|
raise ArgumentError, "Invalid URL"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allow_local_requests?
|
||||||
|
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -895,6 +895,23 @@ class User < ApplicationRecord
|
||||||
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
|
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# See https://gitlab.com/gitlab-org/security/gitlab/-/issues/638
|
||||||
|
DISALLOWED_PASSWORDS = %w[123qweQWE!@#000000000].freeze
|
||||||
|
|
||||||
|
# Overwrites valid_password? from Devise::Models::DatabaseAuthenticatable
|
||||||
|
# In constant-time, check both that the password isn't on a denylist AND
|
||||||
|
# that the password is the user's password
|
||||||
|
def valid_password?(password)
|
||||||
|
password_allowed = true
|
||||||
|
DISALLOWED_PASSWORDS.each do |disallowed_password|
|
||||||
|
password_allowed = false if Devise.secure_compare(password, disallowed_password)
|
||||||
|
end
|
||||||
|
|
||||||
|
original_result = super
|
||||||
|
|
||||||
|
password_allowed && original_result
|
||||||
|
end
|
||||||
|
|
||||||
def remember_me!
|
def remember_me!
|
||||||
super if ::Gitlab::Database.read_write?
|
super if ::Gitlab::Database.read_write?
|
||||||
end
|
end
|
||||||
|
|
|
@ -234,7 +234,6 @@ class ProjectPolicy < BasePolicy
|
||||||
|
|
||||||
rule { can?(:guest_access) }.policy do
|
rule { can?(:guest_access) }.policy do
|
||||||
enable :read_project
|
enable :read_project
|
||||||
enable :create_merge_request_in
|
|
||||||
enable :read_issue_board
|
enable :read_issue_board
|
||||||
enable :read_issue_board_list
|
enable :read_issue_board_list
|
||||||
enable :read_wiki
|
enable :read_wiki
|
||||||
|
@ -487,7 +486,7 @@ class ProjectPolicy < BasePolicy
|
||||||
prevent(*create_read_update_admin_destroy(:issue_board_list))
|
prevent(*create_read_update_admin_destroy(:issue_board_list))
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { merge_requests_disabled | repository_disabled }.policy do
|
rule { merge_requests_disabled | repository_disabled | ~can?(:download_code) }.policy do
|
||||||
prevent :create_merge_request_in
|
prevent :create_merge_request_in
|
||||||
prevent :create_merge_request_from
|
prevent :create_merge_request_from
|
||||||
prevent(*create_read_update_admin_destroy(:merge_request))
|
prevent(*create_read_update_admin_destroy(:merge_request))
|
||||||
|
@ -589,13 +588,14 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :read_cycle_analytics
|
enable :read_cycle_analytics
|
||||||
enable :read_pages_content
|
enable :read_pages_content
|
||||||
enable :read_analytics
|
enable :read_analytics
|
||||||
enable :read_ci_cd_analytics
|
|
||||||
enable :read_insights
|
enable :read_insights
|
||||||
|
|
||||||
# NOTE: may be overridden by IssuePolicy
|
# NOTE: may be overridden by IssuePolicy
|
||||||
enable :read_issue
|
enable :read_issue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rule { can?(:public_access) & public_builds }.enable :read_ci_cd_analytics
|
||||||
|
|
||||||
rule { public_builds }.policy do
|
rule { public_builds }.policy do
|
||||||
enable :read_build
|
enable :read_build
|
||||||
end
|
end
|
||||||
|
@ -653,6 +653,10 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :read_security_configuration
|
enable :read_security_configuration
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rule { can?(:guest_access) & can?(:read_commit_status) }.policy do
|
||||||
|
enable :create_merge_request_in
|
||||||
|
end
|
||||||
|
|
||||||
# Design abilities could also be prevented in the issue policy.
|
# Design abilities could also be prevented in the issue policy.
|
||||||
rule { design_management_disabled }.policy do
|
rule { design_management_disabled }.policy do
|
||||||
prevent :read_design
|
prevent :read_design
|
||||||
|
|
|
@ -12,7 +12,7 @@ module Ci
|
||||||
|
|
||||||
def destroy_records
|
def destroy_records
|
||||||
@job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation|
|
@job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation|
|
||||||
service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current)
|
service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current, fix_expire_at: false)
|
||||||
result = service.execute(update_stats: false)
|
result = service.execute(update_stats: false)
|
||||||
updates = result[:statistics_updates]
|
updates = result[:statistics_updates]
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,18 @@ module Ci
|
||||||
# +pick_up_at+:: When to pick up for deletion of files
|
# +pick_up_at+:: When to pick up for deletion of files
|
||||||
# Returns:
|
# Returns:
|
||||||
# +Hash+:: A hash with status and destroyed_artifacts_count keys
|
# +Hash+:: A hash with status and destroyed_artifacts_count keys
|
||||||
def initialize(job_artifacts, pick_up_at: nil)
|
def initialize(job_artifacts, pick_up_at: nil, fix_expire_at: fix_expire_at?)
|
||||||
@job_artifacts = job_artifacts.with_destroy_preloads.to_a
|
@job_artifacts = job_artifacts.with_destroy_preloads.to_a
|
||||||
@pick_up_at = pick_up_at
|
@pick_up_at = pick_up_at
|
||||||
|
@fix_expire_at = fix_expire_at
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def execute(update_stats: true)
|
def execute(update_stats: true)
|
||||||
|
# Detect and fix artifacts that had `expire_at` wrongly backfilled by migration
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47723
|
||||||
|
detect_and_fix_wrongly_expired_artifacts
|
||||||
|
|
||||||
return success(destroyed_artifacts_count: 0, statistics_updates: {}) if @job_artifacts.empty?
|
return success(destroyed_artifacts_count: 0, statistics_updates: {}) if @job_artifacts.empty?
|
||||||
|
|
||||||
destroy_related_records(@job_artifacts)
|
destroy_related_records(@job_artifacts)
|
||||||
|
@ -89,6 +94,55 @@ module Ci
|
||||||
@job_artifacts.sum { |artifact| artifact.try(:size) || 0 }
|
@job_artifacts.sum { |artifact| artifact.try(:size) || 0 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This detects and fixes job artifacts that have `expire_at` wrongly backfilled by the migration
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47723.
|
||||||
|
# These job artifacts will not be deleted and will have their `expire_at` removed.
|
||||||
|
#
|
||||||
|
# The migration would have backfilled `expire_at`
|
||||||
|
# to midnight on the 22nd of the month of the local timezone,
|
||||||
|
# storing it as UTC time in the database.
|
||||||
|
#
|
||||||
|
# If the timezone setting has changed since the migration,
|
||||||
|
# the `expire_at` stored in the database could have changed to a different local time other than midnight.
|
||||||
|
# For example:
|
||||||
|
# - changing timezone from UTC+02:00 to UTC+02:30 would change the `expire_at` in local time 00:00:00 to 00:30:00.
|
||||||
|
# - changing timezone from UTC+00:00 to UTC-01:00 would change the `expire_at` in local time 00:00:00 to 23:00:00 on the previous day (21st).
|
||||||
|
#
|
||||||
|
# Therefore job artifacts that have `expire_at` exactly on the 00, 30 or 45 minute mark
|
||||||
|
# on the dates 21, 22, 23 of the month will not be deleted.
|
||||||
|
# https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
|
||||||
|
def detect_and_fix_wrongly_expired_artifacts
|
||||||
|
return unless @fix_expire_at
|
||||||
|
|
||||||
|
wrongly_expired_artifacts, @job_artifacts = @job_artifacts.partition { |artifact| wrongly_expired?(artifact) }
|
||||||
|
|
||||||
|
remove_expire_at(wrongly_expired_artifacts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_expire_at?
|
||||||
|
Feature.enabled?(:ci_detect_wrongly_expired_artifacts, default_enabled: :yaml)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wrongly_expired?(artifact)
|
||||||
|
return false unless artifact.expire_at.present?
|
||||||
|
|
||||||
|
match_date?(artifact.expire_at) && match_time?(artifact.expire_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_date?(expire_at)
|
||||||
|
[21, 22, 23].include?(expire_at.day)
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_time?(expire_at)
|
||||||
|
%w[00:00.000 30:00.000 45:00.000].include?(expire_at.strftime('%M:%S.%L'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_expire_at(artifacts)
|
||||||
|
Ci::JobArtifact.id_in(artifacts).update_all(expire_at: nil)
|
||||||
|
|
||||||
|
Gitlab::AppLogger.info(message: "Fixed expire_at from artifacts.", fixed_artifacts_expire_at_count: artifacts.count)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348786
|
||||||
milestone: '14.6'
|
milestone: '14.6'
|
||||||
type: development
|
type: development
|
||||||
group: group::pipeline execution
|
group: group::pipeline execution
|
||||||
default_enabled: false
|
default_enabled: true
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: ci_detect_wrongly_expired_artifacts
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82084
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354955
|
||||||
|
milestone: '14.9'
|
||||||
|
type: development
|
||||||
|
group: group::pipeline insights
|
||||||
|
default_enabled: true
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: groups_runners_token_prefix
|
|
||||||
introduced_by_url:
|
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805
|
|
||||||
milestone: '14.9'
|
|
||||||
type: development
|
|
||||||
group: group::database
|
|
||||||
default_enabled: true
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: projects_runners_token_prefix
|
|
||||||
introduced_by_url:
|
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805
|
|
||||||
milestone: '14.9'
|
|
||||||
type: development
|
|
||||||
group: group::database
|
|
||||||
default_enabled: true
|
|
21
config/initializers/rdoc_segfault_patch.rb
Normal file
21
config/initializers/rdoc_segfault_patch.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Monkey patch of RDoc to prevent Ruby segfault due to
|
||||||
|
# stack buffer overflow Ruby bug -
|
||||||
|
# https://bugs.ruby-lang.org/issues/16376
|
||||||
|
#
|
||||||
|
# Safe to remove once GitLab upgrades to Ruby 3.0
|
||||||
|
# or once the fix is backported to 2.7.x and
|
||||||
|
# GitLab upgrades.
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/351179
|
||||||
|
class RDoc::Markup::ToHtml
|
||||||
|
def parseable?(_)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class RDoc::Markup::Verbatim
|
||||||
|
def ruby?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,7 +11,7 @@ module Db
|
||||||
name: FFaker::Name.name,
|
name: FFaker::Name.name,
|
||||||
email: FFaker::Internet.email,
|
email: FFaker::Internet.email,
|
||||||
confirmed_at: DateTime.now,
|
confirmed_at: DateTime.now,
|
||||||
password: Gitlab::Password.test_default
|
password: '12345678'
|
||||||
)
|
)
|
||||||
|
|
||||||
::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')
|
::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AddUniqueIndexToVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
|
class AddUniqueIndexToVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
|
||||||
disable_ddl_transaction!
|
# This migration has been moved to db/post_migrate/20220201193033_add_unique_index_to_vulnerability_finding_links_with_truncate.rb
|
||||||
|
# Previously, this was causing an bug where there was a conflict between the table cleanup and the index creation.
|
||||||
NAME_URL_INDEX_NAME = 'finding_link_name_url_idx'
|
|
||||||
URL_INDEX_NAME = 'finding_link_url_idx'
|
|
||||||
|
|
||||||
def up
|
def up
|
||||||
add_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :name, :url], unique: true, name: NAME_URL_INDEX_NAME
|
# no op
|
||||||
add_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :url], unique: true, where: 'name is null', name: URL_INDEX_NAME
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
remove_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :name, :url], name: NAME_URL_INDEX_NAME
|
# no op
|
||||||
remove_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :url], name: URL_INDEX_NAME
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RemoveVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
|
class RemoveVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
|
||||||
BATCH_SIZE = 50_000
|
# This migration has been moved to a TRUNCATE in db/post_migrate/20220201193033_add_unique_index_to_vulnerability_finding_links_with_truncate.rb
|
||||||
MIGRATION = 'RemoveVulnerabilityFindingLinks'
|
# Previously, this was causing an bug where there was a conflict between the table cleanup and the index creation.
|
||||||
|
|
||||||
disable_ddl_transaction!
|
|
||||||
|
|
||||||
def up
|
def up
|
||||||
queue_background_migration_jobs_by_range_at_intervals(
|
# no op
|
||||||
define_batchable_model('vulnerability_finding_links'),
|
|
||||||
MIGRATION,
|
|
||||||
2.minutes,
|
|
||||||
batch_size: BATCH_SIZE
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
# no ops
|
# no op
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RemoveVulnerabilityFindingLinksAgain < Gitlab::Database::Migration[1.0]
|
class RemoveVulnerabilityFindingLinksAgain < Gitlab::Database::Migration[1.0]
|
||||||
BATCH_SIZE = 50_000
|
# This migration has been moved to a TRUNCATE in db/post_migrate/20220201193033_add_unique_index_to_vulnerability_finding_links_with_truncate.rb
|
||||||
MIGRATION = 'RemoveVulnerabilityFindingLinks'
|
# Previously, this was causing an bug where there was a conflict between the table cleanup and the index creation.
|
||||||
|
|
||||||
disable_ddl_transaction!
|
|
||||||
|
|
||||||
def up
|
def up
|
||||||
queue_background_migration_jobs_by_range_at_intervals(
|
# no op
|
||||||
define_batchable_model('vulnerability_finding_links'),
|
|
||||||
MIGRATION,
|
|
||||||
2.minutes,
|
|
||||||
batch_size: BATCH_SIZE
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
# no ops
|
# no op
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddUniqueIndexToVulnerabilityFindingLinksWithTruncate < Gitlab::Database::Migration[1.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
NAME_URL_INDEX_NAME = 'finding_link_name_url_idx'
|
||||||
|
URL_INDEX_NAME = 'finding_link_url_idx'
|
||||||
|
|
||||||
|
def up
|
||||||
|
execute('TRUNCATE TABLE vulnerability_finding_links')
|
||||||
|
|
||||||
|
add_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :name, :url], unique: true, name: NAME_URL_INDEX_NAME
|
||||||
|
add_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :url], unique: true, where: 'name is null', name: URL_INDEX_NAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :name, :url], name: NAME_URL_INDEX_NAME
|
||||||
|
remove_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :url], name: URL_INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20220201193033
Normal file
1
db/schema_migrations/20220201193033
Normal file
|
@ -0,0 +1 @@
|
||||||
|
92bbe74c6c3627dd26f709acd2a20f442212eab933f719be815701a3bc429539
|
|
@ -6,8 +6,10 @@ require "asciidoctor/extensions/asciidoctor_kroki/extension"
|
||||||
module Banzai
|
module Banzai
|
||||||
module Filter
|
module Filter
|
||||||
# HTML that replaces all diagrams supported by Kroki with the corresponding img tags.
|
# HTML that replaces all diagrams supported by Kroki with the corresponding img tags.
|
||||||
#
|
# If the source content is large then the hidden attribute is added to the img tag.
|
||||||
class KrokiFilter < HTML::Pipeline::Filter
|
class KrokiFilter < HTML::Pipeline::Filter
|
||||||
|
MAX_CHARACTER_LIMIT = 2000
|
||||||
|
|
||||||
def call
|
def call
|
||||||
return doc unless settings.kroki_enabled
|
return doc unless settings.kroki_enabled
|
||||||
|
|
||||||
|
@ -21,7 +23,12 @@ module Banzai
|
||||||
diagram_format = "svg"
|
diagram_format = "svg"
|
||||||
doc.xpath(xpath).each do |node|
|
doc.xpath(xpath).each do |node|
|
||||||
diagram_type = node.parent['lang']
|
diagram_type = node.parent['lang']
|
||||||
img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{create_image_src(diagram_type, diagram_format, node.content)}"/>))
|
diagram_src = node.content
|
||||||
|
image_src = create_image_src(diagram_type, diagram_format, diagram_src)
|
||||||
|
lazy_load = diagram_src.length > MAX_CHARACTER_LIMIT
|
||||||
|
other_attrs = lazy_load ? "hidden" : ""
|
||||||
|
|
||||||
|
img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img class="js-render-kroki" src="#{image_src}" #{other_attrs} />))
|
||||||
node.parent.replace(img_tag)
|
node.parent.replace(img_tag)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ module Banzai
|
||||||
retry
|
retry
|
||||||
end
|
end
|
||||||
|
|
||||||
sourcepos_attr = sourcepos ? "data-sourcepos=\"#{sourcepos}\"" : ''
|
sourcepos_attr = sourcepos ? "data-sourcepos=\"#{escape_once(sourcepos)}\"" : ''
|
||||||
|
|
||||||
highlighted = %(<div class="gl-relative markdown-code-block js-markdown-code"><pre #{sourcepos_attr} class="#{css_classes}"
|
highlighted = %(<div class="gl-relative markdown-code-block js-markdown-code"><pre #{sourcepos_attr} class="#{css_classes}"
|
||||||
lang="#{language}"
|
lang="#{language}"
|
||||||
|
|
|
@ -65,16 +65,15 @@ module Banzai
|
||||||
#
|
#
|
||||||
def redacted_node_content(node)
|
def redacted_node_content(node)
|
||||||
original_content = node.attr('data-original')
|
original_content = node.attr('data-original')
|
||||||
link_reference = node.attr('data-link-reference')
|
original_content = CGI.escape_html(original_content) if original_content
|
||||||
|
|
||||||
# Build the raw <a> tag just with a link as href and content if
|
# Build the raw <a> tag just with a link as href and content if
|
||||||
# it's originally a link pattern. We shouldn't return a plain text href.
|
# it's originally a link pattern. We shouldn't return a plain text href.
|
||||||
original_link =
|
original_link =
|
||||||
if link_reference == 'true'
|
if node.attr('data-link-reference') == 'true'
|
||||||
href = node.attr('href')
|
href = node.attr('href')
|
||||||
content = original_content
|
|
||||||
|
|
||||||
%(<a href="#{href}">#{content}</a>)
|
%(<a href="#{href}">#{original_content}</a>)
|
||||||
end
|
end
|
||||||
|
|
||||||
# The reference should be replaced by the original link's content,
|
# The reference should be replaced by the original link's content,
|
||||||
|
|
|
@ -230,8 +230,8 @@ module Gitlab
|
||||||
name: name.strip.presence || valid_username,
|
name: name.strip.presence || valid_username,
|
||||||
username: valid_username,
|
username: valid_username,
|
||||||
email: email,
|
email: email,
|
||||||
password: Gitlab::Password.test_default(21),
|
password: auth_hash.password,
|
||||||
password_confirmation: Gitlab::Password.test_default(21),
|
password_confirmation: auth_hash.password,
|
||||||
password_automatically_set: true
|
password_automatically_set: true
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
10
lib/gitlab/ci/config/external/context.rb
vendored
10
lib/gitlab/ci/config/external/context.rb
vendored
|
@ -70,6 +70,16 @@ module Gitlab
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mask_variables_from(location)
|
||||||
|
variables.reduce(location.dup) do |loc, variable|
|
||||||
|
if variable[:masked]
|
||||||
|
Gitlab::Ci::MaskSecret.mask!(loc, variable[:value])
|
||||||
|
else
|
||||||
|
loc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_writer :expandset, :execution_deadline, :logger
|
attr_writer :expandset, :execution_deadline, :logger
|
||||||
|
|
|
@ -37,7 +37,7 @@ module Gitlab
|
||||||
def validate_content!
|
def validate_content!
|
||||||
return unless ensure_preconditions_satisfied!
|
return unless ensure_preconditions_satisfied!
|
||||||
|
|
||||||
errors.push("File `#{location}` is empty!") unless content.present?
|
errors.push("File `#{masked_location}` is empty!") unless content.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_preconditions_satisfied!
|
def ensure_preconditions_satisfied!
|
||||||
|
|
14
lib/gitlab/ci/config/external/file/base.rb
vendored
14
lib/gitlab/ci/config/external/file/base.rb
vendored
|
@ -79,21 +79,21 @@ module Gitlab
|
||||||
|
|
||||||
def validate_location!
|
def validate_location!
|
||||||
if invalid_location_type?
|
if invalid_location_type?
|
||||||
errors.push("Included file `#{location}` needs to be a string")
|
errors.push("Included file `#{masked_location}` needs to be a string")
|
||||||
elsif invalid_extension?
|
elsif invalid_extension?
|
||||||
errors.push("Included file `#{location}` does not have YAML extension!")
|
errors.push("Included file `#{masked_location}` does not have YAML extension!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_content!
|
def validate_content!
|
||||||
if content.blank?
|
if content.blank?
|
||||||
errors.push("Included file `#{location}` is empty or does not exist!")
|
errors.push("Included file `#{masked_location}` is empty or does not exist!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_hash!
|
def validate_hash!
|
||||||
if to_hash.blank?
|
if to_hash.blank?
|
||||||
errors.push("Included file `#{location}` does not have valid YAML syntax!")
|
errors.push("Included file `#{masked_location}` does not have valid YAML syntax!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -104,6 +104,12 @@ module Gitlab
|
||||||
def expand_context_attrs
|
def expand_context_attrs
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def masked_location
|
||||||
|
strong_memoize(:masked_location) do
|
||||||
|
context.mask_variables_from(location)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
6
lib/gitlab/ci/config/external/file/local.rb
vendored
6
lib/gitlab/ci/config/external/file/local.rb
vendored
|
@ -23,11 +23,11 @@ module Gitlab
|
||||||
|
|
||||||
def validate_content!
|
def validate_content!
|
||||||
if context.project&.repository.nil?
|
if context.project&.repository.nil?
|
||||||
errors.push("Local file `#{location}` does not have project!")
|
errors.push("Local file `#{masked_location}` does not have project!")
|
||||||
elsif content.nil?
|
elsif content.nil?
|
||||||
errors.push("Local file `#{location}` does not exist!")
|
errors.push("Local file `#{masked_location}` does not exist!")
|
||||||
elsif content.blank?
|
elsif content.blank?
|
||||||
errors.push("Local file `#{location}` is empty!")
|
errors.push("Local file `#{masked_location}` is empty!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,9 @@ module Gitlab
|
||||||
elsif sha.nil?
|
elsif sha.nil?
|
||||||
errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!")
|
errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!")
|
||||||
elsif content.nil?
|
elsif content.nil?
|
||||||
errors.push("Project `#{project_name}` file `#{location}` does not exist!")
|
errors.push("Project `#{project_name}` file `#{masked_location}` does not exist!")
|
||||||
elsif content.blank?
|
elsif content.blank?
|
||||||
errors.push("Project `#{project_name}` file `#{location}` is empty!")
|
errors.push("Project `#{project_name}` file `#{masked_location}` is empty!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
10
lib/gitlab/ci/config/external/file/remote.rb
vendored
10
lib/gitlab/ci/config/external/file/remote.rb
vendored
|
@ -24,7 +24,7 @@ module Gitlab
|
||||||
super
|
super
|
||||||
|
|
||||||
unless ::Gitlab::UrlSanitizer.valid?(location)
|
unless ::Gitlab::UrlSanitizer.valid?(location)
|
||||||
errors.push("Remote file `#{location}` does not have a valid address!")
|
errors.push("Remote file `#{masked_location}` does not have a valid address!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -32,17 +32,17 @@ module Gitlab
|
||||||
begin
|
begin
|
||||||
response = Gitlab::HTTP.get(location)
|
response = Gitlab::HTTP.get(location)
|
||||||
rescue SocketError
|
rescue SocketError
|
||||||
errors.push("Remote file `#{location}` could not be fetched because of a socket error!")
|
errors.push("Remote file `#{masked_location}` could not be fetched because of a socket error!")
|
||||||
rescue Timeout::Error
|
rescue Timeout::Error
|
||||||
errors.push("Remote file `#{location}` could not be fetched because of a timeout error!")
|
errors.push("Remote file `#{masked_location}` could not be fetched because of a timeout error!")
|
||||||
rescue Gitlab::HTTP::Error
|
rescue Gitlab::HTTP::Error
|
||||||
errors.push("Remote file `#{location}` could not be fetched because of HTTP error!")
|
errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP error!")
|
||||||
rescue Gitlab::HTTP::BlockedUrlError => e
|
rescue Gitlab::HTTP::BlockedUrlError => e
|
||||||
errors.push("Remote file could not be fetched because #{e}!")
|
errors.push("Remote file could not be fetched because #{e}!")
|
||||||
end
|
end
|
||||||
|
|
||||||
if response&.code.to_i >= 400
|
if response&.code.to_i >= 400
|
||||||
errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!")
|
errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP code `#{response.code}` error!")
|
||||||
end
|
end
|
||||||
|
|
||||||
response.body if errors.none?
|
response.body if errors.none?
|
||||||
|
|
|
@ -26,7 +26,7 @@ module Gitlab
|
||||||
super
|
super
|
||||||
|
|
||||||
unless template_name_valid?
|
unless template_name_valid?
|
||||||
errors.push("Template file `#{location}` is not a valid location!")
|
errors.push("Template file `#{masked_location}` is not a valid location!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
6
lib/gitlab/ci/config/external/mapper.rb
vendored
6
lib/gitlab/ci/config/external/mapper.rb
vendored
|
@ -146,7 +146,7 @@ module Gitlab
|
||||||
file_class.new(location, context)
|
file_class.new(location, context)
|
||||||
end.select(&:matching?)
|
end.select(&:matching?)
|
||||||
|
|
||||||
raise AmbigiousSpecificationError, "Include `#{location.to_json}` needs to match exactly one accessor!" unless matching.one?
|
raise AmbigiousSpecificationError, "Include `#{masked_location(location.to_json)}` needs to match exactly one accessor!" unless matching.one?
|
||||||
|
|
||||||
matching.first
|
matching.first
|
||||||
end
|
end
|
||||||
|
@ -181,6 +181,10 @@ module Gitlab
|
||||||
def expand(data)
|
def expand(data)
|
||||||
ExpandVariables.expand(data, -> { context.variables_hash })
|
ExpandVariables.expand(data, -> { context.variables_hash })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def masked_location(location)
|
||||||
|
context.mask_variables_from(location)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -99,6 +99,9 @@ module Gitlab
|
||||||
# ^--+--+- components of hashed storage project path
|
# ^--+--+- components of hashed storage project path
|
||||||
cmd += %w[-mindepth 6 -maxdepth 6]
|
cmd += %w[-mindepth 6 -maxdepth 6]
|
||||||
|
|
||||||
|
# Intentionally exclude pipeline artifacts which match the same path
|
||||||
|
cmd += %w[-not -path */pipelines/*]
|
||||||
|
|
||||||
# Artifact directories are named on their ID
|
# Artifact directories are named on their ID
|
||||||
cmd += %w[-type d]
|
cmd += %w[-type d]
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,8 @@ module Gitlab
|
||||||
PROCESSORS = [
|
PROCESSORS = [
|
||||||
::Gitlab::ErrorTracking::Processor::SidekiqProcessor,
|
::Gitlab::ErrorTracking::Processor::SidekiqProcessor,
|
||||||
::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor,
|
::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor,
|
||||||
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor
|
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor,
|
||||||
|
::Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module ErrorTracking
|
||||||
|
module Processor
|
||||||
|
module Concerns
|
||||||
|
module ProcessesExceptions
|
||||||
|
private
|
||||||
|
|
||||||
|
def extract_exceptions_from(event)
|
||||||
|
exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values
|
||||||
|
|
||||||
|
Array.wrap(exceptions)
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_exception?(exception)
|
||||||
|
case exception
|
||||||
|
when Raven::SingleExceptionInterface
|
||||||
|
exception&.value.present?
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,8 @@ module Gitlab
|
||||||
module ErrorTracking
|
module ErrorTracking
|
||||||
module Processor
|
module Processor
|
||||||
module GrpcErrorProcessor
|
module GrpcErrorProcessor
|
||||||
|
extend Gitlab::ErrorTracking::Processor::Concerns::ProcessesExceptions
|
||||||
|
|
||||||
DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)')
|
DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)')
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
@ -18,10 +20,7 @@ module Gitlab
|
||||||
# only the first one since that's what is used for grouping.
|
# only the first one since that's what is used for grouping.
|
||||||
def process_first_exception_value(event)
|
def process_first_exception_value(event)
|
||||||
# Better in new version, will be event.exception.values
|
# Better in new version, will be event.exception.values
|
||||||
exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values
|
exceptions = extract_exceptions_from(event)
|
||||||
|
|
||||||
return unless exceptions.is_a?(Array)
|
|
||||||
|
|
||||||
exception = exceptions.first
|
exception = exceptions.first
|
||||||
|
|
||||||
return unless valid_exception?(exception)
|
return unless valid_exception?(exception)
|
||||||
|
@ -68,15 +67,6 @@ module Gitlab
|
||||||
|
|
||||||
[match[1], match[2]]
|
[match[1], match[2]]
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_exception?(exception)
|
|
||||||
case exception
|
|
||||||
when Raven::SingleExceptionInterface
|
|
||||||
exception&.value
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module ErrorTracking
|
||||||
|
module Processor
|
||||||
|
module SanitizeErrorMessageProcessor
|
||||||
|
extend Gitlab::ErrorTracking::Processor::Concerns::ProcessesExceptions
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def call(event)
|
||||||
|
exceptions = extract_exceptions_from(event)
|
||||||
|
|
||||||
|
exceptions.each do |exception|
|
||||||
|
next unless valid_exception?(exception)
|
||||||
|
|
||||||
|
exception.value = Gitlab::Sanitizers::ExceptionMessage.clean(exception.type, exception.value)
|
||||||
|
end
|
||||||
|
|
||||||
|
event
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@ module Gitlab
|
||||||
# Use periods to flatten the fields.
|
# Use periods to flatten the fields.
|
||||||
payload.merge!(
|
payload.merge!(
|
||||||
'exception.class' => exception.class.name,
|
'exception.class' => exception.class.name,
|
||||||
'exception.message' => exception.message
|
'exception.message' => sanitize_message(exception)
|
||||||
)
|
)
|
||||||
|
|
||||||
if exception.backtrace
|
if exception.backtrace
|
||||||
|
@ -38,6 +38,10 @@ module Gitlab
|
||||||
rescue PgQuery::ParseError
|
rescue PgQuery::ParseError
|
||||||
sql
|
sql
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sanitize_message(exception)
|
||||||
|
Gitlab::Sanitizers::ExceptionMessage.clean(exception.class.name, exception.message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,9 +19,8 @@ module Gitlab
|
||||||
@exported_members.inject(missing_keys_tracking_hash) do |hash, member|
|
@exported_members.inject(missing_keys_tracking_hash) do |hash, member|
|
||||||
if member['user']
|
if member['user']
|
||||||
old_user_id = member['user']['id']
|
old_user_id = member['user']['id']
|
||||||
old_user_email = member.dig('user', 'public_email') || member.dig('user', 'email')
|
existing_user_id = existing_users_email_map[get_email(member)]
|
||||||
existing_user = User.find_by(find_user_query(old_user_email)) if old_user_email
|
hash[old_user_id] = existing_user_id if existing_user_id && add_team_member(member, existing_user_id)
|
||||||
hash[old_user_id] = existing_user.id if existing_user && add_team_member(member, existing_user)
|
|
||||||
else
|
else
|
||||||
add_team_member(member)
|
add_team_member(member)
|
||||||
end
|
end
|
||||||
|
@ -72,11 +71,45 @@ module Gitlab
|
||||||
member&.user == @user && member.access_level >= highest_access_level
|
member&.user == @user && member.access_level >= highest_access_level
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_team_member(member, existing_user = nil)
|
# Returns {email => user_id} hash where user_id is an ID at current instance
|
||||||
return true if existing_user && @importable.members.exists?(user_id: existing_user.id)
|
def existing_users_email_map
|
||||||
|
@existing_users_email_map ||= begin
|
||||||
|
emails = @exported_members.map { |member| get_email(member) }
|
||||||
|
|
||||||
|
User.by_user_email(emails).pluck(:email, :id).to_h
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns {user_id => email} hash where user_id is an ID at source "old" instance
|
||||||
|
def exported_members_email_map
|
||||||
|
@exported_members_email_map ||= begin
|
||||||
|
result = {}
|
||||||
|
@exported_members.each do |member|
|
||||||
|
email = get_email(member)
|
||||||
|
|
||||||
|
next unless email
|
||||||
|
|
||||||
|
result[member.dig('user', 'id')] = email
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_email(member_data)
|
||||||
|
return unless member_data['user']
|
||||||
|
|
||||||
|
member_data.dig('user', 'public_email') || member_data.dig('user', 'email')
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_team_member(member, existing_user_id = nil)
|
||||||
|
return true if existing_user_id && @importable.members.exists?(user_id: existing_user_id)
|
||||||
|
|
||||||
member['user'] = existing_user
|
|
||||||
member_hash = member_hash(member)
|
member_hash = member_hash(member)
|
||||||
|
if existing_user_id
|
||||||
|
member_hash.delete('user')
|
||||||
|
member_hash['user_id'] = existing_user_id
|
||||||
|
end
|
||||||
|
|
||||||
member = relation_class.create(member_hash)
|
member = relation_class.create(member_hash)
|
||||||
|
|
||||||
|
@ -92,11 +125,19 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def member_hash(member)
|
def member_hash(member)
|
||||||
parsed_hash(member).merge(
|
result = parsed_hash(member).merge(
|
||||||
'source_id' => @importable.id,
|
'source_id' => @importable.id,
|
||||||
'importing' => true,
|
'importing' => true,
|
||||||
'access_level' => [member['access_level'], highest_access_level].min
|
'access_level' => [member['access_level'], highest_access_level].min
|
||||||
).except('user_id')
|
).except('user_id')
|
||||||
|
|
||||||
|
if result['created_by_id']
|
||||||
|
created_by_email = exported_members_email_map[result['created_by_id']]
|
||||||
|
|
||||||
|
result['created_by_id'] = existing_users_email_map[created_by_email]
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
def parsed_hash(member)
|
def parsed_hash(member)
|
||||||
|
@ -104,14 +145,6 @@ module Gitlab
|
||||||
relation_class: relation_class)
|
relation_class: relation_class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_user_query(email)
|
|
||||||
user_arel[:email].eq(email)
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_arel
|
|
||||||
@user_arel ||= User.arel_table
|
|
||||||
end
|
|
||||||
|
|
||||||
def relation_class
|
def relation_class
|
||||||
case @importable
|
case @importable
|
||||||
when ::Project
|
when ::Project
|
||||||
|
@ -143,7 +176,7 @@ module Gitlab
|
||||||
|
|
||||||
def base_log_params(member_hash)
|
def base_log_params(member_hash)
|
||||||
{
|
{
|
||||||
user_id: member_hash['user']&.id,
|
user_id: member_hash['user_id'],
|
||||||
access_level: member_hash['access_level'],
|
access_level: member_hash['access_level'],
|
||||||
importable_type: @importable.class.to_s,
|
importable_type: @importable.class.to_s,
|
||||||
importable_id: @importable.id,
|
importable_id: @importable.id,
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# This module is used to return fake strong password for tests
|
|
||||||
|
|
||||||
module Gitlab
|
|
||||||
module Password
|
|
||||||
DEFAULT_LENGTH = 12
|
|
||||||
TEST_DEFAULT = "123qweQWE!@#" + "0" * (User.password_length.max - DEFAULT_LENGTH)
|
|
||||||
def self.test_default(length = 12)
|
|
||||||
password_length = [[User.password_length.min, length].max, User.password_length.max].min
|
|
||||||
TEST_DEFAULT[...password_length]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
19
lib/gitlab/sanitizers/exception_message.rb
Normal file
19
lib/gitlab/sanitizers/exception_message.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Sanitizers
|
||||||
|
module ExceptionMessage
|
||||||
|
FILTERED_STRING = '[FILTERED]'
|
||||||
|
EXCEPTION_NAMES = %w(URI::InvalidURIError Addressable::URI::InvalidURIError).freeze
|
||||||
|
MESSAGE_REGEX = %r{(\A[^:]+:\s).*\Z}.freeze
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def clean(exception_name, message)
|
||||||
|
return message unless exception_name.in?(EXCEPTION_NAMES)
|
||||||
|
|
||||||
|
message.sub(MESSAGE_REGEX, '\1' + FILTERED_STRING)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -125,7 +125,7 @@ class GroupSeeder
|
||||||
name: FFaker::Name.name,
|
name: FFaker::Name.name,
|
||||||
email: FFaker::Internet.email,
|
email: FFaker::Internet.email,
|
||||||
confirmed_at: DateTime.now,
|
confirmed_at: DateTime.now,
|
||||||
password: Gitlab::Password.test_default
|
password: Devise.friendly_token
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -12625,6 +12625,9 @@ msgstr ""
|
||||||
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
|
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Display"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Display alerts from all configured monitoring tools."
|
msgid "Display alerts from all configured monitoring tools."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@
|
||||||
"sql.js": "^0.4.0",
|
"sql.js": "^0.4.0",
|
||||||
"string-hash": "1.1.3",
|
"string-hash": "1.1.3",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"swagger-ui-dist": "^3.52.3",
|
"swagger-ui-dist": "4.8.0",
|
||||||
"three": "^0.84.0",
|
"three": "^0.84.0",
|
||||||
"three-orbit-controls": "^82.1.0",
|
"three-orbit-controls": "^82.1.0",
|
||||||
"three-stl-loader": "^1.0.4",
|
"three-stl-loader": "^1.0.4",
|
||||||
|
|
|
@ -6,6 +6,10 @@ module Gitlab
|
||||||
class Subscription < Chemlab::Page
|
class Subscription < Chemlab::Page
|
||||||
path '/admin/subscription'
|
path '/admin/subscription'
|
||||||
|
|
||||||
|
div :subscription_details
|
||||||
|
text_field :activation_code
|
||||||
|
button :activate
|
||||||
|
label :terms_of_services, text: /I agree that/
|
||||||
p :plan
|
p :plan
|
||||||
p :started
|
p :started
|
||||||
p :name
|
p :name
|
||||||
|
@ -16,6 +20,33 @@ module Gitlab
|
||||||
h2 :users_in_subscription
|
h2 :users_in_subscription
|
||||||
h2 :users_over_subscription
|
h2 :users_over_subscription
|
||||||
table :subscription_history
|
table :subscription_history
|
||||||
|
|
||||||
|
def accept_terms
|
||||||
|
terms_of_services_element.click # workaround for hidden checkbox
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if a subscription record exists in subscription history table
|
||||||
|
#
|
||||||
|
# @param plan [Hash] Name of the plan
|
||||||
|
# @option plan [Hash] Support::Helpers::FREE
|
||||||
|
# @option plan [Hash] Support::Helpers::PREMIUM
|
||||||
|
# @option plan [Hash] Support::Helpers::PREMIUM_SELF_MANAGED
|
||||||
|
# @option plan [Hash] Support::Helpers::ULTIMATE
|
||||||
|
# @option plan [Hash] Support::Helpers::ULTIMATE_SELF_MANAGED
|
||||||
|
# @option plan [Hash] Support::Helpers::CI_MINUTES
|
||||||
|
# @option plan [Hash] Support::Helpers::STORAGE
|
||||||
|
# @param users_in_license [Integer] Number of users in license
|
||||||
|
# @param license_type [Hash] Type of the license
|
||||||
|
# @option license_type [String] 'license file'
|
||||||
|
# @option license_type [String] 'cloud license'
|
||||||
|
# @return [Boolean] True if record exsists, false if not
|
||||||
|
def has_subscription_record?(plan, users_in_license, license_type)
|
||||||
|
# find any records that have a matching plan and seats and type
|
||||||
|
subscription_history_element.hashes.any? do |record|
|
||||||
|
record['Plan'] == plan[:name].capitalize && record['Seats'] == users_in_license.to_s && \
|
||||||
|
record['Type'].strip.downcase == license_type
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,112 @@ module Gitlab
|
||||||
module Page
|
module Page
|
||||||
module Admin
|
module Admin
|
||||||
module Subscription
|
module Subscription
|
||||||
|
# @note Defined as +h6 :subscription_details+
|
||||||
|
# @return [String] The text content or value of +subscription_details+
|
||||||
|
def subscription_details
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription.subscription_details_element).to exist
|
||||||
|
# end
|
||||||
|
# @return [Watir::H6] The raw +H6+ element
|
||||||
|
def subscription_details_element
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription).to be_subscription_details
|
||||||
|
# end
|
||||||
|
# @return [Boolean] true if the +subscription_details+ element is present on the page
|
||||||
|
def subscription_details?
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @note Defined as +text_field :activation_code+
|
||||||
|
# @return [String] The text content or value of +activation_code+
|
||||||
|
def activation_code
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set the value of activation_code
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# subscription.activation_code = 'value'
|
||||||
|
# end
|
||||||
|
# @param value [String] The value to set.
|
||||||
|
def activation_code=(value)
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription.activation_code_element).to exist
|
||||||
|
# end
|
||||||
|
# @return [Watir::TextField] The raw +TextField+ element
|
||||||
|
def activation_code_element
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription).to be_activation_code
|
||||||
|
# end
|
||||||
|
# @return [Boolean] true if the +activation_code+ element is present on the page
|
||||||
|
def activation_code?
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @note Defined as +label :terms_of_services+
|
||||||
|
# @return [String] The text content or value of +terms_of_services+
|
||||||
|
def terms_of_services
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription.terms_of_services_element).to exist
|
||||||
|
# end
|
||||||
|
# @return [Watir::Label] The raw +Label+ element
|
||||||
|
def terms_of_services_element
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription).to be_terms_of_services
|
||||||
|
# end
|
||||||
|
# @return [Boolean] true if the +terms_of_services+ element is present on the page
|
||||||
|
def terms_of_services?
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @note Defined as +button :activate+
|
||||||
|
# Clicks +activate+
|
||||||
|
def activate
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription.activate_element).to exist
|
||||||
|
# end
|
||||||
|
# @return [Watir::Button] The raw +Button+ element
|
||||||
|
def activate_element
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
|
# @example
|
||||||
|
# Gitlab::Page::Admin::Subscription.perform do |subscription|
|
||||||
|
# expect(subscription).to be_activate
|
||||||
|
# end
|
||||||
|
# @return [Boolean] true if the +activate+ element is present on the page
|
||||||
|
def activate?
|
||||||
|
# This is a stub, used for indexing. The method is dynamically generated.
|
||||||
|
end
|
||||||
|
|
||||||
# @note Defined as +p :plan+
|
# @note Defined as +p :plan+
|
||||||
# @return [String] The text content or value of +plan+
|
# @return [String] The text content or value of +plan+
|
||||||
def plan
|
def plan
|
||||||
|
|
|
@ -434,6 +434,10 @@ module QA
|
||||||
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
|
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ee_activation_code
|
||||||
|
ENV['QA_EE_ACTIVATION_CODE']
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def remote_grid_credentials
|
def remote_grid_credentials
|
||||||
|
|
|
@ -64,7 +64,9 @@ module QA
|
||||||
Page::Profile::Accounts::Show.perform do |show|
|
Page::Profile::Accounts::Show.perform do |show|
|
||||||
show.delete_account(user.password)
|
show.delete_account(user.password)
|
||||||
end
|
end
|
||||||
Support::Waiter.wait_until { !user.exists? }
|
|
||||||
|
# TODO: Remove retry_on_exception once https://gitlab.com/gitlab-org/gitlab/-/issues/24294 is resolved
|
||||||
|
Support::Waiter.wait_until(retry_on_exception: true, sleep_interval: 3) { !user.exists? }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
|
it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
|
||||||
|
|
|
@ -612,8 +612,8 @@ RSpec.describe Admin::UsersController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the new password does not match the password confirmation' do
|
context 'when the new password does not match the password confirmation' do
|
||||||
let(:password) { Gitlab::Password.test_default }
|
let(:password) { 'some_password' }
|
||||||
let(:password_confirmation) { "not" + Gitlab::Password.test_default }
|
let(:password_confirmation) { 'not_same_as_password' }
|
||||||
|
|
||||||
it 'shows the edit page again' do
|
it 'shows the edit page again' do
|
||||||
update_password(user, password, password_confirmation)
|
update_password(user, password, password_confirmation)
|
||||||
|
|
|
@ -186,6 +186,7 @@ RSpec.describe Projects::MergeRequests::CreationsController do
|
||||||
|
|
||||||
it 'fetches the commit if a user has access' do
|
it 'fetches the commit if a user has access' do
|
||||||
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
|
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
|
||||||
|
expect(Ability).to receive(:allowed?).with(user, :create_merge_request_in, project) { true }.at_least(:once)
|
||||||
|
|
||||||
get :branch_to,
|
get :branch_to,
|
||||||
params: {
|
params: {
|
||||||
|
@ -199,8 +200,25 @@ RSpec.describe Projects::MergeRequests::CreationsController do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not load the commit when the user cannot create_merge_request_in' do
|
||||||
|
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
|
||||||
|
expect(Ability).to receive(:allowed?).with(user, :create_merge_request_in, project) { false }.at_least(:once)
|
||||||
|
|
||||||
|
get :branch_to,
|
||||||
|
params: {
|
||||||
|
namespace_id: fork_project.namespace,
|
||||||
|
project_id: fork_project,
|
||||||
|
target_project_id: project.id,
|
||||||
|
ref: 'master'
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(assigns(:commit)).to be_nil
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not load the commit when the user cannot read the project' do
|
it 'does not load the commit when the user cannot read the project' do
|
||||||
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false }
|
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false }
|
||||||
|
expect(Ability).to receive(:allowed?).with(user, :create_merge_request_in, project) { true }.at_least(:once)
|
||||||
|
|
||||||
get :branch_to,
|
get :branch_to,
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -177,6 +177,7 @@ RSpec.describe Projects::MirrorsController do
|
||||||
INVALID
|
INVALID
|
||||||
git@example.com:foo/bar.git
|
git@example.com:foo/bar.git
|
||||||
ssh://git@example.com:foo/bar.git
|
ssh://git@example.com:foo/bar.git
|
||||||
|
ssh://127.0.0.1/foo/bar.git
|
||||||
].each do |url|
|
].each do |url|
|
||||||
it "returns an error with a 400 response for URL #{url.inspect}" do
|
it "returns an error with a 400 response for URL #{url.inspect}" do
|
||||||
do_get(project, url)
|
do_get(project, url)
|
||||||
|
|
|
@ -499,7 +499,7 @@ RSpec.describe RegistrationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'succeeds if password is confirmed' do
|
it 'succeeds if password is confirmed' do
|
||||||
post :destroy, params: { password: Gitlab::Password.test_default }
|
post :destroy, params: { password: '12345678' }
|
||||||
|
|
||||||
expect_success
|
expect_success
|
||||||
end
|
end
|
||||||
|
@ -540,7 +540,7 @@ RSpec.describe RegistrationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails' do
|
it 'fails' do
|
||||||
delete :destroy, params: { password: Gitlab::Password.test_default }
|
delete :destroy, params: { password: '12345678' }
|
||||||
|
|
||||||
expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
|
expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ FactoryBot.define do
|
||||||
file_format { :zip }
|
file_format { :zip }
|
||||||
|
|
||||||
trait :expired do
|
trait :expired do
|
||||||
expire_at { Date.yesterday }
|
expire_at { Time.current.yesterday.change(minute: 9) }
|
||||||
end
|
end
|
||||||
|
|
||||||
trait :locked do
|
trait :locked do
|
||||||
|
|
|
@ -5,7 +5,7 @@ FactoryBot.define do
|
||||||
email { generate(:email) }
|
email { generate(:email) }
|
||||||
name { generate(:name) }
|
name { generate(:name) }
|
||||||
username { generate(:username) }
|
username { generate(:username) }
|
||||||
password { Gitlab::Password.test_default }
|
password { "12345678" }
|
||||||
role { 'software_developer' }
|
role { 'software_developer' }
|
||||||
confirmed_at { Time.now }
|
confirmed_at { Time.now }
|
||||||
confirmation_token { nil }
|
confirmation_token { nil }
|
||||||
|
@ -23,6 +23,10 @@ FactoryBot.define do
|
||||||
after(:build) { |user, _| user.block! }
|
after(:build) { |user, _| user.block! }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :disallowed_password do
|
||||||
|
password { User::DISALLOWED_PASSWORDS.first }
|
||||||
|
end
|
||||||
|
|
||||||
trait :blocked_pending_approval do
|
trait :blocked_pending_approval do
|
||||||
after(:build) { |user, _| user.block_pending_approval! }
|
after(:build) { |user, _| user.block_pending_approval! }
|
||||||
end
|
end
|
||||||
|
|
55
spec/features/markdown/kroki_spec.rb
Normal file
55
spec/features/markdown/kroki_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'kroki rendering', :js do
|
||||||
|
let_it_be(:project) { create(:project, :public) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_application_setting(kroki_enabled: true, kroki_url: 'http://localhost:8000')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows kroki image' do
|
||||||
|
plain_text = 'This text length is ignored. ' * 300
|
||||||
|
|
||||||
|
description = <<~KROKI
|
||||||
|
#{plain_text}
|
||||||
|
```plantuml
|
||||||
|
A -> A: T
|
||||||
|
```
|
||||||
|
KROKI
|
||||||
|
|
||||||
|
issue = create(:issue, project: project, description: description)
|
||||||
|
|
||||||
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
within('.description') do
|
||||||
|
expect(page).to have_css('img')
|
||||||
|
expect(page).not_to have_text 'Warning: Displaying this diagram might cause performance issues on this page.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hides kroki image and shows warning alert when kroki source size is large' do
|
||||||
|
plantuml_text = 'A -> A: T ' * 300
|
||||||
|
|
||||||
|
description = <<~KROKI
|
||||||
|
```plantuml
|
||||||
|
#{plantuml_text}
|
||||||
|
```
|
||||||
|
KROKI
|
||||||
|
|
||||||
|
issue = create(:issue, project: project, description: description)
|
||||||
|
|
||||||
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
within('.description') do
|
||||||
|
expect(page).not_to have_css('img')
|
||||||
|
expect(page).to have_text 'Warning: Displaying this diagram might cause performance issues on this page.'
|
||||||
|
|
||||||
|
click_button 'Display'
|
||||||
|
|
||||||
|
expect(page).to have_css('img')
|
||||||
|
expect(page).not_to have_text 'Warning: Displaying this diagram might cause performance issues on this page.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -44,8 +44,8 @@ RSpec.describe 'Password reset' do
|
||||||
|
|
||||||
visit(edit_user_password_path(reset_password_token: token))
|
visit(edit_user_password_path(reset_password_token: token))
|
||||||
|
|
||||||
fill_in 'New password', with: "new" + Gitlab::Password.test_default
|
fill_in 'New password', with: 'hello1234'
|
||||||
fill_in 'Confirm new password', with: "new" + Gitlab::Password.test_default
|
fill_in 'Confirm new password', with: 'hello1234'
|
||||||
|
|
||||||
click_button 'Change your password'
|
click_button 'Change your password'
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe 'Profile account page', :js do
|
||||||
it 'deletes user', :js, :sidekiq_might_not_need_inline do
|
it 'deletes user', :js, :sidekiq_might_not_need_inline do
|
||||||
click_button 'Delete account'
|
click_button 'Delete account'
|
||||||
|
|
||||||
fill_in 'password', with: Gitlab::Password.test_default
|
fill_in 'password', with: '12345678'
|
||||||
|
|
||||||
page.within '.modal' do
|
page.within '.modal' do
|
||||||
click_button 'Delete account'
|
click_button 'Delete account'
|
||||||
|
|
|
@ -39,7 +39,7 @@ RSpec.describe 'Profile > Password' do
|
||||||
|
|
||||||
describe 'User puts the same passwords in the field and in the confirmation' do
|
describe 'User puts the same passwords in the field and in the confirmation' do
|
||||||
it 'shows a success message' do
|
it 'shows a success message' do
|
||||||
fill_passwords(Gitlab::Password.test_default, Gitlab::Password.test_default)
|
fill_passwords('mypassword', 'mypassword')
|
||||||
|
|
||||||
page.within('.flash-notice') do
|
page.within('.flash-notice') do
|
||||||
expect(page).to have_content('Password was successfully updated. Please sign in again.')
|
expect(page).to have_content('Password was successfully updated. Please sign in again.')
|
||||||
|
@ -79,7 +79,7 @@ RSpec.describe 'Profile > Password' do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Change password' do
|
context 'Change password' do
|
||||||
let(:new_password) { "new" + Gitlab::Password.test_default }
|
let(:new_password) { '22233344' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
@ -170,8 +170,8 @@ RSpec.describe 'Profile > Password' do
|
||||||
expect(current_path).to eq new_profile_password_path
|
expect(current_path).to eq new_profile_password_path
|
||||||
|
|
||||||
fill_in :user_password, with: user.password
|
fill_in :user_password, with: user.password
|
||||||
fill_in :user_new_password, with: Gitlab::Password.test_default
|
fill_in :user_new_password, with: '12345678'
|
||||||
fill_in :user_password_confirmation, with: Gitlab::Password.test_default
|
fill_in :user_password_confirmation, with: '12345678'
|
||||||
click_button 'Set new password'
|
click_button 'Set new password'
|
||||||
|
|
||||||
expect(current_path).to eq new_user_session_path
|
expect(current_path).to eq new_user_session_path
|
||||||
|
|
|
@ -9,7 +9,7 @@ RSpec.describe 'Session TTLs', :clean_gitlab_redis_shared_state do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
# The session key only gets created after a post
|
# The session key only gets created after a post
|
||||||
fill_in 'user_login', with: 'non-existant@gitlab.org'
|
fill_in 'user_login', with: 'non-existant@gitlab.org'
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
expect(page).to have_content('Invalid login or password')
|
expect(page).to have_content('Invalid login or password')
|
||||||
|
|
|
@ -49,15 +49,15 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
expect(current_path).to eq edit_user_password_path
|
expect(current_path).to eq edit_user_password_path
|
||||||
expect(page).to have_content('Please create a password for your new account.')
|
expect(page).to have_content('Please create a password for your new account.')
|
||||||
|
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: 'password'
|
||||||
fill_in 'user_password_confirmation', with: Gitlab::Password.test_default
|
fill_in 'user_password_confirmation', with: 'password'
|
||||||
click_button 'Change your password'
|
click_button 'Change your password'
|
||||||
|
|
||||||
expect(current_path).to eq new_user_session_path
|
expect(current_path).to eq new_user_session_path
|
||||||
expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))
|
expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))
|
||||||
|
|
||||||
fill_in 'user_login', with: user.username
|
fill_in 'user_login', with: user.username
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: 'password'
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
expect_single_session_with_authenticated_ttl
|
expect_single_session_with_authenticated_ttl
|
||||||
|
@ -150,6 +150,27 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'with a disallowed password' do
|
||||||
|
let(:user) { create(:user, :disallowed_password) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(authentication_metrics)
|
||||||
|
.to increment(:user_unauthenticated_counter)
|
||||||
|
.and increment(:user_password_invalid_counter)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'disallows login' do
|
||||||
|
gitlab_sign_in(user, password: user.password)
|
||||||
|
|
||||||
|
expect(page).to have_content('Invalid login or password.')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update Devise trackable attributes' do
|
||||||
|
expect { gitlab_sign_in(user, password: user.password) }
|
||||||
|
.not_to change { User.ghost.reload.sign_in_count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'with the ghost user' do
|
describe 'with the ghost user' do
|
||||||
it 'disallows login' do
|
it 'disallows login' do
|
||||||
expect(authentication_metrics)
|
expect(authentication_metrics)
|
||||||
|
@ -210,7 +231,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not allow sign-in if the user password is updated before entering a one-time code' do
|
it 'does not allow sign-in if the user password is updated before entering a one-time code' do
|
||||||
user.update!(password: "new" + Gitlab::Password.test_default)
|
user.update!(password: 'new_password')
|
||||||
|
|
||||||
enter_code(user.current_otp)
|
enter_code(user.current_otp)
|
||||||
|
|
||||||
|
@ -447,7 +468,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
|
|
||||||
fill_in 'user_login', with: user.email
|
fill_in 'user_login', with: user.email
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
expect(current_path).to eq(new_profile_password_path)
|
expect(current_path).to eq(new_profile_password_path)
|
||||||
|
@ -456,7 +477,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid username and password' do
|
context 'with invalid username and password' do
|
||||||
let(:user) { create(:user, password: "not" + Gitlab::Password.test_default) }
|
let(:user) { create(:user, password: 'not-the-default') }
|
||||||
|
|
||||||
it 'blocks invalid login' do
|
it 'blocks invalid login' do
|
||||||
expect(authentication_metrics)
|
expect(authentication_metrics)
|
||||||
|
@ -767,7 +788,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
|
|
||||||
fill_in 'user_login', with: user.email
|
fill_in 'user_login', with: user.email
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
|
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
|
@ -788,7 +809,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
|
|
||||||
fill_in 'user_login', with: user.email
|
fill_in 'user_login', with: user.email
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
|
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
|
@ -809,7 +830,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
|
|
||||||
fill_in 'user_login', with: user.email
|
fill_in 'user_login', with: user.email
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
|
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
|
@ -844,7 +865,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
|
|
||||||
fill_in 'user_login', with: user.email
|
fill_in 'user_login', with: user.email
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
fill_in 'user_otp_attempt', with: user.reload.current_otp
|
fill_in 'user_otp_attempt', with: user.reload.current_otp
|
||||||
|
@ -870,7 +891,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
visit new_user_session_path
|
visit new_user_session_path
|
||||||
|
|
||||||
fill_in 'user_login', with: user.email
|
fill_in 'user_login', with: user.email
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
click_button 'Sign in'
|
click_button 'Sign in'
|
||||||
|
|
||||||
expect_to_be_on_terms_page
|
expect_to_be_on_terms_page
|
||||||
|
@ -878,7 +899,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
||||||
|
|
||||||
expect(current_path).to eq(new_profile_password_path)
|
expect(current_path).to eq(new_profile_password_path)
|
||||||
|
|
||||||
fill_in 'user_password', with: Gitlab::Password.test_default
|
fill_in 'user_password', with: '12345678'
|
||||||
fill_in 'user_new_password', with: 'new password'
|
fill_in 'user_new_password', with: 'new password'
|
||||||
fill_in 'user_password_confirmation', with: 'new password'
|
fill_in 'user_password_confirmation', with: 'new password'
|
||||||
click_button 'Set new password'
|
click_button 'Set new password'
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { GlAlert } from '@gitlab/ui';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import DiagramPerformanceWarning from '~/behaviors/components/diagram_performance_warning.vue';
|
||||||
|
|
||||||
|
describe('DiagramPerformanceWarning component', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = shallowMount(DiagramPerformanceWarning);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders warning alert with button', () => {
|
||||||
|
expect(findAlert().props()).toMatchObject({
|
||||||
|
primaryButtonText: DiagramPerformanceWarning.i18n.buttonText,
|
||||||
|
variant: 'warning',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders warning message', () => {
|
||||||
|
expect(findAlert().text()).toBe(DiagramPerformanceWarning.i18n.bodyText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits event when closing alert', () => {
|
||||||
|
findAlert().vm.$emit('dismiss');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('closeAlert')).toEqual([[]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits event when accepting alert', () => {
|
||||||
|
findAlert().vm.$emit('primaryAction');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('showImage')).toEqual([[]]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,7 +11,7 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
|
||||||
|
|
||||||
let(:current_user) { reporter }
|
let(:current_user) { reporter }
|
||||||
|
|
||||||
before_all do
|
before do
|
||||||
project.add_guest(guest)
|
project.add_guest(guest)
|
||||||
project.add_reporter(reporter)
|
project.add_reporter(reporter)
|
||||||
end
|
end
|
||||||
|
@ -20,13 +20,8 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
|
||||||
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
|
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_statistics(project, args)
|
shared_examples 'returns the pipelines statistics for a given project' do
|
||||||
ctx = { current_user: current_user }
|
it do
|
||||||
resolve(described_class, obj: project, args: args, ctx: ctx)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#resolve' do
|
|
||||||
it 'returns the pipelines statistics for a given project' do
|
|
||||||
result = resolve_statistics(project, {})
|
result = resolve_statistics(project, {})
|
||||||
expect(result.keys).to contain_exactly(
|
expect(result.keys).to contain_exactly(
|
||||||
:week_pipelines_labels,
|
:week_pipelines_labels,
|
||||||
|
@ -42,14 +37,67 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
|
||||||
:pipeline_times_values
|
:pipeline_times_values
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'it returns nils' do
|
||||||
|
it do
|
||||||
|
result = resolve_statistics(project, {})
|
||||||
|
|
||||||
|
expect(result).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_statistics(project, args)
|
||||||
|
ctx = { current_user: current_user }
|
||||||
|
resolve(described_class, obj: project, args: args, ctx: ctx)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#resolve' do
|
||||||
|
it_behaves_like 'returns the pipelines statistics for a given project'
|
||||||
|
|
||||||
context 'when the user does not have access to the CI/CD analytics data' do
|
context 'when the user does not have access to the CI/CD analytics data' do
|
||||||
let(:current_user) { guest }
|
let(:current_user) { guest }
|
||||||
|
|
||||||
it 'returns nil' do
|
it_behaves_like 'it returns nils'
|
||||||
result = resolve_statistics(project, {})
|
end
|
||||||
|
|
||||||
expect(result).to be_nil
|
context 'when the project is public' do
|
||||||
|
let_it_be(:project) { create(:project, :public) }
|
||||||
|
|
||||||
|
context 'public pipelines are disabled' do
|
||||||
|
before do
|
||||||
|
project.update!(public_builds: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user is not a member' do
|
||||||
|
let(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
it_behaves_like 'it returns nils'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user is a guest' do
|
||||||
|
let(:current_user) { guest }
|
||||||
|
|
||||||
|
it_behaves_like 'it returns nils'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user is a reporter or above' do
|
||||||
|
let(:current_user) { reporter }
|
||||||
|
|
||||||
|
it_behaves_like 'returns the pipelines statistics for a given project'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'public pipelines are enabled' do
|
||||||
|
before do
|
||||||
|
project.update!(public_builds: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user is not a member' do
|
||||||
|
let(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
it_behaves_like 'returns the pipelines statistics for a given project'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
24
spec/initializers/rdoc_segfault_patch_spec.rb
Normal file
24
spec/initializers/rdoc_segfault_patch_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe 'RDoc segfault patch fix' do
|
||||||
|
describe 'RDoc::Markup::ToHtml' do
|
||||||
|
describe '#parseable?' do
|
||||||
|
it 'returns false' do
|
||||||
|
to_html = RDoc::Markup::ToHtml.new( nil)
|
||||||
|
|
||||||
|
expect(to_html.parseable?('"def foo; end"')).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'RDoc::Markup::Verbatim' do
|
||||||
|
describe 'ruby?' do
|
||||||
|
it 'returns false' do
|
||||||
|
verbatim = RDoc::Markup::Verbatim.new('def foo; end')
|
||||||
|
verbatim.format = :ruby
|
||||||
|
|
||||||
|
expect(verbatim.ruby?).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,7 +9,7 @@ RSpec.describe Banzai::Filter::KrokiFilter do
|
||||||
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
|
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
|
||||||
doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
|
doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
|
||||||
|
|
||||||
expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
|
expect(doc.to_s).to eq '<img class="js-render-kroki" src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'replaces nomnoml pre tag with img tag if both kroki and plantuml are enabled' do
|
it 'replaces nomnoml pre tag with img tag if both kroki and plantuml are enabled' do
|
||||||
|
@ -19,7 +19,7 @@ RSpec.describe Banzai::Filter::KrokiFilter do
|
||||||
plantuml_url: "http://localhost:8080")
|
plantuml_url: "http://localhost:8080")
|
||||||
doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
|
doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
|
||||||
|
|
||||||
expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
|
expect(doc.to_s).to eq '<img class="js-render-kroki" src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not replace nomnoml pre tag with img tag if kroki is disabled' do
|
it 'does not replace nomnoml pre tag with img tag if kroki is disabled' do
|
||||||
|
@ -38,4 +38,12 @@ RSpec.describe Banzai::Filter::KrokiFilter do
|
||||||
|
|
||||||
expect(doc.to_s).to eq '<pre lang="plantuml"><code>Bob->Alice : hello</code></pre>'
|
expect(doc.to_s).to eq '<pre lang="plantuml"><code>Bob->Alice : hello</code></pre>'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'adds hidden attribute when content size is large' do
|
||||||
|
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
|
||||||
|
text = '[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]' * 25
|
||||||
|
doc = filter("<pre lang='nomnoml'><code>#{text}</code></pre>")
|
||||||
|
|
||||||
|
expect(doc.to_s).to eq '<img class="js-render-kroki" src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KyJyVNQiE5KTSxKidXVjS5ILCrKL4lFFrSyi07LL81RyM0vLckAysRGjxo8avCowaMGjxo8avCowaMGU8lgAE7mIdc=" hidden>'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -132,6 +132,12 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
|
||||||
|
|
||||||
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
|
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "escape sourcepos metadata to prevent XSS" do
|
||||||
|
result = filter('<pre data-sourcepos=""%22 href="x"></pre><base href=http://unsafe-website.com/><pre x=""><code></code></pre>')
|
||||||
|
|
||||||
|
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos=\'"%22 href="x"></pre><base href=http://unsafe-website.com/><pre x="\' class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code></code></pre><copy-code></copy-code></div>')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when Rouge lexing fails" do
|
context "when Rouge lexing fails" do
|
||||||
|
|
|
@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceRedactor do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when data-original attribute provided' do
|
context 'when data-original attribute provided' do
|
||||||
let(:original_content) { '<code>foo</code>' }
|
let(:original_content) { '<script>alert(1);</script>' }
|
||||||
|
|
||||||
it 'replaces redacted reference with original content' do
|
it 'replaces redacted reference with original content' do
|
||||||
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
|
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
|
||||||
|
|
|
@ -87,7 +87,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when IP is already banned' do
|
context 'when IP is already banned' do
|
||||||
subject { gl_auth.find_for_git_client('username', Gitlab::Password.test_default, project: nil, ip: 'ip') }
|
subject { gl_auth.find_for_git_client('username', 'password', project: nil, ip: 'ip') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
|
expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
|
||||||
|
@ -204,16 +204,16 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'recognizes master passwords' do
|
it 'recognizes master passwords' do
|
||||||
user = create(:user, password: Gitlab::Password.test_default)
|
user = create(:user, password: 'password')
|
||||||
|
|
||||||
expect(gl_auth.find_for_git_client(user.username, Gitlab::Password.test_default, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
|
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
|
||||||
end
|
end
|
||||||
|
|
||||||
include_examples 'user login operation with unique ip limit' do
|
include_examples 'user login operation with unique ip limit' do
|
||||||
let(:user) { create(:user, password: Gitlab::Password.test_default) }
|
let(:user) { create(:user, password: 'password') }
|
||||||
|
|
||||||
def operation
|
def operation
|
||||||
expect(gl_auth.find_for_git_client(user.username, Gitlab::Password.test_default, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
|
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
:user,
|
:user,
|
||||||
:blocked,
|
:blocked,
|
||||||
username: 'normal_user',
|
username: 'normal_user',
|
||||||
password: Gitlab::Password.test_default
|
password: 'my-secret'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
|
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
|
||||||
|
@ -486,7 +486,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
|
|
||||||
context 'when 2fa is enabled globally' do
|
context 'when 2fa is enabled globally' do
|
||||||
let_it_be(:user) do
|
let_it_be(:user) do
|
||||||
create(:user, username: 'normal_user', password: Gitlab::Password.test_default, otp_grace_period_started_at: 1.day.ago)
|
create(:user, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -510,7 +510,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
|
|
||||||
context 'when 2fa is enabled personally' do
|
context 'when 2fa is enabled personally' do
|
||||||
let(:user) do
|
let(:user) do
|
||||||
create(:user, :two_factor, username: 'normal_user', password: Gitlab::Password.test_default, otp_grace_period_started_at: 1.day.ago)
|
create(:user, :two_factor, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails' do
|
it 'fails' do
|
||||||
|
@ -523,7 +523,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
user = create(
|
user = create(
|
||||||
:user,
|
:user,
|
||||||
username: 'normal_user',
|
username: 'normal_user',
|
||||||
password: Gitlab::Password.test_default
|
password: 'my-secret'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
|
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
|
||||||
|
@ -534,7 +534,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
user = create(
|
user = create(
|
||||||
:user,
|
:user,
|
||||||
username: 'oauth2',
|
username: 'oauth2',
|
||||||
password: Gitlab::Password.test_default
|
password: 'my-secret'
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
|
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
|
||||||
|
@ -609,7 +609,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
|
|
||||||
context 'when deploy token and user have the same username' do
|
context 'when deploy token and user have the same username' do
|
||||||
let(:username) { 'normal_user' }
|
let(:username) { 'normal_user' }
|
||||||
let(:user) { create(:user, username: username, password: Gitlab::Password.test_default) }
|
let(:user) { create(:user, username: username, password: 'my-secret') }
|
||||||
let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }
|
let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }
|
||||||
|
|
||||||
it 'succeeds for the token' do
|
it 'succeeds for the token' do
|
||||||
|
@ -622,7 +622,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
it 'succeeds for the user' do
|
it 'succeeds for the user' do
|
||||||
auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities }
|
auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities }
|
||||||
|
|
||||||
expect(gl_auth.find_for_git_client(username, Gitlab::Password.test_default, project: project, ip: 'ip'))
|
expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
|
||||||
.to have_attributes(auth_success)
|
.to have_attributes(auth_success)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -816,7 +816,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:username) { 'John' } # username isn't lowercase, test this
|
let(:username) { 'John' } # username isn't lowercase, test this
|
||||||
let(:password) { Gitlab::Password.test_default }
|
let(:password) { 'my-secret' }
|
||||||
|
|
||||||
it "finds user by valid login/password" do
|
it "finds user by valid login/password" do
|
||||||
expect(gl_auth.find_with_user_password(username, password)).to eql user
|
expect(gl_auth.find_with_user_password(username, password)).to eql user
|
||||||
|
@ -941,13 +941,13 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
||||||
it "does not find user by using ldap as fallback to for authentication" do
|
it "does not find user by using ldap as fallback to for authentication" do
|
||||||
expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(nil)
|
expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(nil)
|
||||||
|
|
||||||
expect(gl_auth.find_with_user_password('ldap_user', Gitlab::Password.test_default)).to be_nil
|
expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "find new user by using ldap as fallback to for authentication" do
|
it "find new user by using ldap as fallback to for authentication" do
|
||||||
expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(user)
|
expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(user)
|
||||||
|
|
||||||
expect(gl_auth.find_with_user_password('ldap_user', Gitlab::Password.test_default)).to eq(user)
|
expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
|
||||||
let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) }
|
let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) }
|
||||||
|
|
||||||
context 'when file is empty' do
|
context 'when file is empty' do
|
||||||
|
let(:params) { { artifact: 'secret_stuff/generated.yml', job: 'generator' } }
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_stuff', 'masked' => true }]) }
|
||||||
|
let(:context) do
|
||||||
|
Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline, variables: variables)
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
|
allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
|
||||||
allow(reader).to receive(:read).and_return('')
|
allow(reader).to receive(:read).and_return('')
|
||||||
|
@ -134,7 +140,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:expected_error) do
|
let(:expected_error) do
|
||||||
'File `generated.yml` is empty!'
|
'File `xxxxxxxxxxxx/generated.yml` is empty!'
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'is invalid'
|
it_behaves_like 'is invalid'
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::Ci::Config::External::File::Base do
|
RSpec.describe Gitlab::Ci::Config::External::File::Base do
|
||||||
let(:context_params) { { sha: 'HEAD' } }
|
let(:variables) { }
|
||||||
|
let(:context_params) { { sha: 'HEAD', variables: variables } }
|
||||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||||
|
|
||||||
let(:test_class) do
|
let(:test_class) do
|
||||||
|
@ -76,7 +77,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when there are YAML syntax errors' do
|
context 'when there are YAML syntax errors' do
|
||||||
let(:location) { 'some/file/config.yml' }
|
let(:location) { 'some/file/secret_file_name.yml' }
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file_name', 'masked' => true }]) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_any_instance_of(test_class)
|
allow_any_instance_of(test_class)
|
||||||
|
@ -85,7 +87,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
|
||||||
|
|
||||||
it 'is not a valid file' do
|
it 'is not a valid file' do
|
||||||
expect(subject).not_to be_valid
|
expect(subject).not_to be_valid
|
||||||
expect(subject.error_message).to match /does not have valid YAML syntax/
|
expect(subject.error_message).to eq('Included file `some/file/xxxxxxxxxxxxxxxx.yml` does not have valid YAML syntax!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
let(:sha) { '12345' }
|
let(:sha) { '12345' }
|
||||||
|
let(:variables) { project.predefined_variables.to_runner_variables }
|
||||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||||
let(:params) { { local: location } }
|
let(:params) { { local: location } }
|
||||||
let(:local_file) { described_class.new(params, context) }
|
let(:local_file) { described_class.new(params, context) }
|
||||||
|
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
||||||
sha: sha,
|
sha: sha,
|
||||||
user: user,
|
user: user,
|
||||||
parent_pipeline: parent_pipeline,
|
parent_pipeline: parent_pipeline,
|
||||||
variables: project.predefined_variables.to_runner_variables
|
variables: variables
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when is not a valid local path' do
|
context 'when it is not a valid local path' do
|
||||||
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
|
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
|
@ -74,13 +75,23 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when is not a yaml file' do
|
context 'when it is not a yaml file' do
|
||||||
let(:location) { '/config/application.rb' }
|
let(:location) { '/config/application.rb' }
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
expect(local_file.valid?).to be_falsy
|
expect(local_file.valid?).to be_falsy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when it is an empty file' do
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret', 'masked' => true }]) }
|
||||||
|
let(:location) { '/lib/gitlab/ci/templates/secret/existent-file.yml' }
|
||||||
|
|
||||||
|
it 'returns false and adds an error message about an empty file' do
|
||||||
|
allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("")
|
||||||
|
expect(local_file.errors).to include("Local file `/lib/gitlab/ci/templates/xxxxxx/existent-file.yml` is empty!")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#content' do
|
describe '#content' do
|
||||||
|
@ -116,10 +127,11 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#error_message' do
|
describe '#error_message' do
|
||||||
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
|
let(:location) { '/lib/gitlab/ci/templates/secret_file.yml' }
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
|
||||||
|
|
||||||
it 'returns an error message' do
|
it 'returns an error message' do
|
||||||
expect(local_file.error_message).to eq("Local file `#{location}` does not exist!")
|
expect(local_file.error_message).to eq("Local file `/lib/gitlab/ci/templates/xxxxxxxxxxx.yml` does not exist!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
||||||
let(:parent_pipeline) { double(:parent_pipeline) }
|
let(:parent_pipeline) { double(:parent_pipeline) }
|
||||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||||
let(:project_file) { described_class.new(params, context) }
|
let(:project_file) { described_class.new(params, context) }
|
||||||
|
let(:variables) { project.predefined_variables.to_runner_variables }
|
||||||
|
|
||||||
let(:context_params) do
|
let(:context_params) do
|
||||||
{
|
{
|
||||||
|
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
||||||
sha: '12345',
|
sha: '12345',
|
||||||
user: context_user,
|
user: context_user,
|
||||||
parent_pipeline: parent_pipeline,
|
parent_pipeline: parent_pipeline,
|
||||||
variables: project.predefined_variables.to_runner_variables
|
variables: variables
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,18 +109,19 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
||||||
|
|
||||||
context 'when an empty file is used' do
|
context 'when an empty file is used' do
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ project: project.full_path, file: '/file.yml' }
|
{ project: project.full_path, file: '/secret_file.yml' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
|
||||||
let(:root_ref_sha) { project.repository.root_ref_sha }
|
let(:root_ref_sha) { project.repository.root_ref_sha }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_project_blob(root_ref_sha, '/file.yml') { '' }
|
stub_project_blob(root_ref_sha, '/secret_file.yml') { '' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
expect(project_file).not_to be_valid
|
expect(project_file).not_to be_valid
|
||||||
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!")
|
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxx.yml` is empty!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -135,13 +137,15 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when non-existing file is requested' do
|
context 'when non-existing file is requested' do
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-invalid-file', 'masked' => true }]) }
|
||||||
|
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{ project: project.full_path, file: '/invalid-file.yml' }
|
{ project: project.full_path, file: '/secret-invalid-file.yml' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
expect(project_file).not_to be_valid
|
expect(project_file).not_to be_valid
|
||||||
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!")
|
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxxxxxxxxxx.yml` does not exist!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ require 'spec_helper'
|
||||||
RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
include StubRequests
|
include StubRequests
|
||||||
|
|
||||||
let(:context_params) { { sha: '12345' } }
|
let(:variables) {Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
|
||||||
|
let(:context_params) { { sha: '12345', variables: variables } }
|
||||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||||
let(:params) { { remote: location } }
|
let(:params) { { remote: location } }
|
||||||
let(:remote_file) { described_class.new(params, context) }
|
let(:remote_file) { described_class.new(params, context) }
|
||||||
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.secret_file.yml' }
|
||||||
let(:remote_file_content) do
|
let(:remote_file_content) do
|
||||||
<<~HEREDOC
|
<<~HEREDOC
|
||||||
before_script:
|
before_script:
|
||||||
|
@ -144,10 +145,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
subject { remote_file.error_message }
|
subject { remote_file.error_message }
|
||||||
|
|
||||||
context 'when remote file location is not valid' do
|
context 'when remote file location is not valid' do
|
||||||
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/?secret_file.yml' }
|
||||||
|
|
||||||
it 'returns an error message describing invalid address' do
|
it 'returns an error message describing invalid address' do
|
||||||
expect(subject).to match /does not have a valid address!/
|
expect(subject).to eq('Remote file `not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/?xxxxxxxxxxx.yml` does not have a valid address!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns error message about a timeout' do
|
it 'returns error message about a timeout' do
|
||||||
expect(subject).to match /could not be fetched because of a timeout error!/
|
expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of a timeout error!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns error message about a HTTP error' do
|
it 'returns error message about a HTTP error' do
|
||||||
expect(subject).to match /could not be fetched because of HTTP error!/
|
expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of HTTP error!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -177,7 +178,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns error message about a timeout' do
|
it 'returns error message about a timeout' do
|
||||||
expect(subject).to match /could not be fetched because of HTTP code `404` error!/
|
expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of HTTP code `404` error!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -54,11 +54,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid template name' do
|
context 'with invalid template name' do
|
||||||
let(:template) { 'Template.yml' }
|
let(:template) { 'SecretTemplate.yml' }
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'SecretTemplate', 'masked' => true }]) }
|
||||||
|
let(:context_params) { { project: project, sha: '12345', user: user, variables: variables } }
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
expect(template_file).not_to be_valid
|
expect(template_file).not_to be_valid
|
||||||
expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!')
|
expect(template_file.error_message).to include('`xxxxxxxxxxxxxx.yml` is not a valid location!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
|
||||||
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
|
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
|
||||||
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
||||||
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
|
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
|
||||||
let(:context_params) { { project: project, sha: '123456', user: user, variables: project.predefined_variables } }
|
let(:variables) { project.predefined_variables }
|
||||||
|
let(:context_params) { { project: project, sha: '123456', user: user, variables: variables } }
|
||||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||||
|
|
||||||
let(:file_content) do
|
let(:file_content) do
|
||||||
|
@ -92,13 +93,16 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the key is a hash of file and remote' do
|
context 'when the key is a hash of file and remote' do
|
||||||
|
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-file', 'masked' => true }]) }
|
||||||
|
let(:local_file) { 'secret-file.yml' }
|
||||||
|
let(:remote_url) { 'https://gitlab.com/secret-file.yml' }
|
||||||
let(:values) do
|
let(:values) do
|
||||||
{ include: { 'local' => local_file, 'remote' => remote_url },
|
{ include: { 'local' => local_file, 'remote' => remote_url },
|
||||||
image: 'ruby:2.7' }
|
image: 'ruby:2.7' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns ambigious specification error' do
|
it 'returns ambigious specification error' do
|
||||||
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
|
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, 'Include `{"local":"xxxxxxxxxxx.yml","remote":"https://gitlab.com/xxxxxxxxxxx.yml"}` needs to match exactly one accessor!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,33 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
|
||||||
cleanup.run!
|
cleanup.run!
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds artifacts on disk' do
|
it 'finds job artifacts on disk' do
|
||||||
artifact = create(:ci_job_artifact, :archive)
|
artifact = create(:ci_job_artifact, :archive)
|
||||||
|
artifact_directory = artifact.file.relative_path.to_s.split('/')[0...6].join('/')
|
||||||
|
|
||||||
|
cleaned = []
|
||||||
|
|
||||||
|
expect(cleanup).to receive(:find_artifacts).and_wrap_original do |original_method, *args, &block|
|
||||||
|
original_method.call(*args) { |dir| cleaned << dir }
|
||||||
|
end
|
||||||
|
|
||||||
|
cleanup.run!
|
||||||
|
|
||||||
|
expect(cleaned).to include(/#{artifact_directory}/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not find pipeline artifacts on disk' do
|
||||||
|
artifact = create(:ci_pipeline_artifact, :with_coverage_report)
|
||||||
|
# using 0...6 to match the -min/maxdepth 6 strictly, since this is one directory
|
||||||
|
# deeper than job artifacts, and .dirname would not match
|
||||||
|
artifact_directory = artifact.file.relative_path.to_s.split('/')[0...6].join('/')
|
||||||
|
|
||||||
|
expect(cleanup).to receive(:find_artifacts).and_wrap_original do |original_method, *args, &block|
|
||||||
|
# this can either _not_ yield at all, or yield with any other file
|
||||||
|
# except the one that we're explicitly excluding
|
||||||
|
original_method.call(*args) { |path| expect(path).not_to match(artifact_directory) }
|
||||||
|
end
|
||||||
|
|
||||||
expect(cleanup).to receive(:find_artifacts).and_yield(artifact.file.path)
|
|
||||||
cleanup.run!
|
cleanup.run!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor, :sentry do
|
||||||
|
describe '.call' do
|
||||||
|
let(:exception) { StandardError.new('raw error') }
|
||||||
|
let(:event) { Raven::Event.from_exception(exception, raven_required_options) }
|
||||||
|
let(:result_hash) { described_class.call(event).to_hash }
|
||||||
|
let(:raven_required_options) do
|
||||||
|
{
|
||||||
|
configuration: Raven.configuration,
|
||||||
|
context: Raven.context,
|
||||||
|
breadcrumbs: Raven.breadcrumbs
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'cleans the exception message' do
|
||||||
|
expect(Gitlab::Sanitizers::ExceptionMessage).to receive(:clean).with('StandardError', 'raw error').and_return('cleaned')
|
||||||
|
|
||||||
|
expect(result_hash[:exception][:values].first).to include(
|
||||||
|
type: 'StandardError',
|
||||||
|
value: 'cleaned'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when event is invalid' do
|
||||||
|
let(:event) { instance_double('Raven::Event', to_hash: { invalid: true }) }
|
||||||
|
|
||||||
|
it 'does nothing' do
|
||||||
|
extracted_exception = instance_double('Raven::SingleExceptionInterface', value: nil)
|
||||||
|
allow(described_class).to receive(:extract_exceptions_from).and_return([extracted_exception])
|
||||||
|
|
||||||
|
expect(Gitlab::Sanitizers::ExceptionMessage).not_to receive(:clean)
|
||||||
|
expect(result_hash).to eq(invalid: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -301,5 +301,48 @@ RSpec.describe Gitlab::ErrorTracking do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when processing invalid URI exceptions' do
|
||||||
|
let(:invalid_uri) { 'http://foo:bar' }
|
||||||
|
let(:sentry_exception_values) { sentry_event['exception']['values'] }
|
||||||
|
|
||||||
|
context 'when the error is a URI::InvalidURIError' do
|
||||||
|
let(:exception) do
|
||||||
|
URI.parse(invalid_uri)
|
||||||
|
rescue URI::InvalidURIError => error
|
||||||
|
error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters the URI from the error message' do
|
||||||
|
track_exception
|
||||||
|
|
||||||
|
expect(sentry_exception_values).to include(
|
||||||
|
hash_including(
|
||||||
|
'type' => 'URI::InvalidURIError',
|
||||||
|
'value' => 'bad URI(is not URI?): [FILTERED]'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the error is a Addressable::URI::InvalidURIError' do
|
||||||
|
let(:exception) do
|
||||||
|
Addressable::URI.parse(invalid_uri)
|
||||||
|
rescue Addressable::URI::InvalidURIError => error
|
||||||
|
error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'filters the URI from the error message' do
|
||||||
|
track_exception
|
||||||
|
|
||||||
|
expect(sentry_exception_values).to include(
|
||||||
|
hash_including(
|
||||||
|
'type' => 'Addressable::URI::InvalidURIError',
|
||||||
|
'value' => 'Invalid port number: [FILTERED]'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,14 @@ RSpec.describe Gitlab::ExceptionLogFormatter do
|
||||||
expect(payload['exception.sql']).to be_nil
|
expect(payload['exception.sql']).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'cleans the exception message' do
|
||||||
|
expect(Gitlab::Sanitizers::ExceptionMessage).to receive(:clean).with('RuntimeError', 'bad request').and_return('cleaned')
|
||||||
|
|
||||||
|
described_class.format!(exception, payload)
|
||||||
|
|
||||||
|
expect(payload['exception.message']).to eq('cleaned')
|
||||||
|
end
|
||||||
|
|
||||||
context 'when exception is ActiveRecord::StatementInvalid' do
|
context 'when exception is ActiveRecord::StatementInvalid' do
|
||||||
let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
|
let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
||||||
"notification_level" => 3,
|
"notification_level" => 3,
|
||||||
"created_at" => "2016-03-11T10:21:44.822Z",
|
"created_at" => "2016-03-11T10:21:44.822Z",
|
||||||
"updated_at" => "2016-03-11T10:21:44.822Z",
|
"updated_at" => "2016-03-11T10:21:44.822Z",
|
||||||
"created_by_id" => nil,
|
"created_by_id" => 1,
|
||||||
"invite_email" => nil,
|
"invite_email" => nil,
|
||||||
"invite_token" => nil,
|
"invite_token" => nil,
|
||||||
"invite_accepted_at" => nil,
|
"invite_accepted_at" => nil,
|
||||||
|
@ -38,10 +38,24 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
||||||
"notification_level" => 3,
|
"notification_level" => 3,
|
||||||
"created_at" => "2016-03-11T10:21:44.822Z",
|
"created_at" => "2016-03-11T10:21:44.822Z",
|
||||||
"updated_at" => "2016-03-11T10:21:44.822Z",
|
"updated_at" => "2016-03-11T10:21:44.822Z",
|
||||||
"created_by_id" => 1,
|
"created_by_id" => 2,
|
||||||
"invite_email" => 'invite@test.com',
|
"invite_email" => 'invite@test.com',
|
||||||
"invite_token" => 'token',
|
"invite_token" => 'token',
|
||||||
"invite_accepted_at" => nil
|
"invite_accepted_at" => nil
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id" => 3,
|
||||||
|
"access_level" => 40,
|
||||||
|
"source_id" => 14,
|
||||||
|
"source_type" => source_type,
|
||||||
|
"user_id" => nil,
|
||||||
|
"notification_level" => 3,
|
||||||
|
"created_at" => "2016-03-11T10:21:44.822Z",
|
||||||
|
"updated_at" => "2016-03-11T10:21:44.822Z",
|
||||||
|
"created_by_id" => nil,
|
||||||
|
"invite_email" => 'invite2@test.com',
|
||||||
|
"invite_token" => 'token',
|
||||||
|
"invite_accepted_at" => nil
|
||||||
}]
|
}]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -68,12 +82,37 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
||||||
expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil
|
expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes old user_id from member_hash to avoid conflict with user key' do
|
it 'maps created_by_id to user on new instance' do
|
||||||
expect(member_class)
|
expect(member_class)
|
||||||
.to receive(:create)
|
.to receive(:create)
|
||||||
.twice
|
.once
|
||||||
.with(hash_excluding('user_id'))
|
.with(hash_including('user_id' => user2.id, 'created_by_id' => nil))
|
||||||
.and_call_original
|
.and_call_original
|
||||||
|
expect(member_class)
|
||||||
|
.to receive(:create)
|
||||||
|
.once
|
||||||
|
.with(hash_including('invite_email' => 'invite@test.com', 'created_by_id' => nil))
|
||||||
|
.and_call_original
|
||||||
|
expect(member_class)
|
||||||
|
.to receive(:create)
|
||||||
|
.once
|
||||||
|
.with(hash_including('invite_email' => 'invite2@test.com', 'created_by_id' => nil))
|
||||||
|
.and_call_original
|
||||||
|
|
||||||
|
members_mapper.map
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'replaced user_id with user_id from new instance' do
|
||||||
|
expect(member_class)
|
||||||
|
.to receive(:create)
|
||||||
|
.once
|
||||||
|
.with(hash_including('user_id' => user2.id))
|
||||||
|
.and_call_original
|
||||||
|
expect(member_class)
|
||||||
|
.to receive(:create)
|
||||||
|
.twice
|
||||||
|
.with(hash_excluding('user_id'))
|
||||||
|
.and_call_original
|
||||||
|
|
||||||
members_mapper.map
|
members_mapper.map
|
||||||
end
|
end
|
||||||
|
@ -99,7 +138,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(user2.id))).once
|
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(user2.id))).once
|
||||||
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(nil))).once
|
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(nil))).twice
|
||||||
|
|
||||||
members_mapper.map
|
members_mapper.map
|
||||||
end
|
end
|
||||||
|
|
54
spec/lib/gitlab/sanitizers/exception_message_spec.rb
Normal file
54
spec/lib/gitlab/sanitizers/exception_message_spec.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'fast_spec_helper'
|
||||||
|
require 'rspec-parameterized'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Sanitizers::ExceptionMessage do
|
||||||
|
describe '.clean' do
|
||||||
|
let(:exception_name) { exception.class.name }
|
||||||
|
let(:exception_message) { exception.message }
|
||||||
|
|
||||||
|
subject { described_class.clean(exception_name, exception_message) }
|
||||||
|
|
||||||
|
context 'when error is a URI::InvalidURIError' do
|
||||||
|
let(:exception) do
|
||||||
|
URI.parse('http://foo:bar')
|
||||||
|
rescue URI::InvalidURIError => error
|
||||||
|
error
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq('bad URI(is not URI?): [FILTERED]') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when error is an Addressable::URI::InvalidURIError' do
|
||||||
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
let(:exception) do
|
||||||
|
Addressable::URI.parse(uri)
|
||||||
|
rescue Addressable::URI::InvalidURIError => error
|
||||||
|
error
|
||||||
|
end
|
||||||
|
|
||||||
|
where(:uri, :result) do
|
||||||
|
'http://foo:bar' | 'Invalid port number: [FILTERED]'
|
||||||
|
'http://foo:%eb' | 'Invalid encoding in port'
|
||||||
|
'ht%0atp://foo' | 'Invalid scheme format: [FILTERED]'
|
||||||
|
'http:' | 'Absolute URI missing hierarchical segment: [FILTERED]'
|
||||||
|
'::http' | 'Cannot assemble URI string with ambiguous path: [FILTERED]'
|
||||||
|
'http://foo bar' | 'Invalid character in host: [FILTERED]'
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it { is_expected.to eq(result) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with any other exception' do
|
||||||
|
let(:exception) { StandardError.new('Error message: http://foo@bar:baz@ex:ample.com') }
|
||||||
|
|
||||||
|
it 'is not invoked and does nothing' do
|
||||||
|
is_expected.to eq('Error message: http://foo@bar:baz@ex:ample.com')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -49,7 +49,7 @@ RSpec.describe Emails::Profile do
|
||||||
|
|
||||||
describe 'for users that signed up, the email' do
|
describe 'for users that signed up, the email' do
|
||||||
let(:example_site_path) { root_path }
|
let(:example_site_path) { root_path }
|
||||||
let(:new_user) { create(:user, email: new_user_address, password: Gitlab::Password.test_default) }
|
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
|
||||||
|
|
||||||
subject { Notify.new_user_email(new_user.id) }
|
subject { Notify.new_user_email(new_user.id) }
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe Ci::DeletedObject, :aggregate_failures do
|
||||||
expect(deleted_artifact.file_store).to eq(artifact.file_store)
|
expect(deleted_artifact.file_store).to eq(artifact.file_store)
|
||||||
expect(deleted_artifact.store_dir).to eq(artifact.file.store_dir.to_s)
|
expect(deleted_artifact.store_dir).to eq(artifact.file.store_dir.to_s)
|
||||||
expect(deleted_artifact.file_identifier).to eq(artifact.file_identifier)
|
expect(deleted_artifact.file_identifier).to eq(artifact.file_identifier)
|
||||||
expect(deleted_artifact.pick_up_at).to eq(artifact.expire_at)
|
expect(deleted_artifact.pick_up_at).to be_like_time(artifact.expire_at)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -29,15 +29,25 @@ RSpec.describe Ci::Runner do
|
||||||
|
|
||||||
context 'when runner is not allowed to pick untagged jobs' do
|
context 'when runner is not allowed to pick untagged jobs' do
|
||||||
context 'when runner does not have tags' do
|
context 'when runner does not have tags' do
|
||||||
|
let(:runner) { build(:ci_runner, tag_list: [], run_untagged: false) }
|
||||||
|
|
||||||
|
it 'is not valid' do
|
||||||
|
expect(runner).to be_invalid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when runner has too many tags' do
|
||||||
|
let(:runner) { build(:ci_runner, tag_list: (1..::Ci::Runner::TAG_LIST_MAX_LENGTH + 1).map { |i| "tag#{i}" }, run_untagged: false) }
|
||||||
|
|
||||||
it 'is not valid' do
|
it 'is not valid' do
|
||||||
runner = build(:ci_runner, tag_list: [], run_untagged: false)
|
|
||||||
expect(runner).to be_invalid
|
expect(runner).to be_invalid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when runner has tags' do
|
context 'when runner has tags' do
|
||||||
|
let(:runner) { build(:ci_runner, tag_list: ['tag'], run_untagged: false) }
|
||||||
|
|
||||||
it 'is valid' do
|
it 'is valid' do
|
||||||
runner = build(:ci_runner, tag_list: ['tag'], run_untagged: false)
|
|
||||||
expect(runner).to be_valid
|
expect(runner).to be_valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue