Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									5699348c82
								
							
						
					
					
						commit
						6031d32432
					
				|  | @ -96,7 +96,7 @@ class Ability | |||
| 
 | ||||
|     # Hook call right before ability check. | ||||
|     def before_check(policy, ability, user, subject, opts) | ||||
|       # See Support::AbilityCheck. | ||||
|       # See Support::AbilityCheck and Support::PermissionsCheck. | ||||
|     end | ||||
| 
 | ||||
|     def policy_for(user, subject = :global) | ||||
|  |  | |||
|  | @ -156,6 +156,9 @@ class MergeRequest < ApplicationRecord | |||
|   # when creating new merge request | ||||
|   attr_accessor :can_be_created, :compare_commits, :diff_options, :compare | ||||
| 
 | ||||
|   # Flag to skip triggering mergeRequestMergeStatusUpdated GraphQL subscription. | ||||
|   attr_accessor :skip_merge_status_trigger | ||||
| 
 | ||||
|   participant :reviewers | ||||
| 
 | ||||
|   # Keep states definition to be evaluated before the state_machine block to avoid spec failures. | ||||
|  | @ -252,6 +255,8 @@ class MergeRequest < ApplicationRecord | |||
|     end | ||||
| 
 | ||||
|     after_transition any => [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking, :can_be_merged, :cannot_be_merged] do |merge_request, transition| | ||||
|       next if merge_request.skip_merge_status_trigger | ||||
| 
 | ||||
|       merge_request.run_after_commit do | ||||
|         GraphqlTriggers.merge_request_merge_status_updated(merge_request) | ||||
|       end | ||||
|  |  | |||
|  | @ -127,16 +127,23 @@ module MergeRequests | |||
| 
 | ||||
|       merge_requests_array = merge_requests.to_a + merge_requests_from_forks.to_a | ||||
|       filter_merge_requests(merge_requests_array).each do |merge_request| | ||||
|         skip_merge_status_trigger = true | ||||
| 
 | ||||
|         if branch_and_project_match?(merge_request) || @push.force_push? | ||||
|           merge_request.reload_diff(current_user) | ||||
|           # Clear existing merge error if the push were directed at the | ||||
|           # source branch. Clearing the error when the target branch | ||||
|           # changes will hide the error from the user. | ||||
|           merge_request.merge_error = nil | ||||
| 
 | ||||
|           # Don't skip trigger since we to update the MR's merge status in real-time | ||||
|           # when the push if for the MR's source branch and project. | ||||
|           skip_merge_status_trigger = false | ||||
|         elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids) | ||||
|           merge_request.reload_diff(current_user) | ||||
|         end | ||||
| 
 | ||||
|         merge_request.skip_merge_status_trigger = skip_merge_status_trigger | ||||
|         merge_request.mark_as_unchecked | ||||
|       end | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,9 @@ | |||
| - group = local_assigns.fetch(:group) | ||||
| 
 | ||||
| %li.group-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'group_row_content' } } | ||||
|   .avatar-container.rect-avatar.s40.gl-flex-shrink-0 | ||||
|     = group_icon(group, class: "avatar s40") | ||||
|   = render Pajamas::AvatarComponent.new(group, size: 32, alt: '') | ||||
| 
 | ||||
|   .gl-min-w-0.gl-flex-grow-1 | ||||
|   .gl-min-w-0.gl-flex-grow-1.gl-ml-3 | ||||
|     .title | ||||
|       = link_to [:admin, group], class: 'group-name', data: { qa_selector: 'group_name_link' } do | ||||
|         = group.full_name | ||||
|  |  | |||
|  | @ -20,8 +20,7 @@ | |||
|       - c.body do | ||||
|         %ul.content-list.content-list-items-padding | ||||
|           %li | ||||
|             .avatar-container.rect-avatar.s60 | ||||
|               = group_icon(@group, class: "avatar s60") | ||||
|             = render Pajamas::AvatarComponent.new(@group, size: 64, alt: '') | ||||
|           %li | ||||
|             %span.light= _('Name:') | ||||
|             %strong | ||||
|  |  | |||
|  | @ -3,9 +3,8 @@ | |||
|     %ul.content-list | ||||
|       - @projects.each do |project| | ||||
|         %li.project-row.gl-align-items-center{ class: 'gl-display-flex!' } | ||||
|           .avatar-container.rect-avatar.s40.gl-flex-shrink-0 | ||||
|             = project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40) | ||||
|           .gl-min-w-0.gl-flex-grow-1 | ||||
|           = render Pajamas::AvatarComponent.new(project, size: 32, alt: '') | ||||
|           .gl-min-w-0.gl-flex-grow-1.gl-ml-3 | ||||
|             .title | ||||
|               = link_to(admin_project_path(project)) do | ||||
|                 %span.project-full-name | ||||
|  |  | |||
|  | @ -2,10 +2,9 @@ | |||
| - title = topic.title || topic.name | ||||
| 
 | ||||
