diff options
author | azul <azul@riseup.net> | 2016-06-15 14:22:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-15 14:22:08 +0200 |
commit | 3c6dc3d7b902b46b82a3d8cd38bd3fa896024beb (patch) | |
tree | 559b3998465248138b6f24a76b6b3e55e60f3986 | |
parent | 5cad637a4a2a3de6b95ff1204fc29174e18b3124 (diff) | |
parent | 93258bd6fe6247e7af67f423243eba9808e920ee (diff) |
Merge pull request #3 from azul/refactor/transport-adapters
Refactor em specifics into http adapter
44 files changed, 852 insertions, 255 deletions
@@ -95,6 +95,6 @@ To run the test suite, run: rake -The tests that actually make real network calls are disabled by default. To run these tests to, do this: +The tests that actually make real network calls are enabled by default. To only run local, do this: - REAL_NET=true rake + ONLY_LOCAL=true rake diff --git a/lib/kernel_ext.rb b/lib/kernel_ext.rb new file mode 100644 index 0000000..b5b58e0 --- /dev/null +++ b/lib/kernel_ext.rb @@ -0,0 +1,28 @@ +module Kernel + # Sets $VERBOSE to nil for the duration of the block and back to its original + # value afterwards. + # + # silence_warnings do + # value = noisy_call # no warning voiced + # end + # + # noisy_call # warning voiced + def silence_warnings + with_warnings(nil) { yield } + end + + # Sets $VERBOSE to +true+ for the duration of the block and back to its + # original value afterwards. + def enable_warnings + with_warnings(true) { yield } + end + + # Sets $VERBOSE for the duration of the block and back to its original + # value afterwards. + def with_warnings(flag) + old_verbose, $VERBOSE = $VERBOSE, flag + yield + ensure + $VERBOSE = old_verbose + end +end diff --git a/lib/nickserver.rb b/lib/nickserver.rb index eb7eddb..8843acd 100644 --- a/lib/nickserver.rb +++ b/lib/nickserver.rb @@ -3,12 +3,5 @@ require "nickserver/version" require "nickserver/config" require "nickserver/email_address" -require "nickserver/couch/fetch_key" - -require "nickserver/hkp/fetch_key" -require "nickserver/hkp/fetch_key_info" -require "nickserver/hkp/parse_key_info" -require "nickserver/hkp/key_info" - require "nickserver/server" require "nickserver/daemon" 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/em_http.rb b/lib/nickserver/adapters/em_http.rb new file mode 100644 index 0000000..16db5ae --- /dev/null +++ b/lib/nickserver/adapters/em_http.rb @@ -0,0 +1,24 @@ +require 'nickserver/adapters' +require 'em-http' + +module Nickserver::Adapters + class EmHttp + + def initialize + @timeout = 5 + end + + def get(url, options = {}) + get_request(url, options).callback {|http| + yield http.response_header.status, http.response + }.errback {|http| + yield 0, http.error + } + end + + def get_request(url, options = {}) + @request = EventMachine::HttpRequest.new(url) + @request.get timeout: @timeout, query: options[:query] + end + 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/config.rb b/lib/nickserver/config.rb index 46b6bef..b1def7c 100644 --- a/lib/nickserver/config.rb +++ b/lib/nickserver/config.rb @@ -36,6 +36,21 @@ module Nickserver self.validate end + def self.couch_url + [ 'http://', + couch_auth, + couch_host, + ':', + couch_port, + '/', + couch_database + ].join + end + + def self.couch_auth + "#{couch_user}:#{couch_password}@" if couch_user + end + private def self.validate @@ -59,7 +74,7 @@ module Nickserver YAML.load(File.read(file_path)).each do |key, value| begin self.send("#{key}=", value) - rescue NoMethodError => exc + rescue NoMethodError STDERR.puts "ERROR in file #{file_path}, '#{key}' is not a valid option" exit(1) end diff --git a/lib/nickserver/couch/fetch_key.rb b/lib/nickserver/couch/fetch_key.rb deleted file mode 100644 index 3fe2a63..0000000 --- a/lib/nickserver/couch/fetch_key.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'em-http' -require 'json' - -module Nickserver; module Couch - class FetchKey - include EM::Deferrable - - VIEW = "_design/Identity/_view/pgp_key_by_email" - - def initialize(options={}) - @timeout = 5 - end - - def get(uid) - couch_request(uid) - self - end - - protected - - # - # For example: - # curl "$COUCH/identities/_design/Identity/_view/pgp_key_by_email?key=\"test1@bitmask.net\"" - # - def couch_request(uid) - query = {"reduce" => "false", "key" => "\"#{uid}\""} - request = EventMachine::HttpRequest.new(FetchKey.couch_url).get(timeout: @timeout, query: query) - request.callback {|http| - if http.response_header.status != 200 - self.fail http.response_header.status, 'Unknown Error' - else - self.succeed parse_key_from_response(uid, http.response) - end - }.errback {|http| - self.fail 0, http.error - } - end - - def parse_key_from_response(uid, response) - json = JSON.load(response) - if json["rows"].empty? - self.fail 404, "Not Found" - else - return json["rows"].first["value"] - end - rescue Exception - self.fail 0, "Error parsing CouchDB reply" - end - - def self.couch_url - @couch_url ||= begin - url = ['http://'] - if Config.couch_user - url.push Config.couch_user, ':', Config.couch_password, '@' - end - url.push Config.couch_host, ':', Config.couch_port, '/', Config.couch_database - url.push '/', VIEW - url.join - end - end - - end -end; end
\ No newline at end of file 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..874fe4f --- /dev/null +++ b/lib/nickserver/couch_db/source.rb @@ -0,0 +1,39 @@ +# +# This class allows querying couch for public keys. +# +require 'nickserver/couch_db/response' +require 'nickserver/config' + +module Nickserver::CouchDB + class Source + + VIEW = '/_design/Identity/_view/pgp_key_by_email' + + def initialize(adapter) + @adapter = adapter + end + + def query(nick) + adapter.get url, query: query_for(nick) do |status, body| + yield Response.new(nick, status: status, body: body) + end + end + + protected + + def url + Nickserver::Config.couch_url + VIEW + end + + 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/daemon.rb b/lib/nickserver/daemon.rb index 7d0f02a..9b06a3c 100644 --- a/lib/nickserver/daemon.rb +++ b/lib/nickserver/daemon.rb @@ -60,7 +60,7 @@ module Nickserver def daemonize return bail("Process is already started") if daemon_running? - pid = fork do + _pid = fork do exit if fork Process.setsid exit if fork @@ -219,7 +219,7 @@ module Nickserver end def override_default_config(flag, value) - flag = flag.sub /^--/, '' + flag = flag.sub(/^--/, '') if Config.respond_to?("#{flag}=") Config.send("#{flag}=", value) else 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/client.rb b/lib/nickserver/hkp/client.rb new file mode 100644 index 0000000..6bd239d --- /dev/null +++ b/lib/nickserver/hkp/client.rb @@ -0,0 +1,44 @@ +require 'nickserver/hkp' + +# +# Client for the HKP protocol. +# +# This is not a complete implementation - only the parts we need. +# Instantiate with an adapter that will take care of the http requests. +# +# For each request we yield http_status and the response content just +# like the adapter does. + + +module Nickserver; module Hkp + class Client + + def initialize(adapter) + @adapter = adapter + end + + # + # used to fetch an array of KeyInfo objects that match the given email + # + def get_key_infos_by_email(email, &block) + get op: 'vindex', search: email, fingerprint: 'on', &block + end + + # + # fetches ascii armored OpenPGP public key from the keyserver + # + def get_key_by_fingerprint(fingerprint, &block) + get op: 'get', search: "0x" + fingerprint, &block + end + + protected + + attr_reader :adapter + + def get(query, &block) + # in practice, exact=on seems to have no effect + query = {exact: 'on', options: 'mr'}.merge query + adapter.get Config.hkp_url, query: query, &block + end + end +end; end diff --git a/lib/nickserver/hkp/fetch_key.rb b/lib/nickserver/hkp/fetch_key.rb deleted file mode 100644 index 44621d3..0000000 --- a/lib/nickserver/hkp/fetch_key.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'em-http' - -# -# Fetch keys via HKP -# http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00 -# - -module Nickserver; module HKP - - class FetchKey - include EM::Deferrable - - def get(uid) - FetchKeyInfo.new.search(uid).callback {|key_info_list| - best = pick_best_key(key_info_list) - get_key_by_fingerprint(best.keyid) {|key| - self.succeed key - } - }.errback {|status, msg| - self.fail status, msg - } - self - end - - # - # fetches ascii armored OpenPGP public key from the keyserver - # - def get_key_by_fingerprint(key_id) - params = {op: 'get', search: "0x" + key_id, exact: 'on', options: 'mr'} - http = EventMachine::HttpRequest.new(Config.hkp_url).get(query: params) - http.callback { - if http.response_header.status != 200 - self.fail http.response_header.status, "HKP Request failed" - else - yield http.response - end - } - http.errback { - self.fail 500, http.error - } - end - - protected - - # - # for now, just pick the newest key. - # - # in the future, we should perhaps pick the newest key - # that is signed by the oldest key. - # - def pick_best_key(key_info_list) - key_info_list.sort {|a,b| a.creationdate <=> b.creationdate}.last - end - end - -end; end
\ No newline at end of file diff --git a/lib/nickserver/hkp/fetch_key_info.rb b/lib/nickserver/hkp/fetch_key_info.rb deleted file mode 100644 index 2448bb1..0000000 --- a/lib/nickserver/hkp/fetch_key_info.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'em-http' - -# -# used to fetch an array of KeyInfo objects that match the given uid. -# - -module Nickserver; module HKP - class FetchKeyInfo - include EM::Deferrable - - def search(uid) - # in practice, exact=on seems to have no effect - params = {op: 'vindex', search: uid, exact: 'on', options: 'mr', fingerprint: 'on'} - EventMachine::HttpRequest.new(Config.hkp_url).get(query: params).callback {|http| - parser = ParseKeyInfo.new http.response_header, http.response - keys = parser.keys(uid) - if keys.any? - self.succeed keys - else - self.fail parser.status(uid), parser.msg(uid) - end - }.errback {|http| - self.fail 500, http.error - } - self - end - - end - -end; end diff --git a/lib/nickserver/hkp/key_info.rb b/lib/nickserver/hkp/key_info.rb index adb75d8..d4ecf10 100644 --- a/lib/nickserver/hkp/key_info.rb +++ b/lib/nickserver/hkp/key_info.rb @@ -1,4 +1,5 @@ require 'cgi' +require 'nickserver/hkp' # # Class to represent the key information result from a query to a key server @@ -9,9 +10,9 @@ 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::Hkp class KeyInfo - attr_accessor :uids, :keyid, :algo, :keylen, :creationdate, :expirationdate, :flags + attr_accessor :uids, :keyid, :algo, :flags def initialize(hkp_record) uid_lines = hkp_record.split("\n") @@ -19,7 +20,7 @@ module Nickserver; module HKP @keyid, @algo, @keylen_s, @creationdate_s, @expirationdate_s, @flags = pub_line.split(':')[1..-1] @uids = [] uid_lines.each do |uid_line| - uid, creationdate, expirationdate, flags = uid_line.split(':')[1..-1] + uid, _creationdate, _expirationdate, _flags = uid_line.split(':')[1..-1] # for now, ignore the expirationdate and flags of uids. sks does return them anyway @uids << CGI.unescape(uid.sub(/.*<(.+)>.*/, '\1')) end @@ -66,4 +67,4 @@ module Nickserver; module HKP end end -end; end +end diff --git a/lib/nickserver/hkp/parse_key_info.rb b/lib/nickserver/hkp/parse_key_info.rb index 8934829..9d59d6b 100644 --- a/lib/nickserver/hkp/parse_key_info.rb +++ b/lib/nickserver/hkp/parse_key_info.rb @@ -1,29 +1,37 @@ # -# 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", # which the output of sks does. MATCH_PUB_KEY = /(^pub:.+?\n(^uid:.+?\n)+)/m - # header -- header of the hkp response + # status -- http status of the hkp response # vindex_result -- raw output from a vindex hkp query (machine readable) - def initialize(header, vindex_result) - @header = header + def initialize(status, vindex_result) + @status = status @vindex_result = vindex_result end - def status(uid) + def status_for(uid) if hkp_ok? && keys(uid).empty? error_status(uid) else - header.status + status + end + end + + def response_for(uid) + if keys(uid).any? + keys(uid) + else + msg(uid) end end @@ -41,7 +49,7 @@ module Nickserver; module HKP protected - attr_reader :header + attr_reader :status attr_reader :vindex_result def error_status(uid) @@ -78,7 +86,7 @@ module Nickserver; module HKP end def hkp_ok? - header.status == 200 + status == 200 end def error_message(uid, key, err) diff --git a/lib/nickserver/hkp/response.rb b/lib/nickserver/hkp/response.rb new file mode 100644 index 0000000..c52e25f --- /dev/null +++ b/lib/nickserver/hkp/response.rb @@ -0,0 +1,18 @@ +module Nickserver::Hkp + class Response + + attr_reader :status, :content + + def initialize(uid, key) + @content = format_response(address: uid, openpgp: key) + @status = 200 + end + + protected + + def format_response(map) + map.to_json + end + + end +end diff --git a/lib/nickserver/hkp/source.rb b/lib/nickserver/hkp/source.rb new file mode 100644 index 0000000..cae3e01 --- /dev/null +++ b/lib/nickserver/hkp/source.rb @@ -0,0 +1,66 @@ +require 'nickserver/response' +require 'nickserver/hkp/response' +require 'nickserver/hkp/client' +require "nickserver/hkp/parse_key_info" +require "nickserver/hkp/key_info" + + +# +# Fetch keys via HKP +# http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00 +# + +module Nickserver; module Hkp + class Source + + def initialize(adapter) + @adapter = adapter + end + + def query(nick, &block) + search(nick) do |status, response| + if status == 200 + best = pick_best_key(response) + get_key_by_fingerprint(nick, best.keyid, &block) + else + yield Nickserver::Response.new(status, response) + end + end + end + + def search(nick, &block) + client.get_key_infos_by_email(nick) do |status, response| + parser = ParseKeyInfo.new status, response + yield parser.status_for(nick), parser.response_for(nick) + end + end + + protected + + attr_reader :adapter + + # + # for now, just pick the newest key. + # + # in the future, we should perhaps pick the newest key + # that is signed by the oldest key. + # + def pick_best_key(key_info_list) + key_info_list.sort {|a,b| a.creationdate <=> b.creationdate}.last + end + + def get_key_by_fingerprint(nick, fingerprint) + client.get_key_by_fingerprint fingerprint do |status, response| + if status == 200 + yield Response.new nick, response + else + yield Nickserver::Response.new status, "HKP Request failed" + end + end + end + + def client + @client ||= Client.new(adapter) + end + end +end; end 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..2453f94 100644 --- a/lib/nickserver/server.rb +++ b/lib/nickserver/server.rb @@ -1,6 +1,13 @@ +require 'kernel_ext' require 'eventmachine' -require 'evma_httpserver' +silence_warnings do + require 'evma_httpserver' +end require 'json' +require 'nickserver/couch_db/source' +require 'nickserver/hkp/source' +require 'nickserver/adapters/em_http' + # # This is the main HTTP server that clients connect to in order to fetch keys @@ -64,12 +71,8 @@ module Nickserver response.status = options[:status] response.content_type options[:content_type] response.content = options[:content] - response.send_response - end - - def send_key(uid) - get_key_from_uid(uid) do |key| - send_response content: format_response(address: uid, openpgp: key) + silence_warnings do + response.send_response end end @@ -86,25 +89,15 @@ module Nickserver end end - def get_key_from_uid(uid) - fetcher = if local_address?(uid) - Nickserver::Couch::FetchKey.new + def send_key(uid) + if local_address?(uid) + source = Nickserver::CouchDB::Source.new(adapter) else - Nickserver::HKP::FetchKey.new + source = Nickserver::Hkp::Source.new(adapter) + end + source.query(uid) do |response| + send_response(status: response.status, content: response.content) end - fetcher.get(uid).callback {|key| - yield key - }.errback {|status, msg| - if status == 404 - send_not_found - else - send_response(status: status, content: msg) - end - } - end - - def format_response(map) - map.to_json end # @@ -127,8 +120,13 @@ module Nickserver return uid_domain == host end end - rescue + rescue # XXX what are we rescueing here? return false end + + def adapter + @adapter ||= Nickserver::Adapters::EmHttp.new + end + end end 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/helpers/test_adapter.rb b/test/helpers/test_adapter.rb new file mode 100644 index 0000000..46d4713 --- /dev/null +++ b/test/helpers/test_adapter.rb @@ -0,0 +1,10 @@ +class TestAdapter + def initialize(status, content) + @status = status + @content = content + end + + def get(url, opts) + yield @status, @content + 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..21e3642 --- /dev/null +++ b/test/integration/couch_db/source_test.rb @@ -0,0 +1,19 @@ +require 'test_helper' +require 'file_content' +require 'helpers/test_adapter' +require 'nickserver/couch_db/source' + +module Nickserver::CouchDB + class SourceTest < Minitest::Test + include FileContent + + 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/unit/hkp_test.rb b/test/integration/hkp_test.rb index 0ecde2d..a824a3f 100644 --- a/test/unit/hkp_test.rb +++ b/test/integration/hkp_test.rb @@ -1,4 +1,6 @@ -require File.expand_path('test_helper', File.dirname(__FILE__)) +require 'test_helper' +require 'nickserver/hkp/source' +require 'nickserver/adapters/em_http' class HkpTest < Minitest::Test @@ -29,17 +31,13 @@ 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| - assert_equal 404, error - end + assert_response_status_for_uid uid, 404 end 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| - assert_equal 404, error - end + assert_response_status_for_uid uid, 404 end def test_fetch_key @@ -48,8 +46,9 @@ 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| - assert_equal file_content(:leap_public_key), key_text + assert_response_for_uid(uid) do |response| + content = JSON.parse response.content + assert_equal file_content(:leap_public_key), content['openpgp'] end end @@ -60,19 +59,14 @@ 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| - assert_equal 404, error - end + assert_response_status_for_uid uid, 404 end def test_fetch_key_too_short uid = 'chiiph@leap.se' - 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| - assert_equal 500, error - end + assert_response_status_for_uid uid, 500 end # @@ -83,7 +77,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| + assert_key_info_for_uid uid do |keys| assert_equal 1, keys.size assert keys.first.keyid =~ /00440025$/ end @@ -100,7 +94,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| + assert_key_info_for_uid uid do |keys| assert_equal 1, keys.size assert keys.first.keyid =~ /00440025$/ end @@ -111,53 +105,38 @@ class HkpTest < Minitest::Test protected - # - # Takes a code snippet that returns a Deferrable, and yields the callback result. - # Assertion fails if errback is called instead of callback. - # - # This method takes care of the calls to EM.run and EM.stop. It works kind of like EM.run_block, - # except I couldn't get run_block to work with multiple nested HTTP requests. - # - def test_em_callback(code, &block) + def assert_response_status_for_uid(uid, status) + assert_response_for_uid(uid) do |response| + assert_equal status, response.status + end + end + + def assert_response_for_uid(uid, &block) EM.run do - deferrable = instance_eval(code) - deferrable.callback {|response| - EM.stop + Nickserver::Hkp::Source.new(adapter).query uid do |response| yield response - return - } - deferrable.errback {|response, msg| EM.stop - puts caller.join("\n") - flunk "Expecting callback, but errback invoked with response: #{response} #{msg}\n\n#{caller.join("\n")}" - } + end end - assert false, 'should not get here' end - # - # like test_em_callback, except value yielded is the result of errback, and - # we raise an exception if errback was not called. - # - def test_em_errback(code, &block) + def assert_key_info_for_uid(uid, &block) EM.run do - deferrable = instance_eval(code) - deferrable.callback {|response| - EM.stop - flunk "Expecting errback, but callback invoked with response: #{response}" - } - deferrable.errback {|response| + Nickserver::Hkp::Source.new(adapter).search uid do |status, keys| + assert_equal 200, status + yield keys EM.stop - yield response - return - } + end end - assert false, 'should not get here' + end + + def adapter + Nickserver::Adapters::EmHttp.new end 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 + assert_key_info_for_uid(uid, &block) end end diff --git a/test/unit/nickserver_test.rb b/test/integration/nickserver_test.rb index 65ade8c..b4ff4da 100644 --- a/test/unit/nickserver_test.rb +++ b/test/integration/nickserver_test.rb @@ -1,4 +1,4 @@ -require File.expand_path('test_helper', File.dirname(__FILE__)) +require 'test_helper' require 'json' # diff --git a/test/test_helper.rb b/test/test_helper.rb index d4765bc..afdd3f9 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,10 +1,15 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') require 'rubygems' +require 'kernel_ext' require 'bundler/setup' require 'minitest/autorun' -require 'webmock/minitest' +silence_warnings do + require 'webmock/minitest' +end require 'nickserver' +require 'minitest/pride' +require 'minitest/hell' TESTING = true @@ -27,7 +32,7 @@ class Minitest::Test end def real_network - if ENV['REAL_NET'] == 'true' + unless ENV['ONLY_LOCAL'] == 'true' WebMock.allow_net_connect! yield WebMock.disable_net_connect! @@ -53,7 +58,7 @@ class Minitest::Test Nickserver::Config.stub :couch_host, 'notlocalhost' do options = {status: 200, body: ""}.merge(opts) query = "\?key=#{"%22#{uid}%22"}&reduce=false" - stub_http_request(:get, /#{Regexp.escape(Nickserver::Couch::FetchKey.couch_url)}.*#{query}/).to_return(options) + stub_http_request(:get, /#{Regexp.escape(Nickserver::Config.couch_url)}.*#{query}/).to_return(options) yield end end 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/em_http_test.rb b/test/unit/adapters/em_http_test.rb new file mode 100644 index 0000000..659ff1b --- /dev/null +++ b/test/unit/adapters/em_http_test.rb @@ -0,0 +1,25 @@ +require 'test_helper' +require 'nickserver/adapters/em_http' + +class Nickserver::Adapters::EmHttpTest < Minitest::Test + + def test_successful_request + url = 'http://url.to' + stub_http_request(:get, url) + .with(query: {key: :value}) + .to_return status: 200, body: 'body' + EM.run do + adapter.get(url, query: {key: :value}) do |status, body| + assert_equal 200, status + assert_equal 'body', body + EM.stop + end + end + end + + protected + + def adapter + Nickserver::Adapters::EmHttp.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/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 |