summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorazul <azul@riseup.net>2016-06-15 14:22:08 +0200
committerGitHub <noreply@github.com>2016-06-15 14:22:08 +0200
commit3c6dc3d7b902b46b82a3d8cd38bd3fa896024beb (patch)
tree559b3998465248138b6f24a76b6b3e55e60f3986
parent5cad637a4a2a3de6b95ff1204fc29174e18b3124 (diff)
parent93258bd6fe6247e7af67f423243eba9808e920ee (diff)
Merge pull request #3 from azul/refactor/transport-adapters
Refactor em specifics into http adapter
-rw-r--r--README.md4
-rw-r--r--lib/kernel_ext.rb28
-rw-r--r--lib/nickserver.rb7
-rw-r--r--lib/nickserver/adapters.rb4
-rw-r--r--lib/nickserver/adapters/em_http.rb24
-rw-r--r--lib/nickserver/adapters/local.rb8
-rw-r--r--lib/nickserver/adapters/remote.rb4
-rw-r--r--lib/nickserver/config.rb17
-rw-r--r--lib/nickserver/couch/fetch_key.rb63
-rw-r--r--lib/nickserver/couch_db.rb4
-rw-r--r--lib/nickserver/couch_db/response.rb51
-rw-r--r--lib/nickserver/couch_db/source.rb39
-rw-r--r--lib/nickserver/daemon.rb4
-rw-r--r--lib/nickserver/hkp.rb4
-rw-r--r--lib/nickserver/hkp/client.rb44
-rw-r--r--lib/nickserver/hkp/fetch_key.rb56
-rw-r--r--lib/nickserver/hkp/fetch_key_info.rb30
-rw-r--r--lib/nickserver/hkp/key_info.rb9
-rw-r--r--lib/nickserver/hkp/parse_key_info.rb26
-rw-r--r--lib/nickserver/hkp/response.rb18
-rw-r--r--lib/nickserver/hkp/source.rb66
-rw-r--r--lib/nickserver/hkp/v_index_response.rb96
-rw-r--r--lib/nickserver/invalid_source.rb14
-rw-r--r--lib/nickserver/lookup.rb31
-rw-r--r--lib/nickserver/nickname.rb51
-rw-r--r--lib/nickserver/response.rb15
-rw-r--r--lib/nickserver/server.rb48
-rw-r--r--lib/server.rb19
-rw-r--r--test/file_content.rb11
-rw-r--r--test/helpers/test_adapter.rb10
-rw-r--r--test/integration/couch_db/source_test.rb19
-rw-r--r--test/integration/hkp_test.rb (renamed from test/unit/hkp_test.rb)83
-rw-r--r--test/integration/nickserver_test.rb (renamed from test/unit/nickserver_test.rb)2
-rw-r--r--test/test_helper.rb11
-rw-r--r--test/unit/adapters/couch_db.rb15
-rw-r--r--test/unit/adapters/em_http_test.rb25
-rw-r--r--test/unit/adapters/local_test.rb5
-rw-r--r--test/unit/couch_db/response_test.rb30
-rw-r--r--test/unit/couch_db/source_unit_test.rb17
-rw-r--r--test/unit/hkp/v_index_response_test.rb17
-rw-r--r--test/unit/invalid_source_test.rb16
-rw-r--r--test/unit/lookup_test.rb23
-rw-r--r--test/unit/nickname_test.rb28
-rw-r--r--test/unit/response_test.rb11
44 files changed, 852 insertions, 255 deletions
diff --git a/README.md b/README.md
index 21c5581..50460c4 100644
--- a/README.md
+++ b/README.md
@@ -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