| %li.topic-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'topic_row_content' } } | ||||
|   .avatar-container.rect-avatar.s40.gl-flex-shrink-0 | ||||
|     = topic_icon(topic, class: "avatar s40") | ||||
|   = render Pajamas::AvatarComponent.new(topic, size: 32, alt: '') | ||||
| 
 | ||||
|   .gl-min-w-0.gl-flex-grow-1 | ||||
|   .gl-min-w-0.gl-flex-grow-1.gl-ml-3 | ||||
|     .title | ||||
|       = link_to title, topic_explore_projects_path(topic_name: topic.name) | ||||
|     %div | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
|     - if current_action?(:new) || current_action?(:create) | ||||
|       %span.float-left.gl-mr-3 | ||||
|         \/ | ||||
|       = text_field_tag 'file_name', params[:file_name], placeholder: "File name", data: { qa_selector: 'file_name_field' }, | ||||
|       = text_field_tag 'file_name', params[:file_name], placeholder: "Filename", data: { qa_selector: 'file_name_field' }, | ||||
|         required: true, class: 'form-control gl-form-input new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '') | ||||
|       = render 'template_selectors' | ||||
|       - if should_suggest_gitlab_ci_yml? | ||||
|  |  | |||
|  | @ -15,18 +15,13 @@ module Gitlab | |||
|         # client - An instance of Gitlab::GithubImport::Client. | ||||
|         # project - An instance of Project. | ||||
|         def import(client, project) | ||||
|           info(project.id, message: "starting importer", importer: 'Importer::CollaboratorsImporter') | ||||
|           waiter = Importer::CollaboratorsImporter | ||||
|             .new(project, client) | ||||
|             .execute | ||||
|           info(project.id, message: 'starting importer', importer: 'Importer::CollaboratorsImporter') | ||||
|           return skip_to_next_stage(project) unless has_push_access?(client, project.import_source) | ||||
| 
 | ||||
|           waiter = Importer::CollaboratorsImporter.new(project, client).execute | ||||
|           project.import_state.refresh_jid_expiration | ||||
| 
 | ||||
