Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-06-10 15:12:04 +00:00
parent ac34bff890
commit 4c61af12af
58 changed files with 828 additions and 1020 deletions

View File

@ -16,7 +16,7 @@ variables:
# Retry failed specs in separate process
QA_RETRY_FAILED_SPECS: "true"
# Helm chart ref used by test-on-cng pipeline
GITLAB_HELM_CHART_REF: "0e35aaeaaf6a85ab70a89c67c5f39bc08f11d669"
GITLAB_HELM_CHART_REF: "5613c3aaf1cacdc6df7e05b96de980be74742116"
# Specific ref for cng-mirror project to trigger builds for
GITLAB_CNG_MIRROR_REF: "df7aafcccafdbab732a7cf757efb3b7b74c851dd"
# Makes sure some of the common scripts from pipeline-common use bundler to execute commands

View File

@ -1 +1 @@
42c36c24adc64b5d1b0bb467873d67c923c8a612
b285b9aba37c9b2605d693275f6a0a8dfbbca160

View File

@ -121,7 +121,7 @@
{"name":"devise","version":"4.9.4","platform":"ruby","checksum":"920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8"},
{"name":"devise-two-factor","version":"4.1.1","platform":"ruby","checksum":"c95f5b07533e62217aaed3c386874d94e2d472fb5f2b6598afe8600fc17a8b95"},
{"name":"diff-lcs","version":"1.5.0","platform":"ruby","checksum":"49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67"},
{"name":"diffy","version":"3.4.3","platform":"ruby","checksum":"4264b9e7db00d1cd426fcd32e36565779163cedc2340a95b0e6f025e71f9aaa7"},
{"name":"diffy","version":"3.4.4","platform":"ruby","checksum":"79384ab5ca82d0e115b2771f0961e27c164c456074bd2ec46b637ebf7b6e47e3"},
{"name":"digest-crc","version":"0.6.5","platform":"ruby","checksum":"5ca456f3352dc5ff17eb95deb3dd5a79dc79f8bf751d8005abca5b7b9b252124"},
{"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"},
{"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"},
@ -218,7 +218,7 @@
{"name":"gitaly","version":"18.1.0.pre.rc1","platform":"ruby","checksum":"8f65a0c5bb3694c91c9fa4bfa7ceabfc131846b78feed8ee32a744aaacf6e70a"},
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
{"name":"gitlab-chronic","version":"0.10.6","platform":"ruby","checksum":"a244d11a1396d2aac6ae9b2f326adf1605ec1ad20c29f06e8b672047d415a9ac"},
{"name":"gitlab-cloud-connector","version":"1.14.0","platform":"ruby","checksum":"a5f75d8891b8e61dcb380069fced461d71fba8105875c19bcf5921f9a4b8d8cc"},
{"name":"gitlab-cloud-connector","version":"1.15.0","platform":"ruby","checksum":"19c45cd38e0d8721c61809bb05a4d593a365854bb60bb7e78ad765613d668193"},
{"name":"gitlab-crystalball","version":"1.0.0","platform":"ruby","checksum":"74f56646345a5bc130da64ee5c2a90fad1bd70b26b551928676030fddaf76201"},
{"name":"gitlab-dangerfiles","version":"4.9.2","platform":"ruby","checksum":"d5c050f685d8720f6e70191a7d1216854d860dbdea5b455f87abe7542e005798"},
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},

View File

@ -547,7 +547,7 @@ GEM
railties (~> 7.0)
rotp (~> 6.0)
diff-lcs (1.5.0)
diffy (3.4.3)
diffy (3.4.4)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
docile (1.4.0)
@ -744,7 +744,7 @@ GEM
terminal-table (>= 1.5.1)
gitlab-chronic (0.10.6)
numerizer (~> 0.2)
gitlab-cloud-connector (1.14.0)
gitlab-cloud-connector (1.15.0)
activesupport (~> 7.0)
jwt (~> 2.9.3)
gitlab-crystalball (1.0.0)
@ -2429,4 +2429,4 @@ DEPENDENCIES
yard (~> 0.9)
BUNDLED WITH
2.6.5
2.6.9

View File

@ -121,7 +121,7 @@
{"name":"devise","version":"4.9.4","platform":"ruby","checksum":"920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8"},
{"name":"devise-two-factor","version":"4.1.1","platform":"ruby","checksum":"c95f5b07533e62217aaed3c386874d94e2d472fb5f2b6598afe8600fc17a8b95"},
{"name":"diff-lcs","version":"1.5.0","platform":"ruby","checksum":"49b934001c8c6aedb37ba19daec5c634da27b318a7a3c654ae979d6ba1929b67"},
{"name":"diffy","version":"3.4.3","platform":"ruby","checksum":"4264b9e7db00d1cd426fcd32e36565779163cedc2340a95b0e6f025e71f9aaa7"},
{"name":"diffy","version":"3.4.4","platform":"ruby","checksum":"79384ab5ca82d0e115b2771f0961e27c164c456074bd2ec46b637ebf7b6e47e3"},
{"name":"digest-crc","version":"0.6.5","platform":"ruby","checksum":"5ca456f3352dc5ff17eb95deb3dd5a79dc79f8bf751d8005abca5b7b9b252124"},
{"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"},
{"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"},
@ -218,7 +218,7 @@
{"name":"gitaly","version":"18.1.0.pre.rc1","platform":"ruby","checksum":"8f65a0c5bb3694c91c9fa4bfa7ceabfc131846b78feed8ee32a744aaacf6e70a"},
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
{"name":"gitlab-chronic","version":"0.10.6","platform":"ruby","checksum":"a244d11a1396d2aac6ae9b2f326adf1605ec1ad20c29f06e8b672047d415a9ac"},
{"name":"gitlab-cloud-connector","version":"1.14.0","platform":"ruby","checksum":"a5f75d8891b8e61dcb380069fced461d71fba8105875c19bcf5921f9a4b8d8cc"},
{"name":"gitlab-cloud-connector","version":"1.15.0","platform":"ruby","checksum":"19c45cd38e0d8721c61809bb05a4d593a365854bb60bb7e78ad765613d668193"},
{"name":"gitlab-crystalball","version":"1.0.0","platform":"ruby","checksum":"74f56646345a5bc130da64ee5c2a90fad1bd70b26b551928676030fddaf76201"},
{"name":"gitlab-dangerfiles","version":"4.9.2","platform":"ruby","checksum":"d5c050f685d8720f6e70191a7d1216854d860dbdea5b455f87abe7542e005798"},
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},

View File

@ -541,7 +541,7 @@ GEM
railties (~> 7.0)
rotp (~> 6.0)
diff-lcs (1.5.0)
diffy (3.4.3)
diffy (3.4.4)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
docile (1.4.0)
@ -738,7 +738,7 @@ GEM
terminal-table (>= 1.5.1)
gitlab-chronic (0.10.6)
numerizer (~> 0.2)
gitlab-cloud-connector (1.14.0)
gitlab-cloud-connector (1.15.0)
activesupport (~> 7.0)
jwt (~> 2.9.3)
gitlab-crystalball (1.0.0)
@ -2424,4 +2424,4 @@ DEPENDENCIES
yard (~> 0.9)
BUNDLED WITH
2.6.5
2.6.9

View File

