Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
33ce7f500d
commit
ba12e5053f
|
|
@ -322,8 +322,7 @@ ldap-no-server:
|
|||
- if: $QA_SUITES =~ /Test::Integration::LDAPNoServer/
|
||||
- !reference [.rules:test:manual, rules]
|
||||
|
||||
# TODO: re-enable after gitlab-org/quality/quality-engineering/team-tasks#3153 is resolved
|
||||
.ldap-tls:
|
||||
ldap-tls:
|
||||
extends: .qa
|
||||
variables:
|
||||
QA_SCENARIO: Test::Integration::LDAPTLS
|
||||
|
|
@ -332,8 +331,7 @@ ldap-no-server:
|
|||
- if: $QA_SUITES =~ /Test::Integration::LDAPTLS/
|
||||
- !reference [.rules:test:manual, rules]
|
||||
|
||||
# TODO: re-enable after gitlab-org/quality/quality-engineering/team-tasks#3153 is resolved
|
||||
.ldap-no-tls:
|
||||
ldap-no-tls:
|
||||
extends: .qa
|
||||
variables:
|
||||
QA_SCENARIO: Test::Integration::LDAPNoTLS
|
||||
|
|
|
|||
|
|
@ -219,12 +219,12 @@ export default {
|
|||
:data-item-iid="item.iid"
|
||||
:data-item-path="item.referencePath"
|
||||
data-testid="board-card"
|
||||
class="board-card gl-border gl-relative gl-mb-3 gl-rounded-base gl-leading-normal hover:gl-bg-gray-10"
|
||||
class="board-card gl-border gl-relative gl-mb-3 gl-rounded-base gl-border-section gl-bg-section gl-leading-normal hover:gl-bg-subtle dark:hover:gl-bg-gray-200"
|
||||
>
|
||||
<button
|
||||
:class="[
|
||||
{
|
||||
'focus:gl-bg-gray-10': showFocusBackground,
|
||||
'focus:gl-bg-subtle dark:focus:gl-bg-gray-200': showFocusBackground,
|
||||
'gl-border-l-4 gl-pl-4 gl-border-l-solid': itemColor,
|
||||
},
|
||||
]"
|
||||
|
|
|
|||
|
|
@ -43,14 +43,14 @@ export default {
|
|||
},
|
||||
inject: ['projectPath'],
|
||||
i18n: {
|
||||
settingBlockTitle: s__('ContainerRegistry|Protected containers'),
|
||||
settingBlockTitle: s__('ContainerRegistry|Protected container repositories'),
|
||||
settingBlockDescription: s__(
|
||||
'ContainerRegistry|When a container is protected, only certain user roles can push the protected container image, which helps to avoid tampering with the container image.',
|
||||
'ContainerRegistry|When a container repository is protected, only certain user roles can push the protected container image, which helps to avoid tampering with the container image.',
|
||||
),
|
||||
protectionRuleDeletionConfirmModal: {
|
||||
title: s__('ContainerRegistry|Delete container protection rule?'),
|
||||
title: s__('ContainerRegistry|Delete container repository protection rule?'),
|
||||
descriptionWarning: s__(
|
||||
'ContainerRegistry|You are about to delete the container protection rule for %{repositoryPathPattern}.',
|
||||
'ContainerRegistry|You are about to delete the container repository protection rule for %{repositoryPathPattern}.',
|
||||
),
|
||||
descriptionConsequence: s__(
|
||||
'ContainerRegistry|Users with at least the Developer role for this project will be able to push and delete container images to this repository path.',
|
||||
|
|
|
|||
|
|
@ -148,29 +148,16 @@
|
|||
|
||||
blockquote,
|
||||
.blockquote {
|
||||
font-size: inherit;
|
||||
color: $gray-700;
|
||||
padding-top: $gl-spacing-scale-3;
|
||||
padding-bottom: $gl-spacing-scale-3;
|
||||
padding-left: $gl-spacing-scale-5;
|
||||
margin-top: $gl-spacing-scale-3;
|
||||
margin-bottom: $gl-spacing-scale-3;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
border-left: 4px solid var(--gl-color-neutral-100);
|
||||
|
||||
.gl-dark & {
|
||||
border-left-color: var(--gl-color-neutral-600);
|
||||
}
|
||||
@apply gl-text-size-reset gl-text-subtle;
|
||||
@apply gl-py-3 gl-pl-5 gl-my-3 gl-mx-0;
|
||||
@apply gl-border-l gl-border-l-4 gl-border-strong;
|
||||
|
||||
&:dir(rtl) {
|
||||
border-left: 0;
|
||||
border-right: 3px solid $gray-100;
|
||||
@apply gl-border-l-0 gl-border-r gl-border-r-4;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
color: inherit;
|
||||
@apply gl-text-inherit gl-leading-[1.5];
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ module Types
|
|||
null: false,
|
||||
experiment: { milestone: '17.2' },
|
||||
description:
|
||||
'Whether any matching container protection rule exists for the container. ' \
|
||||
'Whether any matching container protection rule exists for the container repository. ' \
|
||||
'Available only when feature flag `container_registry_protected_containers` is enabled.'
|
||||
field :status, Types::ContainerRegistry::ContainerRepositoryStatusEnum, null: true,
|
||||
description: 'Status of the container repository.'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Types
|
|||
module Protection
|
||||
class RuleType < ::Types::BaseObject
|
||||
graphql_name 'ContainerRegistryProtectionRule'
|
||||
description 'A container registry protection rule designed to prevent users with a certain ' \
|
||||
description 'A container repository protection rule designed to prevent users with a certain ' \
|
||||
'access level or lower from altering the container registry.'
|
||||
|
||||
authorize :admin_container_image
|
||||
|
|
@ -14,7 +14,7 @@ module Types
|
|||
::Types::GlobalIDType[::ContainerRegistry::Protection::Rule],
|
||||
null: false,
|
||||
experiment: { milestone: '16.6' },
|
||||
description: 'ID of the container registry protection rule.'
|
||||
description: 'ID of the container repository protection rule.'
|
||||
|
||||
field :repository_path_pattern,
|
||||
GraphQL::Types::String,
|
||||
|
|
@ -29,20 +29,20 @@ module Types
|
|||
null: true,
|
||||
experiment: { milestone: '16.6' },
|
||||
description:
|
||||
'Minimum GitLab access level to allow to delete container images from the container registry. ' \
|
||||
'Minimum GitLab access level required to delete container images from the container repository. ' \
|
||||
'For example, `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
|
||||
'If the value is `nil`, the minimum access level for delete is ignored. ' \
|
||||
'Users with at least the Developer role are allowed to delete container images.'
|
||||
'If the value is `nil`, the minimum access level is ignored. ' \
|
||||
'Users with at least the Developer role can delete container images.'
|
||||
|
||||
field :minimum_access_level_for_push,
|
||||
Types::ContainerRegistry::Protection::RuleAccessLevelEnum,
|
||||
null: true,
|
||||
experiment: { milestone: '16.6' },
|
||||
description:
|
||||
'Minimum GitLab access level to allow to push container images to the container registry. ' \
|
||||
'Minimum GitLab access level required to push container images to the container repository. ' \
|
||||
'For example, `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
|
||||
'If the value is `nil`, the minimum access level for push is ignored. ' \
|
||||
'Users with at least the Developer role are allowed to push container images.'
|
||||
'If the value is `nil`, the minimum access level is ignored. ' \
|
||||
'Users with at least the Developer role can push container images.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
.btn-group{ role: 'group' }
|
||||
= wiki_sort_controls(@wiki, params[:direction])
|
||||
|
||||
- if can?(current_user, :create_wiki, @project)
|
||||
= render Pajamas::ButtonComponent.new(href: wiki_page_path(@wiki, SecureRandom.uuid, random_title: true), variant: :confirm) do
|
||||
= s_("Wiki|New page")
|
||||
- if can?(current_user, :create_wiki, @project)
|
||||
= render Pajamas::ButtonComponent.new(href: wiki_page_path(@wiki, SecureRandom.uuid, random_title: true), variant: :confirm) do
|
||||
= s_("Wiki|New page")
|
||||
|
||||
#js-vue-wiki-more-actions{ data: {
|
||||
clone_ssh_url: ssh_clone_url_to_repo(@wiki),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
migration_job_name: BackfillIssueMetricsNamespaceId
|
||||
description: Backfills sharding key `issue_metrics.namespace_id` from `issues`.
|
||||
feature_category: value_stream_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174502
|
||||
milestone: '17.7'
|
||||
queued_migration_version: 20241203074404
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
migration_job_name: DeleteOrphanedGroups
|
||||
description: Deletes orhpaned groups whose parent's does not exist
|
||||
feature_category: groups_and_projects
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172420
|
||||
milestone: '17.7'
|
||||
queued_migration_version: 20241112163029
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -18,3 +18,4 @@ desired_sharding_key:
|
|||
sharding_key: namespace_id
|
||||
belongs_to: issue
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name: BackfillIssueMetricsNamespaceId
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddNamespaceIdToIssueMetrics < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
def change
|
||||
add_column :issue_metrics, :namespace_id, :bigint
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueDeleteOrphanedGroups < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
MIGRATION = "DeleteOrphanedGroups"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
return unless Gitlab.com_except_jh? && !Gitlab.staging?
|
||||
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:namespaces,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :namespaces, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexIssueMetricsOnNamespaceId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_issue_metrics_on_namespace_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :issue_metrics, :namespace_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :issue_metrics, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIssueMetricsNamespaceIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :issue_metrics, :namespaces, column: :namespace_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :issue_metrics, column: :namespace_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIssueMetricsNamespaceIdTrigger < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
|
||||
def up
|
||||
install_sharding_key_assignment_trigger(
|
||||
table: :issue_metrics,
|
||||
sharding_key: :namespace_id,
|
||||
parent_table: :issues,
|
||||
parent_sharding_key: :namespace_id,
|
||||
foreign_key: :issue_id
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_sharding_key_assignment_trigger(
|
||||
table: :issue_metrics,
|
||||
sharding_key: :namespace_id,
|
||||
parent_table: :issues,
|
||||
parent_sharding_key: :namespace_id,
|
||||
foreign_key: :issue_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillIssueMetricsNamespaceId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.7'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
MIGRATION = "BackfillIssueMetricsNamespaceId"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:issue_metrics,
|
||||
:id,
|
||||
:namespace_id,
|
||||
:issues,
|
||||
:namespace_id,
|
||||
:issue_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
:issue_metrics,
|
||||
:id,
|
||||
[
|
||||
:namespace_id,
|
||||
:issues,
|
||||
:namespace_id,
|
||||
:issue_id
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
ce1cea13a912cd65923aea1757bc0c073e425d20ac6e7bae620f66cee518bdb7
|
||||
|
|
@ -0,0 +1 @@
|
|||
f8cb5aba90abb415616c2ed31aeedc1f4645acb43cdc972962970d62828c1a3a
|
||||
|
|
@ -0,0 +1 @@
|
|||
86eeed8757efb216ff2ccab4cc3de42ee38a021621cd07671fb17852c6e437be
|
||||
|
|
@ -0,0 +1 @@
|
|||
cd6cb8ac7ac93a2ebbeabff061015cc215ed72369135c1d0098ca4bba852c62a
|
||||
|
|
@ -0,0 +1 @@
|
|||
dbf4c9348eba0b9e8ba2b717edb1b5afe6e36d7b3587dbed75b384859297d40a
|
||||
|
|
@ -0,0 +1 @@
|
|||
0906a3ed9bdc751059782071ecb1f03140da8662e4a2fedf38f2d6067e7413d4
|
||||
|
|
@ -2129,6 +2129,22 @@ RETURN NEW;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_85d89f0f11db() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW."namespace_id" IS NULL THEN
|
||||
SELECT "namespace_id"
|
||||
INTO NEW."namespace_id"
|
||||
FROM "issues"
|
||||
WHERE "issues"."id" = NEW."issue_id";
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_8a38ce2327de() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
|
@ -13817,7 +13833,8 @@ CREATE TABLE issue_metrics (
|
|||
first_associated_with_milestone_at timestamp without time zone,
|
||||
first_added_to_board_at timestamp without time zone,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
namespace_id bigint
|
||||
);
|
||||
|
||||
CREATE SEQUENCE issue_metrics_id_seq
|
||||
|
|
@ -30839,6 +30856,8 @@ CREATE INDEX index_issue_links_on_target_id ON issue_links USING btree (target_i
|
|||
|
||||
CREATE INDEX index_issue_metrics_on_issue_id_and_timestamps ON issue_metrics USING btree (issue_id, first_mentioned_in_commit_at, first_associated_with_milestone_at, first_added_to_board_at);
|
||||
|
||||
CREATE INDEX index_issue_metrics_on_namespace_id ON issue_metrics USING btree (namespace_id);
|
||||
|
||||
CREATE INDEX index_issue_on_project_id_state_id_and_blocking_issues_count ON issues USING btree (project_id, state_id, blocking_issues_count);
|
||||
|
||||
CREATE INDEX index_issue_tracker_data_on_integration_id ON issue_tracker_data USING btree (integration_id);
|
||||
|
|
@ -35563,6 +35582,8 @@ CREATE TRIGGER trigger_8204480b3a2e BEFORE INSERT OR UPDATE ON incident_manageme
|
|||
|
||||
CREATE TRIGGER trigger_84d67ad63e93 BEFORE INSERT OR UPDATE ON wiki_page_slugs FOR EACH ROW EXECUTE FUNCTION trigger_84d67ad63e93();
|
||||
|
||||
CREATE TRIGGER trigger_85d89f0f11db BEFORE INSERT OR UPDATE ON issue_metrics FOR EACH ROW EXECUTE FUNCTION trigger_85d89f0f11db();
|
||||
|
||||
CREATE TRIGGER trigger_8a38ce2327de BEFORE INSERT OR UPDATE ON boards_epic_user_preferences FOR EACH ROW EXECUTE FUNCTION trigger_8a38ce2327de();
|
||||
|
||||
CREATE TRIGGER trigger_8ac78f164b2d BEFORE INSERT OR UPDATE ON design_management_repositories FOR EACH ROW EXECUTE FUNCTION trigger_8ac78f164b2d();
|
||||
|
|
@ -36352,6 +36373,9 @@ ALTER TABLE ONLY packages_conan_package_revisions
|
|||
ALTER TABLE ONLY project_access_tokens
|
||||
ADD CONSTRAINT fk_5f7e8450e1 FOREIGN KEY (personal_access_token_id) REFERENCES personal_access_tokens(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY issue_metrics
|
||||
ADD CONSTRAINT fk_5fc5653bb3 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY user_achievements
|
||||
ADD CONSTRAINT fk_60b12fcda3 FOREIGN KEY (awarded_by_user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
---
|
||||
stage: Package
|
||||
group: Container Registry
|
||||
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments"
|
||||
description: "Documentation for the REST API for container repository protection rules in GitLab."
|
||||
---
|
||||
|
||||
# Container repository protection rules API
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed
|
||||
**Status:** Experiment
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155798) in GitLab 17.2 [with a flag](../administration/feature_flags.md) named `container_registry_protected_containers`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
This API endpoint manages protection rules for container repositories in a project's container registry. This feature is an experiment.
|
||||
|
||||
## List container repository protection rules
|
||||
|
||||
Gets a list of container repository protection rules from a project's container registry.
|
||||
|
||||
```plaintext
|
||||
GET /api/v4/projects/:id/registry/protection/rules
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------------------------|-----------------|----------|--------------------------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
|
||||
If successful, returns [`200`](rest/troubleshooting.md#status-codes) and a list of container repository protection rules.
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `200 OK`: A list of protection rules.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to list protection rules for this project.
|
||||
- `404 Not Found`: The project was not found.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"project_id": 7,
|
||||
"repository_path_pattern": "flightjs/flight0",
|
||||
"minimum_access_level_for_push": "maintainer",
|
||||
"minimum_access_level_for_delete": "maintainer"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"project_id": 7,
|
||||
"repository_path_pattern": "flightjs/flight1",
|
||||
"minimum_access_level_for_push": "maintainer",
|
||||
"minimum_access_level_for_delete": "maintainer"
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Create a container repository protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.2.
|
||||
|
||||
Create a container repository protection rule for a project's container registry.
|
||||
|
||||
```plaintext
|
||||
POST /api/v4/projects/:id/registry/protection/rules
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-----------------------------------|----------------|----------|-------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
| `repository_path_pattern` | string | Yes | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. |
|
||||
| `minimum_access_level_for_push` | string | No | Minimum GitLab access level required to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. |
|
||||
| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level required to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. |
|
||||
|
||||
If successful, returns [`201`](rest/troubleshooting.md#status-codes) and the created container repository protection rule.
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `201 Created`: The protection rule was created successfully.
|
||||
- `400 Bad Request`: The protection rule is invalid.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to create a protection rule.
|
||||
- `404 Not Found`: The project was not found.
|
||||
- `422 Unprocessable Entity`: The protection rule could not be created. For example, because the `repository_path_pattern` is already taken.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--header "Content-Type: application/json" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules" \
|
||||
--data '{
|
||||
"repository_path_pattern": "flightjs/flight-needs-to-be-a-unique-path",
|
||||
"minimum_access_level_for_push": "maintainer",
|
||||
"minimum_access_level_for_delete": "maintainer"
|
||||
}'
|
||||
```
|
||||
|
||||
## Update a container repository protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.2.
|
||||
|
||||
Update a container repository protection rule for a project's container registry.
|
||||
|
||||
```plaintext
|
||||
PATCH /api/v4/projects/:id/registry/protection/rules/:protection_rule_id
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-----------------------------------|----------------|----------|-------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
| `protection_rule_id` | integer | Yes | ID of the protection rule to be updated. |
|
||||
| `repository_path_pattern` | string | No | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. |
|
||||
| `minimum_access_level_for_push` | string | No | Minimum GitLab access level required to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. To unset the value, use an empty string `""`. |
|
||||
| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level required to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. To unset the value, use an empty string `""`. |
|
||||
|
||||
If successful, returns [`200`](rest/troubleshooting.md#status-codes) and the updated protection rule.
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `200 OK`: The protection rule was updated successfully.
|
||||
- `400 Bad Request`: The protection rule is invalid.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to update the protection rule.
|
||||
- `404 Not Found`: The project was not found.
|
||||
- `422 Unprocessable Entity`: The protection rule could not be updated. For example, because the `repository_path_pattern` is already taken.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PATCH \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--header "Content-Type: application/json" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules/32" \
|
||||
--data '{
|
||||
"repository_path_pattern": "flight/flight-*"
|
||||
}'
|
||||
```
|
||||
|
||||
## Delete a container repository protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.4.
|
||||
|
||||
Deletes a container repository protection rule from a project's container registry.
|
||||
|
||||
```plaintext
|
||||
DELETE /api/v4/projects/:id/registry/protection/rules/:protection_rule_id
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------|----------------|----------|-------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
| `protection_rule_id` | integer | Yes | ID of the container repository protection rule to be deleted. |
|
||||
|
||||
If successful, returns [`204 No Content`](rest/troubleshooting.md#status-codes).
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `204 No Content`: The protection rule was deleted successfully.
|
||||
- `400 Bad Request`: The `id` or the `protection_rule_id` are missing or are invalid.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to delete the protection rule.
|
||||
- `404 Not Found`: The project or the protection rule was not found.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules/1"
|
||||
```
|
||||
|
|
@ -3654,8 +3654,8 @@ Input type: `CreateContainerRegistryProtectionRuleInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level to allow to delete container images from the container registry. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level for delete is ignored. Users with at least the Developer role are allowed to delete container images. Introduced in GitLab 16.6: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level to allow to push container images to the container registry. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level for push is ignored. Users with at least the Developer role are allowed to push container images. Introduced in GitLab 16.6: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level required to delete container images from the container repository. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level is ignored. Users with at least the Developer role can delete container images. Introduced in GitLab 16.6: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | Minimum GitLab access level required to push container images to the container repository. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level is ignored. Users with at least the Developer role can push container images. Introduced in GitLab 16.6: **Status**: Experiment. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionruleprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project where a protection rule is located. |
|
||||
| <a id="mutationcreatecontainerregistryprotectionrulerepositorypathpattern"></a>`repositoryPathPattern` | [`String!`](#string) | Container repository path pattern protected by the protection rule. For example, `my-project/my-container-*`. Wildcard character `*` allowed. Introduced in GitLab 16.6: **Status**: Experiment. |
|
||||
|
||||
|
|
@ -21216,15 +21216,15 @@ A tag expiration policy designed to keep only the images that matter most.
|
|||
|
||||
### `ContainerRegistryProtectionRule`
|
||||
|
||||
A container registry protection rule designed to prevent users with a certain access level or lower from altering the container registry.
|
||||
A container repository protection rule designed to prevent users with a certain access level or lower from altering the container registry.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="containerregistryprotectionruleid"></a>`id` **{warning-solid}** | [`ContainerRegistryProtectionRuleID!`](#containerregistryprotectionruleid) | **Introduced** in GitLab 16.6. **Status**: Experiment. ID of the container registry protection rule. |
|
||||
| <a id="containerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` **{warning-solid}** | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | **Introduced** in GitLab 16.6. **Status**: Experiment. Minimum GitLab access level to allow to delete container images from the container registry. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level for delete is ignored. Users with at least the Developer role are allowed to delete container images. |
|
||||
| <a id="containerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` **{warning-solid}** | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | **Introduced** in GitLab 16.6. **Status**: Experiment. Minimum GitLab access level to allow to push container images to the container registry. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level for push is ignored. Users with at least the Developer role are allowed to push container images. |
|
||||
| <a id="containerregistryprotectionruleid"></a>`id` **{warning-solid}** | [`ContainerRegistryProtectionRuleID!`](#containerregistryprotectionruleid) | **Introduced** in GitLab 16.6. **Status**: Experiment. ID of the container repository protection rule. |
|
||||
| <a id="containerregistryprotectionruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` **{warning-solid}** | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | **Introduced** in GitLab 16.6. **Status**: Experiment. Minimum GitLab access level required to delete container images from the container repository. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level is ignored. Users with at least the Developer role can delete container images. |
|
||||
| <a id="containerregistryprotectionruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` **{warning-solid}** | [`ContainerRegistryProtectionRuleAccessLevel`](#containerregistryprotectionruleaccesslevel) | **Introduced** in GitLab 16.6. **Status**: Experiment. Minimum GitLab access level required to push container images to the container repository. For example, `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, the minimum access level is ignored. Users with at least the Developer role can push container images. |
|
||||
| <a id="containerregistryprotectionrulerepositorypathpattern"></a>`repositoryPathPattern` **{warning-solid}** | [`String!`](#string) | **Introduced** in GitLab 16.6. **Status**: Experiment. Container repository path pattern protected by the protection rule. For example, `my-project/my-container-*`. Wildcard character `*` allowed. |
|
||||
|
||||
### `ContainerRepository`
|
||||
|
|
@ -21245,7 +21245,7 @@ A container repository.
|
|||
| <a id="containerrepositoryname"></a>`name` | [`String!`](#string) | Name of the container repository. |
|
||||
| <a id="containerrepositorypath"></a>`path` | [`String!`](#string) | Path of the container repository. |
|
||||
| <a id="containerrepositoryproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |
|
||||
| <a id="containerrepositoryprotectionruleexists"></a>`protectionRuleExists` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.2. **Status**: Experiment. Whether any matching container protection rule exists for the container. Available only when feature flag `container_registry_protected_containers` is enabled. |
|
||||
| <a id="containerrepositoryprotectionruleexists"></a>`protectionRuleExists` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.2. **Status**: Experiment. Whether any matching container protection rule exists for the container repository. Available only when feature flag `container_registry_protected_containers` is enabled. |
|
||||
| <a id="containerrepositorystatus"></a>`status` | [`ContainerRepositoryStatus`](#containerrepositorystatus) | Status of the container repository. |
|
||||
| <a id="containerrepositorytagscount"></a>`tagsCount` | [`Int!`](#int) | Number of tags associated with the image. |
|
||||
| <a id="containerrepositoryupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp when the container repository was updated. |
|
||||
|
|
@ -21270,7 +21270,7 @@ Details of a container repository.
|
|||
| <a id="containerrepositorydetailsname"></a>`name` | [`String!`](#string) | Name of the container repository. |
|
||||
| <a id="containerrepositorydetailspath"></a>`path` | [`String!`](#string) | Path of the container repository. |
|
||||
| <a id="containerrepositorydetailsproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |
|
||||
| <a id="containerrepositorydetailsprotectionruleexists"></a>`protectionRuleExists` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.2. **Status**: Experiment. Whether any matching container protection rule exists for the container. Available only when feature flag `container_registry_protected_containers` is enabled. |
|
||||
| <a id="containerrepositorydetailsprotectionruleexists"></a>`protectionRuleExists` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.2. **Status**: Experiment. Whether any matching container protection rule exists for the container repository. Available only when feature flag `container_registry_protected_containers` is enabled. |
|
||||
| <a id="containerrepositorydetailssize"></a>`size` | [`Float`](#float) | Deduplicated size of the image repository in bytes. This is only available on GitLab.com for repositories created after `2021-11-04`. |
|
||||
| <a id="containerrepositorydetailsstatus"></a>`status` | [`ContainerRepositoryStatus`](#containerrepositorystatus) | Status of the container repository. |
|
||||
| <a id="containerrepositorydetailstagscount"></a>`tagsCount` | [`Int!`](#int) | Number of tags associated with the image. |
|
||||
|
|
|
|||
|
|
@ -1,194 +1,13 @@
|
|||
---
|
||||
stage: Package
|
||||
group: Container Registry
|
||||
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments"
|
||||
description: "Documentation for the REST API for container registry protection rules in GitLab."
|
||||
redirect_to: 'container_repository_protection_rules.md'
|
||||
remove_date: '2025-03-02'
|
||||
---
|
||||
|
||||
# Container registry protection rules API
|
||||
<!-- markdownlint-disable -->
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed
|
||||
**Status:** Experiment
|
||||
This document was moved to [another location](container_repository_protection_rules.md).
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155798) in GitLab 17.2 [with a flag](../administration/feature_flags.md) named `container_registry_protected_containers`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
This API endpoint manages the protection rules for container registries in a project. This feature is an experiment.
|
||||
|
||||
## List container registry protection rules
|
||||
|
||||
Gets a list of container registry protection rules from a project.
|
||||
|
||||
```plaintext
|
||||
GET /api/v4/projects/:id/registry/protection/rules
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-------------------------------|-----------------|----------|--------------------------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
|
||||
If successful, returns [`200`](rest/troubleshooting.md#status-codes) and a list of container registry protection rules.
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `200 OK`: A list of container registry protection rules.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to list container registry protection rules for this project.
|
||||
- `404 Not Found`: The project was not found.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"project_id": 7,
|
||||
"repository_path_pattern": "flightjs/flight0",
|
||||
"minimum_access_level_for_push": "maintainer",
|
||||
"minimum_access_level_for_delete": "maintainer"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"project_id": 7,
|
||||
"repository_path_pattern": "flightjs/flight1",
|
||||
"minimum_access_level_for_push": "maintainer",
|
||||
"minimum_access_level_for_delete": "maintainer"
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
## Create a container registry protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.2.
|
||||
|
||||
Create a container registry protection rule for a project.
|
||||
|
||||
```plaintext
|
||||
POST /api/v4/projects/:id/registry/protection/rules
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-----------------------------------|----------------|----------|-------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
| `repository_path_pattern` | string | Yes | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. |
|
||||
| `minimum_access_level_for_push` | string | No | Minimum GitLab access level to allow to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. |
|
||||
| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level to allow to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. |
|
||||
|
||||
If successful, returns [`201`](rest/troubleshooting.md#status-codes) and the created container registry protection rule.
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `201 Created`: The container registry protection rule was created successfully.
|
||||
- `400 Bad Request`: The container registry protection rule is invalid.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to create a container registry protection rule.
|
||||
- `404 Not Found`: The project was not found.
|
||||
- `422 Unprocessable Entity`: The container registry protection rule could not be created, for example, because the `repository_path_pattern` is already taken.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--header "Content-Type: application/json" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules" \
|
||||
--data '{
|
||||
"repository_path_pattern": "flightjs/flight-needs-to-be-a-unique-path",
|
||||
"minimum_access_level_for_push": "maintainer",
|
||||
"minimum_access_level_for_delete": "maintainer"
|
||||
}'
|
||||
```
|
||||
|
||||
## Update a container registry protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.2.
|
||||
|
||||
Update a container registry protection rule for a project.
|
||||
|
||||
```plaintext
|
||||
PATCH /api/v4/projects/:id/registry/protection/rules/:protection_rule_id
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|-----------------------------------|----------------|----------|-------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
| `protection_rule_id` | integer | Yes | ID of the protection rule to be updated. |
|
||||
| `repository_path_pattern` | string | No | Container repository path pattern protected by the protection rule. For example `flight/flight-*`. Wildcard character `*` allowed. |
|
||||
| `minimum_access_level_for_push` | string | No | Minimum GitLab access level to allow to push container images to the container registry. For example `maintainer`, `owner` or `admin`. Must be provided when `minimum_access_level_for_delete` is not set. To unset the value, use an empty string `""`. |
|
||||
| `minimum_access_level_for_delete` | string | No | Minimum GitLab access level to allow to delete container images in the container registry. For example `maintainer`, `owner`, `admin`. Must be provided when `minimum_access_level_for_push` is not set. To unset the value, use an empty string `""`. |
|
||||
|
||||
If successful, returns [`200`](rest/troubleshooting.md#status-codes) and the updated protection rule.
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `200 OK`: The protection rule was patched successfully.
|
||||
- `400 Bad Request`: The patch is invalid.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to patch the protection rule.
|
||||
- `404 Not Found`: The project was not found.
|
||||
- `422 Unprocessable Entity`: The protection rule could not be patched, for example, because the `repository_path_pattern` is already taken.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PATCH \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--header "Content-Type: application/json" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules/32" \
|
||||
--data '{
|
||||
"repository_path_pattern": "flight/flight-*"
|
||||
}'
|
||||
```
|
||||
|
||||
## Delete a container registry protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/457518) in GitLab 17.4.
|
||||
|
||||
Deletes a container registry protection rule from a project.
|
||||
|
||||
```plaintext
|
||||
DELETE /api/v4/projects/:id/registry/protection/rules/:protection_rule_id
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------|----------------|----------|-------------|
|
||||
| `id` | integer/string | Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-paths). |
|
||||
| `protection_rule_id` | integer | Yes | ID of the container registry protection rule to be deleted. |
|
||||
|
||||
If successful, returns [`204 No Content`](rest/troubleshooting.md#status-codes).
|
||||
|
||||
Can return the following status codes:
|
||||
|
||||
- `204 No Content`: The protection rule was deleted successfully.
|
||||
- `400 Bad Request`: The `id` or the `protection_rule_id` are missing or are invalid.
|
||||
- `401 Unauthorized`: The access token is invalid.
|
||||
- `403 Forbidden`: The user does not have permission to delete the protection rule.
|
||||
- `404 Not Found`: The project or the protection rule was not found.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--url "https://gitlab.example.com/api/v4/projects/7/registry/protection/rules/1"
|
||||
```
|
||||
<!-- This redirect file can be deleted after <2025-03-02>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ which could be much faster.
|
|||
|
||||
If desired, `c` and `d` jobs can be left to run in stage sequence.
|
||||
|
||||
The `needs` keyword also works with the [parallel](../yaml/index.md#parallel) keyword,
|
||||
The `needs` keyword also works with the [`parallel`](../yaml/index.md#parallel) keyword,
|
||||
giving you powerful options for parallelization in your pipeline.
|
||||
|
||||
## Use cases
|
||||
|
|
|
|||
|
|
@ -1,86 +1,13 @@
|
|||
---
|
||||
stage: Container
|
||||
group: Container Registry
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: 'container_repository_protection_rules.md'
|
||||
remove_date: '2025-02-28'
|
||||
---
|
||||
|
||||
# Protected container repositories
|
||||
<!-- markdownlint-disable -->
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed
|
||||
**Status:** Experiment
|
||||
This document was moved to [another location](container_repository_protection_rules.md).
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463669) in GitLab 16.7 [with a flag](../../../administration/feature_flags.md) named `containers_protected_containers`. Disabled by default. This feature is an [experiment](../../../policy/development_stages_support.md).
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
To selectively enable this feature for certain projects or groups, you need to use the root namespace of the project or group.
|
||||
It is not possible to enable this feature for projects individually.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
By default, any user with at least the Developer role can push and delete
|
||||
container images to or from container repositories. Protect a container repository to restrict
|
||||
which users can make changes to container images in your container repository.
|
||||
|
||||
When a container repository is protected, the default behavior enforces these restrictions on the container repository and its images:
|
||||
|
||||
| Action | Minimum role |
|
||||
|------------------------------------------------------------|----------------------|
|
||||
| Protect a container repository and its container images | The Maintainer role. |
|
||||
| Push or create a new image in a container repository | The role set in the [**Minimum access level for push**](#protect-a-container-repository-and-create-a-protection-rule) setting. |
|
||||
| Push or update an existing image in a container repository | The role set in the [**Minimum access level for push**](#protect-a-container-repository-and-create-a-protection-rule) setting |
|
||||
|
||||
You can use a wildcard (`*`) to protect multiple container repositories with the same container protection rule.
|
||||
For example, you can protect different container repositories containing temporary container images built during a CI/CD pipeline.
|
||||
|
||||
The following table contains examples of container protection rules that match multiple container repositories:
|
||||
|
||||
| Path pattern with wildcard | Example matching container repositories |
|
||||
|----------------------------|-----------------------------------------|
|
||||
| `group/container-*` | `group/container-prod`, `group/container-prod-sha123456789` |
|
||||
| `group/*container` | `group/container`, `group/prod-container`, `group/prod-sha123456789-container` |
|
||||
| `group/*container*` | `group/container`, `group/prod-sha123456789-container-v1` |
|
||||
|
||||
You can apply several protection rules to the same container repository.
|
||||
A container repository is protected if at least one protection rule matches.
|
||||
|
||||
## Protect a container repository and create a protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146523) in GitLab 16.10.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role.
|
||||
|
||||
To protect a container repository:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Packages and registries**.
|
||||
1. Under **Protected containers**, select **Add protection rule**.
|
||||
1. Complete the fields:
|
||||
- **Repository path pattern** is a container repository path you want to protect.
|
||||
The pattern can include a wildcard (`*`).
|
||||
- **Minimum access level for push** describes the minimum access level required
|
||||
to push (create or update) to the protected container repository path.
|
||||
1. Select **Protect**.
|
||||
|
||||
The container protection rule is created, and appears in the settings.
|
||||
|
||||
## Delete a container protection rule and unprotect a container repository
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146622) in GitLab 17.0.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role.
|
||||
|
||||
To unprotect a container repository:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Packages and registries**.
|
||||
1. Under **Protected containers**, next to the protection rule you want to delete, select **Delete** (**{remove}**).
|
||||
1. On the confirmation dialog, select **Delete**.
|
||||
|
||||
The container protection rule is deleted, and does not appear in the settings.
|
||||
<!-- This redirect file can be deleted after <2025-02-28>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
stage: Container
|
||||
group: Container Registry
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Protected container repositories
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed
|
||||
**Status:** Experiment
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463669) in GitLab 16.7 [with a flag](../../../administration/feature_flags.md) named `containers_protected_containers`. Disabled by default. This feature is an [experiment](../../../policy/development_stages_support.md).
|
||||
|
||||
FLAG:
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
To selectively enable this feature for certain projects or groups, you need to use the root namespace of the project or group.
|
||||
It is not possible to enable this feature for projects individually.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
By default, any user with at least the Developer role can push and delete
|
||||
container images to or from container repositories. Protect a container repository to restrict
|
||||
which users can make changes to container images in your container repository.
|
||||
|
||||
When a container repository is protected, the default behavior enforces these restrictions on the container repository and its images:
|
||||
|
||||
| Action | Minimum role |
|
||||
|------------------------------------------------------------|----------------------|
|
||||
| Protect a container repository and its container images. | The Maintainer role. |
|
||||
| Push or create a new image in a container repository. | The role set in the [**Minimum access level for push**](#create-a-container-repository-protection-rule) setting. |
|
||||
| Push or update an existing image in a container repository. | The role set in the [**Minimum access level for push**](#create-a-container-repository-protection-rule) setting. |
|
||||
|
||||
You can use a wildcard (`*`) to protect multiple container repositories with the same container protection rule.
|
||||
For example, you can protect different container repositories containing temporary container images built during a CI/CD pipeline.
|
||||
|
||||
The following table contains examples of container protection rules that match multiple container repositories:
|
||||
|
||||
| Path pattern with wildcard | Example matching container repositories |
|
||||
|----------------------------|-----------------------------------------|
|
||||
| `group/container-*` | `group/container-prod`, `group/container-prod-sha123456789` |
|
||||
| `group/*container` | `group/container`, `group/prod-container`, `group/prod-sha123456789-container` |
|
||||
| `group/*container*` | `group/container`, `group/prod-sha123456789-container-v1` |
|
||||
|
||||
You can apply several protection rules to the same container repository.
|
||||
A container repository is protected if at least one protection rule matches.
|
||||
|
||||
## Create a container repository protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146523) in GitLab 16.10.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role.
|
||||
|
||||
To create a protection rule:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Packages and registries**.
|
||||
1. Under **Protected container repositories**, select **Add protection rule**.
|
||||
1. Complete the fields:
|
||||
- **Repository path pattern** is a container repository path you want to protect.
|
||||
The pattern can include a wildcard (`*`).
|
||||
- **Minimum access level for push** describes the minimum access level required
|
||||
to push (create or update) to the protected container repository path.
|
||||
1. Select **Protect**.
|
||||
|
||||
The protection rule is created and the container repository is now protected.
|
||||
|
||||
## Delete a container repository protection rule
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146622) in GitLab 17.0.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role.
|
||||
|
||||
To delete a protection rule:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Settings > Packages and registries**.
|
||||
1. Under **Protected container repositories**, next to the protection rule you want to delete, select **Delete** (**{remove}**).
|
||||
1. On the confirmation dialog, select **Delete**.
|
||||
|
||||
The protection rule is deleted and the container repository is no longer protected.
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
stage: Plan
|
||||
group: Knowledge
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Confluence Workspace
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Use a Confluence Workspace as your project wiki.
|
||||
|
||||
This integration adds a link to a Confluence wiki instead of the [GitLab wiki](../wiki/index.md).
|
||||
Any content you have in Confluence is not displayed in GitLab.
|
||||
|
||||
When you turn on the integration:
|
||||
|
||||
- A new menu item is added to the left sidebar: **Plan > Confluence**.
|
||||
It links to your Confluence wiki.
|
||||
- The **Plan > Wiki** menu item is hidden.
|
||||
|
||||
To access the GitLab wiki for the project, use its URL:
|
||||
`<example_project_URL>/-/wikis/home`.
|
||||
To bring back the **Plan > Wiki** menu item, turn off this integration.
|
||||
|
||||
Creating a more comprehensive integration with Confluence Cloud is tracked in
|
||||
[epic 3629](https://gitlab.com/groups/gitlab-org/-/epics/3629).
|
||||
|
||||
## Set up the integration
|
||||
|
||||
This integration can be turned on for a project or for all projects in a group or instance.
|
||||
|
||||
### For your project or all projects in a group
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role for the project.
|
||||
- You must use a Confluence Cloud URL (`https://example.atlassian.net/wiki/`).
|
||||
|
||||
To set up the integration for your project or group:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project or group.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Next to **Confluence Workspace**, select **Configure**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **Confluence Workspace URL**, enter your Confluence Workspace URL.
|
||||
1. Select **Save changes**.
|
||||
|
||||
If the integration has been turned on for the group, you can still turn it off for individual projects.
|
||||
|
||||
### For all projects on the instance
|
||||
|
||||
DETAILS:
|
||||
**Offering:** Self-managed
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have administrator access to the instance.
|
||||
- You must use a Confluence Cloud URL (`https://example.atlassian.net/wiki/`).
|
||||
|
||||
To set up the integration for your instance:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
1. Select **Settings > Integrations**.
|
||||
1. Next to **Confluence Workspace**, select **Configure**.
|
||||
1. Under **Enable integration**, select the **Active** checkbox.
|
||||
1. In **Confluence Workspace URL**, enter your Confluence Workspace URL.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Access your Confluence Workspace from GitLab
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must set up the integration [for your project, group](#for-your-project-or-all-projects-in-a-group),
|
||||
or [for your instance](#for-all-projects-on-the-instance).
|
||||
|
||||
To access your Confluence Workspace from a GitLab project:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select **Plan > Confluence**.
|
||||
1. Select **Go to Confluence**.
|
||||
|
|
@ -114,55 +114,55 @@ To use custom settings for a project or group integration:
|
|||
|
||||
## Available integrations
|
||||
|
||||
| Integration | Description | Integration hooks |
|
||||
|-----------------------------------------------------------------------------|-----------------------------------------------------------------------|------------------------|
|
||||
| [Apple App Store Connect](apple_app_store.md) | Use GitLab to build and release an app in the Apple App Store. | **{dotted-circle}** No |
|
||||
| [Asana](asana.md) | Add commit messages as comments to Asana tasks. | **{dotted-circle}** No |
|
||||
| Assembla | Manage projects with Assembla. | **{dotted-circle}** No |
|
||||
| [Atlassian Bamboo](bamboo.md) | Run CI/CD pipelines with Atlassian Bamboo. | **{check-circle}** Yes |
|
||||
| [Bugzilla](bugzilla.md) | Use Bugzilla as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Beyond Identity](beyond_identity.md) | Verify that GPG keys are authorized by Beyond Identity Authenticator. | **{dotted-circle}** No |
|
||||
| Buildkite | Run CI/CD pipelines with Buildkite. | **{check-circle}** Yes |
|
||||
| Campfire | Connect Campfire to chat. | **{dotted-circle}** No |
|
||||
| [ClickUp](clickup.md) | Use ClickUp as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Confluence Workspace](../../../api/integrations.md#confluence-workspace) | Use Confluence Workspace as an internal wiki. | **{dotted-circle}** No |
|
||||
| [Custom issue tracker](custom_issue_tracker.md) | Use a custom issue tracker. | **{dotted-circle}** No |
|
||||
| [Datadog](../../../integration/datadog.md) | Trace your GitLab pipelines with Datadog. | **{check-circle}** Yes |
|
||||
| [Diffblue Cover](../../../integration/diffblue_cover.md) | Automatically write comprehensive, human-like Java unit tests. | **{check-circle}** No |
|
||||
| [Discord Notifications](discord_notifications.md) | Send notifications about project events to a Discord channel. | **{dotted-circle}** No |
|
||||
| Drone | Run CI/CD pipelines with Drone. | **{check-circle}** Yes |
|
||||
| [Emails on push](emails_on_push.md) | Send commits and diffs on push by email. | **{dotted-circle}** No |
|
||||
| [Engineering Workflow Management (EWM)](ewm.md) | Use EWM as an issue tracker. | **{dotted-circle}** No |
|
||||
| [External wiki](../wiki/index.md#link-an-external-wiki) | Link an external wiki. | **{dotted-circle}** No |
|
||||
| [GitGuardian](git_guardian.md) | Reject commits based on GitGuardian policies. | **{dotted-circle}** No |
|
||||
| [GitHub](github.md) | Receive statuses for commits and pull requests. | **{dotted-circle}** No |
|
||||
| [GitLab for Slack app](gitlab_slack_application.md) | Use the native Slack app to receive notifications and run commands. | **{dotted-circle}** No |
|
||||
| [Google Artifact Management](google_artifact_management.md) | Manage your artifacts in Google Artifact Registry. | **{dotted-circle}** No |
|
||||
| [Google Chat](hangouts_chat.md) | Send notifications from your GitLab project to a space in Google Chat. | **{dotted-circle}** No |
|
||||
| [Google Cloud IAM](../../../integration/google_cloud_iam.md) | Manage permissions for Google Cloud resources with Identity and Access Management (IAM). | **{dotted-circle}** No |
|
||||
| [Google Play](google_play.md) | Use GitLab to build and release an app in Google Play. | **{dotted-circle}** No |
|
||||
| [Harbor](harbor.md) | Use Harbor as the container registry for GitLab. | **{dotted-circle}** No |
|
||||
| [irker (IRC gateway)](irker.md) | Send IRC messages. | **{dotted-circle}** No |
|
||||
| [Jenkins](../../../integration/jenkins.md) | Run CI/CD pipelines with Jenkins. | **{check-circle}** Yes |
|
||||
| JetBrains TeamCity | Run CI/CD pipelines with TeamCity. | **{check-circle}** Yes |
|
||||
| [JetBrains YouTrack](youtrack.md) | Use JetBrains YouTrack as your project's issue tracker. | **{dotted-circle}** No |
|
||||
| [Jira](../../../integration/jira/index.md) | Use Jira as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Matrix notifications](matrix.md) | Send notifications about project events to Matrix. | **{dotted-circle}** No |
|
||||
| [Mattermost notifications](mattermost.md) | Send notifications about project events to Mattermost channels. | **{dotted-circle}** No |
|
||||
| [Mattermost slash commands](mattermost_slash_commands.md) | Run slash commands from a Mattermost chat environment. | **{dotted-circle}** No |
|
||||
| [Microsoft Teams notifications](microsoft_teams.md) | Receive event notifications in Microsoft Teams. | **{dotted-circle}** No |
|
||||
| Packagist | Update your PHP dependencies in Packagist. | **{check-circle}** Yes |
|
||||
| [Phorge](phorge.md) | Use Phorge as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Pipeline status emails](pipeline_status_emails.md) | Send the pipeline status to a list of recipients by email. | **{dotted-circle}** No |
|
||||
| [Pivotal Tracker](pivotal_tracker.md) | Add commit messages as comments to Pivotal Tracker stories. | **{dotted-circle}** No |
|
||||
| [Pumble](pumble.md) | Send event notifications to a Pumble channel. | **{dotted-circle}** No |
|
||||
| Pushover | Get real-time notifications on your device. | **{dotted-circle}** No |
|
||||
| [Redmine](redmine.md) | Use Redmine as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Slack slash commands](slack_slash_commands.md) | Run slash commands from a Slack chat environment. | **{dotted-circle}** No |
|
||||
| [Squash TM](squash_tm.md) | Update Squash TM requirements when GitLab issues are modified. | **{check-circle}** Yes |
|
||||
| [Telegram](telegram.md) | Send notifications about project events to Telegram. | **{dotted-circle}** No |
|
||||
| [Unify Circuit](unify_circuit.md) | Send notifications about project events to Unify Circuit. | **{dotted-circle}** No |
|
||||
| [Webex Teams](webex_teams.md) | Receive event notifications in Webex Teams. | **{dotted-circle}** No |
|
||||
| Integration | Description | Integration hooks |
|
||||
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ----------------- |
|
||||
| [Apple App Store Connect](apple_app_store.md) | Use GitLab to build and release an app in the Apple App Store. | **{dotted-circle}** No |
|
||||
| [Asana](asana.md) | Add commit messages as comments to Asana tasks. | **{dotted-circle}** No |
|
||||
| Assembla | Manage projects with Assembla. | **{dotted-circle}** No |
|
||||
| [Atlassian Bamboo](bamboo.md) | Run CI/CD pipelines with Atlassian Bamboo. | **{check-circle}** Yes |
|
||||
| [Bugzilla](bugzilla.md) | Use Bugzilla as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Beyond Identity](beyond_identity.md) | Verify that GPG keys are authorized by Beyond Identity Authenticator. | **{dotted-circle}** No |
|
||||
| Buildkite | Run CI/CD pipelines with Buildkite. | **{check-circle}** Yes |
|
||||
| Campfire | Connect Campfire to chat. | **{dotted-circle}** No |
|
||||
| [ClickUp](clickup.md) | Use ClickUp as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Confluence Workspace](confluence.md) | Use Confluence Workspace as an internal wiki. | **{dotted-circle}** No |
|
||||
| [Custom issue tracker](custom_issue_tracker.md) | Use a custom issue tracker. | **{dotted-circle}** No |
|
||||
| [Datadog](../../../integration/datadog.md) | Trace your GitLab pipelines with Datadog. | **{check-circle}** Yes |
|
||||
| [Diffblue Cover](../../../integration/diffblue_cover.md) | Automatically write comprehensive, human-like Java unit tests. | **{check-circle}** No |
|
||||
| [Discord Notifications](discord_notifications.md) | Send notifications about project events to a Discord channel. | **{dotted-circle}** No |
|
||||
| Drone | Run CI/CD pipelines with Drone. | **{check-circle}** Yes |
|
||||
| [Emails on push](emails_on_push.md) | Send commits and diffs on push by email. | **{dotted-circle}** No |
|
||||
| [Engineering Workflow Management (EWM)](ewm.md) | Use EWM as an issue tracker. | **{dotted-circle}** No |
|
||||
| [External wiki](../wiki/index.md#link-an-external-wiki) | Link an external wiki. | **{dotted-circle}** No |
|
||||
| [GitGuardian](git_guardian.md) | Reject commits based on GitGuardian policies. | **{dotted-circle}** No |
|
||||
| [GitHub](github.md) | Receive statuses for commits and pull requests. | **{dotted-circle}** No |
|
||||
| [GitLab for Slack app](gitlab_slack_application.md) | Use the native Slack app to receive notifications and run commands. | **{dotted-circle}** No |
|
||||
| [Google Artifact Management](google_artifact_management.md) | Manage your artifacts in Google Artifact Registry. | **{dotted-circle}** No |
|
||||
| [Google Chat](hangouts_chat.md) | Send notifications from your GitLab project to a space in Google Chat. | **{dotted-circle}** No |
|
||||
| [Google Cloud IAM](../../../integration/google_cloud_iam.md) | Manage permissions for Google Cloud resources with Identity and Access Management (IAM). | **{dotted-circle}** No |
|
||||
| [Google Play](google_play.md) | Use GitLab to build and release an app in Google Play. | **{dotted-circle}** No |
|
||||
| [Harbor](harbor.md) | Use Harbor as the container registry for GitLab. | **{dotted-circle}** No |
|
||||
| [irker (IRC gateway)](irker.md) | Send IRC messages. | **{dotted-circle}** No |
|
||||
| [Jenkins](../../../integration/jenkins.md) | Run CI/CD pipelines with Jenkins. | **{check-circle}** Yes |
|
||||
| JetBrains TeamCity | Run CI/CD pipelines with TeamCity. | **{check-circle}** Yes |
|
||||
| [JetBrains YouTrack](youtrack.md) | Use JetBrains YouTrack as your project's issue tracker. | **{dotted-circle}** No |
|
||||
| [Jira](../../../integration/jira/index.md) | Use Jira as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Matrix notifications](matrix.md) | Send notifications about project events to Matrix. | **{dotted-circle}** No |
|
||||
| [Mattermost notifications](mattermost.md) | Send notifications about project events to Mattermost channels. | **{dotted-circle}** No |
|
||||
| [Mattermost slash commands](mattermost_slash_commands.md) | Run slash commands from a Mattermost chat environment. | **{dotted-circle}** No |
|
||||
| [Microsoft Teams notifications](microsoft_teams.md) | Receive event notifications in Microsoft Teams. | **{dotted-circle}** No |
|
||||
| Packagist | Update your PHP dependencies in Packagist. | **{check-circle}** Yes |
|
||||
| [Phorge](phorge.md) | Use Phorge as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Pipeline status emails](pipeline_status_emails.md) | Send the pipeline status to a list of recipients by email. | **{dotted-circle}** No |
|
||||
| [Pivotal Tracker](pivotal_tracker.md) | Add commit messages as comments to Pivotal Tracker stories. | **{dotted-circle}** No |
|
||||
| [Pumble](pumble.md) | Send event notifications to a Pumble channel. | **{dotted-circle}** No |
|
||||
| Pushover | Get real-time notifications on your device. | **{dotted-circle}** No |
|
||||
| [Redmine](redmine.md) | Use Redmine as an issue tracker. | **{dotted-circle}** No |
|
||||
| [Slack slash commands](slack_slash_commands.md) | Run slash commands from a Slack chat environment. | **{dotted-circle}** No |
|
||||
| [Squash TM](squash_tm.md) | Update Squash TM requirements when GitLab issues are modified. | **{check-circle}** Yes |
|
||||
| [Telegram](telegram.md) | Send notifications about project events to Telegram. | **{dotted-circle}** No |
|
||||
| [Unify Circuit](unify_circuit.md) | Send notifications about project events to Unify Circuit. | **{dotted-circle}** No |
|
||||
| [Webex Teams](webex_teams.md) | Receive event notifications in Webex Teams. | **{dotted-circle}** No |
|
||||
|
||||
## Project webhooks
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ module API
|
|||
feature_category :importers
|
||||
urgency :low
|
||||
|
||||
before do
|
||||
set_current_organization
|
||||
end
|
||||
|
||||
helpers do
|
||||
def client
|
||||
@client ||= BitbucketServer::Client.new(credentials)
|
||||
|
|
@ -45,7 +49,8 @@ module API
|
|||
end
|
||||
|
||||
post 'import/bitbucket_server' do
|
||||
result = Import::BitbucketServerService.new(client, current_user, params).execute(credentials)
|
||||
result = Import::BitbucketServerService.new(client, current_user,
|
||||
params.merge(organization_id: Current.organization.id)).execute(credentials)
|
||||
|
||||
if result[:status] == :success
|
||||
present ProjectSerializer.new.represent(result[:project], serializer: :import)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ module Gitlab
|
|||
ImpersonationDisabled = Class.new(AuthenticationError)
|
||||
UnauthorizedError = Class.new(AuthenticationError)
|
||||
|
||||
class DpopValidationError < AuthenticationError
|
||||
def initialize(msg)
|
||||
super("DPoP validation error: #{msg}")
|
||||
end
|
||||
end
|
||||
|
||||
class InsufficientScopeError < AuthenticationError
|
||||
attr_reader :scopes
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Demonstrated Proof of Possession (DPoP) is a mechanism to tie a user's
|
||||
# Personal Access Token (PAT) to one of their signing keys.
|
||||
#
|
||||
# A DPoP Token is a signed JSON Web Token. This class implements
|
||||
# the logic to ensure a provided DPoP Token is well-formed and
|
||||
# cryptographically signed.
|
||||
#
|
||||
module Gitlab
|
||||
module Auth
|
||||
class DpopToken
|
||||
KID_DELIMITER = ':'
|
||||
|
||||
attr_reader :data, :payload, :header
|
||||
|
||||
def initialize(data:)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def validate!
|
||||
begin
|
||||
@payload, @header = JWT.decode(
|
||||
data,
|
||||
nil, # we do not pass a key here as we are not checking the signature
|
||||
false # we are not verifying the signature or claims
|
||||
)
|
||||
rescue JWT::DecodeError => e
|
||||
raise Gitlab::Auth::DpopValidationError, "Malformed JWT, unable to decode. #{e.message}"
|
||||
end
|
||||
|
||||
# All comparisons should be case-sensitive, using secure comparison
|
||||
# See https://www.rfc-editor.org/rfc/rfc7515#section-4.1.1
|
||||
raise Gitlab::Auth::DpopValidationError, 'Invalid typ value in JWT' unless header['typ'].casecmp?('dpop+jwt')
|
||||
|
||||
raise Gitlab::Auth::DpopValidationError, 'No kid in JWT, unable to fetch key' if header['kid'].nil?
|
||||
|
||||
# Check header[alg] is one of SUPPORTED_JWS_ALGORITHMS.
|
||||
# Remove when support for ED25519 is added
|
||||
# This checks for 'alg' in the header and exits early
|
||||
unless header['alg'].casecmp?('RS512')
|
||||
raise Gitlab::Auth::DpopValidationError,
|
||||
'Currently only RSA keys are supported'
|
||||
end
|
||||
|
||||
# Check the format of header[kid] (ALGORITHM DELIMITER b64(HASH))
|
||||
kid_parts = header['kid'].split(KID_DELIMITER)
|
||||
raise Gitlab::Auth::DpopValidationError, 'Malformed fingerprint value in kid' unless kid_parts.size == 2
|
||||
|
||||
# Check kid_algorithm is supported
|
||||
kid_algorithm = kid_parts[0]
|
||||
unless kid_algorithm.casecmp?('SHA256')
|
||||
raise Gitlab::Auth::DpopValidationError, 'Unsupported fingerprint algorithm in kid'
|
||||
end
|
||||
|
||||
return if header.dig('jwk', 'kty').eql?('RSA')
|
||||
|
||||
raise Gitlab::Auth::DpopValidationError, 'JWK algorithm must be RSA'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackfillIssueMetricsNamespaceId < BackfillDesiredShardingKeyJob
|
||||
operation_name :backfill_issue_metrics_namespace_id
|
||||
feature_category :value_stream_management
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class DeleteOrphanedGroups < BatchedMigrationJob
|
||||
operation_name :delete_orphaned_group_records
|
||||
feature_category :groups_and_projects
|
||||
|
||||
scope_to ->(relation) { relation.where(type: 'Group').where.not(parent_id: nil) }
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
sub_batch
|
||||
.joins("LEFT JOIN namespaces AS parent ON namespaces.parent_id = parent.id")
|
||||
.where(parent: { id: nil })
|
||||
.pluck(:id).each do |orphaned_group_id|
|
||||
::GroupDestroyWorker.perform(orphaned_group_id, admin_bot.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def admin_bot
|
||||
@_admin_bot ||= Users::Internal.admin_bot
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14985,7 +14985,7 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Delete container protection rule"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Delete container protection rule?"
|
||||
msgid "ContainerRegistry|Delete container repository protection rule?"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Delete image repository"
|
||||
|
|
@ -15096,7 +15096,7 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Please try different search criteria"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Protected containers"
|
||||
msgid "ContainerRegistry|Protected container repositories"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Published %{timeInfo}"
|
||||
|
|
@ -15266,7 +15266,7 @@ msgstr ""
|
|||
msgid "ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|When a container is protected, only certain user roles can push the protected container image, which helps to avoid tampering with the container image."
|
||||
msgid "ContainerRegistry|When a container repository is protected, only certain user roles can push the protected container image, which helps to avoid tampering with the container image."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|While the rename is in progress, new uploads to the container registry are blocked. Ongoing uploads may fail and need to be retried."
|
||||
|
|
@ -15278,7 +15278,7 @@ msgstr ""
|
|||
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|You are about to delete the container protection rule for %{repositoryPathPattern}."
|
||||
msgid "ContainerRegistry|You are about to delete the container repository protection rule for %{repositoryPathPattern}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|You are about to remove %{item} tags. Are you sure?"
|
||||
|
|
|
|||
|
|
@ -281,11 +281,7 @@ module QA
|
|||
end
|
||||
|
||||
def default_credentials
|
||||
if Runtime::User.ldap_user?
|
||||
[Runtime::User.ldap_username, Runtime::User.ldap_password]
|
||||
else
|
||||
[default_user.username, default_user.password]
|
||||
end
|
||||
[default_user.username, default_user.password]
|
||||
end
|
||||
|
||||
def read_netrc_content
|
||||
|
|
|
|||
|
|
@ -70,12 +70,9 @@ module QA
|
|||
using_wait_time 0 do
|
||||
set_initial_password_if_present
|
||||
|
||||
if Runtime::User.ldap_user? && user && user.username != Runtime::User.ldap_username
|
||||
raise QA::Resource::User::InvalidUserError, 'If an LDAP user is provided, it must be used for sign-in'
|
||||
end
|
||||
|
||||
test_user = user || Runtime::UserStore.test_user
|
||||
if Runtime::User.ldap_user?
|
||||
|
||||
if test_user.ldap_user?
|
||||
sign_in_using_ldap_credentials(user: test_user)
|
||||
else
|
||||
sign_in_using_gitlab_credentials(user: test_user, skip_page_validation: skip_page_validation)
|
||||
|
|
@ -104,8 +101,8 @@ module QA
|
|||
|
||||
switch_to_ldap_tab
|
||||
|
||||
fill_element 'username-field', user.ldap_username
|
||||
fill_element 'password-field', user.ldap_password
|
||||
fill_element 'username-field', user.username
|
||||
fill_element 'password-field', user.password
|
||||
click_element 'sign-in-button'
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module QA
|
|||
InvalidUserError = Class.new(RuntimeError)
|
||||
|
||||
attr_reader :unique_id
|
||||
attr_writer :username, :password
|
||||
attr_writer :username, :password, :ldap_user
|
||||
attr_accessor :admin,
|
||||
:provider,
|
||||
:extern_uid,
|
||||
|
|
@ -49,6 +49,7 @@ module QA
|
|||
@email_domain = 'example.com'
|
||||
@with_personal_access_token = false
|
||||
@personal_access_tokens = []
|
||||
@ldap_user = false
|
||||
end
|
||||
|
||||
def admin?
|
||||
|
|
@ -58,12 +59,10 @@ module QA
|
|||
def username
|
||||
@username || "qa-user-#{unique_id}"
|
||||
end
|
||||
alias_method :ldap_username, :username
|
||||
|
||||
def password
|
||||
@password ||= "Pa$$w0rd"
|
||||
end
|
||||
alias_method :ldap_password, :password
|
||||
|
||||
def name
|
||||
@name ||= api_resource&.dig(:name) || "QA User #{unique_id}"
|
||||
|
|
@ -220,6 +219,13 @@ module QA
|
|||
parse_body(resp)
|
||||
end
|
||||
|
||||
# User registered through LDAP protocol
|
||||
#
|
||||
# @return [Boolean]
|
||||
def ldap_user?
|
||||
@ldap_user
|
||||
end
|
||||
|
||||
# Create new personal access token for user
|
||||
#
|
||||
# @return [QA::Resource::PersonalAccessToken]
|
||||
|
|
|
|||
|
|
@ -424,16 +424,11 @@ module QA
|
|||
end
|
||||
|
||||
def ldap_username
|
||||
@ldap_username ||= ENV['GITLAB_LDAP_USERNAME']
|
||||
end
|
||||
|
||||
def ldap_username=(ldap_username)
|
||||
@ldap_username = ldap_username # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
ENV['GITLAB_LDAP_USERNAME'] = ldap_username
|
||||
ENV['GITLAB_LDAP_USERNAME']
|
||||
end
|
||||
|
||||
def ldap_password
|
||||
@ldap_password ||= ENV['GITLAB_LDAP_PASSWORD']
|
||||
ENV['GITLAB_LDAP_PASSWORD']
|
||||
end
|
||||
|
||||
def sandbox_name
|
||||
|
|
|
|||
|
|
@ -22,18 +22,6 @@ module QA
|
|||
'5iveL!fe'
|
||||
end
|
||||
|
||||
def ldap_user?
|
||||
Runtime::Env.ldap_username.present? && Runtime::Env.ldap_password.present?
|
||||
end
|
||||
|
||||
def ldap_username
|
||||
Runtime::Env.ldap_username || username
|
||||
end
|
||||
|
||||
def ldap_password
|
||||
Runtime::Env.ldap_password || password
|
||||
end
|
||||
|
||||
def admin_username
|
||||
Runtime::Env.admin_username || default_username
|
||||
end
|
||||
|
|
|
|||
|
|
@ -83,7 +83,16 @@ module QA
|
|||
return @test_user if defined?(@test_user)
|
||||
|
||||
info("Creating test user")
|
||||
return @test_user = create_new_user if create_unique_test_user?
|
||||
|
||||
if ldap_user_configured?
|
||||
return @test_user = Resource::User.init do |user|
|
||||
user.username = Env.ldap_username
|
||||
user.password = Env.ldap_password
|
||||
user.ldap_user = true
|
||||
end
|
||||
elsif create_unique_test_user?
|
||||
return @test_user = create_new_user
|
||||
end
|
||||
|
||||
if Env.user_username.blank? || Env.user_password.blank?
|
||||
raise "Missing user_username and user_password variable values"
|
||||
|
|
@ -280,6 +289,13 @@ module QA
|
|||
def status_ok?(resp)
|
||||
resp.code == Support::API::HTTP_STATUS_OK
|
||||
end
|
||||
|
||||
# Check if environment has ldap user set
|
||||
#
|
||||
# @return [Boolean]
|
||||
def ldap_user_configured?
|
||||
Runtime::Env.ldap_username.present? && Runtime::Env.ldap_password.present?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Govern', :skip_signup_disabled, :requires_admin, product_group: :authentication do
|
||||
shared_examples 'registration and login' do
|
||||
describe 'while LDAP is enabled', :orchestrated, :ldap_no_tls,
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347934' do
|
||||
it 'allows the user to register and login' do
|
||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||
|
||||
Resource::User.fabricate_via_browser_ui! do |user_resource|
|
||||
user_resource.email_domain = 'gitlab.com'
|
||||
Resource::User.fabricate_via_browser_ui! do |user|
|
||||
user.username = Runtime::Env.ldap_username
|
||||
user.password = Runtime::Env.ldap_password
|
||||
user.email_domain = 'gitlab.com'
|
||||
user.ldap_user = true
|
||||
end
|
||||
|
||||
Page::Main::Menu.perform do |menu|
|
||||
|
|
@ -16,35 +20,6 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
describe 'while LDAP is enabled', :orchestrated, :ldap_no_tls,
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347934' do
|
||||
let!(:personal_access_token) { Runtime::Env.personal_access_token }
|
||||
|
||||
around do |example|
|
||||
with_application_settings(require_admin_approval_after_user_signup: false) { example.run }
|
||||
end
|
||||
|
||||
before do
|
||||
# When LDAP is enabled, a previous test might have created a token for the LDAP 'tanuki' user who is not
|
||||
# an admin. So we need to set it to nil in order to create a new token for admin user so that we are able
|
||||
# to set_application_settings. Also, when GITLAB_LDAP_USERNAME is provided, it is used to create a token.
|
||||
# This also needs to be set to nil temporarily for the same reason as above.
|
||||
|
||||
Runtime::Env.personal_access_token = nil
|
||||
|
||||
ldap_username = Runtime::Env.ldap_username
|
||||
Runtime::Env.ldap_username = nil
|
||||
|
||||
Runtime::Env.ldap_username = ldap_username
|
||||
end
|
||||
|
||||
after do
|
||||
Runtime::Env.personal_access_token = personal_access_token
|
||||
end
|
||||
|
||||
it_behaves_like 'registration and login'
|
||||
end
|
||||
|
||||
# TODO: needs to be refactored to correctly support parallel testing
|
||||
# If any other spec file depends on require_admin_approval setting, it could fail
|
||||
describe 'standard', :smoke, :external_api_calls do
|
||||
|
|
@ -55,7 +30,17 @@ module QA
|
|||
|
||||
context "with basic registration",
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347867' do
|
||||
it_behaves_like 'registration and login'
|
||||
it 'allows the user to register and login' do
|
||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||
|
||||
Resource::User.fabricate_via_browser_ui! do |user_resource|
|
||||
user_resource.email_domain = 'gitlab.com'
|
||||
end
|
||||
|
||||
Page::Main::Menu.perform do |menu|
|
||||
expect(menu).to have_personal_area
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with user deletion" do
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ RSpec.describe Admin::UsersController, :with_current_organization, feature_categ
|
|||
request
|
||||
|
||||
errors = assigns[:user].errors
|
||||
expect(errors).to contain_exactly('Organization users is invalid')
|
||||
expect(errors).to contain_exactly("Namespace organization can't be blank", "Organization users is invalid")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ RSpec.describe GroupsController, :with_current_organization, factory_default: :k
|
|||
project = create(:project, group: group)
|
||||
create(:event, project: project)
|
||||
end
|
||||
subgroup = create(:group, parent: group)
|
||||
subgroup = create(:group, parent: group, organization: group.organization)
|
||||
project = create(:project, group: subgroup)
|
||||
create(:event, project: project)
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ FactoryBot.define do
|
|||
|
||||
children.times do
|
||||
factory_name = parent.model_name.singular
|
||||
child = FactoryBot.create(factory_name, parent: parent)
|
||||
child = create(factory_name, parent: parent, organization: parent.organization)
|
||||
create_graph(parent: child, children: children, depth: depth - 1)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,16 @@ FactoryBot.define do
|
|||
|
||||
owner { association(:user, strategy: :build, namespace: instance, username: path) }
|
||||
|
||||
# TODO: Remove usage of default organization https://gitlab.com/gitlab-org/gitlab/-/issues/446293
|
||||
organization { parent ? parent.organization : association(:organization, :default) }
|
||||
after(:build) do |namespace, evaluator|
|
||||
namespace.organization ||= evaluator.parent&.organization ||
|
||||
# The ordering of Organizations by created_at does not match ordering by the id column.
|
||||
# This is because Organization::DEFAULT_ORGANIZATION_ID is 1, but in the specs the default
|
||||
# organization may get created after another organization.
|
||||
Organizations::Organization.order(:created_at).first ||
|
||||
# We create an organization next even though we are building here. We need to ensure
|
||||
# that an organization exists so other entities can belong to the same organization
|
||||
create(:organization)
|
||||
end
|
||||
|
||||
after(:create) do |namespace, evaluator|
|
||||
# simulating ::Namespaces::ProcessSyncEventsWorker because most tests don't run Sidekiq inline
|
||||
|
|
|
|||
|
|
@ -21,8 +21,15 @@ FactoryBot.define do
|
|||
true
|
||||
end
|
||||
|
||||
# TODO: Remove usage of default organization https://gitlab.com/gitlab-org/gitlab/-/issues/446293
|
||||
user.assign_personal_namespace(create(:organization, :default)) if assign_ns
|
||||
if assign_ns
|
||||
org = user&.namespace&.organization ||
|
||||
Organizations::Organization.order(:created_at).first ||
|
||||
# We create an organization next even though we are building here. We need to ensure
|
||||
# that an organization exists so other entities can belong to the same organization
|
||||
create(:organization)
|
||||
|
||||
user.assign_personal_namespace(org)
|
||||
end
|
||||
end
|
||||
|
||||
trait :without_default_org do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Admin Groups', feature_category: :groups_and_projects do
|
||||
RSpec.describe 'Admin Groups', :with_current_organization, feature_category: :groups_and_projects do
|
||||
include Features::MembersHelpers
|
||||
include Features::InviteMembersModalHelpers
|
||||
include Spec::Support::Helpers::ModalHelpers
|
||||
|
|
|
|||
|
|
@ -422,7 +422,7 @@ RSpec.describe 'Admin::Users', :with_current_organization, feature_category: :us
|
|||
|
||||
it 'creates user in the selected organization' do
|
||||
within_testid 'organization-section' do
|
||||
select_from_listbox 'New Organization', from: 'Default'
|
||||
select_from_listbox 'New Organization', from: current_organization.name
|
||||
end
|
||||
|
||||
expect { click_button 'Create user' }.to change { organization.users.count }.by(1)
|
||||
|
|
|
|||
|
|
@ -328,9 +328,10 @@ RSpec.describe GroupsFinder, feature_category: :groups_and_projects do
|
|||
context 'with organization' do
|
||||
let_it_be(:organization_user) { create(:organization_user) }
|
||||
let_it_be(:organization) { organization_user.organization }
|
||||
let_it_be(:other_organization) { create(:organization) }
|
||||
let_it_be(:user) { organization_user.user }
|
||||
let_it_be(:public_group) { create(:group, name: 'public-group', organization: organization) }
|
||||
let_it_be(:outside_organization_group) { create(:group) }
|
||||
let_it_be(:outside_organization_group) { create(:group, organization: other_organization) }
|
||||
let_it_be(:private_group) { create(:group, :private, name: 'private-group', organization: organization) }
|
||||
let_it_be(:no_access_group_in_org) { create(:group, :private, name: 'no-access', organization: organization) }
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ RSpec.describe Organizations::GroupsFinder, feature_category: :groups_and_projec
|
|||
let_it_be(:organization_user) { create(:organization_user) }
|
||||
let_it_be(:organization) { organization_user.organization }
|
||||
let_it_be(:user) { organization_user.user }
|
||||
let_it_be(:default_organization) { create(:organization, :default) }
|
||||
let_it_be_with_reload(:public_group) { create(:group, name: 'public-group', organization: organization) }
|
||||
let_it_be_with_reload(:outside_organization_group) { create(:group) }
|
||||
let_it_be_with_reload(:private_group) do
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ describe('Container protection rules project settings', () => {
|
|||
|
||||
const findSettingsBlock = () => wrapper.findComponent(SettingsSection);
|
||||
const findTable = () =>
|
||||
extendedWrapper(wrapper.findByRole('table', { name: /protected containers/i }));
|
||||
extendedWrapper(wrapper.findByRole('table', { name: /protected container repositories/i }));
|
||||
const findTableBody = () => extendedWrapper(findTable().findAllByRole('rowgroup').at(1));
|
||||
const findTableLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findTableRow = (i) => extendedWrapper(findTableBody().findAllByRole('row').at(i));
|
||||
|
|
@ -445,7 +445,7 @@ describe('Container protection rules project settings', () => {
|
|||
const modalId = getBinding(findTableRowButtonDelete(0).element, 'gl-modal');
|
||||
|
||||
expect(findModal().props('modal-id')).toBe(modalId);
|
||||
expect(findModal().props('title')).toBe('Delete container protection rule?');
|
||||
expect(findModal().props('title')).toBe('Delete container repository protection rule?');
|
||||
expect(findModal().text()).toContain(
|
||||
'Users with at least the Developer role for this project will be able to push and delete container images to this repository path.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do
|
||||
let!(:admin) { create(:admin) }
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let!(:admin) { create(:admin, organizations: [organization]) }
|
||||
let!(:admin_project_creation_level) { nil }
|
||||
let!(:admin_group) do
|
||||
create(:group, :private, project_creation_level: admin_project_creation_level)
|
||||
create(:group, :private, project_creation_level: admin_project_creation_level, organization: organization)
|
||||
end
|
||||
|
||||
let!(:user) { create(:user) }
|
||||
let!(:user) { create(:user, organizations: [organization]) }
|
||||
let!(:user_project_creation_level) { nil }
|
||||
let!(:user_group) do
|
||||
create(:group, :private, project_creation_level: user_project_creation_level)
|
||||
create(:group, :private, project_creation_level: user_project_creation_level, organization: organization)
|
||||
end
|
||||
|
||||
let!(:subgroup1) do
|
||||
|
|
@ -39,7 +40,7 @@ RSpec.describe NamespacesHelper, feature_category: :groups_and_projects do
|
|||
|
||||
let!(:project1) { build(:project, namespace: subgroup1) }
|
||||
let!(:project2) do
|
||||
user.create_namespace!(path: user.username, name: user.name) unless user.namespace
|
||||
user.create_namespace!(path: user.username, name: user.name, organization: organization) unless user.namespace
|
||||
build(:project, namespace: user.namespace)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Auth::DpopToken, feature_category: :system_access do
|
||||
include Auth::DpopTokenHelper
|
||||
|
||||
let_it_be(:user, freeze: true) { create(:user) }
|
||||
let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) }
|
||||
|
||||
let(:dpop_proof) { generate_dpop_proof_for(user, alg: alg, typ: typ, kty: kty, fingerprint: fingerprint) }
|
||||
let(:data) { dpop_proof.proof }
|
||||
let(:alg) { Auth::DpopTokenHelper::VALID_ALG }
|
||||
let(:typ) { Auth::DpopTokenHelper::VALID_TYP }
|
||||
let(:kty) { Auth::DpopTokenHelper::VALID_KTY }
|
||||
let(:fingerprint) { nil }
|
||||
|
||||
describe '#validate!' do
|
||||
subject(:validate!) { described_class.new(data: data).validate! }
|
||||
|
||||
context 'when the token is valid' do
|
||||
it 'does not error' do
|
||||
expect { validate! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the token is invalid' do
|
||||
let(:data) { "this_is_obviously_not_a_valid_jwt" }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
||||
/Malformed JWT, unable to decode. Not enough or too many segments/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the token is nil' do
|
||||
let(:data) { nil }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /Malformed JWT, unable to decode. Nil JSON web token/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the typ is invalid' do
|
||||
let(:typ) { 'invalid' }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /Invalid typ value in JWT/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the kid is missing' do
|
||||
let(:fingerprint) { '' }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /No kid in JWT, unable to fetch key/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the alg is unsupported' do
|
||||
let(:alg) { 'RS256' }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /Currently only RSA keys are supported/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the kid is invalid' do
|
||||
let(:fingerprint) { 'invalid' }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /Malformed fingerprint value in kid/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the kid algorithm is unsupported' do
|
||||
let(:fingerprint) { 'SHA512:invalid' }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /Unsupported fingerprint algorithm in kid/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the JWK algorithm is invalid' do
|
||||
let(:kty) { 'ABC' }
|
||||
|
||||
it 'raises DpopValidationError' do
|
||||
expect do
|
||||
validate!
|
||||
end.to raise_error(Gitlab::Auth::DpopValidationError, /JWK algorithm must be RSA/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillIssueMetricsNamespaceId,
|
||||
feature_category: :value_stream_management,
|
||||
schema: 20241203074400 do
|
||||
include_examples 'desired sharding key backfill job' do
|
||||
let(:batch_table) { :issue_metrics }
|
||||
let(:backfill_column) { :namespace_id }
|
||||
let(:backfill_via_table) { :issues }
|
||||
let(:backfill_via_column) { :namespace_id }
|
||||
let(:backfill_via_foreign_key) { :issue_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::DeleteOrphanedGroups, feature_category: :groups_and_projects do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let!(:parent) { namespaces.create!(name: 'Group', type: 'Group', path: 'space1') }
|
||||
let!(:group) { namespaces.create!(name: 'GitLab', type: 'Group', path: 'group1') }
|
||||
|
||||
subject(:background_migration) do
|
||||
described_class.new(
|
||||
start_id: namespaces.without(parent).minimum(:id),
|
||||
end_id: namespaces.maximum(:id),
|
||||
batch_table: :namespaces,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 1,
|
||||
pause_ms: 0,
|
||||
connection: ApplicationRecord.connection
|
||||
).perform
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
before do
|
||||
# Remove constraint so we can create invalid records
|
||||
ApplicationRecord.connection.execute("ALTER TABLE namespaces DROP CONSTRAINT fk_7f813d8c90;")
|
||||
end
|
||||
|
||||
after do
|
||||
# Re-create constraint after the test
|
||||
ApplicationRecord.connection.execute(<<~SQL)
|
||||
ALTER TABLE ONLY namespaces ADD CONSTRAINT fk_7f813d8c90
|
||||
FOREIGN KEY (parent_id) REFERENCES namespaces(id) ON DELETE RESTRICT NOT VALID;
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'enqueues ::GroupDestroyWorker for each group whose parent\'s do not exist' do
|
||||
orphaned_groups = (1..4).map do |i|
|
||||
namespaces.create!(name: "Group #{i}", path: "orphaned_group_#{i}", type: 'Group', parent_id: parent.id)
|
||||
end
|
||||
groups = (1..4).map do |i|
|
||||
namespaces.create!(name: "Group #{i}", path: "group_#{i}", type: 'Group', parent_id: group.id)
|
||||
end
|
||||
parent.destroy!
|
||||
|
||||
orphaned_groups.each do |group|
|
||||
expect(::GroupDestroyWorker).to receive(:perform).with(group.id, ::Users::Internal.admin_bot.id)
|
||||
end
|
||||
|
||||
groups.each do |group|
|
||||
expect(::GroupDestroyWorker).not_to receive(:perform).with(group.id, ::Users::Internal.admin_bot.id)
|
||||
end
|
||||
|
||||
background_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueDeleteOrphanedGroups, migration: :gitlab_main, feature_category: :groups_and_projects do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'does not schedule a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'when executed on .com' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com_except_jh?).and_return(true)
|
||||
end
|
||||
|
||||
describe '#up' do
|
||||
it 'schedules background migration' do
|
||||
migrate!
|
||||
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :namespaces,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#down' do
|
||||
it 'removes scheduled background migrations' do
|
||||
migrate!
|
||||
schema_migrate_down!
|
||||
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillIssueMetricsNamespaceId, feature_category: :value_stream_management do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :issue_metrics,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main_cell,
|
||||
job_arguments: [
|
||||
:namespace_id,
|
||||
:issues,
|
||||
:namespace_id,
|
||||
:issue_id
|
||||
]
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,6 +8,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
include AdminModeHelper
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let!(:group) { create(:group) }
|
||||
|
||||
let(:developer_access) { Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS }
|
||||
|
|
@ -296,7 +297,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
|
||||
it 'does not allow a subgroup to have the same name as an existing subgroup' do
|
||||
sub_group1 = create(:group, parent: group, name: "SG", path: 'api')
|
||||
sub_group2 = described_class.new(parent: group, name: "SG", path: 'api2', organization: sub_group1.organization)
|
||||
sub_group2 = described_class.new(parent: group, name: "SG", path: 'api2', organization: organization)
|
||||
|
||||
expect(sub_group1).to be_valid
|
||||
expect(sub_group2).not_to be_valid
|
||||
|
|
@ -1390,14 +1391,15 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
describe '.in_organization' do
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let_it_be(:groups) { create_pair(:group, organization: organization) }
|
||||
let_it_be(:org1) { create(:organization) }
|
||||
let_it_be(:org2) { create(:organization) }
|
||||
let_it_be(:groups) { create_pair(:group, organization: org1) }
|
||||
|
||||
before do
|
||||
create(:group)
|
||||
create(:group, organization: org2)
|
||||
end
|
||||
|
||||
subject { described_class.in_organization(organization) }
|
||||
subject { described_class.in_organization(org1) }
|
||||
|
||||
it { is_expected.to match_array(groups) }
|
||||
end
|
||||
|
|
@ -2096,8 +2098,7 @@ RSpec.describe Group, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when organization owner' do
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let_it_be(:group) { create(:group, organization: organization) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:org_owner) do
|
||||
create(:organization_owner, organization: organization).user
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ RSpec.describe SystemHook, feature_category: :webhooks do
|
|||
let(:project) { build(:project, namespace: user.namespace) }
|
||||
let(:group) { build(:group) }
|
||||
let(:params) do
|
||||
{ name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: User.random_password }
|
||||
{ name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: User.random_password,
|
||||
organization_id: group.organization_id }
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
let_it_be(:project_sti_name) { Namespaces::ProjectNamespace.sti_name }
|
||||
let_it_be(:user_sti_name) { Namespaces::UserNamespace.sti_name }
|
||||
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let!(:namespace) { create(:namespace, :with_namespace_settings) }
|
||||
let(:gitlab_shell) { Gitlab::Shell.new }
|
||||
let(:repository_storage) { 'default' }
|
||||
|
|
@ -881,8 +882,8 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when made a child group' do
|
||||
let!(:namespace) { create(:group) }
|
||||
let!(:parent_namespace) { create(:group, children: [namespace]) }
|
||||
let!(:parent_namespace) { create(:group) }
|
||||
let!(:namespace) { create(:group, parent: parent_namespace) }
|
||||
|
||||
it 'returns database value' do
|
||||
expect(namespace.traversal_ids).to eq [parent_namespace.id, namespace.id]
|
||||
|
|
@ -890,9 +891,9 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when root_ancestor changes' do
|
||||
let(:old_root) { create(:group) }
|
||||
let(:old_root) { create(:group, organization: organization) }
|
||||
let(:namespace) { create(:group, parent: old_root) }
|
||||
let(:new_root) { create(:group) }
|
||||
let(:new_root) { create(:group, organization: organization) }
|
||||
|
||||
it 'resets root_ancestor memo' do
|
||||
expect(namespace.root_ancestor).to eq old_root
|
||||
|
|
@ -952,8 +953,8 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'traversal_ids on update' do
|
||||
let(:namespace1) { create(:group) }
|
||||
let(:namespace2) { create(:group) }
|
||||
let(:namespace1) { create(:group, organization: organization) }
|
||||
let(:namespace2) { create(:group, organization: organization) }
|
||||
|
||||
context 'when parent_id is changed' do
|
||||
subject { namespace1.update!(parent: namespace2) }
|
||||
|
|
@ -993,7 +994,7 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
describe "after_commit :expire_child_caches" do
|
||||
let(:namespace) { create(:group) }
|
||||
let(:namespace) { create(:group, organization: organization) }
|
||||
|
||||
it "expires the child caches when updated" do
|
||||
child_1 = create(:group, parent: namespace, updated_at: 1.week.ago)
|
||||
|
|
@ -1027,7 +1028,8 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
it "expires on parent changes" do
|
||||
expect(namespace).to receive(:expire_child_caches).once
|
||||
|
||||
namespace.update!(parent: create(:group))
|
||||
new_parent = create(:group, organization: organization)
|
||||
namespace.update!(parent: new_parent)
|
||||
end
|
||||
|
||||
it "doesn't expire on other field changes" do
|
||||
|
|
@ -1883,10 +1885,10 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
|
||||
context 'when a group is transferred into a root group' do
|
||||
context 'when the root group "Share with group lock" is enabled' do
|
||||
let(:root_group) { create(:group, share_with_group_lock: true) }
|
||||
let(:root_group) { create(:group, share_with_group_lock: true, organization: organization) }
|
||||
|
||||
context 'when the subgroup "Share with group lock" is enabled' do
|
||||
let(:subgroup) { create(:group, share_with_group_lock: true) }
|
||||
let(:subgroup) { create(:group, share_with_group_lock: true, organization: organization) }
|
||||
|
||||
it 'the subgroup "Share with group lock" does not change' do
|
||||
subgroup.parent = root_group
|
||||
|
|
@ -1897,7 +1899,7 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when the subgroup "Share with group lock" is disabled' do
|
||||
let(:subgroup) { create(:group) }
|
||||
let(:subgroup) { create(:group, organization: organization) }
|
||||
|
||||
it 'the subgroup "Share with group lock" becomes enabled' do
|
||||
subgroup.parent = root_group
|
||||
|
|
@ -1909,10 +1911,10 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when the root group "Share with group lock" is disabled' do
|
||||
let(:root_group) { create(:group) }
|
||||
let(:root_group) { create(:group, organization: organization) }
|
||||
|
||||
context 'when the subgroup "Share with group lock" is enabled' do
|
||||
let(:subgroup) { create(:group, share_with_group_lock: true) }
|
||||
let(:subgroup) { create(:group, share_with_group_lock: true, organization: organization) }
|
||||
|
||||
it 'the subgroup "Share with group lock" does not change' do
|
||||
subgroup.parent = root_group
|
||||
|
|
@ -1923,7 +1925,7 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'when the subgroup "Share with group lock" is disabled' do
|
||||
let(:subgroup) { create(:group) }
|
||||
let(:subgroup) { create(:group, organization: organization) }
|
||||
|
||||
it 'the subgroup "Share with group lock" does not change' do
|
||||
subgroup.parent = root_group
|
||||
|
|
@ -2094,8 +2096,8 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
|
||||
context 'when a parent is assigned to a group with no previous parent' do
|
||||
it 'returns the path before last save' do
|
||||
group = create(:group, parent: nil)
|
||||
parent = create(:group)
|
||||
group = create(:group, parent: nil, organization: organization)
|
||||
parent = create(:group, organization: organization)
|
||||
|
||||
group.update!(parent: parent)
|
||||
|
||||
|
|
@ -2116,9 +2118,9 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
|
||||
context 'when changing parents' do
|
||||
it 'returns the previous parent full path' do
|
||||
parent = create(:group)
|
||||
parent = create(:group, organization: organization)
|
||||
group = create(:group, parent: parent)
|
||||
new_parent = create(:group)
|
||||
new_parent = create(:group, organization: organization)
|
||||
|
||||
group.update!(parent: new_parent)
|
||||
|
||||
|
|
@ -2544,10 +2546,10 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context 'Namespaces::SyncEvent' do
|
||||
let!(:namespace) { create(:group) }
|
||||
let!(:namespace) { create(:group, organization: organization) }
|
||||
|
||||
let_it_be(:new_namespace1) { create(:group) }
|
||||
let_it_be(:new_namespace2) { create(:group) }
|
||||
let_it_be(:new_namespace1) { create(:group, organization: organization) }
|
||||
let_it_be(:new_namespace2) { create(:group, organization: organization) }
|
||||
|
||||
context 'when creating the namespace' do
|
||||
it 'creates a namespaces_sync_event record' do
|
||||
|
|
@ -2571,8 +2573,9 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
it 'creates a namespaces_sync_event for the parent and all the descendent namespaces' do
|
||||
children_namespaces = create_list(:group, 2, parent_id: namespace.id)
|
||||
grand_children_namespaces = create_list(:group, 2, parent_id: children_namespaces.first.id)
|
||||
children_namespaces = create_list(:group, 2, parent_id: namespace.id, organization: organization)
|
||||
grand_children_namespaces = create_list(:group, 2, parent_id: children_namespaces.first.id, organization:
|
||||
organization)
|
||||
expect(Namespaces::ProcessSyncEventsWorker).to receive(:perform_async).exactly(:once)
|
||||
Namespaces::SyncEvent.delete_all
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Organizations::Organization, type: :model, feature_category: :cell do
|
||||
let_it_be_with_refind(:organization) { create(:organization) }
|
||||
let_it_be(:default_organization) { create(:organization, :default) }
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to have_one(:organization_detail).inverse_of(:organization).autosave(true) }
|
||||
|
|
@ -147,16 +146,6 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
end
|
||||
|
||||
context 'when using scopes' do
|
||||
describe '.without_default' do
|
||||
it 'excludes default organization' do
|
||||
expect(described_class.without_default).not_to include(default_organization)
|
||||
end
|
||||
|
||||
it 'includes other organizations organization' do
|
||||
expect(described_class.without_default).to include(organization)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_namespace_path' do
|
||||
let_it_be(:group) { create(:group, organization: organization) }
|
||||
let(:path) { group.path }
|
||||
|
|
@ -188,41 +177,7 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
organization.users << user
|
||||
end
|
||||
|
||||
it { is_expected.to eq([default_organization, organization, second_organization]) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default_organization' do
|
||||
it 'returns the default organization' do
|
||||
expect(described_class.default_organization).to eq(default_organization)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default?' do
|
||||
context 'when organization is default' do
|
||||
it 'returns true' do
|
||||
expect(described_class.default?(default_organization.id)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization is not default' do
|
||||
it 'returns false' do
|
||||
expect(described_class.default?(organization.id)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#id' do
|
||||
context 'when organization is default' do
|
||||
it 'has id 1' do
|
||||
expect(default_organization.id).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization is not default' do
|
||||
it 'does not have id 1' do
|
||||
expect(organization.id).not_to eq(1)
|
||||
end
|
||||
it { is_expected.to eq([organization, second_organization]) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -278,40 +233,6 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
end
|
||||
end
|
||||
|
||||
describe '#destroy!' do
|
||||
context 'when trying to delete the default organization' do
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
default_organization.destroy!
|
||||
end.to raise_error(ActiveRecord::RecordNotDestroyed, _('Cannot delete the default organization'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to delete a non-default organization' do
|
||||
let(:to_be_removed) { create(:organization) }
|
||||
|
||||
it 'does not raise error' do
|
||||
expect { to_be_removed.destroy! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
context 'when trying to delete the default organization' do
|
||||
it 'returns false' do
|
||||
expect(default_organization.destroy).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to delete a non-default organization' do
|
||||
let(:to_be_removed) { create(:organization) }
|
||||
|
||||
it 'returns true' do
|
||||
expect(to_be_removed.destroy).to eq(to_be_removed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#organization_detail' do
|
||||
it 'ensures organization has organization_detail upon initialization' do
|
||||
expect(organization.organization_detail).to be_present
|
||||
|
|
@ -319,28 +240,6 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
end
|
||||
end
|
||||
|
||||
describe '#default?' do
|
||||
context 'when organization is default' do
|
||||
it 'returns true' do
|
||||
expect(default_organization.default?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization is not default' do
|
||||
it 'returns false' do
|
||||
expect(organization.default?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#name' do
|
||||
context 'when organization is default' do
|
||||
it 'returns Default' do
|
||||
expect(default_organization.name).to eq('Default')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_param' do
|
||||
let_it_be(:organization) { build(:organization, path: 'org_path') }
|
||||
|
||||
|
|
@ -349,20 +248,6 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
end
|
||||
end
|
||||
|
||||
context 'on deleting organizations via SQL' do
|
||||
it 'does not allow to delete default organization' do
|
||||
expect { default_organization.delete }.to raise_error(
|
||||
ActiveRecord::StatementInvalid, /Deletion of the default Organization is not allowed/
|
||||
)
|
||||
end
|
||||
|
||||
it 'allows to delete any other organization' do
|
||||
organization.delete
|
||||
|
||||
expect(described_class.where(id: organization)).not_to exist
|
||||
end
|
||||
end
|
||||
|
||||
describe '#user?' do
|
||||
let_it_be(:user) { create :user }
|
||||
|
||||
|
|
@ -429,6 +314,8 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
end
|
||||
|
||||
describe '.search' do
|
||||
let_it_be(:other_organization) { create(:organization, name: 'Other') }
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
subject { described_class.search(query) }
|
||||
|
|
@ -436,7 +323,7 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
context 'when searching by name' do
|
||||
where(:query, :expected_organizations) do
|
||||
'Organization' | [ref(:organization)]
|
||||
'default' | [ref(:default_organization)]
|
||||
'Other' | [ref(:other_organization)]
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -447,7 +334,7 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
context 'when searching by path' do
|
||||
where(:query, :expected_organizations) do
|
||||
'organization' | [ref(:organization)]
|
||||
'default' | [ref(:default_organization)]
|
||||
'other' | [ref(:other_organization)]
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -455,4 +342,122 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a default organization exists' do
|
||||
let_it_be(:default_organization) { create(:organization, :default) }
|
||||
|
||||
describe '.without_default' do
|
||||
it 'excludes default organization' do
|
||||
expect(described_class.without_default).not_to include(default_organization)
|
||||
end
|
||||
|
||||
it 'includes other organizations organization' do
|
||||
expect(described_class.without_default).to include(organization)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default_organization' do
|
||||
it 'returns the default organization' do
|
||||
expect(described_class.default_organization).to eq(default_organization)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default?' do
|
||||
context 'when organization is default' do
|
||||
it 'returns true' do
|
||||
expect(described_class.default?(default_organization.id)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization is not default' do
|
||||
it 'returns false' do
|
||||
expect(described_class.default?(organization.id)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#id' do
|
||||
context 'when organization is default' do
|
||||
it 'has id 1' do
|
||||
expect(default_organization.id).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization is not default' do
|
||||
it 'does not have id 1' do
|
||||
expect(organization.id).not_to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy!' do
|
||||
context 'when trying to delete the default organization' do
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
default_organization.destroy!
|
||||
end.to raise_error(ActiveRecord::RecordNotDestroyed, _('Cannot delete the default organization'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to delete a non-default organization' do
|
||||
let(:to_be_removed) { create(:organization) }
|
||||
|
||||
it 'does not raise error' do
|
||||
expect { to_be_removed.destroy! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
context 'when trying to delete the default organization' do
|
||||
it 'returns false' do
|
||||
expect(default_organization.destroy).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to delete a non-default organization' do
|
||||
let(:to_be_removed) { create(:organization) }
|
||||
|
||||
it 'returns true' do
|
||||
expect(to_be_removed.destroy).to eq(to_be_removed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'on deleting organizations via SQL' do
|
||||
it 'does not allow to delete default organization' do
|
||||
expect { default_organization.delete }.to raise_error(
|
||||
ActiveRecord::StatementInvalid, /Deletion of the default Organization is not allowed/
|
||||
)
|
||||
end
|
||||
|
||||
it 'allows to delete any other organization' do
|
||||
organization.delete
|
||||
|
||||
expect(described_class.where(id: organization)).not_to exist
|
||||
end
|
||||
end
|
||||
|
||||
describe '#default?' do
|
||||
context 'when organization is default' do
|
||||
it 'returns true' do
|
||||
expect(default_organization.default?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization is not default' do
|
||||
it 'returns false' do
|
||||
expect(organization.default?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#name' do
|
||||
context 'when organization is default' do
|
||||
it 'returns Default' do
|
||||
expect(default_organization.name).to eq('Default')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -257,6 +257,8 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
describe 'organizations association' do
|
||||
let_it_be(:default_organization) { create(:organization, :default) }
|
||||
|
||||
it 'does not create a cross-database query' do
|
||||
user = create(:user)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ RSpec.describe 'getting organization information', feature_category: :cell do
|
|||
|
||||
let_it_be(:organization_owner) { create(:organization_owner) }
|
||||
let_it_be(:organization) { organization_owner.organization }
|
||||
let_it_be(:default_organization) { create(:organization, :default) }
|
||||
let_it_be(:user) { organization_owner.user }
|
||||
let_it_be(:project) { create(:project, organization: organization) { |p| p.add_developer(user) } }
|
||||
let_it_be(:other_group) do
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::GroupMilestones, feature_category: :team_planning do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
let_it_be(:user) { create(:user, organization: organization) }
|
||||
let_it_be_with_refind(:group) { create(:group, :private) }
|
||||
let_it_be(:project) { create(:project, namespace: group) }
|
||||
let_it_be(:project) { create(:project, namespace: group, organization: organization) }
|
||||
let_it_be(:group_member) { create(:group_member, group: group, user: user) }
|
||||
let_it_be(:closed_milestone) do
|
||||
create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone')
|
||||
|
|
@ -31,7 +32,7 @@ RSpec.describe API::GroupMilestones, feature_category: :team_planning do
|
|||
|
||||
describe 'GET /groups/:id/milestones' do
|
||||
context 'for REST only' do
|
||||
let_it_be(:ancestor_group) { create(:group, :private) }
|
||||
let_it_be(:ancestor_group) { create(:group, :private, organization: organization) }
|
||||
let_it_be(:ancestor_group_milestone) { create(:milestone, group: ancestor_group, updated_at: 2.days.ago) }
|
||||
|
||||
before_all do
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ImportBitbucketServer, feature_category: :importers do
|
||||
RSpec.describe API::ImportBitbucketServer, :with_current_organization, feature_category: :importers do
|
||||
let(:base_uri) { "https://test:7990" }
|
||||
let(:user) { create(:user) }
|
||||
let(:user) { create(:user, organizations: [current_organization]) }
|
||||
let(:token) { "asdasd12345" }
|
||||
let(:secret) { "sekrettt" }
|
||||
let(:project_key) { 'TES' }
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ RSpec.describe API::ImportBitbucket, :with_current_organization, feature_categor
|
|||
bitbucket_username: 'foo',
|
||||
bitbucket_app_password: 'bar',
|
||||
repo_path: 'path/to/repo',
|
||||
target_namespace: user.namespace_path,
|
||||
organization_id: current_organization.id
|
||||
target_namespace: user.namespace_path
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
|
|||
include GitlabShellHelpers
|
||||
include APIInternalBaseHelpers
|
||||
|
||||
let_it_be(:user, reload: true) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :repository, :wiki_repo) }
|
||||
let_it_be(:user, reload: true) { create(:user, organizations: [project.organization]) }
|
||||
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
|
||||
let_it_be(:project_snippet) { create(:project_snippet, :repository, author: user, project: project) }
|
||||
let_it_be(:max_pat_access_token_lifetime) do
|
||||
|
|
|
|||
|
|
@ -418,19 +418,20 @@ RSpec.describe API::Invitations, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
it 'does not exceed expected queries count with secondary emails', :request_store, :use_sql_query_cache do
|
||||
create(:email, :confirmed, email: email, user: create(:user))
|
||||
organization = project.organization
|
||||
create(:email, :confirmed, email: email, user: create(:user, organizations: [organization]))
|
||||
|
||||
post invitations_url(project, maintainer), params: { email: email, access_level: Member::DEVELOPER }
|
||||
|
||||
create(:email, :confirmed, email: email2, user: create(:user))
|
||||
create(:email, :confirmed, email: email2, user: create(:user, organizations: [organization]))
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
||||
post invitations_url(project, maintainer), params: { email: email2, access_level: Member::DEVELOPER }
|
||||
end
|
||||
|
||||
create(:email, :confirmed, email: 'email4@example.com', user: create(:user))
|
||||
create(:email, :confirmed, email: 'email6@example.com', user: create(:user))
|
||||
create(:email, :confirmed, email: 'email8@example.com', user: create(:user))
|
||||
create(:email, :confirmed, email: 'email4@example.com', user: create(:user, organizations: [organization]))
|
||||
create(:email, :confirmed, email: 'email6@example.com', user: create(:user, organizations: [organization]))
|
||||
create(:email, :confirmed, email: 'email8@example.com', user: create(:user, organizations: [organization]))
|
||||
|
||||
emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com,' \
|
||||
'EMAIL8@EXamPle.com'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ProjectMilestones, feature_category: :team_planning do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:organization_user) { create(:organization_user) }
|
||||
let_it_be(:organization) { organization_user.organization }
|
||||
let_it_be(:user) { organization_user.user }
|
||||
let_it_be_with_reload(:project) { create(:project, namespace: user.namespace, reporters: user) }
|
||||
let_it_be(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
|
||||
let_it_be(:route) { "/projects/#{project.id}/milestones" }
|
||||
|
|
|
|||
|
|
@ -1298,7 +1298,7 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
|
|||
context "when an oauth token is provided" do
|
||||
before do
|
||||
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
|
||||
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
|
||||
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api", organization_id: project.organization_id)
|
||||
end
|
||||
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Auth
|
||||
module DpopTokenHelper
|
||||
VALID_ALG = 'RS512'
|
||||
VALID_TYP = 'dpop+jwt'
|
||||
VALID_KTY = 'RSA'
|
||||
|
||||
DpopProof = Struct.new(:ssh_public_key, :public_key_in_jwk, :openssl_private_key, :fingerprint, :proof)
|
||||
|
||||
def generate_dpop_proof_for(user, alg: VALID_ALG, typ: VALID_TYP, kty: VALID_KTY, fingerprint: nil)
|
||||
# `ssh_public_key` and `ssh_private_key` are not real secrets. They are a
|
||||
# key pair generated solely for testing.
|
||||
#
|
||||
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC1ZgRbeixURy9/HxU5r5O3Xobnw1bmQx3dyFMkRLMFCy" \
|
||||
"8aVkBvMw6CAc+81miOv+Sg/CZA2DKBAiEz0YwgPlD32o0q/OR5JdFAMH7e5IObm/4wr8dqm4JDE6eZ6f" \
|
||||
"eO+0tFwlrPnV8oiymw4SXeJLJf0n9f7HhH7xJJdWOOQZ2Ku/KMuNdf0aWYhbywUFWN4k5JtwCdEBxZYM" \
|
||||
"NqRYv28i76j3rTm7hBMyor7B2+3lPfeUQpTJkW1UBwUDAYeKZAl6HgZPE9DmcaDSRVViLErp00/iaQSs" \
|
||||
"MxlDSvhaOWARxFgFVNX7iV6S2MNaxah2CrPOyhBA2f2QSdQIQRB/NYjZivROMKaHmaflSB7VVUWyKWPR" \
|
||||
"QfSHbCgbnLpjYyoG7W+mRZ47i9+DeQLsBcIXjTJF/XfcV0iotFxPXiQk5h42+Oi+4YdGWerNl3JY15Dj" \
|
||||
"6QG1Q5UD8J026H4/P/2mPytFTb3iA9jmWEveF6nMC9RVNmqEep51/ZsL1zfenX3vr9Nl0= example@gitlab.com"
|
||||
|
||||
ssh_private_key = "-----BEGIN RSA PRIVATE KEY-----" \
|
||||
"\nMIIG5AIBAAKCAYEAtWYEW3osVEcvfx8VOa+Tt16G58NW5kMd3chTJESzBQsvGlZA" \
|
||||
"\nbzMOggHPvNZojr/koPwmQNgygQIhM9GMID5Q99qNKvzkeSXRQDB+3uSDm5v+MK/H" \
|
||||
"\napuCQxOnmen3jvtLRcJaz51fKIspsOEl3iSyX9J/X+x4R+8SSXVjjkGdirvyjLjX" \
|
||||
"\nX9GlmIW8sFBVjeJOSbcAnRAcWWDDakWL9vIu+o9605u4QTMqK+wdvt5T33lEKUyZ" \
|
||||
"\nFtVAcFAwGHimQJeh4GTxPQ5nGg0kVVYixK6dNP4mkErDMZQ0r4WjlgEcRYBVTV+4" \
|
||||
"\nlektjDWsWodgqzzsoQQNn9kEnUCEEQfzWI2Yr0TjCmh5mn5Uge1VVFsilj0UH0h2" \
|
||||
"\nwoG5y6Y2MqBu1vpkWeO4vfg3kC7AXCF40yRf133FdIqLRcT14kJOYeNvjovuGHRl" \
|
||||
"\nnqzZdyWNeQ4+kBtUOVA/CdNuh+Pz/9pj8rRU294gPY5lhL3hepzAvUVTZqhHqedf" \
|
||||
"\n2bC9c33p1976/TZdAgMBAAECggGBAKkx5o6Mfhx96Udg7qNHqTg36wzxnnRX1duv" \
|
||||
"\nph0GFxR1QhIGsUMHFFke52zzb8L2KYIerm99OF4sZlu28ESC23LTXyjhiRmWtH5y" \
|
||||
"\nvWOZMUhLT+SJkC9XrUBzbLibClVK/wKqLZnI56EhbFmXJ4L0J4xJApWuMuKlkyEB" \
|
||||
"\nZUKi4RcuByZKoli1awfAdibeR253zx3im6fkBw02vA67n7lOW5NJkP8fF9V4q7Uc" \
|
||||
"\nHwKQzRp8OZ9r2r75WYlowfORVUCaLMc0PN9AvduKOY7EAKULfyeFaPWFkY8UV6Ec" \
|
||||
"\n2JQAJ0MkJqxek5Hwi0+st9QGgUbg9z2yDEsOcJuYL1Se8DoagMqjmY66NNGYHe+D" \
|
||||
"\nwL6kw68JYxcQ5fQqyavCECPx8ayzVGbsCSMfaPDscSDO8gyZPnmbjDxyAiPpaPWk" \
|
||||
"\nXnInnKLAQHtVe9zCrgfpdXHqjvcnl/xxNcnKKzsASsdTFFJ2GOcGBatgIdQ0nVkg" \
|
||||
"\nuJbtvHwcK9qt1cux0dZnPNzuqNVBAQKBwQDsQy/JUIYdi8jxImUevetwRvB0tfZA" \
|
||||
"\nnHaIpHwUoX3fzwtVXD/14HbgB9LPG9aQDZ64Gr7HTvCY4tStSTIVoTiNOWc3JbjR" \
|
||||
"\nzN/FLykAseUCfZPI/EJolrgoVFq49C1O5zb2LwBhHWrA9RuQuvGCEHcTmyinm7pG" \
|
||||
"\n6CgqKrZ23FYnn4rj3pb1rv12pcPtYwIV9jksAaYOeGBFJ4GgDnL8XIdJYMdIkIeb" \
|
||||
"\niJAKLAOJEqYmqedVJcU49h6LFF0mXz6/eZUCgcEAxI18WnTjC66ZTTqcitohBVMi" \
|
||||
"\nbc4P1AeP08WspQSkpgnUvOzKIwyCzuwNEeBgmznD6iVFV86+tW2wAY3Ap2wcs9Vk" \
|
||||
"\nOqL+YYPtQDQSHfco7E9bNp7E/30w6t4WJfBg5X7AmCMUkVoyW2ol7aybGjgDWuB6" \
|
||||
"\nMQPczCgqfoLJk47tZu+5baZtqH7E36r7/Nf1rVgiksei4uX5O3wP+eNYTBoKwvlB" \
|
||||
"\n1XDLoidoXXO2tKhCd3j/x2XnLUcT3ha3H/H1P2epAoHAfCr3U1shkSek7K4B7P0t" \
|
||||
"\nXm258+ypxd01Iq0nlQQmjlhXAX6hEszsTONvtG9R/ZVa5DESMNdY9VDJK2U7kEiR" \
|
||||
"\n2w7fIwmNL533wL7/UqEr1XpAEDIbiLIliPSEVY3mvgAgT5P2JBP8xfpLiW3mfU+/" \
|
||||
"\n9SrnW+cpKBjc+wRFrwQvt1VO/mE+f1J/XTrTVNBjCT3FYE5hgltbZRzVMFRHtD/A" \
|
||||
"\nzhyxv35N9rz3zpDBLuoBLnK+5G4cT8px1PBX4FHQPXtdAoHANIkQvOjTKvMvHJpW" \
|
||||
"\n7zIgc1jmMe1LA8RFqDgEzlKwY4TrLNgpqzaT3BTx5V5Q1AyblgECSNcE2F+KFNA7" \
|
||||
"\nt0RJY7Pcx2N7lLr7dha05PeEI62OVsoXI6blpVFZICjg7VZ0yfVOcQ9nuFFl8+IX" \
|
||||
"\nzuk71FV9s44xvQvbV9dDY8JnKAVZTbqXQtsnahU8pzdd/kg5bXwYyIbpmAGwD325" \
|
||||
"\nwxWO3NBczV0JwLzBw4DDTARRR7e6viQ5pzuBTvJJXiuA/sKJAoHBAMu1EauYlaDZ" \
|
||||
"\nHFEzPUuII8nVuD+FrslJFmbArudzEJcq4byLRlLiBhELKTY2QNIYcmHKzD7rAkuW" \
|
||||
"\nLFEEU4FQ8fYJ9JDZjjOJmwmCzpBKRFRraIrcbmUWHvPPqcaG59dRi9BX0+ZVtjS3" \
|
||||
"\nH+ud5zpAjLvpRVVuxKtg38GPo9+F99iUHTqM1zBouhSpTqWQebR+Dln6HjMFqRwL" \
|
||||
"\ny1Y0tD9WVuVwFMEfkENQzOEJxVHwQpsxBRQ5snustS/HmrF5SIZyeg==" \
|
||||
"\n-----END RSA PRIVATE KEY-----"
|
||||
|
||||
user.keys.create!(title: "Sample key #{user.id}", key: ssh_public_key)
|
||||
|
||||
keys = user.keys.signing
|
||||
openssl_private_key = OpenSSL::PKey::RSA.new(ssh_private_key)
|
||||
fingerprint ||= create_fingerprint(keys.find_by(key: ssh_public_key).key)
|
||||
|
||||
public_key_in_jwk = {
|
||||
kty: kty,
|
||||
n: Base64.urlsafe_encode64(openssl_private_key.n.to_s(2), padding: false),
|
||||
e: Base64.urlsafe_encode64(openssl_private_key.e.to_s(2), padding: false)
|
||||
}
|
||||
|
||||
dpop_proof = create_dpop_proof(
|
||||
alg,
|
||||
typ,
|
||||
fingerprint,
|
||||
public_key_in_jwk,
|
||||
openssl_private_key,
|
||||
ath: generate_ath(personal_access_token)
|
||||
)
|
||||
|
||||
DpopProof.new(ssh_public_key, public_key_in_jwk, openssl_private_key, fingerprint, dpop_proof)
|
||||
end
|
||||
|
||||
def generate_ath(pat)
|
||||
Base64.urlsafe_encode64(Digest::SHA256.digest(pat.token), padding: false)
|
||||
end
|
||||
|
||||
def create_dpop_proof(alg, typ, kid, public_key, private_key, htu: '', htm: '', ath: nil, iat: Time.now.to_i, exp: Time.now.to_i + 300) # rubocop:disable Metrics/ParameterLists -- all params needed for edge cases
|
||||
headers = create_headers(alg, typ, public_key, kid)
|
||||
|
||||
jti = SecureRandom.uuid
|
||||
|
||||
payload = create_payload(
|
||||
htu: htu, htm: htm, ath: ath, iat: iat, jti: jti, exp: exp)
|
||||
|
||||
JWT.encode(payload, private_key, alg, headers)
|
||||
end
|
||||
|
||||
def create_headers(alg, typ, public_key, kid)
|
||||
if kid == ""
|
||||
{
|
||||
alg: alg,
|
||||
typ: typ,
|
||||
jwk: public_key
|
||||
}
|
||||
else
|
||||
{
|
||||
alg: alg,
|
||||
typ: typ,
|
||||
jwk: public_key,
|
||||
kid: kid
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def create_payload(
|
||||
htu:, htm:, ath: nil, iat: Time.now.to_i, jti: SecureRandom.uuid,
|
||||
exp: Time.now.to_i + 300)
|
||||
if exp == ""
|
||||
{
|
||||
htu: htu,
|
||||
htm: htm,
|
||||
ath: ath,
|
||||
iat: iat,
|
||||
jti: jti
|
||||
}
|
||||
else
|
||||
{
|
||||
htu: htu,
|
||||
htm: htm,
|
||||
ath: ath,
|
||||
iat: iat,
|
||||
jti: jti,
|
||||
exp: exp
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def create_fingerprint(key)
|
||||
Gitlab::SSHPublicKey.new(key).fingerprint_sha256
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'namespace traversal scopes' do
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
|
||||
# Hierarchy 1
|
||||
let_it_be(:group_1) { create(:group) }
|
||||
let_it_be(:group_1) { create(:group, organization: organization) }
|
||||
let_it_be(:nested_group_1) { create(:group, parent: group_1) }
|
||||
let_it_be(:deep_nested_group_1) { create(:group, parent: nested_group_1) }
|
||||
|
||||
# Hierarchy 2
|
||||
let_it_be(:group_2) { create(:group) }
|
||||
let_it_be(:group_2) { create(:group, organization: organization) }
|
||||
let_it_be(:nested_group_2) { create(:group, parent: group_2) }
|
||||
let_it_be(:deep_nested_group_2) { create(:group, parent: nested_group_2) }
|
||||
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition|
|
|||
end
|
||||
|
||||
describe 'confidential issues' do
|
||||
let!(:public_project) { create(:project, :public) }
|
||||
let!(:public_project) { create(:project, :public, organization: organization) }
|
||||
let!(:context_group) { try(:group) }
|
||||
let!(:milestone) do
|
||||
context_group ? create(:milestone, group: context_group) : create(:milestone, project: public_project)
|
||||
|
|
|
|||
Loading…
Reference in New Issue