diff options
-rw-r--r-- | changelogs/unreleased/api-gpg-key-management.yml | 5 | ||||
-rw-r--r-- | doc/api/users.md | 211 | ||||
-rw-r--r-- | lib/api/entities.rb | 4 | ||||
-rw-r--r-- | lib/api/users.rb | 150 | ||||
-rw-r--r-- | spec/requests/api/users_spec.rb | 326 |
5 files changed, 696 insertions, 0 deletions
diff --git a/changelogs/unreleased/api-gpg-key-management.yml b/changelogs/unreleased/api-gpg-key-management.yml new file mode 100644 index 00000000000..0be35a5823b --- /dev/null +++ b/changelogs/unreleased/api-gpg-key-management.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Add GPG key management' +merge_request: 13828 +author: Robert Schilling +type: added diff --git a/doc/api/users.md b/doc/api/users.md index 57a13eb477d..57b4e117cf3 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -550,6 +550,217 @@ Parameters: Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found. +## List all GPG keys + +Get a list of currently authenticated user's GPG keys. + +``` +GET /user/gpg_keys +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys +``` + +Example response: + +```json +[ + { + "id": 1, + "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----", + "created_at": "2017-09-05T09:17:46.264Z" + } +] +``` + +## Get a specific GPG key + +Get a specific GPG key of currently authenticated user. + +``` +GET /user/gpg_keys/:key_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `key_id` | integer | yes | The ID of the GPG key | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys/1 +``` + +Example response: + +```json + { + "id": 1, + "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----", + "created_at": "2017-09-05T09:17:46.264Z" + } +``` + +## Add a GPG key + +Creates a new GPG key owned by the currently authenticated user. + +``` +POST /user/gpg_keys +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| key | string | yes | The new GPG key | + +```bash +curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys +``` + +Example response: + +```json +[ + { + "id": 1, + "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----", + "created_at": "2017-09-05T09:17:46.264Z" + } +] +``` + +## Delete a GPG key + +Delete a GPG key owned by currently authenticated user. + +``` +DELETE /user/gpg_keys/:key_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `key_id` | integer | yes | The ID of the GPG key | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys/1 +``` + +Returns `204 No Content` on success, or `404 Not found` if the key cannot be found. + +## List all GPG keys for given user + +Get a list of a specified user's GPG keys. Available only for admins. + +``` +GET /users/:id/gpg_keys +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys +``` + +Example response: + +```json +[ + { + "id": 1, + "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----", + "created_at": "2017-09-05T09:17:46.264Z" + } +] +``` + +## Get a specific GPG key for a given user + +Get a specific GPG key for a given user. Available only for admins. + +``` +GET /users/:id/gpg_keys/:key_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | +| `key_id` | integer | yes | The ID of the GPG key | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys/1 +``` + +Example response: + +```json + { + "id": 1, + "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----", + "created_at": "2017-09-05T09:17:46.264Z" + } +``` + +## Add a GPG key for a given user + +Create new GPG key owned by the specified user. Available only for admins. + +``` +POST /users/:id/gpg_keys +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | +| `key_id` | integer | yes | The ID of the GPG key | + +```bash +curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys +``` + +Example response: + +```json +[ + { + "id": 1, + "key": "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFVjnlIBCACibzXOLCiZiL2oyzYUaTOCkYnSUhymg3pdbfKtd4mpBa58xKBj\r\nt1pTHVpw3Sk03wmzhM/Ndlt1AV2YhLv++83WKr+gAHFYFiCV/tnY8bx3HqvVoy8O\r\nCfxWhw4QZK7+oYzVmJj8ZJm3ZjOC4pzuegNWlNLCUdZDx9OKlHVXLCX1iUbjdYWa\r\nqKV6tdV8hZolkbyjedQgrpvoWyeSHHpwHF7yk4gNJWMMI5rpcssL7i6mMXb/sDzO\r\nVaAtU5wiVducsOa01InRFf7QSTxoAm6Xy0PGv/k48M6xCALa9nY+BzlOv47jUT57\r\nvilf4Szy9dKD0v9S0mQ+IHB+gNukWrnwtXx5ABEBAAHNFm5hbWUgKGNvbW1lbnQp\r\nIDxlbUBpbD7CwHUEEwECACkFAlVjnlIJEINgJNgv009/AhsDAhkBBgsJCAcDAgYV\r\nCAIJCgsEFgIDAQAAxqMIAFBHuBA8P1v8DtHonIK8Lx2qU23t8Mh68HBIkSjk2H7/\r\noO2cDWCw50jZ9D91PXOOyMPvBWV2IE3tARzCvnNGtzEFRtpIEtZ0cuctxeIF1id5\r\ncrfzdMDsmZyRHAOoZ9VtuD6mzj0ybQWMACb7eIHjZDCee3Slh3TVrLy06YRdq2I4\r\nbjMOPePtK5xnIpHGpAXkB3IONxyITpSLKsA4hCeP7gVvm7r7TuQg1ygiUBlWbBYn\r\niE5ROzqZjG1s7dQNZK/riiU2umGqGuwAb2IPvNiyuGR3cIgRE4llXH/rLuUlspAp\r\no4nlxaz65VucmNbN1aMbDXLJVSqR1DuE00vEsL1AItI=\r\n=XQoy\r\n-----END PGP PUBLIC KEY BLOCK-----", + "created_at": "2017-09-05T09:17:46.264Z" + } +] +``` + +## Delete a GPG key for a given user + +Delete a GPG key owned by a specified user. Available only for admins. + +``` +DELETE /users/:id/gpg_keys/:key_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | +| `key_id` | integer | yes | The ID of the GPG key | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys/1 +``` + ## List emails Get a list of currently authenticated user's emails. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0092cc14e74..031dd02c6eb 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -491,6 +491,10 @@ module API expose :user, using: Entities::UserPublic end + class GPGKey < Grape::Entity + expose :id, :key, :created_at + end + class Note < Grape::Entity # Only Issue and MergeRequest have iid NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze diff --git a/lib/api/users.rb b/lib/api/users.rb index 96f47bb618a..1825c90a23b 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -233,6 +233,86 @@ module API destroy_conditionally!(key) 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 success Entities::Email end @@ -492,6 +572,76 @@ module API destroy_conditionally!(key) 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 success Entities::Email end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 5fef4437997..37cb95a16e3 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -4,6 +4,7 @@ describe API::Users do let(:user) { create(:user) } let(:admin) { create(:admin) } let(:key) { create(:key, user: user) } + let(:gpg_key) { create(:gpg_key, user: user) } let(:email) { create(:email, user: user) } let(:omniauth_user) { create(:omniauth_user) } let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') } @@ -753,6 +754,164 @@ describe API::Users do 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 before do admin @@ -1153,6 +1312,173 @@ describe API::Users do 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 context "when unauthenticated" do it "returns authentication error" do |