|           AdvanceStageWorker.perform_async( | ||||
|             project.id, | ||||
|             { waiter.key => waiter.jobs_remaining }, | ||||
|             :pull_requests_merged_by | ||||
|           ) | ||||
|           move_to_next_stage(project, { waiter.key => waiter.jobs_remaining }) | ||||
|         rescue StandardError => e | ||||
|           Gitlab::Import::ImportFailureService.track( | ||||
|             project_id: project.id, | ||||
|  | @ -41,6 +36,27 @@ module Gitlab | |||
| 
 | ||||
|         private | ||||
| 
 | ||||
|         def has_push_access?(client, repo) | ||||
|           client.repository(repo).dig(:permissions, :push) | ||||
|         end | ||||
| 
 | ||||
|         def skip_to_next_stage(project) | ||||
|           Gitlab::GithubImport::Logger.warn( | ||||
|             log_attributes( | ||||
|               project.id, | ||||
|               message: 'no push access rights to fetch collaborators', | ||||
|               importer: 'Importer::CollaboratorsImporter' | ||||
|             ) | ||||
|           ) | ||||
|           move_to_next_stage(project, {}) | ||||
|         end | ||||
| 
 | ||||
|         def move_to_next_stage(project, waiters = {}) | ||||
|           AdvanceStageWorker.perform_async( | ||||
|             project.id, waiters, :pull_requests_merged_by | ||||
|           ) | ||||
|         end | ||||
| 
 | ||||
|         def abort_on_failure | ||||
|           true | ||||
|         end | ||||
|  |  | |||
|  | @ -0,0 +1,132 @@ | |||
| --- | ||||
| stage: Create | ||||
| group: Source Code | ||||
| info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments | ||||
| --- | ||||
| 
 | ||||
| # Code Owners development guidelines | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219916) in GitLab 15.10. | ||||
| 
 | ||||
| This document was created to help contributors understand the code design of | ||||
| [Code Owners](../../user/project/code_owners.md). You should read this | ||||
| document before making changes to the code for this feature. | ||||
| 
 | ||||
| This document is intentionally limited to an overview of how the code is | ||||
| designed, as code can change often. To understand how a specific part of the | ||||
| feature works, view the code and the specs. The details here explain how the | ||||
| major components of the Code Owners feature work. | ||||
| 
 | ||||
| NOTE: | ||||
| This document should be updated when parts of the codebase referenced in this | ||||
| document are updated, removed, or new parts are added. | ||||
| 
 | ||||
| ## Business logic | ||||
| 
 | ||||
| All of the business logic for code owners is located in the `Gitlab::CodeOwners` | ||||
| namespace. Code Owners is an EE-only feature, so the files only exist in the `./ee` directory. | ||||
| 
 | ||||
| - `Gitlab::CodeOwners`: the main module used to interact with the code owner rules. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners.rb`. | ||||
| - `Gitlab::CodeOwners::Loader`: finds the correct `CODEOWNER` file and loads the | ||||
|   content into a `Gitlab::CodeOwners::File` instance. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/loader.rb`. | ||||
| - `Gitlab::CodeOwners::ReferenceExtractor`: extracts `CODEOWNER` user, group, | ||||
|   and email references from texts. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/reference_extractor.rb`. | ||||
| - `Gitlab::CodeOwners::UsersLoader`: the correct `CODEOWNER` file and loads the | ||||
|   content into a `Gitlab::CodeOwners::File` instance. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/users_loader.rb`. | ||||
| - `Gitlab::CodeOwners::GroupsLoader`: finds the correct `CODEOWNER` file and loads | ||||
|   the content into a `Gitlab::CodeOwners::File` instance. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/groups_loader.rb`. | ||||
| - `Gitlab::CodeOwners::File`: wraps a `CODEOWNERS` file and exposes the data through | ||||
|   the class' public methods. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/file.rb`. | ||||
| - `Gitlab::CodeOwners::Entry`: wraps an entry (a pattern and owners line) in a | ||||
|   `CODEOWNERS` file and exposes the data through the class' public methods. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/entry.rb`. | ||||
| - `Gitlab::CodeOwners::Validator`: validates no files in the `CODEOWNERS` entries | ||||
|   have been changed when a user pushes to a protected branch with `require_code_owner_approval` enabled. | ||||
|   - Defined in `./ee/lib/gitlab/code_owners/validator.rb`. | ||||
| 
 | ||||
| ## Related models | ||||
| 
 | ||||
| ### `ProtectedBranch` | ||||
| 
 | ||||
| The `ProtectedBranch` model is defined in `app/models/protected_branch.rb` and | ||||
| extended in `ee/app/ee/models/protected_branch.rb`. The EE version includes a column | ||||
| named `require_code_owner_approval` which prevents changes from being pushed directly | ||||
| to the branch being protected if the file is listed in `CODEOWNERS`. | ||||
| 
 | ||||
| ### `ApprovalMergeRequestRule` | ||||
| 
 | ||||
| The `ApprovalMergeRequestRule` model is defined in `ee/app/models/approval_merge_request_rule.rb`. | ||||
| The model stores approval rules for a merge request. We use multiple rule types, | ||||
| including a `code_owner` type rule. | ||||
| 
 | ||||
| ## Controllers and Services | ||||
| 
 | ||||
| The following controllers and services below are being used for the approval | ||||
| rules feature to work: | ||||
| 
 | ||||
| ### `Api::Internal::Base` | ||||
| 
 | ||||
| This `/internal/allowed` endpoint is called when pushing to GitLab to ensure the | ||||
| user is allowed to push. The `/internal/allowed` endpoint performs a `Gitlab::Checks::DiffCheck`. | ||||
| In EE, this includes code owner checks. | ||||
| 
 | ||||
| Defined in `lib/api/internal/base.rb`. | ||||
| 
 | ||||
| ### `Repositories::GitHttpController` | ||||
| 
 | ||||
| When changes are pushed to GitLab over HTTP, the controller performs an access check | ||||
| to ensure the user is allowed to push. The checks perform a `Gitlab::Checks::DiffCheck`. | ||||
| In EE, this includes Code Owner checks. | ||||
| 
 | ||||
| Defined in `app/controllers/repositories/git_http_controller.rb`. | ||||
| 
 | ||||
| ### `EE::Gitlab::Checks::DiffCheck` | ||||
| 
 | ||||
| This module extends the CE `Gitlab::Checks::DiffChecks` class and adds code owner | ||||
| validation. It uses the `Gitlab::CodeOwner::Validator` class to verify users are | ||||
| not pushing files listed in `CODEOWNER` directly to a protected branch while the | ||||
| branch requires code owner approval. | ||||
| 
 | ||||
| ### `MergeRequests::SyncCodeOwnerApprovalRules` | ||||
| 
 | ||||
| This service is defined in `services/merge_requests/sync_code_owner_approval_rules.rb` and used for: | ||||
| 
 | ||||
| - Deleting outdated code owner approval rules when new changes are pushed to a merge request. | ||||
| - Creating code owner approval rules for each changed file in a merge request that is also listed in the `CODEOWNER` file. | ||||
| 
 | ||||
| ## Flow | ||||
| 
 | ||||
| These flowcharts should help explain the flow from the controllers down to the | ||||
| models for different features. | ||||
| 
 | ||||
| ### Push changes to a protected branch with `require_code_owner_approval` enabled | ||||
| 
 | ||||
| ```mermaid | ||||
| graph TD | ||||
|   Api::Internal::Base --> Gitlab::GitAccess | ||||
|   Gitlab::GitAccess --> Gitlab::Checks::DiffCheck | ||||
|   Gitlab::Checks::DiffCheck --> Gitlab::CodeOwners::Validator | ||||
|   Gitlab::CodeOwners::Validator --> ProtectedBranch | ||||
|   Gitlab::CodeOwners::Validator --> Gitlab::CodeOwners::Loader | ||||
|   Gitlab::CodeOwners::Loader --> Gitlab::CodeOwners::Entry | ||||
| ``` | ||||
| 
 | ||||
| ### Sync code owner rules to merge request approval rules | ||||
| 
 | ||||
| ```mermaid | ||||
| graph TD | ||||
|   EE::ProtectedBranches::CreateService --> MergeRequest::SyncCodeOwnerApprovalRules | ||||
|   EE::MergeRequestRefreshService --> MergeRequest::SyncCodeOwnerApprovalRules | ||||
|   EE::MergeRequests::ReloadMergeHeadDiffService --> MergeRequest::SyncCodeOwnerApprovalRules | ||||
|   EE::MergeRequests::CreateService --> MergeRequests::SyncCodeOwnerApprovalRulesWorker | ||||
|   EE::MergeRequests::UpdateService --> MergeRequests::SyncCodeOwnerApprovalRulesWorker | ||||
|   MergeRequests::SyncCodeOwnerApprovalRulesWorker --> MergeRequest::SyncCodeOwnerApprovalRules | ||||
|   MergeRequest::SyncCodeOwnerApprovalRules --> id1{delete outdated code owner rules} | ||||
|   MergeRequest::SyncCodeOwnerApprovalRules --> id2{create rule for each code owner entry} | ||||
| ``` | ||||
|  | @ -969,6 +969,12 @@ Use lowercase for **personal access token**. | |||
| 
 | ||||
| Do not use **please**. For details, see the [Microsoft style guide](https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please). | ||||
| 
 | ||||
| ## prerequisites | ||||
| 
 | ||||
| Use **prerequisites** when documenting the steps before a task. Do not use **requirements**. | ||||
| 
 | ||||
| For more information, see [the task topic type](../topic_types/task.md). | ||||
| 
 | ||||
| ## press | ||||
| 
 | ||||
| Use **press** when talking about keyboard keys. For example: | ||||
|  | @ -1031,6 +1037,12 @@ Do not use **Reporter permissions**. A user who is assigned the Reporter role ha | |||
| 
 | ||||
| Use title case for **Repository Mirroring**. | ||||
| 
 | ||||
| ## requirements | ||||
| 
 | ||||
| Use **prerequisites** when documenting the steps before a task. Do not use **requirements**. | ||||
| 
 | ||||
| For more information, see [the task topic type](../topic_types/task.md). | ||||
| 
 | ||||
| ## respectively | ||||
| 
 | ||||
| Avoid **respectively** and be more precise instead. | ||||
|  |  | |||
|  | @ -198,3 +198,124 @@ Say, for example, we extract the whole endpoint into a service. The `can?` check | |||
| - If the finder doesn't accept `current_user`, and therefore doesn't check permissions, then probably no. | ||||
| - If the finder accepts `current_user`, and doesn't check permissions, then it would be a good idea to double check other usages of the finder, and we might consider adding authorization. | ||||
| - If the finder accepts `current_user`, and already checks permissions, then either we need to add our case, or the existing checks are appropriate. | ||||
| 
 | ||||
| ### Refactoring permissions | ||||
| 
 | ||||
| #### Finding existing permissions checks | ||||
| 
 | ||||
| As mentioned [above](#where-should-permissions-be-checked), permissions are | ||||
| often checked in multiple locations for a single endpoint or web request. As a | ||||
| result, finding the list of authorization checks that are run for a given endpoint | ||||
| can be challenging. | ||||
| 
 | ||||
| To assist with this, you can locally set `GITLAB_DEBUG_POLICIES=true`. | ||||
| 
 | ||||
| This outputs information about which abilities are checked in the requests | ||||
| made in any specs that you run. The output also includes the line of code where the | ||||
| authorization check was made. Caller information is especially helpful in cases | ||||
| where there is metaprogramming used because those cases are difficult to find by | ||||
| grepping for ability name strings. | ||||
| 
 | ||||
| Example: | ||||
| 
 | ||||
| ```shell | ||||
| # example spec run | ||||
| 
 | ||||
| GITLAB_DEBUG_POLICIES=true bundle exec rspec spec/controllers/groups_controller_spec.rb:162 | ||||
| 
 | ||||
| # permissions debug output when spec is run; if multiple policy checks are run they will all be in the debug output. | ||||
| 
 | ||||
| POLICY CHECK DEBUG -> policy: GlobalPolicy, ability: create_group, called_from: ["/gitlab/app/controllers/application_controller.rb:245:in `can?'", "/gitlab/app/controllers/groups_controller.rb:255:in `authorize_create_group!'"] | ||||
| ``` | ||||
| 
 | ||||
| This flag is meant to help learn more about authorization checks while | ||||
| refactoring and should not remain enabled for any specs on the default branch. | ||||
| 
 | ||||
| #### Understanding logic for individual abilities | ||||
| 
 | ||||
| References to an ability may appear in a `DeclarativePolicy` class many times | ||||
| and depend on conditions and rules which reference other abilities. As a result, | ||||
| it can be challenging to know exactly which conditions apply to a particular | ||||
| ability. | ||||
| 
 | ||||
| `DeclarativePolicy` provides a `ability_map` for each Policy class, which | ||||
| pulls all Rules for an ability into an array. | ||||
| 
 | ||||
| Example: | ||||
| 
 | ||||
| ```ruby | ||||
| > GroupPolicy.ability_map.map.select { |k,v| k == :read_group_member } | ||||
| => {:read_group_member=>[[:enable, #<Rule can?(:read_group)>], [:prevent, #<Rule ~can_read_group_member>]]} | ||||
| 
 | ||||
| > GroupPolicy.ability_map.map.select { |k,v| k == :read_group } | ||||
| => {:read_group=> | ||||
|   [[:enable, #<Rule public_group>], | ||||
|    [:enable, #<Rule logged_in_viewable>], | ||||
|    [:enable, #<Rule guest>], | ||||
|    [:enable, #<Rule admin>], | ||||
|    [:enable, #<Rule has_projects>], | ||||
|    [:enable, #<Rule read_package_registry_deploy_token>], | ||||
|    [:enable, #<Rule write_package_registry_deploy_token>], | ||||
|    [:prevent, #<Rule all?(~public_group, ~admin, user_banned_from_group)>], | ||||
|    [:enable, #<Rule auditor>], | ||||
|    [:prevent, #<Rule needs_new_sso_session>], | ||||
|    [:prevent, #<Rule all?(ip_enforcement_prevents_access, ~owner, ~auditor)>]]} | ||||
| ``` | ||||
| 
 | ||||
| `DeclarativePolicy` also provides a `debug` method that can be used to | ||||
| understand the logic tree for a specific object and actor. The output is similar | ||||
| to the list of rules from `ability_map`. But, `DeclarativePolicy` stops | ||||
| evaluating rules once one `prevent`s an ability, so it is possible that | ||||
| not all conditions are called. | ||||
| 
 | ||||
| Example: | ||||
| 
 | ||||
| ```ruby | ||||
| policy = GroupPolicy.new(User.last,  Group.last) | ||||
| policy.debug(:read_group) | ||||
| 
 | ||||
| - [0] enable when public_group ((@custom_guest_user1 : Group/139)) | ||||
| - [0] enable when logged_in_viewable ((@custom_guest_user1 : Group/139)) | ||||
| - [0] enable when admin ((@custom_guest_user1 : Group/139)) | ||||
| - [0] enable when auditor ((@custom_guest_user1 : Group/139)) | ||||
| - [14] prevent when all?(~public_group, ~admin, user_banned_from_group) ((@custom_guest_user1 : Group/139)) | ||||
| - [14] prevent when needs_new_sso_session ((@custom_guest_user1 : Group/139)) | ||||
| - [16] enable when guest ((@custom_guest_user1 : Group/139)) | ||||
| - [16] enable when has_projects ((@custom_guest_user1 : Group/139)) | ||||
| - [16] enable when read_package_registry_deploy_token ((@custom_guest_user1 : Group/139)) | ||||
| - [16] enable when write_package_registry_deploy_token ((@custom_guest_user1 : Group/139)) | ||||
|   [21] prevent when all?(ip_enforcement_prevents_access, ~owner, ~auditor) ((@custom_guest_user1 : Group/139)) | ||||
| 
 | ||||
| => #<DeclarativePolicy::Runner::State:0x000000015c665050 | ||||
|  @called_conditions= | ||||
|   #<Set: { | ||||
|    "/dp/condition/GroupPolicy/public_group/Group:139", | ||||
|    "/dp/condition/GroupPolicy/logged_in_viewable/User:83,Group:139", | ||||
|    "/dp/condition/BasePolicy/admin/User:83", | ||||
|    "/dp/condition/BasePolicy/auditor/User:83", | ||||
|    "/dp/condition/GroupPolicy/user_banned_from_group/User:83,Group:139", | ||||
|    "/dp/condition/GroupPolicy/needs_new_sso_session/User:83,Group:139", | ||||
|    "/dp/condition/GroupPolicy/guest/User:83,Group:139", | ||||
|    "/dp/condition/GroupPolicy/has_projects/User:83,Group:139", | ||||
|    "/dp/condition/GroupPolicy/read_package_registry_deploy_token/User:83,Group:139", | ||||
|    "/dp/condition/GroupPolicy/write_package_registry_deploy_token/User:83,Group:139"}>, | ||||
|  @enabled=false, | ||||
|  @prevented=true> | ||||
| ``` | ||||
| 
 | ||||
| #### Testing that individual policies are equivalent | ||||
| 
 | ||||
| You can use the `'equivalent project policy abilities'` shared example to ensure | ||||
| that 2 project policy abilities are equivalent for all project visibility levels | ||||
| and access levels. | ||||
| 
 | ||||
| Example: | ||||
| 
 | ||||
| ```ruby | ||||
|   context 'when refactoring read_pipeline_schedule and read_pipeline' do | ||||
|     let(:old_policy) { :read_pipeline_schedule } | ||||
|     let(:new_policy) { :read_pipeline } | ||||
| 
 | ||||
|     it_behaves_like 'equivalent policies' | ||||
|   end | ||||
| ``` | ||||
|  |  | |||
|  | @ -149,19 +149,19 @@ Depending on your version of GitLab, the Chain of Custody report is either sent | |||
| 
 | ||||
| #### Generate commit-specific Chain of Custody report | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6. | ||||
| > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6. | ||||
| > - Support for including all commits instead of only merge commits [added](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) in GitLab 15.10. | ||||
| 
 | ||||
| You can generate a commit-specific Chain of Custody report for a given merge commit SHA. This report provides only the | ||||
| details for the provided merge commit SHA. Issue [393446](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) proposes | ||||
| to extend the commit SHA filtering to work with all commits instead of only merge commits. | ||||
| You can generate a commit-specific Chain of Custody report for a given commit SHA. This report provides only the | ||||
| details for the provided commit SHA. | ||||
| 
 | ||||
| To generate a commit-specific Chain of Custody report: | ||||
| 
 | ||||
| 1. On the top bar, select **Main menu > Groups** and find your group. | ||||
| 1. On the left sidebar, select **Security and Compliance > Compliance report**. | ||||
| 1. At the top of the compliance report, to the right of **List of all merge commits**, select the down arrow | ||||
| 1. At the top of the compliance report, to the right of **List of all commits**, select the down arrow | ||||
|    (**{chevron-lg-down}**). | ||||
| 1. Enter the merge commit SHA, and then select **Export commit custody report**. | ||||
| 1. Enter the commit SHA, and then select **Export commit custody report**. | ||||
| 
 | ||||
| Depending on your version of GitLab, the Chain of Custody report is either sent through email or available for download. | ||||
| 
 | ||||
|  |  | |||
|  | @ -284,6 +284,8 @@ GitHub Enterprise Cloud has | |||
| [custom repository roles](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-repository-roles). | ||||
| These roles aren't supported and cause partial imports. | ||||
| 
 | ||||
| To import GitHub collaborators, you must have at least the Write role on the GitHub project. Otherwise collaborators import is skipped. | ||||
| 
 | ||||
| ## Import from GitHub Enterprise on an internal network | ||||
| 
 | ||||
| If your GitHub Enterprise instance is on a internal network that is inaccessible to the internet, you can use a reverse proxy | ||||
|  |  | |||
|  | @ -26,11 +26,32 @@ for any change you commit through the Web Editor. | |||
| To create a text file in the Web Editor: | ||||
| 
 | ||||
| 1. On the top bar, select **Main menu > Projects** and find your project. | ||||
| 1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**). | ||||
| 1. From the project dashboard or repository, next to the branch name, | ||||
|    select the plus icon (**{plus}**). | ||||
| 1. From the dropdown list, select **New file**. | ||||
| 1. Complete the fields. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected. | ||||
| 1. Complete the fields. | ||||
| 1. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected, if you had chosen a **Target branch** other than the [default branch (such as `main`)](../../../user/project/repository/branches/default.md). | ||||
| 1. Select **Commit changes**. | ||||
| 
 | ||||
| ### Create a file from a template | ||||
| 
 | ||||
| 1. On the top bar, select **Main menu > Projects** and find your project. | ||||
| 1. On the left sidebar, select **Repository > Files**. | ||||
| 1. Next to the project name, select the plus icon (**{plus}**) to display a | ||||
|    dropdown list, then select **New file** from the list. | ||||
| 1. For **Filename**, provide one of the filenames that GitLab provides a template for: | ||||
|    - `.gitignore` | ||||
|    - `.gitlab-ci.yml` | ||||
|    - `LICENSE` | ||||
|    - `Dockerfile` | ||||
| 1. Select **Apply a template**, then select the template you want to apply. | ||||
| 1. Make your changes to the file. | ||||
| 1. Provide a **Commit message**. | ||||
| 1. Enter a **Target branch** to merge into. To create a new merge request with | ||||
|    your changes, enter a branch name that is not your repository's | ||||
|    [default branch](../../../user/project/repository/branches/default.md), | ||||
| 1. Select **Commit changes** to add the commit to your branch. | ||||
| 
 | ||||
| ## Edit a file | ||||
| 
 | ||||
| To edit a text file in the Web Editor: | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ module Gitlab | |||
|         def load(string) | ||||
|           return unless string | ||||
| 
 | ||||
|           object = YAML.safe_load(string, [Symbol]) | ||||
|           object = YAML.safe_load(string, permitted_classes: [Symbol]) | ||||
| 
 | ||||
|           object.map do |variable| | ||||
|             variable.symbolize_keys.tap do |variable| | ||||
|  |  | |||
|  | @ -26847,9 +26847,6 @@ msgstr "" | |||
| msgid "Merge blocked: pipeline must succeed. It's waiting for a manual job to continue." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Merge commit SHA" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Merge commit message" | ||||
| msgstr "" | ||||
| 
 | ||||
|  |  | |||
|  | @ -100,8 +100,8 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do | |||
|       end | ||||
| 
 | ||||
|       it 'sets merged_config' do | ||||
|         root_config = YAML.safe_load(content, [Symbol]) | ||||
|         included_config = YAML.safe_load(included_content, [Symbol]) | ||||
|         root_config = YAML.safe_load(content, permitted_classes: [Symbol]) | ||||
|         included_config = YAML.safe_load(included_content, permitted_classes: [Symbol]) | ||||
|         expected_config = included_config.merge(root_config).except(:include).deep_stringify_keys | ||||
| 
 | ||||
|         expect(subject.merged_yaml).to eq(expected_config.to_yaml) | ||||
|  |  | |||
|  | @ -47,8 +47,8 @@ module Gitlab | |||
|           end | ||||
| 
 | ||||
|           it 'returns expanded yaml config' do | ||||
|             expanded_config = YAML.safe_load(config_metadata[:merged_yaml], [Symbol]) | ||||
|             included_config = YAML.safe_load(included_yml, [Symbol]) | ||||
|             expanded_config = YAML.safe_load(config_metadata[:merged_yaml], permitted_classes: [Symbol]) | ||||
|             included_config = YAML.safe_load(included_yml, permitted_classes: [Symbol]) | ||||
| 
 | ||||
|             expect(expanded_config).to include(*included_config.keys) | ||||
|           end | ||||
|  |  | |||
|  | @ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::Config, feature_category: :importers do | |||
|         EOF | ||||
|       end | ||||
| 
 | ||||
|       let(:config_hash) { YAML.safe_load(config, [Symbol]) } | ||||
|       let(:config_hash) { YAML.safe_load(config, permitted_classes: [Symbol]) } | ||||
| 
 | ||||
|       before do | ||||
|         allow_any_instance_of(described_class).to receive(:parse_yaml) do | ||||
|  |  | |||
|  | @ -4321,6 +4321,14 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev | |||
|         transition! | ||||
|       end | ||||
| 
 | ||||
|       context 'when skip_merge_status_trigger is set to true' do | ||||
|         before do | ||||
|           subject.skip_merge_status_trigger = true | ||||
|         end | ||||
| 
 | ||||
|         it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription' | ||||
|       end | ||||
| 
 | ||||
|       context 'when transaction is not committed' do | ||||
|         it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription' do | ||||
|           def transition! | ||||
|  |  | |||
|  | @ -245,8 +245,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_composition do | |||
|       it 'passes validation' do | ||||
|         ci_lint | ||||
| 
 | ||||
|         included_config = YAML.safe_load(included_content, [Symbol]) | ||||
|         root_config = YAML.safe_load(yaml_content, [Symbol]) | ||||
|         included_config = YAML.safe_load(included_content, permitted_classes: [Symbol]) | ||||
|         root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol]) | ||||
|         expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml | ||||
| 
 | ||||
|         expect(response).to have_gitlab_http_status(:ok) | ||||
|  | @ -535,8 +535,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_composition do | |||
|       it 'passes validation' do | ||||
|         ci_lint | ||||
| 
 | ||||
|         included_config = YAML.safe_load(included_content, [Symbol]) | ||||
|         root_config = YAML.safe_load(yaml_content, [Symbol]) | ||||
|         included_config = YAML.safe_load(included_content, permitted_classes: [Symbol]) | ||||
|         root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol]) | ||||
|         expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml | ||||
| 
 | ||||
|         expect(response).to have_gitlab_http_status(:ok) | ||||
|  |  | |||
|  | @ -109,6 +109,14 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor | |||
|         expect(@fork_build_failed_todo).to be_done | ||||
|       end | ||||
| 
 | ||||
|       it 'triggers mergeRequestMergeStatusUpdated GraphQL subscription conditionally' do | ||||
|         expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@merge_request) | ||||
|         expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@another_merge_request) | ||||
|         expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated).with(@fork_merge_request) | ||||
| 
 | ||||
|         refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') | ||||
|       end | ||||
| 
 | ||||
|       context 'when a merge error exists' do | ||||
|         let(:error_message) { 'This is a merge error' } | ||||
| 
 | ||||
|  |  | |||
|  | @ -546,6 +546,7 @@ end | |||
| # Disabled because it's causing N+1 queries. | ||||
| # See https://gitlab.com/gitlab-org/gitlab/-/issues/396352. | ||||
| # Support::AbilityCheck.inject(Ability.singleton_class) | ||||
| Support::PermissionsCheck.inject(Ability.singleton_class) | ||||
| 
 | ||||
| ActiveRecord::Migration.maintain_test_schema! | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,18 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module Support | ||||
|   module PermissionsCheck | ||||
|     def self.inject(mod) | ||||
|       mod.prepend PermissionsExtension if Gitlab::Utils.to_boolean(ENV['GITLAB_DEBUG_POLICIES']) | ||||
|     end | ||||
| 
 | ||||
|     module PermissionsExtension | ||||
|       def before_check(policy, ability, _user, _subject, _opts) | ||||
|         puts( | ||||
|           "POLICY CHECK DEBUG -> " \ | ||||
|           "policy: #{policy.class.name}, ability: #{ability}, called_from: #{caller_locations(2, 5)}" | ||||
|         ) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -401,3 +401,24 @@ RSpec.shared_examples 'package access with repository disabled' do | |||
| 
 | ||||
|   it { is_expected.to be_allowed(:read_package) } | ||||
| end | ||||
| 
 | ||||
| RSpec.shared_examples 'equivalent project policy abilities' do | ||||
|   where(:project_visibility, :user_role_on_project) do | ||||
|     project_visibilities = [:public, :internal, :private] | ||||
|     user_role_on_project = [:anonymous, :non_member, :guest, :reporter, :developer, :maintainer, :owner, :admin] | ||||
|     project_visibilities.product(user_role_on_project) | ||||
|   end | ||||
| 
 | ||||
|   with_them do | ||||
|     it 'evaluates the same' do | ||||
|       project = public_send("#{project_visibility}_project") | ||||
|       current_user = public_send(user_role_on_project) | ||||
|       enable_admin_mode!(current_user) if user_role_on_project == :admin | ||||
|       policy = ProjectPolicy.new(current_user, project) | ||||
|       old_permissions = policy.allowed?(old_policy) | ||||
|       new_permissions = policy.allowed?(new_policy) | ||||
| 
 | ||||
|       expect(old_permissions).to eq new_permissions | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -11,6 +11,14 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c | |||
|   let(:client) { instance_double(Gitlab::GithubImport::Client) } | ||||
| 
 | ||||
|   describe '#import' do | ||||
|     let(:push_rights_granted) { true } | ||||
| 
 | ||||
|     before do | ||||
|       allow(client).to receive(:repository).with(project.import_source) | ||||
|         .and_return({ permissions: { push: push_rights_granted } }) | ||||
|     end | ||||
| 
 | ||||
|     context 'when user has push access for this repo' do | ||||
|       it 'imports all the pull requests' do | ||||
|         waiter = Gitlab::JobWaiter.new(2, '123') | ||||
| 
 | ||||
|  | @ -30,6 +38,20 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when user do not have push access for this repo' do | ||||
|       let(:push_rights_granted) { false } | ||||
| 
 | ||||
|       it 'skips stage' do | ||||
|         expect(Gitlab::GithubImport::Importer::CollaboratorsImporter).not_to receive(:new) | ||||
| 
 | ||||
|         expect(Gitlab::GithubImport::AdvanceStageWorker) | ||||
|           .to receive(:perform_async) | ||||
|           .with(project.id, {}, :pull_requests_merged_by) | ||||
| 
 | ||||
|         worker.import(client, project) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     it 'raises an error' do | ||||
|       exception = StandardError.new('_some_error_') | ||||
| 
 | ||||
|  | @ -47,4 +69,5 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c | |||
| 
 | ||||
|       expect { worker.import(client, project) }.to raise_error(StandardError) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -12,7 +12,9 @@ module Tooling | |||
|     def parse(yaml_files) | ||||
|       Array(yaml_files).each do |yaml_file| | ||||
|         data = File.read(yaml_file) | ||||
|         metadata, example_groups = data.split("---\n").reject(&:empty?).map { |yml| YAML.safe_load(yml, [Symbol]) } | ||||
|         metadata, example_groups = data.split("---\n").reject(&:empty?).map do |yml| | ||||
|           YAML.safe_load(yml, permitted_classes: [Symbol]) | ||||
|         end | ||||
| 
 | ||||
|         if example_groups.nil? | ||||
|           puts "No examples in #{yaml_file}! Metadata: #{metadata}" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue