Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e34c22f2ec
commit
65963de2ae
|
|
@ -102,7 +102,6 @@ RSpec/InstanceVariable:
|
|||
- 'spec/rack_servers/puma_spec.rb'
|
||||
- 'spec/requests/api/admin/plan_limits_spec.rb'
|
||||
- 'spec/requests/api/users_spec.rb'
|
||||
- 'spec/requests/git_http_spec.rb'
|
||||
- 'spec/requests/openid_connect_spec.rb'
|
||||
- 'spec/requests/projects/issues/discussions_spec.rb'
|
||||
- 'spec/services/ci/process_sync_events_service_spec.rb'
|
||||
|
|
|
|||
62
CHANGELOG.md
62
CHANGELOG.md
|
|
@ -2,6 +2,27 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 17.6.2 (2024-12-10)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Add guard clause to Wiki#find_page when title is nil](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1be99d9925c659f168dccb4b2cfb3510ac74e7ed)
|
||||
- [Fix 401 errors when installing the GitLab for Jira app](https://gitlab.com/gitlab-org/security/gitlab/-/commit/8e15de4128733083fe3bf640751aecf95d5471a7)
|
||||
|
||||
### Security (11 changes)
|
||||
|
||||
- [Add timeout around Parslet in template parser](https://gitlab.com/gitlab-org/security/gitlab/-/commit/74de080527cf262ecec44e97c78705953cfa1cdc) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4653))
|
||||
- [Add authorization check to protectableBranches field](https://gitlab.com/gitlab-org/security/gitlab/-/commit/16152cf39642bd4dc9ed023d52493c9522ef87f2) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4652))
|
||||
- [Check harbor name & digest for path traversal](https://gitlab.com/gitlab-org/security/gitlab/-/commit/734520792bc637580fd79ce2d368268501382d76) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4629))
|
||||
- [Ignore titles for GFM links in rich text editor](https://gitlab.com/gitlab-org/security/gitlab/-/commit/769b309ded5f3fca7f550ef9972750cd60298b73) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4649))
|
||||
- [Restrict user and group creation when same pages unique domain exist](https://gitlab.com/gitlab-org/security/gitlab/-/commit/09997ce510251b8f58343464143e40f1f5ed00c2) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4618))
|
||||
- [DoS by repeatedly sending unauthenticated requests for diff-files of a commit or merge request](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c0045078225c4b64fa1dd2582c246df5b7b4a96a) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4639))
|
||||
- [Add query to filter_parameters](https://gitlab.com/gitlab-org/security/gitlab/-/commit/32485a34d6f3ee22fdbe20d0a41cd6b10f0cd511) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4625))
|
||||
- [Added invalid redirect fragment check](https://gitlab.com/gitlab-org/security/gitlab/-/commit/5c69fef592ceab17eaeda04fd78e120116229b03) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4609))
|
||||
- [Make confidential threads unresolvable via new issue](https://gitlab.com/gitlab-org/security/gitlab/-/commit/1396d48051a02153a9bd064d39d2d5c09233f3c6) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4633))
|
||||
- [Do not set session cookie for /v2 endpoints in the response](https://gitlab.com/gitlab-org/security/gitlab/-/commit/3305b0fafe245a02fa01a5b882e8ad5b565f8736) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4630))
|
||||
- [HTML injection in vulnerability details, leads to XSS on self hosted servers](https://gitlab.com/gitlab-org/security/gitlab/-/commit/4284532cd6ae8f0166806a81628887f82756ceef) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4619))
|
||||
|
||||
## 17.6.1 (2024-11-26)
|
||||
|
||||
### Security (6 changes)
|
||||
|
|
@ -995,6 +1016,26 @@ entry.
|
|||
- [Quarantine a flaky test](https://gitlab.com/gitlab-org/gitlab/-/commit/7427f68ca476bd1294900155a2a93b470ef888a6) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165742))
|
||||
- [Quarantine a flaky test](https://gitlab.com/gitlab-org/gitlab/-/commit/81ccade46593d99c938fd8ab03c2e299f6f62377) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164711))
|
||||
|
||||
## 17.5.4 (2024-12-10)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
||||
- [Fix 401 errors when installing the GitLab for Jira app](https://gitlab.com/gitlab-org/security/gitlab/-/commit/5499b8941f6d0dec42bbd7469ca806890edae35e)
|
||||
|
||||
### Security (11 changes)
|
||||
|
||||
- [Add timeout around Parslet in template parser](https://gitlab.com/gitlab-org/security/gitlab/-/commit/b9ce9e051da449add787b16f7cf2d08f8eb11115) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4654))
|
||||
- [Add authorization check to protectableBranches field](https://gitlab.com/gitlab-org/security/gitlab/-/commit/3f870e741e15034bca056fba125a0badbbe264bf) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4595))
|
||||
- [Check harbor name & digest for path traversal](https://gitlab.com/gitlab-org/security/gitlab/-/commit/2257cdf16e6ddbfdfddbbecd694e30589581be4e) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4628))
|
||||
- [Ignore titles for GFM links in rich text editor](https://gitlab.com/gitlab-org/security/gitlab/-/commit/2215af32dfa6074844e4b39a5ce12dc8b2590d09) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4650))
|
||||
- [Restrict user and group creation when same pages unique domain exist](https://gitlab.com/gitlab-org/security/gitlab/-/commit/c7c6fbba10470644b4d532b3ba1aa00240bde391) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4576))
|
||||
- [DoS by repeatedly sending unauthenticated requests for diff-files of a commit or merge request](https://gitlab.com/gitlab-org/security/gitlab/-/commit/8f0c1b73b4e2584aba7866653828b15283d10a90) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4638))
|
||||
- [Add query to filter_parameters](https://gitlab.com/gitlab-org/security/gitlab/-/commit/707d7792996ebe8e4c8da2a587810e3339432352) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4626))
|
||||
- [Added invalid redirect fragment check](https://gitlab.com/gitlab-org/security/gitlab/-/commit/e2760b5a3425f50c3444ff264d4e3381f11894ea) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4605))
|
||||
- [Make confidential threads unresolvable via new issue](https://gitlab.com/gitlab-org/security/gitlab/-/commit/a7ff5a159f7d699eec9e9844e5ab0727219ecb91) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4634))
|
||||
- [Do not set session cookie for /v2 endpoints in the response](https://gitlab.com/gitlab-org/security/gitlab/-/commit/542c5b0dbc4744dab0d89bc42b34bfe16e760e54) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4631))
|
||||
- [HTML injection in vulnerability details, leads to XSS on self hosted servers](https://gitlab.com/gitlab-org/security/gitlab/-/commit/f7e572e94c2360b93fe6e04a65b9874975382693) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4553))
|
||||
|
||||
## 17.5.3 (2024-11-26)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
|
@ -1764,6 +1805,27 @@ entry.
|
|||
- [Adjust signup page items for more clarity](https://gitlab.com/gitlab-org/gitlab/-/commit/e272c8a4c7b243758454d6f15363d0c13ca05c04) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165202)) **GitLab Enterprise Edition**
|
||||
- [Removes Unused CSS class](https://gitlab.com/gitlab-org/gitlab/-/commit/4e17154650ee4afc8b1ae4238d27efb908855a19) by @NIKU-SINGH ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164637))
|
||||
|
||||
## 17.4.6 (2024-12-10)
|
||||
|
||||
### Fixed (2 changes)
|
||||
|
||||
- [Add param filtering to avoid error while saving project settings](https://gitlab.com/gitlab-org/security/gitlab/-/commit/4787ee4000679f645aa1eaa1f1d07bfc34c461cd) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173428)) **GitLab Enterprise Edition**
|
||||
- [Fix 401 errors when installing the GitLab for Jira app](https://gitlab.com/gitlab-org/security/gitlab/-/commit/601e8e20637690102b5118d638e290f68f79fb43)
|
||||
|
||||
### Security (11 changes)
|
||||
|
||||
- [Add timeout around Parslet in template parser](https://gitlab.com/gitlab-org/security/gitlab/-/commit/f974f850463f267b5a636f28c99cac61c4ef6259) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4655))
|
||||
- [Add authorization check to protectableBranches field](https://gitlab.com/gitlab-org/security/gitlab/-/commit/e6a47ce0dbdc4da3e8838451194203709c56fc5d) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4596))
|
||||
- [Check harbor name & digest for path traversal](https://gitlab.com/gitlab-org/security/gitlab/-/commit/cb40c0144b6bf27b49a7745d61fcf37dbe84e8d2) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4642))
|
||||
- [Ignore titles for GFM links in rich text editor](https://gitlab.com/gitlab-org/security/gitlab/-/commit/551e6018a99c91918f0f9a2f177ee237ae897246) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4651))
|
||||
- [Restrict user and group creation when same pages unique domain exist](https://gitlab.com/gitlab-org/security/gitlab/-/commit/495025a35f59b39fcfb6a49077a067c246f9fe06) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4577))
|
||||
- [DoS by repeatedly sending unauthenticated requests for diff-files of a commit or merge request](https://gitlab.com/gitlab-org/security/gitlab/-/commit/01fa899f15e792ce2c54dae3d3db85cb00a49789) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4637))
|
||||
- [Add query to filter_parameters](https://gitlab.com/gitlab-org/security/gitlab/-/commit/322db9627a33a74d73e48ef05d87269191328346) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4627))
|
||||
- [Added invalid redirect fragment check](https://gitlab.com/gitlab-org/security/gitlab/-/commit/f690a49166c32965403070699436d8328768cd69) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4606))
|
||||
- [Make confidential threads unresolvable via new issue](https://gitlab.com/gitlab-org/security/gitlab/-/commit/b055634ab615a20599b0403570b5a8b27b812ec2) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4635))
|
||||
- [Do not set session cookie for /v2 endpoints in the response](https://gitlab.com/gitlab-org/security/gitlab/-/commit/d6dd0f12d146021074a4a36412b6e3cae9782001) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4632))
|
||||
- [HTML injection in vulnerability details, leads to XSS on self hosted servers](https://gitlab.com/gitlab-org/security/gitlab/-/commit/7a6bd953a1f70b58b2fd48d58431fadb9e8249f8) ([merge request](https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/4516))
|
||||
|
||||
## 17.4.5 (2024-11-26)
|
||||
|
||||
### Security (6 changes)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
d3839f7e054c608c98399cff2b4c07aa18f102ed
|
||||
351ea2b5585c837394a616f08fa16bdbb28808aa
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ export default {
|
|||
[Image.name]: __('Edit image description'),
|
||||
[Video.name]: __('Edit video description'),
|
||||
},
|
||||
replaceLabels: {
|
||||
[Audio.name]: __('Replace audio'),
|
||||
[Image.name]: __('Replace image'),
|
||||
[Video.name]: __('Replace video'),
|
||||
},
|
||||
deleteLabels: {
|
||||
[Audio.name]: __('Delete audio'),
|
||||
[DrawioDiagram.name]: __('Delete diagram'),
|
||||
|
|
@ -85,9 +80,6 @@ export default {
|
|||
editLabel() {
|
||||
return this.$options.i18n.editLabels[this.mediaType];
|
||||
},
|
||||
replaceLabel() {
|
||||
return this.$options.i18n.replaceLabels[this.mediaType];
|
||||
},
|
||||
deleteLabel() {
|
||||
return this.$options.i18n.deleteLabels[this.mediaType];
|
||||
},
|
||||
|
|
@ -178,10 +170,6 @@ export default {
|
|||
this.uploadProgress = 0;
|
||||
},
|
||||
|
||||
replaceMedia() {
|
||||
this.$refs.fileSelector.click();
|
||||
},
|
||||
|
||||
editDiagram() {
|
||||
this.tiptapEditor.chain().focus().createOrEditDiagram().run();
|
||||
},
|
||||
|
|
@ -275,18 +263,6 @@ export default {
|
|||
icon="diagram"
|
||||
@click="editDiagram"
|
||||
/>
|
||||
<gl-button
|
||||
v-else
|
||||
v-gl-tooltip
|
||||
variant="default"
|
||||
category="tertiary"
|
||||
size="medium"
|
||||
data-testid="replace-media"
|
||||
:aria-label="replaceLabel"
|
||||
:title="replaceLabel"
|
||||
icon="retry"
|
||||
@click="replaceMedia"
|
||||
/>
|
||||
</gl-button-group>
|
||||
<gl-form v-else class="bubble-menu-form gl-w-full gl-p-4" @submit.prevent="saveEditedMedia">
|
||||
<gl-form-group :label="__('URL')" label-for="media-src">
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ export default Link.extend({
|
|||
},
|
||||
title: {
|
||||
title: null,
|
||||
parseHTML: (element) => element.getAttribute('title'),
|
||||
parseHTML: (element) =>
|
||||
element.classList.contains('gfm') ? null : element.getAttribute('title'),
|
||||
},
|
||||
// only for gollum links (wikis)
|
||||
isGollumLink: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import { ApolloLink } from '@apollo/client/core';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
|
||||
const REQUEST = 'graphql.request';
|
||||
const RESPONSE = 'graphql.response';
|
||||
|
||||
const addGraphqlBreadcrumb = (category, data = {}) => {
|
||||
Sentry.addBreadcrumb({
|
||||
level: 'info',
|
||||
category,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* An ApolloLink that sets a Sentry breadcrumb to make the GraphQL operation name
|
||||
* visible in a Sentry event report.
|
||||
*
|
||||
* @see https://develop.sentry.dev/sdk/data-model/event-payloads/breadcrumbs/
|
||||
* @see https://www.apollographql.com/docs/react/api/link/introduction
|
||||
*/
|
||||
export const sentryBreadcrumbLink = new ApolloLink((operation, forward) => {
|
||||
addGraphqlBreadcrumb(REQUEST, { operationName: operation.operationName });
|
||||
|
||||
return forward(operation).map((response) => {
|
||||
addGraphqlBreadcrumb(RESPONSE, {
|
||||
correlationId: response.correlationId,
|
||||
operationName: operation.operationName,
|
||||
});
|
||||
|
||||
return response;
|
||||
});
|
||||
});
|
||||
|
|
@ -13,6 +13,7 @@ import { getInstrumentationLink } from './apollo/instrumentation_link';
|
|||
import { getSuppressNetworkErrorsDuringNavigationLink } from './apollo/suppress_network_errors_during_navigation_link';
|
||||
import { getPersistLink } from './apollo/persist_link';
|
||||
import { persistenceMapper } from './apollo/persistence_mapper';
|
||||
import { sentryBreadcrumbLink } from './apollo/sentry_breadcrumb_link';
|
||||
import { correlationIdLink } from './apollo/correlation_id_link';
|
||||
|
||||
export const fetchPolicies = {
|
||||
|
|
@ -253,6 +254,7 @@ function createApolloClient(resolvers = {}, config = {}) {
|
|||
[
|
||||
getSuppressNetworkErrorsDuringNavigationLink(),
|
||||
getInstrumentationLink(),
|
||||
sentryBreadcrumbLink,
|
||||
correlationIdLink,
|
||||
requestCounterLink,
|
||||
performanceBarLink,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { GlAvatar, GlTab, GlTabs, GlBadge, GlButton, GlSprintf, GlIcon, GlLink }
|
|||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import { createAlert, VARIANT_DANGER } from '~/alert';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { setUrlFragment, visitUrlWithAlerts } from '~/lib/utils/url_utility';
|
||||
import getModelVersionQuery from '~/ml/model_registry/graphql/queries/get_model_version.query.graphql';
|
||||
import deleteModelVersionMutation from '~/ml/model_registry/graphql/mutations/delete_model_version.mutation.graphql';
|
||||
|
|
@ -232,7 +232,7 @@ export default {
|
|||
},
|
||||
},
|
||||
i18n: {
|
||||
editModelVersionButtonLabel: s__('MlModelRegistry|Edit model version'),
|
||||
editModelVersionButtonLabel: __('Edit'),
|
||||
authorTitle: s__('MlModelRegistry|Publisher'),
|
||||
tabs: {
|
||||
modelVersionCard: s__('MlModelRegistry|Version card'),
|
||||
|
|
@ -280,7 +280,7 @@ export default {
|
|||
<gl-button
|
||||
v-if="canWriteModelRegistry"
|
||||
data-testid="edit-model-version-button"
|
||||
variant="confirm"
|
||||
category="secondary"
|
||||
:href="editModelVersionPath"
|
||||
>{{ $options.i18n.editModelVersionButtonLabel }}</gl-button
|
||||
>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
|
||||
// exports
|
||||
captureException,
|
||||
addBreadcrumb,
|
||||
SDK_VERSION,
|
||||
} from '@sentry/browser';
|
||||
|
||||
|
|
@ -71,6 +72,7 @@ const initSentry = () => {
|
|||
// eslint-disable-next-line no-underscore-dangle
|
||||
window._Sentry = {
|
||||
captureException,
|
||||
addBreadcrumb,
|
||||
SDK_VERSION, // used to verify compatibility with the Sentry instance
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,3 +21,17 @@ export const captureException = (...args) => {
|
|||
|
||||
Sentry?.captureException(...args);
|
||||
};
|
||||
|
||||
/** @type {import('@sentry/core').addBreadcrumb} */
|
||||
export const addBreadcrumb = (...args) => {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const Sentry = window._Sentry;
|
||||
|
||||
// When Sentry is not configured during development, show console error
|
||||
if (process.env.NODE_ENV === 'development' && !Sentry) {
|
||||
console.debug('[Sentry stub]', 'addBreadcrumb(...) called with:', { ...args });
|
||||
return;
|
||||
}
|
||||
|
||||
Sentry?.addBreadcrumb(...args);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ module Groups
|
|||
skip_before_action :authenticate_user!, raise: false
|
||||
|
||||
prepend_before_action :authenticate_user_from_jwt_token!
|
||||
before_action :skip_session
|
||||
|
||||
def authenticate_user_from_jwt_token!
|
||||
authenticate_with_http_token do |token, _|
|
||||
|
|
@ -55,6 +56,10 @@ module Groups
|
|||
def set_auth_result(actor, type)
|
||||
@authentication_result = Gitlab::Auth::Result.new(actor, nil, type, [])
|
||||
end
|
||||
|
||||
def skip_session
|
||||
request.session_options[:skip] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
|
||||
ACTIVE_SINCE_KEY = 'active_since'
|
||||
|
||||
# Following https://www.rfc-editor.org/rfc/rfc3986.txt
|
||||
# to check for the present of reserved characters
|
||||
# in redirect_fragment
|
||||
INVALID_FRAGMENT_EXP = %r{[;/?:@&=+$,]+}
|
||||
|
||||
InvalidFragmentError = Class.new(StandardError)
|
||||
|
||||
after_action :verify_known_sign_in
|
||||
|
||||
protect_from_forgery except: [:failure] + AuthHelper.saml_providers, with: :exception, prepend: true
|
||||
|
|
@ -102,6 +109,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
|
||||
private
|
||||
|
||||
def verify_redirect_fragment(fragment)
|
||||
if URI.decode_uri_component(fragment).match(INVALID_FRAGMENT_EXP)
|
||||
raise InvalidFragmentError
|
||||
else
|
||||
fragment
|
||||
end
|
||||
end
|
||||
|
||||
def track_event(user, provider, status)
|
||||
log_audit_event(user, with: provider)
|
||||
update_login_counter_metric(provider, status)
|
||||
|
|
@ -172,6 +187,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
else
|
||||
sign_in_user_flow(auth_module::User)
|
||||
end
|
||||
rescue InvalidFragmentError
|
||||
fail_login_with_message("Invalid state")
|
||||
end
|
||||
|
||||
def link_identity(identity_linker)
|
||||
|
|
@ -369,7 +386,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
key = stored_location_key_for(:user)
|
||||
location = session[key]
|
||||
if uri = parse_uri(location)
|
||||
uri.fragment = redirect_fragment
|
||||
uri.fragment = verify_redirect_fragment(redirect_fragment)
|
||||
store_location_for(:user, uri.to_s)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:ci_graphql_pipeline_mini_graph, @project)
|
||||
end
|
||||
before_action :rate_limit_for_expanded_diff_files, only: :diff_files
|
||||
|
||||
BRANCH_SEARCH_LIMIT = 1000
|
||||
COMMIT_DIFFS_PER_PAGE = 20
|
||||
|
|
@ -57,7 +58,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
format.html do
|
||||
render template: 'projects/commit/diff_files',
|
||||
layout: false,
|
||||
locals: { diffs: @diffs, environment: @environment }
|
||||
locals: { diffs: @diffs.with_highlights_preloaded, environment: @environment }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -290,6 +291,12 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
view: diff_view
|
||||
)
|
||||
end
|
||||
|
||||
def rate_limit_for_expanded_diff_files
|
||||
return unless diffs_expanded?
|
||||
|
||||
check_rate_limit!(:expanded_diff_files, scope: current_user || request.ip)
|
||||
end
|
||||
end
|
||||
|
||||
Projects::CommitController.prepend_mod_with('Projects::CommitController')
|
||||
|
|
|
|||
|
|
@ -742,7 +742,8 @@ module Types
|
|||
description: 'List of unprotected branches, ignoring any wildcard branch rules',
|
||||
null: true,
|
||||
calls_gitaly: true,
|
||||
experiment: { milestone: '16.9' }
|
||||
experiment: { milestone: '16.9' },
|
||||
authorize: :read_code
|
||||
|
||||
field :project_plan_limits, Types::ProjectPlanLimitsType,
|
||||
resolver: Resolvers::Projects::PlanLimitsResolver,
|
||||
|
|
|
|||
|
|
@ -171,6 +171,8 @@ class Group < Namespace
|
|||
|
||||
validates :group_feature, presence: true
|
||||
|
||||
validate :top_level_group_name_not_assigned_to_pages_unique_domain, if: :path_changed?
|
||||
|
||||
add_authentication_token_field :runners_token,
|
||||
encrypted: :required,
|
||||
format_with_prefix: :runners_token_prefix,
|
||||
|
|
@ -1157,6 +1159,15 @@ class Group < Namespace
|
|||
def runners_token_prefix
|
||||
RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
|
||||
end
|
||||
|
||||
def top_level_group_name_not_assigned_to_pages_unique_domain
|
||||
return unless parent_id.nil?
|
||||
|
||||
return unless ProjectSetting.unique_domain_exists?(path)
|
||||
|
||||
# We cannot disclose the Pages unique domain, hence returning generic error message
|
||||
errors.add(:path, _('has already been taken'))
|
||||
end
|
||||
end
|
||||
|
||||
Group.prepend_mod_with('Group')
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ class Namespace < ApplicationRecord
|
|||
def clean_path(path, limited_to: Namespace.all)
|
||||
slug = Gitlab::Slug::Path.new(path).generate
|
||||
path = Namespaces::RandomizedSuffixPath.new(slug)
|
||||
Gitlab::Utils::Uniquify.new.string(path) { |s| limited_to.find_by_path_or_name(s) }
|
||||
Gitlab::Utils::Uniquify.new.string(path) { |s| limited_to.find_by_path_or_name(s) || ProjectSetting.unique_domain_exists?(s) }
|
||||
end
|
||||
|
||||
def clean_name(value)
|
||||
|
|
@ -759,6 +759,14 @@ class Namespace < ApplicationRecord
|
|||
{ organization_id: organization_id }
|
||||
end
|
||||
|
||||
def pipeline_variables_default_role
|
||||
return namespace_settings.pipeline_variables_default_role if namespace_settings.present?
|
||||
|
||||
# We could have old namespaces that don't have an associated `namespace_settings` record.
|
||||
# To avoid returning `nil` we return the database-level default.
|
||||
NamespaceSetting.column_defaults['pipeline_variables_default_role']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_organization?
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ class NamespaceSetting < ApplicationRecord
|
|||
include ChronicDurationAttribute
|
||||
|
||||
ignore_column :token_expiry_notify_inherited, remove_with: '17.9', remove_after: '2025-01-11'
|
||||
enum pipeline_variables_default_role: ProjectCiCdSetting::PIPELINE_VARIABLES_OVERRIDE_ROLES, _prefix: true
|
||||
|
||||
ignore_column :third_party_ai_features_enabled, remove_with: '16.11', remove_after: '2024-04-18'
|
||||
ignore_column :code_suggestions, remove_with: '17.8', remove_after: '2024-05-16'
|
||||
|
||||
|
|
@ -29,6 +31,8 @@ class NamespaceSetting < ApplicationRecord
|
|||
|
||||
sanitizes! :default_branch_name
|
||||
|
||||
before_validation :set_pipeline_variables_default_role, on: :create
|
||||
|
||||
before_validation :normalize_default_branch_name
|
||||
|
||||
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
|
||||
|
|
@ -68,6 +72,15 @@ class NamespaceSetting < ApplicationRecord
|
|||
namespace.root_ancestor.prevent_sharing_groups_outside_hierarchy
|
||||
end
|
||||
|
||||
def pipeline_variables_default_role
|
||||
# We consider only the root namespace setting to cascade the default value to all projects.
|
||||
# By ignoring settings from sub-groups we don't need to deal with complexities from
|
||||
# hierarchical settings.
|
||||
return namespace.root_ancestor.pipeline_variables_default_role unless namespace.root?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def emails_enabled?
|
||||
return emails_enabled unless namespace.has_parent?
|
||||
|
||||
|
|
@ -101,6 +114,18 @@ class NamespaceSetting < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def set_pipeline_variables_default_role
|
||||
# After FF `change_namespace_default_role_for_pipeline_variables` rollout - we have to remove both FF and pipeline_variables_default_role = NO_ONE_ALLOWED_ROLE
|
||||
# As any self-managed and Dedicated instance should opt-in by changing their namespace settings explicitly.
|
||||
# NO_ONE_ALLOWED will be set as the default value for namespace_settings through a database migration.
|
||||
|
||||
# WARNING: Removing this FF could cause breaking changes for self-hosted and dedicated instances.
|
||||
|
||||
return if Feature.disabled?(:change_namespace_default_role_for_pipeline_variables, namespace)
|
||||
|
||||
self.pipeline_variables_default_role = ProjectCiCdSetting::NO_ONE_ALLOWED_ROLE
|
||||
end
|
||||
|
||||
def all_ancestors_have_emails_enabled?
|
||||
self.class.where(namespace_id: namespace.self_and_ancestors, emails_enabled: false).none?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,15 +11,17 @@ class ProjectCiCdSetting < ApplicationRecord
|
|||
MAINTAINER_ROLE = 3
|
||||
OWNER_ROLE = 4
|
||||
|
||||
PIPELINE_VARIABLES_OVERRIDE_ROLES =
|
||||
{ no_one_allowed: NO_ONE_ALLOWED_ROLE,
|
||||
developer: DEVELOPER_ROLE,
|
||||
maintainer: MAINTAINER_ROLE,
|
||||
owner: OWNER_ROLE }.freeze
|
||||
|
||||
ALLOWED_SUB_CLAIM_COMPONENTS = %w[project_path ref_type ref].freeze
|
||||
|
||||
enum pipeline_variables_minimum_override_role: {
|
||||
no_one_allowed: NO_ONE_ALLOWED_ROLE,
|
||||
developer: DEVELOPER_ROLE,
|
||||
maintainer: MAINTAINER_ROLE,
|
||||
owner: OWNER_ROLE
|
||||
}, _prefix: true
|
||||
enum pipeline_variables_minimum_override_role: PIPELINE_VARIABLES_OVERRIDE_ROLES, _prefix: true
|
||||
|
||||
before_validation :set_pipeline_variables_secure_defaults, on: :create
|
||||
before_create :set_default_git_depth
|
||||
|
||||
validates :id_token_sub_claim_components, length: {
|
||||
|
|
@ -79,6 +81,11 @@ class ProjectCiCdSetting < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def set_pipeline_variables_secure_defaults
|
||||
self.restrict_user_defined_variables = true
|
||||
self.pipeline_variables_minimum_override_role = project.root_namespace.pipeline_variables_default_role
|
||||
end
|
||||
|
||||
def role_map_pipeline_variables_minimum_override_role
|
||||
{
|
||||
DEVELOPER_ROLE => Gitlab::Access::DEVELOPER,
|
||||
|
|
|
|||
|
|
@ -328,6 +328,7 @@ class User < ApplicationRecord
|
|||
presence: true,
|
||||
exclusion: { in: Gitlab::PathRegex::TOP_LEVEL_ROUTES, message: N_('%{value} is a reserved name') }
|
||||
validates :username, uniqueness: true, unless: :namespace
|
||||
validate :username_not_assigned_to_pages_unique_domain, if: :username_changed?
|
||||
validates :name, presence: true, length: { maximum: 255 }
|
||||
validates :first_name, length: { maximum: 127 }
|
||||
validates :last_name, length: { maximum: 127 }
|
||||
|
|
@ -2882,6 +2883,13 @@ class User < ApplicationRecord
|
|||
|
||||
# method overridden in EE
|
||||
def audit_unlock_access(author: self); end
|
||||
|
||||
def username_not_assigned_to_pages_unique_domain
|
||||
if ProjectSetting.unique_domain_exists?(username)
|
||||
# We cannot disclose the Pages unique domain, hence returning generic error message
|
||||
errors.add(:username, _('has already been taken'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
User.prepend_mod_with('User')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module Integrations
|
|||
end
|
||||
|
||||
expose :digest do |item|
|
||||
strip_tags(item['digest'])
|
||||
validate_path(item['digest']).then { |digest| strip_tags(digest) }
|
||||
end
|
||||
|
||||
expose :size do |item|
|
||||
|
|
@ -24,6 +24,19 @@ module Integrations
|
|||
expose :tags do |item|
|
||||
item['tags'].map { |tag| strip_tags(tag['name']) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_path(path)
|
||||
::Gitlab::PathTraversal.check_path_traversal!(path)
|
||||
rescue ::Gitlab::PathTraversal::PathTraversalAttackError => e
|
||||
::Gitlab::ErrorTracking.track_exception(
|
||||
e,
|
||||
message: "Path traversal attack detected #{path}",
|
||||
class: self.class.name
|
||||
)
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module Integrations
|
|||
end
|
||||
|
||||
expose :name do |item|
|
||||
strip_tags(item['name'])
|
||||
validate_path(item['name']).then { |name| strip_tags(name) }
|
||||
end
|
||||
|
||||
expose :artifact_count do |item|
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ module Ci
|
|||
LOG_MAX_PIPELINE_SIZE = 2_000
|
||||
LOG_MAX_CREATION_THRESHOLD = 20.seconds
|
||||
SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
|
||||
Gitlab::Ci::Pipeline::Chain::Build::Associations,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
|
||||
Gitlab::Ci::Pipeline::Chain::Build::Associations,
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::RateLimit,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy,
|
||||
Gitlab::Ci::Pipeline::Chain::AssignPartition,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ module Issues
|
|||
else
|
||||
merge_request_to_resolve_discussions_of
|
||||
.discussions_to_be_resolved
|
||||
end
|
||||
end.reject(&:confidential?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ module Gitlab
|
|||
/key$/,
|
||||
/^body$/,
|
||||
/^description$/,
|
||||
/^query$/,
|
||||
/^note$/,
|
||||
/^text$/,
|
||||
/^title$/,
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ Rails.application.configure do
|
|||
end
|
||||
|
||||
config.middleware.insert_before(
|
||||
ActionDispatch::Cookies, Gitlab::Middleware::StripCookies, paths: [%r{^/assets/}, %r{^/v2$}, %r{^/v2/}]
|
||||
ActionDispatch::Cookies, Gitlab::Middleware::StripCookies, paths: [%r{^/assets/}]
|
||||
)
|
||||
|
||||
config.log_level = Gitlab::Utils.to_rails_log_level(ENV["GITLAB_LOG_LEVEL"], :debug)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'gitlab/middleware/strip_cookies'
|
||||
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb
|
||||
|
||||
|
|
@ -77,8 +75,4 @@ Rails.application.configure do
|
|||
config.action_mailer.raise_delivery_errors = true
|
||||
|
||||
config.eager_load = true
|
||||
|
||||
config.middleware.insert_before(
|
||||
ActionDispatch::Cookies, Gitlab::Middleware::StripCookies, paths: [%r{^/v2$}, %r{^/v2/}]
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ Rails.application.configure do
|
|||
config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RobotsBlockerMiddleware)
|
||||
config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::RequestInspectorMiddleware)
|
||||
config.middleware.insert_before(ActionDispatch::Static, Gitlab::Testing::ClearProcessMemoryCacheMiddleware)
|
||||
config.middleware.insert_before(
|
||||
ActionDispatch::Cookies, Gitlab::Middleware::StripCookies, paths: [%r{^/assets/}, %r{^/v2$}, %r{^/v2/}]
|
||||
)
|
||||
config.middleware.insert_before(ActionDispatch::Cookies, Gitlab::Middleware::StripCookies, paths: [%r{^/assets/}])
|
||||
|
||||
Gitlab::Testing::ActionCableBlocker.install
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: change_namespace_default_role_for_pipeline_variables
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171424
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502238
|
||||
milestone: '17.7'
|
||||
type: development
|
||||
group: group::pipeline security
|
||||
default_enabled: false
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
"runner",
|
||||
"scalability",
|
||||
"secret_detection",
|
||||
"security_infrastructure",
|
||||
"security_insights",
|
||||
"security_policies",
|
||||
"source_code",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,5 @@ description: Geo events implemented generically, used by the SSF where all objec
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23447
|
||||
milestone: '12.8'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/479383
|
||||
table_size: small
|
||||
exempt_from_sharding: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPipelineVariablesDefaultRoleToNamespaceSettings < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
DEVELOPER_ROLE = 2
|
||||
|
||||
def change
|
||||
add_column :namespace_settings, :pipeline_variables_default_role,
|
||||
:integer, default: DEVELOPER_ROLE, null: false, limit: 2
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ValidateNamespacesOrganizationIdNotNullConstraint < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
def up
|
||||
validate_not_null_constraint :namespaces, :organization_id
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
3a45e94fffa5b618bff82b0768429dfb68412f709f21cc380ac39058ba4adc6b
|
||||
|
|
@ -0,0 +1 @@
|
|||
bfb69bc3eb91b9b301f546eb18726512c3dee89f9d025974b4181a14d4e10b98
|
||||
|
|
@ -201,7 +201,8 @@ CREATE TABLE namespaces (
|
|||
shared_runners_enabled boolean DEFAULT true NOT NULL,
|
||||
allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL,
|
||||
traversal_ids bigint[] DEFAULT '{}'::bigint[] NOT NULL,
|
||||
organization_id bigint DEFAULT 1
|
||||
organization_id bigint DEFAULT 1,
|
||||
CONSTRAINT check_2eae3bdf93 CHECK ((organization_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE FUNCTION find_namespaces_by_id(namespaces_id bigint) RETURNS namespaces
|
||||
|
|
@ -15589,6 +15590,7 @@ CREATE TABLE namespace_settings (
|
|||
token_expiry_notify_inherited boolean DEFAULT true NOT NULL,
|
||||
resource_access_token_notify_inherited boolean,
|
||||
lock_resource_access_token_notify_inherited boolean DEFAULT false NOT NULL,
|
||||
pipeline_variables_default_role smallint DEFAULT 2 NOT NULL,
|
||||
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)),
|
||||
CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)),
|
||||
CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100))
|
||||
|
|
@ -25397,9 +25399,6 @@ ALTER TABLE workspaces
|
|||
ALTER TABLE security_scans
|
||||
ADD CONSTRAINT check_2d56d882f6 CHECK ((project_id IS NOT NULL)) NOT VALID;
|
||||
|
||||
ALTER TABLE namespaces
|
||||
ADD CONSTRAINT check_2eae3bdf93 CHECK ((organization_id IS NOT NULL)) NOT VALID;
|
||||
|
||||
ALTER TABLE vulnerability_scanners
|
||||
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ The following API resources are available in the project context:
|
|||
| [Agents](cluster_agents.md) | `/projects/:id/cluster_agents` |
|
||||
| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
|
||||
| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
|
||||
| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
|
||||
| [Container registry](container_registry.md) | `/projects/:id/registry/repositories` |
|
||||
| [Container repository protection rules](container_repository_protection_rules.md) | `/projects/:id/registry/protection/repository/rules` |
|
||||
| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
|
||||
| [Composer distributions](packages/composer.md) | `/projects/:id/packages/composer` (also available for groups) |
|
||||
| [Conan distributions](packages/conan.md) | `/projects/:id/packages/conan` (also available standalone) |
|
||||
|
|
|
|||
|
|
@ -330,6 +330,10 @@ of commits, GitLab generates a changelog for all commits that use a particular
|
|||
a new Markdown-formatted section to a changelog file in the Git repository of
|
||||
the project. The output format can be customized.
|
||||
|
||||
For performance and security reasons, parsing the changelog configuration is limited to `2` seconds.
|
||||
This limitation helps prevent potential DoS attacks from malformed changelog templates.
|
||||
If the request times out, consider reducing the size of your `changelog_config.yml` file.
|
||||
|
||||
For user-facing documentation, see [Changelogs](../user/project/changelogs.md).
|
||||
|
||||
```plaintext
|
||||
|
|
|
|||
|
|
@ -838,11 +838,8 @@ use this setting for control over the environment the pipeline runs in.
|
|||
|
||||
#### Set a minimum role for pipeline variables
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/440338) in GitLab 17.1
|
||||
|
||||
When [pipeline variables are restricted](#restrict-pipeline-variables), you can also
|
||||
set a specific minimum [role](../../user/permissions.md#roles) that can run pipelines
|
||||
with pipeline variables.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/440338) in GitLab 17.1.
|
||||
> - For GitLab.com, setting defaults [updated for all new projects in new namespaces](https://gitlab.com/gitlab-org/gitlab/-/issues/502382) to `enabled` for `restrict_user_defined_variables` and `no_one_allowed` for `ci_pipeline_variables_minimum_override_role` in GitLab 17.7.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
@ -853,10 +850,11 @@ To change the setting, use [the projects API](../../api/projects.md#edit-a-proje
|
|||
to set `ci_pipeline_variables_minimum_override_role` to one of:
|
||||
|
||||
- `no_one_allowed`: No pipelines can run with pipeline variables.
|
||||
Default for new projects in new namespaces on GitLab.com.
|
||||
- `owner`: Only users with the Owner role can run pipelines with pipeline variables.
|
||||
You must have the Owner role for the project to change the setting to this value.
|
||||
- `maintainer`: Only users with at least the Maintainer role can run pipelines with pipeline variables.
|
||||
Default when not specified.
|
||||
Default when not specified on self-managed and Dedicated.
|
||||
- `developer`: Only users with at least the Developer role can run pipelines with pipeline variables.
|
||||
|
||||
## Exporting variables
|
||||
|
|
|
|||
|
|
@ -214,6 +214,20 @@ There is a rate limit for triggering project imports from FogBugz.
|
|||
|
||||
The **rate limit** is 1 triggered import per minute per user.
|
||||
|
||||
### Commit diff files
|
||||
|
||||
This is a rate limit for expanded commit diff files (`/[group]/[project]/-/commit/[:sha]/diff_files?expanded=1`),
|
||||
which is enforced to prevent from abusing this endpoint.
|
||||
|
||||
The **rate limit** is 6 requests per minute per user (authenticated) or per IP address (unauthenticated).
|
||||
|
||||
### Changelog generation
|
||||
|
||||
There is a rate limit per user per project on the `:id/repository/changelog` endpoint. This is to mitigate attempts to misuse the endpoint.
|
||||
The rate limit is shared between GET and POST actions.
|
||||
|
||||
The **rate limit** is 5 calls per minute per user per project.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Rack Attack is denylisting the load balancer
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ the following sections and tables provide an alternative.
|
|||
| `pipeline_config_strategy` | `string` | false | Can either be `inject_ci` or `override_project_ci`. See [Pipeline strategies](#pipeline-strategies) for more information. |
|
||||
| `policy_scope` | `object` of [`policy_scope`](index.md#scope) | false | Scopes the policy based on projects, groups, or compliance framework labels you specify. |
|
||||
| `suffix` | `string` | false | Can either be `on_conflict` (default), or `never`. Defines the behavior for handling job naming conflicts. `on_conflict` applies a unique suffix to the job names for jobs that would break the uniqueness. `never` causes the pipeline to fail if the job names across the project and all applicable policies are not unique. |
|
||||
| `skip_ci` | `object` of [`skip_ci`](pipeline_execution_policies.md#skip_ci-type) | false | Defines whether users can apply the `skip-ci` directive. By default, the use of `skip-ci` is ignored and as a result, pipelines with pipeline execution policies cannot be skipped. |
|
||||
|
||||
Note the following:
|
||||
|
||||
|
|
@ -164,6 +165,19 @@ Prerequisites:
|
|||
Enabling this setting grants the user who triggered the pipeline access to
|
||||
read the CI/CD configuration file enforced by the pipeline execution policy. This setting does not grant the user access to any other parts of the project where the configuration file is stored.
|
||||
|
||||
### `skip_ci` type
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173480) in GitLab 17.7.
|
||||
|
||||
Use the `skip_ci` keyword to specify whether users are allowed to apply the `skip-ci` directive to skip the pipelines.
|
||||
When the keyword is not specified, the `skip-ci` directive is ignored, preventing all users
|
||||
from bypassing the pipeline execution policies.
|
||||
|
||||
| Field | Type | Possible values | Description |
|
||||
|-------------------------|----------|--------------------------|-------------|
|
||||
| `allowed` | `boolean` | `true`, `false` | Flag to allow (`true`) or prevent (`false`) the use of the `skip-ci` directive for pipelines with enforced pipeline execution policies. |
|
||||
| `allowlist` | `object` | `users` | Specify users who are always allowed to use `skip-ci` directive, regardless of the `allowed` flag. Use `users:` followed by an array of objects with `id` keys representing user IDs. |
|
||||
|
||||
### Policy scope schema
|
||||
|
||||
To customize policy enforcement, you can define a policy's scope to either include, or exclude,
|
||||
|
|
|
|||
|
|
@ -111,9 +111,11 @@ for definitions and usage.
|
|||
|
||||
## Customize the changelog output
|
||||
|
||||
To customize the changelog output, edit the changelog configuration file, and commit these changes to your project's Git repository.
|
||||
The default location for this configuration is `.gitlab/changelog_config.yml`. The file supports
|
||||
these variables:
|
||||
To customize the changelog output, edit the changelog configuration file, and commit these changes to your project's Git repository. The default location for this configuration is `.gitlab/changelog_config.yml`.
|
||||
|
||||
For performance and security reasons, parsing the changelog configuration is limited to `2` seconds.
|
||||
If parsing the configuration results in timeout errors, consider reducing the size of the configuration.
|
||||
The file supports these variables:
|
||||
|
||||
- `date_format`: The date format, in `strftime` format, used in the title of the newly added changelog data.
|
||||
- `template`: A custom template to use when generating the changelog data.
|
||||
|
|
|
|||
|
|
@ -358,6 +358,7 @@ module API
|
|||
mount ::API::UserRunners
|
||||
mount ::API::VirtualRegistries::Packages::Maven::Registries
|
||||
mount ::API::VirtualRegistries::Packages::Maven::Upstreams
|
||||
mount ::API::VirtualRegistries::Packages::Maven::CachedResponses
|
||||
mount ::API::VirtualRegistries::Packages::Maven::Endpoints
|
||||
mount ::API::WebCommits
|
||||
mount ::API::Wikis
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Concerns
|
||||
module VirtualRegistries
|
||||
module Packages
|
||||
module Maven
|
||||
module CachedResponseEndpoints
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ::API::PaginationParams
|
||||
|
||||
helpers do
|
||||
def cached_responses
|
||||
upstream.cached_responses.default.search_by_relative_path(params[:search])
|
||||
end
|
||||
|
||||
def cached_response
|
||||
upstream.cached_responses.default.find_by_relative_path!(declared_params[:cached_response_id])
|
||||
end
|
||||
end
|
||||
|
||||
desc 'List maven virtual registry upstream cached responses' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success Entities::VirtualRegistries::Packages::Maven::CachedResponse
|
||||
failure [
|
||||
{ code: 400, message: 'Bad Request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
is_array true
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
optional :search, type: String, desc: 'Search query', documentation: { example: 'foo/bar/mypkg' }
|
||||
use :pagination
|
||||
end
|
||||
get do
|
||||
authorize! :read_virtual_registry, registry
|
||||
|
||||
# TODO: refactor this when we support multiple upstreams.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/480461
|
||||
not_found! if upstream&.id != params[:upstream_id]
|
||||
|
||||
present paginate(cached_responses), with: Entities::VirtualRegistries::Packages::Maven::CachedResponse
|
||||
end
|
||||
|
||||
desc 'Delete a maven virtual registry upstream cached response' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 204
|
||||
failure [
|
||||
{ code: 400, message: 'Bad Request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
requires :cached_response_id, type: String, coerce_with: Base64.method(:urlsafe_decode64),
|
||||
desc: 'The base64 encoded relative path of the cached response',
|
||||
documentation: { example: 'Zm9vL2Jhci9teXBrZy5wb20=' }
|
||||
end
|
||||
|
||||
delete '*cached_response_id' do
|
||||
authorize! :destroy_virtual_registry, registry
|
||||
|
||||
# TODO: refactor this when we support multiple upstreams.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/480461
|
||||
not_found! if upstream&.id != params[:upstream_id]
|
||||
|
||||
destroy_conditionally!(cached_response) do |cached_response|
|
||||
render_validation_error!(cached_response) unless cached_response.update(upstream: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,8 +6,8 @@ module API
|
|||
module Packages
|
||||
module Maven
|
||||
class CachedResponse < Grape::Entity
|
||||
expose :cached_response_id do |cached_response, _options|
|
||||
Base64.urlsafe_encode64(cached_response.relative_path)
|
||||
expose :id do |cached_response, _options|
|
||||
Base64.urlsafe_encode64("#{cached_response.upstream_id} #{cached_response.relative_path}")
|
||||
end
|
||||
|
||||
expose :group_id,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ module API
|
|||
namespace_path = params[:id]
|
||||
existing_namespaces_within_the_parent = Namespace.without_project_namespaces.by_parent(params[:parent_id])
|
||||
|
||||
exists = existing_namespaces_within_the_parent.filter_by_path(namespace_path).exists?
|
||||
exists = existing_namespaces_within_the_parent.filter_by_path(namespace_path).exists? || ProjectSetting.unique_domain_exists?(namespace_path)
|
||||
suggestions = exists ? [Namespace.clean_path(namespace_path, limited_to: existing_namespaces_within_the_parent)] : []
|
||||
|
||||
present :exists, exists
|
||||
|
|
|
|||
|
|
@ -288,6 +288,10 @@ module API
|
|||
end
|
||||
route_setting :authentication, job_token_allowed: true
|
||||
get ':id/repository/changelog' do
|
||||
check_rate_limit!(:project_repositories_changelog, scope: [current_user, user_project]) do
|
||||
render_api_error!({ error: 'This changelog has been requested too many times. Try again later.' }, 429)
|
||||
end
|
||||
|
||||
service = ::Repositories::ChangelogService.new(
|
||||
user_project,
|
||||
current_user,
|
||||
|
|
@ -329,6 +333,10 @@ module API
|
|||
documentation: { example: 'Initial commit' }
|
||||
end
|
||||
post ':id/repository/changelog' do
|
||||
check_rate_limit!(:project_repositories_changelog, scope: [current_user, user_project]) do
|
||||
render_api_error!({ error: 'This changelog has been requested too many times. Try again later.' }, 429)
|
||||
end
|
||||
|
||||
branch = params[:branch] || user_project.default_branch_or_main
|
||||
access = Gitlab::UserAccess.new(current_user, container: user_project)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module VirtualRegistries
|
||||
module Packages
|
||||
module Maven
|
||||
class CachedResponses < ::API::Base
|
||||
include ::API::Helpers::Authentication
|
||||
include ::API::PaginationParams
|
||||
|
||||
feature_category :virtual_registry
|
||||
urgency :low
|
||||
|
||||
authenticate_with do |accept|
|
||||
accept.token_types(:personal_access_token).sent_through(:http_private_token_header)
|
||||
accept.token_types(:deploy_token).sent_through(:http_deploy_token_header)
|
||||
accept.token_types(:job_token).sent_through(:http_job_token_header)
|
||||
end
|
||||
|
||||
helpers do
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
def require_dependency_proxy_enabled!
|
||||
not_found! unless ::Gitlab.config.dependency_proxy.enabled
|
||||
end
|
||||
|
||||
def upstream
|
||||
::VirtualRegistries::Packages::Maven::Upstream.find(params[:id])
|
||||
end
|
||||
strong_memoize_attr :upstream
|
||||
|
||||
def cached_responses
|
||||
upstream.cached_responses.default.search_by_relative_path(params[:search])
|
||||
end
|
||||
|
||||
def cached_response
|
||||
::VirtualRegistries::Packages::Maven::CachedResponse
|
||||
.default
|
||||
.find_by_upstream_id_and_relative_path!(*declared_params[:id].split)
|
||||
end
|
||||
strong_memoize_attr :cached_response
|
||||
end
|
||||
|
||||
after_validation do
|
||||
not_found! unless Feature.enabled?(:virtual_registry_maven, current_user)
|
||||
|
||||
require_dependency_proxy_enabled!
|
||||
|
||||
authenticate!
|
||||
end
|
||||
|
||||
namespace 'virtual_registries/packages/maven' do
|
||||
namespace :upstreams do
|
||||
route_param :id, type: Integer, desc: 'The ID of the maven virtual registry upstream' do
|
||||
namespace :cached_responses do
|
||||
desc 'List maven virtual registry upstream cached responses' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success ::API::Entities::VirtualRegistries::Packages::Maven::CachedResponse
|
||||
failure [
|
||||
{ code: 400, message: 'Bad Request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
is_array true
|
||||
hidden true
|
||||
end
|
||||
|
||||
params do
|
||||
optional :search, type: String, desc: 'Search query', documentation: { example: 'foo/bar/mypkg' }
|
||||
use :pagination
|
||||
end
|
||||
get do
|
||||
authorize! :read_virtual_registry, upstream
|
||||
|
||||
present paginate(cached_responses),
|
||||
with: ::API::Entities::VirtualRegistries::Packages::Maven::CachedResponse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :cached_responses do
|
||||
desc 'Delete a maven virtual registry upstream cached response' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in an experimental state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success code: 204
|
||||
failure [
|
||||
{ code: 400, message: 'Bad Request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
requires :id, type: String, coerce_with: Base64.method(:urlsafe_decode64),
|
||||
desc: 'The base64 encoded upstream id and relative path of the cached response',
|
||||
documentation: { example: 'Zm9vL2Jhci9teXBrZy5wb20=' }
|
||||
end
|
||||
|
||||
delete '*id' do
|
||||
authorize! :destroy_virtual_registry, cached_response.upstream
|
||||
|
||||
destroy_conditionally!(cached_response) do |cached_response|
|
||||
render_validation_error!(cached_response) unless cached_response.update(upstream: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ module API
|
|||
module Maven
|
||||
class Endpoints < ::API::Base
|
||||
include ::API::Helpers::Authentication
|
||||
include ::API::Concerns::VirtualRegistries::Packages::Endpoint
|
||||
|
||||
feature_category :virtual_registry
|
||||
urgency :low
|
||||
|
|
@ -39,6 +40,13 @@ module API
|
|||
end
|
||||
strong_memoize_attr :registry
|
||||
|
||||
def download_file_extra_response_headers(action_params:)
|
||||
{
|
||||
SHA1_CHECKSUM_HEADER => action_params[:file_sha1],
|
||||
MD5_CHECKSUM_HEADER => action_params[:file_md5]
|
||||
}
|
||||
end
|
||||
|
||||
params :id_and_path do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
|
|
@ -59,108 +67,83 @@ module API
|
|||
authenticate!
|
||||
end
|
||||
|
||||
namespace 'virtual_registries/packages/maven' do
|
||||
namespace :registries do
|
||||
route_param :id, type: Integer, desc: 'The ID of the maven virtual registry' do
|
||||
namespace :upstreams do
|
||||
route_param :upstream_id, type: Integer, desc: 'The ID of the maven virtual registry upstream' do
|
||||
namespace :cached_responses do
|
||||
include ::API::Concerns::VirtualRegistries::Packages::Maven::CachedResponseEndpoints
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
namespace 'virtual_registries/packages/maven/:id/*path' do
|
||||
desc 'Download endpoint of the Maven virtual registry.' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success [
|
||||
{ code: 200 }
|
||||
]
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not Found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
use :id_and_path
|
||||
end
|
||||
get format: false do
|
||||
service_response = ::VirtualRegistries::Packages::Maven::HandleFileRequestService.new(
|
||||
registry: registry,
|
||||
current_user: current_user,
|
||||
params: { path: declared_params[:path] }
|
||||
).execute
|
||||
|
||||
send_error_response_from!(service_response: service_response) if service_response.error?
|
||||
send_successful_response_from(service_response: service_response)
|
||||
end
|
||||
|
||||
namespace ':id/*path' do
|
||||
include ::API::Concerns::VirtualRegistries::Packages::Endpoint
|
||||
|
||||
helpers do
|
||||
def download_file_extra_response_headers(action_params:)
|
||||
{
|
||||
SHA1_CHECKSUM_HEADER => action_params[:file_sha1],
|
||||
MD5_CHECKSUM_HEADER => action_params[:file_md5]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Download endpoint of the Maven virtual registry.' do
|
||||
detail 'This feature was introduced in GitLab 17.3. \
|
||||
desc 'Workhorse upload endpoint of the Maven virtual registry. Only workhorse can access it.' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success [
|
||||
{ code: 200 }
|
||||
]
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not Found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
use :id_and_path
|
||||
end
|
||||
get format: false do
|
||||
service_response = ::VirtualRegistries::Packages::Maven::HandleFileRequestService.new(
|
||||
registry: registry,
|
||||
current_user: current_user,
|
||||
params: { path: declared_params[:path] }
|
||||
).execute
|
||||
success [
|
||||
{ code: 200 }
|
||||
]
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not Found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
use :id_and_path
|
||||
requires :file,
|
||||
type: ::API::Validations::Types::WorkhorseFile,
|
||||
desc: 'The file being uploaded',
|
||||
documentation: { type: 'file' }
|
||||
end
|
||||
post 'upload' do
|
||||
require_gitlab_workhorse!
|
||||
authorize!(:read_virtual_registry, registry)
|
||||
|
||||
send_error_response_from!(service_response: service_response) if service_response.error?
|
||||
send_successful_response_from(service_response: service_response)
|
||||
end
|
||||
etag, content_type, upstream_gid = request.headers.fetch_values(
|
||||
'Etag',
|
||||
::Gitlab::Workhorse::SEND_DEPENDENCY_CONTENT_TYPE_HEADER,
|
||||
UPSTREAM_GID_HEADER
|
||||
) { nil }
|
||||
|
||||
desc 'Workhorse upload endpoint of the Maven virtual registry. Only workhorse can access it.' do
|
||||
detail 'This feature was introduced in GitLab 17.4. \
|
||||
This feature is currently in experiment state. \
|
||||
This feature is behind the `virtual_registry_maven` feature flag.'
|
||||
success [
|
||||
{ code: 200 }
|
||||
]
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not Found' }
|
||||
]
|
||||
tags %w[maven_virtual_registries]
|
||||
hidden true
|
||||
end
|
||||
params do
|
||||
use :id_and_path
|
||||
requires :file,
|
||||
type: ::API::Validations::Types::WorkhorseFile,
|
||||
desc: 'The file being uploaded',
|
||||
documentation: { type: 'file' }
|
||||
end
|
||||
post 'upload' do
|
||||
require_gitlab_workhorse!
|
||||
authorize!(:read_virtual_registry, registry)
|
||||
# TODO: revisit this part when multiple upstreams are supported
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/480461
|
||||
# coherence check
|
||||
not_found!('Upstream') unless upstream == GlobalID::Locator.locate(upstream_gid)
|
||||
|
||||
etag, content_type, upstream_gid = request.headers.fetch_values(
|
||||
'Etag',
|
||||
::Gitlab::Workhorse::SEND_DEPENDENCY_CONTENT_TYPE_HEADER,
|
||||
UPSTREAM_GID_HEADER
|
||||
) { nil }
|
||||
service_response = ::VirtualRegistries::Packages::Maven::CachedResponses::CreateOrUpdateService.new(
|
||||
upstream: upstream,
|
||||
current_user: current_user,
|
||||
params: declared_params.merge(etag: etag, content_type: content_type)
|
||||
).execute
|
||||
|
||||
# TODO: revisit this part when multiple upstreams are supported
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/480461
|
||||
# coherence check
|
||||
not_found!('Upstream') unless upstream == GlobalID::Locator.locate(upstream_gid)
|
||||
|
||||
service_response = ::VirtualRegistries::Packages::Maven::CachedResponses::CreateOrUpdateService.new(
|
||||
upstream: upstream,
|
||||
current_user: current_user,
|
||||
params: declared_params.merge(etag: etag, content_type: content_type)
|
||||
).execute
|
||||
|
||||
send_error_response_from!(service_response: service_response) if service_response.error?
|
||||
ok_empty_response
|
||||
end
|
||||
send_error_response_from!(service_response: service_response) if service_response.error?
|
||||
ok_empty_response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ module Gitlab
|
|||
project_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
|
||||
project_download_export: { threshold: -> { application_settings.project_download_export_limit }, interval: 1.minute },
|
||||
project_repositories_archive: { threshold: 5, interval: 1.minute },
|
||||
project_repositories_changelog: { threshold: 5, interval: 1.minute },
|
||||
project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute },
|
||||
project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute },
|
||||
play_pipeline_schedule: { threshold: 1, interval: 1.minute },
|
||||
|
|
@ -92,7 +93,8 @@ module Gitlab
|
|||
},
|
||||
downstream_pipeline_trigger: {
|
||||
threshold: -> { application_settings.downstream_pipeline_trigger_limit_per_project_user_sha }, interval: 1.minute
|
||||
}
|
||||
},
|
||||
expanded_diff_files: { threshold: 6, interval: 1.minute }
|
||||
}.freeze
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -228,9 +228,12 @@ module Gitlab
|
|||
|
||||
def oauth_access_token_check(password)
|
||||
if password.present?
|
||||
token = Doorkeeper::AccessToken.by_token(password)
|
||||
token = OauthAccessToken.by_token(password)
|
||||
|
||||
if valid_oauth_token?(token)
|
||||
identity = ::Gitlab::Auth::Identity.link_from_oauth_token(token)
|
||||
return if identity && !identity.valid?
|
||||
|
||||
user = User.id_in(token.resource_owner_id).first
|
||||
return unless user && user.can_log_in_with_non_expired_password?
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ module Gitlab
|
|||
module Diff
|
||||
module FileCollection
|
||||
class Commit < Base
|
||||
# The maximum time allowed to highlight all the files in a commit (in seconds).
|
||||
DEFAULT_LIMIT_HIGHLIGHT_COLLECTION = 10
|
||||
|
||||
def initialize(commit, diff_options:)
|
||||
super(commit,
|
||||
project: commit.project,
|
||||
|
|
@ -11,6 +14,29 @@ module Gitlab
|
|||
diff_refs: commit.diff_refs)
|
||||
end
|
||||
|
||||
# We need to preload the diffs highlighting to track every diff file
|
||||
# and the time that they take to format. If the highlight rich collection
|
||||
# limit is reached, then we render the rest of diff files
|
||||
# as plain text to avoid saturating the resources.
|
||||
def with_highlights_preloaded
|
||||
@with_highlights_preloaded ||= begin
|
||||
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
||||
|
||||
diff_files.each do |diff_file|
|
||||
current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
||||
use_plain_highlight = current_time - start_time >= DEFAULT_LIMIT_HIGHLIGHT_COLLECTION
|
||||
|
||||
diff_file.highlighted_diff_lines = Gitlab::Diff::Highlight.new(
|
||||
diff_file,
|
||||
repository: diff_file.repository,
|
||||
plain: use_plain_highlight
|
||||
).highlight
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def cache_key
|
||||
['commit', @diffable.id]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ module Gitlab
|
|||
|
||||
delegate :old_path, :new_path, :old_sha, :new_sha, to: :diff_file, prefix: :diff
|
||||
|
||||
def initialize(diff_lines, repository: nil)
|
||||
def initialize(diff_lines, repository: nil, plain: false)
|
||||
@repository = repository
|
||||
@project = repository&.project
|
||||
@plain = plain
|
||||
|
||||
if diff_lines.is_a?(Gitlab::Diff::File)
|
||||
@diff_file = diff_lines
|
||||
|
|
@ -42,6 +43,8 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :plain
|
||||
|
||||
def populate_marker_ranges
|
||||
pair_selector = Gitlab::Diff::PairSelector.new(@raw_lines)
|
||||
|
||||
|
|
@ -77,7 +80,7 @@ module Gitlab
|
|||
|
||||
def highlight_line(diff_line)
|
||||
return unless diff_file && diff_file.diff_refs
|
||||
return diff_line_highlighting(diff_line, plain: true) if blobs_too_large?
|
||||
return diff_line_highlighting(diff_line, plain: true) if blobs_too_large? || plain
|
||||
|
||||
if Feature.enabled?(:diff_line_syntax_highlighting, project)
|
||||
diff_line_highlighting(diff_line)
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ module Gitlab
|
|||
end
|
||||
|
||||
def gitlab_id
|
||||
project.import_data.user_mapping_enabled? ? gitlab_user&.id : find_by_email
|
||||
user_mapping_enabled? ? gitlab_user&.id : find_by_email
|
||||
end
|
||||
strong_memoize_attr :gitlab_id
|
||||
|
||||
def source_user
|
||||
return if !project.import_data.user_mapping_enabled? || ghost_user?
|
||||
return if !user_mapping_enabled? || ghost_user?
|
||||
|
||||
source_user_mapper.find_or_create_source_user(
|
||||
source_name: gitea_user[:full_name].presence || gitea_user[:login],
|
||||
|
|
@ -78,6 +78,11 @@ module Gitlab
|
|||
source_user.mapped_user
|
||||
end
|
||||
strong_memoize_attr :gitlab_user
|
||||
|
||||
def user_mapping_enabled?
|
||||
project.import_data.reset.user_mapping_enabled?
|
||||
end
|
||||
strong_memoize_attr :user_mapping_enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ module Gitlab
|
|||
def generate_unique_domain(project)
|
||||
10.times do
|
||||
pages_unique_domain = Gitlab::Pages::RandomDomain.generate(project_path: project.path)
|
||||
return pages_unique_domain unless ProjectSetting.unique_domain_exists?(pages_unique_domain)
|
||||
return pages_unique_domain unless
|
||||
ProjectSetting.unique_domain_exists?(pages_unique_domain) ||
|
||||
Namespace.top_level.by_path(pages_unique_domain).present?
|
||||
end
|
||||
|
||||
raise UniqueDomainGenerationFailure
|
||||
|
|
|
|||
|
|
@ -164,12 +164,14 @@ module Gitlab
|
|||
end
|
||||
|
||||
def parse_and_transform(input)
|
||||
AST::Transformer.new.apply(parse(input))
|
||||
Timeout.timeout(2.seconds) { AST::Transformer.new.apply(parse(input)) }
|
||||
rescue Parslet::ParseFailed => ex
|
||||
# We raise a custom error so it's easier to catch different parser
|
||||
# related errors. In addition, this ensures the caller of this method
|
||||
# doesn't depend on a Parslet specific error class.
|
||||
raise Error, "Failed to parse the template: #{ex.message}"
|
||||
rescue Timeout::Error
|
||||
raise Error, 'Template parser timeout. Consider reducing the size of the template'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4931,9 +4931,6 @@ msgstr ""
|
|||
msgid "AdminUsers|user cap"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administrator"
|
||||
msgstr ""
|
||||
|
||||
msgid "Administrator Account Setup"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7458,9 +7455,6 @@ msgstr ""
|
|||
msgid "Are you sure you want to %{action} %{name}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to %{action} this directory?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to approve %{user}?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46181,18 +46175,9 @@ msgstr ""
|
|||
msgid "Replace all labels"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replace audio"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replace file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replace image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replace video"
|
||||
msgstr ""
|
||||
|
||||
msgid "Replaced all labels with %{label_references} %{label_text}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -64232,6 +64217,9 @@ msgstr ""
|
|||
msgid "You do not have permission to leave this %{namespaceType}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You do not have permission to lock this"
|
||||
msgstr ""
|
||||
|
||||
msgid "You do not have permission to run a pipeline on this branch."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -297,6 +297,18 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def change_pipeline_variables_minimum_override_role(new_role)
|
||||
response = put(request_url(api_put_path), ci_pipeline_variables_minimum_override_role: new_role)
|
||||
|
||||
return if response.code == HTTP_STATUS_OK
|
||||
|
||||
raise(
|
||||
ResourceUpdateFailedError,
|
||||
"Failed to update pipeline_variables_minimum_override_role to '#{new_role}'. " \
|
||||
"Response (#{response.code}): `#{response}`."
|
||||
)
|
||||
end
|
||||
|
||||
def change_repository_storage(new_storage)
|
||||
response = put(request_url(api_put_path), repository_storage: new_storage)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ module QA
|
|||
before do
|
||||
Flow::Login.sign_in
|
||||
|
||||
project.change_pipeline_variables_minimum_override_role('developer')
|
||||
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:go_to_pipelines)
|
||||
Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ module QA
|
|||
Flow::Login.sign_in
|
||||
add_ci_file(downstream_project, downstream_ci_file)
|
||||
add_ci_file(upstream_project, upstream_ci_file)
|
||||
|
||||
upstream_project.change_pipeline_variables_minimum_override_role('developer')
|
||||
downstream_project.change_pipeline_variables_minimum_override_role('developer')
|
||||
upstream_project.visit!
|
||||
Flow::Pipeline.visit_latest_pipeline(status: 'Passed')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ module QA
|
|||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
upstream_project.change_pipeline_variables_minimum_override_role('developer')
|
||||
downstream1_project.change_pipeline_variables_minimum_override_role('developer')
|
||||
downstream2_project.change_pipeline_variables_minimum_override_role('developer')
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ ee/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js
|
|||
ee/spec/frontend/vue_shared/security_reports/components/security_training_promo_spec.js
|
||||
ee/spec/frontend/vulnerabilities/generic_report/types/list_graphql_spec.js
|
||||
ee/spec/frontend/vulnerabilities/related_issues_spec.js
|
||||
ee/spec/frontend/vulnerabilities/vulnerability_file_contents_spec.js
|
||||
spec/frontend/__helpers__/vue_test_utils_helper_spec.js
|
||||
spec/frontend/access_tokens/index_spec.js
|
||||
spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ RSpec.describe Gitlab::Application, feature_category: :scalability do # rubocop:
|
|||
'/?safe[note]=secret&target_type=1' | { 'safe' => { 'note' => filtered }, 'target_type' => '1' }
|
||||
'/?safe[selectedText]=secret' | { 'safe' => { 'selectedText' => filtered } }
|
||||
'/?selectedText=secret' | { 'selectedText' => filtered }
|
||||
'/?query=secret' | { 'query' => filtered }
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
|||
|
|
@ -314,6 +314,46 @@ RSpec.describe OmniauthCallbacksController, type: :controller, feature_category:
|
|||
|
||||
it_behaves_like 'omniauth sign in that remembers user with two factor enabled'
|
||||
end
|
||||
|
||||
context 'when redirect fragment contains special characters' do
|
||||
before do
|
||||
request.env['omniauth.params'] = { 'redirect_fragment' => 'confirm-merge_request_diff_id-context' }
|
||||
end
|
||||
|
||||
it 'redirects with fragment' do
|
||||
post provider, session: { user_return_to: '/fake/url' }
|
||||
|
||||
expect(response).to redirect_to('/fake/url#confirm-merge_request_diff_id-context')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stored redirect fragment is malicious' do
|
||||
let(:malicious_redirect_fragment) { '#code=test_code&' }
|
||||
|
||||
before do
|
||||
request.env['omniauth.params'] = { 'redirect_fragment' => malicious_redirect_fragment }
|
||||
end
|
||||
|
||||
it 'fails login and redirects to login path' do
|
||||
post provider, session: { user_return_to: '/fake/url#replaceme' }
|
||||
|
||||
expect(response.redirect?).to be true
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
expect(flash[:alert]).to match(/Invalid state/)
|
||||
end
|
||||
|
||||
context 'when fragment has encoded content' do
|
||||
let_it_be(:malicious_redirect_fragment, reload: true) { '#code%3Dtest_code&L90' }
|
||||
|
||||
it 'fails login and redirects to login path' do
|
||||
post provider, session: { user_return_to: '/fake/url#replaceme' }
|
||||
|
||||
expect(response.redirect?).to be true
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
expect(flash[:alert]).to match(/Invalid state/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with strategies' do
|
||||
|
|
@ -720,7 +760,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller, feature_category:
|
|||
|
||||
post :saml, params: { SAMLResponse: mock_saml_response }
|
||||
|
||||
expect(flash[:alert]).to eq('Signing in using your SAML account without a pre-existing account in localhost is not allowed. Create an account in localhost first, and then <a href="/help/user/profile/index.md#sign-in-services">connect it to your SAML account</a>.')
|
||||
expect(flash[:alert]).to eq("Signing in using your SAML account without a pre-existing account in #{Gitlab.config.gitlab.host} is not allowed. Create an account in #{Gitlab.config.gitlab.host} first, and then <a href=\"/help/user/profile/index.md#sign-in-services\">connect it to your SAML account</a>.")
|
||||
expect(response).to redirect_to(new_user_registration_path)
|
||||
end
|
||||
end
|
||||
|
|
@ -735,7 +775,7 @@ RSpec.describe OmniauthCallbacksController, type: :controller, feature_category:
|
|||
|
||||
post :saml, params: { SAMLResponse: mock_saml_response }
|
||||
|
||||
expect(flash[:alert]).to eq('Signing in using your SAML account without a pre-existing account in localhost is not allowed.')
|
||||
expect(flash[:alert]).to eq("Signing in using your SAML account without a pre-existing account in #{Gitlab.config.gitlab.host} is not allowed.")
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ RSpec.describe Projects::CommitController, feature_category: :source_code_manage
|
|||
{
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: commit.id,
|
||||
id: master_pickable_sha,
|
||||
format: format
|
||||
}
|
||||
end
|
||||
|
|
@ -471,6 +471,25 @@ RSpec.describe Projects::CommitController, feature_category: :source_code_manage
|
|||
expect(assigns(:environment)).to be_nil
|
||||
end
|
||||
|
||||
it 'preloads highlights' do
|
||||
allow(Process).to receive(:clock_gettime).and_call_original
|
||||
allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC, :second).and_return(0, 4, 8, 12, 16, 20)
|
||||
|
||||
diff_highlight = instance_double(Gitlab::Diff::Highlight, highlight: [])
|
||||
allow(Gitlab::Diff::Highlight).to receive(:new).and_return(diff_highlight)
|
||||
|
||||
send_request
|
||||
|
||||
assigns(:diffs).diff_files.each do |diff_file|
|
||||
expect(diff_file.instance_variable_get(:@highlighted_diff_lines)).not_to be_nil
|
||||
end
|
||||
|
||||
expect(Gitlab::Diff::Highlight)
|
||||
.to have_received(:new).with(anything, hash_including(plain: false)).twice.times
|
||||
expect(Gitlab::Diff::Highlight)
|
||||
.to have_received(:new).with(anything, hash_including(plain: true)).exactly(4).times
|
||||
end
|
||||
|
||||
context 'when format is not html' do
|
||||
let(:format) { :json }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
|
|||
let_it_be(:guest) { create(:user) }
|
||||
|
||||
before_all do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
project.add_owner(owner)
|
||||
project.add_maintainer(maintainer)
|
||||
project.add_developer(developer)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
let_it_be_with_reload(:project) { create(:project, :public, :repository, developers: user) }
|
||||
let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
|
||||
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
end
|
||||
|
||||
shared_examples 'access update schedule' do
|
||||
describe 'security' do
|
||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||
|
|
@ -178,7 +182,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
|
||||
context 'when the user is not allowed to create a pipeline schedule with variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(restrict_user_defined_variables: true,
|
||||
ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
it 'does not create a new schedule' do
|
||||
|
|
@ -272,7 +277,8 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu
|
|||
|
||||
context 'when the user is not allowed to update pipeline schedule variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(restrict_user_defined_variables: true,
|
||||
ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
it 'does not update the schedule' do
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ RSpec.describe 'User triggers manual job with variables', :js, feature_category:
|
|||
let!(:build) { create(:ci_build, :manual, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
project.add_maintainer(user)
|
||||
project.enable_ci
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
|
|||
let!(:user) { create(:user) }
|
||||
let!(:maintainer) { create(:user) }
|
||||
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
end
|
||||
|
||||
context 'logged in as the pipeline schedule owner' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
|
|
|||
|
|
@ -789,6 +789,10 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do
|
|||
end
|
||||
|
||||
context 'when variables are specified' do
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
end
|
||||
|
||||
it 'creates a new pipeline with variables' do
|
||||
within_testid('ci-variable-row-container') do
|
||||
find_by_testid('pipeline-form-ci-variable-key-field').set('key_name')
|
||||
|
|
|
|||
|
|
@ -9,12 +9,7 @@ import Audio from '~/content_editor/extensions/audio';
|
|||
import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
|
||||
import Image from '~/content_editor/extensions/image';
|
||||
import Video from '~/content_editor/extensions/video';
|
||||
import {
|
||||
createTestEditor,
|
||||
emitEditorEvent,
|
||||
mockChainedCommands,
|
||||
createTransactionWithMeta,
|
||||
} from '../../test_utils';
|
||||
import { createTestEditor, emitEditorEvent, createTransactionWithMeta } from '../../test_utils';
|
||||
import {
|
||||
PROJECT_WIKI_ATTACHMENT_IMAGE_HTML,
|
||||
PROJECT_WIKI_ATTACHMENT_AUDIO_HTML,
|
||||
|
|
@ -83,14 +78,6 @@ describe.each`
|
|||
return showMenu();
|
||||
};
|
||||
|
||||
const selectFile = async (file) => {
|
||||
const input = wrapper.findComponent({ ref: 'fileSelector' });
|
||||
|
||||
// override the property definition because `input.files` isn't directly modifyable
|
||||
Object.defineProperty(input.element, 'files', { value: [file], writable: true });
|
||||
await input.trigger('change');
|
||||
};
|
||||
|
||||
const expectLinkButtonsToExist = (exist = true) => {
|
||||
expect(wrapper.findComponent(GlLink).exists()).toBe(exist);
|
||||
expect(wrapper.findByTestId('edit-media').exists()).toBe(exist);
|
||||
|
|
@ -177,44 +164,6 @@ describe.each`
|
|||
});
|
||||
});
|
||||
|
||||
describe(`replace ${mediaType} button`, () => {
|
||||
beforeEach(buildWrapperAndDisplayMenu);
|
||||
|
||||
if (mediaType !== 'drawioDiagram') {
|
||||
it('uploads and replaces the selected image when file input changes', async () => {
|
||||
const commands = mockChainedCommands(tiptapEditor, [
|
||||
'focus',
|
||||
'deleteSelection',
|
||||
'uploadAttachment',
|
||||
'run',
|
||||
]);
|
||||
const file = new File(['foo'], 'foo.png', { type: 'image/png' });
|
||||
|
||||
await wrapper.findByTestId('replace-media').vm.$emit('click');
|
||||
await selectFile(file);
|
||||
|
||||
expect(commands.focus).toHaveBeenCalled();
|
||||
expect(commands.deleteSelection).toHaveBeenCalled();
|
||||
expect(commands.uploadAttachment).toHaveBeenCalledWith({ file });
|
||||
expect(commands.run).toHaveBeenCalled();
|
||||
});
|
||||
} else {
|
||||
// draw.io diagrams are replaced using the edit diagram button
|
||||
it('invokes editDiagram command', async () => {
|
||||
const commands = mockChainedCommands(tiptapEditor, [
|
||||
'focus',
|
||||
'createOrEditDiagram',
|
||||
'run',
|
||||
]);
|
||||
await wrapper.findByTestId('edit-diagram').vm.$emit('click');
|
||||
|
||||
expect(commands.focus).toHaveBeenCalled();
|
||||
expect(commands.createOrEditDiagram).toHaveBeenCalled();
|
||||
expect(commands.run).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('edit button', () => {
|
||||
let mediaSrcInput;
|
||||
let mediaAltInput;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ import { builders } from 'prosemirror-test-builder';
|
|||
import Link from '~/content_editor/extensions/link';
|
||||
import { createTestEditor, triggerMarkInputRule } from '../test_utils';
|
||||
|
||||
const GFM_LINK_HTML =
|
||||
'<p data-sourcepos="1:1-1:63" dir="auto"><a href="https://gitlab.com/gitlab-org/gitlab-test/-/issues/1" data-reference-type="issue" data-original="test" data-link="true" data-link-reference="true" data-issue="11" data-project="2" data-iid="1" data-namespace-path="gitlab-org/gitlab-test" data-project-path="gitlab-org/gitlab-test" data-issue-type="issue" data-container="body" data-placement="top" title="Rerum vero repellat saepe sunt ullam provident." class="gfm gfm-issue">test</a></p>';
|
||||
|
||||
describe('content_editor/extensions/link', () => {
|
||||
let tiptapEditor;
|
||||
let doc;
|
||||
|
|
@ -13,10 +16,6 @@ describe('content_editor/extensions/link', () => {
|
|||
({ doc, paragraph: p, link } = builders(tiptapEditor.schema));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tiptapEditor.destroy();
|
||||
});
|
||||
|
||||
it.each`
|
||||
input | insertedNode
|
||||
${'[gitlab](https://gitlab.com)'} | ${() => p(link({ href: 'https://gitlab.com' }, 'gitlab'))}
|
||||
|
|
@ -32,4 +31,15 @@ describe('content_editor/extensions/link', () => {
|
|||
|
||||
expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON());
|
||||
});
|
||||
|
||||
describe('when parsing HTML', () => {
|
||||
it('ignores titles for links with "gfm" class in it', () => {
|
||||
const expectedDoc = doc(
|
||||
p(link({ href: 'https://gitlab.com/gitlab-org/gitlab-test/-/issues/1' }, 'test')),
|
||||
);
|
||||
tiptapEditor.commands.setContent(GFM_LINK_HTML);
|
||||
|
||||
expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { gql, execute, ApolloLink, Observable } from '@apollo/client/core';
|
||||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
|
||||
import { sentryBreadcrumbLink } from '~/lib/apollo/sentry_breadcrumb_link';
|
||||
|
||||
jest.mock('~/sentry/sentry_browser_wrapper');
|
||||
|
||||
const executeQuery = async (query, correlationId) => {
|
||||
const terminatingLink = new ApolloLink(() =>
|
||||
Observable.of({ data: { things: 1 }, correlationId }),
|
||||
);
|
||||
const mockLink = sentryBreadcrumbLink.concat(terminatingLink);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
execute(mockLink, { query }).subscribe(resolve);
|
||||
});
|
||||
};
|
||||
|
||||
describe('sentryBreadcrumbLink', () => {
|
||||
describe('with a named query', () => {
|
||||
const QUERY = gql`
|
||||
query getThings {
|
||||
things
|
||||
}
|
||||
`;
|
||||
|
||||
beforeEach(async () => {
|
||||
await executeQuery(QUERY, 'my-correlation-id');
|
||||
});
|
||||
|
||||
it('addBreadcrumb is called', () => {
|
||||
expect(Sentry.addBreadcrumb).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(Sentry.addBreadcrumb).toHaveBeenNthCalledWith(1, {
|
||||
level: 'info',
|
||||
category: 'graphql.request',
|
||||
data: {
|
||||
operationName: 'getThings',
|
||||
},
|
||||
});
|
||||
|
||||
expect(Sentry.addBreadcrumb).toHaveBeenNthCalledWith(2, {
|
||||
level: 'info',
|
||||
category: 'graphql.response',
|
||||
data: {
|
||||
operationName: 'getThings',
|
||||
correlationId: 'my-correlation-id',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -141,9 +141,10 @@ describe('ml/model_registry/apps/show_model_version.vue', () => {
|
|||
|
||||
it('displays model version edit button', () => {
|
||||
expect(findModelVersionEditButton().props()).toMatchObject({
|
||||
variant: 'confirm',
|
||||
category: 'primary',
|
||||
variant: 'default',
|
||||
category: 'secondary',
|
||||
});
|
||||
expect(findModelVersionEditButton().text()).toBe('Edit');
|
||||
});
|
||||
|
||||
describe('when user has no permission to write model registry', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable no-restricted-imports */
|
||||
import { captureException, SDK_VERSION } from '@sentry/browser';
|
||||
import { captureException, addBreadcrumb, SDK_VERSION } from '@sentry/browser';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
import { initSentry } from '~/sentry/init_sentry';
|
||||
|
|
@ -111,6 +111,7 @@ describe('SentryConfig', () => {
|
|||
// eslint-disable-next-line no-underscore-dangle
|
||||
expect(window._Sentry).toEqual({
|
||||
captureException,
|
||||
addBreadcrumb,
|
||||
SDK_VERSION,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
|
||||
const mockError = new Error('error!');
|
||||
const mockBreadcrumb = { category: 'mockCategory' };
|
||||
|
||||
describe('SentryBrowserWrapper', () => {
|
||||
beforeAll(() => {
|
||||
|
|
@ -15,10 +16,12 @@ describe('SentryBrowserWrapper', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
jest.spyOn(console, 'debug').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.error.mockRestore();
|
||||
console.debug.mockRestore();
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
delete window._Sentry;
|
||||
|
|
@ -35,17 +38,31 @@ describe('SentryBrowserWrapper', () => {
|
|||
{ 0: mockError },
|
||||
);
|
||||
});
|
||||
|
||||
it('addBreadcrumb will report to console instead', () => {
|
||||
Sentry.addBreadcrumb(mockBreadcrumb);
|
||||
|
||||
expect(console.debug).toHaveBeenCalledTimes(1);
|
||||
expect(console.debug).toHaveBeenCalledWith(
|
||||
'[Sentry stub]',
|
||||
'addBreadcrumb(...) called with:',
|
||||
{ 0: mockBreadcrumb },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when _Sentry is defined', () => {
|
||||
let mockCaptureException;
|
||||
let mockAddBreadcrumb;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCaptureException = jest.fn();
|
||||
mockAddBreadcrumb = jest.fn();
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
window._Sentry = {
|
||||
captureException: mockCaptureException,
|
||||
addBreadcrumb: mockAddBreadcrumb,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -54,5 +71,11 @@ describe('SentryBrowserWrapper', () => {
|
|||
|
||||
expect(mockCaptureException).toHaveBeenCalledWith(mockError);
|
||||
});
|
||||
|
||||
it('addBreadcrumb is called', () => {
|
||||
Sentry.addBreadcrumb(mockBreadcrumb);
|
||||
|
||||
expect(mockAddBreadcrumb).toHaveBeenCalledWith(mockBreadcrumb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1242,6 +1242,26 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
|
|||
expect(protectable_branches).to match_array(branch_names)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without read_code permissions' do
|
||||
before do
|
||||
project.add_guest(current_user)
|
||||
end
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(protectable_branches).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with read_code permissions' do
|
||||
before do
|
||||
project.add_member(current_user, Gitlab::Access::REPORTER)
|
||||
end
|
||||
|
||||
it 'returns all the branch names' do
|
||||
expect(protectable_branches).to match_array(branch_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe API::Entities::VirtualRegistries::Packages::Maven::CachedResponse
|
|||
subject { described_class.new(cached_response).as_json }
|
||||
|
||||
it 'has the expected attributes' do
|
||||
is_expected.to include(:cached_response_id, :group_id, :upstream_id, :upstream_checked_at, :created_at, :updated_at,
|
||||
is_expected.to include(:id, :group_id, :upstream_id, :upstream_checked_at, :created_at, :updated_at,
|
||||
:file, :file_md5, :file_sha1, :size, :relative_path, :upstream_etag, :content_type)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -130,11 +130,17 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
|
|||
end
|
||||
|
||||
context 'with oauth token' do
|
||||
let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
|
||||
let(:doorkeeper_access_token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api', organization_id: organization.id) }
|
||||
let_it_be(:oauth_application) { create(:oauth_application, owner: user) }
|
||||
let(:oauth_access_token) do
|
||||
create(:oauth_access_token,
|
||||
application_id: oauth_application.id,
|
||||
resource_owner_id: user.id,
|
||||
scopes: 'api',
|
||||
organization_id: organization.id)
|
||||
end
|
||||
|
||||
before do
|
||||
set_bearer_token(doorkeeper_access_token.plaintext_token)
|
||||
set_bearer_token(oauth_access_token.plaintext_token)
|
||||
end
|
||||
|
||||
it { is_expected.to eq user }
|
||||
|
|
@ -708,18 +714,11 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
|
|||
end
|
||||
|
||||
describe '#find_oauth_access_token' do
|
||||
let_it_be(:oauth_application) { create(:oauth_application, owner: user) }
|
||||
let(:scopes) { 'api' }
|
||||
|
||||
let(:application) do
|
||||
Doorkeeper::Application.create!(
|
||||
name: 'MyApp',
|
||||
redirect_uri: 'https://app.com',
|
||||
owner: user)
|
||||
end
|
||||
|
||||
let(:doorkeeper_access_token) do
|
||||
Doorkeeper::AccessToken.create!(
|
||||
application_id: application.id,
|
||||
let(:oauth_access_token) do
|
||||
create(:oauth_access_token,
|
||||
application_id: oauth_application.id,
|
||||
resource_owner_id: user.id,
|
||||
scopes: scopes,
|
||||
organization_id: organization.id)
|
||||
|
|
@ -727,19 +726,19 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
|
|||
|
||||
context 'passed as header' do
|
||||
before do
|
||||
set_bearer_token(doorkeeper_access_token.plaintext_token)
|
||||
set_bearer_token(oauth_access_token.plaintext_token)
|
||||
end
|
||||
|
||||
it 'returns token if valid oauth_access_token' do
|
||||
expect(find_oauth_access_token.token).to eq doorkeeper_access_token.token
|
||||
expect(find_oauth_access_token.token).to eq oauth_access_token.token
|
||||
end
|
||||
end
|
||||
|
||||
context 'passed as param' do
|
||||
it 'returns user if valid oauth_access_token' do
|
||||
set_param(:access_token, doorkeeper_access_token.plaintext_token)
|
||||
set_param(:access_token, oauth_access_token.plaintext_token)
|
||||
|
||||
expect(find_oauth_access_token.token).to eq doorkeeper_access_token.token
|
||||
expect(find_oauth_access_token.token).to eq oauth_access_token.token
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -765,7 +764,7 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
|
|||
user.username == 'gitlab-duo'
|
||||
end
|
||||
|
||||
set_bearer_token(doorkeeper_access_token.plaintext_token)
|
||||
set_bearer_token(oauth_access_token.plaintext_token)
|
||||
end
|
||||
|
||||
context 'when scoped user is specified' do
|
||||
|
|
@ -773,7 +772,7 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
|
|||
|
||||
context 'when linking composite identitiy succeeds' do
|
||||
it 'returns the oauth token' do
|
||||
expect(find_oauth_access_token.token).to eq(doorkeeper_access_token.token)
|
||||
expect(find_oauth_access_token.token).to eq(oauth_access_token.token)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -487,16 +487,28 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate
|
|||
end
|
||||
end
|
||||
|
||||
context 'while using OAuth tokens as passwords' do
|
||||
describe 'using OAuth tokens as passwords' do
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
|
||||
let(:user) { create(:user, organizations: [organization]) }
|
||||
let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
|
||||
let(:scopes) { 'api' }
|
||||
|
||||
let(:token) do
|
||||
Doorkeeper::AccessToken.create!(
|
||||
application_id: application.id,
|
||||
resource_owner_id: user.id,
|
||||
scopes: scopes,
|
||||
organization_id: organization.id).plaintext_token
|
||||
end
|
||||
|
||||
def authenticate(username:, password:)
|
||||
gl_auth.find_for_git_client(username, password, project: nil, request: request)
|
||||
end
|
||||
|
||||
shared_examples 'an oauth failure' do
|
||||
it 'fails' do
|
||||
access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api', organization_id: organization.id)
|
||||
|
||||
expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, request: request))
|
||||
expect(authenticate(username: "oauth2", password: token))
|
||||
.to have_attributes(auth_failure)
|
||||
end
|
||||
end
|
||||
|
|
@ -522,27 +534,28 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching, feature_cate
|
|||
|
||||
with_them do
|
||||
it 'authenticates with correct abilities' do
|
||||
access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: scopes, organization_id: organization.id)
|
||||
|
||||
expect(gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, request: request))
|
||||
expect(authenticate(username: 'oauth2', password: token))
|
||||
.to have_attributes(actor: user, project: nil, type: :oauth, authentication_abilities: abilities)
|
||||
end
|
||||
|
||||
it 'authenticates with correct abilities without special username' do
|
||||
access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: scopes, organization_id: organization.id)
|
||||
expect(authenticate(username: user.username, password: token))
|
||||
.to have_attributes(actor: user, project: nil, type: :oauth, authentication_abilities: abilities)
|
||||
end
|
||||
|
||||
expect(gl_auth.find_for_git_client(user.username, access_token.token, project: nil, request: request))
|
||||
it 'tracks any composite identity' do
|
||||
expect(::Gitlab::Auth::Identity).to receive(:link_from_oauth_token).and_call_original
|
||||
|
||||
expect(authenticate(username: "oauth2", password: token))
|
||||
.to have_attributes(actor: user, project: nil, type: :oauth, authentication_abilities: abilities)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not try password auth before oauth' do
|
||||
access_token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api', organization_id: organization.id)
|
||||
|
||||
expect(gl_auth).not_to receive(:find_with_user_password)
|
||||
|
||||
gl_auth.find_for_git_client("oauth2", access_token.token, project: nil, request: request)
|
||||
authenticate(username: "oauth2", password: token)
|
||||
end
|
||||
|
||||
context 'blocked user' do
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillCiRunnersPartitionedTable,
|
||||
feature_category: :runner,
|
||||
schema: 20241023144448,
|
||||
migration: :gitlab_ci do
|
||||
feature_category: :runner, migration: :gitlab_ci do
|
||||
let(:connection) { Ci::ApplicationRecord.connection }
|
||||
|
||||
describe '#perform' do
|
||||
|
|
@ -27,24 +25,19 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillCiRunnersPartitionedTable,
|
|||
end
|
||||
|
||||
before do
|
||||
# Don't sync records to partitioned table
|
||||
connection.execute <<~SQL
|
||||
DROP TRIGGER table_sync_trigger_61879721b5 ON ci_runners;
|
||||
SQL
|
||||
BEGIN;
|
||||
ALTER TABLE ci_runners DISABLE TRIGGER ALL; -- Don't sync records to partitioned table
|
||||
|
||||
runners.create!(runner_type: 1)
|
||||
runners.create!(runner_type: 2, sharding_key_id: 89)
|
||||
runners.create!(runner_type: 2, sharding_key_id: nil)
|
||||
runners.create!(runner_type: 3, sharding_key_id: 10)
|
||||
runners.create!(runner_type: 3, sharding_key_id: nil)
|
||||
runners.create!(runner_type: 3, sharding_key_id: 100)
|
||||
INSERT INTO ci_runners(runner_type) VALUES (1);
|
||||
INSERT INTO ci_runners(runner_type, sharding_key_id) VALUES (2, 89);
|
||||
INSERT INTO ci_runners(runner_type, sharding_key_id) VALUES (2, NULL);
|
||||
INSERT INTO ci_runners(runner_type, sharding_key_id) VALUES (3, 10);
|
||||
INSERT INTO ci_runners(runner_type, sharding_key_id) VALUES (3, NULL);
|
||||
INSERT INTO ci_runners(runner_type, sharding_key_id) VALUES (3, 100);
|
||||
|
||||
ensure
|
||||
connection.execute <<~SQL
|
||||
CREATE TRIGGER table_sync_trigger_61879721b5
|
||||
AFTER INSERT OR DELETE OR UPDATE ON ci_runners
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION table_sync_function_686d6c7993 ();
|
||||
ALTER TABLE ci_runners ENABLE TRIGGER ALL;
|
||||
COMMIT;
|
||||
SQL
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations, feature_categor
|
|||
|
||||
let(:step) { described_class.new(pipeline, command) }
|
||||
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
end
|
||||
|
||||
shared_examples 'breaks the chain' do
|
||||
it 'returns true' do
|
||||
step.perform!
|
||||
|
|
@ -85,7 +89,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations, feature_categor
|
|||
|
||||
context 'when project setting restrict_user_defined_variables is enabled' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(restrict_user_defined_variables: true, ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
context 'when user is developer' do
|
||||
|
|
|
|||
|
|
@ -140,6 +140,17 @@ RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_managemen
|
|||
expect(subject[2].rich_text).to be_html_safe
|
||||
end
|
||||
end
|
||||
|
||||
context 'when blob highlight is plain' do
|
||||
let(:subject) { described_class.new(diff_file, repository: project.repository, plain: true).highlight }
|
||||
|
||||
it 'blobs are highlighted as plain text without loading all data' do
|
||||
expect(diff_file.blob).not_to receive(:load_all_data!)
|
||||
|
||||
expect(subject[2].rich_text).to eq(%{ <span id="LC7" class="line" lang=""> def popen(cmd, path=nil)</span>\n})
|
||||
expect(subject[2].rich_text).to be_html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'diff highlighter'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter, :clean_gitlab_redis_shared_state, feature_category: :importers do
|
||||
include Import::GiteaHelper
|
||||
|
||||
let_it_be(:project) do
|
||||
create(:project, :with_import_url, :import_user_mapping_enabled, import_type: ::Import::SOURCE_GITEA)
|
||||
end
|
||||
|
|
@ -191,7 +193,7 @@ RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter, :clean_gitlab_redis
|
|||
let(:raw) { base.merge(user: octocat) }
|
||||
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
context 'when author is a GitLab user' do
|
||||
|
|
@ -277,7 +279,7 @@ RSpec.describe Gitlab::LegacyGithubImport::CommentFormatter, :clean_gitlab_redis
|
|||
|
||||
context 'when user contribution mapping is disabled' do
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'does not push any placeholder references' do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::LegacyGithubImport::Importer, :clean_gitlab_redis_shared_state, feature_category: :importers do
|
||||
include Import::GiteaHelper
|
||||
|
||||
subject(:importer) { described_class.new(project) }
|
||||
|
||||
let_it_be(:api_root) { 'https://try.gitea.io/api/v1' }
|
||||
|
|
@ -254,7 +256,7 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer, :clean_gitlab_redis_shared_
|
|||
|
||||
context 'when user contribution mapping is disabled' do
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'does not enqueue the worker to load placeholder references' do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, :clean_gitlab_redis_shared_state, feature_category: :importers do
|
||||
include Import::GiteaHelper
|
||||
|
||||
let_it_be(:project) do
|
||||
create(
|
||||
:project,
|
||||
|
|
@ -148,7 +150,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, :clean_gitlab_redis_s
|
|||
let(:raw_data) { base_data.merge(assignee: octocat) }
|
||||
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns nil as assignee_id when is not a GitLab user' do
|
||||
|
|
@ -216,7 +218,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, :clean_gitlab_redis_s
|
|||
let(:raw_data) { base_data.merge(user: octocat) }
|
||||
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns project creator_id as author_id when is not a GitLab user' do
|
||||
|
|
@ -407,7 +409,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter, :clean_gitlab_redis_s
|
|||
|
||||
context 'when user contribution mapping is disabled' do
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'does not push any placeholder references' do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, :clean_gitlab_redis_shared_state, feature_category: :importers do
|
||||
include Import::GiteaHelper
|
||||
|
||||
let_it_be(:project) do
|
||||
create(
|
||||
:project,
|
||||
|
|
@ -192,7 +194,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, :clean_gitlab_r
|
|||
let(:raw_data) { base_data.merge(assignee: octocat) }
|
||||
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns nil as assignee_id when is not a GitLab user' do
|
||||
|
|
@ -239,7 +241,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, :clean_gitlab_r
|
|||
let(:raw_data) { base_data.merge(user: octocat) }
|
||||
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns project creator_id as author_id when is not a GitLab user' do
|
||||
|
|
@ -546,7 +548,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter, :clean_gitlab_r
|
|||
|
||||
context 'when user contribution mapping is disabled' do
|
||||
before do
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'does not push any placeholder references' do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :importers do
|
||||
include Import::GiteaHelper
|
||||
|
||||
let_it_be(:project) { create(:project, :import_user_mapping_enabled, import_type: 'gitea') }
|
||||
|
||||
let_it_be(:source_user_mapper) do
|
||||
|
|
@ -72,7 +74,7 @@ RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :imp
|
|||
context 'when user contribution mapping is disabled' do
|
||||
before do
|
||||
allow(client).to receive(:user).and_return(gitea_user)
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns GitLab user id when user confirmed primary email matches Gitea email' do
|
||||
|
|
@ -118,7 +120,7 @@ RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :imp
|
|||
context 'and improved user mapping is disabled' do
|
||||
before do
|
||||
allow(client).to receive(:user).and_return(ghost_user)
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
|
|
@ -193,7 +195,7 @@ RSpec.describe Gitlab::LegacyGithubImport::UserFormatter, feature_category: :imp
|
|||
context 'when user contribution mapping is disabled' do
|
||||
before do
|
||||
allow(client).to receive(:user).and_return(gitea_user)
|
||||
allow(project).to receive_message_chain(:import_data, :user_mapping_enabled?).and_return(false)
|
||||
stub_user_mapping_chain(project, false)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ RSpec.describe Gitlab::Middleware::StripCookies, feature_category: :shared do
|
|||
let(:app) { mock_app.new }
|
||||
|
||||
subject do
|
||||
described_class.new(app, paths: [%r{^/assets/}, %r{^/v2$}, %r{^/v2/}])
|
||||
described_class.new(app, paths: [%r{^/assets/}])
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
|
|
@ -36,10 +36,6 @@ RSpec.describe Gitlab::Middleware::StripCookies, feature_category: :shared do
|
|||
"/assets/test.css" | false
|
||||
"/something/assets/test.css" | true
|
||||
"/merge_requests/1" | true
|
||||
"/v2" | false
|
||||
"/v2/" | false
|
||||
"/v2/something" | false
|
||||
"/v2something" | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
|||
|
|
@ -123,9 +123,10 @@ RSpec.describe Gitlab::Pages::VirtualHostFinder, feature_category: :pages do
|
|||
|
||||
context 'when a project path conflicts with a unique domain' do
|
||||
it 'prioritizes the unique domain project' do
|
||||
group = create(:group, path: 'unique-domain')
|
||||
group = build(:group, path: 'unique-domain')
|
||||
.tap { |g| g.save!(validate: false) }
|
||||
other_project = build(:project, path: 'unique-domain.example.com', group: group)
|
||||
.tap { |project| project.save!(validate: false) }
|
||||
.tap { |project| project.save!(validate: false) }
|
||||
|
||||
create(:pages_deployment, project: project)
|
||||
create(:pages_deployment, project: other_project)
|
||||
|
|
|
|||
|
|
@ -141,6 +141,24 @@ RSpec.describe Gitlab::Pages, feature_category: :pages do
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'generates a different unique domain' do |entity|
|
||||
let!(:existing_entity) { create(entity, path: 'existing-path') }
|
||||
|
||||
context "when #{entity} path is already in use" do
|
||||
it 'assigns a different unique domain to pages' do
|
||||
allow(Gitlab::Pages::RandomDomain).to receive(:generate).and_return('existing-path', 'new-unique-domain')
|
||||
|
||||
described_class.add_unique_domain_to(project)
|
||||
|
||||
expect(project.project_setting.pages_unique_domain_enabled).to eq(true)
|
||||
expect(project.project_setting.pages_unique_domain).to eq('new-unique-domain')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'generates a different unique domain', :group
|
||||
it_behaves_like 'generates a different unique domain', :namespace
|
||||
|
||||
context 'when generated 10 unique domains are already in use' do
|
||||
it 'raises an error' do
|
||||
allow(Gitlab::Pages::RandomDomain).to receive(:generate).and_return('existing-domain')
|
||||
|
|
|
|||
|
|
@ -74,5 +74,19 @@ RSpec.describe Gitlab::TemplateParser::Parser do
|
|||
expect { parser.parse_and_transform('{% each') }
|
||||
.to raise_error(Gitlab::TemplateParser::Error)
|
||||
end
|
||||
|
||||
context 'when Parslet times out' do
|
||||
before do
|
||||
allow_next_instance_of(Gitlab::TemplateParser::AST::Transformer) do |instance|
|
||||
allow(instance).to receive(:apply).and_raise(Timeout::Error)
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises a custom timeout error' do
|
||||
expect { parser.parse_and_transform('foo') }
|
||||
.to raise_error(Gitlab::TemplateParser::Error,
|
||||
'Template parser timeout. Consider reducing the size of the template')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -324,6 +324,16 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
|
||||
expect(group).not_to be_valid
|
||||
end
|
||||
|
||||
it 'rejects paths already assigned to any pages unique domain' do
|
||||
# Simulate the existing domain being in use
|
||||
create(:project_setting, pages_unique_domain: 'existing-domain')
|
||||
|
||||
group = build(:group, path: 'existing-domain')
|
||||
|
||||
expect(group).not_to be_valid
|
||||
expect(group.errors.full_messages.to_sentence).to eq('Group URL has already been taken')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#notification_settings' do
|
||||
|
|
@ -2829,6 +2839,18 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
group.update!(name: 'new name')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the path is changed to existing pages unique domain' do
|
||||
let(:new_path) { 'existing-domain' }
|
||||
|
||||
it 'rejects path' do
|
||||
# Simulate the existing domain being in use
|
||||
create(:project_setting, pages_unique_domain: 'existing-domain')
|
||||
|
||||
expect(group.update(path: new_path)).to be_falsey
|
||||
expect(group.errors.full_messages.to_sentence).to eq('Group URL has already been taken')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -451,4 +451,59 @@ RSpec.describe NamespaceSetting, feature_category: :groups_and_projects, type: :
|
|||
it { is_expected.not_to allow_value({ foo: 'bar' }).for(:default_branch_protection_defaults) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'pipeline_variables_default_role' do
|
||||
subject { group.namespace_settings.pipeline_variables_default_role }
|
||||
|
||||
context 'validations' do
|
||||
let(:namespace_settings) { build(:namespace_settings) }
|
||||
|
||||
context 'when pipeline_variables_default_role is valid' do
|
||||
it 'does not add an error' do
|
||||
valid_roles = ProjectCiCdSetting::PIPELINE_VARIABLES_OVERRIDE_ROLES.keys.map(&:to_s)
|
||||
|
||||
valid_roles.each do |role|
|
||||
namespace_settings.pipeline_variables_default_role = role
|
||||
expect(namespace_settings).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an invalid role is assigned to pipeline_variables_default_role' do
|
||||
it 'raises an ArgumentError' do
|
||||
expect do
|
||||
namespace_settings.pipeline_variables_default_role = 'invalid_role'
|
||||
end.to raise_error(ArgumentError, "'invalid_role' is not a valid pipeline_variables_default_role")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace is root' do
|
||||
let(:group) { create(:group) }
|
||||
let(:variables_default_role) { group.namespace_settings.pipeline_variables_default_role }
|
||||
|
||||
it { expect(variables_default_role).to eq('no_one_allowed') }
|
||||
|
||||
context 'when feature flag `change_namespace_default_role_for_pipeline_variables` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(change_namespace_default_role_for_pipeline_variables: false)
|
||||
end
|
||||
|
||||
it { expect(variables_default_role).to eq('developer') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when namespace is not root' do
|
||||
let(:root_group) { create(:group) }
|
||||
|
||||
before do
|
||||
group.parent = root_group
|
||||
|
||||
root_settings = group.parent.namespace_settings
|
||||
root_settings.pipeline_variables_default_role = 'maintainer'
|
||||
end
|
||||
|
||||
it { is_expected.to eq('maintainer') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1522,12 +1522,14 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
it "cleans the path and makes sure it's available", time_travel_to: '2023-04-20 00:07 -0700' do
|
||||
create :user, username: "johngitlab-etc"
|
||||
create :namespace, path: "JohnGitLab-etc1"
|
||||
create :project_setting, pages_unique_domain: 'existing-domain'
|
||||
[nil, 1, 2, 3].each do |count|
|
||||
create :namespace, path: "pickle#{count}"
|
||||
end
|
||||
|
||||
expect(described_class.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
|
||||
expect(described_class.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
|
||||
expect(described_class.clean_path("existing-domain")).to eq("existing-domain1")
|
||||
|
||||
# when we have more than MAX_TRIES count of a path use a more randomized suffix
|
||||
expect(described_class.clean_path("pickle@gmail.com")).to eq("pickle4")
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ RSpec.describe ProjectCiCdSetting, feature_category: :continuous_integration do
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
describe 'validations' do
|
||||
let(:project) { build(:project) }
|
||||
|
||||
subject { described_class.new(project: project) }
|
||||
|
||||
it 'validates default_git_depth is between 0 and 1000 or nil' do
|
||||
expect(subject).to validate_numericality_of(:default_git_depth)
|
||||
.only_integer
|
||||
|
|
@ -44,8 +48,54 @@ RSpec.describe ProjectCiCdSetting, feature_category: :continuous_integration do
|
|||
end
|
||||
|
||||
describe '#pipeline_variables_minimum_override_role' do
|
||||
it 'is maintainer by default' do
|
||||
expect(described_class.new.pipeline_variables_minimum_override_role).to eq('maintainer')
|
||||
shared_examples 'enables restrict_user_defined_variables' do
|
||||
it 'enables restrict_user_defined_variables' do
|
||||
expect(project.restrict_user_defined_variables).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'sets the default ci_pipeline_variables_minimum_override_role' do |expected_role|
|
||||
it "sets ci_pipeline_variables_minimum_override_role to #{expected_role}" do
|
||||
expect(project.ci_pipeline_variables_minimum_override_role).to eq(expected_role)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a namespace is defined' do
|
||||
let_it_be(:project) { create(:project, :with_namespace_settings) }
|
||||
|
||||
it_behaves_like 'sets the default ci_pipeline_variables_minimum_override_role', 'no_one_allowed'
|
||||
|
||||
it_behaves_like 'enables restrict_user_defined_variables'
|
||||
end
|
||||
|
||||
context 'when a namespace is not defined' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
it_behaves_like 'sets the default ci_pipeline_variables_minimum_override_role', 'developer'
|
||||
|
||||
it_behaves_like 'enables restrict_user_defined_variables'
|
||||
end
|
||||
|
||||
context 'when feature flag `change_namespace_default_role_for_pipeline_variables` is disabled' do
|
||||
before do
|
||||
stub_feature_flags(change_namespace_default_role_for_pipeline_variables: false)
|
||||
end
|
||||
|
||||
context 'and a namespace is defined' do
|
||||
let(:project) { create(:project, :with_namespace_settings) }
|
||||
|
||||
it_behaves_like 'sets the default ci_pipeline_variables_minimum_override_role', 'developer'
|
||||
|
||||
it_behaves_like 'enables restrict_user_defined_variables'
|
||||
end
|
||||
|
||||
context 'and a namespace is not defined' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it_behaves_like 'sets the default ci_pipeline_variables_minimum_override_role', 'developer'
|
||||
|
||||
it_behaves_like 'enables restrict_user_defined_variables'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -79,15 +129,13 @@ RSpec.describe ProjectCiCdSetting, feature_category: :continuous_integration do
|
|||
|
||||
describe '#default_git_depth' do
|
||||
let(:default_value) { described_class::DEFAULT_GIT_DEPTH }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
it 'sets default value for new records' do
|
||||
project = create(:project)
|
||||
|
||||
expect(project.ci_cd_settings.default_git_depth).to eq(default_value)
|
||||
end
|
||||
|
||||
it 'does not set default value if present' do
|
||||
project = build(:project)
|
||||
project.build_ci_cd_settings(default_git_depth: 0)
|
||||
project.save!
|
||||
|
||||
|
|
|
|||
|
|
@ -695,6 +695,18 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the username is assigned to another project pages unique domain' do
|
||||
let(:username) { 'existing-domain' }
|
||||
|
||||
it 'is invalid' do
|
||||
# Simulate the existing domain being in use
|
||||
create(:project_setting, pages_unique_domain: 'existing-domain')
|
||||
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors.full_messages).to eq(['Username has already been taken'])
|
||||
end
|
||||
end
|
||||
|
||||
Mime::EXTENSION_LOOKUP.keys.each do |type|
|
||||
context 'with extension format' do
|
||||
let(:username) { "test.#{type}" }
|
||||
|
|
@ -6450,6 +6462,14 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
expect(user.errors[:base]).to include('A user, alias, or group already exists with that username.')
|
||||
end
|
||||
end
|
||||
|
||||
it 'when the username is assigned to another project pages unique domain' do
|
||||
# Simulate the existing domain being in use
|
||||
create(:project_setting, pages_unique_domain: 'existing-domain')
|
||||
|
||||
expect(user.update(username: 'existing-domain')).to be_falsey
|
||||
expect(user.errors.full_messages).to eq(['Username has already been taken'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the username is not changed' do
|
||||
|
|
|
|||
|
|
@ -1087,7 +1087,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
|
||||
context 'when project restricts use of user defined variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
it { is_expected.not_to be_allowed(:set_pipeline_variables) }
|
||||
|
|
@ -1107,7 +1107,7 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do
|
|||
|
||||
context 'when project restricts use of user defined variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
it { is_expected.to be_allowed(:set_pipeline_variables) }
|
||||
|
|
|
|||
|
|
@ -1041,6 +1041,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
|
|||
let(:params) { {} }
|
||||
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
post api("/projects/#{project.id}/jobs/#{job.id}/play", api_user), params: params
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integration do
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :repository, public_builds: false) }
|
||||
let_it_be_with_reload(:project) { create(:project, :repository, public_builds: false) }
|
||||
|
||||
before do
|
||||
project.add_developer(developer)
|
||||
|
|
@ -687,7 +687,7 @@ RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integra
|
|||
|
||||
context 'when project restricts use of user defined variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
context 'as developer' do
|
||||
|
|
@ -743,6 +743,10 @@ RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integra
|
|||
end
|
||||
|
||||
context 'when key has validation error' do
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :developer)
|
||||
end
|
||||
|
||||
it 'does not create pipeline_schedule_variable' do
|
||||
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
|
||||
params: params.merge('key' => '!?!?')
|
||||
|
|
@ -815,7 +819,7 @@ RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integra
|
|||
|
||||
context 'when project restricts use of user defined variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
context 'as developer' do
|
||||
|
|
@ -928,7 +932,7 @@ RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integra
|
|||
|
||||
context 'when project restricts use of user defined variables' do
|
||||
before do
|
||||
project.update!(restrict_user_defined_variables: true)
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
context 'as developer' do
|
||||
|
|
|
|||
|
|
@ -775,6 +775,10 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
|
|||
context 'variables given' do
|
||||
let(:variables) { [{ 'variable_type' => 'file', 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] }
|
||||
|
||||
before do
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
it 'creates and returns a new pipeline using the given variables', :aggregate_failures do
|
||||
expect do
|
||||
post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
|
||||
|
|
@ -794,6 +798,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
|
|||
before do
|
||||
config = YAML.dump(test: { script: 'test', only: { variables: ['$STAGING'] } })
|
||||
stub_ci_pipeline_yaml_file(config)
|
||||
project.update!(ci_pipeline_variables_minimum_override_role: :maintainer)
|
||||
end
|
||||
|
||||
it 'creates and returns a new pipeline using the given variables', :aggregate_failures do
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue