diff options
author | Azul <azul@riseup.net> | 2016-05-25 13:13:30 +0200 |
---|---|---|
committer | Azul <azul@riseup.net> | 2016-05-25 14:13:30 +0200 |
commit | 8a81429f0eb8aa5041d47557d0c5b5359bb959e6 (patch) | |
tree | 10f5d3db69883c685408edc3365d1e762f13e322 | |
parent | 5c0fa0fb7b10820f2956807cb457421bf1e00708 (diff) |
copy over all files from rewritten attempt
I started a nickserver from scratch to implement the things that are independent of our choice of stack (eventmachine or other).
This commit copies them over and tests both things in parallel.
32 files changed, 564 insertions, 14 deletions
diff --git a/lib/nickserver/adapters.rb b/lib/nickserver/adapters.rb new file mode 100644 index 0000000..c87cb63 --- /dev/null +++ b/lib/nickserver/adapters.rb @@ -0,0 +1,4 @@ +module Nickserver + module Adapters + end +end diff --git a/lib/nickserver/adapters/couch_db.rb b/lib/nickserver/adapters/couch_db.rb new file mode 100644 index 0000000..e8a23ad --- /dev/null +++ b/lib/nickserver/adapters/couch_db.rb @@ -0,0 +1,12 @@ +require 'nickserver/adapters' + +class Nickserver::Adapters::CouchDB + + + protected + + def query_couch(nick) + yield 404, "{}" + end + +end diff --git a/lib/nickserver/adapters/local.rb b/lib/nickserver/adapters/local.rb new file mode 100644 index 0000000..d6210c3 --- /dev/null +++ b/lib/nickserver/adapters/local.rb @@ -0,0 +1,8 @@ +require 'nickserver/adapters' + +class Nickserver::Adapters::Local + + def query(nick) + end + +end diff --git a/lib/nickserver/adapters/remote.rb b/lib/nickserver/adapters/remote.rb new file mode 100644 index 0000000..e12bd26 --- /dev/null +++ b/lib/nickserver/adapters/remote.rb @@ -0,0 +1,4 @@ +require 'nickserver/adapters' + +class Nickserver::Adapters::Remote +end diff --git a/lib/nickserver/couch_db.rb b/lib/nickserver/couch_db.rb new file mode 100644 index 0000000..5c63fc1 --- /dev/null +++ b/lib/nickserver/couch_db.rb @@ -0,0 +1,4 @@ +module Nickserver + module CouchDB + end +end diff --git a/lib/nickserver/couch_db/response.rb b/lib/nickserver/couch_db/response.rb new file mode 100644 index 0000000..c6afe03 --- /dev/null +++ b/lib/nickserver/couch_db/response.rb @@ -0,0 +1,51 @@ +require 'nickserver/couch_db' +require 'json' + +module Nickserver::CouchDB + class Response + + def initialize(nick, couch_response = {}) + @nick = nick + @couch_status = couch_response[:status] + @json = JSON.load(couch_response[:body]) if couch_status == 200 + end + + def status + if ok? && empty? then 404 + else couch_status + end + end + + def content + key_response if ok? && !empty? + end + + protected + + def key_response + format address: nick.to_s, openpgp: key + end + + def format(response) + response.to_json + end + + def key + rows.first["value"] + end + + def ok? + couch_status == 200 + end + + def empty? + rows.empty? + end + + def rows + json["rows"] + end + + attr_reader :couch_status, :json, :nick + end +end diff --git a/lib/nickserver/couch_db/source.rb b/lib/nickserver/couch_db/source.rb new file mode 100644 index 0000000..fffa76e --- /dev/null +++ b/lib/nickserver/couch_db/source.rb @@ -0,0 +1,34 @@ +# +# This class allows querying couch for public keys. +# +require 'nickserver/couch_db/response' + +module Nickserver::CouchDB + class Source + + VIEW = '/_design/Identity/_view/pgp_key_by_email' + + def initialize(adapter) + @adapter = adapter + end + + def query(nick) + adapter.get VIEW, query: query_for(nick) do |status, body| + yield Response.new nick, status: status, body: body + end + end + + protected + + def query_for(nick) + { reduce: "false", key: "\"#{nick}\"" } + end + + def adapter + @adapter + # Nickserver::Adapters::Http.new(config) + end + + attr_reader :config + end +end diff --git a/lib/nickserver/hkp.rb b/lib/nickserver/hkp.rb new file mode 100644 index 0000000..bb82a20 --- /dev/null +++ b/lib/nickserver/hkp.rb @@ -0,0 +1,4 @@ +module Nickserver + module Hkp + end +end diff --git a/lib/nickserver/hkp/fetch_key.rb b/lib/nickserver/hkp/fetch_key.rb index 44621d3..55eeefa 100644 --- a/lib/nickserver/hkp/fetch_key.rb +++ b/lib/nickserver/hkp/fetch_key.rb @@ -5,7 +5,7 @@ require 'em-http' # http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00 # -module Nickserver; module HKP +module Nickserver; module Hkp class FetchKey include EM::Deferrable diff --git a/lib/nickserver/hkp/fetch_key_info.rb b/lib/nickserver/hkp/fetch_key_info.rb index 2448bb1..b7481d0 100644 --- a/lib/nickserver/hkp/fetch_key_info.rb +++ b/lib/nickserver/hkp/fetch_key_info.rb @@ -4,7 +4,7 @@ require 'em-http' # used to fetch an array of KeyInfo objects that match the given uid. # -module Nickserver; module HKP +module Nickserver; module Hkp class FetchKeyInfo include EM::Deferrable diff --git a/lib/nickserver/hkp/key_info.rb b/lib/nickserver/hkp/key_info.rb index adb75d8..dc0443c 100644 --- a/lib/nickserver/hkp/key_info.rb +++ b/lib/nickserver/hkp/key_info.rb @@ -9,7 +9,7 @@ require 'cgi' # format definition of machine readable index output is here: # http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5.2 # -module Nickserver; module HKP +module Nickserver; module Hkp class KeyInfo attr_accessor :uids, :keyid, :algo, :keylen, :creationdate, :expirationdate, :flags diff --git a/lib/nickserver/hkp/parse_key_info.rb b/lib/nickserver/hkp/parse_key_info.rb index 8934829..d8abe4f 100644 --- a/lib/nickserver/hkp/parse_key_info.rb +++ b/lib/nickserver/hkp/parse_key_info.rb @@ -1,11 +1,11 @@ # -# Simple parser for HKP KeyInfo responses. +# Simple parser for Hkp KeyInfo responses. # # Focus is on simple here. Trying to avoid state and sideeffects. # Parsing a response with 12 keys and validating them takes 2ms. # So no need for memoization and making things more complex. # -module Nickserver; module HKP +module Nickserver; module Hkp class ParseKeyInfo # for this regexp to work, the source text must end in a trailing "\n", diff --git a/lib/nickserver/hkp/v_index_response.rb b/lib/nickserver/hkp/v_index_response.rb new file mode 100644 index 0000000..865d476 --- /dev/null +++ b/lib/nickserver/hkp/v_index_response.rb @@ -0,0 +1,96 @@ +require 'nickserver/hkp' +require 'nickserver/hkp/key_info' + +# +# Simple parser for Hkp KeyInfo responses. +# +# Focus is on simple here. Trying to avoid state and sideeffects. +# Parsing a response with 12 keys and validating them takes 2ms. +# So no need for memoization and making things more complex. +module Nickserver::Hkp + class VIndexResponse + + # for this regexp to work, the source text must end in a trailing "\n", + # which the output of sks does. + MATCH_PUB_KEY = /(^pub:.+?\n(^uid:.+?\n)+)/m + + # hkp_response -- raw output from a vindex hkp query (machine readable) + def initialize(nick, hkp_response) + @nick = nick.to_s + @vindex_result = hkp_response[:body] + end + + def status + if keys.empty? + error_status + else + 200 + end + end + + def keys + key_infos.reject { |key| error_for_key(key) } + end + + def msg + if errors.any? + error_messages.join "\n" + else + "Could not fetch keyinfo." + end + end + + protected + + attr_reader :vindex_result, :nick + + def error_status + if errors.any? + 500 + else + 404 + end + end + + def errors + key_infos.map{|key| error_for_key(key) }.compact + end + + def error_messages + key_infos.map do |key| + err = error_for_key(key) + error_message(key, err) + end.compact + end + + def key_infos + all_key_infos.select do |key_info| + key_info.uids.include?(nick) + end + end + + def all_key_infos + @all_key_infos ||= vindex_result.scan(MATCH_PUB_KEY).map do |match| + KeyInfo.new(match[0]) + end + end + + def error_message(key, err) + "Ignoring key #{key.keyid} for #{nick}: #{err}" if err + end + + def error_for_key(key) + if key.keylen < 2048 + "key length is too short." + elsif key.expired? + "key expired." + elsif key.revoked? + "key revoked." + elsif key.disabled? + "key disabled." + elsif key.expirationdate && key.expirationdate < Time.now + "key expired" + end + end + end +end diff --git a/lib/nickserver/invalid_source.rb b/lib/nickserver/invalid_source.rb new file mode 100644 index 0000000..dac245a --- /dev/null +++ b/lib/nickserver/invalid_source.rb @@ -0,0 +1,14 @@ +# +# This is a dummy source for invalid queries. +# It simply always returns 500 and "Not a valid address" +# + +module Nickserver + class InvalidSource + + def query(nick) + yield 500, "Not a valid address" + end + + end +end diff --git a/lib/nickserver/lookup.rb b/lib/nickserver/lookup.rb new file mode 100644 index 0000000..105e77e --- /dev/null +++ b/lib/nickserver/lookup.rb @@ -0,0 +1,31 @@ +require 'nickserver/invalid_source' + +module Nickserver + class Lookup + + attr_reader :nick + + def initialize(nick) + @nick = nick + end + + def respond_with(responder) + query do |status, content| + responder.send_response status: status, content: content + end + end + + protected + + def query(&block) + source.query nick, &block + end + + def source + if nick.invalid? then Nickserver::InvalidSource + elsif nick.local? then Nickserver::Config.local_source + else Nickserver::Config.remote_source + end + end + end +end diff --git a/lib/nickserver/nickname.rb b/lib/nickserver/nickname.rb new file mode 100644 index 0000000..938d4a4 --- /dev/null +++ b/lib/nickserver/nickname.rb @@ -0,0 +1,51 @@ +module Nickserver + class Nickname + + EmailAddress = begin + qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]' + dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]' + atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+' + quoted_pair = '\\x5c[\\x00-\\x7f]' + domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d" + quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22" + domain_ref = atom + sub_domain = "(?:#{domain_ref}|#{domain_literal})" + word = "(?:#{atom}|#{quoted_string})" + domain = "#{sub_domain}(?:\\x2e#{sub_domain})*" + local_part = "#{word}(?:\\x2e#{word})*" + addr_spec = "#{local_part}\\x40#{domain}" + /\A#{addr_spec}\z/n + end + + LOCAL_DOMAIN = 'test.me' + + def initialize(address) + @address = address.to_s + end + + def valid? + address =~ EmailAddress + end + + def invalid? + !valid? + end + + def local? + address.end_with? LOCAL_DOMAIN + end + + def remote? + !local? + end + + def to_s + address + end + + protected + + attr_reader :address + + end +end diff --git a/lib/nickserver/response.rb b/lib/nickserver/response.rb new file mode 100644 index 0000000..c55d359 --- /dev/null +++ b/lib/nickserver/response.rb @@ -0,0 +1,15 @@ +module Nickserver + class Response + + attr_reader :status, :message + + def initialize(status, message) + @status = status + @message = message + end + + def content + "#{status} #{message}" + end + end +end diff --git a/lib/nickserver/server.rb b/lib/nickserver/server.rb index 32afdae..8329406 100644 --- a/lib/nickserver/server.rb +++ b/lib/nickserver/server.rb @@ -90,7 +90,7 @@ module Nickserver fetcher = if local_address?(uid) Nickserver::Couch::FetchKey.new else - Nickserver::HKP::FetchKey.new + Nickserver::Hkp::FetchKey.new end fetcher.get(uid).callback {|key| yield key diff --git a/lib/server.rb b/lib/server.rb new file mode 100644 index 0000000..4e7cf51 --- /dev/null +++ b/lib/server.rb @@ -0,0 +1,19 @@ +class Server + + def process_http_request + lookup.respond_with(Responder) + end + + def lookup + LookupFactory.lookup_for(nick) + end + + def nick + Nickname.new(request.address) + end + + def request + Request.new(params) + end + +end diff --git a/test/file_content.rb b/test/file_content.rb new file mode 100644 index 0000000..0d0ac21 --- /dev/null +++ b/test/file_content.rb @@ -0,0 +1,11 @@ +module FileContent + + def file_content(filename) + (@file_contents ||= {})[filename] ||= File.read(file_path(filename)) + end + + def file_path(filename) + "%s/files/%s" % [File.dirname(__FILE__), filename] + end + +end diff --git a/test/integration/couch_db/source_test.rb b/test/integration/couch_db/source_test.rb new file mode 100644 index 0000000..9e319f4 --- /dev/null +++ b/test/integration/couch_db/source_test.rb @@ -0,0 +1,29 @@ +require 'test_helper' +require 'file_content' +require 'nickserver/couch_db/source' + +module Nickserver::CouchDB + class SourceTest < Minitest::Test + include FileContent + + class TestAdapter + def initialize(status, content) + @status = status + @content = content + end + + def get(url, opts) + yield @status, @content + end + end + + def test_couch_query_and_response + adapter = TestAdapter.new 200, file_content(:blue_couchdb_result) + source = Source.new adapter + source.query 'blue@example.org' do |response| + assert_equal 200, response.status + assert_equal file_content(:blue_nickserver_result), response.content + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index d4765bc..842f05f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require 'bundler/setup' require 'minitest/autorun' require 'webmock/minitest' require 'nickserver' +require 'minitest/pride' TESTING = true diff --git a/test/unit/adapters/couch_db.rb b/test/unit/adapters/couch_db.rb new file mode 100644 index 0000000..339e4f4 --- /dev/null +++ b/test/unit/adapters/couch_db.rb @@ -0,0 +1,15 @@ +require 'testhelper' +require 'nickserver/adapters/couch_db' + +class Nickserver::Adapters::CouchDBTest < Minitest::Test + + def test_query_404 + adapter.query(nil) do |status, content| + assert_equal 404, status + end + end + + def adapter + @adapter ||= Nickserver::Adapters::CouchDB.new + end +end diff --git a/test/unit/adapters/local_test.rb b/test/unit/adapters/local_test.rb new file mode 100644 index 0000000..97d394f --- /dev/null +++ b/test/unit/adapters/local_test.rb @@ -0,0 +1,5 @@ +require 'test_helper' +require 'nickserver/adapters/local' + +class Nickserver::Adapters::LocalTest < Minitest::Test +end diff --git a/test/unit/couch_db/response_test.rb b/test/unit/couch_db/response_test.rb new file mode 100644 index 0000000..d44760d --- /dev/null +++ b/test/unit/couch_db/response_test.rb @@ -0,0 +1,30 @@ +require 'test_helper' +require 'file_content' +require 'nickserver/couch_db/response' + +class Nickserver::CouchDB::ResponseTest < Minitest::Test + include FileContent + + def test_404 + response = response_for "bananas@example.org", + status: 404, body: "{}" + assert_equal 404, response.status + end + + def test_200_with_empty_response + response = response_for "stompy@example.org", + status: 200, body: file_content(:empty_couchdb_result) + assert_equal 404, response.status + end + + def test_200_with_success + response = response_for "blue@example.org", + status: 200, body: file_content(:blue_couchdb_result) + assert_equal 200, response.status + assert_equal file_content(:blue_nickserver_result), response.content + end + + def response_for(uid, couch_response = {}) + Nickserver::CouchDB::Response.new uid, couch_response + end +end diff --git a/test/unit/couch_db/source_unit_test.rb b/test/unit/couch_db/source_unit_test.rb new file mode 100644 index 0000000..19ea9bc --- /dev/null +++ b/test/unit/couch_db/source_unit_test.rb @@ -0,0 +1,17 @@ +require 'test_helper' +require 'nickserver/couch_db/source' + +module Nickserver::CouchDB + class SourceUnitTest < Minitest::Test + + def test_query + address = "nick@domain.tl" + adapter = Minitest::Mock.new + adapter.expect :get, nil, + [String, {query: { reduce: "false", key: "\"#{address}\"" }}] + query = Source.new(adapter) + query.query address + adapter.verify + end + end +end diff --git a/test/unit/hkp/v_index_response_test.rb b/test/unit/hkp/v_index_response_test.rb new file mode 100644 index 0000000..d909520 --- /dev/null +++ b/test/unit/hkp/v_index_response_test.rb @@ -0,0 +1,17 @@ +require 'test_helper' +require 'file_content' +require 'nickserver/hkp/v_index_response' + +class Nickserver::Hkp::VIndexResponseTest < Minitest::Test + include FileContent + + def test_leap_public_key + response = response_for 'cloudadmin@leap.se', + body: file_content(:leap_vindex_result) + assert_equal 'E818C478D3141282F7590D29D041EB11B1647490', response.keys.first.keyid + end + + def response_for(uid, hkp_response = {}) + Nickserver::Hkp::VIndexResponse.new uid, hkp_response + end +end diff --git a/test/unit/hkp_test.rb b/test/unit/hkp_test.rb index 0ecde2d..fc3cf97 100644 --- a/test/unit/hkp_test.rb +++ b/test/unit/hkp_test.rb @@ -29,7 +29,7 @@ class HkpTest < Minitest::Test def test_key_info_not_found uid = 'leaping_lemur@leap.se' stub_sks_vindex_reponse(uid, status: 404) - test_em_errback "Nickserver::HKP::FetchKeyInfo.new.search '#{uid}'" do |error| + test_em_errback "Nickserver::Hkp::FetchKeyInfo.new.search '#{uid}'" do |error| assert_equal 404, error end end @@ -37,7 +37,7 @@ class HkpTest < Minitest::Test def test_no_matching_key_found uid = 'leaping_lemur@leap.se' stub_sks_vindex_reponse(uid, status: 200) - test_em_errback "Nickserver::HKP::FetchKeyInfo.new.search '#{uid}'" do |error| + test_em_errback "Nickserver::Hkp::FetchKeyInfo.new.search '#{uid}'" do |error| assert_equal 404, error end end @@ -48,7 +48,7 @@ class HkpTest < Minitest::Test stub_sks_vindex_reponse(uid, body: file_content(:leap_vindex_result)) stub_sks_get_reponse(key_id, body: file_content(:leap_public_key)) - test_em_callback "Nickserver::HKP::FetchKey.new.get '#{uid}'" do |key_text| + test_em_callback "Nickserver::Hkp::FetchKey.new.get '#{uid}'" do |key_text| assert_equal file_content(:leap_public_key), key_text end end @@ -60,7 +60,7 @@ class HkpTest < Minitest::Test stub_sks_vindex_reponse(uid, body: file_content(:leap_vindex_result)) stub_sks_get_reponse(key_id, status: 404) - test_em_errback "Nickserver::HKP::FetchKey.new.get '#{uid}'" do |error| + test_em_errback "Nickserver::Hkp::FetchKey.new.get '#{uid}'" do |error| assert_equal 404, error end end @@ -70,7 +70,7 @@ class HkpTest < Minitest::Test key_id = '9A753A6B' stub_sks_vindex_reponse(uid, body: file_content(:short_key_vindex_result)) - test_em_errback "Nickserver::HKP::FetchKey.new.get '#{uid}'" do |error| + test_em_errback "Nickserver::Hkp::FetchKey.new.get '#{uid}'" do |error| assert_equal 500, error end end @@ -83,7 +83,7 @@ class HkpTest < Minitest::Test def test_key_info_real_network real_network do uid = 'elijah@riseup.net' - test_em_callback "Nickserver::HKP::FetchKeyInfo.new.search '#{uid}'" do |keys| + test_em_callback "Nickserver::Hkp::FetchKeyInfo.new.search '#{uid}'" do |keys| assert_equal 1, keys.size assert keys.first.keyid =~ /00440025$/ end @@ -100,7 +100,7 @@ class HkpTest < Minitest::Test #stub_config(:hkp_ca_file, file_path('autistici-ca.pem')) do assert File.exist?(Nickserver::Config.hkp_ca_file) uid = 'elijah@riseup.net' - test_em_callback "Nickserver::HKP::FetchKeyInfo.new.search '#{uid}'" do |keys| + test_em_callback "Nickserver::Hkp::FetchKeyInfo.new.search '#{uid}'" do |keys| assert_equal 1, keys.size assert keys.first.keyid =~ /00440025$/ end @@ -157,7 +157,7 @@ class HkpTest < Minitest::Test def fetch_key_info(body_source, uid, &block) stub_sks_vindex_reponse(uid, body: file_content(body_source)) - test_em_callback "Nickserver::HKP::FetchKeyInfo.new.search '#{uid}'", &block + test_em_callback "Nickserver::Hkp::FetchKeyInfo.new.search '#{uid}'", &block end end diff --git a/test/unit/invalid_source_test.rb b/test/unit/invalid_source_test.rb new file mode 100644 index 0000000..37a38fc --- /dev/null +++ b/test/unit/invalid_source_test.rb @@ -0,0 +1,16 @@ +require 'test_helper' +require 'nickserver/invalid_source' + +class Nickserver::InvalidSourceTest < Minitest::Test + + def test_query + adapter.query(nil) do |status, content| + assert_equal 500, status + assert_equal "Not a valid address", content + end + end + + def adapter + Nickserver::InvalidSource.new + end +end diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb new file mode 100644 index 0000000..ac827e0 --- /dev/null +++ b/test/unit/lookup_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' +require 'minitest/mock' +require 'nickserver/lookup' + +class TestLookup < Nickserver::Lookup + + def query + yield 200, 'yeah' + end + +end + +class LookupTest < Minitest::Test + + def test_responding + responder = Minitest::Mock.new + responder.expect :send_response, nil, + [{status: 200, content: 'yeah'}] + lookup = TestLookup.new nil + lookup.respond_with responder + responder.verify + end +end diff --git a/test/unit/nickname_test.rb b/test/unit/nickname_test.rb new file mode 100644 index 0000000..8681545 --- /dev/null +++ b/test/unit/nickname_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' +require 'nickserver/nickname' + +class NicknameTest < Minitest::Test + + def test_local + nick = Nickserver::Nickname.new 'nick@test.me' + assert nick.local? + assert !nick.remote? + end + + def test_remote + nick = Nickserver::Nickname.new 'nick@remote.domain' + assert !nick.local? + assert nick.remote? + end + + def test_valid + nick = Nickserver::Nickname.new 'nick@remote.domain' + assert nick.valid? + end + + def test_invalid + nick = Nickserver::Nickname.new 'asdf' + assert nick.invalid? + end + +end diff --git a/test/unit/response_test.rb b/test/unit/response_test.rb new file mode 100644 index 0000000..8a53066 --- /dev/null +++ b/test/unit/response_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' +require 'nickserver/response' + +class ResponseTest < Minitest::Test + + def test_content + response = Nickserver::Response.new 500, "Not a valid address" + assert_equal "500 Not a valid address", response.content + end + +end |