Commit 063e285e authored by Sean McGivern's avatar Sean McGivern

Merge branch 'api-gpg-key-management' into 'master'

API: Add GPG key management

Closes #36926

See merge request !13828
parents 9bb056e0 f4834c8a
---
title: 'API: Add GPG key management'
merge_request: 13828
author: Robert Schilling
type: added
This diff is collapsed.
...@@ -491,6 +491,10 @@ module API ...@@ -491,6 +491,10 @@ module API
expose :user, using: Entities::UserPublic expose :user, using: Entities::UserPublic
end end
class GPGKey < Grape::Entity
expose :id, :key, :created_at
end
class Note < Grape::Entity class Note < Grape::Entity
# Only Issue and MergeRequest have iid # Only Issue and MergeRequest have iid
NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
......
...@@ -233,6 +233,86 @@ module API ...@@ -233,6 +233,86 @@ module API
destroy_conditionally!(key) destroy_conditionally!(key)
end end
desc 'Add a GPG key to a specified user. Available only for admins.' do
detail 'This feature was added in GitLab 10.0'
success Entities::GPGKey
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
requires :key, type: String, desc: 'The new GPG key'
end
post ':id/gpg_keys' do
authenticated_as_admin!
user = User.find_by(id: params.delete(:id))
not_found!('User') unless user
key = user.gpg_keys.new(declared_params(include_missing: false))
if key.save
present key, with: Entities::GPGKey
else
render_validation_error!(key)
end
end
desc 'Get the GPG keys of a specified user. Available only for admins.' do
detail 'This feature was added in GitLab 10.0'
success Entities::GPGKey
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
use :pagination
end
get ':id/gpg_keys' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
present paginate(user.gpg_keys), with: Entities::GPGKey
end
desc 'Delete an existing GPG key from a specified user. Available only for admins.' do
detail 'This feature was added in GitLab 10.0'
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
delete ':id/gpg_keys/:key_id' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
key = user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
status 204
key.destroy
end
desc 'Revokes an existing GPG key from a specified user. Available only for admins.' do
detail 'This feature was added in GitLab 10.0'
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
post ':id/gpg_keys/:key_id/revoke' do
authenticated_as_admin!
user = User.find_by(id: params[:id])
not_found!('User') unless user
key = user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
key.revoke
status :accepted
end
desc 'Add an email address to a specified user. Available only for admins.' do desc 'Add an email address to a specified user. Available only for admins.' do
success Entities::Email success Entities::Email
end end
...@@ -492,6 +572,76 @@ module API ...@@ -492,6 +572,76 @@ module API
destroy_conditionally!(key) destroy_conditionally!(key)
end end
desc "Get the currently authenticated user's GPG keys" do
detail 'This feature was added in GitLab 10.0'
success Entities::GPGKey
end
params do
use :pagination
end
get 'gpg_keys' do
present paginate(current_user.gpg_keys), with: Entities::GPGKey
end
desc 'Get a single GPG key owned by currently authenticated user' do
detail 'This feature was added in GitLab 10.0'
success Entities::GPGKey
end
params do
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
get 'gpg_keys/:key_id' do
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
present key, with: Entities::GPGKey
end
desc 'Add a new GPG key to the currently authenticated user' do
detail 'This feature was added in GitLab 10.0'
success Entities::GPGKey
end
params do
requires :key, type: String, desc: 'The new GPG key'
end
post 'gpg_keys' do
key = current_user.gpg_keys.new(declared_params)
if key.save
present key, with: Entities::GPGKey
else
render_validation_error!(key)
end
end
desc 'Revoke a GPG key owned by currently authenticated user' do
detail 'This feature was added in GitLab 10.0'
end
params do
requires :key_id, type: Integer, desc: 'The ID of the GPG key'
end
post 'gpg_keys/:key_id/revoke' do
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
key.revoke
status :accepted
end
desc 'Delete a GPG key from the currently authenticated user' do
detail 'This feature was added in GitLab 10.0'
end
params do
requires :key_id, type: Integer, desc: 'The ID of the SSH key'
end
delete 'gpg_keys/:key_id' do
key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key
status 204
key.destroy
end
desc "Get the currently authenticated user's email addresses" do desc "Get the currently authenticated user's email addresses" do
success Entities::Email success Entities::Email
end end
......
...@@ -4,6 +4,7 @@ describe API::Users do ...@@ -4,6 +4,7 @@ describe API::Users do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) } let(:key) { create(:key, user: user) }
let(:gpg_key) { create(:gpg_key, user: user) }
let(:email) { create(:email, user: user) } let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) } let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') } let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
...@@ -753,6 +754,164 @@ describe API::Users do ...@@ -753,6 +754,164 @@ describe API::Users do
end end
end end
describe 'POST /users/:id/keys' do
before do
admin
end
it 'does not create invalid GPG key' do
post api("/users/#{user.id}/gpg_keys", admin)
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('key is missing')
end
it 'creates GPG key' do
key_attrs = attributes_for :gpg_key
expect do
post api("/users/#{user.id}/gpg_keys", admin), key_attrs
expect(response).to have_http_status(201)
end.to change { user.gpg_keys.count }.by(1)
end
it 'returns 400 for invalid ID' do
post api('/users/999999/gpg_keys', admin)
expect(response).to have_http_status(400)
end
end
describe 'GET /user/:id/gpg_keys' do
before do
admin
end
context 'when unauthenticated' do
it 'returns authentication error' do
get api("/users/#{user.id}/gpg_keys")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'returns 404 for non-existing user' do
get api('/users/999999/gpg_keys', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns 404 error if key not foud' do
delete api("/users/#{user.id}/gpg_keys/42", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
it 'returns array of GPG keys' do
user.gpg_keys << gpg_key
user.save
get api("/users/#{user.id}/gpg_keys", admin)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['key']).to eq(gpg_key.key)
end
end
end
describe 'DELETE /user/:id/gpg_keys/:key_id' do
before do
admin
end
context 'when unauthenticated' do
it 'returns authentication error' do
delete api("/users/#{user.id}/keys/42")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'deletes existing key' do
user.gpg_keys << gpg_key
user.save
expect do
delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin)
expect(response).to have_http_status(204)
end.to change { user.gpg_keys.count }.by(-1)
end
it 'returns 404 error if user not found' do
user.keys << key
user.save
delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns 404 error if key not foud' do
delete api("/users/#{user.id}/gpg_keys/42", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
end
end
describe 'POST /user/:id/gpg_keys/:key_id/revoke' do
before do
admin
end
context 'when unauthenticated' do
it 'returns authentication error' do
post api("/users/#{user.id}/gpg_keys/42/revoke")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'revokes existing key' do
user.gpg_keys << gpg_key
user.save
expect do
post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin)
expect(response).to have_http_status(:accepted)
end.to change { user.gpg_keys.count }.by(-1)
end
it 'returns 404 error if user not found' do
user.gpg_keys << gpg_key
user.save
post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'returns 404 error if key not foud' do
post api("/users/#{user.id}/gpg_keys/42/revoke", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
end
end
describe "POST /users/:id/emails" do describe "POST /users/:id/emails" do
before do before do
admin admin
...@@ -1153,6 +1312,173 @@ describe API::Users do ...@@ -1153,6 +1312,173 @@ describe API::Users do
end end
end end
describe 'GET /user/gpg_keys' do
context 'when unauthenticated' do
it 'returns authentication error' do
get api('/user/gpg_keys')
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
it 'returns array of GPG keys' do
user.gpg_keys << gpg_key
user.save
get api('/user/gpg_keys', user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['key']).to eq(gpg_key.key)
end
context 'scopes' do
let(:path) { '/user/gpg_keys' }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
end
end
end
describe 'GET /user/gpg_keys/:key_id' do
it 'returns a single key' do
user.gpg_keys << gpg_key
user.save
get api("/user/gpg_keys/#{gpg_key.id}", user)
expect(response).to have_http_status(200)
expect(json_response['key']).to eq(gpg_key.key)
end
it 'returns 404 Not Found within invalid ID' do
get api('/user/gpg_keys/42', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
it "returns 404 error if admin accesses user's GPG key" do
user.gpg_keys << gpg_key
user.save
get api("/user/gpg_keys/#{gpg_key.id}", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
it 'returns 404 for invalid ID' do
get api('/users/gpg_keys/ASDF', admin)
expect(response).to have_http_status(404)
end
context 'scopes' do
let(:path) { "/user/gpg_keys/#{gpg_key.id}" }
let(:api_call) { method(:api) }
include_examples 'allows the "read_user" scope'
end
end
describe 'POST /user/gpg_keys' do
it 'creates a GPG key' do
key_attrs = attributes_for :gpg_key
expect do
post api('/user/gpg_keys', user), key_attrs
expect(response).to have_http_status(201)
end.to change { user.gpg_keys.count }.by(1)
end
it 'returns a 401 error if unauthorized' do
post api('/user/gpg_keys'), key: 'some key'
expect(response).to have_http_status(401)
end
it 'does not create GPG key without key' do
post api('/user/gpg_keys', user)
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('key is missing')
end
end
describe 'POST /user/gpg_keys/:key_id/revoke' do
it 'revokes existing GPG key' do
user.gpg_keys << gpg_key
user.save
expect do
post api("/user/gpg_keys/#{gpg_key.id}/revoke", user)
expect(response).to have_http_status(:accepted)
end.to change { user.gpg_keys.count}.by(-1)
end
it 'returns 404 if key ID not found' do
post api('/user/gpg_keys/42/revoke', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
it 'returns 401 error if unauthorized' do
user.gpg_keys << gpg_key
user.save
post api("/user/gpg_keys/#{gpg_key.id}/revoke")
expect(response).to have_http_status(401)
end
it 'returns a 404 for invalid ID' do
post api('/users/gpg_keys/ASDF/revoke', admin)
expect(response).to have_http_status(404)
end
end
describe 'DELETE /user/gpg_keys/:key_id' do
it 'deletes existing GPG key' do
user.gpg_keys << gpg_key
user.save
expect do
delete api("/user/gpg_keys/#{gpg_key.id}", user)
expect(response).to have_http_status(204)
end.to change { user.gpg_keys.count}.by(-1)
end
it 'returns 404 if key ID not found' do
delete api('/user/gpg_keys/42', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 GPG Key Not Found')
end
it 'returns 401 error if unauthorized' do
user.gpg_keys << gpg_key
user.save
delete api("/user/gpg_keys/#{gpg_key.id}")
expect(response).to have_http_status(401)
end
it 'returns a 404 for invalid ID' do
delete api('/users/gpg_keys/ASDF', admin)
expect(response).to have_http_status(404)
end
end
describe "GET /user/emails" do describe "GET /user/emails" do
context "when unauthenticated" do context "when unauthenticated" do
it "returns authentication error" do it "returns authentication error" do
......
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