@ -3,7 +3,13 @@
query boardsGetGroupProjects($fullPath: ID!, $search: String, $after: String) {
group(fullPath: $fullPath) {
id
projects(search: $search, after: $after, first: 100, includeSubgroups: true) {
projects(
search: $search
after: $after
first: 100
includeSubgroups: true
withIssuesEnabled: true
) {
nodes {
id
name

View File

@ -24,7 +24,12 @@ export default {
</script>
<template>
<gl-alert variant="success" class="gl-mb-5" @dismiss="setToken(null)">
<gl-alert
data-testid="new-access-token"
variant="success"
class="gl-mb-5"
@dismiss="setToken(null)"
>
<input-copy-toggle-visibility
:copy-button-title="s__('AccessTokens|Copy token')"
:label="s__('AccessTokens|Your token')"

View File

@ -4,6 +4,8 @@ import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { mapActions, mapState } from 'pinia';
import { useAccessTokens } from '../stores/access_tokens';
import { slugify } from '../../../lib/utils/text_utility';
export default {
components: {
GlButton,
@ -20,6 +22,10 @@ export default {
this.setPage(1);
this.fetchTokens();
},
slugifyStat(stat) {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `${slugify(stat)}-count`;
},
},
};
</script>
@ -27,7 +33,12 @@ export default {
<template>
<div class="gl-my-5 gl-grid gl-gap-4 sm:gl-grid-cols-2 lg:gl-grid-cols-4">
<gl-card v-for="statistic in statistics" :key="statistic.title">
<gl-single-stat class="!gl-p-0" :title="statistic.title" :value="statistic.value" />
<gl-single-stat
class="!gl-p-0"
:data-testid="slugifyStat(statistic.title)"
:title="statistic.title"
:value="statistic.value"
/>
<gl-button
class="mt-2"
:title="statistic.tooltipTitle"

View File

@ -176,6 +176,7 @@ export default {
<template>
<div>
<gl-table
data-testid="access-token-table"
:items="tokens"
:fields="$options.fields"
:empty-text="s__('AccessTokens|No access tokens')"
@ -279,6 +280,7 @@ export default {
<template #cell(options)="{ item }">
<gl-disclosure-dropdown
v-if="item.active"
data-testid="access-token-options"
icon="ellipsis_v"
:no-caret="true"
:disabled="busy"

View File

@ -6,7 +6,8 @@ module Types
class Base < BasePermissionType
graphql_name 'NamespacePermissions'
abilities :admin_label
abilities :admin_label, :admin_issue, :create_work_item,
:import_issues, :read_crm_contact, :read_crm_organization
ability_field :read_namespace
end

View File

@ -34,3 +34,5 @@ module Types
end
end
end
Types::WorkItems::NegatedWorkItemFilterInputType.prepend_mod_with('Types::WorkItems::NegatedWorkItemFilterInputType')

View File

@ -17,7 +17,7 @@ module Types
description: 'Whether the current user is subscribed to notifications on the work item.'
def subscribed
object.work_item.subscribed?(current_user, object.work_item.project)
object.work_item.subscribed?(current_user, object.work_item.project, cache_enforced: false)
end
end
# rubocop:enable Graphql/AuthorizeTypes

View File

@ -17,20 +17,21 @@ module Subscribable
scope :explicitly_unsubscribed, ->(user) { joins(:subscriptions).where(subscriptions: { user_id: user.id, subscribed: false }) }
end
def subscribed?(user, project = nil)
def subscribed?(user, project = nil, cache_enforced: true)
return false unless user
if (subscription = lazy_subscription(user, project)&.itself)
if (subscription = lazy_subscription(user, project, cache_enforced: cache_enforced)&.itself)
subscription.subscribed
else
subscribed_without_subscriptions?(user, project)
end
end
def lazy_subscription(user, project = nil)
def lazy_subscription(user, project = nil, cache_enforced: true)
return unless user
BatchLoader.for(id: id, subscribable_type: subscribable_type, project_id: project&.id).batch do |items, loader|
BatchLoader.for(id: id, subscribable_type: subscribable_type, project_id: project&.id)
.batch(cache: cache_enforced) do |items, loader|
values = items.each_with_object({ ids: Set.new, subscribable_types: Set.new, project_ids: Set.new }) do |item, result|
result[:ids] << item[:id]
result[:subscribable_types] << item[:subscribable_type]

View File

@ -1103,10 +1103,6 @@ class Group < Namespace
feature_flag_enabled_for_self_or_ancestor?(:continue_indented_text, type: :wip)
end
def markdown_placeholders_feature_flag_enabled?
feature_flag_enabled_for_self_or_ancestor?(:markdown_placeholders, type: :gitlab_com_derisk)
end
def glql_integration_feature_flag_enabled?
feature_flag_enabled_for_self_or_ancestor?(:glql_integration)
end

View File

@ -3438,10 +3438,6 @@ class Project < ApplicationRecord
group&.continue_indented_text_feature_flag_enabled? || Feature.enabled?(:continue_indented_text, self, type: :wip)
end
def markdown_placeholders_feature_flag_enabled?
group&.markdown_placeholders_feature_flag_enabled? || Feature.enabled?(:markdown_placeholders, self, type: :gitlab_com_derisk)
end
def enqueue_record_project_target_platforms
return unless Gitlab.com?

View File

@ -57,14 +57,25 @@ module Notes
update_params[:spend_time][:note_id] = note.id
end
service_response = execute_update_service(note, update_params)
execute_triggers(note, update_params)
execute_update_service(note, update_params)
service_response
end
private
def execute_triggers(note, params)
# This is overridden in EE
trigger_work_item_updated(note, params)
end
def trigger_work_item_updated(note, params)
GraphqlTriggers.work_item_updated(note.noteable) if quick_action_requires_subscription_update?(params) &&
note.for_work_item?
end
def quick_action_requires_subscription_update?(update_params)
update_params.has_key?(:subscription_event)
end
def execute_update_service(note, params)

View File

@ -5,11 +5,18 @@ module Packages
class GenerateDistributionKeyService
include Gitlab::Utils::StrongMemoize
NEWLINE_REGEX = /\R/
INVALID_PASSPHRASE_ERROR = ServiceResponse.error(
message: 'Passphrase contains invalid characters', reason: :invalid_passphrase
).freeze
def initialize(params: {})
@params = params
end
def execute
return INVALID_PASSPHRASE_ERROR if invalid_passphrase?
using_pinentry do |ctx|
# Generate key
ctx.generate_key generate_key_params
@ -29,12 +36,14 @@ module Packages
data.seek 0
public_key = data.read
{
private_key: private_key,
public_key: public_key,
passphrase: passphrase,
fingerprint: fingerprint
}
ServiceResponse.success(
payload: {
private_key: private_key,
public_key: public_key,
passphrase: passphrase,
fingerprint: fingerprint
}
)
end
end
@ -93,6 +102,10 @@ module Packages
}.map { |k, v| "#{k}: #{v}\n" }.join +
'</GnupgKeyParms>'
end
def invalid_passphrase?
params[:passphrase]&.match?(NEWLINE_REGEX)
end
end
end
end

View File

@ -62,6 +62,8 @@ module Packages
Installer-Menu-Item
].freeze
GenerateDistributionError = Class.new(StandardError)
def initialize(distribution)
@distribution = distribution
@oldest_kept_generated_at = nil
@ -202,7 +204,8 @@ module Packages
end
def generate_release
@distribution.key || @distribution.create_key(GenerateDistributionKeyService.new.execute)
generate_distribution_key unless @distribution.key
@distribution.file = CarrierWaveStringFile.new(release_content)
@distribution.file_signature = SignDistributionService.new(@distribution, release_content, detach: true).execute
@distribution.signed_file = CarrierWaveStringFile.new(
@ -272,6 +275,14 @@ module Packages
def lease_timeout
DEFAULT_LEASE_TIMEOUT
end
def generate_distribution_key
response = GenerateDistributionKeyService.new.execute
raise GenerateDistributionError, response.message unless response.success?
@distribution.create_key(response.payload)
end
end
end
end

View File

@ -1,10 +0,0 @@
---
name: duo_code_review_full_file
description:
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/510266
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189314
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/537556
milestone: '18.0'
group: group::code review
type: beta
default_enabled: true

View File

@ -1,10 +0,0 @@
---
name: markdown_placeholders
description:
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/14389
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186028
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/544860
milestone: '18.1'
group: group::knowledge
type: gitlab_com_derisk
default_enabled: false

View File

@ -5,4 +5,4 @@ feature_category: package_registry
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174312
milestone: '17.7'
queued_migration_version: 20241201164238
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250601231655'

View File

@ -9,14 +9,6 @@ description: Tracks past and present on-call shifts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49423
milestone: '13.8'
gitlab_schema: gitlab_main_cell
desired_sharding_key:
project_id:
references: projects
backfill_via:
parent:
foreign_key: rotation_id
table: incident_management_oncall_rotations
sharding_key: project_id
belongs_to: rotation
sharding_key:
project_id: projects
table_size: small
desired_sharding_key_migration_job_name: BackfillIncidentManagementOncallShiftsProjectId

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkUpdateStatusForDeprecatedNpmPackages < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'UpdateStatusForDeprecatedNpmPackages',
table_name: :packages_npm_metadata,
column_name: :package_id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddIncidentManagementOncallShiftsProjectIdNotNull < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
def up
add_not_null_constraint :incident_management_oncall_shifts, :project_id
end
def down
remove_not_null_constraint :incident_management_oncall_shifts, :project_id
end
end

View File

@ -0,0 +1 @@
a656fc6add5b7e261a8f05f0a559ac3bdaabd59d723a7490386e0c290b116e36

View File

@ -0,0 +1 @@
35a5bf92efe206f7e709919069942482a4619d307e988d478c1f1818e479177b

View File

@ -15701,7 +15701,8 @@ CREATE TABLE incident_management_oncall_shifts (
participant_id bigint NOT NULL,
starts_at timestamp with time zone NOT NULL,
ends_at timestamp with time zone NOT NULL,
project_id bigint
project_id bigint,
CONSTRAINT check_a37955f387 CHECK ((project_id IS NOT NULL))
);
CREATE SEQUENCE incident_management_oncall_shifts_id_seq

View File

@ -29781,7 +29781,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
| <a id="groupworkitemstatecountsdueafter"></a>`dueAfter` | [`Time`](#time) | Work items due after the timestamp. |
| <a id="groupworkitemstatecountsduebefore"></a>`dueBefore` | [`Time`](#time) | Work items due before the timestamp. |
| <a id="groupworkitemstatecountsexcludeprojects"></a>`excludeProjects` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 17.5. **Status**: Experiment. Exclude work items from projects within the group. |
| <a id="groupworkitemstatecountshealthstatus"></a>`healthStatus` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="groupworkitemstatecountshealthstatusfilter"></a>`healthStatusFilter` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="groupworkitemstatecountsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
| <a id="groupworkitemstatecountsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
| <a id="groupworkitemstatecountsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
@ -29808,6 +29808,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
| <a id="groupworkitemstatecountsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Work items updated before the timestamp. |
| <a id="groupworkitemstatecountsverificationstatuswidget"></a>`verificationStatusWidget` | [`VerificationStatusFilterInput`](#verificationstatusfilterinput) | Input for verification status widget filter. Ignored if `work_items_alpha` is disabled. |
| <a id="groupworkitemstatecountsweight"></a>`weight` | [`String`](#string) | Weight applied to the work item, "none" and "any" values are supported. |
| <a id="groupworkitemstatecountsweightwildcardid"></a>`weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. |
##### `Group.workItemTypes`
@ -29858,7 +29859,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="groupworkitemsdueafter"></a>`dueAfter` | [`Time`](#time) | Work items due after the timestamp. |
| <a id="groupworkitemsduebefore"></a>`dueBefore` | [`Time`](#time) | Work items due before the timestamp. |
| <a id="groupworkitemsexcludeprojects"></a>`excludeProjects` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 17.5. **Status**: Experiment. Exclude work items from projects within the group. |
| <a id="groupworkitemshealthstatus"></a>`healthStatus` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="groupworkitemshealthstatusfilter"></a>`healthStatusFilter` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="groupworkitemsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
| <a id="groupworkitemsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
| <a id="groupworkitemsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
@ -29885,6 +29886,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="groupworkitemsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Work items updated before the timestamp. |
| <a id="groupworkitemsverificationstatuswidget"></a>`verificationStatusWidget` | [`VerificationStatusFilterInput`](#verificationstatusfilterinput) | Input for verification status widget filter. Ignored if `work_items_alpha` is disabled. |
| <a id="groupworkitemsweight"></a>`weight` | [`String`](#string) | Weight applied to the work item, "none" and "any" values are supported. |
| <a id="groupworkitemsweightwildcardid"></a>`weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. |
##### `Group.workspacesClusterAgents`
@ -34198,8 +34200,13 @@ Represents a namespace-cluster-agent mapping.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="namespacepermissionsadminissue"></a>`adminIssue` | [`Boolean!`](#boolean) | If `true`, the user can perform `admin_issue` on this resource. |
| <a id="namespacepermissionsadminlabel"></a>`adminLabel` | [`Boolean!`](#boolean) | If `true`, the user can perform `admin_label` on this resource. |
| <a id="namespacepermissionscreateworkitem"></a>`createWorkItem` | [`Boolean!`](#boolean) | If `true`, the user can perform `create_work_item` on this resource. |
| <a id="namespacepermissionsgeneratedescription"></a>`generateDescription` | [`Boolean!`](#boolean) | If `true`, the user can perform `generate_description` on this resource. |
| <a id="namespacepermissionsimportissues"></a>`importIssues` | [`Boolean!`](#boolean) | If `true`, the user can perform `import_issues` on this resource. |
| <a id="namespacepermissionsreadcrmcontact"></a>`readCrmContact` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_crm_contact` on this resource. |
| <a id="namespacepermissionsreadcrmorganization"></a>`readCrmOrganization` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_crm_organization` on this resource. |
| <a id="namespacepermissionsreadnamespace"></a>`readNamespace` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_namespace` on this resource. |
### `NamespaceSidebar`
@ -35710,6 +35717,7 @@ Project-level settings for product analytics provider.
| <a id="projectcontainerprotectiontagrules"></a>`containerProtectionTagRules` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleConnection`](#containerprotectiontagruleconnection) | **Introduced** in GitLab 17.8. **Status**: Experiment. Container repository tag protection rules for the project. |
| <a id="projectcontainerregistryenabled"></a>`containerRegistryEnabled` | [`Boolean`](#boolean) | Indicates if Container registry is enabled for the current user. |
| <a id="projectcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. |
| <a id="projectcontainerscanningforregistryenabled"></a>`containerScanningForRegistryEnabled` | [`Boolean`](#boolean) | Indicates whether Container Scanning for Registry is enabled or not for the project. Returns `null` if unauthorized. |
| <a id="projectcontainertagsexpirationpolicy"></a>`containerTagsExpirationPolicy` | [`ContainerTagsExpirationPolicy`](#containertagsexpirationpolicy) | Container tags expiration policy of the project. |
| <a id="projectcorpuses"></a>`corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. (see [Connections](#connections)) |
| <a id="projectcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of the project creation. |
@ -38003,7 +38011,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
| <a id="projectworkitemstatecountscustomfield"></a>`customField` {{< icon name="warning-solid" >}} | [`[WorkItemWidgetCustomFieldFilterInputType!]`](#workitemwidgetcustomfieldfilterinputtype) | **Introduced** in GitLab 17.10. **Status**: Experiment. Filter by custom fields. |
| <a id="projectworkitemstatecountsdueafter"></a>`dueAfter` | [`Time`](#time) | Work items due after the timestamp. |
| <a id="projectworkitemstatecountsduebefore"></a>`dueBefore` | [`Time`](#time) | Work items due before the timestamp. |
| <a id="projectworkitemstatecountshealthstatus"></a>`healthStatus` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="projectworkitemstatecountshealthstatusfilter"></a>`healthStatusFilter` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="projectworkitemstatecountsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
| <a id="projectworkitemstatecountsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
| <a id="projectworkitemstatecountsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
@ -38027,6 +38035,7 @@ Returns [`WorkItemStateCountsType`](#workitemstatecountstype).
| <a id="projectworkitemstatecountsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Work items updated before the timestamp. |
| <a id="projectworkitemstatecountsverificationstatuswidget"></a>`verificationStatusWidget` | [`VerificationStatusFilterInput`](#verificationstatusfilterinput) | Input for verification status widget filter. Ignored if `work_items_alpha` is disabled. |
| <a id="projectworkitemstatecountsweight"></a>`weight` | [`String`](#string) | Weight applied to the work item, "none" and "any" values are supported. |
| <a id="projectworkitemstatecountsweightwildcardid"></a>`weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. |
##### `Project.workItemTypes`
@ -38076,7 +38085,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="projectworkitemscustomfield"></a>`customField` {{< icon name="warning-solid" >}} | [`[WorkItemWidgetCustomFieldFilterInputType!]`](#workitemwidgetcustomfieldfilterinputtype) | **Introduced** in GitLab 17.10. **Status**: Experiment. Filter by custom fields. |
| <a id="projectworkitemsdueafter"></a>`dueAfter` | [`Time`](#time) | Work items due after the timestamp. |
| <a id="projectworkitemsduebefore"></a>`dueBefore` | [`Time`](#time) | Work items due before the timestamp. |
| <a id="projectworkitemshealthstatus"></a>`healthStatus` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="projectworkitemshealthstatusfilter"></a>`healthStatusFilter` | [`HealthStatusFilter`](#healthstatusfilter) | Health status of the work item, "none" and "any" values are supported. |
| <a id="projectworkitemsiid"></a>`iid` | [`String`](#string) | IID of the work item. For example, "1". |
| <a id="projectworkitemsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
| <a id="projectworkitemsin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
@ -38100,6 +38109,7 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="projectworkitemsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Work items updated before the timestamp. |
| <a id="projectworkitemsverificationstatuswidget"></a>`verificationStatusWidget` | [`VerificationStatusFilterInput`](#verificationstatusfilterinput) | Input for verification status widget filter. Ignored if `work_items_alpha` is disabled. |
| <a id="projectworkitemsweight"></a>`weight` | [`String`](#string) | Weight applied to the work item, "none" and "any" values are supported. |
| <a id="projectworkitemsweightwildcardid"></a>`weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. |
### `ProjectCiCdSetting`
@ -50864,12 +50874,14 @@ A year and month input for querying product analytics usage data.
| ---- | ---- | ----------- |
| <a id="negatedworkitemfilterinputassigneeusernames"></a>`assigneeUsernames` | [`[String!]`](#string) | Usernames of users not assigned to the work item. |
| <a id="negatedworkitemfilterinputauthorusername"></a>`authorUsername` | [`[String!]`](#string) | Username of a user who didn't author the work item. |
| <a id="negatedworkitemfilterinputhealthstatusfilter"></a>`healthStatusFilter` | [`[HealthStatus!]`](#healthstatus) | Health status not applied to the work items. Includes work items where health status is not set. |
| <a id="negatedworkitemfilterinputlabelname"></a>`labelName` | [`[String!]`](#string) | Labels not applied to the work item. |
| <a id="negatedworkitemfilterinputmilestonetitle"></a>`milestoneTitle` | [`[String!]`](#string) | Milestone not applied to the work item. |
| <a id="negatedworkitemfilterinputmilestonewildcardid"></a>`milestoneWildcardId` | [`NegatedMilestoneWildcardId`](#negatedmilestonewildcardid) | Filter by negated milestone wildcard values. |
| <a id="negatedworkitemfilterinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji not applied by the current user. |
| <a id="negatedworkitemfilterinputreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag not associated with the work items's milestone. Ignored when parent is a group. |
| <a id="negatedworkitemfilterinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter out work items by the given types. |
| <a id="negatedworkitemfilterinputweight"></a>`weight` | [`String`](#string) | Weight not applied to the work items. |
### `OncallRotationActivePeriodInputType`

View File

@ -732,6 +732,109 @@ Example response:
]
```
### List all SAML users
{{< details >}}
- Tier: Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193748) in GitLab 18.1.
{{< /history >}}
Lists all SAML users for a given top-level group.
Use the `page` and `per_page` [pagination parameters](rest/_index.md#offset-based-pagination) to filter the results.
```plaintext
GET /groups/:id/saml_users
```
Supported attributes:
| Attribute | Type | Required | Description |
|:-----------------|:---------------|:---------|:------------|
| `id` | integer/string | yes | ID or [URL-encoded path](rest/_index.md#namespaced-paths) of a top-level group. |
| `username` | string | no | Return a user with a given username. |
| `search` | string | no | Return users with a matching name, email, or username. Use partial values to increase results. |
| `active` | boolean | no | Return only active users. |
| `blocked` | boolean | no | Return only blocked users. |
| `created_after` | datetime | no | Return users created after the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). |
| `created_before` | datetime | no | Return users created before the specified time. Format: ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). |
Example request:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/saml_users"
```
Example response:
```json
[
{
"id": 66,
"username": "user22",
"name": "Sidney Jones22",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/xxx?s=80&d=identicon",
"web_url": "http://my.gitlab.com/user22",
"created_at": "2021-09-10T12:48:22.381Z",
"bio": "",
"location": null,
"public_email": "",
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": null,
"job_title": "",
"pronouns": null,
"bot": false,
"work_information": null,
"followers": 0,
"following": 0,
"local_time": null,
"last_sign_in_at": null,
"confirmed_at": "2021-09-10T12:48:22.330Z",
"last_activity_on": null,
"email": "user22@example.org",
"theme_id": 1,
"color_scheme_id": 1,
"projects_limit": 100000,
"current_sign_in_at": null,
"identities": [
{
"provider": "group_saml",
"extern_uid": "2435223452345",
"saml_provider_id": 1
}
],
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": false,
"external": false,
"private_profile": false,
"commit_email": "user22@example.org",
"shared_runners_minutes_limit": null,
"extra_shared_runners_minutes_limit": null,
"scim_identities": [
{
"extern_uid": "2435223452345",
"group_id": 1,
"active": true
}
]
},
...
]
```
### List provisioned users
{{< details >}}

View File

@ -12,9 +12,10 @@ title: Project badges API
{{< /details >}}
## Placeholder tokens
Use this API to manage and review [project badges](../user/project/badges.md).
[Badges](../user/project/badges.md) support placeholders that are replaced in real-time in both the link and image URL. The allowed placeholders are:>
Badges can include placeholders that are replaced in both the
link and image URL. The available placeholders are:
- `%{project_path}`: Replaced by the project path.
- `%{project_title}`: Replaced by the project title.
@ -38,7 +39,7 @@ GET /projects/:id/badges
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `name` | string | no | Name of the badges to return (case-sensitive). |
```shell
@ -81,7 +82,7 @@ GET /projects/:id/badges/:badge_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `badge_id` | integer | yes | The badge ID |
```shell
@ -113,7 +114,7 @@ POST /projects/:id/badges
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `link_url` | string | yes | URL of the badge link |
| `image_url` | string | yes | URL of the badge image |
| `name` | string | no | Name of the badge |
@ -149,7 +150,7 @@ PUT /projects/:id/badges/:badge_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `badge_id` | integer | yes | The badge ID |
| `link_url` | string | no | URL of the badge link |
| `image_url` | string | no | URL of the badge image |
@ -185,7 +186,7 @@ DELETE /projects/:id/badges/:badge_id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `badge_id` | integer | yes | The badge ID |
```shell
@ -204,7 +205,7 @@ GET /projects/:id/badges/render
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `link_url` | string | yes | URL of the badge link|
| `image_url` | string | yes | URL of the badge image |

View File

@ -12,16 +12,21 @@ title: Protected branches API
{{< /details >}}
Use this API to manage [branch protections](../user/project/repository/branches/protected.md)
for your repositories.
GitLab Premium and GitLab Ultimate support more granular protections for pushing to branches.
Administrators can grant permission to modify and push to protected branches only to deploy keys,
instead of specific users.
## Valid access levels
The access levels are defined in the `ProtectedRefAccess.allowed_access_levels` method. The following levels are recognized:
The `ProtectedRefAccess.allowed_access_levels` method defines the following access levels:
```plaintext
0 => No access
30 => Developer access
40 => Maintainer access
60 => Admin access
```
- `0`: No access
- `30`: Developer role
- `40`: Maintainer role
- `60`: Administrator
## List protected branches
@ -41,7 +46,7 @@ GET /projects/:id/protected_branches
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `search` | string | no | Name or part of the name of protected branches to be searched for |
In the following example, the project ID is `5`.
@ -168,7 +173,7 @@ GET /projects/:id/protected_branches/:name
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `name` | string | yes | The name of the branch or wildcard |
In the following example, the project ID is `5` and branch name is `main`:
@ -252,7 +257,7 @@ POST /projects/:id/protected_branches
| Attribute | Type | Required | Description |
| -------------------------------------------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
| `name` | string | yes | The name of the branch or wildcard. |
| `allow_force_push` | boolean | no | When enabled, members who can push to this branch can also force push. (default: `false`) |
| `allowed_to_merge` | array | no | Array of merge access levels, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. Premium and Ultimate only. |
@ -585,7 +590,7 @@ DELETE /projects/:id/protected_branches/:name
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `name` | string | yes | The name of the branch |
In the following example, the project ID is `5` and branch name is `*-stable`.
@ -612,7 +617,7 @@ PATCH /projects/:id/protected_branches/:name
| Attribute | Type | Required | Description |
| -------------------------------------------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
| `name` | string | yes | The name of the branch or wildcard. |
| `allow_force_push` | boolean | no | When enabled, members who can push to this branch can also force push. |
| `allowed_to_merge` | array | no | Array of merge access levels, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. Premium and Ultimate only. |

View File

@ -12,6 +12,8 @@ title: Protected tags API
{{< /details >}}
Use this API to manage [protected tags](../user/project/protected_tags.md) for your repositories.
## Valid access levels
These access levels are recognized:

View File

@ -13,6 +13,8 @@ title: Repositories API
{{< /details >}}
Use this API to manage your [GitLab repository](../user/project/repository/_index.md).
## List repository tree
Get a list of repository files and directories in a project. This endpoint can
@ -25,7 +27,7 @@ in the Git internals documentation.
{{< alert type="warning" >}}
In version 17.7, the error handling behavior when a requested path is not found is updated.
GitLab version 17.7 changes the error handling behavior when a requested path is not found.
The endpoint now returns a status code `404 Not Found`. Previously, the status code was `200 OK`.
If your implementation relies on receiving a `200` status code with an empty array for
@ -122,7 +124,7 @@ Supported attributes:
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). |
| `sha` | string | yes | The blob SHA. |
## Raw blob content
## Get raw blob content
Get the raw file contents for a blob, by blob SHA. This endpoint can be accessed
without authentication if the repository is publicly accessible.
@ -240,7 +242,7 @@ Example response:
}
```
## Contributors
## Get contributor list
{{< history >}}
@ -291,7 +293,7 @@ Example response:
}]
```
## Merge Base
## Get merge base
Get the common ancestor for 2 or more refs, such as commit SHAs, branch names, or tags.

View File

@ -363,7 +363,7 @@ GET /users?with_custom_attributes=true
You can use the `created_by` parameter to see if a user account was created:
- [Manually by an administrator](../user/profile/account/create_accounts.md#create-users-in-admin-area).
- [Manually by an administrator](../user/profile/account/create_accounts.md#create-a-user-in-the-admin-area).
- As a [project bot user](../user/project/settings/project_access_tokens.md#bot-users-for-projects).
If the returned value is `null`, the account was created by a user who registered an account themselves.
@ -568,7 +568,7 @@ see the `scim_identities` parameter:
Administrators can use the `created_by` parameter to see if a user account was created:
- [Manually by an administrator](../user/profile/account/create_accounts.md#create-users-in-admin-area).
- [Manually by an administrator](../user/profile/account/create_accounts.md#create-a-user-in-the-admin-area).
- As a [project bot user](../user/project/settings/project_access_tokens.md#bot-users-for-projects).
If the returned value is `null`, the account was created by a user who registered an account themselves.

View File

@ -17,8 +17,8 @@ You can reset user passwords by using the UI, a Rake task, a Rails console, or t
## Prerequisites
- You must be an administrator of GitLab Self-Managed.
- The new password must meet all [password requirements](../user/profile/user_passwords.md#password-requirements).
- You must be an administrator for the instance.
- The password must meet all [password requirements](../user/profile/user_passwords.md#password-requirements).
## Use the UI
@ -30,7 +30,7 @@ To reset a user password in the UI:
1. In the **Password** section, enter and confirm a new password.
1. Select **Save changes**.
A confirmation is displayed.
GitLab updates the user password.
## Use a Rake task
@ -89,7 +89,7 @@ Prerequisites:
- You must know the associated username, user ID, or email address.
1. Open a [Rails console](../administration/operations/rails_console.md).
1. Start a [Rails console session](../administration/operations/rails_console.md#starting-a-rails-console-session).
1. Find the user:
- By username:

View File

@ -28,7 +28,7 @@ You can also use the [users API endpoint](../../../api/users.md#create-a-user) t
Choose the right method based on your organization's size, security requirements, and workflows.
## Create users on sign-in page
## Create a user on the sign-in page
By default, any user visiting your GitLab instance can register for an account.
If you have previously [disabled this setting](../../../administration/settings/sign_up_restrictions.md#disable-new-sign-ups), you must turn it back on.
@ -38,46 +38,37 @@ Users can create their own accounts by either:
- Selecting the **Register now** link on the sign-in page.
- Navigating to your GitLab instance's sign-up link (for example: `https://gitlab.example.com/users/sign_up`).
## Create users in Admin area
## Create a user in the Admin area
Prerequisites:
- You must have administrator access to the instance.
- You must be an administrator for the instance.
To create a user manually:
To create a user:
1. On the left sidebar, at the bottom, select **Admin**.
1. Select **Overview > Users**.
1. Select **New user**.
1. Complete the required fields, such as name, username, and email.
1. In the **Account** section, enter the required account information.
1. Optional. In the **Access** section, configure any project limits or user type settings.
1. Select **Create user**.
A reset link is sent to the user's email, and they are required to set their password when they first sign in.
GitLab sends an email to the user with a sign-in link, and the user must create a password when
they first sign in. You can also directly [set a password](../../../security/reset_user_password.md#use-the-ui)
for the user.
### Set user password
To set a user's password without relying on the email confirmation, after you create a user:
1. Select the user.
1. Select **Edit**.
1. Complete the password and password confirmation fields.
1. Select **Save changes**.
The user can now sign in with the new username and password,
and they are required to change the password you set up for them.
## Create users through authentication integrations
## Create a user with an authentication integration
GitLab can automatically create user accounts through authentication integrations.
Users are created when they:
- Are provisioned through [SCIM](../../group/saml_sso/scim_setup.md) in the identity provider.
- Sign in for the first time with:
- [LDAP](../../../administration/auth/ldap/_index.md)
- [Group SAML](../../group/saml_sso/_index.md)
- An [OmniAuth provider](../../../integration/omniauth.md) that has the setting `allow_single_sign_on` turned on
- Are provisioned through [SCIM](../../group/saml_sso/scim_setup.md) in the identity provider.
## Create users through the Rails console
## Create a user through the Rails console
{{< alert type="warning" >}}
@ -88,7 +79,7 @@ Always run commands in a test environment first and have a backup instance ready
To create a user through the Rails console:
1. [Start a Rails console session](../../../administration/operations/rails_console.md#starting-a-rails-console-session).
1. Start a [Rails console session](../../../administration/operations/rails_console.md#starting-a-rails-console-session).
1. Run the command according to your GitLab version:
{{< tabs >}}

View File

@ -181,7 +181,7 @@ Prerequisites:
To delete the root account:
1. In the **Admin** area, [create a new user with administrator access](create_accounts.md#create-users-in-admin-area). This ensures that you maintain administrator access to the instance whilst mitigating the risks associated with deleting the root account.
1. In the **Admin** area, [create a new user with administrator access](create_accounts.md#create-a-user-in-the-admin-area). This ensures that you maintain administrator access to the instance whilst mitigating the risks associated with deleting the root account.
1. [Delete the root account](#delete-users-and-user-contributions).
### Use the GitLab Rails console

View File

@ -10,8 +10,8 @@ module Banzai
const_get("#{name.to_s.camelize}Filter", false)
end
def self.filter_item_limit_exceeded?(count, limit: FILTER_ITEM_LIMIT)
count >= limit
def self.filter_item_limit_exceeded?(count)
count >= FILTER_ITEM_LIMIT
end
end
end

View File

@ -37,7 +37,6 @@ module Banzai
# Allow data-escaped-chars span attribute
allowlist[:attributes]['span'].push('data-escaped-chars')
allowlist[:attributes]['span'].push('data-placeholder')
# Allow html5 details/summary elements
allowlist[:elements].push('details')
@ -52,11 +51,9 @@ module Banzai
allowlist[:attributes]['a'].push('name')
allowlist[:attributes]['a'].push('data-wikilink')
allowlist[:attributes]['a'].push('data-placeholder')
allowlist[:attributes]['img'].push('data-diagram')
allowlist[:attributes]['img'].push('data-diagram-src')
allowlist[:attributes]['img'].push('data-placeholder')
# Allow any protocol in `a` elements
# and then remove links with unsafe protocols in SanitizeLinkFilter

View File

@ -22,7 +22,6 @@ module Banzai
math_code: true,
math_dollars: true,
multiline_block_quotes: true,
placeholder_detection: true,
relaxed_autolinks: true,
sourcepos: true,
smart: false,
@ -51,18 +50,13 @@ module Banzai
def render_options
return MINIMUM_MARKDOWN if minimum_markdown_enabled?
unless sourcepos_disabled? || headers_disabled? || autolink_disabled? || raw_html_disabled? ||
placeholders_disabled?
return OPTIONS
end
return OPTIONS unless sourcepos_disabled? || headers_disabled? || autolink_disabled? || raw_html_disabled?
OPTIONS.merge(
sourcepos: !sourcepos_disabled?,
header_ids: headers_disabled? ? nil : OPTIONS[:header_ids],
autolink: !autolink_disabled?,
relaxed_autolinks: !autolink_disabled?,
placeholder_detection: !placeholders_disabled?,
unsafe: !raw_html_disabled?
)
end
@ -82,13 +76,6 @@ module Banzai
def minimum_markdown_enabled?
context[:minimum_markdown]
end
def placeholders_disabled?
return true unless context[:project]&.markdown_placeholders_feature_flag_enabled? ||
context[:group]&.markdown_placeholders_feature_flag_enabled?
context[:disable_placeholders] || context[:broadcast_message_placeholders]
end
end
end
end

View File

@ -1,203 +0,0 @@
# frozen_string_literal: true
module Banzai
module Filter
# Replaces previously identified dynamic placeholders with current values.
# By performing this as a post-processing filter, we can show current
# information.
class PlaceholdersPostFilter < HTML::Pipeline::Filter
prepend Concerns::TimeoutFilterHandler
prepend Concerns::PipelineTimingCheck
# gitlab-glfm-markdown will detect possible placeholder values, and mark them with a
# `<span data-placeholder>` for text, or add `data-placeholder` to link or images.
# This allows us to specifically search for those nodes.
#
# The syntax used is `%{PLACEHOLDER}`. Markdown processing ignores
# this syntax, so even links with embedded placeholders will get
# preserved.
CSS = '[data-placeholder]'
XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
FILTER_ITEM_LIMIT = 100
# Variables that can be replaced. We handle them all dynamically as there
# is a _possibility_ that their value could change in the future.
#
# While it might be rare for something like `gitlab_server`, we
# error on the side of safety right now. If needed in the future,
# we can add a `PlaceholderPreFilter` that would insert the values in the
# main pipeline. Downside of not caching is that
# the value is not cached in the DB and has to be filled in on each display
PLACEHOLDERS = {
'gitlab_server' => ->(_context) { Gitlab.config.gitlab.host },
'gitlab_pages_domain' => ->(_context) { Gitlab.config.pages.host },
'project_path' => ->(context) do
context[:project]&.full_path if Ability.allowed?(context[:current_user], :read_project, context[:project])
end,
'project_name' => ->(context) do
context[:project]&.path if Ability.allowed?(context[:current_user], :read_project, context[:project])
end,
'project_id' => ->(context) do
context[:project]&.id.to_s if Ability.allowed?(context[:current_user], :read_project, context[:project])
end,
'project_namespace' => ->(context) do
if Ability.allowed?(context[:current_user], :read_project, context[:project])
context[:project]&.project_namespace&.to_param
end
end,
'project_title' => ->(context) do
context[:project]&.title if Ability.allowed?(context[:current_user], :read_project, context[:project])
end,
'group_name' => ->(context) do
group = context[:project]&.group || context[:group]
group.name if Ability.allowed?(context[:current_user], :read_group, group)
end,
'default_branch' => ->(context) do
if context[:project]&.repository_exists? &&
Ability.allowed?(context[:current_user], :read_code, context[:project])
context[:project]&.default_branch
end
end,
'commit_sha' => ->(context) do
if context[:project]&.repository_exists? &&
Ability.allowed?(context[:current_user], :read_code, context[:project])
context[:project]&.commit&.sha
end
end,
'latest_tag' => ->(context) do
if context[:project]&.repository_exists? &&
Ability.allowed?(context[:current_user], :read_code, context[:project])
TagsFinder.new(context[:project].repository, per_page: 1, sort: 'updated_desc')
&.execute&.first&.name
end
end
}.freeze
PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/
PLACEHOLDERS_FULL_REGEX = ::Gitlab::StringPlaceholderReplacer.placeholder_full_regex(PLACEHOLDERS_REGEX)
def call
return doc unless context[:project]&.markdown_placeholders_feature_flag_enabled? ||
context[:group]&.markdown_placeholders_feature_flag_enabled?
return doc if context[:disable_placeholders] || context[:broadcast_message_placeholders]
doc.xpath(XPATH).each_with_index do |node, index|
break if Banzai::Filter.filter_item_limit_exceeded?(index, limit: FILTER_ITEM_LIMIT)
case node.name
when 'span'
replace_text_placeholders(node)
when 'a'
replace_link_placeholders(node)
when 'img'
replace_image_placeholders(node)
else
next
end
end
doc
end
private
def replace_text_placeholders(node)
content = node.to_html
html_content =
Gitlab::StringPlaceholderReplacer.replace_string_placeholders(content, PLACEHOLDERS_REGEX) do |arg|
# project_title is user supplied, but has a validation limiting the characters allowed.
# Sanitization is not necessary
replace_placeholder_action(PLACEHOLDERS[arg])
end
node.replace(html_content) if content != html_content
node
end
def replace_link_placeholders(node)
href = link_href(node)
new_href =
Gitlab::StringPlaceholderReplacer.replace_string_placeholders(href, PLACEHOLDERS_REGEX) do |arg|
# project_title is a user supplied value, and doesn't really belong in a url
if arg != 'project_title'
replace_placeholder_action(PLACEHOLDERS[arg])
else
"%{#{arg}}"
end
end
node['href'] = new_href
sanitize_link(node)
end
def replace_image_placeholders(node)
url = img_src(node)
new_url = Gitlab::StringPlaceholderReplacer.replace_string_placeholders(url, PLACEHOLDERS_REGEX) do |arg|
# project_title is a user supplied value, and doesn't really belong in a url
if arg != 'project_title'
replace_placeholder_action(PLACEHOLDERS[arg])
else
"%{#{arg}}"
end
end
adjust_image_node(node, url, new_url)
node
end
def adjust_image_node(node, url, new_url)
if node['data-canonical-src']
# most likely generated by the asset proxy
node['src'] = new_url
node.remove_attribute('data-canonical-src')
node.remove_attribute('data-src')
asset = Banzai::Filter::AssetProxyFilter.new(node.to_html, context).call
asset_node = asset&.children&.first
node['src'] = asset_node['src'] if asset_node && asset_node['data-canonical-src']
else
node['src'] = new_url
end
node['data-src'] = new_url
node['data-canonical-src'] = url
sanitize_link(node)
if node['class']&.include?('lazy')
node['data-src'] = node['src'] if node['src'].present?
node['src'] = LazyImageTagHelper.placeholder_image
end
return unless node.parent&.name == 'a'
# since we wrap images with a link, we need to update it's href
node.parent['href'] = node['data-src'] || node['src']
end
def link_href(node)
node.name == 'a' && node['href']
end
def img_src(node)
node.name == 'img' && (node['data-canonical-src'] || node['data-src'] || node['src'])
end
# The action param represents the Proc to call in order to retrieve the value
def replace_placeholder_action(action)
action.call(context) || ''
end
def sanitize_link(node)
Banzai::Filter::SanitizeLinkFilter.new(node).call
end
end
end
end

View File

@ -6,7 +6,6 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::TruncateVisibleFilter,
Filter::PlaceholdersPostFilter,
*internal_link_filters,
Filter::AbsoluteLinkFilter,
Filter::BroadcastMessagePlaceholdersFilter
@ -29,8 +28,6 @@ module Banzai
context.merge(
post_process: true
)
Filter::AssetProxyFilter.transform_context(context)
end
end
end

View File

@ -72,7 +72,7 @@ module Gitlab
provider: provider, scope: scope)
end
conditions.inject(:&).present?
conditions.present? && conditions.all?
end
def build_flow(provider:, session:, scope: STEP_UP_AUTH_SCOPE_ADMIN_MODE)

View File

@ -46301,6 +46301,9 @@ msgstr ""
msgid "Please select a Jira project"
msgstr ""
msgid "Please select a group for your trial."
msgstr ""
msgid "Please select a valid target branch"
msgstr ""
@ -52045,6 +52048,9 @@ msgstr ""
msgid "Restricted shift times are not available for hourly shifts"
msgstr ""
msgid "Resubmit request"
msgstr ""
msgid "Results limit reached"
msgstr ""
@ -64905,6 +64911,9 @@ msgstr ""
msgid "Trending"
msgstr ""
msgid "Trial registration unsuccessful"
msgstr ""
msgid "TrialWidget|%{daysLeft} days left in trial"
msgstr ""
@ -68793,6 +68802,9 @@ msgstr ""
msgid "We're sorry, your trial could not be created because our system did not respond successfully."
msgstr ""
msgid "We're sorry, your trial could not be created. Please resubmit below to complete your registration."
msgstr ""
msgid "We've detected some unusual activity"
msgstr ""
@ -72220,6 +72232,9 @@ msgstr ""
msgid "Your top-level group is over the user limit and has been placed in a read-only state."
msgstr ""
msgid "Your trial will be applied to this group."
msgstr ""
msgid "Your update failed. You can only upload one design when dropping onto an existing design."
msgstr ""

View File

@ -12,212 +12,431 @@ RSpec.describe 'User Settings > Personal access tokens', :with_current_organizat
execute: ServiceResponse.error(message: 'error', payload: { personal_access_token: PersonalAccessToken.new }))
end
before do
stub_feature_flags(migrate_user_access_tokens_ui: false)
sign_in(user)
end
describe "token creation" do
it "allows creation of a personal access token" do
name = 'My PAT'
description = 'My PAT description'
visit user_settings_personal_access_tokens_path
expect(active_access_tokens_counter).to have_text('0')
click_button 'Add new token'
fill_in "Token name", with: name
fill_in "Token description", with: description
# Set date to 1st of next month
find_field("Expiration date").click
find(".pika-next").click
click_on "1"
# Scopes
check "read_api"
check "read_user"
click_on "Create personal access token"
wait_for_all_requests
expect(active_access_tokens).to have_text(name)
expect(active_access_tokens).to have_text(description)
expect(active_access_tokens).to have_text('in')
expect(active_access_tokens).to have_text('read_api')
expect(active_access_tokens).to have_text('read_user')
expect(created_access_token).to match(/[\w-]{20}/)
expect(active_access_tokens_counter).to have_text('1')
describe "migrate_user_access_tokens_ui feature flag on" do
before do
sign_in(user)
end
context "when creation fails" do
it "displays an error message" do
number_tokens_before = PersonalAccessToken.count
describe "token creation" do
it "allows creation of a personal access token" do
name = 'My PAT'
description = 'My PAT description'
visit user_settings_personal_access_tokens_path
expect(active_access_tokens_count).to have_text('0')
click_button 'Add new token'
fill_in "Token name", with: 'My PAT'
fill_in "Token name", with: name
fill_in "Description", with: description
# Set date to 1st of next month
find_field("Expiration date").click
find(".pika-next").click
click_on "1"
# Scopes
check "read_api"
check "read_user"
click_on "Create token"
wait_for_all_requests
expect(access_token_table).to have_text(name)
expect(access_token_table).to have_text(description)
expect(access_token_table).to have_text('in')
expect(access_token_table).to have_text('read_api')
expect(access_token_table).to have_text('read_user')
expect(new_access_token).to match(/[\w-]{20}/)
expect(active_access_tokens_count).to have_text('1')
end
context "when creation fails" do
it "displays an error message" do
number_tokens_before = PersonalAccessToken.count
visit user_settings_personal_access_tokens_path
click_button 'Add new token'
fill_in "Token name", with: 'My PAT'
click_on "Create token"
wait_for_all_requests
expect(number_tokens_before).to equal(PersonalAccessToken.count)
expect(page).to have_content(_("At least one scope is required."))
expect(page).to have_content("No access tokens")
end
end
end
describe 'active tokens' do
let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it 'only shows personal access tokens' do
visit user_settings_personal_access_tokens_path
expect(access_token_table).to have_text(personal_access_token.name)
expect(access_token_table).not_to have_text(impersonation_token.name)
end
context 'when User#time_display_relative is false' do
before do
user.update!(time_display_relative: false)
end
it 'shows absolute times for expires_at' do
visit user_settings_personal_access_tokens_path
expect(access_token_table).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
end
end
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/547507
# context 'when token has no Last Used IPs' do
# it 'shows "Never" as the value' do
# visit user_settings_personal_access_tokens_path
# expect(last_used_ip).to have_text('Never')
# end
# end
# context 'when token has Last Used IPs' do
# let(:current_ip_address) { '127.0.0.1' }
# before do
# personal_access_token.last_used_ips << Authn::PersonalAccessTokenLastUsedIp.new(
# organization: personal_access_token.organization,
# ip_address: current_ip_address)
# end
# it 'shows the current_ip_address in last_used_ips' do
# visit user_settings_personal_access_tokens_path
# expect(last_used_ip).to have_text(current_ip_address)
# end
# end
end
describe "inactive tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "allows revocation of an active token" do
visit user_settings_personal_access_tokens_path
access_token_options.click
accept_gl_confirm(button_text: 'Revoke') { click_on "Revoke" }
expect(access_token_table).to have_text("No access tokens")
end
it "removes expired tokens from 'active' section" do
personal_access_token.update!(expires_at: 5.days.ago)
visit user_settings_personal_access_tokens_path
expect(access_token_table).to have_text("No access tokens")
end
context "when revocation fails" do
it "displays an error message" do
allow_next_instance_of(PersonalAccessTokens::RevokeService) do |instance|
allow(instance).to receive(:revocation_permitted?).and_return(false)
end
visit user_settings_personal_access_tokens_path
access_token_options.click
accept_gl_confirm(button_text: "Revoke") { click_on "Revoke" }
expect(access_token_table).to have_text(personal_access_token.name)
end
end
end
describe "rotating tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "displays the newly created token" do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens_count).to have_text('1')
access_token_options.click
accept_gl_confirm(button_text: s_('AccessTokens|Rotate')) { click_on s_('AccessTokens|Rotate') }
wait_for_all_requests
expect(page).to have_content("Make sure you save it - you won't be able to access it again.")
expect(access_token_table).to have_text(personal_access_token.name)
expect(new_access_token).to match(/[\w-]{20}/)
expect(active_access_tokens_count).to have_text('1')
end
context "when rotation fails" do
it "displays an error message" do
visit user_settings_personal_access_tokens_path
access_token_options.click
accept_gl_confirm(button_text: s_('AccessTokens|Rotate')) do
personal_access_token.revoke!
click_on s_('AccessTokens|Rotate')
end
wait_for_all_requests
expect(page).to have_content(s_('AccessTokens|Token already revoked'))
end
end
end
it "prefills token details" do
name = 'My PAT'
description = 'My PAT description'
scopes = 'api,read_user'
visit user_settings_personal_access_tokens_path({ name: name, scopes: scopes, description: description })
expect(page).to have_field("Token name", with: name)
expect(page).to have_field("Description", with: description)
expect(find_field('api', with: 'api')).to be_checked
expect(find_field('read_user')).to be_checked
end
describe "feed token" do
def feed_token_description
"Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar\
application loads a personalized calendar. It is visible in those feed URLs. It cannot be used to access\
any other data."
end
context "when enabled" do
it "displays feed token" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false)
visit user_settings_personal_access_tokens_path
within_testid('feed-token-container') do
click_button('Click to reveal')
expect(page).to have_field('Feed token', with: user.feed_token)
expect(page).to have_content(feed_token_description)
end
end
end
context "when disabled" do
it "does not display feed token" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
visit user_settings_personal_access_tokens_path
expect(page).not_to have_content(feed_token_description)
expect(page).not_to have_field('Feed token')
end
end
end
end
describe "migrate_user_access_tokens_ui feature flag off" do
before do
stub_feature_flags(migrate_user_access_tokens_ui: false)
sign_in(user)
end
describe "token creation" do
it "allows creation of a personal access token" do
name = 'My PAT'
description = 'My PAT description'
visit user_settings_personal_access_tokens_path
expect(active_access_tokens_counter).to have_text('0')
click_button 'Add new token'
fill_in "Token name", with: name
fill_in "Token description", with: description
# Set date to 1st of next month
find_field("Expiration date").click
find(".pika-next").click
click_on "1"
# Scopes
check "read_api"
check "read_user"
click_on "Create personal access token"
wait_for_all_requests
expect(number_tokens_before).to equal(PersonalAccessToken.count)
expect(page).to have_content(_("Scopes can't be blank"))
expect(page).not_to have_selector("[data-testid='new-access-tokens']")
end
end
end
describe 'active tokens' do
let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it 'only shows personal access tokens' do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_text(personal_access_token.name)
expect(active_access_tokens).not_to have_text(impersonation_token.name)
end
context 'when User#time_display_relative is false' do
before do
user.update!(time_display_relative: false)
expect(active_access_tokens).to have_text(name)
expect(active_access_tokens).to have_text(description)
expect(active_access_tokens).to have_text('in')
expect(active_access_tokens).to have_text('read_api')
expect(active_access_tokens).to have_text('read_user')
expect(created_access_token).to match(/[\w-]{20}/)
expect(active_access_tokens_counter).to have_text('1')
end
it 'shows absolute times for expires_at' do
visit user_settings_personal_access_tokens_path
context "when creation fails" do
it "displays an error message" do
number_tokens_before = PersonalAccessToken.count
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
end
end
click_button 'Add new token'
fill_in "Token name", with: 'My PAT'
context 'when token has no Last Used IPs' do
it 'shows "-" as the value' do
visit user_settings_personal_access_tokens_path
click_on "Create personal access token"
wait_for_all_requests
expect(active_access_tokens).to have_selector('td[data-label="Last Used IPs"]', text: '-')
end
end
context 'when token has Last Used IPs' do
let(:current_ip_address) { '127.0.0.1' }
before do
personal_access_token.last_used_ips << Authn::PersonalAccessTokenLastUsedIp.new(
organization: personal_access_token.organization,
ip_address: current_ip_address)
end
it 'shows the current_ip_address in last_used_ips' do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_selector('td[data-label="Last Used IPs"]', text: current_ip_address)
end
end
end
describe "inactive tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "allows revocation of an active token" do
visit user_settings_personal_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on "Revoke" }
expect(active_access_tokens).to have_text("This user has no active personal access tokens.")
end
it "removes expired tokens from 'active' section" do
personal_access_token.update!(expires_at: 5.days.ago)
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_text("This user has no active personal access tokens.")
end
context "when revocation fails" do
it "displays an error message" do
allow_next_instance_of(PersonalAccessTokens::RevokeService) do |instance|
allow(instance).to receive(:revocation_permitted?).and_return(false)
expect(number_tokens_before).to equal(PersonalAccessToken.count)
expect(page).to have_content(_("Scopes can't be blank"))
expect(page).not_to have_selector("[data-testid='new-access-tokens']")
end
end
end
describe 'active tokens' do
let!(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) }
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it 'only shows personal access tokens' do
visit user_settings_personal_access_tokens_path
accept_gl_confirm(button_text: "Revoke") { click_on "Revoke" }
expect(active_access_tokens).to have_text(personal_access_token.name)
expect(active_access_tokens).not_to have_text(impersonation_token.name)
end
end
end
describe "rotating tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "displays the newly created token" do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens_counter).to have_text('1')
accept_gl_confirm(button_text: s_('AccessTokens|Rotate')) { click_on s_('AccessTokens|Rotate') }
wait_for_all_requests
expect(page).to have_content("Your new personal access token has been created.")
expect(active_access_tokens).to have_text(personal_access_token.name)
expect(created_access_token).to match(/[\w-]{20}/)
expect(active_access_tokens_counter).to have_text('1')
end
context "when rotation fails" do
it "displays an error message" do
visit user_settings_personal_access_tokens_path
accept_gl_confirm(button_text: s_('AccessTokens|Rotate')) do
personal_access_token.revoke!
click_on s_('AccessTokens|Rotate')
context 'when User#time_display_relative is false' do
before do
user.update!(time_display_relative: false)
end
it 'shows absolute times for expires_at' do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_text(PersonalAccessToken.last.expires_at.strftime('%b %-d'))
end
end
context 'when token has no Last Used IPs' do
it 'shows "-" as the value' do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_selector('td[data-label="Last Used IPs"]', text: '-')
end
end
context 'when token has Last Used IPs' do
let(:current_ip_address) { '127.0.0.1' }
before do
personal_access_token.last_used_ips << Authn::PersonalAccessTokenLastUsedIp.new(
organization: personal_access_token.organization,
ip_address: current_ip_address)
end
it 'shows the current_ip_address in last_used_ips' do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_selector('td[data-label="Last Used IPs"]', text: current_ip_address)
end
end
end
describe "inactive tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "allows revocation of an active token" do
visit user_settings_personal_access_tokens_path
accept_gl_confirm(button_text: 'Revoke') { click_on "Revoke" }
expect(active_access_tokens).to have_text("This user has no active personal access tokens.")
end
it "removes expired tokens from 'active' section" do
personal_access_token.update!(expires_at: 5.days.ago)
visit user_settings_personal_access_tokens_path
expect(active_access_tokens).to have_text("This user has no active personal access tokens.")
end
context "when revocation fails" do
it "displays an error message" do
allow_next_instance_of(PersonalAccessTokens::RevokeService) do |instance|
allow(instance).to receive(:revocation_permitted?).and_return(false)
end
visit user_settings_personal_access_tokens_path
accept_gl_confirm(button_text: "Revoke") { click_on "Revoke" }
expect(active_access_tokens).to have_text(personal_access_token.name)
end
end
end
describe "rotating tokens" do
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "displays the newly created token" do
visit user_settings_personal_access_tokens_path
expect(active_access_tokens_counter).to have_text('1')
accept_gl_confirm(button_text: s_('AccessTokens|Rotate')) { click_on s_('AccessTokens|Rotate') }
wait_for_all_requests
expect(page).to have_content(s_('AccessTokens|Token already revoked'))
expect(page).to have_content("Your new personal access token has been created.")
expect(active_access_tokens).to have_text(personal_access_token.name)
expect(created_access_token).to match(/[\w-]{20}/)
expect(active_access_tokens_counter).to have_text('1')
end
end
end
describe "feed token" do
def feed_token_description
"Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar\
application loads a personalized calendar. It is visible in those feed URLs."
end
context "when rotation fails" do
it "displays an error message" do
visit user_settings_personal_access_tokens_path
context "when enabled" do
it "displays feed token" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false)
visit user_settings_personal_access_tokens_path
accept_gl_confirm(button_text: s_('AccessTokens|Rotate')) do
personal_access_token.revoke!
click_on s_('AccessTokens|Rotate')
end
within_testid('feed-token-container') do
click_button('Click to reveal')
expect(page).to have_field('Feed token', with: user.feed_token)
expect(page).to have_content(feed_token_description)
wait_for_all_requests
expect(page).to have_content(s_('AccessTokens|Token already revoked'))
end
end
end
context "when disabled" do
it "does not display feed token" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
visit user_settings_personal_access_tokens_path
it "prefills token details" do
name = 'My PAT'
description = 'My PAT description'
scopes = 'api,read_user'
expect(page).not_to have_content(feed_token_description)
expect(page).not_to have_field('Feed token')
visit user_settings_personal_access_tokens_path({ name: name, scopes: scopes, description: description })
expect(page).to have_field("Token name", with: name)
expect(page).to have_field("Token description", with: description)
expect(find("#personal_access_token_scopes_api")).to be_checked
expect(find("#personal_access_token_scopes_read_user")).to be_checked
end
describe "feed token" do
def feed_token_description
"Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar\
application loads a personalized calendar. It is visible in those feed URLs. It cannot be used to access\
any other data."
end
context "when enabled" do
it "displays feed token" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(false)
visit user_settings_personal_access_tokens_path
within_testid('feed-token-container') do
click_button('Click to reveal')
expect(page).to have_field('Feed token', with: user.feed_token)
expect(page).to have_content(feed_token_description)
end
end
end
context "when disabled" do
it "does not display feed token" do
allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true)
visit user_settings_personal_access_tokens_path
expect(page).not_to have_content(feed_token_description)
expect(page).not_to have_field('Feed token')
end
end
end
end
it "prefills token details" do
name = 'My PAT'
description = 'My PAT description'
scopes = 'api,read_user'
visit user_settings_personal_access_tokens_path({ name: name, scopes: scopes, description: description })
expect(page).to have_field("Token name", with: name)
expect(page).to have_field("Token description", with: description)
expect(find("#personal_access_token_scopes_api")).to be_checked
expect(find("#personal_access_token_scopes_read_user")).to be_checked
end
end

View File

@ -4,7 +4,8 @@ require 'spec_helper'
RSpec.describe Types::PermissionTypes::Namespaces::Base, feature_category: :groups_and_projects do
specify do
expected_permissions = [:admin_label, :read_namespace]
expected_permissions = [:admin_label, :read_namespace, :admin_issue, :create_work_item,
:import_issues, :read_crm_contact, :read_crm_organization]
expected_permissions.each do |permission|
expect(described_class).to have_graphql_field(permission)

View File

@ -56,46 +56,4 @@ RSpec.describe Banzai::Filter::MarkdownEngines::GlfmMarkdown, feature_category:
expect(engine.render('http://example.com _emphasis_ $x + y$')).to eq expected
end
describe 'placeholder detection' do
let_it_be(:project) { create(:project) }
it 'turns off placeholder detection when :markdown_placeholders disabled' do
stub_feature_flags(markdown_placeholders: false)
engine = described_class.new({ project: project, no_sourcepos: true })
expected = <<~TEXT
<p>%{test}</p>
TEXT
expect(engine.render('%{test}')).to eq expected
end
it 'defaults to on' do
engine = described_class.new({ project: project, no_sourcepos: true })
expected = <<~TEXT
<p><span data-placeholder>%{test}</span></p>
TEXT
expect(engine.render('%{test}')).to eq expected
end
it 'turns off placeholder detection when :disable_placeholders' do
engine = described_class.new({ disable_placeholders: true, project: project, no_sourcepos: true })
expected = <<~TEXT
<p>%{test}</p>
TEXT
expect(engine.render('%{test}')).to eq expected
end
it 'turns off placeholder detection when :broadcast_message_placeholders' do
engine = described_class.new({ broadcast_message_placeholders: true, project: project, no_sourcepos: true })
expected = <<~TEXT
<p>%{test}</p>
TEXT
expect(engine.render('%{test}')).to eq expected
end
end
end

View File

@ -1,420 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Banzai::Filter::PlaceholdersPostFilter, feature_category: :markdown do
include FilterSpecHelper
using RSpec::Parameterized::TableSyntax
def run_pipeline(text, context = { project: project })
stub_commonmark_sourcepos_disabled
Banzai.render_and_post_process(text, context)
end
def run_filter(text, context = { project: project })
stub_commonmark_sourcepos_disabled
text = Banzai::Filter::MarkdownFilter.new(text, context).call
filter(text, context).to_html
end
let_it_be(:gitlab_server) { "<span data-placeholder>#{Gitlab.config.gitlab.host}</span>" }
let_it_be(:gitlab_pages_domain) { "<span data-placeholder>#{Gitlab.config.pages.host}</span>" }
let!(:project_path) { "<span data-placeholder>#{project.full_path}</span>" }
let!(:project_name) { "<span data-placeholder>#{project.path}</span>" }
let!(:project_id) { "<span data-placeholder>#{project.id}</span>" }
let!(:project_namespace) { "<span data-placeholder>#{project.project_namespace.to_param}</span>" }
let!(:project_title) { "<span data-placeholder>#{project.title}</span>" }
let!(:group_name) { "<span data-placeholder>#{project.group&.name}</span>" }
let!(:default_branch) { "<span data-placeholder>#{project.default_branch}</span>" }
let!(:commit_sha) { "<span data-placeholder>#{project.commit&.sha}</span>" }
let!(:latest_tag) { "<span data-placeholder>#{project_tag}</span>" }
let!(:empty_span) { "<span data-placeholder></span>" }
let!(:project_tag) do
if project.repository_exists?
TagsFinder.new(project.repository, per_page: 1, sort: 'updated_desc')&.execute&.first&.name
end
end
shared_examples 'placeholders with no access' do
where(:markdown, :expected) do
'%{gitlab_server}' | ref(:gitlab_server)
'%{gitlab_pages_domain}' | ref(:gitlab_pages_domain)
'%{project_path}' | ref(:empty_span)
'%{project_name}' | ref(:empty_span)
'%{project_title}' | ref(:empty_span)
'%{project_id}' | ref(:empty_span)
'%{project_namespace}' | ref(:empty_span)
'%{group_name}' | ref(:group_name)
'%{default_branch}' | ref(:empty_span)
'%{commit_sha}' | ref(:empty_span)
'%{latest_tag}' | ref(:empty_span)
end
with_them do
it 'replaces placeholder' do
expect(run_pipeline(markdown, project: project, current_user: user)).to eq "<p dir=\"auto\">#{expected}</p>"
end
end
end
shared_examples 'placeholders with no access, no group' do
where(:markdown, :expected) do
'%{gitlab_server}' | ref(:gitlab_server)
'%{gitlab_pages_domain}' | ref(:gitlab_pages_domain)
'%{project_path}' | ref(:empty_span)
'%{project_name}' | ref(:empty_span)
'%{project_title}' | ref(:empty_span)
'%{project_id}' | ref(:empty_span)
'%{project_namespace}' | ref(:empty_span)
'%{group_name}' | ref(:empty_span)
'%{default_branch}' | ref(:empty_span)
'%{commit_sha}' | ref(:empty_span)
'%{latest_tag}' | ref(:empty_span)
end
with_them do
it 'replaces placeholder' do
expect(run_pipeline(markdown, project: project, current_user: user)).to eq "<p dir=\"auto\">#{expected}</p>"
end
end
end
shared_examples 'placeholders with access' do
where(:markdown, :expected) do
'%{gitlab_server}' | ref(:gitlab_server)
'%{gitlab_pages_domain}' | ref(:gitlab_pages_domain)
'%{project_path}' | ref(:project_path)
'%{project_name}' | ref(:project_name)
'%{project_title}' | ref(:project_title)
'%{project_id}' | ref(:project_id)
'%{project_namespace}' | ref(:project_namespace)
'%{group_name}' | ref(:group_name)
'%{default_branch}' | ref(:default_branch)
'%{commit_sha}' | ref(:commit_sha)
'%{latest_tag}' | ref(:latest_tag)
end
with_them do
it 'replaces placeholder' do
expect(run_pipeline(markdown, project: project, current_user: user)).to eq "<p dir=\"auto\">#{expected}</p>"
end
end
end
shared_examples 'placeholders with access, no code access' do
where(:markdown, :expected) do
'%{gitlab_server}' | ref(:gitlab_server)
'%{gitlab_pages_domain}' | ref(:gitlab_pages_domain)
'%{project_path}' | ref(:project_path)
'%{project_name}' | ref(:project_name)
'%{project_title}' | ref(:project_title)
'%{project_id}' | ref(:project_id)
'%{project_namespace}' | ref(:project_namespace)
'%{group_name}' | ref(:group_name)
'%{default_branch}' | ref(:empty_span)
'%{commit_sha}' | ref(:empty_span)
'%{latest_tag}' | ref(:empty_span)
end
with_them do
it 'replaces placeholder' do
expect(run_pipeline(markdown, project: project, current_user: user)).to eq "<p dir=\"auto\">#{expected}</p>"
end
end
end
context 'when `markdown_placeholders` feature flag is disabled' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :small_repo, :public, group: group) }
before do
stub_feature_flags(markdown_placeholders: false)
project.add_member(user, Gitlab::Access::GUEST)
end
it 'does not replace placeholders' do
expect(run_pipeline('%{gitlab_server}', project: project, current_user: user))
.to eq '<p dir="auto">%{gitlab_server}</p>'
end
end
context 'when disabled' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :small_repo, :public, group: group) }
before do
project.add_member(user, Gitlab::Access::GUEST)
end
it 'does not replace placeholders when :disable_placeholders' do
result = run_pipeline('%{gitlab_server}', project: project, current_user: user, disable_placeholders: true)
expect(result).to eq '<p dir="auto">%{gitlab_server}</p>'
end
it 'does not replace placeholders when :broadcast_message_placeholders' do
result = run_pipeline('%{gitlab_server}', project: project, current_user: user,
broadcast_message_placeholders: true)
expect(result).to eq '<p dir="auto">%{gitlab_server}</p>'
end
end
context 'when private project' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :small_repo, group: group, create_tag: 'test') }
context 'with no access' do
it_behaves_like 'placeholders with no access'
end
context 'with guest access' do
before do
project.add_member(user, Gitlab::Access::GUEST)
end
it_behaves_like 'placeholders with access, no code access'
end
end
context 'when public project' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :small_repo, :public, group: group, create_tag: 'test') }
context 'with no access' do
it_behaves_like 'placeholders with access'
end
context 'with guest access' do
before do
project.add_member(user, Gitlab::Access::GUEST)
end
it_behaves_like 'placeholders with access'
end
end
context 'when private group, private project' do
let_it_be(:user) { create(:user) }
let_it_be(:group, reload: true) { create(:group, :private) }
let_it_be(:project, reload: true) { create(:project, :small_repo, group: group, create_tag: 'test') }
context 'with no access' do
it_behaves_like 'placeholders with no access, no group'
end
context 'with guest access' do
before do
group.add_member(user, Gitlab::Access::GUEST)
end
it_behaves_like 'placeholders with access, no code access'
end
context 'with reporter access' do
before do
group.add_member(user, Gitlab::Access::REPORTER)
end
it_behaves_like 'placeholders with access'
end
end
context 'when project has a disabled repository' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :public, :repository_disabled, group: group) }
it_behaves_like 'placeholders with access, no code access'
end
context 'when project has no repository' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :public, group: group) }
it_behaves_like 'placeholders with access, no code access'
end
context 'when placeholders in text' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :small_repo, group: group) }
# if the validation is ever changed to allow characters such as `<`, then
# we will need to sanitize the project_title
it 'verifes project_title is limited in characters' do
project.name = '<script>'
expect { project.save! }.to raise_error(ActiveRecord::RecordInvalid)
project.name = 'script:'
expect { project.save! }.to raise_error(ActiveRecord::RecordInvalid)
end
end
context 'when placeholders in link' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :small_repo, group: group) }
before do
project.add_member(user, Gitlab::Access::GUEST)
end
it 'does not allow project_title in a link href' do
markdown = '[test](%{gitlab_server}/%{project_title}/foo.png)'
expected = '<p dir="auto"><a href="localhost/%{project_title}/foo.png" ' \
'data-placeholder rel="nofollow noreferrer noopener" target="_blank">test</a></p>'
expect(run_pipeline(markdown)).to eq expected
end
it 'does not recognize unknown placeholder in a link href' do
markdown = '[test](%{gitlab_server}/%{foo}/foo.png)'
expected = '<p dir="auto"><a href="localhost/%%7Bfoo%7D/foo.png" ' \
'data-placeholder rel="nofollow noreferrer noopener" target="_blank">test</a></p>'
expect(run_pipeline(markdown)).to eq expected
end
it 'does not allow placeholders in link text (parser limitation)' do
markdown = '[%{gitlab_server}](%{gitlab_server})'
expected = '<p dir="auto"><a href="localhost" data-placeholder rel="nofollow noreferrer noopener" ' \
'target="_blank">%{gitlab_server}</a></p>'
expect(run_pipeline(markdown)).to eq expected
end
context 'when replacing href' do
let_it_be(:project) { create(:project, :small_repo, :public, group: group, path: 'script') }
it 'sanitizes the link' do
expect(Banzai::Filter::SanitizeLinkFilter).to receive(:new).twice.and_call_original
markdown = '[foo](java%{project_name})'
expect(run_pipeline(markdown)).to include "<a href=\"/#{project.full_path}/-/blob/master/javascript\""
end
end
end
context 'when placeholders in image' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :small_repo, :public, path: 'project-img', group: group) }
before do
project.add_member(user, Gitlab::Access::GUEST)
end
context 'when placeholder in the `src` attribute' do
it 'generates the correct attributes' do
markdown = '![](https://%{gitlab_server}/%{project_name}/foo.png)'
expected = "<p><img src=\"https://#{Gitlab.config.gitlab.host}/#{project.path}/foo.png\" " \
'data-placeholder alt="" ' \
"data-src=\"https://#{Gitlab.config.gitlab.host}/#{project.path}/foo.png\" " \
'data-canonical-src="https://%%7Bgitlab_server%7D/%%7Bproject_name%7D/foo.png"></p>'
# can't use the pipeline because the image_link_filter modifies `src`
expect(run_filter(markdown)).to eq expected
end
it 'does not allow project_title in a src' do
markdown = '![](https://%{gitlab_server}/%{project_title}/foo.png)'
expected = "<p><img src=\"https://#{Gitlab.config.gitlab.host}/%{project_title}/foo.png\" " \
'data-placeholder alt="" ' \
"data-src=\"https://#{Gitlab.config.gitlab.host}/%{project_title}/foo.png\" " \
'data-canonical-src="https://%%7Bgitlab_server%7D/%%7Bproject_title%7D/foo.png"></p>'
expect(run_filter(markdown)).to eq expected
end
end
context 'when asset proxy is disabled' do
before do
stub_asset_proxy_setting(enabled: false)
end
it 'generates the correct attributes' do
markdown = '![](https://%{gitlab_server}/%{project_name}/foo.png)'
result = run_pipeline(markdown)
expect(result).to include "href=\"https://#{Gitlab.config.gitlab.host}/#{project.path}/foo.png\""
expect(result).to include "data-src=\"https://#{Gitlab.config.gitlab.host}/#{project.path}/foo.png\""
expect(result).to include 'data-canonical-src="https://%%7Bgitlab_server%7D/%%7Bproject_name%7D/foo.png"'
expect(result)
.to include '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="'
end
end
context 'when asset proxy is enabled' do
before do
stub_asset_proxy_setting(enabled: true)
stub_asset_proxy_setting(secret_key: 'shared-secret')
stub_asset_proxy_setting(url: 'https://assets.example.com')
stub_asset_proxy_setting(allowlist: %w[gitlab.com *.mydomain.com])
end
it 'generates the correct attributes' do
markdown = '![](https://example.com/%{project_name}/foo.png)'
result = run_pipeline(markdown)
expect(result).to include 'href="https://assets.example.com/8641679ca136e7fe8cf9415852374bbe4595f929/68747470733a2f2f6578616d706c652e636f6d2f70726f6a6563742d696d672f666f6f2e706e67"'
expect(result).to include 'f6f2e706e67" data-canonical-src="https://example.com/%%7Bproject_name%7D/foo.png"'
expect(result).to include 'data-src="https://assets.example.com/8641679ca136e7fe8cf9415852374bbe4595f929/68747470733a2f2f6578616d706c652e636f6d2f70726f6a6563742d696d672f666f6f2e706e67"'
expect(result)
.to include '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="'
end
end
end
context 'when placeholders in an unsupported node' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :small_repo, :public, path: 'project-img', group: group) }
before do
project.add_member(user, Gitlab::Access::GUEST)
end
it 'does not replace it' do
markdown = '<code data-placeholder>%{gitlab-server}</code>'
expect(run_filter(markdown)).to include '<code data-placeholder>%{gitlab-server}</code>'
end
end
it_behaves_like 'pipeline timing check' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project) }
end
it_behaves_like 'a filter timeout' do
let_it_be(:project_tag) { nil }
let(:text) { 'text' }
end
it_behaves_like 'limits the number of filtered items' do
let_it_be(:project) { create(:project) }
let(:text) do
'<span data-placeholder>%{gitlab_server}</span> <span data-placeholder>%{gitlab_server}</span> ' \
'<span data-placeholder>%{gitlab_server}</span>'
end
let(:ends_with) { '<span data-placeholder>%{gitlab_server}</span>' }
before do
stub_const('Banzai::Filter::PlaceholdersPostFilter::FILTER_ITEM_LIMIT', 2)
end
end
end

View File

@ -149,24 +149,6 @@ RSpec.describe Banzai::Filter::SanitizationFilter, feature_category: :markdown d
expect(filter(html).to_html).to eq(output)
end
it 'allows `data-placeholder` attributes on `span`, `a` and `img` elements' do
html = <<-HTML
<span class="code" data-placeholder="true">something</span>
<a href="example.com" class="code" data-placeholder="true">something</a>
<img src="example.com" class="code" data-placeholder="true" />
<div data-placeholder-text="true" data-placeholder="true">something</div>
HTML
output = <<-HTML
<span data-placeholder="true">something</span>
<a href="example.com" data-placeholder="true">something</a>
<img src="example.com" data-placeholder="true">
<div>something</div>
HTML
expect(filter(html).to_html).to eq(output)
end
it 'allows the `data-sourcepos` attribute globally' do
exp = %q(<p data-sourcepos="1:1-1:10">foo/bar.md</p>)
act = filter(exp)

View File

@ -8,9 +8,5 @@ RSpec.describe Banzai::Filter, feature_category: :markdown do
expect(described_class.filter_item_limit_exceeded?(described_class::FILTER_ITEM_LIMIT - 1)).to be_falsey
expect(described_class.filter_item_limit_exceeded?(described_class::FILTER_ITEM_LIMIT + 1)).to be_truthy
end
it 'allows limit top be specified' do
expect(described_class.filter_item_limit_exceeded?(2, limit: 1)).to be_truthy
end
end
end

View File

@ -36,7 +36,7 @@ RSpec.describe Banzai::Pipeline::PostProcessPipeline, feature_category: :markdow
end
let(:doc) { HTML::Pipeline.parse(html) }
let(:non_related_xpath_calls) { 2 }
let(:non_related_xpath_calls) { 1 }
it 'searches for attributes only once' do
expect(doc).to receive(:xpath).exactly(non_related_xpath_calls + 1).times

View File

@ -49,6 +49,17 @@ RSpec.describe Subscribable, 'Subscribable' do
expect(resource.public_send(method, user_1, project)).to be_falsey
end
end
context 'with cache_enforced: false' do
let(:batch_loader) { instance_double(BatchLoader) }
it 'calls batch loader with correct params' do
expect(batch_loader).to receive(:batch).with(hash_including(cache: false))
expect(BatchLoader).to receive(:for).and_return(batch_loader)
resource.lazy_subscription(user_1, project, cache_enforced: false)
end
end
end
describe '#subscribed?' do

View File

@ -2542,9 +2542,15 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
control = ActiveRecord::QueryRecorder.new { perform_request(user) }
create(:merge_request, :closed, source_project: project, source_branch: 'master', target_branch: 'feature')
# 5 queries are intermittently seen to update user_preferences, and select scan_result_policy_violations
query_buffer = 5
expect { perform_request(user) }.not_to exceed_query_limit(control)
# Make query_buffer + 1 new merge requests to ensure an n + 1 introduces more than query_buffer queries
(query_buffer + 1).times do
create(:merge_request, :closed, source_project: project, source_branch: 'master', target_branch: 'feature')
end
expect { perform_request(user) }.not_to exceed_query_limit(control).with_threshold(query_buffer)
end
end

View File

@ -748,6 +748,36 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
end
end
end
describe '/subscribe or /unsubscribe' do
shared_examples 'when applying to work_item' do
it 'leaves the note empty' do
expect(execute(note)).to be_empty
end
it 'triggers work item updated subscription' do
expect(GraphqlTriggers).to receive(:work_item_updated).with(work_item)
execute(note)
end
end
describe '/subscribe' do
let_it_be(:note_text) { '/subscribe' }
it_behaves_like 'when applying to work_item'
end
describe '/unsubscribe' do
let_it_be(:note_text) { '/unsubscribe' }
before do
work_item.subscribe(maintainer, project)
end
it_behaves_like 'when applying to work_item'
end
end
end
end

View File

@ -13,11 +13,24 @@ RSpec.describe Packages::Debian::GenerateDistributionKeyService, feature_categor
expect(GPGME::Ctx).to receive(:new).with(armor: true, offline: true).and_call_original
expect(User).to receive(:random_password).with(no_args).and_call_original
expect(response).to be_a Hash
expect(response.keys).to contain_exactly(:private_key, :public_key, :fingerprint, :passphrase)
expect(response[:private_key]).to start_with('-----BEGIN PGP PRIVATE KEY BLOCK-----')
expect(response[:public_key]).to start_with('-----BEGIN PGP PUBLIC KEY BLOCK-----')
expect(response[:fingerprint].length).to eq(40)
expect(response[:passphrase].length).to be > 10
payload = response.payload
expect(response).to be_a ServiceResponse
expect(payload.keys).to contain_exactly(:private_key, :public_key, :fingerprint, :passphrase)
expect(payload[:private_key]).to start_with('-----BEGIN PGP PRIVATE KEY BLOCK-----')
expect(payload[:public_key]).to start_with('-----BEGIN PGP PUBLIC KEY BLOCK-----')
expect(payload[:fingerprint].length).to eq(40)
expect(payload[:passphrase].length).to be > 10
end
context 'with invalid passphrase parameter' do
let(:params) { { passphrase: "k\n%pubring /tmp/file" } }
it 'returns the error', :aggregate_failures do
expect(GPGME::Ctx).not_to receive(:new)
expect(response).to be_error.and have_attributes(
message: 'Passphrase contains invalid characters',
reason: :invalid_passphrase
)
end
end
end

View File

@ -2,6 +2,7 @@
module Features
module AccessTokenHelpers
# Remove after migrate_user_access_tokens_ui feature flag removal
def active_access_tokens
find_by_testid('active-tokens')
end
@ -13,8 +14,32 @@ module Features
end
end
# Keep after migrate_user_access_tokens_ui feature flag removal
def new_access_token
within_testid('new-access-token') do
find_by_testid('toggle-visibility-button').click
find_field('access-token-field').value
end
end
def active_access_tokens_counter
find_by_testid('active-token-count')
end
def access_token_table
find_by_testid('access-token-table')
end
def active_access_tokens_count
find_by_testid('active-tokens-count')
end
def last_used_ip
find_by_testid('field-last-used')
end
def access_token_options
find_by_testid('access-token-options')
end
end
end

View File

@ -274,4 +274,17 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do
end
end
end
context 'when the distribution key cannot be generated' do
it 'raises the error' do
allow(::Packages::Debian::GenerateDistributionKeyService).to receive(:new).and_wrap_original do |m, _|
m.call(params: { passphrase: "k\n%pubring /tmp/file" })
end
expect { subject }.to raise_error(
::Packages::Debian::GenerateDistributionService::GenerateDistributionError,
'Passphrase contains invalid characters'
)
end
end
end