Commit cb8ab923 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'create-group-push-rules' into 'master'

API to create group push rule

See merge request gitlab-org/gitlab!39760
parents bddd3da1 d18f6f64
...@@ -1207,3 +1207,26 @@ the `commit_committer_check` and `reject_unsigned_commits` parameters: ...@@ -1207,3 +1207,26 @@ the `commit_committer_check` and `reject_unsigned_commits` parameters:
... ...
} }
``` ```
### Add group push rule **(STARTER)**
Adds [push rules](../user/group/index.md#group-push-rules-starter) to the specified group.
```plaintext
POST /groups/:id/push_rule
```
| Attribute | Type | Required | Description |
| --------------------------------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
| `member_check` **(STARTER)** | boolean | no | Allows only GitLab users to author commits |
| `prevent_secrets` **(STARTER)** | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) will be rejected |
| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match the regular expression provided in this attribute, e.g. `Fixed \d+\..*` |
| `commit_message_negative_regex` **(STARTER)** | string | no | Commit messages matching the regular expression provided in this attribute will not be allowed, e.g. `ssh\:\/\/` |
| `branch_name_regex` **(STARTER)** | string | no | All branch names must match the regular expression provided in this attribute, e.g. `(feature|hotfix)\/*` |
| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match the regular expression provided in this attribute, e.g. `@my-company.com$` |
| `file_name_regex` **(STARTER)** | string | no | Filenames matching the regular expression provided in this attribute will **not** be allowed, e.g. `(jar|exe)$` |
| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) allowed |
| `commit_committer_check` **(PREMIUM)** | boolean | no | Only commits pushed using verified emails will be allowed |
| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Only commits signed through GPG will be allowed |
---
title: Add an API to add a push rule to a group
merge_request: 39760
author:
type: added
...@@ -5,6 +5,7 @@ module API ...@@ -5,6 +5,7 @@ module API
before { authenticate! } before { authenticate! }
before { authorize_admin_group } before { authorize_admin_group }
before { check_feature_availability! } before { check_feature_availability! }
before { authorize_change_param(user_group, :commit_committer_check, :reject_unsigned_commits) }
params do params do
requires :id, type: String, desc: 'The ID of a group' requires :id, type: String, desc: 'The ID of a group'
...@@ -15,6 +16,26 @@ module API ...@@ -15,6 +16,26 @@ module API
def check_feature_availability! def check_feature_availability!
not_found! unless user_group.feature_available?(:push_rules) not_found! unless user_group.feature_available?(:push_rules)
end end
params :push_rule_params do
optional :deny_delete_tag, type: Boolean, desc: 'Deny deleting a tag'
optional :member_check, type: Boolean, desc: 'Restrict commits by author (email) to existing GitLab users'
optional :prevent_secrets, type: Boolean, desc: 'GitLab will reject any files that are likely to contain secrets'
optional :commit_message_regex, type: String, desc: 'All commit messages must match this'
optional :commit_message_negative_regex, type: String, desc: 'No commit message is allowed to match this'
optional :branch_name_regex, type: String, desc: 'All branches names must match this'
optional :author_email_regex, type: String, desc: 'All commit author emails must match this'
optional :file_name_regex, type: String, desc: 'All committed filenames must not match this'
optional :max_file_size, type: Integer, desc: 'Maximum file size (MB)'
optional :commit_committer_check, type: Boolean, desc: 'Users may only push their own commits'
optional :reject_unsigned_commits, type: Boolean, desc: 'Only GPG signed commits can be pushed to this repository'
at_least_one_of :deny_delete_tag, :member_check, :prevent_secrets,
:commit_message_regex, :commit_message_negative_regex, :branch_name_regex,
:author_email_regex,
:file_name_regex, :max_file_size,
:commit_committer_check,
:reject_unsigned_commits
end
end end
desc 'Get group push rule' do desc 'Get group push rule' do
...@@ -28,6 +49,21 @@ module API ...@@ -28,6 +49,21 @@ module API
present push_rule, with: EE::API::Entities::GroupPushRule, user: current_user present push_rule, with: EE::API::Entities::GroupPushRule, user: current_user
end end
desc 'Add a push rule to a group' do
detail 'This feature was introduced in GitLab 13.4.'
success EE::API::Entities::GroupPushRule
end
params do
use :push_rule_params
end
post ":id/push_rule" do
render_api_error!(_('Group push rule exists, try updating'), 422) if user_group.push_rule
allowed_params = declared_params(include_missing: false)
user_group.update!(push_rule: PushRule.create!(allowed_params))
present user_group.push_rule, with: EE::API::Entities::GroupPushRule, user: current_user
end
end end
end end
end end
...@@ -8,6 +8,19 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do ...@@ -8,6 +8,19 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
let_it_be(:admin) { create(:user, :admin) } let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:attributes) do
{
author_email_regex: '^[A-Za-z0-9.]+@gitlab.com$',
commit_committer_check: true,
commit_message_negative_regex: '[x+]',
commit_message_regex: '[a-zA-Z]',
deny_delete_tag: false,
max_file_size: 100,
member_check: false,
prevent_secrets: true,
reject_unsigned_commits: true
}
end
shared_examples 'not found when feature is unavailable' do shared_examples 'not found when feature is unavailable' do
before do before do
...@@ -31,21 +44,16 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do ...@@ -31,21 +44,16 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
it { expect { subject }.to be_denied_for(:anonymous) } it { expect { subject }.to be_denied_for(:anonymous) }
end end
shared_context 'licensed features available' do
before do
stub_licensed_features(push_rules: true,
commit_committer_check: true,
reject_unsigned_commits: true)
end
end
describe 'GET /groups/:id/push_rule' do describe 'GET /groups/:id/push_rule' do
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:attributes) do
{
author_email_regex: '^[A-Za-z0-9.]+@gitlab.com$',
commit_committer_check: true,
commit_message_negative_regex: '[x+]',
commit_message_regex: '[a-zA-Z]',
deny_delete_tag: false,
max_file_size: 100,
member_check: false,
prevent_secrets: true,
reject_unsigned_commits: true
}
end
before_all do before_all do
push_rule = create(:push_rule, **attributes) push_rule = create(:push_rule, **attributes)
...@@ -62,11 +70,7 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do ...@@ -62,11 +70,7 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
subject { get api("/groups/#{group.id}/push_rule", admin) } subject { get api("/groups/#{group.id}/push_rule", admin) }
context 'when licensed' do context 'when licensed' do
before do include_context 'licensed features available'
stub_licensed_features(push_rules: true,
commit_committer_check: true,
reject_unsigned_commits: true)
end
it 'returns attributes as expected' do it 'returns attributes as expected' do
subject subject
...@@ -78,7 +82,7 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do ...@@ -78,7 +82,7 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
"commit_committer_check" => true, "commit_committer_check" => true,
"commit_message_negative_regex" => attributes[:commit_message_negative_regex], "commit_message_negative_regex" => attributes[:commit_message_negative_regex],
"commit_message_regex" => attributes[:commit_message_regex], "commit_message_regex" => attributes[:commit_message_regex],
"created_at" => group.push_rule.created_at.iso8601(3), "created_at" => group.reload.push_rule.created_at.iso8601(3),
"deny_delete_tag" => false, "deny_delete_tag" => false,
"file_name_regex" => nil, "file_name_regex" => nil,
"id" => group.push_rule.id, "id" => group.push_rule.id,
...@@ -138,4 +142,119 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do ...@@ -138,4 +142,119 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
end end
end end
end end
describe 'POST /groups/:id/push_rule' do
let_it_be(:group) { create(:group) }
context 'when unlicensed' do
subject { post api("/groups/#{group.id}/push_rule", admin), params: attributes }
it_behaves_like 'not found when feature is unavailable'
end
context 'authorized user' do
subject { post api("/groups/#{group.id}/push_rule", admin), params: attributes }
context 'when licensed' do
include_context 'licensed features available'
it do
subject
expect(response).to have_gitlab_http_status(:created)
end
it do
expect { subject }.to change { PushRule.count }.by(1)
end
it 'creates record with appropriate attributes', :aggregate_failures do
subject
push_rule = group.reload.push_rule
expect(push_rule.author_email_regex).to eq(attributes[:author_email_regex])
expect(push_rule.commit_committer_check).to eq(attributes[:commit_committer_check])
expect(push_rule.commit_message_negative_regex).to eq(attributes[:commit_message_negative_regex])
expect(push_rule.commit_message_regex).to eq(attributes[:commit_message_regex])
expect(push_rule.deny_delete_tag).to eq(attributes[:deny_delete_tag])
expect(push_rule.max_file_size).to eq(attributes[:max_file_size])
expect(push_rule.member_check).to eq(attributes[:member_check])
expect(push_rule.prevent_secrets).to eq(attributes[:prevent_secrets])
expect(push_rule.reject_unsigned_commits).to eq(attributes[:reject_unsigned_commits])
end
context 'when push rule exists' do
before do
push_rule = create(:push_rule, **attributes)
group.update!(push_rule: push_rule)
end
it do
subject
expect(response).to have_gitlab_http_status(:unprocessable_entity)
expect(json_response['message']).to eq('Group push rule exists, try updating')
end
end
context 'permissions' do
subject { post api("/groups/#{group.id}/push_rule", user), params: attributes }
it_behaves_like 'allow access to api based on role'
end
context 'when no rule is specified' do
it do
post api("/groups/#{group.id}/push_rule", admin), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to include('at least one parameter must be provided')
end
end
end
context 'when reject_unsigned_commits is unavailable' do
before do
stub_licensed_features(reject_unsigned_commits: false)
stub_licensed_features(push_rules: true, commit_committer_check: true)
end
it 'returns forbidden' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'and reject_unsigned_commits is not set' do
it 'returns created' do
post api("/groups/#{group.id}/push_rule", admin), params: attributes.except(:reject_unsigned_commits)
expect(response).to have_gitlab_http_status(:created)
end
end
end
context 'when commit_committer_check is unavailable' do
before do
stub_licensed_features(commit_committer_check: false)
stub_licensed_features(push_rules: true, reject_unsigned_commits: true)
end
it do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'and commit_committer_check is not set' do
it 'returns created' do
post api("/groups/#{group.id}/push_rule", admin), params: attributes.except(:commit_committer_check)
expect(response).to have_gitlab_http_status(:created)
end
end
end
end
end
end end
...@@ -12025,6 +12025,9 @@ msgstr "" ...@@ -12025,6 +12025,9 @@ msgstr ""
msgid "Group project URLs are prefixed with the group namespace" msgid "Group project URLs are prefixed with the group namespace"
msgstr "" msgstr ""
msgid "Group push rule exists, try updating"
msgstr ""
msgid "Group requires separate account" msgid "Group requires separate account"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment