Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6225d57e55
commit
e3bd590af4
|
@ -569,11 +569,13 @@
|
|||
- <<: *if-merge-request-labels-run-review-app
|
||||
- <<: *if-auto-deploy-branches
|
||||
- changes: *code-qa-patterns
|
||||
- changes: *workhorse-patterns
|
||||
|
||||
.frontend:rules:compile-test-assets:
|
||||
rules:
|
||||
- changes: *code-backstage-qa-patterns
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- changes: *code-backstage-qa-patterns
|
||||
- changes: *workhorse-patterns
|
||||
|
||||
.frontend:rules:compile-test-assets-as-if-foss:
|
||||
rules:
|
||||
|
@ -583,6 +585,7 @@
|
|||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- changes: *code-backstage-qa-patterns
|
||||
- changes: *startup-css-patterns
|
||||
- changes: *workhorse-patterns
|
||||
|
||||
.frontend:rules:compile-test-assets-as-if-jh:
|
||||
rules:
|
||||
|
@ -604,6 +607,7 @@
|
|||
allow_failure: true
|
||||
- changes: *startup-css-patterns
|
||||
allow_failure: true
|
||||
- changes: *workhorse-patterns
|
||||
|
||||
.frontend:rules:default-frontend-jobs:
|
||||
rules:
|
||||
|
@ -1335,8 +1339,9 @@
|
|||
|
||||
.rails:rules:detect-tests:
|
||||
rules:
|
||||
- changes: *code-backstage-qa-patterns
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- changes: *code-backstage-qa-patterns
|
||||
- changes: *workhorse-patterns
|
||||
|
||||
.rails:rules:detect-previous-failed-tests:
|
||||
rules:
|
||||
|
@ -1803,6 +1808,8 @@
|
|||
allow_failure: true
|
||||
- changes: *startup-css-patterns
|
||||
allow_failure: true
|
||||
- changes: *workhorse-patterns
|
||||
allow_failure: true
|
||||
|
||||
#######################
|
||||
# Test metadata rules #
|
||||
|
@ -1810,7 +1817,7 @@
|
|||
.test-metadata:rules:retrieve-tests-metadata:
|
||||
rules:
|
||||
- changes: *code-backstage-patterns
|
||||
when: on_success
|
||||
- changes: *workhorse-patterns
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
|
||||
.test-metadata:rules:update-tests-metadata:
|
||||
|
@ -1827,8 +1834,7 @@
|
|||
###################
|
||||
.workhorse:rules:workhorse:
|
||||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *workhorse-patterns
|
||||
- changes: *workhorse-patterns
|
||||
|
||||
###################
|
||||
# yaml-lint rules #
|
||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -2,6 +2,27 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 14.7.2 (2022-02-08)
|
||||
|
||||
### Added (1 change)
|
||||
|
||||
- [Allow self-hosted instances to render same-origin Iframe](gitlab-org/gitlab@eb7c78363cdfc670286967872d8458fc5f6d82e8) ([merge request](gitlab-org/gitlab!79966))
|
||||
|
||||
### Fixed (4 changes)
|
||||
|
||||
- [Geo: Fix reverify object stored files](gitlab-org/gitlab@603700dcca3b8f25a3b80b44b11a73df549c0cb3) ([merge request](gitlab-org/gitlab!79966)) **GitLab Enterprise Edition**
|
||||
- [Geo: Fix verification failures of remote stored files](gitlab-org/gitlab@2eb8ac7e88dcd40f0e8266966655962e4d6e3171) ([merge request](gitlab-org/gitlab!79966)) **GitLab Enterprise Edition**
|
||||
- [GitLab Version - CE Admin Dashboard [RUN ALL RSPEC] [RUN AS-IF-FOSS]](gitlab-org/gitlab@f2253ce2d729fa202a26b54f3ca870b932ea1855) ([merge request](gitlab-org/gitlab!79966))
|
||||
- [Fix cluster integration HTTP adapter](gitlab-org/gitlab@c05027ef4d7ec35fc16e8e16dc6e5af201f665c3) ([merge request](gitlab-org/gitlab!79966))
|
||||
|
||||
### Changed (1 change)
|
||||
|
||||
- [Update to ruby-magic v0.5.4](gitlab-org/gitlab@ced6ef1001730dc2851f58f7db3229d1c585b9d3) ([merge request](gitlab-org/gitlab!79966))
|
||||
|
||||
### Removed (1 change)
|
||||
|
||||
- [Disable sandboxed_mermaid feature flag by default](gitlab-org/gitlab@70c40d43169bd48d360ed7a6a03c33c05d5e3738) ([merge request](gitlab-org/gitlab!79966))
|
||||
|
||||
## 14.7.1 (2022-02-03)
|
||||
|
||||
### Security
|
||||
|
|
|
@ -1 +1 @@
|
|||
269b04ae5248eea05fe2d6ca02e33fdd3f6cee76
|
||||
32b9777c9f3f217324d95d6e25b6ed1ddee13f68
|
||||
|
|
|
@ -3,7 +3,7 @@ const viewers = {
|
|||
image: () => import('./image_viewer.vue'),
|
||||
video: () => import('./video_viewer.vue'),
|
||||
empty: () => import('./empty_viewer.vue'),
|
||||
text: () => import('~/vue_shared/components/source_viewer.vue'),
|
||||
text: () => import('~/vue_shared/components/source_viewer/source_viewer.vue'),
|
||||
pdf: () => import('./pdf_viewer.vue'),
|
||||
lfs: () => import('./lfs_viewer.vue'),
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
|
|||
rawSize
|
||||
rawTextBlob
|
||||
fileType
|
||||
language
|
||||
path
|
||||
editBlobPath
|
||||
ideEditPath
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// Language map from Rouge::Lexer to highlight.js
|
||||
// Rouge::Lexer - We use it on the BE to determine the language of a source file (https://github.com/rouge-ruby/rouge/blob/master/docs/Languages.md).
|
||||
// Highlight.js - We use it on the FE to highlight the syntax of a source file (https://github.com/highlightjs/highlight.js/tree/main/src/languages).
|
||||
export const ROUGE_TO_HLJS_LANGUAGE_MAP = {
|
||||
bsl: '1c',
|
||||
actionscript: 'actionscript',
|
||||
ada: 'ada',
|
||||
apache: 'apache',
|
||||
applescript: 'applescript',
|
||||
armasm: 'armasm',
|
||||
awk: 'awk',
|
||||
c: 'c',
|
||||
ceylon: 'ceylon',
|
||||
clean: 'clean',
|
||||
clojure: 'clojure',
|
||||
cmake: 'cmake',
|
||||
coffeescript: 'coffeescript',
|
||||
coq: 'coq',
|
||||
cpp: 'cpp',
|
||||
crystal: 'crystal',
|
||||
csharp: 'csharp',
|
||||
css: 'css',
|
||||
d: 'd',
|
||||
dart: 'dart',
|
||||
pascal: 'delphi',
|
||||
diff: 'diff',
|
||||
jinja: 'django',
|
||||
docker: 'dockerfile',
|
||||
batchfile: 'dos',
|
||||
elixir: 'elixir',
|
||||
elm: 'elm',
|
||||
erb: 'erb',
|
||||
erlang: 'erlang',
|
||||
fortran: 'fortran',
|
||||
fsharp: 'fsharp',
|
||||
gherkin: 'gherkin',
|
||||
glsl: 'glsl',
|
||||
go: 'go',
|
||||
gradle: 'gradle',
|
||||
groovy: 'groovy',
|
||||
haml: 'haml',
|
||||
handlebars: 'handlebars',
|
||||
haskell: 'haskell',
|
||||
haxe: 'haxe',
|
||||
http: 'http',
|
||||
hylang: 'hy',
|
||||
ini: 'ini',
|
||||
isbl: 'isbl',
|
||||
java: 'java',
|
||||
javascript: 'javascript',
|
||||
json: 'json',
|
||||
julia: 'julia',
|
||||
kotlin: 'kotlin',
|
||||
lasso: 'lasso',
|
||||
tex: 'latex',
|
||||
common_lisp: 'lisp',
|
||||
livescript: 'livescript',
|
||||
llvm: 'llvm',
|
||||
hlsl: 'lsl',
|
||||
lua: 'lua',
|
||||
make: 'makefile',
|
||||
markdown: 'markdown',
|
||||
mathematica: 'mathematica',
|
||||
matlab: 'matlab',
|
||||
moonscript: 'moonscript',
|
||||
nginx: 'nginx',
|
||||
nim: 'nim',
|
||||
nix: 'nix',
|
||||
objective_c: 'objectivec',
|
||||
ocaml: 'ocaml',
|
||||
perl: 'perl',
|
||||
php: 'php',
|
||||
plaintext: 'plaintext',
|
||||
pony: 'pony',
|
||||
powershell: 'powershell',
|
||||
prolog: 'prolog',
|
||||
properties: 'properties',
|
||||
protobuf: 'protobuf',
|
||||
puppet: 'puppet',
|
||||
python: 'python',
|
||||
q: 'q',
|
||||
qml: 'qml',
|
||||
r: 'r',
|
||||
reasonml: 'reasonml',
|
||||
ruby: 'ruby',
|
||||
rust: 'rust',
|
||||
sas: 'sas',
|
||||
scala: 'scala',
|
||||
scheme: 'scheme',
|
||||
scss: 'scss',
|
||||
shell: 'shell',
|
||||
smalltalk: 'smalltalk',
|
||||
sml: 'sml',
|
||||
sqf: 'sqf',
|
||||
sql: 'sql',
|
||||
stan: 'stan',
|
||||
stata: 'stata',
|
||||
swift: 'swift',
|
||||
tap: 'tap',
|
||||
tcl: 'tcl',
|
||||
twig: 'twig',
|
||||
typescript: 'typescript',
|
||||
vala: 'vala',
|
||||
vb: 'vbnet',
|
||||
verilog: 'verilog',
|
||||
vhdl: 'vhdl',
|
||||
viml: 'vim',
|
||||
xml: 'xml',
|
||||
xquery: 'xquery',
|
||||
yaml: 'yaml',
|
||||
};
|
|
@ -1,14 +1,16 @@
|
|||
<script>
|
||||
import { GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
|
||||
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from './constants';
|
||||
import { wrapLines } from './utils';
|
||||
|
||||
const LINE_SELECT_CLASS_NAME = 'hll';
|
||||
const PLAIN_TEXT_LANGUAGE = 'plaintext';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LineNumbers,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
directives: {
|
||||
SafeHtml: GlSafeHtmlDirective,
|
||||
|
@ -18,17 +20,12 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
autoDetect: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true, // We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
languageDefinition: null,
|
||||
content: this.blob.rawTextBlob,
|
||||
language: this.blob.language || PLAIN_TEXT_LANGUAGE,
|
||||
language: ROUGE_TO_HLJS_LANGUAGE_MAP[this.blob.language],
|
||||
hljs: null,
|
||||
};
|
||||
},
|
||||
|
@ -40,14 +37,14 @@ export default {
|
|||
let highlightedContent;
|
||||
|
||||
if (this.hljs) {
|
||||
if (this.autoDetect) {
|
||||
if (!this.language) {
|
||||
highlightedContent = this.hljs.highlightAuto(this.content).value;
|
||||
} else if (this.languageDefinition) {
|
||||
highlightedContent = this.hljs.highlight(this.content, { language: this.language }).value;
|
||||
}
|
||||
}
|
||||
|
||||
return this.wrapLines(highlightedContent);
|
||||
return wrapLines(highlightedContent);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
@ -61,14 +58,14 @@ export default {
|
|||
async mounted() {
|
||||
this.hljs = await this.loadHighlightJS();
|
||||
|
||||
if (!this.autoDetect) {
|
||||
if (this.language) {
|
||||
this.languageDefinition = await this.loadLanguage();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadHighlightJS() {
|
||||
// With auto-detect enabled we load all common languages else we load only the core (smallest footprint)
|
||||
return this.autoDetect ? import('highlight.js/lib/common') : import('highlight.js/lib/core');
|
||||
// If no language can be mapped to highlight.js we load all common languages else we load only the core (smallest footprint)
|
||||
return !this.language ? import('highlight.js/lib/common') : import('highlight.js/lib/core');
|
||||
},
|
||||
async loadLanguage() {
|
||||
let languageDefinition;
|
||||
|
@ -82,15 +79,6 @@ export default {
|
|||
|
||||
return languageDefinition;
|
||||
},
|
||||
wrapLines(content) {
|
||||
return (
|
||||
content &&
|
||||
content
|
||||
.split('\n')
|
||||
.map((line, i) => `<span id="LC${i + 1}" class="line">${line}</span>`)
|
||||
.join('\r\n')
|
||||
);
|
||||
},
|
||||
selectLine() {
|
||||
const hash = sanitize(this.$route.hash);
|
||||
const lineToSelect = hash && this.$el.querySelector(hash);
|
||||
|
@ -113,7 +101,9 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-loading-icon v-if="!highlightedContent" size="sm" class="gl-my-5" />
|
||||
<div
|
||||
v-else
|
||||
class="file-content code js-syntax-highlight blob-content gl-display-flex"
|
||||
:class="$options.userColorScheme"
|
||||
data-type="simple"
|
|
@ -0,0 +1,26 @@
|
|||
export const wrapLines = (content) => {
|
||||
return (
|
||||
content &&
|
||||
content
|
||||
.split('\n')
|
||||
.map((line, i) => {
|
||||
let formattedLine;
|
||||
const idAttribute = `id="LC${i + 1}"`;
|
||||
|
||||
if (line.includes('<span class="hljs') && !line.includes('</span>')) {
|
||||
/**
|
||||
* In some cases highlight.js will wrap multiple lines in a span, in these cases we want to append the line number to the existing span
|
||||
*
|
||||
* example (before): <span class="hljs-code">```bash
|
||||
* example (after): <span id="LC67" class="hljs-code">```bash
|
||||
*/
|
||||
formattedLine = line.replace(/(?=class="hljs)/, `${idAttribute} `);
|
||||
} else {
|
||||
formattedLine = `<span ${idAttribute} class="line">${line}</span>`;
|
||||
}
|
||||
|
||||
return formattedLine;
|
||||
})
|
||||
.join('\n')
|
||||
);
|
||||
};
|
|
@ -720,7 +720,7 @@ $calendar-activity-colors: (
|
|||
#7fa8c9,
|
||||
#527ba0,
|
||||
#254e77,
|
||||
);
|
||||
) !default;
|
||||
|
||||
/*
|
||||
* Commit Page
|
||||
|
|
|
@ -259,3 +259,11 @@ $line-removed-dark: $red-200;
|
|||
|
||||
$well-expand-item: $gray-200;
|
||||
$well-inner-border: $gray-200;
|
||||
|
||||
$calendar-activity-colors: (
|
||||
#303030,
|
||||
#333861,
|
||||
#4a5593,
|
||||
#6172c5,
|
||||
#788ff7
|
||||
);
|
||||
|
|
|
@ -423,7 +423,9 @@ module ApplicationSettingsHelper
|
|||
:sidekiq_job_limiter_compression_threshold_bytes,
|
||||
:sidekiq_job_limiter_limit_bytes,
|
||||
:suggest_pipeline_enabled,
|
||||
:user_email_lookup_limit
|
||||
:user_email_lookup_limit,
|
||||
:users_get_by_id_limit,
|
||||
:users_get_by_id_limit_allowlist_raw
|
||||
].tap do |settings|
|
||||
settings << :deactivate_dormant_users unless Gitlab.com?
|
||||
end
|
||||
|
|
|
@ -563,6 +563,12 @@ class ApplicationSetting < ApplicationRecord
|
|||
presence: true, length: { maximum: 255 },
|
||||
if: :sentry_enabled?
|
||||
|
||||
validates :users_get_by_id_limit,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
validates :users_get_by_id_limit_allowlist,
|
||||
length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
|
||||
allow_nil: false
|
||||
|
||||
attr_encrypted :asset_proxy_secret_key,
|
||||
mode: :per_attribute_iv,
|
||||
key: Settings.attr_encrypted_db_key_base_truncated,
|
||||
|
|
|
@ -231,7 +231,9 @@ module ApplicationSettingImplementation
|
|||
rate_limiting_response_text: nil,
|
||||
whats_new_variant: 0,
|
||||
user_deactivation_emails_enabled: true,
|
||||
user_email_lookup_limit: 60
|
||||
user_email_lookup_limit: 60,
|
||||
users_get_by_id_limit: 300,
|
||||
users_get_by_id_limit_allowlist: []
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -334,6 +336,14 @@ module ApplicationSettingImplementation
|
|||
self.notes_create_limit_allowlist = strings_to_array(values).map(&:downcase)
|
||||
end
|
||||
|
||||
def users_get_by_id_limit_allowlist_raw
|
||||
array_to_string(self.users_get_by_id_limit_allowlist)
|
||||
end
|
||||
|
||||
def users_get_by_id_limit_allowlist_raw=(values)
|
||||
self.users_get_by_id_limit_allowlist = strings_to_array(values).map(&:downcase)
|
||||
end
|
||||
|
||||
def asset_proxy_whitelist=(values)
|
||||
values = strings_to_array(values) if values.is_a?(String)
|
||||
|
||||
|
|
|
@ -118,7 +118,12 @@ class InstanceConfiguration
|
|||
group_export_download: application_setting_limit_per_minute(:group_download_export_limit),
|
||||
group_import: application_setting_limit_per_minute(:group_import_limit),
|
||||
raw_blob: application_setting_limit_per_minute(:raw_blob_request_limit),
|
||||
user_email_lookup: application_setting_limit_per_minute(:user_email_lookup_limit)
|
||||
user_email_lookup: application_setting_limit_per_minute(:user_email_lookup_limit),
|
||||
users_get_by_id: {
|
||||
enabled: application_settings[:users_get_by_id_limit] > 0,
|
||||
requests_per_period: application_settings[:users_get_by_id_limit],
|
||||
period_in_seconds: 10.minutes
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
module Projects
|
||||
module ImportExport
|
||||
class ProjectExportPresenter < Gitlab::View::Presenter::Delegated
|
||||
# NOTE: This is needed because this presenter is serialized to JSON,
|
||||
# and we need to make sure that `#as_json` is called in this class so
|
||||
# it will use the overriden attributes below. Otherwise the call is
|
||||
# delegated to the model and will use the original methods.
|
||||
include ActiveModel::Serializers::JSON
|
||||
|
||||
presents ::Project, as: :project
|
||||
|
||||
# TODO: Remove `ActiveModel::Serializers::JSON` inclusion as it's duplicate
|
||||
delegator_override_with ActiveModel::Serializers::JSON
|
||||
delegator_override_with ActiveModel::Naming
|
||||
delegator_override :include_root_in_json, :include_root_in_json?
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
= f.label :notes_create_limit_allowlist, _('Users to exclude from the rate limit'), class: 'label-bold'
|
||||
= f.text_area :notes_create_limit_allowlist_raw, placeholder: 'username1, username2', class: 'form-control gl-form-input', rows: 5
|
||||
.form-text.text-muted
|
||||
= _('Comma-separated list of users allowed to exceed the rate limit.')
|
||||
= _('List of users allowed to exceed the rate limit.')
|
||||
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-users-api-limits-settings'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :users_get_by_id_limit, _('Maximum requests per 10 minutes per user'), class: 'label-bold'
|
||||
= f.number_field :users_get_by_id_limit, class: 'form-control gl-form-input'
|
||||
.form-group
|
||||
= f.label :users_get_by_id_limit_allowlist_raw, _('Users to exclude from the rate limit'), class: 'label-bold'
|
||||
= f.text_area :users_get_by_id_limit_allowlist_raw, placeholder: 'username1, username2', class: 'form-control gl-form-input', rows: 5
|
||||
.form-text.text-muted
|
||||
= _('List of users allowed to exceed the rate limit.')
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
|
|
@ -122,6 +122,18 @@
|
|||
.settings-content
|
||||
= render 'note_limits'
|
||||
|
||||
%section.settings.as-users-api-limits.no-animate#js-users-api-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||
.settings-header
|
||||
%h4
|
||||
= _('Users API rate limit')
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
|
||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Set the per-user rate limit for getting a user by ID via the API.')
|
||||
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/rate_limit_on_users_api.md'), target: '_blank', rel: 'noopener noreferrer'
|
||||
.settings-content
|
||||
= render 'users_api_limits'
|
||||
|
||||
%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||
.settings-header
|
||||
%h4
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
- link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
|
||||
= link_to link_text, polymorphic_path([:leave, source, :members]),
|
||||
method: :delete,
|
||||
data: { confirm: leave_confirmation_message(source), qa_selector: 'leave_group_link' },
|
||||
aria: { label: link_text },
|
||||
data: { confirm: leave_confirmation_message(source), confirm_btn_variant: 'danger', qa_selector: 'leave_group_link' },
|
||||
class: 'js-leave-link'
|
||||
- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord
|
||||
= link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
module MergeRequests
|
||||
class UpdateHeadPipelineWorker
|
||||
include ApplicationWorker
|
||||
include Gitlab::EventStore::Subscriber
|
||||
|
||||
feature_category :code_review
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: enforce_security_report_validation
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79798
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351000
|
||||
milestone: '14.8'
|
||||
type: development
|
||||
group: group::threat insights
|
||||
default_enabled: false
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddUsersGetByIdLimitToApplicationSetting < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
add_column :application_settings, :users_get_by_id_limit, :integer, null: false, default: 300
|
||||
add_column :application_settings, :users_get_by_id_limit_allowlist, :text, array: true, limit: 255, null: false, default: []
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :application_settings, :users_get_by_id_limit
|
||||
remove_column :application_settings, :users_get_by_id_limit_allowlist
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
01cc0139097235991fa2caf8b780ccd1c3ce580647197418424ade83ce9be77e
|
|
@ -10620,6 +10620,8 @@ CREATE TABLE application_settings (
|
|||
project_runner_token_expiration_interval integer,
|
||||
ecdsa_sk_key_restriction integer DEFAULT 0 NOT NULL,
|
||||
ed25519_sk_key_restriction integer DEFAULT 0 NOT NULL,
|
||||
users_get_by_id_limit integer DEFAULT 300 NOT NULL,
|
||||
users_get_by_id_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
|
||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
|
||||
|
|
|
@ -232,7 +232,6 @@ the event safely via the `handle_event` method. For example:
|
|||
```ruby
|
||||
module MergeRequests
|
||||
class UpdateHeadPipelineWorker
|
||||
include ApplicationWorker
|
||||
include Gitlab::EventStore::Subscriber
|
||||
|
||||
def handle_event(event)
|
||||
|
|
|
@ -64,6 +64,30 @@ Please use your best judgment when to use it and please contribute new points th
|
|||
- [ ] Follow up on issues that came out of the review. Create issues for discovered edge cases that should be covered in future iterations.
|
||||
```
|
||||
|
||||
### Code deletion checklist
|
||||
|
||||
When your merge request deletes code, it's important to also delete all
|
||||
related code that is no longer used.
|
||||
When deleting Haml and Vue code, check whether it contains the following types of
|
||||
code that is unused:
|
||||
|
||||
- CSS.
|
||||
|
||||
For example, we've deleted a Vue component that contained the `.mr-card` class, which is now unused.
|
||||
The `.mr-card` CSS rule set should then be deleted from `merge_requests.scss`.
|
||||
|
||||
- Ruby variables.
|
||||
|
||||
Deleting unused Ruby variables is important so we don't continue instantiating them with
|
||||
potentially expensive code.
|
||||
|
||||
For example, we've deleted a Haml template that used the `@total_count` Ruby variable.
|
||||
The `@total_count` variable was no longer used in the remaining templates for the page.
|
||||
The instantiation of `@total_count` in `issues_controller.rb` should then be deleted so that we
|
||||
don't make unnecessary database calls to calculate the count of issues.
|
||||
|
||||
- Ruby methods.
|
||||
|
||||
### Merge Request Review
|
||||
|
||||
With the purpose of being [respectful of others' time](https://about.gitlab.com/handbook/values/#be-respectful-of-others-time) please follow these guidelines when asking for a review:
|
||||
|
|
|
@ -137,6 +137,7 @@ The **Network** settings contain:
|
|||
- [Incident Management Limits](../../../operations/incident_management/index.md) - Limit the
|
||||
number of inbound alerts that can be sent to a project.
|
||||
- [Notes creation limit](rate_limit_on_notes_creation.md) - Set a rate limit on the note creation requests.
|
||||
- [Get single user limit](rate_limit_on_users_api.md) - Set a rate limit on users API endpoint to get a user by ID.
|
||||
|
||||
### Preferences
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
type: reference
|
||||
stage: Manage
|
||||
group: Authentication & Authorization
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Rate limits on Users API **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78364) in GitLab 14.8.
|
||||
|
||||
You can configure the per-user rate limit for requests to the users API endpoint to get a user by ID.
|
||||
|
||||
To change getting a single user rate limit:
|
||||
|
||||
1. On the top bar, select **Menu > Admin**.
|
||||
1. On the left sidebar, select **Settings > Network**.
|
||||
1. Expand **Users API rate limit**.
|
||||
1. In the **Maximum requests per 10 minutes** text box, enter the new value.
|
||||
1. Optional. In the **Users to exclude from the rate limit** box, list users allowed to exceed the limit.
|
||||
1. Select **Save changes**.
|
||||
|
||||
This limit is:
|
||||
|
||||
- Applied independently per user.
|
||||
- Not applied per IP address.
|
||||
|
||||
The default value is `300`.
|
||||
|
||||
Requests over the rate limit are logged into the `auth.log` file.
|
||||
|
||||
For example, if you set a limit of 300, requests to the `GET /users/:id` API endpoint
|
||||
exceeding a rate of 300 per 10 minutes are blocked. Access to the endpoint is allowed after ten minutes have elapsed.
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 75 KiB |
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
|
@ -14,6 +14,9 @@ As with all projects, the items mentioned on this page are subject to change or
|
|||
The development, release, and timing of any products, features, or functionality remain at the
|
||||
sole discretion of GitLab Inc.
|
||||
|
||||
NOTE:
|
||||
Workspace is currently in development.
|
||||
|
||||
Workspace will be above the [top-level namespaces](../group/index.md#namespaces) for you to manage
|
||||
everything you do as a GitLab administrator, including:
|
||||
|
||||
|
@ -27,33 +30,12 @@ Our goal is to reach feature parity between SaaS and self-managed installations,
|
|||
- Hardware Controls. For functionality that does not apply to groups, Hardware Controls are only
|
||||
applicable to self-managed installations. There is one Hardware Controls section per installation.
|
||||
|
||||
NOTE:
|
||||
Workspace is currently in development.
|
||||
To learn about the current state of workspace development,
|
||||
see [epic 4257](https://gitlab.com/groups/gitlab-org/-/epics/4257).
|
||||
|
||||
## Demo: New hierarchy concept for groups and projects for epics
|
||||
|
||||
The following demo introduces the new hierarchy concept for groups and projects for epics.
|
||||
|
||||
<div class="video-fallback">
|
||||
See the video: <a href="https://www.youtube.com/embed/fE74lsG_8yM">Consolidating groups and projects update (2021-08-23)</a>.
|
||||
</div>
|
||||
<figure class="video-container">
|
||||
<iframe src="https://www.youtube.com/embed/fE74lsG_8yM" frameborder="0" allowfullscreen="true"> </iframe>
|
||||
</figure>
|
||||
|
||||
## Concept previews
|
||||
|
||||
The following provide a preview to the Workspace concept.
|
||||
|
||||
![Workspace Overview](img/1.1-Instance_overview.png)
|
||||
|
||||
![Groups Overview](img/1.2-Groups_overview.png)
|
||||
|
||||
![Admin Overview](img/1.3-Admin.png)
|
||||
|
||||
![Admin Overview](img/Admin_Settings.png)
|
||||
|
||||
![Admin Overview](img/hardware_settings.png)
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For a video introduction to the new hierarchy concept for groups and projects for epics, see
|
||||
[Consolidating groups and projects update (August 2021)](https://www.youtube.com/watch?v=fE74lsG_8yM).
|
||||
|
||||
## Related topics
|
||||
|
||||
|
|
|
@ -177,6 +177,7 @@ module API
|
|||
optional :floc_enabled, type: Grape::API::Boolean, desc: 'Enable FloC (Federated Learning of Cohorts)'
|
||||
optional :user_deactivation_emails_enabled, type: Boolean, desc: 'Send emails to users upon account deactivation'
|
||||
optional :suggest_pipeline_enabled, type: Boolean, desc: 'Enable pipeline suggestion banner'
|
||||
optional :users_get_by_id_limit, type: Integer, desc: "Maximum number of calls to the /users/:id API per 10 minutes per user. Set to 0 for unlimited requests."
|
||||
|
||||
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
|
||||
optional :"#{type}_key_restriction",
|
||||
|
|
|
@ -143,7 +143,12 @@ module API
|
|||
forbidden!('Not authorized!') unless current_user
|
||||
|
||||
if Feature.enabled?(:rate_limit_user_by_id_endpoint, type: :development)
|
||||
check_rate_limit! :users_get_by_id, scope: current_user unless current_user.admin?
|
||||
unless current_user.admin?
|
||||
check_rate_limit!(:users_get_by_id,
|
||||
scope: current_user,
|
||||
users_allowlist: Gitlab::CurrentSettings.current_application_settings.users_get_by_id_limit_allowlist
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
user = User.find_by(id: params[:id])
|
||||
|
|
|
@ -14,7 +14,7 @@ module Gitlab
|
|||
# Threshold value can be either an Integer or a Proc
|
||||
# in order to not evaluate it's value every time this method is called
|
||||
# and only do that when it's needed.
|
||||
def rate_limits
|
||||
def rate_limits # rubocop:disable Metrics/AbcSize
|
||||
{
|
||||
issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute },
|
||||
notes_create: { threshold: -> { application_settings.notes_create_limit }, interval: 1.minute },
|
||||
|
@ -32,7 +32,7 @@ module Gitlab
|
|||
group_testing_hook: { threshold: 5, interval: 1.minute },
|
||||
profile_add_new_email: { threshold: 5, interval: 1.minute },
|
||||
web_hook_calls: { interval: 1.minute },
|
||||
users_get_by_id: { threshold: 10, interval: 1.minute },
|
||||
users_get_by_id: { threshold: -> { application_settings.users_get_by_id_limit }, interval: 10.minutes },
|
||||
username_exists: { threshold: 20, interval: 1.minute },
|
||||
user_sign_up: { threshold: 20, interval: 1.minute },
|
||||
profile_resend_email_confirmation: { threshold: 5, interval: 1.minute },
|
||||
|
|
|
@ -42,11 +42,22 @@ module Gitlab
|
|||
attr_reader :json_data, :report, :validate
|
||||
|
||||
def valid?
|
||||
return true if !validate || schema_validator.valid?
|
||||
if Feature.enabled?(:enforce_security_report_validation)
|
||||
if !validate || schema_validator.valid?
|
||||
report.schema_validation_status = :valid_schema
|
||||
true
|
||||
else
|
||||
report.schema_validation_status = :invalid_schema
|
||||
schema_validator.errors.each { |error| report.add_error('Schema', error) }
|
||||
false
|
||||
end
|
||||
else
|
||||
return true if !validate || schema_validator.valid?
|
||||
|
||||
schema_validator.errors.each { |error| report.add_error('Schema', error) }
|
||||
schema_validator.errors.each { |error| report.add_error('Schema', error) }
|
||||
|
||||
false
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def schema_validator
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
14.0.0/dependency-scanning-report-format.json
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
module Security
|
||||
class Report
|
||||
attr_reader :created_at, :type, :pipeline, :findings, :scanners, :identifiers
|
||||
attr_accessor :scan, :scanned_resources, :errors, :analyzer, :version
|
||||
attr_accessor :scan, :scanned_resources, :errors, :analyzer, :version, :schema_validation_status
|
||||
|
||||
delegate :project_id, to: :pipeline
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#
|
||||
# @example:
|
||||
# class SomeEventSubscriber
|
||||
# include ApplicationWorker
|
||||
# include Gitlab::EventStore::Subscriber
|
||||
#
|
||||
# def handle_event(event)
|
||||
|
@ -18,6 +17,14 @@
|
|||
module Gitlab
|
||||
module EventStore
|
||||
module Subscriber
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ApplicationWorker
|
||||
|
||||
loggable_arguments 0, 1
|
||||
end
|
||||
|
||||
def perform(event_type, data)
|
||||
raise InvalidEvent, event_type unless self.class.const_defined?(event_type)
|
||||
|
||||
|
|
|
@ -8751,9 +8751,6 @@ msgstr ""
|
|||
msgid "Comma-separated list of email addresses."
|
||||
msgstr ""
|
||||
|
||||
msgid "Comma-separated list of users allowed to exceed the rate limit."
|
||||
msgstr ""
|
||||
|
||||
msgid "Comma-separated, e.g. '1.1.1.1, 2.2.2.0/24'"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21734,6 +21731,9 @@ msgstr ""
|
|||
msgid "List of all merge commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "List of users allowed to exceed the rate limit."
|
||||
msgstr ""
|
||||
|
||||
msgid "List options"
|
||||
msgstr ""
|
||||
|
||||
|
@ -22361,6 +22361,9 @@ msgstr ""
|
|||
msgid "Maximum push size (MB)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum requests per 10 minutes per user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum requests per minute"
|
||||
msgstr ""
|
||||
|
||||
|
@ -33140,6 +33143,9 @@ msgstr ""
|
|||
msgid "Set the milestone to %{milestone_reference}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the per-user rate limit for getting a user by ID via the API."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the per-user rate limit for notes created by web or API requests."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39585,6 +39591,9 @@ msgstr ""
|
|||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users API rate limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users can launch a development environment from a GitLab browser tab when the %{linkStart}Gitpod%{linkEnd} integration is enabled."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39910,6 +39919,9 @@ msgstr ""
|
|||
msgid "View group labels"
|
||||
msgstr ""
|
||||
|
||||
msgid "View group pipeline usage quota"
|
||||
msgstr ""
|
||||
|
||||
msgid "View incident details at"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ module QA
|
|||
element :copy_contents_button
|
||||
end
|
||||
|
||||
base.view 'app/assets/javascripts/vue_shared/components/source_viewer.vue' do
|
||||
base.view 'app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue' do
|
||||
element :blob_viewer_file_content
|
||||
end
|
||||
|
||||
|
|
|
@ -631,6 +631,20 @@ RSpec.describe 'Admin updates settings' do
|
|||
expect(current_settings.issues_create_limit).to eq(0)
|
||||
end
|
||||
|
||||
it 'changes Users API rate limits settings' do
|
||||
visit network_admin_application_settings_path
|
||||
|
||||
page.within('.as-users-api-limits') do
|
||||
fill_in 'Maximum requests per 10 minutes per user', with: 0
|
||||
fill_in 'Users to exclude from the rate limit', with: 'someone, someone_else'
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
expect(page).to have_content "Application settings saved successfully"
|
||||
expect(current_settings.users_get_by_id_limit).to eq(0)
|
||||
expect(current_settings.users_get_by_id_limit_allowlist).to eq(%w[someone someone_else])
|
||||
end
|
||||
|
||||
shared_examples 'regular throttle rate limit settings' do
|
||||
it 'changes rate limit settings' do
|
||||
visit network_admin_application_settings_path
|
||||
|
|
|
@ -15,7 +15,7 @@ import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
|
|||
import { loadViewer } from '~/repository/components/blob_viewers';
|
||||
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
|
||||
import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
|
||||
import SourceViewer from '~/vue_shared/components/source_viewer.vue';
|
||||
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
|
||||
import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
|
||||
import { redirectTo } from '~/lib/utils/url_utility';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
|
|
|
@ -5,6 +5,7 @@ export const simpleViewerMock = {
|
|||
rawSize: 123,
|
||||
rawTextBlob: 'raw content',
|
||||
fileType: 'text',
|
||||
language: 'javascript',
|
||||
path: 'some_file.js',
|
||||
webPath: 'some_file.js',
|
||||
editBlobPath: 'some_file.js/edit',
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import hljs from 'highlight.js/lib/core';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import SourceViewer from '~/vue_shared/components/source_viewer.vue';
|
||||
import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
|
||||
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
|
||||
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
||||
|
@ -12,43 +14,50 @@ const router = new VueRouter();
|
|||
|
||||
describe('Source Viewer component', () => {
|
||||
let wrapper;
|
||||
const language = 'javascript';
|
||||
const language = 'docker';
|
||||
const mappedLanguage = ROUGE_TO_HLJS_LANGUAGE_MAP[language];
|
||||
const content = `// Some source code`;
|
||||
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content };
|
||||
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
|
||||
|
||||
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
|
||||
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
||||
|
||||
const createComponent = async (props = { autoDetect: false }) => {
|
||||
const createComponent = async (blob = {}) => {
|
||||
wrapper = shallowMountExtended(SourceViewer, {
|
||||
router,
|
||||
propsData: { blob: { ...DEFAULT_BLOB_DATA }, ...props },
|
||||
propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob } },
|
||||
});
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findLineNumbers = () => wrapper.findComponent(LineNumbers);
|
||||
const findHighlightedContent = () => wrapper.findByTestId('test-highlighted');
|
||||
const findFirstLine = () => wrapper.find('#LC1');
|
||||
|
||||
beforeEach(() => createComponent());
|
||||
beforeEach(() => {
|
||||
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
|
||||
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
|
||||
|
||||
return createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => wrapper.destroy());
|
||||
|
||||
describe('highlight.js', () => {
|
||||
it('registers the language definition', async () => {
|
||||
const languageDefinition = await import(`highlight.js/lib/languages/${language}`);
|
||||
const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
|
||||
|
||||
expect(hljs.registerLanguage).toHaveBeenCalledWith(language, languageDefinition.default);
|
||||
expect(hljs.registerLanguage).toHaveBeenCalledWith(
|
||||
mappedLanguage,
|
||||
languageDefinition.default,
|
||||
);
|
||||
});
|
||||
|
||||
it('highlights the content', () => {
|
||||
expect(hljs.highlight).toHaveBeenCalledWith(content, { language });
|
||||
expect(hljs.highlight).toHaveBeenCalledWith(content, { language: mappedLanguage });
|
||||
});
|
||||
|
||||
describe('auto-detect enabled', () => {
|
||||
beforeEach(() => createComponent({ autoDetect: true }));
|
||||
describe('auto-detects if a language cannot be loaded', () => {
|
||||
beforeEach(() => createComponent({ language: 'some_unknown_language' }));
|
||||
|
||||
it('highlights the content with auto-detection', () => {
|
||||
expect(hljs.highlightAuto).toHaveBeenCalledWith(content);
|
||||
|
@ -57,6 +66,13 @@ describe('Source Viewer component', () => {
|
|||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('renders a loading icon if no highlighted content is available yet', async () => {
|
||||
hljs.highlight.mockImplementation(() => ({ value: null }));
|
||||
await createComponent();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders Line Numbers', () => {
|
||||
expect(findLineNumbers().props('lines')).toBe(1);
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
import { wrapLines } from '~/vue_shared/components/source_viewer/utils';
|
||||
|
||||
describe('Wrap lines', () => {
|
||||
it.each`
|
||||
input | output
|
||||
${'line 1'} | ${'<span id="LC1" class="line">line 1</span>'}
|
||||
${'line 1\nline 2'} | ${`<span id="LC1" class="line">line 1</span>\n<span id="LC2" class="line">line 2</span>`}
|
||||
${'<span class="hljs-code">line 1\nline 2</span>'} | ${`<span id="LC1" class="hljs-code">line 1\n<span id="LC2" class="line">line 2</span></span>`}
|
||||
${'<span class="hljs-code">```bash'} | ${'<span id="LC1" class="hljs-code">```bash'}
|
||||
`('returns lines wrapped in spans containing line numbers', ({ input, output }) => {
|
||||
expect(wrapLines(input)).toBe(output);
|
||||
});
|
||||
});
|
|
@ -46,6 +46,15 @@ RSpec.describe ApplicationSettingsHelper do
|
|||
expect(helper.visible_attributes).to include(:deactivate_dormant_users)
|
||||
end
|
||||
|
||||
it 'contains rate limit parameters' do
|
||||
expect(helper.visible_attributes).to include(*%i(
|
||||
issues_create_limit notes_create_limit project_export_limit
|
||||
project_download_export_limit project_export_limit project_import_limit
|
||||
raw_blob_request_limit group_export_limit group_download_export_limit
|
||||
group_import_limit users_get_by_id_limit user_email_lookup_limit
|
||||
))
|
||||
end
|
||||
|
||||
context 'when GitLab.com' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
|
|
|
@ -40,60 +40,142 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
|
|||
allow(validator_class).to receive(:new).and_call_original
|
||||
end
|
||||
|
||||
context 'when the validate flag is set as `false`' do
|
||||
let(:validate) { false }
|
||||
context 'when enforce_security_report_validation is enabled' do
|
||||
before do
|
||||
stub_feature_flags(enforce_security_report_validation: true)
|
||||
end
|
||||
|
||||
it 'does not run the validation logic' do
|
||||
parse_report
|
||||
context 'when the validate flag is set as `true`' do
|
||||
let(:validate) { true }
|
||||
|
||||
expect(validator_class).not_to have_received(:new)
|
||||
it 'instantiates the validator with correct params' do
|
||||
parse_report
|
||||
|
||||
expect(validator_class).to have_received(:new).with(report.type, {})
|
||||
end
|
||||
|
||||
context 'when the report data is valid according to the schema' do
|
||||
let(:valid?) { true }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(validator_class) do |instance|
|
||||
allow(instance).to receive(:valid?).and_return(valid?)
|
||||
allow(instance).to receive(:errors).and_return([])
|
||||
end
|
||||
|
||||
allow(parser).to receive_messages(create_scanner: true, create_scan: true)
|
||||
end
|
||||
|
||||
it 'does not add errors to the report' do
|
||||
expect { parse_report }.not_to change { report.errors }.from([])
|
||||
end
|
||||
|
||||
it 'adds the schema validation status to the report' do
|
||||
parse_report
|
||||
|
||||
expect(report.schema_validation_status).to eq(:valid_schema)
|
||||
end
|
||||
|
||||
it 'keeps the execution flow as normal' do
|
||||
parse_report
|
||||
|
||||
expect(parser).to have_received(:create_scanner)
|
||||
expect(parser).to have_received(:create_scan)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the report data is not valid according to the schema' do
|
||||
let(:valid?) { false }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(validator_class) do |instance|
|
||||
allow(instance).to receive(:valid?).and_return(valid?)
|
||||
allow(instance).to receive(:errors).and_return(['foo'])
|
||||
end
|
||||
|
||||
allow(parser).to receive_messages(create_scanner: true, create_scan: true)
|
||||
end
|
||||
|
||||
it 'adds errors to the report' do
|
||||
expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }])
|
||||
end
|
||||
|
||||
it 'adds the schema validation status to the report' do
|
||||
parse_report
|
||||
|
||||
expect(report.schema_validation_status).to eq(:invalid_schema)
|
||||
end
|
||||
|
||||
it 'does not try to create report entities' do
|
||||
parse_report
|
||||
|
||||
expect(parser).not_to have_received(:create_scanner)
|
||||
expect(parser).not_to have_received(:create_scan)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the validate flag is set as `true`' do
|
||||
let(:validate) { true }
|
||||
let(:valid?) { false }
|
||||
|
||||
context 'when enforce_security_report_validation is disabled' do
|
||||
before do
|
||||
allow_next_instance_of(validator_class) do |instance|
|
||||
allow(instance).to receive(:valid?).and_return(valid?)
|
||||
allow(instance).to receive(:errors).and_return(['foo'])
|
||||
end
|
||||
|
||||
allow(parser).to receive_messages(create_scanner: true, create_scan: true)
|
||||
stub_feature_flags(enforce_security_report_validation: false)
|
||||
end
|
||||
|
||||
it 'instantiates the validator with correct params' do
|
||||
parse_report
|
||||
context 'when the validate flag is set as `false`' do
|
||||
let(:validate) { false }
|
||||
|
||||
expect(validator_class).to have_received(:new).with(report.type, {})
|
||||
end
|
||||
|
||||
context 'when the report data is not valid according to the schema' do
|
||||
it 'adds errors to the report' do
|
||||
expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }])
|
||||
end
|
||||
|
||||
it 'does not try to create report entities' do
|
||||
it 'does not run the validation logic' do
|
||||
parse_report
|
||||
|
||||
expect(parser).not_to have_received(:create_scanner)
|
||||
expect(parser).not_to have_received(:create_scan)
|
||||
expect(validator_class).not_to have_received(:new)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the report data is valid according to the schema' do
|
||||
let(:valid?) { true }
|
||||
context 'when the validate flag is set as `true`' do
|
||||
let(:validate) { true }
|
||||
let(:valid?) { false }
|
||||
|
||||
it 'does not add errors to the report' do
|
||||
expect { parse_report }.not_to change { report.errors }.from([])
|
||||
before do
|
||||
allow_next_instance_of(validator_class) do |instance|
|
||||
allow(instance).to receive(:valid?).and_return(valid?)
|
||||
allow(instance).to receive(:errors).and_return(['foo'])
|
||||
end
|
||||
|
||||
allow(parser).to receive_messages(create_scanner: true, create_scan: true)
|
||||
end
|
||||
|
||||
it 'keeps the execution flow as normal' do
|
||||
it 'instantiates the validator with correct params' do
|
||||
parse_report
|
||||
|
||||
expect(parser).to have_received(:create_scanner)
|
||||
expect(parser).to have_received(:create_scan)
|
||||
expect(validator_class).to have_received(:new).with(report.type, {})
|
||||
end
|
||||
|
||||
context 'when the report data is not valid according to the schema' do
|
||||
it 'adds errors to the report' do
|
||||
expect { parse_report }.to change { report.errors }.from([]).to([{ message: 'foo', type: 'Schema' }])
|
||||
end
|
||||
|
||||
it 'does not try to create report entities' do
|
||||
parse_report
|
||||
|
||||
expect(parser).not_to have_received(:create_scanner)
|
||||
expect(parser).not_to have_received(:create_scan)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the report data is valid according to the schema' do
|
||||
let(:valid?) { true }
|
||||
|
||||
it 'does not add errors to the report' do
|
||||
expect { parse_report }.not_to change { report.errors }.from([])
|
||||
end
|
||||
|
||||
it 'keeps the execution flow as normal' do
|
||||
parse_report
|
||||
|
||||
expect(parser).to have_received(:create_scanner)
|
||||
expect(parser).to have_received(:create_scan)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,6 @@ RSpec.describe Gitlab::EventStore::Store do
|
|||
let(:worker) do
|
||||
stub_const('EventSubscriber', Class.new).tap do |klass|
|
||||
klass.class_eval do
|
||||
include ApplicationWorker
|
||||
include Gitlab::EventStore::Subscriber
|
||||
|
||||
def handle_event(event)
|
||||
|
@ -23,7 +22,6 @@ RSpec.describe Gitlab::EventStore::Store do
|
|||
let(:another_worker) do
|
||||
stub_const('AnotherEventSubscriber', Class.new).tap do |klass|
|
||||
klass.class_eval do
|
||||
include ApplicationWorker
|
||||
include Gitlab::EventStore::Subscriber
|
||||
end
|
||||
end
|
||||
|
@ -32,7 +30,6 @@ RSpec.describe Gitlab::EventStore::Store do
|
|||
let(:unrelated_worker) do
|
||||
stub_const('UnrelatedEventSubscriber', Class.new).tap do |klass|
|
||||
klass.class_eval do
|
||||
include ApplicationWorker
|
||||
include Gitlab::EventStore::Subscriber
|
||||
end
|
||||
end
|
||||
|
@ -253,6 +250,10 @@ RSpec.describe Gitlab::EventStore::Store do
|
|||
|
||||
subject { worker_instance.perform(event_name, data) }
|
||||
|
||||
it 'is a Sidekiq worker' do
|
||||
expect(worker_instance).to be_a(ApplicationWorker)
|
||||
end
|
||||
|
||||
it 'handles the event' do
|
||||
expect(worker_instance).to receive(:handle_event).with(instance_of(event.class))
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ RSpec.describe ApplicationSetting do
|
|||
it { is_expected.not_to allow_value('default' => 101).for(:repository_storages_weighted).with_message("value for 'default' must be between 0 and 100") }
|
||||
it { is_expected.not_to allow_value('default' => 100, shouldntexist: 50).for(:repository_storages_weighted).with_message("can't include: shouldntexist") }
|
||||
|
||||
%i[notes_create_limit user_email_lookup_limit].each do |setting|
|
||||
%i[notes_create_limit user_email_lookup_limit users_get_by_id_limit].each do |setting|
|
||||
it { is_expected.to allow_value(400).for(setting) }
|
||||
it { is_expected.not_to allow_value('two').for(setting) }
|
||||
it { is_expected.not_to allow_value(nil).for(setting) }
|
||||
|
@ -158,6 +158,11 @@ RSpec.describe ApplicationSetting do
|
|||
it { is_expected.not_to allow_value(nil).for(:notes_create_limit_allowlist) }
|
||||
it { is_expected.to allow_value([]).for(:notes_create_limit_allowlist) }
|
||||
|
||||
it { is_expected.to allow_value(many_usernames(100)).for(:users_get_by_id_limit_allowlist) }
|
||||
it { is_expected.not_to allow_value(many_usernames(101)).for(:users_get_by_id_limit_allowlist) }
|
||||
it { is_expected.not_to allow_value(nil).for(:users_get_by_id_limit_allowlist) }
|
||||
it { is_expected.to allow_value([]).for(:users_get_by_id_limit_allowlist) }
|
||||
|
||||
it { is_expected.to allow_value('all_tiers').for(:whats_new_variant) }
|
||||
it { is_expected.to allow_value('current_tier').for(:whats_new_variant) }
|
||||
it { is_expected.to allow_value('disabled').for(:whats_new_variant) }
|
||||
|
|
|
@ -206,7 +206,8 @@ RSpec.describe InstanceConfiguration do
|
|||
group_download_export_limit: 1019,
|
||||
group_import_limit: 1020,
|
||||
raw_blob_request_limit: 1021,
|
||||
user_email_lookup_limit: 1022
|
||||
user_email_lookup_limit: 1022,
|
||||
users_get_by_id_limit: 1023
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -230,6 +231,7 @@ RSpec.describe InstanceConfiguration do
|
|||
expect(rate_limits[:group_import]).to eq({ enabled: true, requests_per_period: 1020, period_in_seconds: 60 })
|
||||
expect(rate_limits[:raw_blob]).to eq({ enabled: true, requests_per_period: 1021, period_in_seconds: 60 })
|
||||
expect(rate_limits[:user_email_lookup]).to eq({ enabled: true, requests_per_period: 1022, period_in_seconds: 60 })
|
||||
expect(rate_limits[:users_get_by_id]).to eq({ enabled: true, requests_per_period: 1023, period_in_seconds: 600 })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -141,7 +141,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
|
|||
personal_access_token_prefix: "GL-",
|
||||
user_deactivation_emails_enabled: false,
|
||||
admin_mode: true,
|
||||
suggest_pipeline_enabled: false
|
||||
suggest_pipeline_enabled: false,
|
||||
users_get_by_id_limit: 456
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -196,6 +197,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
|
|||
expect(json_response['admin_mode']).to be(true)
|
||||
expect(json_response['user_deactivation_emails_enabled']).to be(false)
|
||||
expect(json_response['suggest_pipeline_enabled']).to be(false)
|
||||
expect(json_response['users_get_by_id_limit']).to eq(456)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -499,7 +499,8 @@ RSpec.describe API::Users do
|
|||
let_it_be(:user2, reload: true) { create(:user, username: 'another_user') }
|
||||
|
||||
before do
|
||||
allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:users_get_by_id, scope: user).and_return(false)
|
||||
allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?)
|
||||
.with(:users_get_by_id, scope: user, users_allowlist: []).and_return(false)
|
||||
end
|
||||
|
||||
it "returns a user by id" do
|
||||
|
@ -600,7 +601,7 @@ RSpec.describe API::Users do
|
|||
context 'when the rate limit is not exceeded' do
|
||||
it 'returns a success status' do
|
||||
expect(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:throttled?).with(:users_get_by_id, scope: user)
|
||||
.to receive(:throttled?).with(:users_get_by_id, scope: user, users_allowlist: [])
|
||||
.and_return(false)
|
||||
|
||||
get api("/users/#{user.id}", user)
|
||||
|
@ -613,7 +614,7 @@ RSpec.describe API::Users do
|
|||
context 'when feature flag is enabled' do
|
||||
it 'returns "too many requests" status' do
|
||||
expect(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:throttled?).with(:users_get_by_id, scope: user)
|
||||
.to receive(:throttled?).with(:users_get_by_id, scope: user, users_allowlist: [])
|
||||
.and_return(true)
|
||||
|
||||
get api("/users/#{user.id}", user)
|
||||
|
@ -629,6 +630,24 @@ RSpec.describe API::Users do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'allows users whose username is in the allowlist' do
|
||||
allowlist = [user.username]
|
||||
current_settings = Gitlab::CurrentSettings.current_application_settings
|
||||
|
||||
# Necessary to ensure the same object is returned on each call
|
||||
allow(Gitlab::CurrentSettings).to receive(:current_application_settings).and_return current_settings
|
||||
|
||||
allow(current_settings).to receive(:users_get_by_id_limit_allowlist).and_return(allowlist)
|
||||
|
||||
expect(Gitlab::ApplicationRateLimiter)
|
||||
.to receive(:throttled?).with(:users_get_by_id, scope: user, users_allowlist: allowlist)
|
||||
.and_call_original
|
||||
|
||||
get api("/users/#{user.id}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
|
|
|
@ -475,6 +475,24 @@ RSpec.describe ApplicationSettings::UpdateService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when users_get_by_id_limit and users_get_by_id_limit_allowlist_raw are passed' do
|
||||
let(:params) do
|
||||
{
|
||||
users_get_by_id_limit: 456,
|
||||
users_get_by_id_limit_allowlist_raw: 'someone, someone_else'
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates users_get_by_id_limit and users_get_by_id_limit_allowlist value' do
|
||||
subject.execute
|
||||
|
||||
application_settings.reload
|
||||
|
||||
expect(application_settings.users_get_by_id_limit).to eq(456)
|
||||
expect(application_settings.users_get_by_id_limit_allowlist).to eq(%w[someone someone_else])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when require_admin_approval_after_user_signup changes' do
|
||||
context 'when it goes from enabled to disabled' do
|
||||
let(:params) { { require_admin_approval_after_user_signup: false } }
|
||||
|
|
Loading…
Reference in New Issue