Commit f3be9e12 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'add-api-command-to-delete-pending-invitation' into 'master'

Add API command to remove pending member invitation

See merge request gitlab-org/gitlab!51134
parents 3068cf8b 3562d69d
---
title: Add API command to remove pending member invitation
merge_request: 51134
author:
type: added
......@@ -106,3 +106,27 @@ Example response:
},
]
```
## Delete an invitation to a group or project
Deletes a pending invitation by email address.
```plaintext
DELETE /groups/:id/invitations/:email
DELETE /projects/:id/invitations/:email
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `email` | string | yes | The email address to which the invitation was previously sent |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/55/invitations/email@example.org"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/55/invitations/email@example.org"
```
- Returns `204` and no content on success.
- Returns `403` forbidden if unauthorized to delete the invitation.
- Returns `404` not found if authorized and no invitation is found for that email address.
- Returns `409` if the request was valid but the invitation could not be deleted.
......@@ -48,6 +48,24 @@ module API
present_member_invitations invitations
end
desc 'Removes an invitation from a group or project.'
params do
requires :email, type: String, desc: 'The email address of the invitation'
end
delete ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
source = find_source(source_type, params[:id])
invite_email = params[:email]
authorize_admin_source!(source_type, source)
invite = retrieve_member_invitations(source, invite_email).first
not_found! unless invite
destroy_conditionally!(invite) do
::Members::DestroyService.new(current_user, params).execute(invite)
unprocessable_entity! unless invite.destroyed?
end
end
end
end
end
......
......@@ -30,6 +30,10 @@ RSpec.describe API::Invitations do
api("/#{source.model_name.plural}/#{source.id}/invitations", user)
end
def invite_member_by_email(source, source_type, email, created_by)
create(:"#{source_type}_member", invite_token: '123', invite_email: email, source: source, user: nil, created_by: created_by)
end
shared_examples 'POST /:source_type/:id/invitations' do |source_type|
context "with :source_type == #{source_type.pluralize}" do
it_behaves_like 'a 404 response when source is private' do
......@@ -280,10 +284,6 @@ RSpec.describe API::Invitations do
expect(json_response.first['created_by_name']).to eq(developer.name)
expect(json_response.first['user_name']).to eq(nil)
end
def invite_member_by_email(source, source_type, email, created_by)
create(:"#{source_type}_member", invite_token: '123', invite_email: email, source: source, user: nil, created_by: created_by)
end
end
end
......@@ -298,4 +298,80 @@ RSpec.describe API::Invitations do
let(:source) { group }
end
end
shared_examples 'DELETE /:source_type/:id/invitations/:email' do |source_type|
def invite_api(source, user, email)
api("/#{source.model_name.plural}/#{source.id}/invitations/#{email}", user)
end
context "with :source_type == #{source_type.pluralize}" do
let!(:invite) { invite_member_by_email(source, source_type, developer.email, developer) }
it_behaves_like 'a 404 response when source is private' do
let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/invitations/#{invite.invite_email}", stranger) }
end
context 'when authenticated as a non-member or member with insufficient rights' do
%i[access_requester stranger].each do |type|
context "as a #{type}" do
it 'returns 403' do
user = public_send(type)
delete invite_api(source, user, invite.invite_email)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
end
context 'when authenticated as a member and deleting themself' do
it 'does not delete the member' do
expect do
delete invite_api(source, developer, invite.invite_email)
expect(response).to have_gitlab_http_status(:forbidden)
end.not_to change { source.members.count }
end
end
context 'when authenticated as a maintainer/owner' do
it 'deletes the member and returns 204 with no content' do
expect do
delete invite_api(source, maintainer, invite.invite_email)
expect(response).to have_gitlab_http_status(:no_content)
end.to change { source.members.count }.by(-1)
end
end
it 'returns 404 if member does not exist' do
delete invite_api(source, maintainer, non_existing_record_id)
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns 422 for a valid request if the resource was not destroyed' do
allow_next_instance_of(::Members::DestroyService) do |instance|
allow(instance).to receive(:execute).with(invite).and_return(invite)
end
delete invite_api(source, maintainer, invite.invite_email)
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
describe 'DELETE /projects/:id/inviations/:email' do
it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'project' do
let(:source) { project }
end
end
describe 'DELETE /groups/:id/inviations/:email' do
it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'group' do
let(:source) { group }
end
end
end
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