From 18e365ba5d5ce9a606cfbb414532f49491705edd Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 12 Jan 2018 12:42:14 +0100 Subject: WIP: initial feature description for key uploads This is an initial draft of the keys api for uploading other keys than the OpenPGP public key. refers #8815 --- features/1/keys.feature | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 features/1/keys.feature diff --git a/features/1/keys.feature b/features/1/keys.feature new file mode 100644 index 0000000..23af72f --- /dev/null +++ b/features/1/keys.feature @@ -0,0 +1,148 @@ +Feature: Handle current users collection of keys + + LEAP currently uses OpenPGP and is working on implementing katzenpost. + Both systems require public keys of a user to be available for retrival. + + The /1/keys endpoint allows the client to manage the public keys + registered for their users email address. + + You need to specify the type of the key when publishing it. Some + keytypes such as 'openpgp' and 'katzenpost_id' will only allow a + single key to be published. Others such as 'katzenpost_link' allow + multiple keys to be registered at the same time. We deal with this + by allowing arbitrary json data to be specified as the value of the + key. So katzenpost_link keys can be combined in a json data structure. + + POST request will register a new key. In order to replace an existing + key you need to send a PATCH request to /keys/:type including the last + revision (rev) of the key. This way we can detect conflicts between + concurrend updates. + + Background: + Given I authenticated + Given I set headers: + | Accept | application/json | + | Content-Type | application/json | + | Authorization | Token token="MY_AUTH_TOKEN" | + + Scenario: Get initial empty set of keys + When I send a GET request to "1/keys" + Then the response status should be "200" + And the response should be: + """ + {} + """ + + Scenario: Get all the keys + Given I have published a "openpgp" key + And I have published "katzenpost_kink" keys + When I send a GET request to "1/keys" + Then the response status should be "200" + And the response should be: + """ + { + "openpgp": { + "type": "openpgp", + "value": "ASDF", + "rev": "1234567890" + }, + "katzenpost_link": { + "type": "katzenpost_link", + "value": { + "one": "ASDF", + "two": "QWER" + }, + "rev": "1234567890" + } + } + """ + + Scenario: Get a single key + Given I have published a "openpgp" key + When I send a GET request to "1/keys/openpgp" + Then the response status should be "200" + And the response should be: + """ + "ASDF" + """ + + Scenario: Get a set of keys for one type + Given I have published "katzenpost_link" keys + When I send a GET request to "1/keys/katzenpost_link" + Then the response status should be "200" + And the response should be: + """ + { + "one": "ASDF", + "two": "QWER" + } + """ + + Scenario: Publish an initial OpenPGP key + When I send a POST request to "1/keys" with the following: + """ + { + "type": "openpgp", + "value": "ASDF" + } + """ + Then the response status should be "204" + + Scenario: Do not overwrite an existing key + Given I have published a "openpgp" key + When I send a POST request to "1/keys" with the following: + """ + { + "type": "openpgp", + "value": "QWER" + } + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "key already exists" + } + """ + + Scenario: Updating an existing key require revision + Given I have published a "openpgp" key + When I send a PATCH request to "1/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "value": "QWER" + } + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "no revision specified" + } + """ + + Scenario: Updating an existing key + Given I have published a "openpgp" key with revision "1234567890" + When I send a PATCH request to "1/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "value": "QWER", + "rev": "1234567890" + } + """ + Then the response status should be "204" + + Scenario: Publishing an empty key fails + When I send a POST request to "1/keys" with the following: + """ + {} + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "key type missing" + } + """ -- cgit v1.2.3 From fd2fc85c2daf60605641cc582d75134a10e7b4a4 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 12 Jan 2018 12:56:36 +0100 Subject: gitattributes: help gitlab detect gherkin --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f306504 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.feature gitlab-language=gherkin -- cgit v1.2.3 From b8ba4f27a82868e0b3338b4af761f7c44226e729 Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 15 Jan 2018 18:21:44 +0100 Subject: (WIP) first steps towards implementing keys API --- app/controllers/api/keys_controller.rb | 63 ++++++++++++ app/models/keyring.rb | 38 +++++++ config/routes.rb | 1 + features/1/keys.feature | 148 --------------------------- features/2/keys.feature | 176 +++++++++++++++++++++++++++++++++ features/step_definitions/api_steps.rb | 4 +- features/step_definitions/key_steps.rb | 20 ++++ features/support/env.rb | 2 +- test/unit/keyring_test.rb | 60 +++++++++++ 9 files changed, 361 insertions(+), 151 deletions(-) create mode 100644 app/controllers/api/keys_controller.rb create mode 100644 app/models/keyring.rb delete mode 100644 features/1/keys.feature create mode 100644 features/2/keys.feature create mode 100644 features/step_definitions/key_steps.rb create mode 100644 test/unit/keyring_test.rb diff --git a/app/controllers/api/keys_controller.rb b/app/controllers/api/keys_controller.rb new file mode 100644 index 0000000..d4cb759 --- /dev/null +++ b/app/controllers/api/keys_controller.rb @@ -0,0 +1,63 @@ +class Api::KeysController < ApiController + + before_filter :require_login + before_filter :require_enabled + + # get /keys + def index + keys = identity.keys.map do |k,v| + [k, JSON.parse(v)] + end + render json: keys.to_h + end + + def show + render json: JSON.parse(identity.keys[params[:id]]) + end + + def create + keyring.create type, value + head :no_content + rescue Keyring::Error, ActionController::ParameterMissing => e + render status: 422, json: {error: e.message} + end + + def update + keyring.update type, rev: rev, value: value + head :no_content + rescue Keyring::Error, ActionController::ParameterMissing => e + render status: 422, json: {error: e.message} + end + + protected + + def require_enabled + if !current_user.enabled? + access_denied + end + end + + def service_level + current_user.effective_service_level + end + + def type + params.require :type + end + + def value + params.require :value + end + + def rev + params.require :rev + end + + def keyring + @keyring ||= Keyring.new identity + end + + def identity + @identity ||= Identity.for(current_user) + end +end diff --git a/app/models/keyring.rb b/app/models/keyring.rb new file mode 100644 index 0000000..6779d5d --- /dev/null +++ b/app/models/keyring.rb @@ -0,0 +1,38 @@ +# +# Keyring +# +# A collection of cryptographic keys. +# + +class Keyring + class Error < RuntimeError + end + + def initialize(storage) + @storage = storage + end + + def create(type, value) + raise Error, "key already exists" if storage.keys[type].present? + storage.set_key type, {type: type, value: value, rev: new_rev}.to_json + storage.save + end + + def update(type, rev:, value:) + old_rev = key_of_type(type)['rev'] + raise Error, "wrong revision: #{rev}" unless old_rev == rev + storage.set_key type, {type: type, value: value, rev: new_rev}.to_json + storage.save + end + + def key_of_type(type) + JSON.parse(storage.keys[type]) + end + + protected + attr_reader :storage + + def new_rev + SecureRandom.urlsafe_base64(8) + end +end diff --git a/config/routes.rb b/config/routes.rb index d3d2cec..ba8f168 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -41,6 +41,7 @@ LeapWeb::Application.routes.draw do resource :service, :only => [:show] resources :configs, :only => [:index, :show] resources :identities, :only => [:show] + resources :keys, :only=> [:index, :show, :create, :update] end scope "(:locale)", :locale => CommonLanguages.match_available do diff --git a/features/1/keys.feature b/features/1/keys.feature deleted file mode 100644 index 23af72f..0000000 --- a/features/1/keys.feature +++ /dev/null @@ -1,148 +0,0 @@ -Feature: Handle current users collection of keys - - LEAP currently uses OpenPGP and is working on implementing katzenpost. - Both systems require public keys of a user to be available for retrival. - - The /1/keys endpoint allows the client to manage the public keys - registered for their users email address. - - You need to specify the type of the key when publishing it. Some - keytypes such as 'openpgp' and 'katzenpost_id' will only allow a - single key to be published. Others such as 'katzenpost_link' allow - multiple keys to be registered at the same time. We deal with this - by allowing arbitrary json data to be specified as the value of the - key. So katzenpost_link keys can be combined in a json data structure. - - POST request will register a new key. In order to replace an existing - key you need to send a PATCH request to /keys/:type including the last - revision (rev) of the key. This way we can detect conflicts between - concurrend updates. - - Background: - Given I authenticated - Given I set headers: - | Accept | application/json | - | Content-Type | application/json | - | Authorization | Token token="MY_AUTH_TOKEN" | - - Scenario: Get initial empty set of keys - When I send a GET request to "1/keys" - Then the response status should be "200" - And the response should be: - """ - {} - """ - - Scenario: Get all the keys - Given I have published a "openpgp" key - And I have published "katzenpost_kink" keys - When I send a GET request to "1/keys" - Then the response status should be "200" - And the response should be: - """ - { - "openpgp": { - "type": "openpgp", - "value": "ASDF", - "rev": "1234567890" - }, - "katzenpost_link": { - "type": "katzenpost_link", - "value": { - "one": "ASDF", - "two": "QWER" - }, - "rev": "1234567890" - } - } - """ - - Scenario: Get a single key - Given I have published a "openpgp" key - When I send a GET request to "1/keys/openpgp" - Then the response status should be "200" - And the response should be: - """ - "ASDF" - """ - - Scenario: Get a set of keys for one type - Given I have published "katzenpost_link" keys - When I send a GET request to "1/keys/katzenpost_link" - Then the response status should be "200" - And the response should be: - """ - { - "one": "ASDF", - "two": "QWER" - } - """ - - Scenario: Publish an initial OpenPGP key - When I send a POST request to "1/keys" with the following: - """ - { - "type": "openpgp", - "value": "ASDF" - } - """ - Then the response status should be "204" - - Scenario: Do not overwrite an existing key - Given I have published a "openpgp" key - When I send a POST request to "1/keys" with the following: - """ - { - "type": "openpgp", - "value": "QWER" - } - """ - Then the response status should be "422" - And the response should be: - """ - { - "error": "key already exists" - } - """ - - Scenario: Updating an existing key require revision - Given I have published a "openpgp" key - When I send a PATCH request to "1/keys/openpgp" with the following: - """ - { - "type": "openpgp", - "value": "QWER" - } - """ - Then the response status should be "422" - And the response should be: - """ - { - "error": "no revision specified" - } - """ - - Scenario: Updating an existing key - Given I have published a "openpgp" key with revision "1234567890" - When I send a PATCH request to "1/keys/openpgp" with the following: - """ - { - "type": "openpgp", - "value": "QWER", - "rev": "1234567890" - } - """ - Then the response status should be "204" - - Scenario: Publishing an empty key fails - When I send a POST request to "1/keys" with the following: - """ - {} - """ - Then the response status should be "422" - And the response should be: - """ - { - "error": "key type missing" - } - """ diff --git a/features/2/keys.feature b/features/2/keys.feature new file mode 100644 index 0000000..cc87da0 --- /dev/null +++ b/features/2/keys.feature @@ -0,0 +1,176 @@ +Feature: Handle current users collection of keys + + LEAP currently uses OpenPGP and is working on implementing katzenpost. + Both systems require public keys of a user to be available for retrival. + + The /2/keys endpoint allows the client to manage the public keys + registered for their users email address. + + You need to specify the type of the key when publishing it. Some + keytypes such as 'openpgp' and 'katzenpost_id' will only allow a + single key to be published. Others such as 'katzenpost_link' allow + multiple keys to be registered at the same time. We deal with this + by allowing arbitrary json data to be specified as the value of the + key. So katzenpost_link keys can be combined in a json data structure. + + POST request will register a new key. In order to replace an existing + key you need to send a PATCH request to /keys/:type including the last + revision (rev) of the key. This way we can detect conflicts between + concurrend updates. + + Background: + Given I authenticated + Given I set headers: + | Accept | application/json | + | Content-Type | application/json | + | Authorization | Token token="MY_AUTH_TOKEN" | + + Scenario: Get initial empty set of keys + When I send a GET request to "2/keys" + Then the response status should be "200" + And the response should be: + """ + {} + """ + + Scenario: Get all the keys + Given I have published a "openpgp" key + And I have published "katzenpost_link" keys + When I send a GET request to "2/keys" + Then the response status should be "200" + And the response should be: + """ + { + "openpgp": { + "type": "openpgp", + "value": "DUMMY_KEY", + "rev": "DUMMY_REV" + }, + "katzenpost_link": { + "type": "katzenpost_link", + "value": { + "one": "DUMMY_KEY", + "two": "DUMMY_KEY" + }, + "rev": "DUMMY_REV" + } + } + """ + + Scenario: Get a single key + Given I have published a "openpgp" key + When I send a GET request to "2/keys/openpgp" + Then the response status should be "200" + And the response should be: + """ + { + "type": "openpgp", + "value": "DUMMY_KEY", + "rev": "DUMMY_REV" + } + """ + + Scenario: Get a set of keys for one type + Given I have published "katzenpost_link" keys + When I send a GET request to "2/keys/katzenpost_link" + Then the response status should be "200" + And the response should be: + """ + { + "type": "katzenpost_link", + "value": { + "one": "DUMMY_KEY", + "two": "DUMMY_KEY" + }, + "rev": "DUMMY_REV" + } + """ + + Scenario: Publish an initial OpenPGP key + When I send a POST request to "2/keys" with the following: + """ + { + "type": "openpgp", + "value": "ASDF" + } + """ + Then the response status should be "204" + And I should have published a "openpgp" key + + Scenario: Do not overwrite an existing key + Given I have published a "openpgp" key + When I send a POST request to "2/keys" with the following: + """ + { + "type": "openpgp", + "value": "QWER" + } + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "key already exists" + } + """ + + Scenario: Updating an existing key + Given I have published a "openpgp" key + When I send a PATCH request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "value": "QWER", + "rev": "DUMMY_REV" + } + """ + Then the response status should be "204" + And I should have published a "openpgp" key with value "QWER" + + Scenario: Updating an existing key require revision + Given I have published a "openpgp" key + When I send a PATCH request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "value": "QWER" + } + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "param is missing or the value is empty: rev" + } + """ + + Scenario: Updating an existing key require right revision + Given I have published a "openpgp" key + When I send a PATCH request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "value": "QWER", + "rev": "WRONG_REV" + } + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "wrong revision: WRONG_REV" + } + """ + + Scenario: Publishing an empty key fails + When I send a POST request to "2/keys" with the following: + """ + {} + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "param is missing or the value is empty: type" + } + """ diff --git a/features/step_definitions/api_steps.rb b/features/step_definitions/api_steps.rb index 7188694..7b73272 100644 --- a/features/step_definitions/api_steps.rb +++ b/features/step_definitions/api_steps.rb @@ -37,7 +37,7 @@ When /^I digest\-authenticate as the user "(.*?)" with the password "(.*?)"$/ do digest_authorize user, pass end -When /^I (?:have sent|send) a (GET|POST|PUT|DELETE) request (?:for|to) "([^"]*)"(?: with the following:)?$/ do |*args| +When /^I (?:have sent|send) a (GET|POST|PUT|DELETE|PATCH) request (?:for|to) "([^"]*)"(?: with the following:)?$/ do |*args| request_type = args.shift path = args.shift input = args.shift @@ -45,7 +45,7 @@ When /^I (?:have sent|send) a (GET|POST|PUT|DELETE) request (?:for|to) "([^"]*)" request_opts = {method: request_type.downcase.to_sym} unless input.nil? - if input.class == Cucumber::Ast::Table + if input.class == Cucumber::MultilineArgument::DataTable request_opts[:params] = input.rows_hash else request_opts[:input] = input diff --git a/features/step_definitions/key_steps.rb b/features/step_definitions/key_steps.rb new file mode 100644 index 0000000..70a13bd --- /dev/null +++ b/features/step_definitions/key_steps.rb @@ -0,0 +1,20 @@ +Given /^I have published a "([^"]*)" key$/ do |type| + identity = Identity.for(@user) + keyring = Keyring.new(identity) + SecureRandom.stubs(urlsafe_base64: 'DUMMY_REV') + keyring.create type, 'DUMMY_KEY' +end + +Given /^I have published "([^"]*)" keys$/ do |type| + identity = Identity.for(@user) + keyring = Keyring.new(identity) + SecureRandom.stubs(urlsafe_base64: 'DUMMY_REV') + keyring.create type, one: 'DUMMY_KEY', two: 'DUMMY_KEY' +end + +Then /^I should have published an? "([^"]*)" key(?: with value "([^"]*)")?$/ do |type, value| + identity = Identity.for(@user) + keys = identity.keys + assert_includes keys.keys, type + assert_equal value, JSON.parse(keys[type])['value'] if value +end diff --git a/features/support/env.rb b/features/support/env.rb index d3067db..d722b8e 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -55,4 +55,4 @@ end # The :transaction strategy is faster, but might give you threading problems. # See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature Cucumber::Rails::Database.javascript_strategy = :truncation - +require 'mocha/setup' diff --git a/test/unit/keyring_test.rb b/test/unit/keyring_test.rb new file mode 100644 index 0000000..059b8dd --- /dev/null +++ b/test/unit/keyring_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' + +class KeyringTest < ActiveSupport::TestCase + + test 'create initial key' do + keyring.create 'type', 'value' + assert_equal 'value', keyring.key_of_type('type')['value'] + end + + test 'raise on creating twice' do + keyring.create 'type', 'value' + assert_raises Keyring::Error do + keyring.create 'type', 'value' + end + end + + test 'update with new key' do + keyring.create 'type', 'value' + initial_rev = keyring.key_of_type('type')['rev'] + keyring.update 'type', rev: initial_rev, value: 'new value' + assert_equal 'new value', keyring.key_of_type('type')['value'] + end + + test 'raise on updating without rev' do + keyring.create 'type', 'value' + assert_raises Keyring::Error do + keyring.update 'type', rev: nil ,value: 'new value' + end + assert_equal 'value', keyring.key_of_type('type')['value'] + end + + test 'raise on updating with wrong rev' do + keyring.create 'type', 'value' + assert_raises Keyring::Error do + keyring.update 'type', rev: 'wrong rev', value: 'new value' + end + assert_equal 'value', keyring.key_of_type('type')['value'] + end + + + protected + + def keyring + @keyring ||= Keyring.new(teststorage) + end + + def teststorage + @teststorage ||= Hash.new.tap do |dummy| + def dummy.set_key(type, value) + self[type] = value + end + + def dummy.keys + self + end + + def dummy.save; end + end + end +end -- cgit v1.2.3 From 54653f75cf44890310a06c3a8a6be59625629d2a Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 19 Jan 2018 14:11:24 +0100 Subject: API: implement deleting keys through new keys api --- app/controllers/api/keys_controller.rb | 12 +++++ app/models/identity.rb | 5 ++ app/models/keyring.rb | 23 +++++++-- config/routes.rb | 2 +- features/2/keys.feature | 89 ++++++++++++++++++++++++++++++++-- features/step_definitions/key_steps.rb | 6 +++ test/unit/identity_test.rb | 8 +++ test/unit/keyring_test.rb | 40 +++++++++++++++ 8 files changed, 177 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/keys_controller.rb b/app/controllers/api/keys_controller.rb index d4cb759..7eb76ee 100644 --- a/app/controllers/api/keys_controller.rb +++ b/app/controllers/api/keys_controller.rb @@ -25,10 +25,22 @@ class Api::KeysController < ApiController def update keyring.update type, rev: rev, value: value head :no_content + rescue Keyring::NotFound => e + render status: 404, json: {error: e.message} rescue Keyring::Error, ActionController::ParameterMissing => e render status: 422, json: {error: e.message} end + def destroy + keyring.delete type, rev: rev + head :no_content + rescue Keyring::NotFound => e + render status: 404, json: {error: e.message} + rescue Keyring::Error, ActionController::ParameterMissing => e + render status: 422, json: {error: e.message} + end + + protected def require_enabled diff --git a/app/models/identity.rb b/app/models/identity.rb index 92f8f7a..b8c2245 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -136,6 +136,11 @@ class Identity < CouchRest::Model::Base write_attribute('keys', keys.merge(type => key.to_s)) end + def delete_key(type) + raise 'key not found' unless keys[type] + write_attribute('keys', keys.except(type)) + end + def cert_fingerprints read_attribute('cert_fingerprints') || Hash.new end diff --git a/app/models/keyring.rb b/app/models/keyring.rb index 6779d5d..66f7bfd 100644 --- a/app/models/keyring.rb +++ b/app/models/keyring.rb @@ -8,6 +8,12 @@ class Keyring class Error < RuntimeError end + class NotFound < Error + def initialize(type) + super "no such key: #{type}" + end + end + def initialize(storage) @storage = storage end @@ -19,19 +25,30 @@ class Keyring end def update(type, rev:, value:) - old_rev = key_of_type(type)['rev'] - raise Error, "wrong revision: #{rev}" unless old_rev == rev + check_rev type, rev storage.set_key type, {type: type, value: value, rev: new_rev}.to_json storage.save end + def delete(type, rev:) + check_rev type, rev + storage.delete_key type + storage.save + end + def key_of_type(type) - JSON.parse(storage.keys[type]) + JSON.parse(storage.keys[type]) if storage.keys[type] end protected attr_reader :storage + def check_rev(type, rev) + old = key_of_type(type) + raise NotFound, type unless old + raise Error, "wrong revision: #{rev}" unless old['rev'] == rev + end + def new_rev SecureRandom.urlsafe_base64(8) end diff --git a/config/routes.rb b/config/routes.rb index ba8f168..55d03fa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -41,7 +41,7 @@ LeapWeb::Application.routes.draw do resource :service, :only => [:show] resources :configs, :only => [:index, :show] resources :identities, :only => [:show] - resources :keys, :only=> [:index, :show, :create, :update] + resources :keys, :except=> [:edit, :new] end scope "(:locale)", :locale => CommonLanguages.match_available do diff --git a/features/2/keys.feature b/features/2/keys.feature index cc87da0..83e70e7 100644 --- a/features/2/keys.feature +++ b/features/2/keys.feature @@ -114,6 +114,19 @@ Feature: Handle current users collection of keys } """ + Scenario: Publishing an empty key fails + When I send a POST request to "2/keys" with the following: + """ + {} + """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "param is missing or the value is empty: type" + } + """ + Scenario: Updating an existing key Given I have published a "openpgp" key When I send a PATCH request to "2/keys/openpgp" with the following: @@ -127,6 +140,24 @@ Feature: Handle current users collection of keys Then the response status should be "204" And I should have published a "openpgp" key with value "QWER" + Scenario: Updating a missing key raises + When I send a PATCH request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "value": "QWER", + "rev": "DUMMY_REV" + } + """ + Then the response status should be "404" + And the response should be: + """ + { + "error": "no such key: openpgp" + } + """ + And I should not have published a "openpgp" key + Scenario: Updating an existing key require revision Given I have published a "openpgp" key When I send a PATCH request to "2/keys/openpgp" with the following: @@ -162,15 +193,65 @@ Feature: Handle current users collection of keys } """ - Scenario: Publishing an empty key fails - When I send a POST request to "2/keys" with the following: + Scenario: Deleting an existing key + Given I have published a "openpgp" key + When I send a DELETE request to "2/keys/openpgp" with the following: """ - {} + { + "type": "openpgp", + "rev": "DUMMY_REV" + } + """ + Then the response status should be "204" + And I should not have published a "openpgp" key + + Scenario: Deleting a missing key raises + When I send a DELETE request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "rev": "DUMMY_REV" + } + """ + Then the response status should be "404" + And the response should be: + """ + { + "error": "no such key: openpgp" + } + """ + + Scenario: Deleting an existing key require revision + Given I have published a "openpgp" key + When I send a DELETE request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp" + } """ Then the response status should be "422" And the response should be: """ { - "error": "param is missing or the value is empty: type" + "error": "param is missing or the value is empty: rev" + } + """ + And I should have published a "openpgp" key + + Scenario: Deleting an existing key require right revision + Given I have published a "openpgp" key + When I send a DELETE request to "2/keys/openpgp" with the following: + """ + { + "type": "openpgp", + "rev": "WRONG_REV" } """ + Then the response status should be "422" + And the response should be: + """ + { + "error": "wrong revision: WRONG_REV" + } + """ + And I should have published a "openpgp" key diff --git a/features/step_definitions/key_steps.rb b/features/step_definitions/key_steps.rb index 70a13bd..3d5e015 100644 --- a/features/step_definitions/key_steps.rb +++ b/features/step_definitions/key_steps.rb @@ -18,3 +18,9 @@ Then /^I should have published an? "([^"]*)" key(?: with value "([^"]*)")?$/ do assert_includes keys.keys, type assert_equal value, JSON.parse(keys[type])['value'] if value end + +Then /^I should not have published an? "([^"]*)" key$/ do |type| + identity = Identity.for(@user) + keys = identity.keys + refute_includes keys.keys, type +end diff --git a/test/unit/identity_test.rb b/test/unit/identity_test.rb index 6836487..43f644a 100644 --- a/test/unit/identity_test.rb +++ b/test/unit/identity_test.rb @@ -80,6 +80,14 @@ class IdentityTest < ActiveSupport::TestCase assert_equal pgp_key_string, @id.keys[:pgp] end + test "deleting pgp key" do + @id = Identity.for(@user) + @id.set_key(:pgp, pgp_key_string) + @id.delete_key(:pgp) + assert_nil @id.keys[:pgp] + assert_equal Hash.new, @id.keys + end + test "querying pgp key via couch" do @id = Identity.for(@user) @id.set_key(:pgp, pgp_key_string) diff --git a/test/unit/keyring_test.rb b/test/unit/keyring_test.rb index 059b8dd..c7df63e 100644 --- a/test/unit/keyring_test.rb +++ b/test/unit/keyring_test.rb @@ -21,6 +21,13 @@ class KeyringTest < ActiveSupport::TestCase assert_equal 'new value', keyring.key_of_type('type')['value'] end + test 'raise on updating missing key' do + assert_raises Keyring::NotFound do + keyring.update 'type', rev: nil ,value: 'new value' + end + assert_nil keyring.key_of_type('type') + end + test 'raise on updating without rev' do keyring.create 'type', 'value' assert_raises Keyring::Error do @@ -37,6 +44,35 @@ class KeyringTest < ActiveSupport::TestCase assert_equal 'value', keyring.key_of_type('type')['value'] end + test 'delete key' do + keyring.create 'type', 'value' + initial_rev = keyring.key_of_type('type')['rev'] + keyring.delete 'type', rev: initial_rev + assert_nil keyring.key_of_type('type') + end + + test 'raise on deleting missing key' do + assert_raises Keyring::NotFound do + keyring.delete 'type', rev: nil + end + end + + test 'raise on deleting without rev' do + keyring.create 'type', 'value' + assert_raises Keyring::Error do + keyring.delete 'type', rev: nil + end + assert_equal 'value', keyring.key_of_type('type')['value'] + end + + test 'raise on deleting with wrong rev' do + keyring.create 'type', 'value' + assert_raises Keyring::Error do + keyring.delete 'type', rev: 'wrong rev' + end + assert_equal 'value', keyring.key_of_type('type')['value'] + end + protected @@ -54,6 +90,10 @@ class KeyringTest < ActiveSupport::TestCase self end + def dummy.delete_key(type) + self.delete(type) + end + def dummy.save; end end end -- cgit v1.2.3