Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-04 03:37:55 +00:00
parent 33ce7f500d
commit ba12e5053f
76 changed files with 1439 additions and 626 deletions

View File

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

View File

@ -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,
},
]"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,3 +18,4 @@ desired_sharding_key:
sharding_key: namespace_id
belongs_to: issue
table_size: small
desired_sharding_key_migration_job_name: BackfillIssueMetricsNamespaceId

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
ce1cea13a912cd65923aea1757bc0c073e425d20ac6e7bae620f66cee518bdb7

View File

@ -0,0 +1 @@
f8cb5aba90abb415616c2ed31aeedc1f4645acb43cdc972962970d62828c1a3a

View File

@ -0,0 +1 @@
86eeed8757efb216ff2ccab4cc3de42ee38a021621cd07671fb17852c6e437be

View File

@ -0,0 +1 @@
cd6cb8ac7ac93a2ebbeabff061015cc215ed72369135c1d0098ca4bba852c62a

View File

@ -0,0 +1 @@
dbf4c9348eba0b9e8ba2b717edb1b5afe6e36d7b3587dbed75b384859297d40a

View File

@ -0,0 +1 @@
0906a3ed9bdc751059782071ecb1f03140da8662e4a2fedf38f2d6067e7413d4

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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