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

View file

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

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

View file

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

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
export const DEFAULT_PARSE_RULE_PRIORITY = 50;
export const HIGHER_PARSE_RULE_PRIORITY = 1 + DEFAULT_PARSE_RULE_PRIORITY;
export const unrestrictedPages = [
// Group wiki
'groups:wikis:show',
'groups:wikis:edit',
'groups:wikis:create',
// Project wiki
'projects:wikis:show',
'projects:wikis:edit',
'projects:wikis:create',
// Project files
'projects:show',
'projects:blob:show',
];

View file

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

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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,
email: FFaker::Internet.email,
confirmed_at: DateTime.now,
password: Gitlab::Password.test_default
password: '12345678'
)
::AbuseReport.create(reporter: ::User.take, user: reported_user, message: 'User sends spam')

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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.
payload.merge!(
'exception.class' => exception.class.name,
'exception.message' => exception.message
'exception.message' => sanitize_message(exception)
)
if exception.backtrace
@ -38,6 +38,10 @@ module Gitlab
rescue PgQuery::ParseError
sql
end
def sanitize_message(exception)
Gitlab::Sanitizers::ExceptionMessage.clean(exception.class.name, exception.message)
end
end
end
end

View file

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

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,
email: FFaker::Internet.email,
confirmed_at: DateTime.now,
password: Gitlab::Password.test_default
password: Devise.friendly_token
)
end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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))
fill_in 'New password', with: "new" + Gitlab::Password.test_default
fill_in 'Confirm new password', with: "new" + Gitlab::Password.test_default
fill_in 'New password', with: 'hello1234'
fill_in 'Confirm new password', with: 'hello1234'
click_button 'Change your password'

View file

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

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

View file

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

View file

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

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 }
before_all do
before do
project.add_guest(guest)
project.add_reporter(reporter)
end
@ -20,13 +20,8 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
expect(described_class).to have_nullable_graphql_type(::Types::Ci::AnalyticsType)
end
def resolve_statistics(project, args)
ctx = { current_user: current_user }
resolve(described_class, obj: project, args: args, ctx: ctx)
end
describe '#resolve' do
it 'returns the pipelines statistics for a given project' do
shared_examples 'returns the pipelines statistics for a given project' do
it do
result = resolve_statistics(project, {})
expect(result.keys).to contain_exactly(
:week_pipelines_labels,
@ -42,14 +37,67 @@ RSpec.describe Resolvers::ProjectPipelineStatisticsResolver do
:pipeline_times_values
)
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
let(:current_user) { guest }
it 'returns nil' do
result = resolve_statistics(project, {})
it_behaves_like 'it returns nils'
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

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

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>')
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
context "when Rouge lexing fails" do

View file

@ -35,7 +35,7 @@ RSpec.describe Banzai::ReferenceRedactor do
end
context 'when data-original attribute provided' do
let(:original_content) { '<code>foo</code>' }
let(:original_content) { '&lt;script&gt;alert(1);&lt;/script&gt;' }
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>")

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

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

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.store_dir).to eq(artifact.file.store_dir.to_s)
expect(deleted_artifact.file_identifier).to eq(artifact.file_identifier)
expect(deleted_artifact.pick_up_at).to eq(artifact.expire_at)
expect(deleted_artifact.pick_up_at).to be_like_time(artifact.expire_at)
end
end

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 does not have tags' do
let(:runner) { build(:ci_runner, tag_list: [], run_untagged: false) }
it 'is not valid' do
expect(runner).to be_invalid
end
end
context 'when runner has too many tags' do
let(:runner) { build(:ci_runner, tag_list: (1..::Ci::Runner::TAG_LIST_MAX_LENGTH + 1).map { |i| "tag#{i}" }, run_untagged: false) }
it 'is not valid' do
runner = build(:ci_runner, tag_list: [], run_untagged: false)
expect(runner).to be_invalid
end
end
context 'when runner has tags' do
let(:runner) { build(:ci_runner, tag_list: ['tag'], run_untagged: false) }
it 'is valid' do
runner = build(:ci_runner, tag_list: ['tag'], run_untagged: false)
expect(runner).to be_valid
end
end

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