Commit d18f6f64 authored by Aishwarya Subramanian's avatar Aishwarya Subramanian

Create api for group push rule

Adds an api to create push rule
for groups. Available to group owners
and admins.
parent 6dd8b3a2
......@@ -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
before { authenticate! }
before { authorize_admin_group }
before { check_feature_availability! }
before { authorize_change_param(user_group, :commit_committer_check, :reject_unsigned_commits) }
params do
requires :id, type: String, desc: 'The ID of a group'
......@@ -15,6 +16,26 @@ module API
def check_feature_availability!
not_found! unless user_group.feature_available?(:push_rules)
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
desc 'Get group push rule' do
......@@ -28,6 +49,21 @@ module API
present push_rule, with: EE::API::Entities::GroupPushRule, user: current_user
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
......@@ -8,6 +8,19 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
let_it_be(:admin) { create(:user, :admin) }
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
before do
......@@ -31,21 +44,16 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
it { expect { subject }.to be_denied_for(:anonymous) }
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
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
push_rule = create(:push_rule, **attributes)
......@@ -62,11 +70,7 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
subject { get api("/groups/#{group.id}/push_rule", admin) }
context 'when licensed' do
before do
stub_licensed_features(push_rules: true,
commit_committer_check: true,
reject_unsigned_commits: true)
end
include_context 'licensed features available'
it 'returns attributes as expected' do
subject
......@@ -78,7 +82,7 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
"commit_committer_check" => true,
"commit_message_negative_regex" => attributes[:commit_message_negative_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,
"file_name_regex" => nil,
"id" => group.push_rule.id,
......@@ -138,4 +142,119 @@ RSpec.describe API::GroupPushRule, 'GroupPushRule', api: true do
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
......@@ -11977,6 +11977,9 @@ msgstr ""
msgid "Group project URLs are prefixed with the group namespace"
msgstr ""
msgid "Group push rule exists, try updating"
msgstr ""
msgid "Group requires separate account"
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