Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-11 15:27:34 +00:00
parent e34c22f2ec
commit 65963de2ae
128 changed files with 1523 additions and 553 deletions

View File

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

View File

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

View File

@ -1 +1 @@
d3839f7e054c608c98399cff2b4c07aa18f102ed
351ea2b5585c837394a616f08fa16bdbb28808aa

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ module Issues
else
merge_request_to_resolve_discussions_of
.discussions_to_be_resolved
end
end.reject(&:confidential?)
end
end
end

View File

@ -220,6 +220,7 @@ module Gitlab
/key$/,
/^body$/,
/^description$/,
/^query$/,
/^note$/,
/^text$/,
/^title$/,

View File

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

View File

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

View File

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

View File

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

View File

@ -60,6 +60,7 @@
"runner",
"scalability",
"secret_detection",
"security_infrastructure",
"security_insights",
"security_policies",
"source_code",

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
3a45e94fffa5b618bff82b0768429dfb68412f709f21cc380ac39058ba4adc6b

View File

@ -0,0 +1 @@
bfb69bc3eb91b9b301f546eb18726512c3dee89f9d025974b4181a14d4e10b98

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',
},
});
});
});
});

View File

@ -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', () => {

View File

@ -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,
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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