Commit 2dc5ffc1 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'update-pending-invitation-api' into 'master'

Add API endpoint to update pending member invitation

See merge request gitlab-org/gitlab!54843
parents 575798ef a814c977
......@@ -107,6 +107,36 @@ Example response:
]
```
## Update an invitation to a group or project
Updates a pending invitation's access level or access expiry date.
```plaintext
PUT /groups/:id/invitations/:email
PUT /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. |
| `access_level` | integer | no | A valid access level (defaults: `30`, developer access level). |
| `expires_at` | string | no | A date string in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`). |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/55/invitations/email@example.org?access_level=40"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/55/invitations/email@example.org?access_level=40"
```
Example response:
```json
{
"expires_at": "2012-10-22T14:13:35Z",
"access_level": 40,
}
```
## Delete an invitation to a group or project
Deletes a pending invitation by email address.
......
......@@ -49,6 +49,39 @@ module API
present_member_invitations invitations
end
desc 'Updates a group or project invitation.' do
success Entities::Member
end
params do
requires :email, type: String, desc: 'The email address of the invitation.'
optional :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level).'
optional :expires_at, type: DateTime, desc: 'Date string in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`).'
end
put ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
source = find_source(source_type, params.delete(:id))
invite_email = params[:email]
authorize_admin_source!(source_type, source)
invite = retrieve_member_invitations(source, invite_email).first
not_found! unless invite
update_params = declared_params(include_missing: false)
update_params.delete(:email)
bad_request! unless update_params.any?
result = ::Members::UpdateService
.new(current_user, update_params)
.execute(invite)
updated_member = result[:member]
if result[:status] == :success
present_members updated_member
else
render_validation_error!(updated_member)
end
end
desc 'Removes an invitation from a group or project.'
params do
requires :email, type: String, desc: 'The email address of the invitation'
......
......@@ -374,4 +374,104 @@ RSpec.describe API::Invitations do
let(:source) { group }
end
end
shared_examples 'PUT /:source_type/:id/invitations/:email' do |source_type|
def update_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, maintainer) }
it_behaves_like 'a 404 response when source is private' do
let(:route) do
put update_api(source, stranger, invite.invite_email), params: { access_level: Member::MAINTAINER }
end
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)
put update_api(source, user, invite.invite_email), params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
end
context 'when authenticated as a maintainer/owner' do
context 'updating access level' do
it 'updates the invitation' do
put update_api(source, maintainer, invite.invite_email), params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['access_level']).to eq(Member::MAINTAINER)
expect(invite.reload.access_level).to eq(Member::MAINTAINER)
end
end
it 'returns 409 if member does not exist' do
put update_api(source, maintainer, non_existing_record_id), params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns 400 when access_level is not given and there are no other params' do
put update_api(source, maintainer, invite.invite_email)
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 when access level is not valid' do
put update_api(source, maintainer, invite.invite_email), params: { access_level: non_existing_record_access_level }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'updating access expiry date' do
subject do
put update_api(source, maintainer, invite.invite_email), params: { expires_at: expires_at }
end
context 'when set to a date in the past' do
let(:expires_at) { 2.days.ago.to_date }
it 'does not update the member' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq({ 'expires_at' => ['cannot be a date in the past'] })
end
end
context 'when set to a date in the future' do
let(:expires_at) { 2.days.from_now.to_date }
it 'updates the member' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['expires_at']).to eq(expires_at.to_s)
end
end
end
end
end
describe 'PUT /projects/:id/invitations' do
it_behaves_like 'PUT /:source_type/:id/invitations/:email', 'project' do
let(:source) { project }
end
end
describe 'PUT /groups/:id/invitations' do
it_behaves_like 'PUT /: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