New upstream version 14.7.7+ds1

This commit is contained in:
Pirate Praveen 2022-04-01 21:47:47 +05:30
parent 0d67b01f61
commit c611f599cf
124 changed files with 1804 additions and 417 deletions

View file

@ -13,6 +13,9 @@
.if-jh: &if-jh .if-jh: &if-jh
if: '$CI_PROJECT_PATH =~ /^gitlab-(jh|cn)\/.*/' if: '$CI_PROJECT_PATH =~ /^gitlab-(jh|cn)\/.*/'
.if-force-ci: &if-force-ci
if: '$FORCE_GITLAB_CI'
.if-default-refs: &if-default-refs .if-default-refs: &if-default-refs
if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI' if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
@ -485,6 +488,7 @@
- <<: *if-dot-com-gitlab-org-default-branch - <<: *if-dot-com-gitlab-org-default-branch
changes: *code-qa-patterns changes: *code-qa-patterns
- <<: *if-dot-com-gitlab-org-schedule - <<: *if-dot-com-gitlab-org-schedule
- <<: *if-force-ci
.build-images:rules:build-assets-image: .build-images:rules:build-assets-image:
rules: rules:
@ -781,6 +785,9 @@
allow_failure: true allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule - <<: *if-dot-com-gitlab-org-schedule
allow_failure: true allow_failure: true
- <<: *if-force-ci
when: manual
allow_failure: true
.qa:rules:package-and-qa:feature-flags: .qa:rules:package-and-qa:feature-flags:
rules: rules:

View file

@ -2,6 +2,53 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 14.7.7 (2022-03-31)
### Security (21 changes)
- [Update to commonmarker 0.23.4](gitlab-org/security/gitlab@eb4b231173c86901f93b5b7781716b1f7706dad1) ([merge request](gitlab-org/security/gitlab!2283))
- [Revert merge request approval groups behavior](gitlab-org/security/gitlab@08e3ecced649f6ad241db6de7050b1502f7bef21) ([merge request](gitlab-org/security/gitlab!2333))
- [Disallow login if password matches a fixed list](gitlab-org/security/gitlab@02a69ab32da1ac67d855de3ee388d0bd2bb6586e) ([merge request](gitlab-org/security/gitlab!2359))
- [Update devise-two-factor to 4.0.2](gitlab-org/security/gitlab@c9fde96c7780f5b883cd1ac63d7ac3d5f4d78dc6) ([merge request](gitlab-org/security/gitlab!2351))
- [Limit the number of tags associated with a CI runner](gitlab-org/security/gitlab@00124d5f8ba0d7437d1f6f19b029754bf481185b) ([merge request](gitlab-org/security/gitlab!2305))
- [GitLab Pages Security Updates for 14.9](gitlab-org/security/gitlab@d335917e233658fa9d4452053469c3582ef38368) ([merge request](gitlab-org/security/gitlab!2325))
- [Upgrade swagger-ui dependency](gitlab-org/security/gitlab@7a8ce32f70fd0338817705651ee0dbe0a277d5f1) ([merge request](gitlab-org/security/gitlab!2338))
- [Modify release link format check to avoid regex if string is too long](gitlab-org/security/gitlab@e18dc2be245bca7e192c8536d1ba7de2ad798c43) ([merge request](gitlab-org/security/gitlab!2244))
- [Masks variables in error messages](gitlab-org/security/gitlab@1706c5cf9b939a6ab0682db7b8945feb851a3f8b) ([merge request](gitlab-org/security/gitlab!2292))
- [Escape user provided string to prevent XSS](gitlab-org/security/gitlab@c57edf9ab52810d455e41d71bad4e4d12c098cad) ([merge request](gitlab-org/security/gitlab!2315))
- [Monkey patch of RDoc to prevent Ruby segfault](gitlab-org/security/gitlab@f9e5597d1864d03bf1f0103787becbc84886968d) ([merge request](gitlab-org/security/gitlab!2233))
- [Project import maps members' created_by_id users based on source user ID](gitlab-org/security/gitlab@3ea1e477e0596f15e040f42b59fa86953d057128) ([merge request](gitlab-org/security/gitlab!2239))
- [Redact InvalidURIError error messages](gitlab-org/security/gitlab@a42ede835e32f44b68c1affe78a7ee48332bb30a) ([merge request](gitlab-org/security/gitlab!2297))
- [Fix access for approval rules API](gitlab-org/security/gitlab@b8c3997763d1e041dc2b82e464a99a5b2f15a798) ([merge request](gitlab-org/security/gitlab!2324))
- [Fix kroki exploit](gitlab-org/security/gitlab@ad123e33510103af4fb00378ef1fc8dae4cacb21) ([merge request](gitlab-org/security/gitlab!2278))
- [Fix blind SSRF when looking up SSH host keys for mirroring](gitlab-org/security/gitlab@0209f44cb4876f0a9ef13d4c8875a95a0cda1e2f) ([merge request](gitlab-org/security/gitlab!2311))
- [Escape original content in reference redactor](gitlab-org/security/gitlab@f63861d8fe7b2b8d161162063e7995782cbfada8) ([merge request](gitlab-org/security/gitlab!2319))
- [Security fix for CI/CD analytics visibility](gitlab-org/security/gitlab@fea6a4ff80862f9dba493405d03d82cf129e8854) ([merge request](gitlab-org/security/gitlab!2274))
- [Latest commit exposed through fork of a private project](gitlab-org/security/gitlab@b573cea38cdce020e5f25fb9de60e0e506c87a9b) ([merge request](gitlab-org/security/gitlab!2272))
- [Fix Asana integration restricted branch filter](gitlab-org/security/gitlab@56e2d9ae3de4f587d2c8a5aa111c2922553d6b7b) ([merge request](gitlab-org/security/gitlab!2214))
- [Revert "JH need more complex passwords"](gitlab-org/security/gitlab@2419522b02700ce98e0c4d6e7bfd4d28b6464506) ([merge request](gitlab-org/security/gitlab!2354))
## 14.7.6 (2022-03-24)
### Added (1 change)
- [Detect and fix artifacts with backfilled expire_at](gitlab-org/gitlab@92938348905581798fa669051a61c107d082d908) ([merge request](gitlab-org/gitlab!83054))
### Changed (2 changes)
- [Enable feature flags to resume artifact removal on self-managed](gitlab-org/gitlab@45e4aba7099e0b6963674d192dc87edfe9ff8cdb) ([merge request](gitlab-org/gitlab!83054))
- [Remove runners token prefix feature flags](gitlab-org/gitlab@d57e7e1966cac500ba830dca7843cb315a34a4e4) ([merge request](gitlab-org/gitlab!82121))
## 14.7.5 (2022-03-09)
### Fixed (1 change)
- [Ensure cleanup job artifacts task does not include pipeline artifacts](gitlab-org/gitlab@7b5e91bc78c46109e48537b20239d4ab649a971a) ([merge request](gitlab-org/gitlab!82430))
### Other (1 change)
- [Change to truncate table before adding finding_link_url_idx](gitlab-org/gitlab@6411ec61f40cb8648cea24ed26c1d69c8b910891) ([merge request](gitlab-org/gitlab!82430))
## 14.7.4 (2022-02-25) ## 14.7.4 (2022-02-25)
### Security (8 changes) ### Security (8 changes)

View file

@ -1 +1 @@
14.7.4 14.7.7

View file

@ -1 +1 @@
1.51.0 1.51.1

View file

@ -65,7 +65,7 @@ gem 'akismet', '~> 3.0'
gem 'invisible_captcha', '~> 1.1.0' gem 'invisible_captcha', '~> 1.1.0'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 4.0.0' gem 'devise-two-factor', '~> 4.0.2'
gem 'rqrcode-rails3', '~> 0.1.7' gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 3.1.0' gem 'attr_encrypted', '~> 3.1.0'
gem 'u2f', '~> 0.2.1' gem 'u2f', '~> 0.2.1'
@ -153,7 +153,7 @@ gem 'html-pipeline', '~> 2.13.2'
gem 'deckar01-task_list', '2.3.1' gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.8.0' gem 'gitlab-markup', '~> 1.8.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup' gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.23.2' gem 'commonmarker', '~> 0.23.4'
gem 'kramdown', '~> 2.3.1' gem 'kramdown', '~> 2.3.1'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.3.2' gem 'rdoc', '~> 6.3.2'

View file

@ -200,7 +200,7 @@ GEM
open4 (~> 1.3) open4 (~> 1.3)
coderay (1.1.3) coderay (1.1.3)
colored2 (3.1.2) colored2 (3.1.2)
commonmarker (0.23.2) commonmarker (0.23.4)
concurrent-ruby (1.1.9) concurrent-ruby (1.1.9)
connection_pool (2.2.2) connection_pool (2.2.2)
contracts (0.11.0) contracts (0.11.0)
@ -265,11 +265,11 @@ GEM
railties (>= 4.1.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-two-factor (4.0.0) devise-two-factor (4.0.2)
activesupport (< 6.2) activesupport (< 7.1)
attr_encrypted (>= 1.3, < 4, != 2) attr_encrypted (>= 1.3, < 4, != 2)
devise (~> 4.0) devise (~> 4.0)
railties (< 6.2) railties (< 7.1)
rotp (~> 6.0) rotp (~> 6.0)
diff-lcs (1.4.4) diff-lcs (1.4.4)
diff_match_patch (0.1.0) diff_match_patch (0.1.0)
@ -1421,7 +1421,7 @@ DEPENDENCIES
capybara-screenshot (~> 1.0.22) capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3) carrierwave (~> 1.3)
charlock_holmes (~> 0.7.7) charlock_holmes (~> 0.7.7)
commonmarker (~> 0.23.2) commonmarker (~> 0.23.4)
concurrent-ruby (~> 1.1) concurrent-ruby (~> 1.1)
connection_pool (~> 2.0) connection_pool (~> 2.0)
countries (~> 3.0) countries (~> 3.0)
@ -1435,7 +1435,7 @@ DEPENDENCIES
derailed_benchmarks derailed_benchmarks
device_detector device_detector
devise (~> 4.7.2) devise (~> 4.7.2)
devise-two-factor (~> 4.0.0) devise-two-factor (~> 4.0.2)
diff_match_patch (~> 0.1.0) diff_match_patch (~> 0.1.0)
diffy (~> 3.3) diffy (~> 3.3)
discordrb-webhooks (~> 3.4) discordrb-webhooks (~> 3.4)

View file

@ -1 +1 @@
14.7.4 14.7.7

View file

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

View file

@ -1,3 +1,19 @@
// https://prosemirror.net/docs/ref/#model.ParseRule.priority // https://prosemirror.net/docs/ref/#model.ParseRule.priority
export const DEFAULT_PARSE_RULE_PRIORITY = 50; export const DEFAULT_PARSE_RULE_PRIORITY = 50;
export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY; export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY;
export const unrestrictedPages = [
// Group wiki
'groups:wikis:show',
'groups:wikis:edit',
'groups:wikis:create',
// Project wiki
'projects:wikis:show',
'projects:wikis:edit',
'projects:wikis:create',
// Project files
'projects:show',
'projects:blob:show',
];

View file

@ -2,6 +2,7 @@ import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight'; import syntaxHighlight from '~/syntax_highlight';
import initUserPopovers from '../../user_popovers'; import initUserPopovers from '../../user_popovers';
import highlightCurrentUser from './highlight_current_user'; import highlightCurrentUser from './highlight_current_user';
import { renderKroki } from './render_kroki';
import renderMath from './render_math'; import renderMath from './render_math';
import renderMermaid from './render_mermaid'; import renderMermaid from './render_mermaid';
import renderSandboxedMermaid from './render_sandboxed_mermaid'; import renderSandboxedMermaid from './render_sandboxed_mermaid';
@ -13,6 +14,7 @@ import renderMetrics from './render_metrics';
// //
$.fn.renderGFM = function renderGFM() { $.fn.renderGFM = function renderGFM() {
syntaxHighlight(this.find('.js-syntax-highlight').get()); syntaxHighlight(this.find('.js-syntax-highlight').get());
renderKroki(this.find('.js-render-kroki[hidden]').get());
renderMath(this.find('.js-render-math')); renderMath(this.find('.js-render-math'));
if (gon.features?.sandboxedMermaid) { if (gon.features?.sandboxedMermaid) {
renderSandboxedMermaid(this.find('.js-render-mermaid')); renderSandboxedMermaid(this.find('.js-render-mermaid'));

View 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));
}
});
}

View file

@ -3,6 +3,7 @@ import { once, countBy } from 'lodash';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { darkModeEnabled } from '~/lib/utils/color_utils'; import { darkModeEnabled } from '~/lib/utils/color_utils';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import { unrestrictedPages } from './constants';
// Renders diagrams and flowcharts from text using Mermaid in any element with the // Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class. // `js-render-mermaid` class.
@ -30,24 +31,6 @@ let renderedMermaidBlocks = 0;
let mermaidModule = {}; let mermaidModule = {};
// Whitelist pages where we won't impose any restrictions
// on mermaid rendering
const WHITELISTED_PAGES = [
// Group wiki
'groups:wikis:show',
'groups:wikis:edit',
'groups:wikis:create',
// Project wiki
'projects:wikis:show',
'projects:wikis:edit',
'projects:wikis:create',
// Project files
'projects:show',
'projects:blob:show',
];
export function initMermaid(mermaid) { export function initMermaid(mermaid) {
let theme = 'neutral'; let theme = 'neutral';
@ -163,7 +146,7 @@ function renderMermaids($els) {
* up the entire thread and causing a DoS. * up the entire thread and causing a DoS.
*/ */
if ( if (
!WHITELISTED_PAGES.includes(pageName) && !unrestrictedPages.includes(pageName) &&
((source && source.length > MAX_CHAR_LIMIT) || ((source && source.length > MAX_CHAR_LIMIT) ||
renderedChars > MAX_CHAR_LIMIT || renderedChars > MAX_CHAR_LIMIT ||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT || renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||

View file

@ -9,6 +9,7 @@ import {
} from '~/lib/utils/url_utility'; } from '~/lib/utils/url_utility';
import { darkModeEnabled } from '~/lib/utils/color_utils'; import { darkModeEnabled } from '~/lib/utils/color_utils';
import { setAttributes } from '~/lib/utils/dom_utils'; import { setAttributes } from '~/lib/utils/dom_utils';
import { unrestrictedPages } from './constants';
// Renders diagrams and flowcharts from text using Mermaid in any element with the // Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class. // `js-render-mermaid` class.
@ -36,23 +37,6 @@ const BUFFER_IFRAME_HEIGHT = 10;
const elsProcessingMap = new WeakMap(); const elsProcessingMap = new WeakMap();
let renderedMermaidBlocks = 0; let renderedMermaidBlocks = 0;
// Pages without any restrictions on mermaid rendering
const PAGES_WITHOUT_RESTRICTIONS = [
// Group wiki
'groups:wikis:show',
'groups:wikis:edit',
'groups:wikis:create',
// Project wiki
'projects:wikis:show',
'projects:wikis:edit',
'projects:wikis:create',
// Project files
'projects:show',
'projects:blob:show',
];
function shouldLazyLoadMermaidBlock(source) { function shouldLazyLoadMermaidBlock(source) {
/** /**
* If source contains `&`, which means that it might * If source contains `&`, which means that it might
@ -149,7 +133,7 @@ function renderMermaids($els) {
* up the entire thread and causing a DoS. * up the entire thread and causing a DoS.
*/ */
if ( if (
!PAGES_WITHOUT_RESTRICTIONS.includes(pageName) && !unrestrictedPages.includes(pageName) &&
((source && source.length > MAX_CHAR_LIMIT) || ((source && source.length > MAX_CHAR_LIMIT) ||
renderedChars > MAX_CHAR_LIMIT || renderedChars > MAX_CHAR_LIMIT ||
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT || renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||

View file

@ -1,6 +1,5 @@
import { SwaggerUIBundle } from 'swagger-ui-dist'; import { SwaggerUIBundle } from 'swagger-ui-dist';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default () => { export default () => {
@ -8,14 +7,10 @@ export default () => {
Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')]) Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')])
.then(() => { .then(() => {
// Temporary fix to prevent an XSS attack due to "useUnsafeMarkdown"
// Once we upgrade Swagger to "4.0.0", we can safely remove this as it will be deprecated
// Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/339696
updateHistory({ url: removeParams(['useUnsafeMarkdown']), replace: true });
SwaggerUIBundle({ SwaggerUIBundle({
url: el.dataset.endpoint, url: el.dataset.endpoint,
dom_id: '#js-openapi-viewer', dom_id: '#js-openapi-viewer',
useUnsafeMarkdown: false, deepLinking: true,
}); });
}) })
.catch((error) => { .catch((error) => {

View file

@ -81,7 +81,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def branch_to def branch_to
@target_project = selected_target_project @target_project = selected_target_project
if @target_project && params[:ref].present? if @target_project && params[:ref].present? && Ability.allowed?(current_user, :create_merge_request_in, @target_project)
@ref = params[:ref] @ref = params[:ref]
@commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref) @commit = @target_project.commit(Gitlab::Git::BRANCH_REF_PREFIX + @ref)
end end

View file

@ -65,6 +65,8 @@ module Ci
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze
TAG_LIST_MAX_LENGTH = 50
has_many :builds has_many :builds
has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :runner_projects has_many :projects, through: :runner_projects
@ -508,6 +510,11 @@ module Ci
errors.add(:tags_list, errors.add(:tags_list,
'can not be empty when runner is not allowed to pick untagged jobs') 'can not be empty when runner is not allowed to pick untagged jobs')
end end
if tag_list_changed? && tag_list.count > TAG_LIST_MAX_LENGTH
errors.add(:tags_list,
"Too many tags specified. Please limit the number of tags to #{TAG_LIST_MAX_LENGTH}")
end
end end
# TODO: remove this method once feature flag ci_runners_short_circuit_assignable_for # TODO: remove this method once feature flag ci_runners_short_circuit_assignable_for

View 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

View file

@ -19,14 +19,10 @@ class Group < Namespace
include BulkMemberAccessLoad include BulkMemberAccessLoad
include ChronicDurationAttribute include ChronicDurationAttribute
include RunnerTokenExpirationInterval include RunnerTokenExpirationInterval
include RunnersTokenPrefixable
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
# Prefix for runners_token which can be used to invalidate existing tokens.
# The value chosen here is GR (for Gitlab Runner) combined with the rotation
# date (20220225) decimal to hex encoded.
RUNNERS_TOKEN_PREFIX = 'GR1348941'
def self.sti_name def self.sti_name
'Group' 'Group'
end end
@ -124,7 +120,7 @@ class Group < Namespace
add_authentication_token_field :runners_token, add_authentication_token_field :runners_token,
encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
prefix: ->(instance) { instance.runners_token_prefix } prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
after_create :post_create_hook after_create :post_create_hook
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
@ -678,10 +674,6 @@ class Group < Namespace
ensure_runners_token! ensure_runners_token!
end end
def runners_token_prefix
Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : ''
end
override :format_runners_token override :format_runners_token
def format_runners_token(token) def format_runners_token(token)
"#{runners_token_prefix}#{token}" "#{runners_token_prefix}#{token}"

View file

@ -61,12 +61,9 @@ module Integrations
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
# check the branch restriction is poplulated and branch is not included
branch = Gitlab::Git.ref_name(data[:ref]) branch = Gitlab::Git.ref_name(data[:ref])
branch_restriction = restrict_to_branch.to_s
if branch_restriction.present? && branch_restriction.index(branch).nil? return unless branch_allowed?(branch)
return
end
user = data[:user_name] user = data[:user_name]
project_name = project.full_name project_name = project.full_name
@ -105,5 +102,13 @@ module Integrations
end end
end end
end end
private
def branch_allowed?(branch_name)
return true if restrict_to_branch.blank?
restrict_to_branch.to_s.gsub(/\s+/, '').split(',').include?(branch_name)
end
end end
end end

View file

@ -38,6 +38,7 @@ class Project < ApplicationRecord
include GitlabRoutingHelper include GitlabRoutingHelper
include BulkMemberAccessLoad include BulkMemberAccessLoad
include RunnerTokenExpirationInterval include RunnerTokenExpirationInterval
include RunnersTokenPrefixable
extend Gitlab::Cache::RequestCache extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override extend Gitlab::Utils::Override
@ -74,11 +75,6 @@ class Project < ApplicationRecord
GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze
# Prefix for runners_token which can be used to invalidate existing tokens.
# The value chosen here is GR (for Gitlab Runner) combined with the rotation
# date (20220225) decimal to hex encoded.
RUNNERS_TOKEN_PREFIX = 'GR1348941'
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
default_value_for :packages_enabled, true default_value_for :packages_enabled, true
@ -101,7 +97,7 @@ class Project < ApplicationRecord
add_authentication_token_field :runners_token, add_authentication_token_field :runners_token,
encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required },
prefix: ->(instance) { instance.runners_token_prefix } prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
@ -1847,10 +1843,6 @@ class Project < ApplicationRecord
ensure_runners_token! ensure_runners_token!
end end
def runners_token_prefix
Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : ''
end
override :format_runners_token override :format_runners_token
def format_runners_token(token) def format_runners_token(token)
"#{runners_token_prefix}#{token}" "#{runners_token_prefix}#{token}"

View file

@ -9,10 +9,20 @@ module Releases
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753 # See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
# Regex modified to prevent catastrophic backtracking # Regex modified to prevent catastrophic backtracking
FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze
FILEPATH_MAX_LENGTH = 128
validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release } validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release } validates :name, presence: true, uniqueness: { scope: :release }
validates :filepath, uniqueness: { scope: :release }, format: { with: FILEPATH_REGEX }, allow_blank: true, length: { maximum: 128 } validates :filepath, uniqueness: { scope: :release }, allow_blank: true
validate :filepath_format_valid?
# we use a custom validator here to prevent running the regex if the string is too long
# see https://gitlab.com/gitlab-org/gitlab/-/issues/273771
def filepath_format_valid?
return if filepath.nil? # valid use case
return errors.add(:filepath, "is too long (maximum is #{FILEPATH_MAX_LENGTH} characters)") if filepath.length > FILEPATH_MAX_LENGTH
return errors.add(:filepath, 'is in an invalid format') unless FILEPATH_REGEX.match? filepath
end
scope :sorted, -> { order(created_at: :desc) } scope :sorted, -> { order(created_at: :desc) }

View file

@ -46,11 +46,11 @@ class SshHostKey
.select(&:valid?) .select(&:valid?)
end end
attr_reader :project, :url, :compare_host_keys attr_reader :project, :url, :ip, :compare_host_keys
def initialize(project:, url:, compare_host_keys: nil) def initialize(project:, url:, compare_host_keys: nil)
@project = project @project = project
@url = normalize_url(url) @url, @ip = normalize_url(url)
@compare_host_keys = compare_host_keys @compare_host_keys = compare_host_keys
end end
@ -90,9 +90,11 @@ class SshHostKey
end end
def calculate_reactive_cache def calculate_reactive_cache
input = [ip, url.hostname].compact.join(' ')
known_hosts, errors, status = known_hosts, errors, status =
Open3.popen3({}, *%W[ssh-keyscan -T 5 -p #{url.port} -f-]) do |stdin, stdout, stderr, wait_thr| Open3.popen3({}, *%W[ssh-keyscan -T 5 -p #{url.port} -f-]) do |stdin, stdout, stderr, wait_thr|
stdin.puts(url.host) stdin.puts(input)
stdin.close stdin.close
[ [
@ -127,11 +129,31 @@ class SshHostKey
end end
def normalize_url(url) def normalize_url(url)
full_url = ::Addressable::URI.parse(url) url, real_hostname = Gitlab::UrlBlocker.validate!(
raise ArgumentError, "Invalid URL" unless full_url&.scheme == 'ssh' url,
schemes: %w[ssh],
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
dns_rebind_protection: Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
)
Addressable::URI.parse("ssh://#{full_url.host}:#{full_url.inferred_port}") # When DNS rebinding protection is required, the hostname is replaced by the
rescue Addressable::URI::InvalidURIError # resolved IP. However, `url` is used in `id`, so we can't change it. Track
# the resolved IP separately instead.
if real_hostname
ip = url.hostname
url.hostname = real_hostname
end
# Ensure ssh://foo and ssh://foo:22 share the same cache
url.port = url.inferred_port
[url, ip]
rescue Gitlab::UrlBlocker::BlockedUrlError
raise ArgumentError, "Invalid URL" raise ArgumentError, "Invalid URL"
end end
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end end

View file

@ -895,6 +895,23 @@ class User < ApplicationRecord
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end end
# See https://gitlab.com/gitlab-org/security/gitlab/-/issues/638
DISALLOWED_PASSWORDS = %w[123qweQWE!@#000000000].freeze
# Overwrites valid_password? from Devise::Models::DatabaseAuthenticatable
# In constant-time, check both that the password isn't on a denylist AND
# that the password is the user's password
def valid_password?(password)
password_allowed = true
DISALLOWED_PASSWORDS.each do |disallowed_password|
password_allowed = false if Devise.secure_compare(password, disallowed_password)
end
original_result = super
password_allowed && original_result
end
def remember_me! def remember_me!
super if ::Gitlab::Database.read_write? super if ::Gitlab::Database.read_write?
end end

View file

@ -234,7 +234,6 @@ class ProjectPolicy < BasePolicy
rule { can?(:guest_access) }.policy do rule { can?(:guest_access) }.policy do
enable :read_project enable :read_project
enable :create_merge_request_in
enable :read_issue_board enable :read_issue_board
enable :read_issue_board_list enable :read_issue_board_list
enable :read_wiki enable :read_wiki
@ -487,7 +486,7 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:issue_board_list)) prevent(*create_read_update_admin_destroy(:issue_board_list))
end end
rule { merge_requests_disabled | repository_disabled }.policy do rule { merge_requests_disabled | repository_disabled | ~can?(:download_code) }.policy do
prevent :create_merge_request_in prevent :create_merge_request_in
prevent :create_merge_request_from prevent :create_merge_request_from
prevent(*create_read_update_admin_destroy(:merge_request)) prevent(*create_read_update_admin_destroy(:merge_request))
@ -589,13 +588,14 @@ class ProjectPolicy < BasePolicy
enable :read_cycle_analytics enable :read_cycle_analytics
enable :read_pages_content enable :read_pages_content
enable :read_analytics enable :read_analytics
enable :read_ci_cd_analytics
enable :read_insights enable :read_insights
# NOTE: may be overridden by IssuePolicy # NOTE: may be overridden by IssuePolicy
enable :read_issue enable :read_issue
end end
rule { can?(:public_access) & public_builds }.enable :read_ci_cd_analytics
rule { public_builds }.policy do rule { public_builds }.policy do
enable :read_build enable :read_build
end end
@ -653,6 +653,10 @@ class ProjectPolicy < BasePolicy
enable :read_security_configuration enable :read_security_configuration
end end
rule { can?(:guest_access) & can?(:read_commit_status) }.policy do
enable :create_merge_request_in
end
# Design abilities could also be prevented in the issue policy. # Design abilities could also be prevented in the issue policy.
rule { design_management_disabled }.policy do rule { design_management_disabled }.policy do
prevent :read_design prevent :read_design

View file

@ -12,7 +12,7 @@ module Ci
def destroy_records def destroy_records
@job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation| @job_artifacts_relation.each_batch(of: BATCH_SIZE) do |relation|
service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current) service = Ci::JobArtifacts::DestroyBatchService.new(relation, pick_up_at: Time.current, fix_expire_at: false)
result = service.execute(update_stats: false) result = service.execute(update_stats: false)
updates = result[:statistics_updates] updates = result[:statistics_updates]

View file

@ -17,13 +17,18 @@ module Ci
# +pick_up_at+:: When to pick up for deletion of files # +pick_up_at+:: When to pick up for deletion of files
# Returns: # Returns:
# +Hash+:: A hash with status and destroyed_artifacts_count keys # +Hash+:: A hash with status and destroyed_artifacts_count keys
def initialize(job_artifacts, pick_up_at: nil) def initialize(job_artifacts, pick_up_at: nil, fix_expire_at: fix_expire_at?)
@job_artifacts = job_artifacts.with_destroy_preloads.to_a @job_artifacts = job_artifacts.with_destroy_preloads.to_a
@pick_up_at = pick_up_at @pick_up_at = pick_up_at
@fix_expire_at = fix_expire_at
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def execute(update_stats: true) def execute(update_stats: true)
# Detect and fix artifacts that had `expire_at` wrongly backfilled by migration
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47723
detect_and_fix_wrongly_expired_artifacts
return success(destroyed_artifacts_count: 0, statistics_updates: {}) if @job_artifacts.empty? return success(destroyed_artifacts_count: 0, statistics_updates: {}) if @job_artifacts.empty?
destroy_related_records(@job_artifacts) destroy_related_records(@job_artifacts)
@ -89,6 +94,55 @@ module Ci
@job_artifacts.sum { |artifact| artifact.try(:size) || 0 } @job_artifacts.sum { |artifact| artifact.try(:size) || 0 }
end end
end end
# This detects and fixes job artifacts that have `expire_at` wrongly backfilled by the migration
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47723.
# These job artifacts will not be deleted and will have their `expire_at` removed.
#
# The migration would have backfilled `expire_at`
# to midnight on the 22nd of the month of the local timezone,
# storing it as UTC time in the database.
#
# If the timezone setting has changed since the migration,
# the `expire_at` stored in the database could have changed to a different local time other than midnight.
# For example:
# - changing timezone from UTC+02:00 to UTC+02:30 would change the `expire_at` in local time 00:00:00 to 00:30:00.
# - changing timezone from UTC+00:00 to UTC-01:00 would change the `expire_at` in local time 00:00:00 to 23:00:00 on the previous day (21st).
#
# Therefore job artifacts that have `expire_at` exactly on the 00, 30 or 45 minute mark
# on the dates 21, 22, 23 of the month will not be deleted.
# https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
def detect_and_fix_wrongly_expired_artifacts
return unless @fix_expire_at
wrongly_expired_artifacts, @job_artifacts = @job_artifacts.partition { |artifact| wrongly_expired?(artifact) }
remove_expire_at(wrongly_expired_artifacts)
end
def fix_expire_at?
Feature.enabled?(:ci_detect_wrongly_expired_artifacts, default_enabled: :yaml)
end
def wrongly_expired?(artifact)
return false unless artifact.expire_at.present?
match_date?(artifact.expire_at) && match_time?(artifact.expire_at)
end
def match_date?(expire_at)
[21, 22, 23].include?(expire_at.day)
end
def match_time?(expire_at)
%w[00:00.000 30:00.000 45:00.000].include?(expire_at.strftime('%M:%S.%L'))
end
def remove_expire_at(artifacts)
Ci::JobArtifact.id_in(artifacts).update_all(expire_at: nil)
Gitlab::AppLogger.info(message: "Fixed expire_at from artifacts.", fixed_artifacts_expire_at_count: artifacts.count)
end
end end
end end
end end

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348786
milestone: '14.6' milestone: '14.6'
type: development type: development
group: group::pipeline execution group: group::pipeline execution
default_enabled: false default_enabled: true

View file

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

View file

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

View file

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

View 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

View file

@ -11,7 +11,7 @@ module Db
name: FFaker::Name.name, name: FFaker::Name.name,
email: FFaker::Internet.email, email: FFaker::Internet.email,
confirmed_at: DateTime.now, confirmed_at: DateTime.now,
password: Gitlab::Password.test_default password: '12345678'
) )
::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam') ::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')

View file

@ -1,18 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
class AddUniqueIndexToVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0] class AddUniqueIndexToVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
disable_ddl_transaction! # This migration has been moved to db/post_migrate/20220201193033_add_unique_index_to_vulnerability_finding_links_with_truncate.rb
# Previously, this was causing an bug where there was a conflict between the table cleanup and the index creation.
NAME_URL_INDEX_NAME = 'finding_link_name_url_idx'
URL_INDEX_NAME = 'finding_link_url_idx'
def up def up
add_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :name, :url], unique: true, name: NAME_URL_INDEX_NAME # no op
add_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :url], unique: true, where: 'name is null', name: URL_INDEX_NAME
end end
def down def down
remove_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :name, :url], name: NAME_URL_INDEX_NAME # no op
remove_concurrent_index :vulnerability_finding_links, [:vulnerability_occurrence_id, :url], name: URL_INDEX_NAME
end end
end end

View file

@ -1,21 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
class RemoveVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0] class RemoveVulnerabilityFindingLinks < Gitlab::Database::Migration[1.0]
BATCH_SIZE = 50_000 # This migration has been moved to a TRUNCATE in db/post_migrate/20220201193033_add_unique_index_to_vulnerability_finding_links_with_truncate.rb
MIGRATION = 'RemoveVulnerabilityFindingLinks' # Previously, this was causing an bug where there was a conflict between the table cleanup and the index creation.
disable_ddl_transaction!
def up def up
queue_background_migration_jobs_by_range_at_intervals( # no op
define_batchable_model('vulnerability_finding_links'),
MIGRATION,
2.minutes,
batch_size: BATCH_SIZE
)
end end
def down def down
# no ops # no op
end end
end end

View file

@ -1,21 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
class RemoveVulnerabilityFindingLinksAgain < Gitlab::Database::Migration[1.0] class RemoveVulnerabilityFindingLinksAgain < Gitlab::Database::Migration[1.0]
BATCH_SIZE = 50_000 # This migration has been moved to a TRUNCATE in db/post_migrate/20220201193033_add_unique_index_to_vulnerability_finding_links_with_truncate.rb
MIGRATION = 'RemoveVulnerabilityFindingLinks' # Previously, this was causing an bug where there was a conflict between the table cleanup and the index creation.
disable_ddl_transaction!
def up def up
queue_background_migration_jobs_by_range_at_intervals( # no op
define_batchable_model('vulnerability_finding_links'),
MIGRATION,
2.minutes,
batch_size: BATCH_SIZE
)
end end
def down def down
# no ops # no op
end end
end end

View file

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

View file

@ -0,0 +1 @@
92bbe74c6c3627dd26f709acd2a20f442212eab933f719be815701a3bc429539

View file

@ -6,8 +6,10 @@ require "asciidoctor/extensions/asciidoctor_kroki/extension"
module Banzai module Banzai
module Filter module Filter
# HTML that replaces all diagrams supported by Kroki with the corresponding img tags. # HTML that replaces all diagrams supported by Kroki with the corresponding img tags.
# # If the source content is large then the hidden attribute is added to the img tag.
class KrokiFilter < HTML::Pipeline::Filter class KrokiFilter < HTML::Pipeline::Filter
MAX_CHARACTER_LIMIT = 2000
def call def call
return doc unless settings.kroki_enabled return doc unless settings.kroki_enabled
@ -21,7 +23,12 @@ module Banzai
diagram_format = "svg" diagram_format = "svg"
doc.xpath(xpath).each do |node| doc.xpath(xpath).each do |node|
diagram_type = node.parent['lang'] diagram_type = node.parent['lang']
img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{create_image_src(diagram_type, diagram_format, node.content)}"/>)) diagram_src = node.content
image_src = create_image_src(diagram_type, diagram_format, diagram_src)
lazy_load = diagram_src.length > MAX_CHARACTER_LIMIT
other_attrs = lazy_load ? "hidden" : ""
img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img class="js-render-kroki" src="#{image_src}" #{other_attrs} />))
node.parent.replace(img_tag) node.parent.replace(img_tag)
end end

View file

@ -56,7 +56,7 @@ module Banzai
retry retry
end end
sourcepos_attr = sourcepos ? "data-sourcepos=\"#{sourcepos}\"" : '' sourcepos_attr = sourcepos ? "data-sourcepos=\"#{escape_once(sourcepos)}\"" : ''
highlighted = %(<div class="gl-relative markdown-code-block js-markdown-code"><pre #{sourcepos_attr} class="#{css_classes}" highlighted = %(<div class="gl-relative markdown-code-block js-markdown-code"><pre #{sourcepos_attr} class="#{css_classes}"
lang="#{language}" lang="#{language}"

View file

@ -65,16 +65,15 @@ module Banzai
# #
def redacted_node_content(node) def redacted_node_content(node)
original_content = node.attr('data-original') original_content = node.attr('data-original')
link_reference = node.attr('data-link-reference') original_content = CGI.escape_html(original_content) if original_content
# Build the raw <a> tag just with a link as href and content if # Build the raw <a> tag just with a link as href and content if
# it's originally a link pattern. We shouldn't return a plain text href. # it's originally a link pattern. We shouldn't return a plain text href.
original_link = original_link =
if link_reference == 'true' if node.attr('data-link-reference') == 'true'
href = node.attr('href') href = node.attr('href')
content = original_content
%(<a href="#{href}">#{content}</a>) %(<a href="#{href}">#{original_content}</a>)
end end
# The reference should be replaced by the original link's content, # The reference should be replaced by the original link's content,

View file

@ -230,8 +230,8 @@ module Gitlab
name: name.strip.presence || valid_username, name: name.strip.presence || valid_username,
username: valid_username, username: valid_username,
email: email, email: email,
password: Gitlab::Password.test_default(21), password: auth_hash.password,
password_confirmation: Gitlab::Password.test_default(21), password_confirmation: auth_hash.password,
password_automatically_set: true password_automatically_set: true
} }
end end

View file

@ -70,6 +70,16 @@ module Gitlab
} }
end end
def mask_variables_from(location)
variables.reduce(location.dup) do |loc, variable|
if variable[:masked]
Gitlab::Ci::MaskSecret.mask!(loc, variable[:value])
else
loc
end
end
end
protected protected
attr_writer :expandset, :execution_deadline, :logger attr_writer :expandset, :execution_deadline, :logger

View file

@ -37,7 +37,7 @@ module Gitlab
def validate_content! def validate_content!
return unless ensure_preconditions_satisfied! return unless ensure_preconditions_satisfied!
errors.push("File `#{location}` is empty!") unless content.present? errors.push("File `#{masked_location}` is empty!") unless content.present?
end end
def ensure_preconditions_satisfied! def ensure_preconditions_satisfied!

View file

@ -79,21 +79,21 @@ module Gitlab
def validate_location! def validate_location!
if invalid_location_type? if invalid_location_type?
errors.push("Included file `#{location}` needs to be a string") errors.push("Included file `#{masked_location}` needs to be a string")
elsif invalid_extension? elsif invalid_extension?
errors.push("Included file `#{location}` does not have YAML extension!") errors.push("Included file `#{masked_location}` does not have YAML extension!")
end end
end end
def validate_content! def validate_content!
if content.blank? if content.blank?
errors.push("Included file `#{location}` is empty or does not exist!") errors.push("Included file `#{masked_location}` is empty or does not exist!")
end end
end end
def validate_hash! def validate_hash!
if to_hash.blank? if to_hash.blank?
errors.push("Included file `#{location}` does not have valid YAML syntax!") errors.push("Included file `#{masked_location}` does not have valid YAML syntax!")
end end
end end
@ -104,6 +104,12 @@ module Gitlab
def expand_context_attrs def expand_context_attrs
{} {}
end end
def masked_location
strong_memoize(:masked_location) do
context.mask_variables_from(location)
end
end
end end
end end
end end

View file

@ -23,11 +23,11 @@ module Gitlab
def validate_content! def validate_content!
if context.project&.repository.nil? if context.project&.repository.nil?
errors.push("Local file `#{location}` does not have project!") errors.push("Local file `#{masked_location}` does not have project!")
elsif content.nil? elsif content.nil?
errors.push("Local file `#{location}` does not exist!") errors.push("Local file `#{masked_location}` does not exist!")
elsif content.blank? elsif content.blank?
errors.push("Local file `#{location}` is empty!") errors.push("Local file `#{masked_location}` is empty!")
end end
end end

View file

@ -35,9 +35,9 @@ module Gitlab
elsif sha.nil? elsif sha.nil?
errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!") errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!")
elsif content.nil? elsif content.nil?
errors.push("Project `#{project_name}` file `#{location}` does not exist!") errors.push("Project `#{project_name}` file `#{masked_location}` does not exist!")
elsif content.blank? elsif content.blank?
errors.push("Project `#{project_name}` file `#{location}` is empty!") errors.push("Project `#{project_name}` file `#{masked_location}` is empty!")
end end
end end

View file

@ -24,7 +24,7 @@ module Gitlab
super super
unless ::Gitlab::UrlSanitizer.valid?(location) unless ::Gitlab::UrlSanitizer.valid?(location)
errors.push("Remote file `#{location}` does not have a valid address!") errors.push("Remote file `#{masked_location}` does not have a valid address!")
end end
end end
@ -32,17 +32,17 @@ module Gitlab
begin begin
response = Gitlab::HTTP.get(location) response = Gitlab::HTTP.get(location)
rescue SocketError rescue SocketError
errors.push("Remote file `#{location}` could not be fetched because of a socket error!") errors.push("Remote file `#{masked_location}` could not be fetched because of a socket error!")
rescue Timeout::Error rescue Timeout::Error
errors.push("Remote file `#{location}` could not be fetched because of a timeout error!") errors.push("Remote file `#{masked_location}` could not be fetched because of a timeout error!")
rescue Gitlab::HTTP::Error rescue Gitlab::HTTP::Error
errors.push("Remote file `#{location}` could not be fetched because of HTTP error!") errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP error!")
rescue Gitlab::HTTP::BlockedUrlError => e rescue Gitlab::HTTP::BlockedUrlError => e
errors.push("Remote file could not be fetched because #{e}!") errors.push("Remote file could not be fetched because #{e}!")
end end
if response&.code.to_i >= 400 if response&.code.to_i >= 400
errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!") errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP code `#{response.code}` error!")
end end
response.body if errors.none? response.body if errors.none?

View file

@ -26,7 +26,7 @@ module Gitlab
super super
unless template_name_valid? unless template_name_valid?
errors.push("Template file `#{location}` is not a valid location!") errors.push("Template file `#{masked_location}` is not a valid location!")
end end
end end

View file

@ -146,7 +146,7 @@ module Gitlab
file_class.new(location, context) file_class.new(location, context)
end.select(&:matching?) end.select(&:matching?)
raise AmbigiousSpecificationError, "Include `#{location.to_json}` needs to match exactly one accessor!" unless matching.one? raise AmbigiousSpecificationError, "Include `#{masked_location(location.to_json)}` needs to match exactly one accessor!" unless matching.one?
matching.first matching.first
end end
@ -181,6 +181,10 @@ module Gitlab
def expand(data) def expand(data)
ExpandVariables.expand(data, -> { context.variables_hash }) ExpandVariables.expand(data, -> { context.variables_hash })
end end
def masked_location(location)
context.mask_variables_from(location)
end
end end
end end
end end

View file

@ -99,6 +99,9 @@ module Gitlab
# ^--+--+- components of hashed storage project path # ^--+--+- components of hashed storage project path
cmd += %w[-mindepth 6 -maxdepth 6] cmd += %w[-mindepth 6 -maxdepth 6]
# Intentionally exclude pipeline artifacts which match the same path
cmd += %w[-not -path */pipelines/*]
# Artifact directories are named on their ID # Artifact directories are named on their ID
cmd += %w[-type d] cmd += %w[-type d]

View file

@ -19,7 +19,8 @@ module Gitlab
PROCESSORS = [ PROCESSORS = [
::Gitlab::ErrorTracking::Processor::SidekiqProcessor, ::Gitlab::ErrorTracking::Processor::SidekiqProcessor,
::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor, ::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor,
::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor ::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor,
::Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor
].freeze ].freeze
class << self class << self

View file

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

View file

@ -4,6 +4,8 @@ module Gitlab
module ErrorTracking module ErrorTracking
module Processor module Processor
module GrpcErrorProcessor module GrpcErrorProcessor
extend Gitlab::ErrorTracking::Processor::Concerns::ProcessesExceptions
DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)') DEBUG_ERROR_STRING_REGEX = RE2('(.*) debug_error_string:(.*)')
class << self class << self
@ -18,10 +20,7 @@ module Gitlab
# only the first one since that's what is used for grouping. # only the first one since that's what is used for grouping.
def process_first_exception_value(event) def process_first_exception_value(event)
# Better in new version, will be event.exception.values # Better in new version, will be event.exception.values
exceptions = event.instance_variable_get(:@interfaces)[:exception]&.values exceptions = extract_exceptions_from(event)
return unless exceptions.is_a?(Array)
exception = exceptions.first exception = exceptions.first
return unless valid_exception?(exception) return unless valid_exception?(exception)
@ -68,15 +67,6 @@ module Gitlab
[match[1], match[2]] [match[1], match[2]]
end end
def valid_exception?(exception)
case exception
when Raven::SingleExceptionInterface
exception&.value
else
false
end
end
end end
end end
end end

View file

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

View file

@ -10,7 +10,7 @@ module Gitlab
# Use periods to flatten the fields. # Use periods to flatten the fields.
payload.merge!( payload.merge!(
'exception.class' => exception.class.name, 'exception.class' => exception.class.name,
'exception.message' => exception.message 'exception.message' => sanitize_message(exception)
) )
if exception.backtrace if exception.backtrace
@ -38,6 +38,10 @@ module Gitlab
rescue PgQuery::ParseError rescue PgQuery::ParseError
sql sql
end end
def sanitize_message(exception)
Gitlab::Sanitizers::ExceptionMessage.clean(exception.class.name, exception.message)
end
end end
end end
end end

View file

@ -19,9 +19,8 @@ module Gitlab
@exported_members.inject(missing_keys_tracking_hash) do |hash, member| @exported_members.inject(missing_keys_tracking_hash) do |hash, member|
if member['user'] if member['user']
old_user_id = member['user']['id'] old_user_id = member['user']['id']
old_user_email = member.dig('user', 'public_email') || member.dig('user', 'email') existing_user_id = existing_users_email_map[get_email(member)]
existing_user = User.find_by(find_user_query(old_user_email)) if old_user_email hash[old_user_id] = existing_user_id if existing_user_id && add_team_member(member, existing_user_id)
hash[old_user_id] = existing_user.id if existing_user && add_team_member(member, existing_user)
else else
add_team_member(member) add_team_member(member)
end end
@ -72,11 +71,45 @@ module Gitlab
member&.user == @user && member.access_level >= highest_access_level member&.user == @user && member.access_level >= highest_access_level
end end
def add_team_member(member, existing_user = nil) # Returns {email => user_id} hash where user_id is an ID at current instance
return true if existing_user && @importable.members.exists?(user_id: existing_user.id) def existing_users_email_map
@existing_users_email_map ||= begin
emails = @exported_members.map { |member| get_email(member) }
User.by_user_email(emails).pluck(:email, :id).to_h
end
end
# Returns {user_id => email} hash where user_id is an ID at source "old" instance
def exported_members_email_map
@exported_members_email_map ||= begin
result = {}
@exported_members.each do |member|
email = get_email(member)
next unless email
result[member.dig('user', 'id')] = email
end
result
end
end
def get_email(member_data)
return unless member_data['user']
member_data.dig('user', 'public_email') || member_data.dig('user', 'email')
end
def add_team_member(member, existing_user_id = nil)
return true if existing_user_id && @importable.members.exists?(user_id: existing_user_id)
member['user'] = existing_user
member_hash = member_hash(member) member_hash = member_hash(member)
if existing_user_id
member_hash.delete('user')
member_hash['user_id'] = existing_user_id
end
member = relation_class.create(member_hash) member = relation_class.create(member_hash)
@ -92,11 +125,19 @@ module Gitlab
end end
def member_hash(member) def member_hash(member)
parsed_hash(member).merge( result = parsed_hash(member).merge(
'source_id' => @importable.id, 'source_id' => @importable.id,
'importing' => true, 'importing' => true,
'access_level' => [member['access_level'], highest_access_level].min 'access_level' => [member['access_level'], highest_access_level].min
).except('user_id') ).except('user_id')
if result['created_by_id']
created_by_email = exported_members_email_map[result['created_by_id']]
result['created_by_id'] = existing_users_email_map[created_by_email]
end
result
end end
def parsed_hash(member) def parsed_hash(member)
@ -104,14 +145,6 @@ module Gitlab
relation_class: relation_class) relation_class: relation_class)
end end
def find_user_query(email)
user_arel[:email].eq(email)
end
def user_arel
@user_arel ||= User.arel_table
end
def relation_class def relation_class
case @importable case @importable
when ::Project when ::Project
@ -143,7 +176,7 @@ module Gitlab
def base_log_params(member_hash) def base_log_params(member_hash)
{ {
user_id: member_hash['user']&.id, user_id: member_hash['user_id'],
access_level: member_hash['access_level'], access_level: member_hash['access_level'],
importable_type: @importable.class.to_s, importable_type: @importable.class.to_s,
importable_id: @importable.id, importable_id: @importable.id,

View file

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

View 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

View file

@ -125,7 +125,7 @@ class GroupSeeder
name: FFaker::Name.name, name: FFaker::Name.name,
email: FFaker::Internet.email, email: FFaker::Internet.email,
confirmed_at: DateTime.now, confirmed_at: DateTime.now,
password: Gitlab::Password.test_default password: Devise.friendly_token
) )
end end

View file

@ -12625,6 +12625,9 @@ msgstr ""
msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}" msgid "Dismissed on pipeline %{pipelineLink} at %{projectLink}"
msgstr "" msgstr ""
msgid "Display"
msgstr ""
msgid "Display alerts from all configured monitoring tools." msgid "Display alerts from all configured monitoring tools."
msgstr "" msgstr ""

View file

@ -177,7 +177,7 @@
"sql.js": "^0.4.0", "sql.js": "^0.4.0",
"string-hash": "1.1.3", "string-hash": "1.1.3",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"swagger-ui-dist": "^3.52.3", "swagger-ui-dist": "4.8.0",
"three": "^0.84.0", "three": "^0.84.0",
"three-orbit-controls": "^82.1.0", "three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4", "three-stl-loader": "^1.0.4",

View file

@ -6,6 +6,10 @@ module Gitlab
class Subscription < Chemlab::Page class Subscription < Chemlab::Page
path '/admin/subscription' path '/admin/subscription'
div :subscription_details
text_field :activation_code
button :activate
label :terms_of_services, text: /I agree that/
p :plan p :plan
p :started p :started
p :name p :name
@ -16,6 +20,33 @@ module Gitlab
h2 :users_in_subscription h2 :users_in_subscription
h2 :users_over_subscription h2 :users_over_subscription
table :subscription_history table :subscription_history
def accept_terms
terms_of_services_element.click # workaround for hidden checkbox
end
# Checks if a subscription record exists in subscription history table
#
# @param plan [Hash] Name of the plan
# @option plan [Hash] Support::Helpers::FREE
# @option plan [Hash] Support::Helpers::PREMIUM
# @option plan [Hash] Support::Helpers::PREMIUM_SELF_MANAGED
# @option plan [Hash] Support::Helpers::ULTIMATE
# @option plan [Hash] Support::Helpers::ULTIMATE_SELF_MANAGED
# @option plan [Hash] Support::Helpers::CI_MINUTES
# @option plan [Hash] Support::Helpers::STORAGE
# @param users_in_license [Integer] Number of users in license
# @param license_type [Hash] Type of the license
# @option license_type [String] 'license file'
# @option license_type [String] 'cloud license'
# @return [Boolean] True if record exsists, false if not
def has_subscription_record?(plan, users_in_license, license_type)
# find any records that have a matching plan and seats and type
subscription_history_element.hashes.any? do |record|
record['Plan'] == plan[:name].capitalize && record['Seats'] == users_in_license.to_s && \
record['Type'].strip.downcase == license_type
end
end
end end
end end
end end

View file

@ -4,6 +4,112 @@ module Gitlab
module Page module Page
module Admin module Admin
module Subscription module Subscription
# @note Defined as +h6 :subscription_details+
# @return [String] The text content or value of +subscription_details+
def subscription_details
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.subscription_details_element).to exist
# end
# @return [Watir::H6] The raw +H6+ element
def subscription_details_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_subscription_details
# end
# @return [Boolean] true if the +subscription_details+ element is present on the page
def subscription_details?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +text_field :activation_code+
# @return [String] The text content or value of +activation_code+
def activation_code
# This is a stub, used for indexing. The method is dynamically generated.
end
# Set the value of activation_code
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# subscription.activation_code = 'value'
# end
# @param value [String] The value to set.
def activation_code=(value)
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.activation_code_element).to exist
# end
# @return [Watir::TextField] The raw +TextField+ element
def activation_code_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_activation_code
# end
# @return [Boolean] true if the +activation_code+ element is present on the page
def activation_code?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +label :terms_of_services+
# @return [String] The text content or value of +terms_of_services+
def terms_of_services
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.terms_of_services_element).to exist
# end
# @return [Watir::Label] The raw +Label+ element
def terms_of_services_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_terms_of_services
# end
# @return [Boolean] true if the +terms_of_services+ element is present on the page
def terms_of_services?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +button :activate+
# Clicks +activate+
def activate
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription.activate_element).to exist
# end
# @return [Watir::Button] The raw +Button+ element
def activate_element
# This is a stub, used for indexing. The method is dynamically generated.
end
# @example
# Gitlab::Page::Admin::Subscription.perform do |subscription|
# expect(subscription).to be_activate
# end
# @return [Boolean] true if the +activate+ element is present on the page
def activate?
# This is a stub, used for indexing. The method is dynamically generated.
end
# @note Defined as +p :plan+ # @note Defined as +p :plan+
# @return [String] The text content or value of +plan+ # @return [String] The text content or value of +plan+
def plan def plan

View file

@ -434,6 +434,10 @@ module QA
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name)) ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
end end
def ee_activation_code
ENV['QA_EE_ACTIVATION_CODE']
end
private private
def remote_grid_credentials def remote_grid_credentials

View file

@ -64,7 +64,9 @@ module QA
Page::Profile::Accounts::Show.perform do |show| Page::Profile::Accounts::Show.perform do |show|
show.delete_account(user.password) show.delete_account(user.password)
end end
Support::Waiter.wait_until { !user.exists? }
# TODO: Remove retry_on_exception once https://gitlab.com/gitlab-org/gitlab/-/issues/24294 is resolved
Support::Waiter.wait_until(retry_on_exception: true, sleep_interval: 3) { !user.exists? }
end end
it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do

View file

@ -612,8 +612,8 @@ RSpec.describe Admin::UsersController do
end end
context 'when the new password does not match the password confirmation' do context 'when the new password does not match the password confirmation' do
let(:password) { Gitlab::Password.test_default } let(:password) { 'some_password' }
let(:password_confirmation) { "not" + Gitlab::Password.test_default } let(:password_confirmation) { 'not_same_as_password' }
it 'shows the edit page again' do it 'shows the edit page again' do
update_password(user, password, password_confirmation) update_password(user, password, password_confirmation)

View file

@ -186,6 +186,7 @@ RSpec.describe Projects::MergeRequests::CreationsController do
it 'fetches the commit if a user has access' do it 'fetches the commit if a user has access' do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true } expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
expect(Ability).to receive(:allowed?).with(user, :create_merge_request_in, project) { true }.at_least(:once)
get :branch_to, get :branch_to,
params: { params: {
@ -199,8 +200,25 @@ RSpec.describe Projects::MergeRequests::CreationsController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'does not load the commit when the user cannot create_merge_request_in' do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
expect(Ability).to receive(:allowed?).with(user, :create_merge_request_in, project) { false }.at_least(:once)
get :branch_to,
params: {
namespace_id: fork_project.namespace,
project_id: fork_project,
target_project_id: project.id,
ref: 'master'
}
expect(assigns(:commit)).to be_nil
expect(response).to have_gitlab_http_status(:ok)
end
it 'does not load the commit when the user cannot read the project' do it 'does not load the commit when the user cannot read the project' do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false } expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false }
expect(Ability).to receive(:allowed?).with(user, :create_merge_request_in, project) { true }.at_least(:once)
get :branch_to, get :branch_to,
params: { params: {

View file

@ -177,6 +177,7 @@ RSpec.describe Projects::MirrorsController do
INVALID INVALID
git@example.com:foo/bar.git git@example.com:foo/bar.git
ssh://git@example.com:foo/bar.git ssh://git@example.com:foo/bar.git
ssh://127.0.0.1/foo/bar.git
].each do |url| ].each do |url|
it "returns an error with a 400 response for URL #{url.inspect}" do it "returns an error with a 400 response for URL #{url.inspect}" do
do_get(project, url) do_get(project, url)

View file

@ -499,7 +499,7 @@ RSpec.describe RegistrationsController do
end end
it 'succeeds if password is confirmed' do it 'succeeds if password is confirmed' do
post :destroy, params: { password: Gitlab::Password.test_default } post :destroy, params: { password: '12345678' }
expect_success expect_success
end end
@ -540,7 +540,7 @@ RSpec.describe RegistrationsController do
end end
it 'fails' do it 'fails' do
delete :destroy, params: { password: Gitlab::Password.test_default } delete :destroy, params: { password: '12345678' }
expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account')) expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
end end

View file

@ -7,7 +7,7 @@ FactoryBot.define do
file_format { :zip } file_format { :zip }
trait :expired do trait :expired do
expire_at { Date.yesterday } expire_at { Time.current.yesterday.change(minute: 9) }
end end
trait :locked do trait :locked do

View file

@ -5,7 +5,7 @@ FactoryBot.define do
email { generate(:email) } email { generate(:email) }
name { generate(:name) } name { generate(:name) }
username { generate(:username) } username { generate(:username) }
password { Gitlab::Password.test_default } password { "12345678" }
role { 'software_developer' } role { 'software_developer' }
confirmed_at { Time.now } confirmed_at { Time.now }
confirmation_token { nil } confirmation_token { nil }
@ -23,6 +23,10 @@ FactoryBot.define do
after(:build) { |user, _| user.block! } after(:build) { |user, _| user.block! }
end end
trait :disallowed_password do
password { User::DISALLOWED_PASSWORDS.first }
end
trait :blocked_pending_approval do trait :blocked_pending_approval do
after(:build) { |user, _| user.block_pending_approval! } after(:build) { |user, _| user.block_pending_approval! }
end end

View 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

View file

@ -44,8 +44,8 @@ RSpec.describe 'Password reset' do
visit(edit_user_password_path(reset_password_token: token)) visit(edit_user_password_path(reset_password_token: token))
fill_in 'New password', with: "new" + Gitlab::Password.test_default fill_in 'New password', with: 'hello1234'
fill_in 'Confirm new password', with: "new" + Gitlab::Password.test_default fill_in 'Confirm new password', with: 'hello1234'
click_button 'Change your password' click_button 'Change your password'

View file

@ -29,7 +29,7 @@ RSpec.describe 'Profile account page', :js do
it 'deletes user', :js, :sidekiq_might_not_need_inline do it 'deletes user', :js, :sidekiq_might_not_need_inline do
click_button 'Delete account' click_button 'Delete account'
fill_in 'password', with: Gitlab::Password.test_default fill_in 'password', with: '12345678'
page.within '.modal' do page.within '.modal' do
click_button 'Delete account' click_button 'Delete account'

View file

@ -39,7 +39,7 @@ RSpec.describe 'Profile > Password' do
describe 'User puts the same passwords in the field and in the confirmation' do describe 'User puts the same passwords in the field and in the confirmation' do
it 'shows a success message' do it 'shows a success message' do
fill_passwords(Gitlab::Password.test_default, Gitlab::Password.test_default) fill_passwords('mypassword', 'mypassword')
page.within('.flash-notice') do page.within('.flash-notice') do
expect(page).to have_content('Password was successfully updated. Please sign in again.') expect(page).to have_content('Password was successfully updated. Please sign in again.')
@ -79,7 +79,7 @@ RSpec.describe 'Profile > Password' do
end end
context 'Change password' do context 'Change password' do
let(:new_password) { "new" + Gitlab::Password.test_default } let(:new_password) { '22233344' }
before do before do
sign_in(user) sign_in(user)
@ -170,8 +170,8 @@ RSpec.describe 'Profile > Password' do
expect(current_path).to eq new_profile_password_path expect(current_path).to eq new_profile_password_path
fill_in :user_password, with: user.password fill_in :user_password, with: user.password
fill_in :user_new_password, with: Gitlab::Password.test_default fill_in :user_new_password, with: '12345678'
fill_in :user_password_confirmation, with: Gitlab::Password.test_default fill_in :user_password_confirmation, with: '12345678'
click_button 'Set new password' click_button 'Set new password'
expect(current_path).to eq new_user_session_path expect(current_path).to eq new_user_session_path

View file

@ -9,7 +9,7 @@ RSpec.describe 'Session TTLs', :clean_gitlab_redis_shared_state do
visit new_user_session_path visit new_user_session_path
# The session key only gets created after a post # The session key only gets created after a post
fill_in 'user_login', with: 'non-existant@gitlab.org' fill_in 'user_login', with: 'non-existant@gitlab.org'
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
expect(page).to have_content('Invalid login or password') expect(page).to have_content('Invalid login or password')

View file

@ -49,15 +49,15 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
expect(current_path).to eq edit_user_password_path expect(current_path).to eq edit_user_password_path
expect(page).to have_content('Please create a password for your new account.') expect(page).to have_content('Please create a password for your new account.')
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: 'password'
fill_in 'user_password_confirmation', with: Gitlab::Password.test_default fill_in 'user_password_confirmation', with: 'password'
click_button 'Change your password' click_button 'Change your password'
expect(current_path).to eq new_user_session_path expect(current_path).to eq new_user_session_path
expect(page).to have_content(I18n.t('devise.passwords.updated_not_active')) expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))
fill_in 'user_login', with: user.username fill_in 'user_login', with: user.username
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: 'password'
click_button 'Sign in' click_button 'Sign in'
expect_single_session_with_authenticated_ttl expect_single_session_with_authenticated_ttl
@ -150,6 +150,27 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
end end
end end
describe 'with a disallowed password' do
let(:user) { create(:user, :disallowed_password) }
before do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
.and increment(:user_password_invalid_counter)
end
it 'disallows login' do
gitlab_sign_in(user, password: user.password)
expect(page).to have_content('Invalid login or password.')
end
it 'does not update Devise trackable attributes' do
expect { gitlab_sign_in(user, password: user.password) }
.not_to change { User.ghost.reload.sign_in_count }
end
end
describe 'with the ghost user' do describe 'with the ghost user' do
it 'disallows login' do it 'disallows login' do
expect(authentication_metrics) expect(authentication_metrics)
@ -210,7 +231,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
end end
it 'does not allow sign-in if the user password is updated before entering a one-time code' do it 'does not allow sign-in if the user password is updated before entering a one-time code' do
user.update!(password: "new" + Gitlab::Password.test_default) user.update!(password: 'new_password')
enter_code(user.current_otp) enter_code(user.current_otp)
@ -447,7 +468,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
visit new_user_session_path visit new_user_session_path
fill_in 'user_login', with: user.email fill_in 'user_login', with: user.email
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
expect(current_path).to eq(new_profile_password_path) expect(current_path).to eq(new_profile_password_path)
@ -456,7 +477,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
end end
context 'with invalid username and password' do context 'with invalid username and password' do
let(:user) { create(:user, password: "not" + Gitlab::Password.test_default) } let(:user) { create(:user, password: 'not-the-default') }
it 'blocks invalid login' do it 'blocks invalid login' do
expect(authentication_metrics) expect(authentication_metrics)
@ -767,7 +788,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
visit new_user_session_path visit new_user_session_path
fill_in 'user_login', with: user.email fill_in 'user_login', with: user.email
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
@ -788,7 +809,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
visit new_user_session_path visit new_user_session_path
fill_in 'user_login', with: user.email fill_in 'user_login', with: user.email
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
@ -809,7 +830,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
visit new_user_session_path visit new_user_session_path
fill_in 'user_login', with: user.email fill_in 'user_login', with: user.email
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
@ -844,7 +865,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
visit new_user_session_path visit new_user_session_path
fill_in 'user_login', with: user.email fill_in 'user_login', with: user.email
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
fill_in 'user_otp_attempt', with: user.reload.current_otp fill_in 'user_otp_attempt', with: user.reload.current_otp
@ -870,7 +891,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
visit new_user_session_path visit new_user_session_path
fill_in 'user_login', with: user.email fill_in 'user_login', with: user.email
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
click_button 'Sign in' click_button 'Sign in'
expect_to_be_on_terms_page expect_to_be_on_terms_page
@ -878,7 +899,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
expect(current_path).to eq(new_profile_password_path) expect(current_path).to eq(new_profile_password_path)
fill_in 'user_password', with: Gitlab::Password.test_default fill_in 'user_password', with: '12345678'
fill_in 'user_new_password', with: 'new password' fill_in 'user_new_password', with: 'new password'
fill_in 'user_password_confirmation', with: 'new password' fill_in 'user_password_confirmation', with: 'new password'
click_button 'Set new password' click_button 'Set new password'

View file

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

View file

@ -11,7 +11,7 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
let(:current_user) { reporter } let(:current_user) { reporter }
before_all do before do
project.add_guest(guest) project.add_guest(guest)
project.add_reporter(reporter) project.add_reporter(reporter)
end end
@ -20,13 +20,8 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType) expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
end end
def resolve_statistics(project, args) shared_examples 'returns the pipelines statistics for a given project' do
ctx = { current_user: current_user } it do
resolve(described_class, obj: project, args: args, ctx: ctx)
end
describe '#resolve' do
it 'returns the pipelines statistics for a given project' do
result = resolve_statistics(project, {}) result = resolve_statistics(project, {})
expect(result.keys).to contain_exactly( expect(result.keys).to contain_exactly(
:week_pipelines_labels, :week_pipelines_labels,
@ -42,14 +37,67 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
:pipeline_times_values :pipeline_times_values
) )
end end
end
shared_examples 'it returns nils' do
it do
result = resolve_statistics(project, {})
expect(result).to be_nil
end
end
def resolve_statistics(project, args)
ctx = { current_user: current_user }
resolve(described_class, obj: project, args: args, ctx: ctx)
end
describe '#resolve' do
it_behaves_like 'returns the pipelines statistics for a given project'
context 'when the user does not have access to the CI/CD analytics data' do context 'when the user does not have access to the CI/CD analytics data' do
let(:current_user) { guest } let(:current_user) { guest }
it 'returns nil' do it_behaves_like 'it returns nils'
result = resolve_statistics(project, {}) end
expect(result).to be_nil context 'when the project is public' do
let_it_be(:project) { create(:project, :public) }
context 'public pipelines are disabled' do
before do
project.update!(public_builds: false)
end
context 'user is not a member' do
let(:current_user) { create(:user) }
it_behaves_like 'it returns nils'
end
context 'user is a guest' do
let(:current_user) { guest }
it_behaves_like 'it returns nils'
end
context 'user is a reporter or above' do
let(:current_user) { reporter }
it_behaves_like 'returns the pipelines statistics for a given project'
end
end
context 'public pipelines are enabled' do
before do
project.update!(public_builds: true)
end
context 'user is not a member' do
let(:current_user) { create(:user) }
it_behaves_like 'returns the pipelines statistics for a given project'
end
end end
end end
end end

View 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

View file

@ -9,7 +9,7 @@ RSpec.describe Banzai::Filter::KrokiFilter do
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000") stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>") doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">' expect(doc.to_s).to eq '<img class="js-render-kroki" src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
end end
it 'replaces nomnoml pre tag with img tag if both kroki and plantuml are enabled' do it 'replaces nomnoml pre tag with img tag if both kroki and plantuml are enabled' do
@ -19,7 +19,7 @@ RSpec.describe Banzai::Filter::KrokiFilter do
plantuml_url: "http://localhost:8080") plantuml_url: "http://localhost:8080")
doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>") doc = filter("<pre lang='nomnoml'><code>[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]</code></pre>")
expect(doc.to_s).to eq '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">' expect(doc.to_s).to eq '<img class="js-render-kroki" src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KzhUlCITkpNLEqJ1dWNLkgsKsoviUUSs7KLTssvzVHIzS8tyYjligUAMhEd0g==">'
end end
it 'does not replace nomnoml pre tag with img tag if kroki is disabled' do it 'does not replace nomnoml pre tag with img tag if kroki is disabled' do
@ -38,4 +38,12 @@ RSpec.describe Banzai::Filter::KrokiFilter do
expect(doc.to_s).to eq '<pre lang="plantuml"><code>Bob-&gt;Alice : hello</code></pre>' expect(doc.to_s).to eq '<pre lang="plantuml"><code>Bob-&gt;Alice : hello</code></pre>'
end end
it 'adds hidden attribute when content size is large' do
stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
text = '[Pirate|eyeCount: Int|raid();pillage()|\n [beard]--[parrot]\n [beard]-:>[foul mouth]\n]' * 25
doc = filter("<pre lang='nomnoml'><code>#{text}</code></pre>")
expect(doc.to_s).to eq '<img class="js-render-kroki" src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KyJyVNQiE5KTSxKidXVjS5ILCrKL4lFFrSyi07LL81RyM0vLckAysRGjxo8avCowaMGjxo8avCowaMGU8lgAE7mIdc=" hidden>'
end
end end

View file

@ -132,6 +132,12 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>') expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>')
end end
it "escape sourcepos metadata to prevent XSS" do
result = filter('<pre data-sourcepos="&#34;%22 href=&#34;x&#34;></pre><base href=http://unsafe-website.com/><pre x=&#34;"><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"&gt;&lt;/pre&gt;&lt;base href=http://unsafe-website.com/&gt;&lt;pre x="\' class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code></code></pre><copy-code></copy-code></div>')
end
end end
context "when Rouge lexing fails" do context "when Rouge lexing fails" do

View file

@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceRedactor do
end end
context 'when data-original attribute provided' do context 'when data-original attribute provided' do
let(:original_content) { '<code>foo</code>' } let(:original_content) { '&lt;script&gt;alert(1);&lt;/script&gt;' }
it 'replaces redacted reference with original content' do it 'replaces redacted reference with original content' do
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>") doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>")

View file

@ -87,7 +87,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end end
context 'when IP is already banned' do context 'when IP is already banned' do
subject { gl_auth.find_for_git_client('username', Gitlab::Password.test_default, project: nil, ip: 'ip') } subject { gl_auth.find_for_git_client('username', 'password', project: nil, ip: 'ip') }
before do before do
expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter| expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
@ -204,16 +204,16 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end end
it 'recognizes master passwords' do it 'recognizes master passwords' do
user = create(:user, password: Gitlab::Password.test_default) user = create(:user, password: 'password')
expect(gl_auth.find_for_git_client(user.username, Gitlab::Password.test_default, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
end end
include_examples 'user login operation with unique ip limit' do include_examples 'user login operation with unique ip limit' do
let(:user) { create(:user, password: Gitlab::Password.test_default) } let(:user) { create(:user, password: 'password') }
def operation def operation
expect(gl_auth.find_for_git_client(user.username, Gitlab::Password.test_default, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities) expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
end end
end end
@ -477,7 +477,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
:user, :user,
:blocked, :blocked,
username: 'normal_user', username: 'normal_user',
password: Gitlab::Password.test_default password: 'my-secret'
) )
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@ -486,7 +486,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'when 2fa is enabled globally' do context 'when 2fa is enabled globally' do
let_it_be(:user) do let_it_be(:user) do
create(:user, username: 'normal_user', password: Gitlab::Password.test_default, otp_grace_period_started_at: 1.day.ago) create(:user, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
end end
before do before do
@ -510,7 +510,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'when 2fa is enabled personally' do context 'when 2fa is enabled personally' do
let(:user) do let(:user) do
create(:user, :two_factor, username: 'normal_user', password: Gitlab::Password.test_default, otp_grace_period_started_at: 1.day.ago) create(:user, :two_factor, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
end end
it 'fails' do it 'fails' do
@ -523,7 +523,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
user = create( user = create(
:user, :user,
username: 'normal_user', username: 'normal_user',
password: Gitlab::Password.test_default password: 'my-secret'
) )
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@ -534,7 +534,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
user = create( user = create(
:user, :user,
username: 'oauth2', username: 'oauth2',
password: Gitlab::Password.test_default password: 'my-secret'
) )
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@ -609,7 +609,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'when deploy token and user have the same username' do context 'when deploy token and user have the same username' do
let(:username) { 'normal_user' } let(:username) { 'normal_user' }
let(:user) { create(:user, username: username, password: Gitlab::Password.test_default) } let(:user) { create(:user, username: username, password: 'my-secret') }
let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) } let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }
it 'succeeds for the token' do it 'succeeds for the token' do
@ -622,7 +622,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'succeeds for the user' do it 'succeeds for the user' do
auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities } auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities }
expect(gl_auth.find_for_git_client(username, Gitlab::Password.test_default, project: project, ip: 'ip')) expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
.to have_attributes(auth_success) .to have_attributes(auth_success)
end end
end end
@ -816,7 +816,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end end
let(:username) { 'John' } # username isn't lowercase, test this let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { Gitlab::Password.test_default } let(:password) { 'my-secret' }
it "finds user by valid login/password" do it "finds user by valid login/password" do
expect(gl_auth.find_with_user_password(username, password)).to eql user expect(gl_auth.find_with_user_password(username, password)).to eql user
@ -941,13 +941,13 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it "does not find user by using ldap as fallback to for authentication" do it "does not find user by using ldap as fallback to for authentication" do
expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(nil) expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(nil)
expect(gl_auth.find_with_user_password('ldap_user', Gitlab::Password.test_default)).to be_nil expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil
end end
it "find new user by using ldap as fallback to for authentication" do it "find new user by using ldap as fallback to for authentication" do
expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(user) expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(user)
expect(gl_auth.find_with_user_password('ldap_user', Gitlab::Password.test_default)).to eq(user) expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user)
end end
end end

View file

@ -127,6 +127,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) } let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) }
context 'when file is empty' do context 'when file is empty' do
let(:params) { { artifact: 'secret_stuff/generated.yml', job: 'generator' } }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_stuff', 'masked' => true }]) }
let(:context) do
Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline, variables: variables)
end
before do before do
allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader| allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
allow(reader).to receive(:read).and_return('') allow(reader).to receive(:read).and_return('')
@ -134,7 +140,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
end end
let(:expected_error) do let(:expected_error) do
'File `generated.yml` is empty!' 'File `xxxxxxxxxxxx/generated.yml` is empty!'
end end
it_behaves_like 'is invalid' it_behaves_like 'is invalid'

View file

@ -3,7 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Base do RSpec.describe Gitlab::Ci::Config::External::File::Base do
let(:context_params) { { sha: 'HEAD' } } let(:variables) { }
let(:context_params) { { sha: 'HEAD', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:test_class) do let(:test_class) do
@ -76,7 +77,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
end end
context 'when there are YAML syntax errors' do context 'when there are YAML syntax errors' do
let(:location) { 'some/file/config.yml' } let(:location) { 'some/file/secret_file_name.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file_name', 'masked' => true }]) }
before do before do
allow_any_instance_of(test_class) allow_any_instance_of(test_class)
@ -85,7 +87,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
it 'is not a valid file' do it 'is not a valid file' do
expect(subject).not_to be_valid expect(subject).not_to be_valid
expect(subject.error_message).to match /does not have valid YAML syntax/ expect(subject.error_message).to eq('Included file `some/file/xxxxxxxxxxxxxxxx.yml` does not have valid YAML syntax!')
end end
end end
end end

View file

@ -7,6 +7,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:sha) { '12345' } let(:sha) { '12345' }
let(:variables) { project.predefined_variables.to_runner_variables }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { local: location } } let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) } let(:local_file) { described_class.new(params, context) }
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
sha: sha, sha: sha,
user: user, user: user,
parent_pipeline: parent_pipeline, parent_pipeline: parent_pipeline,
variables: project.predefined_variables.to_runner_variables variables: variables
} }
end end
@ -66,7 +67,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
end end
end end
context 'when is not a valid local path' do context 'when it is not a valid local path' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
it 'returns false' do it 'returns false' do
@ -74,13 +75,23 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
end end
end end
context 'when is not a yaml file' do context 'when it is not a yaml file' do
let(:location) { '/config/application.rb' } let(:location) { '/config/application.rb' }
it 'returns false' do it 'returns false' do
expect(local_file.valid?).to be_falsy expect(local_file.valid?).to be_falsy
end end
end end
context 'when it is an empty file' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret', 'masked' => true }]) }
let(:location) { '/lib/gitlab/ci/templates/secret/existent-file.yml' }
it 'returns false and adds an error message about an empty file' do
allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("")
expect(local_file.errors).to include("Local file `/lib/gitlab/ci/templates/xxxxxx/existent-file.yml` is empty!")
end
end
end end
describe '#content' do describe '#content' do
@ -116,10 +127,11 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
end end
describe '#error_message' do describe '#error_message' do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } let(:location) { '/lib/gitlab/ci/templates/secret_file.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
it 'returns an error message' do it 'returns an error message' do
expect(local_file.error_message).to eq("Local file `#{location}` does not exist!") expect(local_file.error_message).to eq("Local file `/lib/gitlab/ci/templates/xxxxxxxxxxx.yml` does not exist!")
end end
end end

View file

@ -11,6 +11,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
let(:parent_pipeline) { double(:parent_pipeline) } let(:parent_pipeline) { double(:parent_pipeline) }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:project_file) { described_class.new(params, context) } let(:project_file) { described_class.new(params, context) }
let(:variables) { project.predefined_variables.to_runner_variables }
let(:context_params) do let(:context_params) do
{ {
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
sha: '12345', sha: '12345',
user: context_user, user: context_user,
parent_pipeline: parent_pipeline, parent_pipeline: parent_pipeline,
variables: project.predefined_variables.to_runner_variables variables: variables
} }
end end
@ -108,18 +109,19 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
context 'when an empty file is used' do context 'when an empty file is used' do
let(:params) do let(:params) do
{ project: project.full_path, file: '/file.yml' } { project: project.full_path, file: '/secret_file.yml' }
end end
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
let(:root_ref_sha) { project.repository.root_ref_sha } let(:root_ref_sha) { project.repository.root_ref_sha }
before do before do
stub_project_blob(root_ref_sha, '/file.yml') { '' } stub_project_blob(root_ref_sha, '/secret_file.yml') { '' }
end end
it 'returns false' do it 'returns false' do
expect(project_file).not_to be_valid expect(project_file).not_to be_valid
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!") expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxx.yml` is empty!")
end end
end end
@ -135,13 +137,15 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
end end
context 'when non-existing file is requested' do context 'when non-existing file is requested' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-invalid-file', 'masked' => true }]) }
let(:params) do let(:params) do
{ project: project.full_path, file: '/invalid-file.yml' } { project: project.full_path, file: '/secret-invalid-file.yml' }
end end
it 'returns false' do it 'returns false' do
expect(project_file).not_to be_valid expect(project_file).not_to be_valid
expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!") expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxxxxxxxxxx.yml` does not exist!")
end end
end end

View file

@ -5,11 +5,12 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Remote do RSpec.describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests include StubRequests
let(:context_params) { { sha: '12345' } } let(:variables) {Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
let(:context_params) { { sha: '12345', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { remote: location } } let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) } let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' } let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.secret_file.yml' }
let(:remote_file_content) do let(:remote_file_content) do
<<~HEREDOC <<~HEREDOC
before_script: before_script:
@ -144,10 +145,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
subject { remote_file.error_message } subject { remote_file.error_message }
context 'when remote file location is not valid' do context 'when remote file location is not valid' do
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' } let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/?secret_file.yml' }
it 'returns an error message describing invalid address' do it 'returns an error message describing invalid address' do
expect(subject).to match /does not have a valid address!/ expect(subject).to eq('Remote file `not-valid://gitlab.com/gitlab-org/gitlab-foss/blob/1234/?xxxxxxxxxxx.yml` does not have a valid address!')
end end
end end
@ -157,7 +158,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
end end
it 'returns error message about a timeout' do it 'returns error message about a timeout' do
expect(subject).to match /could not be fetched because of a timeout error!/ expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of a timeout error!')
end end
end end
@ -167,7 +168,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
end end
it 'returns error message about a HTTP error' do it 'returns error message about a HTTP error' do
expect(subject).to match /could not be fetched because of HTTP error!/ expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of HTTP error!')
end end
end end
@ -177,7 +178,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
end end
it 'returns error message about a timeout' do it 'returns error message about a timeout' do
expect(subject).to match /could not be fetched because of HTTP code `404` error!/ expect(subject).to eq('Remote file `https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.xxxxxxxxxxx.yml` could not be fetched because of HTTP code `404` error!')
end end
end end

View file

@ -54,11 +54,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template do
end end
context 'with invalid template name' do context 'with invalid template name' do
let(:template) { 'Template.yml' } let(:template) { 'SecretTemplate.yml' }
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'SecretTemplate', 'masked' => true }]) }
let(:context_params) { { project: project, sha: '12345', user: user, variables: variables } }
it 'returns false' do it 'returns false' do
expect(template_file).not_to be_valid expect(template_file).not_to be_valid
expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!') expect(template_file.error_message).to include('`xxxxxxxxxxxxxx.yml` is not a valid location!')
end end
end end

View file

@ -11,7 +11,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' } let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' } let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' } let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
let(:context_params) { { project: project, sha: '123456', user: user, variables: project.predefined_variables } } let(:variables) { project.predefined_variables }
let(:context_params) { { project: project, sha: '123456', user: user, variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:file_content) do let(:file_content) do
@ -92,13 +93,16 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end end
context 'when the key is a hash of file and remote' do context 'when the key is a hash of file and remote' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-file', 'masked' => true }]) }
let(:local_file) { 'secret-file.yml' }
let(:remote_url) { 'https://gitlab.com/secret-file.yml' }
let(:values) do let(:values) do
{ include: { 'local' => local_file, 'remote' => remote_url }, { include: { 'local' => local_file, 'remote' => remote_url },
image: 'ruby:2.7' } image: 'ruby:2.7' }
end end
it 'returns ambigious specification error' do it 'returns ambigious specification error' do
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError) expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, 'Include `{"local":"xxxxxxxxxxx.yml","remote":"https://gitlab.com/xxxxxxxxxxx.yml"}` needs to match exactly one accessor!')
end end
end end

View file

@ -34,10 +34,33 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFiles do
cleanup.run! cleanup.run!
end end
it 'finds artifacts on disk' do it 'finds job artifacts on disk' do
artifact = create(:ci_job_artifact, :archive) artifact = create(:ci_job_artifact, :archive)
artifact_directory = artifact.file.relative_path.to_s.split('/')[0...6].join('/')
cleaned = []
expect(cleanup).to receive(:find_artifacts).and_wrap_original do |original_method, *args, &block|
original_method.call(*args) { |dir| cleaned << dir }
end
cleanup.run!
expect(cleaned).to include(/#{artifact_directory}/)
end
it 'does not find pipeline artifacts on disk' do
artifact = create(:ci_pipeline_artifact, :with_coverage_report)
# using 0...6 to match the -min/maxdepth 6 strictly, since this is one directory
# deeper than job artifacts, and .dirname would not match
artifact_directory = artifact.file.relative_path.to_s.split('/')[0...6].join('/')
expect(cleanup).to receive(:find_artifacts).and_wrap_original do |original_method, *args, &block|
# this can either _not_ yield at all, or yield with any other file
# except the one that we're explicitly excluding
original_method.call(*args) { |path| expect(path).not_to match(artifact_directory) }
end
expect(cleanup).to receive(:find_artifacts).and_yield(artifact.file.path)
cleanup.run! cleanup.run!
end end

View file

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

View file

@ -301,5 +301,48 @@ RSpec.describe Gitlab::ErrorTracking do
end end
end end
end end
context 'when processing invalid URI exceptions' do
let(:invalid_uri) { 'http://foo:bar' }
let(:sentry_exception_values) { sentry_event['exception']['values'] }
context 'when the error is a URI::InvalidURIError' do
let(:exception) do
URI.parse(invalid_uri)
rescue URI::InvalidURIError => error
error
end
it 'filters the URI from the error message' do
track_exception
expect(sentry_exception_values).to include(
hash_including(
'type' => 'URI::InvalidURIError',
'value' => 'bad URI(is not URI?): [FILTERED]'
)
)
end
end
context 'when the error is a Addressable::URI::InvalidURIError' do
let(:exception) do
Addressable::URI.parse(invalid_uri)
rescue Addressable::URI::InvalidURIError => error
error
end
it 'filters the URI from the error message' do
track_exception
expect(sentry_exception_values).to include(
hash_including(
'type' => 'Addressable::URI::InvalidURIError',
'value' => 'Invalid port number: [FILTERED]'
)
)
end
end
end
end end
end end

View file

@ -22,6 +22,14 @@ RSpec.describe Gitlab::ExceptionLogFormatter do
expect(payload['exception.sql']).to be_nil expect(payload['exception.sql']).to be_nil
end end
it 'cleans the exception message' do
expect(Gitlab::Sanitizers::ExceptionMessage).to receive(:clean).with('RuntimeError', 'bad request').and_return('cleaned')
described_class.format!(exception, payload)
expect(payload['exception.message']).to eq('cleaned')
end
context 'when exception is ActiveRecord::StatementInvalid' do context 'when exception is ActiveRecord::StatementInvalid' do
let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') } let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }

View file

@ -17,7 +17,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
"notification_level" => 3, "notification_level" => 3,
"created_at" => "2016-03-11T10:21:44.822Z", "created_at" => "2016-03-11T10:21:44.822Z",
"updated_at" => "2016-03-11T10:21:44.822Z", "updated_at" => "2016-03-11T10:21:44.822Z",
"created_by_id" => nil, "created_by_id" => 1,
"invite_email" => nil, "invite_email" => nil,
"invite_token" => nil, "invite_token" => nil,
"invite_accepted_at" => nil, "invite_accepted_at" => nil,
@ -38,10 +38,24 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
"notification_level" => 3, "notification_level" => 3,
"created_at" => "2016-03-11T10:21:44.822Z", "created_at" => "2016-03-11T10:21:44.822Z",
"updated_at" => "2016-03-11T10:21:44.822Z", "updated_at" => "2016-03-11T10:21:44.822Z",
"created_by_id" => 1, "created_by_id" => 2,
"invite_email" => 'invite@test.com', "invite_email" => 'invite@test.com',
"invite_token" => 'token', "invite_token" => 'token',
"invite_accepted_at" => nil "invite_accepted_at" => nil
},
{
"id" => 3,
"access_level" => 40,
"source_id" => 14,
"source_type" => source_type,
"user_id" => nil,
"notification_level" => 3,
"created_at" => "2016-03-11T10:21:44.822Z",
"updated_at" => "2016-03-11T10:21:44.822Z",
"created_by_id" => nil,
"invite_email" => 'invite2@test.com',
"invite_token" => 'token',
"invite_accepted_at" => nil
}] }]
end end
@ -68,12 +82,37 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil
end end
it 'removes old user_id from member_hash to avoid conflict with user key' do it 'maps created_by_id to user on new instance' do
expect(member_class) expect(member_class)
.to receive(:create) .to receive(:create)
.twice .once
.with(hash_excluding('user_id')) .with(hash_including('user_id' => user2.id, 'created_by_id' => nil))
.and_call_original .and_call_original
expect(member_class)
.to receive(:create)
.once
.with(hash_including('invite_email' => 'invite@test.com', 'created_by_id' => nil))
.and_call_original
expect(member_class)
.to receive(:create)
.once
.with(hash_including('invite_email' => 'invite2@test.com', 'created_by_id' => nil))
.and_call_original
members_mapper.map
end
it 'replaced user_id with user_id from new instance' do
expect(member_class)
.to receive(:create)
.once
.with(hash_including('user_id' => user2.id))
.and_call_original
expect(member_class)
.to receive(:create)
.twice
.with(hash_excluding('user_id'))
.and_call_original
members_mapper.map members_mapper.map
end end
@ -99,7 +138,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
end end
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(user2.id))).once expect(logger).to receive(:info).with(hash_including(expected_log_params.call(user2.id))).once
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(nil))).once expect(logger).to receive(:info).with(hash_including(expected_log_params.call(nil))).twice
members_mapper.map members_mapper.map
end end

View 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

View file

@ -49,7 +49,7 @@ RSpec.describe Emails::Profile do
describe 'for users that signed up, the email' do describe 'for users that signed up, the email' do
let(:example_site_path) { root_path } let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: Gitlab::Password.test_default) } let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) } subject { Notify.new_user_email(new_user.id) }

View file

@ -22,7 +22,7 @@ RSpec.describe Ci::DeletedObject, :aggregate_failures do
expect(deleted_artifact.file_store).to eq(artifact.file_store) expect(deleted_artifact.file_store).to eq(artifact.file_store)
expect(deleted_artifact.store_dir).to eq(artifact.file.store_dir.to_s) expect(deleted_artifact.store_dir).to eq(artifact.file.store_dir.to_s)
expect(deleted_artifact.file_identifier).to eq(artifact.file_identifier) expect(deleted_artifact.file_identifier).to eq(artifact.file_identifier)
expect(deleted_artifact.pick_up_at).to eq(artifact.expire_at) expect(deleted_artifact.pick_up_at).to be_like_time(artifact.expire_at)
end end
end end

View file

@ -29,15 +29,25 @@ RSpec.describe Ci::Runner do
context 'when runner is not allowed to pick untagged jobs' do context 'when runner is not allowed to pick untagged jobs' do
context 'when runner does not have tags' do context 'when runner does not have tags' do
let(:runner) { build(:ci_runner, tag_list: [], run_untagged: false) }
it 'is not valid' do
expect(runner).to be_invalid
end
end
context 'when runner has too many tags' do
let(:runner) { build(:ci_runner, tag_list: (1..::Ci::Runner::TAG_LIST_MAX_LENGTH + 1).map { |i| "tag#{i}" }, run_untagged: false) }
it 'is not valid' do it 'is not valid' do
runner = build(:ci_runner, tag_list: [], run_untagged: false)
expect(runner).to be_invalid expect(runner).to be_invalid
end end
end end
context 'when runner has tags' do context 'when runner has tags' do
let(:runner) { build(:ci_runner, tag_list: ['tag'], run_untagged: false) }
it 'is valid' do it 'is valid' do
runner = build(:ci_runner, tag_list: ['tag'], run_untagged: false)
expect(runner).to be_valid expect(runner).to be_valid
end end
end end

Some files were not shown because too many files have changed in this diff Show more