diff options
| -rw-r--r-- | config/default.yml | 2 | ||||
| -rw-r--r-- | lib/nickserver.rb | 2 | ||||
| -rw-r--r-- | lib/nickserver/config.rb | 2 | ||||
| -rw-r--r-- | lib/nickserver/couch/fetch_key.rb | 62 | ||||
| -rw-r--r-- | lib/nickserver/hkp/fetch_key.rb | 8 | ||||
| -rw-r--r-- | lib/nickserver/hkp/fetch_key_info.rb | 4 | ||||
| -rw-r--r-- | lib/nickserver/server.rb | 23 | ||||
| -rw-r--r-- | test/files/bananas@localhost | 3 | ||||
| -rw-r--r-- | test/files/blue_couchdb_result | 11 | ||||
| -rw-r--r-- | test/files/blue_nickserver_result | 1 | ||||
| -rw-r--r-- | test/test_helper.rb | 14 | ||||
| -rw-r--r-- | test/unit/nickserver_test.rb | 53 | 
12 files changed, 150 insertions, 35 deletions
| diff --git a/config/default.yml b/config/default.yml index c72e3b7..83bfa48 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1,5 +1,7 @@  couch_host: 'localhost'  couch_port: 5984  couch_database: 'users' +#couch_user: 'nickserver' +#couch_password: 'blahblah'  hkp_url: 'https://hkps.pool.sks-keyservers.net:/pks/lookup'  port: 6425 diff --git a/lib/nickserver.rb b/lib/nickserver.rb index 9ddc839..f97a95f 100644 --- a/lib/nickserver.rb +++ b/lib/nickserver.rb @@ -2,6 +2,8 @@  require "nickserver/version"  require "nickserver/config" +require "nickserver/couch/fetch_key" +  require "nickserver/hkp/key_info"  require "nickserver/hkp/fetch_key_info"  require "nickserver/hkp/fetch_key" diff --git a/lib/nickserver/config.rb b/lib/nickserver/config.rb index 779f0a8..3f92186 100644 --- a/lib/nickserver/config.rb +++ b/lib/nickserver/config.rb @@ -12,6 +12,8 @@ module Nickserver        attr_accessor :couch_port        attr_accessor :couch_host        attr_accessor :couch_database +      attr_accessor :couch_user +      attr_accessor :couch_password        attr_accessor :port        attr_accessor :loaded      end diff --git a/lib/nickserver/couch/fetch_key.rb b/lib/nickserver/couch/fetch_key.rb new file mode 100644 index 0000000..d729ebc --- /dev/null +++ b/lib/nickserver/couch/fetch_key.rb @@ -0,0 +1,62 @@ +require 'em-http' +require 'json' + +module Nickserver; module Couch +  class FetchKey +    include EM::Deferrable + +    def initialize(options={}) +      @timeout = 5 +    end + +    def get(uid) +      uid = uid.split('@').first # TEMPORARY HACK FOR NOW. in the future +                                 # the database should be able to be searchable by full address +      couch_request(uid) +      self +    end + +    protected + +    # +    # curl http://localhost:5984/users/_design/User/_view/pgp_key_by_handle?key=%22bla%22\&reduce=false +    # +    def couch_request(uid) +      query = {"reduce" => "false", "key" => "\"#{uid}\""} +      request = EventMachine::HttpRequest.new("#{FetchKey.couch_url}/#{FetchKey.couch_view}").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["offset"] == 0 +        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_view +      "_design/User/_view/pgp_key_by_handle" +    end + +    def self.couch_url +      if Config.couch_user +        ['http://', Config.couch_user, ':', Config.couch_password, '@', Config.couch_host, ':', Config.couch_port, '/', Config.couch_database].join +      else +        ['http://', Config.couch_host, ':', Config.couch_port, '/', Config.couch_database].join +      end +    end + +  end +end; end
\ No newline at end of file diff --git a/lib/nickserver/hkp/fetch_key.rb b/lib/nickserver/hkp/fetch_key.rb index 211ae92..6f91d8c 100644 --- a/lib/nickserver/hkp/fetch_key.rb +++ b/lib/nickserver/hkp/fetch_key.rb @@ -16,8 +16,8 @@ module Nickserver; module HKP          get_key_by_fingerprint(best.keyid) {|key|            self.succeed key          } -      }.errback {|msg| -        self.fail msg +      }.errback {|status, msg| +        self.fail status, msg        }        self      end @@ -30,13 +30,13 @@ module Nickserver; module HKP        http = EventMachine::HttpRequest.new(Config.hkp_url).get(:query => params)        http.callback {          if http.response_header.status != 200 -          self.fail http.response_header.status #"Request failed with #{http.response_header.status}: #{http.response}" +          self.fail http.response_header.status, "HKP Request failed"          else            yield http.response          end        }        http.errback { -        self.fail http.error +        self.fail 0, http.error        }      end diff --git a/lib/nickserver/hkp/fetch_key_info.rb b/lib/nickserver/hkp/fetch_key_info.rb index dce0326..b341aad 100644 --- a/lib/nickserver/hkp/fetch_key_info.rb +++ b/lib/nickserver/hkp/fetch_key_info.rb @@ -17,12 +17,12 @@ module Nickserver; module HKP        params = {:op => 'vindex', :search => uid, :exact => 'on', :options => 'mr', :fingerprint => 'on'}        EventMachine::HttpRequest.new(Config.hkp_url).get(:query => params).callback {|http|          if http.response_header.status != 200 -          self.fail http.response_header.status +          self.fail http.response_header.status, "Could net fetch keyinfo"          else            self.succeed parse(uid, http.response)          end        }.errback {|http| -        self.fail http.error +        self.fail 0, http.error        }        self      end diff --git a/lib/nickserver/server.rb b/lib/nickserver/server.rb index 7e79b98..0bda4f1 100644 --- a/lib/nickserver/server.rb +++ b/lib/nickserver/server.rb @@ -5,6 +5,8 @@ require 'json'  #  # This is the main HTTP server that clients connect to in order to fetch keys  # +# For info on EM::HttpServer, see https://github.com/eventmachine/evma_httpserver +#  module Nickserver    class Server < EM::Connection      include EM::HttpServer @@ -43,7 +45,7 @@ module Nickserver        send_response(:status => 500, :content => msg)      end -    def send_not_found(msg = "not found") +    def send_not_found(msg = "404 Not Found")        send_response(:status => 404, :content => msg)      end @@ -74,15 +76,20 @@ module Nickserver      end      def get_key_from_uid(uid) -      if local_address?(uid) -        send_not_found +      fetcher = if local_address?(uid) +        Nickserver::Couch::FetchKey.new        else -        Nickserver::HKP::FetchKey.new.get(uid).callback {|key| -          yield key -        }.errback {|status| -          send_response(:status => status, :content => 'could not fetch key') -        } +        Nickserver::HKP::FetchKey.new        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) diff --git a/test/files/bananas@localhost b/test/files/bananas@localhost deleted file mode 100644 index 69df2bf..0000000 --- a/test/files/bananas@localhost +++ /dev/null @@ -1,3 +0,0 @@ -{ -  "openpgp": "hi" -}
\ No newline at end of file diff --git a/test/files/blue_couchdb_result b/test/files/blue_couchdb_result new file mode 100644 index 0000000..1e164b8 --- /dev/null +++ b/test/files/blue_couchdb_result @@ -0,0 +1,11 @@ +{ +  "total_rows":2, +  "offset":1, +  "rows":[ +    { +      "id":"c58e909ee1291985b9c5954efa1f91e7", +      "key":"blue", +      "value":"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVmQGiBEDsRrURBACwk7zArsD89vGDlCoBV55RzBeeIyoiNwgQuVlq+t8nGqRdPr+B\r\nZY1mCN6Bj/SOvPgG1DjBbh5CSXuYGzLEw9lkGlAY4VGVvQteHXf3ZpRRtj0SbzEq\r\nUUECLAU4m3L/nOwbTU4Osi5zilQZOTqIooMB+Wq5qCz7rvvzDHPBHF6OHwCg9Wrb\r\n9LT7e0PuRB0qTCyFjIhjB6ED/Ax2ma2D/at4/CVhno0hfZqd3bCCkQDvnArxQfZr\r\nFF3CpnF4djzouGiFYNi3h6y33qaD+ms9/TrewItvvHXgdtPTxHe+12mqWjYTyJPa\r\nN9fXun29YWQrycHLR1NcVo7bLJJ6ESSIhb2Ingo+rsCd5bdqfmMZ7Y0sb0jMrpqB\r\n/MNMA/9EfaKHi/eTREe0/04ZV5rS9tUYAz4elL0yOfwaSbDAgoUatgGAQaQpSBMG\r\n/e+pMyEu07u4Ft2MqG3hPu3Wph0TwWJMANOeFjG4HbJj/9TLYQB1J5mjZa3D091T\r\nxhu7oHzLT5gQL0chCCxHp61lx9tSMqQ/j7F0yZ9WZ8YFV8QDbYhJBCARCgAJBQJP\r\nv/MkAh0CAAoJEKTufqYlGMO1YscAnjrP1HfW10nbOJxxGtRjzdSmESMHAJwIcJMv\r\nX9uCVqwo45h514vu94/N7TPveF2rIEq4yW5IBqxG5aJ0dJDdGjg5GjvmVJtnNIux\r\nonXCYXLcPjkmO+yw8VHZWFUapeIDaWcsXlaBLv/L6uKrrgtrj8v3pUJuOfAuw9td\r\n5nwXz7JXWHEjjMk37HGQHa/fJVwHrRZm99c21jAD4tziZYl4lTM7afBPYiFDO8A0\r\nxBAy4R3dNf2CSBn0\r\n=qogX\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n" +    } +  ] +} diff --git a/test/files/blue_nickserver_result b/test/files/blue_nickserver_result new file mode 100644 index 0000000..611e0df --- /dev/null +++ b/test/files/blue_nickserver_result @@ -0,0 +1 @@ +{"address":"blue@example.org","openpgp":"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVmQGiBEDsRrURBACwk7zArsD89vGDlCoBV55RzBeeIyoiNwgQuVlq+t8nGqRdPr+B\r\nZY1mCN6Bj/SOvPgG1DjBbh5CSXuYGzLEw9lkGlAY4VGVvQteHXf3ZpRRtj0SbzEq\r\nUUECLAU4m3L/nOwbTU4Osi5zilQZOTqIooMB+Wq5qCz7rvvzDHPBHF6OHwCg9Wrb\r\n9LT7e0PuRB0qTCyFjIhjB6ED/Ax2ma2D/at4/CVhno0hfZqd3bCCkQDvnArxQfZr\r\nFF3CpnF4djzouGiFYNi3h6y33qaD+ms9/TrewItvvHXgdtPTxHe+12mqWjYTyJPa\r\nN9fXun29YWQrycHLR1NcVo7bLJJ6ESSIhb2Ingo+rsCd5bdqfmMZ7Y0sb0jMrpqB\r\n/MNMA/9EfaKHi/eTREe0/04ZV5rS9tUYAz4elL0yOfwaSbDAgoUatgGAQaQpSBMG\r\n/e+pMyEu07u4Ft2MqG3hPu3Wph0TwWJMANOeFjG4HbJj/9TLYQB1J5mjZa3D091T\r\nxhu7oHzLT5gQL0chCCxHp61lx9tSMqQ/j7F0yZ9WZ8YFV8QDbYhJBCARCgAJBQJP\r\nv/MkAh0CAAoJEKTufqYlGMO1YscAnjrP1HfW10nbOJxxGtRjzdSmESMHAJwIcJMv\r\nX9uCVqwo45h514vu94/N7TPveF2rIEq4yW5IBqxG5aJ0dJDdGjg5GjvmVJtnNIux\r\nonXCYXLcPjkmO+yw8VHZWFUapeIDaWcsXlaBLv/L6uKrrgtrj8v3pUJuOfAuw9td\r\n5nwXz7JXWHEjjMk37HGQHa/fJVwHrRZm99c21jAD4tziZYl4lTM7afBPYiFDO8A0\r\nxBAy4R3dNf2CSBn0\r\n=qogX\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n"}
\ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 757bcd1..187ee05 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -42,11 +42,15 @@ class MiniTest::Unit::TestCase    end    def stub_couch_response(uid, opts = {}) -    options = {:status => 200, :body => ""}.merge(opts) -    url = ['http://', Nickserver::Config.couch_host, ':', Nickserver::Config.couch_port].join -    stub_http_request(:get, url).with( -      :query => {:address => uid} -    ).to_return(options) +    # can't stub localhost, so set couch_host to anything else +    Nickserver::Config.stub :couch_host, 'notlocalhost' do +      uid = uid.split('@').first # TEMPORARY HACK FOR NOW. in the future +                                 # the database should be able to be searchable by full address +      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) +      yield +    end    end  end diff --git a/test/unit/nickserver_test.rb b/test/unit/nickserver_test.rb index 5753ec3..c746ef5 100644 --- a/test/unit/nickserver_test.rb +++ b/test/unit/nickserver_test.rb @@ -1,6 +1,21 @@  require File.expand_path('test_helper', File.dirname(__FILE__))  require 'json' +# +# Some important notes to understanding these tests: +# +# (1) Requests to localhost always bypass HTTP stub. +# +# (2) All requests to nickserver are to localhost. +# +# (3) the "Host" header for requests to nickserver must be set, because this +# is how it decides if a request is local. I am not happy about this design, +# but that is how it works for now. +# +# (4) When stubbing requests to couchdb, the couchdb host is changed from the +# default (localhost) to a dummy value (notlocalhost). +# +  class NickserverTest < MiniTest::Unit::TestCase    def test_GET_served_via_SKS @@ -34,15 +49,29 @@ class NickserverTest < MiniTest::Unit::TestCase    end    def test_GET_served_via_couch_not_found -    uid    = 'bananas@localhost' -    key_id = 'E818C478D3141282F7590D29D041EB11B1647490' -    stub_couch_response(uid, :body => file_content(uid)) +    domain = "example.org" +    uid    = "bananas@" + domain +    stub_couch_response(uid, :status => 404) do +      start do +        params = {:query => {"address" => uid}, :head => {:host => domain}} +        get(params) do |http| +          assert_equal 404, http.response_header.status +          stop +        end +      end +    end +  end -    start do -      params = {:query => {"address" => uid}} -      get(params) do |http| -        assert_equal 404, http.response_header.status -        stop +  def test_GET_served_via_couch_success +    domain = "example.org" +    uid    = "blue@" + domain +    stub_couch_response(uid, :body => file_content(:blue_couchdb_result)) do +      start do +        params = {:query => {"address" => uid}, :head => {:host => domain}} +        get(params) do |http| +          assert_equal file_content(:blue_nickserver_result), http.response +          stop +        end        end      end    end @@ -61,7 +90,7 @@ class NickserverTest < MiniTest::Unit::TestCase        end      end    rescue Timeout::Error -    flunk 'Eventmachine was not stopped before the timeout expired' +    flunk 'EventMachine was not stopped before the timeout expired'    end    # @@ -84,14 +113,12 @@ class NickserverTest < MiniTest::Unit::TestCase    # this works because http requests to localhost are not stubbed, but requests to other domains are.    #    def request(method, params) -    http = EventMachine::HttpRequest.new("http://localhost:#{Nickserver::Config.port}/").send(method,params) - -    http.callback { +    EventMachine::HttpRequest.new("http://localhost:#{Nickserver::Config.port}/").send(method,params).callback {|http|        # p http.response_header.status        # p http.response_header        # p http.response        yield http -    }.errback { +    }.errback {|http|        flunk(http.error) if http.error        EM.stop      } | 
