diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 67dfeebaf7dea00cb214ef494dd4bc0168352720..10dd605cd96a0373c34f8f361c6dd1f05eac41eb 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -2279,6 +2279,7 @@ Gitlab/NamespacedClass: - 'ee/app/policies/instance_security_dashboard_policy.rb' - 'ee/app/policies/issuable_metric_image_policy.rb' - 'ee/app/policies/iteration_policy.rb' + - 'ee/app/policies/push_rule_policy.rb' - 'ee/app/policies/saml_provider_policy.rb' - 'ee/app/policies/timelog_policy.rb' - 'ee/app/policies/vulnerability_policy.rb' diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index eceff6d09c2a9a83bc77a53d76ccdd944452a75f..6cd21b96084a82611369ec491182e86dbf58be7a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -3388,6 +3388,7 @@ Represents vulnerability finding of a security report on the pipeline. | `printingMergeRequestLinkEnabled` | Boolean | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line. | | `projectMembers` | MemberInterfaceConnection | Members of the project. | | `publicJobs` | Boolean | Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts. | +| `pushRules` | PushRules | The project's push rules settings. | | `release` | Release | A single release of the project. | | `releases` | ReleaseConnection | Releases of the project. | | `removeSourceBranchAfterMerge` | Boolean | Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project. | @@ -3561,6 +3562,14 @@ Autogenerated return type of PromoteToEpic. | `errors` | String! => Array | Errors encountered during execution of the mutation. | | `issue` | Issue | The issue after mutation. | +### `PushRules` + +Represents rules that commit pushes must follow. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `rejectUnsignedCommits` | Boolean! | Indicates whether commits not signed through GPG will be rejected. | + ### `Release` Represents a release. diff --git a/ee/app/graphql/ee/types/project_type.rb b/ee/app/graphql/ee/types/project_type.rb index d30a5415cca2f6693a78d975ba52400a9ef4a907..defe25f2a16c0e723863b8bec47e4fb6346cb3f4 100644 --- a/ee/app/graphql/ee/types/project_type.rb +++ b/ee/app/graphql/ee/types/project_type.rb @@ -132,6 +132,12 @@ module ProjectType null: true, description: 'API fuzzing configuration for the project.', feature_flag: :api_fuzzing_configuration_ui + + field :push_rules, + ::Types::PushRulesType, + null: true, + description: "The project's push rules settings.", + method: :push_rule end def api_fuzzing_ci_configuration diff --git a/ee/app/graphql/types/push_rules_type.rb b/ee/app/graphql/types/push_rules_type.rb new file mode 100644 index 0000000000000000000000000000000000000000..5759597558939915dd715d8efcaedba3a5951bb1 --- /dev/null +++ b/ee/app/graphql/types/push_rules_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + class PushRulesType < BaseObject + graphql_name 'PushRules' + description 'Represents rules that commit pushes must follow.' + accepts ::PushRule + + authorize :read_project + + field :reject_unsigned_commits, + GraphQL::BOOLEAN_TYPE, + null: false, + description: 'Indicates whether commits not signed through GPG will be rejected.' + + def reject_unsigned_commits + !!(object.available?(:reject_unsigned_commits) && object.reject_unsigned_commits) + end + end +end diff --git a/ee/app/policies/push_rule_policy.rb b/ee/app/policies/push_rule_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..739c62bd408ff1100c04ea3f8beaab2c3f606f42 --- /dev/null +++ b/ee/app/policies/push_rule_policy.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class PushRulePolicy < BasePolicy + delegate { @subject.project } +end diff --git a/ee/spec/graphql/types/project_type_spec.rb b/ee/spec/graphql/types/project_type_spec.rb index 337a3ddd2b3fd00abc56e3579b591bce6ca909b2..b69e110675c6fe1e00a4477fe5903115a71fd8bf 100644 --- a/ee/spec/graphql/types/project_type_spec.rb +++ b/ee/spec/graphql/types/project_type_spec.rb @@ -222,6 +222,12 @@ end end + describe 'push rules field' do + subject { described_class.fields['pushRules'] } + + it { is_expected.to have_graphql_type(Types::PushRulesType) } + end + private def query_for_project(project) diff --git a/ee/spec/graphql/types/push_rules_type_spec.rb b/ee/spec/graphql/types/push_rules_type_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..05c5543902ed1e3b62d20a3e33a15a25f4b07ddc --- /dev/null +++ b/ee/spec/graphql/types/push_rules_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PushRules'] do + it { expect(described_class.graphql_name).to eq('PushRules') } + + it { expect(described_class).to require_graphql_authorizations(:read_project) } + + it 'has the expected fields' do + expect(described_class).to have_graphql_fields(:reject_unsigned_commits) + end +end diff --git a/ee/spec/requests/api/graphql/project/push_rules_spec.rb b/ee/spec/requests/api/graphql/project/push_rules_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a29624647d83847eb5ce037cc7d23eb8246d8964 --- /dev/null +++ b/ee/spec/requests/api/graphql/project/push_rules_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.project(fullPath).pushRules' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } + + subject(:push_rules_response) do + post_graphql( + graphql_query_for( + :project, { full_path: project.full_path }, "pushRules { #{all_graphql_fields_for('PushRules')} }" + ), + current_user: user + ) + + graphql_dig_at(graphql_data, 'project', 'pushRules') + end + + it 'returns nil when push_rules license is false' do + create(:push_rule, project: project) + stub_licensed_features(push_rules: false) + + expect(push_rules_response).to be_nil + end + + describe 'pushRules.rejectUnsignedCommits' do + where(:field_value, :license_value, :expected) do + true | true | true + true | false | false + false | true | false + false | false | false + end + + with_them do + before do + create(:push_rule, project: project, reject_unsigned_commits: field_value) + stub_licensed_features(reject_unsigned_commits: license_value) + end + + it "returns" do + expect(push_rules_response).to eq("rejectUnsignedCommits" => expected) + end + end + end +end