Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-03-05 21:11:45 +00:00
parent 9590b41091
commit aa01c59edf
38 changed files with 505 additions and 101 deletions

View File

@ -1 +1 @@
c467a18d6d952c904fa033df2ea5b92aba98e8dc
b54405cd2d14a8a25aa2a5a464008ec9421b7aa6

View File

@ -224,13 +224,13 @@
{"name":"gitlab-dangerfiles","version":"4.8.1","platform":"ruby","checksum":"bbad321c9638152a643d27a20b35ba1e2d8eddcc6bdfc4493d7b96e816ecf300"},
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},
{"name":"gitlab-fog-azure-rm","version":"2.2.0","platform":"ruby","checksum":"31aa7c2170f57874053144e7f716ec9e15f32e71ffbd2c56753dce46e2e78ba9"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"aarch64-linux-gnu","checksum":"bb954cb5dc995f4720edb84caba2112aa54e3d48c18fc1cb552f4ea994a9d518"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"aarch64-linux-musl","checksum":"53cc1b44658860dae476c27ea2e78a36d5e8d39c51003d98267821685665162b"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"arm64-darwin","checksum":"ea05243121cc05ae41fce162be2fd0b86be06b6edc122ac0688cbb0e45341c97"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"ruby","checksum":"82cf8ffc22092cf1e17eb28a1dae8b63a4a141acc1b2864a0c11efc544b53c0f"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-darwin","checksum":"ec6775054481b3e07a97d4be945fe41d043f89dc1fa1d95cdfc6a70b439ea0e4"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-gnu","checksum":"ebcccc617db4f669cd2de900e6d31fae5de67acedeb178d242063e338cb57050"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-musl","checksum":"77cf356f2a2fc496ec17206d68a6a1fe4f4d680bc1aac2206c32ee5393611a15"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"aarch64-linux-gnu","checksum":"67a2e7d2cd1208d22abb9c162e88aa725f0fa31ea7fa8a6f56481370a5d1ef2d"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"aarch64-linux-musl","checksum":"c4b8ce9061238f0cebdaeeceb64bf2e6f1d72f4bfe72b11ac191f45dce12e302"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"arm64-darwin","checksum":"fe71765e04305f5a34647b3338e5101f92547f627a76ddedab35631f98907420"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"ruby","checksum":"960e481c037bbe319ec73792a3fc0fa024a9c11ab16786f24591417a255514a1"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"x86_64-darwin","checksum":"1434de2be23464ac379e30a125213ce00e6c907f47362b50a2d50488bc1e73d2"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"x86_64-linux-gnu","checksum":"59c5a0c577cb9301253bce6c9c0e2b9585110a34c46197cedc16e1142fb2feaf"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"x86_64-linux-musl","checksum":"298757eb48905451285d8c2ef4ecbb1691ed2b30f522998bc65474be340def8a"},
{"name":"gitlab-kas-grpc","version":"17.9.1","platform":"ruby","checksum":"fd480c1669c741ceab8d5f86b7e5e32b71f4f25af8b523725382dae425aaa958"},
{"name":"gitlab-labkit","version":"0.37.0","platform":"ruby","checksum":"d2dd0a60db2149a9a8eebf2975dc23f54ac3ceb01bdba732eb1b26b86dfffa70"},
{"name":"gitlab-license","version":"2.6.0","platform":"ruby","checksum":"2c1f8ae73835640ec77bf758c1d0c9730635043c01cf77902f7976e826d7d016"},

View File

@ -750,7 +750,7 @@ GEM
mime-types
net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8)
gitlab-glfm-markdown (0.0.27)
gitlab-glfm-markdown (0.0.28)
rb_sys (~> 0.9.109)
gitlab-kas-grpc (17.9.1)
grpc (~> 1.0)

View File

@ -224,13 +224,13 @@
{"name":"gitlab-dangerfiles","version":"4.8.1","platform":"ruby","checksum":"bbad321c9638152a643d27a20b35ba1e2d8eddcc6bdfc4493d7b96e816ecf300"},
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},
{"name":"gitlab-fog-azure-rm","version":"2.2.0","platform":"ruby","checksum":"31aa7c2170f57874053144e7f716ec9e15f32e71ffbd2c56753dce46e2e78ba9"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"aarch64-linux-gnu","checksum":"bb954cb5dc995f4720edb84caba2112aa54e3d48c18fc1cb552f4ea994a9d518"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"aarch64-linux-musl","checksum":"53cc1b44658860dae476c27ea2e78a36d5e8d39c51003d98267821685665162b"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"arm64-darwin","checksum":"ea05243121cc05ae41fce162be2fd0b86be06b6edc122ac0688cbb0e45341c97"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"ruby","checksum":"82cf8ffc22092cf1e17eb28a1dae8b63a4a141acc1b2864a0c11efc544b53c0f"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-darwin","checksum":"ec6775054481b3e07a97d4be945fe41d043f89dc1fa1d95cdfc6a70b439ea0e4"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-gnu","checksum":"ebcccc617db4f669cd2de900e6d31fae5de67acedeb178d242063e338cb57050"},
{"name":"gitlab-glfm-markdown","version":"0.0.27","platform":"x86_64-linux-musl","checksum":"77cf356f2a2fc496ec17206d68a6a1fe4f4d680bc1aac2206c32ee5393611a15"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"aarch64-linux-gnu","checksum":"67a2e7d2cd1208d22abb9c162e88aa725f0fa31ea7fa8a6f56481370a5d1ef2d"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"aarch64-linux-musl","checksum":"c4b8ce9061238f0cebdaeeceb64bf2e6f1d72f4bfe72b11ac191f45dce12e302"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"arm64-darwin","checksum":"fe71765e04305f5a34647b3338e5101f92547f627a76ddedab35631f98907420"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"ruby","checksum":"960e481c037bbe319ec73792a3fc0fa024a9c11ab16786f24591417a255514a1"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"x86_64-darwin","checksum":"1434de2be23464ac379e30a125213ce00e6c907f47362b50a2d50488bc1e73d2"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"x86_64-linux-gnu","checksum":"59c5a0c577cb9301253bce6c9c0e2b9585110a34c46197cedc16e1142fb2feaf"},
{"name":"gitlab-glfm-markdown","version":"0.0.28","platform":"x86_64-linux-musl","checksum":"298757eb48905451285d8c2ef4ecbb1691ed2b30f522998bc65474be340def8a"},
{"name":"gitlab-kas-grpc","version":"17.9.1","platform":"ruby","checksum":"fd480c1669c741ceab8d5f86b7e5e32b71f4f25af8b523725382dae425aaa958"},
{"name":"gitlab-labkit","version":"0.37.0","platform":"ruby","checksum":"d2dd0a60db2149a9a8eebf2975dc23f54ac3ceb01bdba732eb1b26b86dfffa70"},
{"name":"gitlab-license","version":"2.6.0","platform":"ruby","checksum":"2c1f8ae73835640ec77bf758c1d0c9730635043c01cf77902f7976e826d7d016"},

View File

@ -762,7 +762,7 @@ GEM
mime-types
net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8)
gitlab-glfm-markdown (0.0.27)
gitlab-glfm-markdown (0.0.28)
rb_sys (~> 0.9.109)
gitlab-kas-grpc (17.9.1)
grpc (~> 1.0)

View File

@ -17,6 +17,7 @@ export default {
i18n: {
deleteLabel: __('Delete'),
editLabel: __('Edit'),
editAriaLabel: (name) => sprintf(__('Edit %{name}'), { name }),
toggleLabel: __('Feature flag status'),
},
components: {
@ -207,7 +208,7 @@ export default {
data-testid="feature-flag-edit-button"
class="gl-flex-grow"
icon="pencil"
:aria-label="$options.i18n.editLabel"
:aria-label="$options.i18n.editAriaLabel(item.name)"
:href="item.edit_path"
/>
</template>
@ -234,7 +235,7 @@ export default {
data-testid="feature-flag-edit-button"
class="gl-flex-grow"
icon="pencil"
:aria-label="$options.i18n.editLabel"
:aria-label="$options.i18n.editAriaLabel(item.name)"
:href="item.edit_path"
/>
</template>

View File

@ -23,6 +23,11 @@ module Ci
scope :for_project, ->(accessed_project) { where(accessed_project: accessed_project) }
scope :preload_origin_project, -> { includes(origin_project: :route) }
attribute :job_token_policies, ::Gitlab::Database::Type::SymbolizedJsonb.new
validates :job_token_policies, json_schema: {
filename: 'ci_job_token_authorizations_policies', detail_errors: true
}
# Record in SafeRequestStore a cross-project access attempt
def self.capture(origin_project:, accessed_project:)
label = origin_project == accessed_project ? 'same-project' : 'cross-project'
@ -43,9 +48,11 @@ module Ci
# completes. We will do that in a middleware. This is because the policy
# rule about job token scope may be satisfied but a subsequent rule in
# the Declarative Policies may block the authorization.
Gitlab::SafeRequestStore.fetch(REQUEST_CACHE_KEY) do
{ accessed_project_id: accessed_project.id, origin_project_id: origin_project.id }
end
add_to_request_store_hash(accessed_project_id: accessed_project.id, origin_project_id: origin_project.id)
end
def self.capture_job_token_policies(policies)
add_to_request_store_hash(policies: policies)
end
# Schedule logging of captured authorizations in a background worker.
@ -57,18 +64,38 @@ module Ci
return unless authorizations
accessed_project_id = authorizations[:accessed_project_id]
origin_project_id = authorizations[:origin_project_id]
return unless accessed_project_id && origin_project_id
Ci::JobToken::LogAuthorizationWorker # rubocop:disable CodeReuse/Worker -- This method is called from a middleware and it's better tested
.perform_in(CAPTURE_DELAY, accessed_project_id, authorizations[:origin_project_id])
.perform_in(CAPTURE_DELAY, accessed_project_id, origin_project_id)
end
def self.log_captures!(accessed_project_id:, origin_project_id:)
upsert({
def self.log_captures!(accessed_project_id:, origin_project_id:, policies: [])
current_time = Time.current
attributes = {
accessed_project_id: accessed_project_id,
origin_project_id: origin_project_id,
last_authorized_at: Time.current
},
unique_by: [:accessed_project_id, :origin_project_id],
on_duplicate: :update)
last_authorized_at: current_time
}
transaction do
if policies.present?
auth_log = lock.find_by(
accessed_project_id: accessed_project_id,
origin_project_id: origin_project_id
)
policies = policies.index_with(current_time)
attributes[:job_token_policies] = auth_log ? auth_log.job_token_policies.merge(policies) : policies
end
upsert(attributes, unique_by: [:accessed_project_id, :origin_project_id], on_duplicate: :update)
end
end
def self.add_to_request_store_hash(hash)
new_hash = captured_authorizations.present? ? captured_authorizations.merge(hash) : hash
Gitlab::SafeRequestStore[REQUEST_CACHE_KEY] = new_hash
end
def self.captured_authorizations

View File

@ -36,9 +36,14 @@ module Ci
def policies_allowed?(accessed_project, policies)
return true if self_referential?(accessed_project)
return true unless accessed_project.ci_inbound_job_token_scope_enabled?
return false unless inbound_accessible?(accessed_project)
# We capture policies even if the inbound scopes are disabled or the feature flag is disabled
Ci::JobToken::Authorization.capture_job_token_policies(policies) if policies.present?
return true unless accessed_project.ci_inbound_job_token_scope_enabled?
return true unless Feature.enabled?(:add_policies_to_ci_job_token, accessed_project)
policies_allowed_for_accessed_project?(accessed_project, policies)
end

View File

@ -0,0 +1,12 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Policies for CI job token authorization logs, captured with their last authorized time",
"type": "object",
"propertyNames": {
"$ref": "./ci_job_token_policies.json#/items"
},
"additionalProperties": {
"type": "string",
"format": "date-time"
}
}

View File

@ -12,10 +12,11 @@ module Ci
idempotent!
deduplicate :until_executed, including_scheduled: true, if_deduplicated: :reschedule_once
def perform(accessed_project_id, origin_project_id)
def perform(accessed_project_id, origin_project_id, policies = [])
Ci::JobToken::Authorization.log_captures!(
accessed_project_id: accessed_project_id,
origin_project_id: origin_project_id
origin_project_id: origin_project_id,
policies: policies.map(&:to_sym)
)
end
end

View File

@ -0,0 +1,8 @@
---
migration_job_name: BackfillOnboardingStatusSetupForCompany
description: Moves data from user_preferences.setup_for_company to the new setup_for_company field in user_details.onboarding_status
feature_category: onboarding
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180602
milestone: '17.10'
queued_migration_version: 20250228155146
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddPoliciesColumnToCiJobTokenAuthorizations < Gitlab::Database::Migration[2.2]
milestone '17.10'
def change
add_column :ci_job_token_authorizations, :job_token_policies, :jsonb, default: {}, null: false
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class QueueBackfillOnboardingStatusSetupForCompany < Gitlab::Database::Migration[2.2]
milestone '17.10'
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "BackfillOnboardingStatusSetupForCompany"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:user_details,
:user_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :user_details, :user_id, [])
end
end

View File

@ -0,0 +1 @@
885e811ba46d6fde0e865d44e89241ce7f777ec65887e59ce7507bf830de333c

View File

@ -0,0 +1 @@
9ce991961668a1e306c2348cf338a353966c35f9c2f11b105380f0a51df7f4d4

View File

@ -10554,7 +10554,8 @@ CREATE TABLE ci_job_token_authorizations (
id bigint NOT NULL,
accessed_project_id bigint NOT NULL,
origin_project_id bigint NOT NULL,
last_authorized_at timestamp with time zone NOT NULL
last_authorized_at timestamp with time zone NOT NULL,
job_token_policies jsonb DEFAULT '{}'::jsonb NOT NULL
);
CREATE SEQUENCE ci_job_token_authorizations_id_seq

View File

@ -17,11 +17,9 @@ this provider also allows Crowd authentication for Git-over-https requests.
## Configure a new Crowd application
1. Choose 'Applications' in the top menu, then 'Add application'.
1. Go through the 'Add application' steps, entering the appropriate details.
The screenshot below shows an example configuration.
![Final confirmation page in Crowd for application configuration](img/crowd_application_v9_0.png)
1. On the top menu, select **Applications > Add application**.
1. Go through the **Add application** steps, entering the appropriate details.
1. When complete, select **Add application**.
## Configure GitLab

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -499,24 +499,37 @@ Now that we have mitmproxy and Docker running, we can attempt to sign in and
push a container image. You may need to run as root to do this. For example:
```shell
docker login s3-testing.myregistry.com:5050
docker push s3-testing.myregistry.com:5050/root/docker-test/docker-image
docker login example.s3.amazonaws.com:5050
docker push example.s3.amazonaws.com:5050/root/docker-test/docker-image
```
In the example above, we see the following trace on the mitmproxy window:
![mitmproxy output from Docker](img/mitmproxy_docker_v8_11.png)
```plaintext
PUT https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/uploads/(UUID)/(QUERYSTRING)
← 201 text/plain [no content] 661ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
← 307 application/octet-stream [no content] 93ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
← 307 application/octet-stream [no content] 101ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
← 307 application/octet-stream [no content] 87ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
← 403 application/xml [no content] 80ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
← 403 application/xml [no content] 62ms
```
The above image shows:
This output shows:
- The initial PUT requests went through fine with a 201 status code.
- The 201 redirected the client to the S3 bucket.
- The HEAD request to the AWS bucket reported a 403 Unauthorized.
- The initial PUT requests went through fine with a `201` status code.
- The `201` redirected the client to the Amazon S3 bucket.
- The HEAD request to the AWS bucket reported a `403 Unauthorized`.
What does this mean? This strongly suggests that the S3 user does not have the right
[permissions to perform a HEAD request](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html).
The solution: check the [IAM permissions again](https://distribution.github.io/distribution/storage-drivers/s3/).
Once the right permissions were set, the error goes away.
After the right permissions were set, the error went away.
## Missing `gitlab-registry.key` prevents container repository deletion

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

View File

@ -2,16 +2,14 @@
stage: none
group: unassigned
info: 'See the Technical Writers assigned to Development Guidelines: https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-development-guidelines'
description: Developer documentation about GitLab feature flags.
title: Feature flags in the development of GitLab
---
{{< alert type="note" >}}
This document explains how to contribute to the development and operations of the GitLab product.
If you want to use feature flags to show and hide functionality in your own applications,
view [this feature flags information](../../operations/feature_flags.md) instead.
{{< /alert >}}
This page explains how developers contribute to the development and operations of the GitLab product
through feature flags. To create custom feature flags to show and hide features in your own applications,
see [Create a feature flag](../../operations/feature_flags.md#create-a-feature-flag).
A [complete list of feature flags](../../user/feature_flags.md) in GitLab is also available.
{{< alert type="warning" >}}

View File

@ -2,6 +2,7 @@
stage: Deploy
group: Environments
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: Create and maintain a custom feature flag for your GitLab application.
title: Feature flags
---
@ -17,6 +18,8 @@ You can toggle a feature on and off to subsets of users, helping you achieve Con
Feature flags help reduce risk, allowing you to do controlled testing, and separate feature
delivery from customer launch.
A [complete list of feature flags](../user/feature_flags.md) in GitLab is also available.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an example of feature flags in action, see [Eliminating risk with feature flags](https://www.youtube.com/watch?v=U9WqoK9froI).
<!-- Video published on 2024-02-01 -->

View File

@ -115,7 +115,7 @@ You can enforce 2FA for all users in a group or subgroup.
Prerequisites:
- You must have the Maintainer or Owner role for the group.
- You must have the Owner role for the group.
To enforce 2FA for a group:

View File

@ -246,9 +246,17 @@ The following operational features are not available:
### Feature flags
[Feature flags](../../administration/feature_flags.md) are not available for GitLab Dedicated.
GitLab uses [feature flags](../../user/feature_flags.md) to support the development and rollout of new or experimental features.
In GitLab Dedicated:
Feature flags support the development and rollout of new or experimental features on GitLab.com. Features behind feature flags are not considered ready for production use, are experimental and therefore unsafe for GitLab Dedicated. Stability and SLAs may be affected by changing default settings.
- Features using feature flags that are **enabled by default** are available.
- Features using feature flags that are **disabled by default** are not available and cannot be enabled by administrators.
Features behind flags that are disabled by default are not ready for production use and therefore unsafe for GitLab Dedicated.
When a feature becomes generally available and the flag is enabled or removed, the feature becomes available in
GitLab Dedicated in the same GitLab version. GitLab Dedicated follows its own
[release schedule](../gitlab_dedicated/maintenance.md) for version deployments.
## Migrate to GitLab Dedicated

View File

@ -2,14 +2,17 @@
stage: none
group: unassigned
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: Complete list of flags.
description: Complete list of all feature flags in GitLab.
title: All feature flags in GitLab
layout: feature_flags
---
The following feature flags exist in GitLab. These flags determine the availability of each feature.
GitLab provides feature flags to turn specific features on or off.
This page contains a list of all feature flags provided by GitLab. In GitLab Self-Managed,
GitLab administrators can [change the state of these feature flags](../administration/feature_flags.md).
For self-managed instances, [GitLab administrators can change the state of the flag](../administration/feature_flags.md).
For help developing custom feature flags, see
[Create a feature flag](../operations/feature_flags.md#create-a-feature-flag).
<!-- markdownlint-disable MD044 -->
<!-- MD044/proper-names test disabled after this line to make page compatible with markdownlint-cli 0.29.0. -->

View File

@ -12,9 +12,12 @@ title: Members of a project
{{< /details >}}
Members are the users and groups who have access to your project.
Members are the users and groups who have access to your project. Members can be added directly to your
project or inherit access through groups.
Each member gets a role, which determines what they can do in the project.
Each member has a role that determines what they can do in the project. Project members with the
appropriate role can add users to projects, remove users from projects, and manage access requests to
control access to project resources.
## Membership types

View File

@ -221,19 +221,37 @@ After you [specify a user cap for the group](../../group/manage.md#specify-a-use
## Sharing groups
When you want a group to have access to your group,
you can invite another [group](../../group/_index.md) to the group.
The invited group's direct members get access to the group.
When you want another group's members to have access to your group,
you can invite the [group](../../group/_index.md) to your group.
The group's direct members get access to the group, which becomes a **shared group**.
Only direct members of the invited group get access to the shared group, not inherited or subgroup members. To grant subgroup members access, invite the subgroup directly.
The following table provides an overview of the group members that get access to a shared group:
| Group member source | Access to shared group |
|--------------------------------------------------------------|------------------------|
| Direct member of the group that is invited | {{< icon name="check-circle" >}} Yes |
| Inherited member of the group that is invited | {{< icon name="dotted-circle" >}} No |
| Member of a subgroup, but not of the group that is invited | {{< icon name="dotted-circle" >}} No |
### Member access and roles
Each member's access is based on the:
- Role they're assigned in the invited group.
- Maximum role you choose when you invite the group.
If a group member has a role for the invited group with fewer permissions than the maximum role for your group,
the member keeps the permissions of their invited group role.
The least access is granted between the access in the invited group and the access in the inviting group.
After you invite a group to your group:
- The **Groups** tab of the group's **Members** page lists the invited group. This list includes both public and private groups.
- The **Members** tab of the group's **Members** page lists the members of the invited group.
- All direct members of the invited group have access to the inviting group.
The least access is granted between the access in the invited group and the access in the inviting group.
- Inherited members of the invited group do not gain access to the inviting group.
- On the group's usage quota page, direct members of the invited group who have the **Group Invite** badge
next to their profile count towards the billable members of the inviting group.
- On the group's overview page, groups that this group has been shared with are listed on the **Shared groups** tab.
- On the group's **Members** page, the invited group is listed on the **Groups** tab. This list includes both public and private groups.
- On the group's **Members** page, the members of the invited group are listed on the **Members** tab.
- On the group's usage quota page, direct members of the invited group who have the **Group Invite** badge next to their profile count towards the billable members of the inviting group.
[In GitLab 16.11 and later](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144638),
the invited group's name and membership source are masked on the **Members** and the **Groups** tabs,

View File

@ -134,8 +134,6 @@ Whether it's a user or a project website, the DNS record
should point to your Pages domain (`namespace.gitlab.io`),
without any path.
![DNS `CNAME` record pointing to GitLab.com project](img/dns_cname_record_example_v9_0.png)
##### For both root and subdomains
There are a few cases where you need to point both the subdomain and root

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1036,7 +1036,6 @@ module API
def job_token_policies_authorized?(project)
return true unless current_user&.from_ci_job_token?
return true unless Feature.enabled?(:add_policies_to_ci_job_token, project)
return true if skip_job_token_policies?
return true if publicly_accessible_feature?(project)

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillOnboardingStatusSetupForCompany < BatchedMigrationJob
operation_name :backfill_onboarding_status_setup_for_company
feature_category :onboarding
def perform
each_sub_batch do |sub_batch|
UserDetail
.where(user_id: sub_batch.select(:user_id))
.where("(user_details.onboarding_status->'setup_for_company') IS NULL")
.joins("INNER JOIN user_preferences ON user_details.user_id = user_preferences.user_id")
.where.not(user_preferences: { setup_for_company: nil })
.update_all(
"onboarding_status = jsonb_set(
COALESCE(onboarding_status, '{}'::jsonb),
'{setup_for_company}',
(SELECT to_jsonb(user_preferences.setup_for_company)
FROM user_preferences
WHERE user_details.user_id = user_preferences.user_id)
)"
)
end
end
end
end
end

View File

@ -126,9 +126,10 @@ describe('Feature flag table', () => {
expect(wrapper.findByTestId('flags-table-action-buttons').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-delete-button').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-edit-button').exists()).toBe(true);
expect(wrapper.findByTestId('feature-flag-edit-button').attributes('href')).toEqual(
'edit/path',
);
expect(wrapper.findByTestId('feature-flag-edit-button').attributes()).toMatchObject({
'aria-label': 'Edit flag name',
href: 'edit/path',
});
});
});

View File

@ -327,14 +327,6 @@ RSpec.describe API::Helpers, feature_category: :shared do
it { is_expected.to eq project }
end
context 'when the `add_policies_to_ci_job_token` feature flag is disabled' do
before do
stub_feature_flags(add_policies_to_ci_job_token: false)
end
it { is_expected.to eq project }
end
context 'when the project feature is publicly accessible' do
let_it_be(:project) { create(:project, :public, :builds_enabled) }

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillOnboardingStatusSetupForCompany, feature_category: :onboarding do
let(:users) { table(:users) }
let(:user_details) { table(:user_details) }
let(:user_preferences) { table(:user_preferences) }
let(:first_user) { users.create!(projects_limit: 0, email: 'user1@example.com') }
let!(:user_preference) do
user_preferences.create!(
user_id: first_user.id,
setup_for_company: true
)
end
let!(:user_detail) do
user_details.create!(
user_id: first_user.id,
onboarding_status: { 'setup_for_company' => true }
)
end
let(:second_user) { users.create!(projects_limit: 0, email: 'user2@example.com') }
let!(:user_preference_to_change) do
user_preferences.create!(
user_id: second_user.id,
setup_for_company: false
)
end
let!(:user_detail_to_change) do
user_details.create!(
user_id: second_user.id,
onboarding_status: {}
)
end
let(:last_user) { users.create!(projects_limit: 0, email: 'user3@example.com') }
let!(:user_preference_not_to_be_set) do
user_preferences.create!(
user_id: last_user.id,
setup_for_company: nil
)
end
let!(:user_detail_not_to_be_set) do
user_details.create!(
user_id: last_user.id,
onboarding_status: {}
)
end
subject(:migration) do
described_class.new(
start_id: user_detail.user_id,
end_id: user_detail_not_to_be_set.user_id,
batch_table: :user_details,
batch_column: :user_id,
sub_batch_size: 100,
pause_ms: 0,
connection: ApplicationRecord.connection
)
end
describe '#perform' do
it 'updates the correct data' do
migration.perform
expect(user_detail.reload.onboarding_status).to eq({ 'setup_for_company' => true })
expect(user_detail_to_change.reload.onboarding_status).to eq({ 'setup_for_company' => false })
expect(user_detail_not_to_be_set.reload.onboarding_status).to eq({})
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillOnboardingStatusSetupForCompany, migration: :gitlab_main, feature_category: :onboarding 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: :user_details,
column_name: :user_id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -6,12 +6,68 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen
let_it_be(:origin_project) { create(:project) }
let_it_be(:accessed_project) { create(:project) }
let_it_be(:another_project) { create(:project) }
let_it_be(:policies) { [:read_jobs, :admin_environments] }
describe 'associations' do
it { is_expected.to belong_to(:origin_project).class_name('Project') }
it { is_expected.to belong_to(:accessed_project).class_name('Project') }
end
describe 'validating job_token_policies' do
subject(:auth_log) { described_class.new(job_token_policies: policies) }
let(:valid_policies) do
schema = 'app/validators/json_schemas/ci_job_token_policies.json'
Gitlab::Json.parse(File.read(schema)).dig('items', 'enum')
end
context 'when not assigning any policies' do
let(:policies) { {} }
it { is_expected.to be_valid }
end
context 'when assigning all valid policies with valid values' do
let(:policies) { valid_policies.index_with(Time.current) }
it { is_expected.to be_valid }
end
context 'when assigning an invalid policy with a valid value' do
let(:policies) { { invalid_policy: Time.current } }
it { is_expected.to be_invalid }
it 'contains a descriptive error' do
auth_log.valid?
expect(auth_log.errors[:job_token_policies]).to include("value at root is not one of: #{valid_policies}")
end
end
context 'when assigning a valid policy with an invalid value' do
let(:policies) { { read_jobs: true } }
it { is_expected.to be_invalid }
it 'contains a descriptive error' do
auth_log.valid?
expect(auth_log.errors[:job_token_policies]).to include('value at `/read_jobs` is not a string')
end
end
context 'when assigning a valid policy with a string that is not a datetime' do
let(:policies) { { read_jobs: 'not a date time string' } }
it { is_expected.to be_invalid }
it 'contains a descriptive error' do
auth_log.valid?
expect(auth_log.errors[:job_token_policies])
.to include('value at `/read_jobs` does not match format: date-time')
end
end
end
describe '.capture', :request_store do
subject(:capture) do
described_class.capture(origin_project: origin_project, accessed_project: accessed_project)
@ -50,6 +106,41 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen
end
end
describe '.capture_job_token_policies', :request_store do
subject(:capture_job_token_policies) { described_class.capture_job_token_policies(policies) }
let(:policies) { [:read_environments, :read_jobs] }
it 'captures the policies in the RequestStore' do
capture_job_token_policies
expect(described_class.captured_authorizations).to eq(policies: policies)
end
end
describe '.add_to_request_store_hash', :request_store do
subject(:captured_authorizations) { described_class.captured_authorizations }
let(:old_hash) { { old_value: 1 } }
let(:new_hash) { { new_value: 2 } }
context 'when no values have been captured in the RequestStore yet' do
before do
described_class.add_to_request_store_hash(old_hash)
end
it { is_expected.to eq(old_hash) }
end
context 'when previous values have been captured in the RequestStore' do
before do
described_class.add_to_request_store_hash(old_hash)
described_class.add_to_request_store_hash(new_hash)
end
it { is_expected.to eq(old_hash.merge(new_hash)) }
end
end
describe '.log_captures_async', :request_store do
subject(:log_captures_async) do
described_class.log_captures_async
@ -65,9 +156,9 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen
context 'when authorizations have been captured during the request' do
before do
described_class.capture(
origin_project: origin_project,
accessed_project: accessed_project)
described_class.add_to_request_store_hash(
origin_project_id: origin_project&.id,
accessed_project_id: accessed_project&.id)
end
context 'when authorization is cross project' do
@ -79,8 +170,14 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen
end
end
context 'when authorization is self-referential' do
let(:accessed_project) { origin_project }
context 'when the origin project is missing' do
let(:origin_project) { nil }
it_behaves_like 'does not log the authorization'
end
context 'when the accessed project is missing' do
let(:accessed_project) { nil }
it_behaves_like 'does not log the authorization'
end
@ -93,16 +190,23 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen
describe '.log_captures!' do
subject(:log_captures) do
described_class.log_captures!(origin_project_id: origin_project.id, accessed_project_id: accessed_project.id)
described_class.log_captures!(
origin_project_id: origin_project.id,
accessed_project_id: accessed_project.id,
policies: policies)
end
context 'when authorization does not exist in the database' do
it 'creates a new authorization' do
it 'creates a new authorization', :freeze_time do
expect { log_captures }.to change { described_class.count }.by(1)
expect(described_class.last).to have_attributes(
origin_project: origin_project,
accessed_project: accessed_project)
accessed_project: accessed_project,
job_token_policies: {
read_jobs: Time.current,
admin_environments: Time.current
})
end
end
@ -111,12 +215,15 @@ RSpec.describe Ci::JobToken::Authorization, feature_category: :secrets_managemen
create(:ci_job_token_authorization,
origin_project: origin_project,
accessed_project: accessed_project,
last_authorized_at: 1.day.ago)
last_authorized_at: 1.day.ago,
job_token_policies: { read_jobs: 1.day.ago })
end
it 'updates the timestamp instead of creating a new record' do
it 'updates the policies and the last_authorized_at timestamp instead of creating a new record', :freeze_time do
expect { log_captures }
.to change { existing_authorization.reload.last_authorized_at }
.to change { existing_authorization.reload.last_authorized_at }.to(Time.current)
.and change { existing_authorization.job_token_policies.keys }.to(policies)
.and change { existing_authorization.job_token_policies[:read_jobs] }.to(Time.current)
.and not_change { described_class.count }
end
end

View File

@ -230,17 +230,35 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
end
describe '#policies_allowed?' do
subject { scope.policies_allowed?(accessed_project, policies) }
subject(:policies_allowed?) { scope.policies_allowed?(accessed_project, policies) }
let(:scope) { described_class.new(target_project) }
let_it_be(:target_project) { create(:project) }
let_it_be(:allowed_policy) { ::Ci::JobToken::Policies::POLICIES.first }
let(:accessed_project) { create_inbound_accessible_project_for_policies(target_project, [allowed_policy]) }
shared_examples 'capturing job token policies' do
it 'captures job token policies' do
expect(::Ci::JobToken::Authorization).to receive(:capture_job_token_policies).with(policies)
policies_allowed?
end
end
shared_examples 'not capturing job token policies' do
it 'does not capture job token policies' do
expect(::Ci::JobToken::Authorization).not_to receive(:capture_job_token_policies)
policies_allowed?
end
end
context 'when no policies are given' do
let_it_be(:policies) { [] }
it { is_expected.to be(false) }
it_behaves_like 'not capturing job token policies'
end
context 'when the policies are defined in the scope' do
@ -248,6 +266,8 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
it { is_expected.to be(true) }
it_behaves_like 'capturing job token policies'
context 'when an admin policy is defined in the scope' do
let_it_be(:allowed_policy) { 'admin_jobs' }
let_it_be(:policies) { [:admin_jobs, :read_jobs] }
@ -261,18 +281,24 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
let(:accessed_project) { create(:project) }
it { is_expected.to be(false) }
it_behaves_like 'not capturing job token policies'
end
end
context 'when the policy are not defined in the scope' do
context 'when the policies are not defined in the scope' do
let_it_be(:policies) { [:not_allowed_policy] }
it { is_expected.to be(false) }
it_behaves_like 'capturing job token policies'
context 'when the accessed project is the target project' do
let(:accessed_project) { target_project }
it { is_expected.to be(true) }
it_behaves_like 'not capturing job token policies'
end
context 'when the accessed project does not have ci_inbound_job_token_scope_enabled set to true' do
@ -281,12 +307,26 @@ RSpec.describe Ci::JobToken::Scope, feature_category: :continuous_integration, f
end
it { is_expected.to be(true) }
it_behaves_like 'capturing job token policies'
end
context 'when the `add_policies_to_ci_job_token` flag is disabled' do
before do
stub_feature_flags(add_policies_to_ci_job_token: false)
end
it { is_expected.to be(true) }
it_behaves_like 'capturing job token policies'
end
context 'when the accessed project has not enabled fine grained permissions' do
let(:accessed_project) { create_inbound_accessible_project(target_project) }
it { is_expected.to be(true) }
it_behaves_like 'capturing job token policies'
end
end
end

View File

@ -7,15 +7,15 @@ RSpec.describe Ci::JobToken::LogAuthorizationWorker, feature_category: :secrets_
let_it_be(:origin_project) { build_stubbed(:project) }
let_it_be(:accessed_project) { build_stubbed(:project) }
subject(:perform) { described_class.new.perform(accessed_project.id, origin_project.id) }
subject(:perform) { described_class.new.perform(*job_args) }
it_behaves_like 'an idempotent worker' do
let(:job_args) { [accessed_project.id, origin_project.id] }
let(:job_args) { [accessed_project.id, origin_project.id, ['read_jobs']] }
it 'calls Ci::JobToken::Authorization#log_captures!' do
expect(Ci::JobToken::Authorization)
.to receive(:log_captures!)
.with(accessed_project_id: accessed_project.id, origin_project_id: origin_project.id)
.with(accessed_project_id: accessed_project.id, origin_project_id: origin_project.id, policies: [:read_jobs])
.and_call_original
perform