Update upstream source from tag 'upstream/14.7.7+ds1'
Update to upstream version '14.7.7+ds1'
with Debian dir bdf762213c
This commit is contained in:
commit
0431e648d7
124 changed files with 1804 additions and 417 deletions
|
@ -13,6 +13,9 @@
|
|||
.if-jh: &if-jh
|
||||
if: '$CI_PROJECT_PATH =~ /^gitlab-(jh|cn)\/.*/'
|
||||
|
||||
.if-force-ci: &if-force-ci
|
||||
if: '$FORCE_GITLAB_CI'
|
||||
|
||||
.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'
|
||||
|
||||
|
@ -485,6 +488,7 @@
|
|||
- <<: *if-dot-com-gitlab-org-default-branch
|
||||
changes: *code-qa-patterns
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
- <<: *if-force-ci
|
||||
|
||||
.build-images:rules:build-assets-image:
|
||||
rules:
|
||||
|
@ -781,6 +785,9 @@
|
|||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
allow_failure: true
|
||||
- <<: *if-force-ci
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
.qa:rules:package-and-qa:feature-flags:
|
||||
rules:
|
||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -2,6 +2,53 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
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)
|
||||
|
||||
### 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'
|
||||
|
||||
# Two-factor authentication
|
||||
gem 'devise-two-factor', '~> 4.0.0'
|
||||
gem 'devise-two-factor', '~> 4.0.2'
|
||||
gem 'rqrcode-rails3', '~> 0.1.7'
|
||||
gem 'attr_encrypted', '~> 3.1.0'
|
||||
gem 'u2f', '~> 0.2.1'
|
||||
|
@ -153,7 +153,7 @@ gem 'html-pipeline', '~> 2.13.2'
|
|||
gem 'deckar01-task_list', '2.3.1'
|
||||
gem 'gitlab-markup', '~> 1.8.0'
|
||||
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 'RedCloth', '~> 4.3.2'
|
||||
gem 'rdoc', '~> 6.3.2'
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -200,7 +200,7 @@ GEM
|
|||
open4 (~> 1.3)
|
||||
coderay (1.1.3)
|
||||
colored2 (3.1.2)
|
||||
commonmarker (0.23.2)
|
||||
commonmarker (0.23.4)
|
||||
concurrent-ruby (1.1.9)
|
||||
connection_pool (2.2.2)
|
||||
contracts (0.11.0)
|
||||
|
@ -265,11 +265,11 @@ GEM
|
|||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (4.0.0)
|
||||
activesupport (< 6.2)
|
||||
devise-two-factor (4.0.2)
|
||||
activesupport (< 7.1)
|
||||
attr_encrypted (>= 1.3, < 4, != 2)
|
||||
devise (~> 4.0)
|
||||
railties (< 6.2)
|
||||
railties (< 7.1)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.4.4)
|
||||
diff_match_patch (0.1.0)
|
||||
|
@ -1421,7 +1421,7 @@ DEPENDENCIES
|
|||
capybara-screenshot (~> 1.0.22)
|
||||
carrierwave (~> 1.3)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
commonmarker (~> 0.23.2)
|
||||
commonmarker (~> 0.23.4)
|
||||
concurrent-ruby (~> 1.1)
|
||||
connection_pool (~> 2.0)
|
||||
countries (~> 3.0)
|
||||
|
@ -1435,7 +1435,7 @@ DEPENDENCIES
|
|||
derailed_benchmarks
|
||||
device_detector
|
||||
devise (~> 4.7.2)
|
||||
devise-two-factor (~> 4.0.0)
|
||||
devise-two-factor (~> 4.0.2)
|
||||
diff_match_patch (~> 0.1.0)
|
||||
diffy (~> 3.3)
|
||||
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
|
||||
export const DEFAULT_PARSE_RULE_PRIORITY = 50;
|
||||
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 initUserPopovers from '../../user_popovers';
|
||||
import highlightCurrentUser from './highlight_current_user';
|
||||
import { renderKroki } from './render_kroki';
|
||||
import renderMath from './render_math';
|
||||
import renderMermaid from './render_mermaid';
|
||||
import renderSandboxedMermaid from './render_sandboxed_mermaid';
|
||||
|
@ -13,6 +14,7 @@ import renderMetrics from './render_metrics';
|
|||
//
|
||||
$.fn.renderGFM = function renderGFM() {
|
||||
syntaxHighlight(this.find('.js-syntax-highlight').get());
|
||||
renderKroki(this.find('.js-render-kroki[hidden]').get());
|
||||
renderMath(this.find('.js-render-math'));
|
||||
if (gon.features?.sandboxedMermaid) {
|
||||
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 { darkModeEnabled } from '~/lib/utils/color_utils';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { unrestrictedPages } from './constants';
|
||||
|
||||
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
||||
// `js-render-mermaid` class.
|
||||
|
@ -30,24 +31,6 @@ let renderedMermaidBlocks = 0;
|
|||
|
||||
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) {
|
||||
let theme = 'neutral';
|
||||
|
||||
|
@ -163,7 +146,7 @@ function renderMermaids($els) {
|
|||
* up the entire thread and causing a DoS.
|
||||
*/
|
||||
if (
|
||||
!WHITELISTED_PAGES.includes(pageName) &&
|
||||
!unrestrictedPages.includes(pageName) &&
|
||||
((source && source.length > MAX_CHAR_LIMIT) ||
|
||||
renderedChars > MAX_CHAR_LIMIT ||
|
||||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from '~/lib/utils/url_utility';
|
||||
import { darkModeEnabled } from '~/lib/utils/color_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
|
||||
// `js-render-mermaid` class.
|
||||
|
@ -36,23 +37,6 @@ const BUFFER_IFRAME_HEIGHT = 10;
|
|||
const elsProcessingMap = new WeakMap();
|
||||
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) {
|
||||
/**
|
||||
* If source contains `&`, which means that it might
|
||||
|
@ -149,7 +133,7 @@ function renderMermaids($els) {
|
|||
* up the entire thread and causing a DoS.
|
||||
*/
|
||||
if (
|
||||
!PAGES_WITHOUT_RESTRICTIONS.includes(pageName) &&
|
||||
!unrestrictedPages.includes(pageName) &&
|
||||
((source && source.length > MAX_CHAR_LIMIT) ||
|
||||
renderedChars > MAX_CHAR_LIMIT ||
|
||||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
||||
import createFlash from '~/flash';
|
||||
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default () => {
|
||||
|
@ -8,14 +7,10 @@ export default () => {
|
|||
|
||||
Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')])
|
||||
.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({
|
||||
url: el.dataset.endpoint,
|
||||
dom_id: '#js-openapi-viewer',
|
||||
useUnsafeMarkdown: false,
|
||||
deepLinking: true,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
@ -81,7 +81,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
|||
def branch_to
|
||||
@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]
|
||||
@commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
|
||||
end
|
||||
|
|
|
@ -65,6 +65,8 @@ module Ci
|
|||
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
|
||||
|
||||
TAG_LIST_MAX_LENGTH = 50
|
||||
|
||||
has_many :builds
|
||||
has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :projects, through: :runner_projects
|
||||
|
@ -508,6 +510,11 @@ module Ci
|
|||
errors.add(:tags_list,
|
||||
'can not be empty when runner is not allowed to pick untagged jobs')
|
||||
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
|
||||
|
||||
# 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 ChronicDurationAttribute
|
||||
include RunnerTokenExpirationInterval
|
||||
include RunnersTokenPrefixable
|
||||
|
||||
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
|
||||
'Group'
|
||||
end
|
||||
|
@ -124,7 +120,7 @@ class Group < Namespace
|
|||
|
||||
add_authentication_token_field :runners_token,
|
||||
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_destroy :post_destroy_hook
|
||||
|
@ -678,10 +674,6 @@ class Group < Namespace
|
|||
ensure_runners_token!
|
||||
end
|
||||
|
||||
def runners_token_prefix
|
||||
Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : ''
|
||||
end
|
||||
|
||||
override :format_runners_token
|
||||
def format_runners_token(token)
|
||||
"#{runners_token_prefix}#{token}"
|
||||
|
|
|
@ -61,12 +61,9 @@ module Integrations
|
|||
def execute(data)
|
||||
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_restriction = restrict_to_branch.to_s
|
||||
if branch_restriction.present? && branch_restriction.index(branch).nil?
|
||||
return
|
||||
end
|
||||
|
||||
return unless branch_allowed?(branch)
|
||||
|
||||
user = data[:user_name]
|
||||
project_name = project.full_name
|
||||
|
@ -105,5 +102,13 @@ module Integrations
|
|||
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
|
||||
|
|
|
@ -38,6 +38,7 @@ class Project < ApplicationRecord
|
|||
include GitlabRoutingHelper
|
||||
include BulkMemberAccessLoad
|
||||
include RunnerTokenExpirationInterval
|
||||
include RunnersTokenPrefixable
|
||||
|
||||
extend Gitlab::Cache::RequestCache
|
||||
extend Gitlab::Utils::Override
|
||||
|
@ -74,11 +75,6 @@ class Project < ApplicationRecord
|
|||
|
||||
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
|
||||
|
||||
default_value_for :packages_enabled, true
|
||||
|
@ -101,7 +97,7 @@ class Project < ApplicationRecord
|
|||
|
||||
add_authentication_token_field :runners_token,
|
||||
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? }
|
||||
|
||||
|
@ -1847,10 +1843,6 @@ class Project < ApplicationRecord
|
|||
ensure_runners_token!
|
||||
end
|
||||
|
||||
def runners_token_prefix
|
||||
Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : ''
|
||||
end
|
||||
|
||||
override :format_runners_token
|
||||
def format_runners_token(token)
|
||||
"#{runners_token_prefix}#{token}"
|
||||
|
|
|
@ -9,10 +9,20 @@ module Releases
|
|||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
|
||||
# Regex modified to prevent catastrophic backtracking
|
||||
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 :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) }
|
||||
|
||||
|
|
|
@ -46,11 +46,11 @@ class SshHostKey
|
|||
.select(&:valid?)
|
||||
end
|
||||
|
||||
attr_reader :project, :url, :compare_host_keys
|
||||
attr_reader :project, :url, :ip, :compare_host_keys
|
||||
|
||||
def initialize(project:, url:, compare_host_keys: nil)
|
||||
@project = project
|
||||
@url = normalize_url(url)
|
||||
@url, @ip = normalize_url(url)
|
||||
@compare_host_keys = compare_host_keys
|
||||
end
|
||||
|
||||
|
@ -90,9 +90,11 @@ class SshHostKey
|
|||
end
|
||||
|
||||
def calculate_reactive_cache
|
||||
input = [ip, url.hostname].compact.join(' ')
|
||||
|
||||
known_hosts, errors, status =
|
||||
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
|
||||
|
||||
[
|
||||
|
@ -127,11 +129,31 @@ class SshHostKey
|
|||
end
|
||||
|
||||
def normalize_url(url)
|
||||
full_url = ::Addressable::URI.parse(url)
|
||||
raise ArgumentError, "Invalid URL" unless full_url&.scheme == 'ssh'
|
||||
url, real_hostname = Gitlab::UrlBlocker.validate!(
|
||||
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}")
|
||||
rescue Addressable::URI::InvalidURIError
|
||||
# When DNS rebinding protection is required, the hostname is replaced by the
|
||||
# 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"
|
||||
end
|
||||
|
||||
def allow_local_requests?
|
||||
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -895,6 +895,23 @@ class User < ApplicationRecord
|
|||
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
|
||||
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!
|
||||
super if ::Gitlab::Database.read_write?
|
||||
end
|
||||
|
|
|
@ -234,7 +234,6 @@ class ProjectPolicy < BasePolicy
|
|||
|
||||
rule { can?(:guest_access) }.policy do
|
||||
enable :read_project
|
||||
enable :create_merge_request_in
|
||||
enable :read_issue_board
|
||||
enable :read_issue_board_list
|
||||
enable :read_wiki
|
||||
|
@ -487,7 +486,7 @@ class ProjectPolicy < BasePolicy
|
|||
prevent(*create_read_update_admin_destroy(:issue_board_list))
|
||||
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_from
|
||||
prevent(*create_read_update_admin_destroy(:merge_request))
|
||||
|
@ -589,13 +588,14 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_cycle_analytics
|
||||
enable :read_pages_content
|
||||
enable :read_analytics
|
||||
enable :read_ci_cd_analytics
|
||||
enable :read_insights
|
||||
|
||||
# NOTE: may be overridden by IssuePolicy
|
||||
enable :read_issue
|
||||
end
|
||||
|
||||
rule { can?(:public_access) & public_builds }.enable :read_ci_cd_analytics
|
||||
|
||||
rule { public_builds }.policy do
|
||||
enable :read_build
|
||||
end
|
||||
|
@ -653,6 +653,10 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_security_configuration
|
||||
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.
|
||||
rule { design_management_disabled }.policy do
|
||||
prevent :read_design
|
||||
|
|
|
@ -12,7 +12,7 @@ module Ci
|
|||
|
||||
def destroy_records
|
||||
@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)
|
||||
updates = result[:statistics_updates]
|
||||
|
||||
|
|
|
@ -17,13 +17,18 @@ module Ci
|
|||
# +pick_up_at+:: When to pick up for deletion of files
|
||||
# Returns:
|
||||
# +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
|
||||
@pick_up_at = pick_up_at
|
||||
@fix_expire_at = fix_expire_at
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
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?
|
||||
|
||||
destroy_related_records(@job_artifacts)
|
||||
|
@ -89,6 +94,55 @@ module Ci
|
|||
@job_artifacts.sum { |artifact| artifact.try(:size) || 0 }
|
||||
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
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348786
|
|||
milestone: '14.6'
|
||||
type: development
|
||||
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,
|
||||
email: FFaker::Internet.email,
|
||||
confirmed_at: DateTime.now,
|
||||
password: Gitlab::Password.test_default
|
||||
password: '12345678'
|
||||
)
|
||||
|
||||
::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddUniqueIndexToVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
NAME_URL_INDEX_NAME = 'finding_link_name_url_idx'
|
||||
URL_INDEX_NAME = 'finding_link_url_idx'
|
||||
# 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.
|
||||
|
||||
def up
|
||||
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
|
||||
# no op
|
||||
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
|
||||
# no op
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
|
||||
BATCH_SIZE = 50_000
|
||||
MIGRATION = 'RemoveVulnerabilityFindingLinks'
|
||||
|
||||
disable_ddl_transaction!
|
||||
# This migration has been moved to a TRUNCATE in 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.
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('vulnerability_finding_links'),
|
||||
MIGRATION,
|
||||
2.minutes,
|
||||
batch_size: BATCH_SIZE
|
||||
)
|
||||
# no op
|
||||
end
|
||||
|
||||
def down
|
||||
# no ops
|
||||
# no op
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveVulnerabilityFindingLinksAgain < Gitlab::Database::Migration[1.0]
|
||||
BATCH_SIZE = 50_000
|
||||
MIGRATION = 'RemoveVulnerabilityFindingLinks'
|
||||
|
||||
disable_ddl_transaction!
|
||||
# This migration has been moved to a TRUNCATE in 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.
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('vulnerability_finding_links'),
|
||||
MIGRATION,
|
||||
2.minutes,
|
||||
batch_size: BATCH_SIZE
|
||||
)
|
||||
# no op
|
||||
end
|
||||
|
||||
def down
|
||||
# no ops
|
||||
# no op
|
||||
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 Filter
|
||||
# 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
|
||||
MAX_CHARACTER_LIMIT = 2000
|
||||
|
||||
def call
|
||||
return doc unless settings.kroki_enabled
|
||||
|
||||
|
@ -21,7 +23,12 @@ module Banzai
|
|||
diagram_format = "svg"
|
||||
doc.xpath(xpath).each do |node|
|
||||
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)
|
||||
end
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ module Banzai
|
|||
retry
|
||||
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}"
|
||||
lang="#{language}"
|
||||
|
|
|
@ -65,16 +65,15 @@ module Banzai
|
|||
#
|
||||
def redacted_node_content(node)
|
||||
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
|
||||
# it's originally a link pattern. We shouldn't return a plain text href.
|
||||
original_link =
|
||||
if link_reference == 'true'
|
||||
if node.attr('data-link-reference') == 'true'
|
||||
href = node.attr('href')
|
||||
content = original_content
|
||||
|
||||
%(<a href="#{href}">#{content}</a>)
|
||||
%(<a href="#{href}">#{original_content}</a>)
|
||||
end
|
||||
|
||||
# The reference should be replaced by the original link's content,
|
||||
|
|
|
@ -230,8 +230,8 @@ module Gitlab
|
|||
name: name.strip.presence || valid_username,
|
||||
username: valid_username,
|
||||
email: email,
|
||||
password: Gitlab::Password.test_default(21),
|
||||
password_confirmation: Gitlab::Password.test_default(21),
|
||||
password: auth_hash.password,
|
||||
password_confirmation: auth_hash.password,
|
||||
password_automatically_set: true
|
||||
}
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
attr_writer :expandset, :execution_deadline, :logger
|
||||
|
|
|
@ -37,7 +37,7 @@ module Gitlab
|
|||
def validate_content!
|
||||
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
|
||||
|
||||
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!
|
||||
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?
|
||||
errors.push("Included file `#{location}` does not have YAML extension!")
|
||||
errors.push("Included file `#{masked_location}` does not have YAML extension!")
|
||||
end
|
||||
end
|
||||
|
||||
def validate_content!
|
||||
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
|
||||
|
||||
def validate_hash!
|
||||
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
|
||||
|
||||
|
@ -104,6 +104,12 @@ module Gitlab
|
|||
def expand_context_attrs
|
||||
{}
|
||||
end
|
||||
|
||||
def masked_location
|
||||
strong_memoize(:masked_location) do
|
||||
context.mask_variables_from(location)
|
||||
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!
|
||||
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?
|
||||
errors.push("Local file `#{location}` does not exist!")
|
||||
errors.push("Local file `#{masked_location}` does not exist!")
|
||||
elsif content.blank?
|
||||
errors.push("Local file `#{location}` is empty!")
|
||||
errors.push("Local file `#{masked_location}` is empty!")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -35,9 +35,9 @@ module Gitlab
|
|||
elsif sha.nil?
|
||||
errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!")
|
||||
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?
|
||||
errors.push("Project `#{project_name}` file `#{location}` is empty!")
|
||||
errors.push("Project `#{project_name}` file `#{masked_location}` is empty!")
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
@ -32,17 +32,17 @@ module Gitlab
|
|||
begin
|
||||
response = Gitlab::HTTP.get(location)
|
||||
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
|
||||
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
|
||||
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
|
||||
errors.push("Remote file could not be fetched because #{e}!")
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
response.body if errors.none?
|
||||
|
|
|
@ -26,7 +26,7 @@ module Gitlab
|
|||
super
|
||||
|
||||
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
|
||||
|
||||
|
|
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)
|
||||
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
|
||||
end
|
||||
|
@ -181,6 +181,10 @@ module Gitlab
|
|||
def expand(data)
|
||||
ExpandVariables.expand(data, -> { context.variables_hash })
|
||||
end
|
||||
|
||||
def masked_location(location)
|
||||
context.mask_variables_from(location)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -99,6 +99,9 @@ module Gitlab
|
|||
# ^--+--+- components of hashed storage project path
|
||||
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
|
||||
cmd += %w[-type d]
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ module Gitlab
|
|||
PROCESSORS = [
|
||||
::Gitlab::ErrorTracking::Processor::SidekiqProcessor,
|
||||
::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor,
|
||||
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor
|
||||
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor,
|
||||
::Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor
|
||||
].freeze
|
||||
|
||||
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 Processor
|
||||
module GrpcErrorProcessor
|
||||
extend Gitlab::ErrorTracking::Processor::Concerns::ProcessesExceptions
|
||||
|
||||
DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)')
|
||||
|
||||
class << self
|
||||
|
@ -18,10 +20,7 @@ module Gitlab
|
|||
# only the first one since that's what is used for grouping.
|
||||
def process_first_exception_value(event)
|
||||
# Better in new version, will be event.exception.values
|
||||
exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values
|
||||
|
||||
return unless exceptions.is_a?(Array)
|
||||
|
||||
exceptions = extract_exceptions_from(event)
|
||||
exception = exceptions.first
|
||||
|
||||
return unless valid_exception?(exception)
|
||||
|
@ -68,15 +67,6 @@ module Gitlab
|
|||
|
||||
[match[1], match[2]]
|
||||
end
|
||||
|
||||
def valid_exception?(exception)
|
||||
case exception
|
||||
when Raven::SingleExceptionInterface
|
||||
exception&.value
|
||||
else
|
||||
false
|
||||
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.
|
||||
payload.merge!(
|
||||
'exception.class' => exception.class.name,
|
||||
'exception.message' => exception.message
|
||||
'exception.message' => sanitize_message(exception)
|
||||
)
|
||||
|
||||
if exception.backtrace
|
||||
|
@ -38,6 +38,10 @@ module Gitlab
|
|||
rescue PgQuery::ParseError
|
||||
sql
|
||||
end
|
||||
|
||||
def sanitize_message(exception)
|
||||
Gitlab::Sanitizers::ExceptionMessage.clean(exception.class.name, exception.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,9 +19,8 @@ module Gitlab
|
|||
@exported_members.inject(missing_keys_tracking_hash) do |hash, member|
|
||||
if member['user']
|
||||
old_user_id = member['user']['id']
|
||||
old_user_email = member.dig('user', 'public_email') || member.dig('user', 'email')
|
||||
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 && add_team_member(member, existing_user)
|
||||
existing_user_id = existing_users_email_map[get_email(member)]
|
||||
hash[old_user_id] = existing_user_id if existing_user_id && add_team_member(member, existing_user_id)
|
||||
else
|
||||
add_team_member(member)
|
||||
end
|
||||
|
@ -72,11 +71,45 @@ module Gitlab
|
|||
member&.user == @user && member.access_level >= highest_access_level
|
||||
end
|
||||
|
||||
def add_team_member(member, existing_user = nil)
|
||||
return true if existing_user && @importable.members.exists?(user_id: existing_user.id)
|
||||
# Returns {email => user_id} hash where user_id is an ID at current instance
|
||||
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)
|
||||
if existing_user_id
|
||||
member_hash.delete('user')
|
||||
member_hash['user_id'] = existing_user_id
|
||||
end
|
||||
|
||||
member = relation_class.create(member_hash)
|
||||
|
||||
|
@ -92,11 +125,19 @@ module Gitlab
|
|||
end
|
||||
|
||||
def member_hash(member)
|
||||
parsed_hash(member).merge(
|
||||
result = parsed_hash(member).merge(
|
||||
'source_id' => @importable.id,
|
||||
'importing' => true,
|
||||
'access_level' => [member['access_level'], highest_access_level].min
|
||||
).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
|
||||
|
||||
def parsed_hash(member)
|
||||
|
@ -104,14 +145,6 @@ module Gitlab
|
|||
relation_class: relation_class)
|
||||
end
|
||||
|
||||
def find_user_query(email)
|
||||
user_arel[:email].eq(email)
|
||||
end
|
||||
|
||||
def user_arel
|
||||
@user_arel ||= User.arel_table
|
||||
end
|
||||
|
||||
def relation_class
|
||||
case @importable
|
||||
when ::Project
|
||||
|
@ -143,7 +176,7 @@ module Gitlab
|
|||
|
||||
def base_log_params(member_hash)
|
||||
{
|
||||
user_id: member_hash['user']&.id,
|
||||
user_id: member_hash['user_id'],
|
||||
access_level: member_hash['access_level'],
|
||||
importable_type: @importable.class.to_s,
|
||||
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,
|
||||
email: FFaker::Internet.email,
|
||||
confirmed_at: DateTime.now,
|
||||
password: Gitlab::Password.test_default
|
||||
password: Devise.friendly_token
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -12625,6 +12625,9 @@ msgstr ""
|
|||
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Display"
|
||||
msgstr ""
|
||||
|
||||
msgid "Display alerts from all configured monitoring tools."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
"sql.js": "^0.4.0",
|
||||
"string-hash": "1.1.3",
|
||||
"style-loader": "^2.0.0",
|
||||
"swagger-ui-dist": "^3.52.3",
|
||||
"swagger-ui-dist": "4.8.0",
|
||||
"three": "^0.84.0",
|
||||
"three-orbit-controls": "^82.1.0",
|
||||
"three-stl-loader": "^1.0.4",
|
||||
|
|
|
@ -6,6 +6,10 @@ module Gitlab
|
|||
class Subscription < Chemlab::Page
|
||||
path '/admin/subscription'
|
||||
|
||||
div :subscription_details
|
||||
text_field :activation_code
|
||||
button :activate
|
||||
label :terms_of_services, text: /I agree that/
|
||||
p :plan
|
||||
p :started
|
||||
p :name
|
||||
|
@ -16,6 +20,33 @@ module Gitlab
|
|||
h2 :users_in_subscription
|
||||
h2 :users_over_subscription
|
||||
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
|
||||
|
|
|
@ -4,6 +4,112 @@ module Gitlab
|
|||
module Page
|
||||
module Admin
|
||||
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+
|
||||
# @return [String] The text content or value of +plan+
|
||||
def plan
|
||||
|
|
|
@ -434,6 +434,10 @@ module QA
|
|||
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
|
||||
end
|
||||
|
||||
def ee_activation_code
|
||||
ENV['QA_EE_ACTIVATION_CODE']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remote_grid_credentials
|
||||
|
|
|
@ -64,7 +64,9 @@ module QA
|
|||
Page::Profile::Accounts::Show.perform do |show|
|
||||
show.delete_account(user.password)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
context 'when the new password does not match the password confirmation' do
|
||||
let(:password) { Gitlab::Password.test_default }
|
||||
let(:password_confirmation) { "not" + Gitlab::Password.test_default }
|
||||
let(:password) { 'some_password' }
|
||||
let(:password_confirmation) { 'not_same_as_password' }
|
||||
|
||||
it 'shows the edit page again' do
|
||||
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
|
||||
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,
|
||||
params: {
|
||||
|
@ -199,8 +200,25 @@ RSpec.describe Projects::MergeRequests::CreationsController do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
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
|
||||
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,
|
||||
params: {
|
||||
|
|
|
@ -177,6 +177,7 @@ RSpec.describe Projects::MirrorsController do
|
|||
INVALID
|
||||
git@example.com:foo/bar.git
|
||||
ssh://git@example.com:foo/bar.git
|
||||
ssh://127.0.0.1/foo/bar.git
|
||||
].each do |url|
|
||||
it "returns an error with a 400 response for URL #{url.inspect}" do
|
||||
do_get(project, url)
|
||||
|
|
|
@ -499,7 +499,7 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
|
||||
it 'succeeds if password is confirmed' do
|
||||
post :destroy, params: { password: Gitlab::Password.test_default }
|
||||
post :destroy, params: { password: '12345678' }
|
||||
|
||||
expect_success
|
||||
end
|
||||
|
@ -540,7 +540,7 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
|
||||
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'))
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ FactoryBot.define do
|
|||
file_format { :zip }
|
||||
|
||||
trait :expired do
|
||||
expire_at { Date.yesterday }
|
||||
expire_at { Time.current.yesterday.change(minute: 9) }
|
||||
end
|
||||
|
||||
trait :locked do
|
||||
|
|
|
@ -5,7 +5,7 @@ FactoryBot.define do
|
|||
email { generate(:email) }
|
||||
name { generate(:name) }
|
||||
username { generate(:username) }
|
||||
password { Gitlab::Password.test_default }
|
||||
password { "12345678" }
|
||||
role { 'software_developer' }
|
||||
confirmed_at { Time.now }
|
||||
confirmation_token { nil }
|
||||
|
@ -23,6 +23,10 @@ FactoryBot.define do
|
|||
after(:build) { |user, _| user.block! }
|
||||
end
|
||||
|
||||
trait :disallowed_password do
|
||||
password { User::DISALLOWED_PASSWORDS.first }
|
||||
end
|
||||
|
||||
trait :blocked_pending_approval do
|
||||
after(:build) { |user, _| user.block_pending_approval! }
|
||||
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))
|
||||
|
||||
fill_in 'New password', with: "new" + Gitlab::Password.test_default
|
||||
fill_in 'Confirm new password', with: "new" + Gitlab::Password.test_default
|
||||
fill_in 'New password', with: 'hello1234'
|
||||
fill_in 'Confirm new password', with: 'hello1234'
|
||||
|
||||
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
|
||||
click_button 'Delete account'
|
||||
|
||||
fill_in 'password', with: Gitlab::Password.test_default
|
||||
fill_in 'password', with: '12345678'
|
||||
|
||||
page.within '.modal' do
|
||||
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
|
||||
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
|
||||
expect(page).to have_content('Password was successfully updated. Please sign in again.')
|
||||
|
@ -79,7 +79,7 @@ RSpec.describe 'Profile > Password' do
|
|||
end
|
||||
|
||||
context 'Change password' do
|
||||
let(:new_password) { "new" + Gitlab::Password.test_default }
|
||||
let(:new_password) { '22233344' }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
@ -170,8 +170,8 @@ RSpec.describe 'Profile > Password' do
|
|||
expect(current_path).to eq new_profile_password_path
|
||||
|
||||
fill_in :user_password, with: user.password
|
||||
fill_in :user_new_password, with: Gitlab::Password.test_default
|
||||
fill_in :user_password_confirmation, with: Gitlab::Password.test_default
|
||||
fill_in :user_new_password, with: '12345678'
|
||||
fill_in :user_password_confirmation, with: '12345678'
|
||||
click_button 'Set new password'
|
||||
|
||||
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
|
||||
# The session key only gets created after a post
|
||||
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'
|
||||
|
||||
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(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_confirmation', with: Gitlab::Password.test_default
|
||||
fill_in 'user_password', with: 'password'
|
||||
fill_in 'user_password_confirmation', with: 'password'
|
||||
click_button 'Change your password'
|
||||
|
||||
expect(current_path).to eq new_user_session_path
|
||||
expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))
|
||||
|
||||
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'
|
||||
|
||||
expect_single_session_with_authenticated_ttl
|
||||
|
@ -150,6 +150,27 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
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
|
||||
it 'disallows login' do
|
||||
expect(authentication_metrics)
|
||||
|
@ -210,7 +231,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
end
|
||||
|
||||
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)
|
||||
|
||||
|
@ -447,7 +468,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
visit new_user_session_path
|
||||
|
||||
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'
|
||||
|
||||
expect(current_path).to eq(new_profile_password_path)
|
||||
|
@ -456,7 +477,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
end
|
||||
|
||||
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
|
||||
expect(authentication_metrics)
|
||||
|
@ -767,7 +788,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
visit new_user_session_path
|
||||
|
||||
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'
|
||||
|
||||
|
@ -788,7 +809,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
visit new_user_session_path
|
||||
|
||||
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'
|
||||
|
||||
|
@ -809,7 +830,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
visit new_user_session_path
|
||||
|
||||
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'
|
||||
|
||||
|
@ -844,7 +865,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
visit new_user_session_path
|
||||
|
||||
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'
|
||||
|
||||
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
|
||||
|
||||
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'
|
||||
|
||||
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)
|
||||
|
||||
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_password_confirmation', with: '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 }
|
||||
|
||||
before_all do
|
||||
before do
|
||||
project.add_guest(guest)
|
||||
project.add_reporter(reporter)
|
||||
end
|
||||
|
@ -20,13 +20,8 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
|
|||
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
|
||||
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 'returns the pipelines statistics for a given project' do
|
||||
shared_examples 'returns the pipelines statistics for a given project' do
|
||||
it do
|
||||
result = resolve_statistics(project, {})
|
||||
expect(result.keys).to contain_exactly(
|
||||
:week_pipelines_labels,
|
||||
|
@ -42,15 +37,68 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
|
|||
:pipeline_times_values
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not have access to the CI/CD analytics data' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
it 'returns nil' do
|
||||
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
|
||||
let(:current_user) { guest }
|
||||
|
||||
it_behaves_like 'it returns nils'
|
||||
end
|
||||
|
||||
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
|
||||
|
|
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")
|
||||
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
|
||||
|
||||
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")
|
||||
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
|
||||
|
||||
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>'
|
||||
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
|
||||
|
|
|
@ -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>')
|
||||
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
|
||||
|
||||
context "when Rouge lexing fails" do
|
||||
|
|
|
@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceRedactor do
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -477,7 +477,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
:user,
|
||||
:blocked,
|
||||
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'))
|
||||
|
@ -486,7 +486,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
|
||||
context 'when 2fa is enabled globally' 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
|
||||
|
||||
before do
|
||||
|
@ -510,7 +510,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
|
||||
context 'when 2fa is enabled personally' 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
|
||||
|
||||
it 'fails' do
|
||||
|
@ -523,7 +523,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
user = create(
|
||||
: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'))
|
||||
|
@ -534,7 +534,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
user = create(
|
||||
:user,
|
||||
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'))
|
||||
|
@ -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
|
||||
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]) }
|
||||
|
||||
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
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
@ -816,7 +816,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
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(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
|
||||
|
||||
|
|
|
@ -127,6 +127,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
|
|||
let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) }
|
||||
|
||||
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
|
||||
allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
|
||||
allow(reader).to receive(:read).and_return('')
|
||||
|
@ -134,7 +140,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
|
|||
end
|
||||
|
||||
let(:expected_error) do
|
||||
'File `generated.yml` is empty!'
|
||||
'File `xxxxxxxxxxxx/generated.yml` is empty!'
|
||||
end
|
||||
|
||||
it_behaves_like 'is invalid'
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
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(:test_class) do
|
||||
|
@ -76,7 +77,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
|||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:sha) { '12345' }
|
||||
let(:variables) { project.predefined_variables.to_runner_variables }
|
||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||
let(:params) { { local: location } }
|
||||
let(:local_file) { described_class.new(params, context) }
|
||||
|
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
|||
sha: sha,
|
||||
user: user,
|
||||
parent_pipeline: parent_pipeline,
|
||||
variables: project.predefined_variables.to_runner_variables
|
||||
variables: variables
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -66,7 +67,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
|||
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' }
|
||||
|
||||
it 'returns false' do
|
||||
|
@ -74,13 +75,23 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when is not a yaml file' do
|
||||
context 'when it is not a yaml file' do
|
||||
let(:location) { '/config/application.rb' }
|
||||
|
||||
it 'returns false' do
|
||||
expect(local_file.valid?).to be_falsy
|
||||
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
|
||||
|
||||
describe '#content' do
|
||||
|
@ -116,10 +127,11 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
|||
let(:parent_pipeline) { double(:parent_pipeline) }
|
||||
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
|
||||
let(:project_file) { described_class.new(params, context) }
|
||||
let(:variables) { project.predefined_variables.to_runner_variables }
|
||||
|
||||
let(:context_params) do
|
||||
{
|
||||
|
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
|||
sha: '12345',
|
||||
user: context_user,
|
||||
parent_pipeline: parent_pipeline,
|
||||
variables: project.predefined_variables.to_runner_variables
|
||||
variables: variables
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -108,18 +109,19 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
|||
|
||||
context 'when an empty file is used' do
|
||||
let(:params) do
|
||||
{ project: project.full_path, file: '/file.yml' }
|
||||
{ project: project.full_path, file: '/secret_file.yml' }
|
||||
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 }
|
||||
|
||||
before do
|
||||
stub_project_blob(root_ref_sha, '/file.yml') { '' }
|
||||
stub_project_blob(root_ref_sha, '/secret_file.yml') { '' }
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
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
|
||||
|
||||
|
@ -135,13 +137,15 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
|
|||
end
|
||||
|
||||
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
|
||||
{ project: project.full_path, file: '/invalid-file.yml' }
|
||||
{ project: project.full_path, file: '/secret-invalid-file.yml' }
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
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
|
||||
|
||||
|
|
|
@ -5,11 +5,12 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
||||
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(:params) { { remote: location } }
|
||||
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
|
||||
<<~HEREDOC
|
||||
before_script:
|
||||
|
@ -144,10 +145,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
|||
subject { remote_file.error_message }
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -157,7 +158,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
@ -167,7 +168,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
@ -177,7 +178,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -54,11 +54,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template do
|
|||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
|
|||
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(: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(:file_content) do
|
||||
|
@ -92,13 +93,16 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
|
|||
end
|
||||
|
||||
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
|
||||
{ include: { 'local' => local_file, 'remote' => remote_url },
|
||||
image: 'ruby:2.7' }
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -34,10 +34,33 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
|
|||
cleanup.run!
|
||||
end
|
||||
|
||||
it 'finds artifacts on disk' do
|
||||
it 'finds job artifacts on disk' do
|
||||
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!
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -22,6 +22,14 @@ RSpec.describe Gitlab::ExceptionLogFormatter do
|
|||
expect(payload['exception.sql']).to be_nil
|
||||
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
|
||||
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,
|
||||
"created_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_token" => nil,
|
||||
"invite_accepted_at" => nil,
|
||||
|
@ -38,10 +38,24 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
"notification_level" => 3,
|
||||
"created_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_token" => 'token',
|
||||
"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
|
||||
|
||||
|
@ -68,7 +82,32 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil
|
||||
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)
|
||||
.to receive(:create)
|
||||
.once
|
||||
.with(hash_including('user_id' => user2.id, 'created_by_id' => nil))
|
||||
.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
|
||||
|
@ -99,7 +138,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
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(nil))).once
|
||||
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(nil))).twice
|
||||
|
||||
members_mapper.map
|
||||
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
|
||||
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) }
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe Ci::DeletedObject, :aggregate_failures do
|
|||
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.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
|
||||
|
||||
|
|
|
@ -29,15 +29,25 @@ RSpec.describe Ci::Runner do
|
|||
|
||||
context 'when runner is not allowed to pick untagged jobs' 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
|
||||
runner = build(:ci_runner, tag_list: [], run_untagged: false)
|
||||
expect(runner).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner has tags' do
|
||||
let(:runner) { build(:ci_runner, tag_list: ['tag'], run_untagged: false) }
|
||||
|
||||
it 'is valid' do
|
||||
runner = build(:ci_runner, tag_list: ['tag'], run_untagged: false)
|
||||
expect(runner).to be_valid
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue