webfinger-1.0.2/0000755000175600017570000000000012736020316012523 5ustar pravipraviwebfinger-1.0.2/spec/0000755000175600017570000000000012736020316013455 5ustar pravipraviwebfinger-1.0.2/spec/spec_helper.rb0000644000175600017570000000040612736020316016273 0ustar pravipravirequire 'simplecov' SimpleCov.start do add_filter 'spec' end require 'rspec' require 'rspec/its' require 'webfinger' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end require 'helpers/webmock_helper'webfinger-1.0.2/spec/webfinger/0000755000175600017570000000000012736020316015425 5ustar pravipraviwebfinger-1.0.2/spec/webfinger/response_spec.rb0000644000175600017570000000205712736020316020626 0ustar pravipravirequire 'spec_helper' describe WebFinger::Response do let(:_subject_) { 'acct:nov@matake.jp' } let(:aliases) { ['mailto:nov@matake.jp'] } let(:properties) do {'http://webfinger.net/rel/name' => 'Nov Matake'} end let(:links) do [{ rel: 'http://openid.net/specs/connect/1.0/issuer', href: 'https://openid.example.com/' }.with_indifferent_access] end let(:attributes) do { subject: _subject_, aliases: aliases, properties: properties, links: links }.with_indifferent_access end subject do WebFinger::Response.new attributes end its(:subject) { should == _subject_ } its(:aliases) { should == aliases } its(:properties) { should == properties } its(:links) { should == links } describe '#link_for' do context 'when unknown' do it do subject.link_for('unknown').should be_nil end end context 'otherwise' do it do subject.link_for('http://openid.net/specs/connect/1.0/issuer').should == links.first end end end endwebfinger-1.0.2/spec/webfinger/debugger/0000755000175600017570000000000012736020316017211 5ustar pravipraviwebfinger-1.0.2/spec/webfinger/debugger/request_filter_spec.rb0000644000175600017570000000172412736020316023611 0ustar pravipravirequire 'spec_helper' describe WebFinger::Debugger::RequestFilter do let(:resource_endpoint) { 'https://example.com/resources' } let(:request) { HTTP::Message.new_request(:get, URI.parse(resource_endpoint)) } let(:response) { HTTP::Message.new_response({:hello => 'world'}.to_json) } let(:request_filter) { WebFinger::Debugger::RequestFilter.new } describe '#filter_request' do it 'should log request' do expect(WebFinger.logger).to receive(:info).with( "======= [WebFinger] HTTP REQUEST STARTED =======\n" + request.dump ) request_filter.filter_request(request) end end describe '#filter_response' do it 'should log response' do expect(WebFinger.logger).to receive(:info).with( "--------------------------------------------------\n" + response.dump + "\n======= [WebFinger] HTTP REQUEST FINISHED =======" ) request_filter.filter_response(request, response) end end endwebfinger-1.0.2/spec/helpers/0000755000175600017570000000000012736020316015117 5ustar pravipraviwebfinger-1.0.2/spec/helpers/webmock_helper.rb0000644000175600017570000000151712736020316020436 0ustar pravipravirequire 'webmock/rspec' module WebMockHelper def mock_json(endpoint, response_file, options = {}) endpoint = endpoint.to_s stub_request(:get, endpoint).with( request_for(options) ).to_return( response_for(response_file, options) ) yield a_request(:get, endpoint).with( request_for(options) ).should have_been_made.once end private def request_for(options = {}) request = {} if options[:query] request[:query] = options[:query] end request end def response_for(response_file, options = {}) response = {} response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_json', "#{response_file}.json")) if options[:status] response[:status] = options[:status] end response end end include WebMockHelper WebMock.disable_net_connect!webfinger-1.0.2/spec/mock_json/0000755000175600017570000000000012736020316015437 5ustar pravipraviwebfinger-1.0.2/spec/mock_json/device_info.json0000644000175600017570000000021712736020316020604 0ustar pravipravi{ "subject": "device:p1.example.com", "links": [{ "rel": "http://example.com/rel/tipsi", "href": "http://192.168.1.5/npap/" }] } webfinger-1.0.2/spec/mock_json/all.json0000644000175600017570000000125312736020316017103 0ustar pravipravi{ "expires": "2012-11-16T19:41:35Z", "subject": "acct:nov@example.com", "aliases": ["http://nov.example.com/"], "properties": { "http://example.com/rel/role/": "employee" }, "links": [{ "rel": "http://webfinger.net/rel/avatar", "type": "image/jpeg", "href": "http://nov.example.com/profile_picture.png" }, { "rel": "http://webfinger.net/rel/profile-page", "href": "http://nov.example.com/" }, { "rel": "blog", "type": "text/html", "href": "http://blogs.nov.example.com/", "titles": { "en-us": "Nov's Blog", "ja": "Novのブログ" } }, { "rel": "vcard", "href": "http://nov.example.com/me.vcf" }] } webfinger-1.0.2/spec/mock_json/open_id.json0000644000175600017570000000046412736020316017753 0ustar pravipravi{ "expires": "2012-11-16T19:41:35Z", "subject": "acct:nov@example.com", "aliases": ["http://nov.example.com/"], "properties": { "http://example.com/rel/role/": "employee" }, "links": [{ "rel": "http://openid.net/specs/connect/1.0/issuer", "href": "https://openid.example.com/" }] } webfinger-1.0.2/spec/mock_json/email_config.json0000644000175600017570000000111612736020316020745 0ustar pravipravi{ "subject": "mailto:nov@example.com", "links": [{ "rel": "http://example.net/rel/smtp-server", "properties": { "http://example.net/email/host": "smtp.example.com", "http://example.net/email/port": "587", "http://example.net/email/login-required": "yes", "http://example.net/email/transport": "starttls" } }, { "rel": "http://example.net/rel/imap-server", "properties": { "http://example.net/email/host": "imap.example.com", "http://example.net/email/port": "993", "http://example.net/email/transport": "ssl" } }] } webfinger-1.0.2/spec/webfinger_spec.rb0000644000175600017570000001466712736020316017002 0ustar pravipravirequire 'spec_helper' describe WebFinger do let(:resource) { 'acct:nov@example.com' } describe '#discover!' do { 'example.com' => 'https://example.com', 'example.com/~nov/' => 'https://example.com', 'nov@example.com' => 'https://example.com', 'nov.matake@example.com' => 'https://example.com', 'acct:nov@example.com' => 'https://example.com', 'mailto:nov@example.com' => 'https://example.com', 'device:example.com' => 'https://example.com', 'unknown:nov@example.com' => 'https://example.com', 'http://example.com/nov' => 'https://example.com', 'https://example.com/nov' => 'https://example.com', 'example.com:8080' => 'https://example.com:8080', 'example.com:8080/~nov/' => 'https://example.com:8080', 'nov@example.com:8080' => 'https://example.com:8080', 'nov.matake@example.com:8080' => 'https://example.com:8080', 'acct:nov@example.com:8080' => 'https://example.com:8080', 'mailto:nov@example.com:8080' => 'https://example.com:8080', 'device:example.com:8080' => 'https://example.com:8080', 'unknown:nov@example.com:8080' => 'https://example.com:8080', 'http://example.com:8080' => 'https://example.com:8080', 'https://example.com:8080' => 'https://example.com:8080', 'discover.example.com' => 'https://discover.example.com', 'discover.example.com/~nov/' => 'https://discover.example.com', 'nov@discover.example.com' => 'https://discover.example.com', 'nov.matake@discover.example.com' => 'https://discover.example.com', 'acct:nov@discover.example.com' => 'https://discover.example.com', 'mailto:nov@discover.example.com' => 'https://discover.example.com', 'device:discover.example.com' => 'https://discover.example.com', 'unknown:nov@discover.example.com' => 'https://discover.example.com', 'http://discover.example.com/nov' => 'https://discover.example.com', 'https://discover.example.com/nov' => 'https://discover.example.com', 'discover.example.com:8080' => 'https://discover.example.com:8080', 'discover.example.com:8080/~nov/' => 'https://discover.example.com:8080', 'nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'nov.matake@discover.example.com:8080' => 'https://discover.example.com:8080', 'acct:nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'mailto:nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'device:discover.example.com:8080' => 'https://discover.example.com:8080', 'unknown:nov@discover.example.com:8080' => 'https://discover.example.com:8080', 'http://discover.example.com:8080/nov' => 'https://discover.example.com:8080', 'https://discover.example.com:8080/nov' => 'https://discover.example.com:8080' }.each do |resource, base_url| endpoint = File.join base_url, '/.well-known/webfinger' context "when resource=#{resource}" do it "should access to #{endpoint}" do mock_json endpoint, 'all', query: {resource: resource} do response = WebFinger.discover! resource response.should be_instance_of WebFinger::Response end end end end context 'with rel option' do shared_examples_for :discovery_with_rel do let(:query_string) do query_params = [{resource: resource}.to_query] Array(rel).each do |_rel_| query_params << {rel: _rel_}.to_query end query_params.join('&') end it 'should request with rel' do query_string.scan('rel').count.should == Array(rel).count mock_json 'https://example.com/.well-known/webfinger', 'all', query: query_string do response = WebFinger.discover! resource, rel: rel response.should be_instance_of WebFinger::Response end end end context 'when single rel' do let(:rel) { 'http://openid.net/specs/connect/1.0/issuer' } it_behaves_like :discovery_with_rel end context 'when multiple rel' do let(:rel) { ['http://openid.net/specs/connect/1.0/issuer', 'vcard'] } it_behaves_like :discovery_with_rel end end context 'when error' do { 400 => WebFinger::BadRequest, 401 => WebFinger::Unauthorized, 403 => WebFinger::Forbidden, 404 => WebFinger::NotFound, 500 => WebFinger::HttpError }.each do |status, exception_class| context "when status=#{status}" do it "should raise #{exception_class}" do expect do mock_json 'https://example.com/.well-known/webfinger', 'all', query: {resource: resource}, status: [status, 'HTTPError'] do response = WebFinger.discover! resource end end.to raise_error exception_class end end end end end describe '#logger' do subject { WebFinger.logger } context 'as default' do it { should be_instance_of Logger } end context 'when specified' do let(:logger) { 'Rails.logger or something' } before { WebFinger.logger = logger } it { should == logger } end end describe '#debugging?' do subject { WebFinger.debugging? } context 'as default' do it { should == false } end context 'when debugging' do before { WebFinger.debug! } it { should == true } context 'when debugging mode canceled' do before { WebFinger.debugging = false } it { should == false } end end end describe '#url_builder' do subject { WebFinger.url_builder } context 'as default' do it { should == URI::HTTPS } end context 'when specified' do let(:url_builder) { 'URI::HTTP or something' } before { WebFinger.url_builder = url_builder } it { should == url_builder } end end describe '#http_client' do subject { WebFinger.http_client } describe '#request_filter' do subject { WebFinger.http_client.request_filter.collect(&:class) } context 'as default' do it { should_not include WebFinger::Debugger::RequestFilter } end context 'when debugging' do before { WebFinger.debug! } it { should include WebFinger::Debugger::RequestFilter } end end end endwebfinger-1.0.2/LICENSE.txt0000644000175600017570000000205212736020316014345 0ustar pravipraviCopyright (c) 2012 nov matake MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.webfinger-1.0.2/VERSION0000644000175600017570000000000512736020316013566 0ustar pravipravi1.0.2webfinger-1.0.2/README.rdoc0000644000175600017570000000424312736020316014334 0ustar pravipravi= WebFinger An Ruby WebFinger client library. {}[http://travis-ci.org/nov/webfinger] Following the latest WebFinger spec discussed at IETF WebFinger WG. http://tools.ietf.org/html/draft-ietf-appsawg-webfinger If you found something different from the latest spec, open an issue please. == Installation Add this line to your application's Gemfile: gem 'webfinger' And then execute: $ bundle Or install it yourself as: $ gem install webfinger == Usage === Basic You can discover resource metadata. WebFinger.discover! 'acct:nov@connect-op.heroku.com' WebFinger.discover! 'connect-op.heroku.com' WebFinger.discover! 'http://connect-op.heroku.com' You can also specify link relations via "rel" option. WebFinger.discover! 'acct:nov@connect-op.heroku.com', rel: 'http://openid.net/specs/connect/1.0/issuer' WebFinger.discover! 'acct:nov@connect-op.heroku.com', rel: ['http://openid.net/specs/connect/1.0/issuer', 'vcard'] === Caching Caching is important in HTTP-based discovery. If you set your own cache object to WebFinger.cache, this gem caches the discovery result until it expires. (the expiry is calculated based on the "expires" value in JRD response) # Set Cache WebFinger.cache = Rails.cache WebFinger.discover! 'acct:nov@connect-op.heroku.com' # do HTTP request WebFinger.discover! 'acct:nov@connect-op.heroku.com' # use cache, no HTTP request === Debugging Once you turn-on debugging, you can see all HTTP request/response in your log. # Turn on debugging WebFinger.debug! # Set logger (OPTIONAL, ::Logger.new(STDOUT) is used as default) WebFinger.logger = Rails.logger You can also specify URL builder to force non-HTTPS access. (NOTE: allow non-HTTPS access only for debugging, not on your production.) WebFinger.url_builder = URI::HTTP # default URI::HTTPS == Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request == Copyright Copyright (c) 2012 nov matake. See LICENSE for details.webfinger-1.0.2/webfinger.gemspec0000644000175600017570000000200512736020316016035 0ustar pravipraviGem::Specification.new do |gem| gem.name = "webfinger" gem.version = File.read("VERSION").delete("\n\r") gem.authors = ["nov matake"] gem.email = ["nov@matake.jp"] gem.description = %q{Ruby WebFinger client library} gem.summary = %q{Ruby WebFinger client library, following IETF WebFinger WG spec updates.} gem.homepage = "https://github.com/nov/webfinger" gem.license = 'MIT' gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.add_runtime_dependency "httpclient", ">= 2.4" gem.add_runtime_dependency "multi_json" gem.add_runtime_dependency "activesupport" gem.add_development_dependency "rake" gem.add_development_dependency "rspec" gem.add_development_dependency 'rspec-its' gem.add_development_dependency "simplecov" gem.add_development_dependency "webmock", ">= 1.6.2" end webfinger-1.0.2/.travis.yml0000644000175600017570000000011712736020316014633 0ustar pravipravibefore_install: - gem install bundler rvm: - 2.0 - 2.1 - 2.2 - 2.3.0webfinger-1.0.2/Gemfile0000644000175600017570000000004612736020316014016 0ustar pravipravisource 'https://rubygems.org' gemspecwebfinger-1.0.2/lib/0000755000175600017570000000000012736020316013271 5ustar pravipraviwebfinger-1.0.2/lib/webfinger.rb0000644000175600017570000000224112736020316015565 0ustar pravipravirequire 'httpclient' require 'multi_json' require 'active_support' require 'active_support/core_ext' module WebFinger VERSION = File.read( File.join(File.dirname(__FILE__), '../VERSION') ).delete("\n\r") module_function def discover!(resource, options = {}) Request.new(resource, options).discover! end def logger @logger end def logger=(logger) @logger = logger end self.logger = ::Logger.new(STDOUT) logger.progname = 'WebFinger' def debugging? @debugging end def debugging=(boolean) @debugging = boolean end def debug! self.debugging = true end self.debugging = false def url_builder @url_builder ||= URI::HTTPS end def url_builder=(builder) @url_builder = builder end def http_client _http_client_ = HTTPClient.new( agent_name: "WebFinger (#{VERSION})" ) _http_client_.request_filter << Debugger::RequestFilter.new if debugging? http_config.try(:call, _http_client_) _http_client_ end def http_config(&block) @http_config ||= block end end require 'webfinger/debugger' require 'webfinger/exception' require 'webfinger/request' require 'webfinger/response'webfinger-1.0.2/lib/webfinger/0000755000175600017570000000000012736020316015241 5ustar pravipraviwebfinger-1.0.2/lib/webfinger/response.rb0000644000175600017570000000063212736020316017425 0ustar pravipravi# NOTE: # Make a JSON Resource Descriptor (JRD) gem as separate one and use it as superclass? module WebFinger class Response < ActiveSupport::HashWithIndifferentAccess [:subject, :aliases, :properties, :links].each do |method| define_method method do self[method] end end def link_for(rel) links.detect do |link| link[:rel] == rel end end end endwebfinger-1.0.2/lib/webfinger/request.rb0000644000175600017570000000360712736020316017264 0ustar pravipravimodule WebFinger class Request attr_accessor :resource, :relations def initialize(resource, options = {}) self.resource = resource self.relations = Array(options[:rel] || options[:relations]) @options = options end def discover! handle_response do WebFinger.http_client.get_content endpoint.to_s end end private def endpoint path = '/.well-known/webfinger' host, port = host_and_port WebFinger.url_builder.build [nil, host, port, path, query_string, nil] end def host_and_port uri = URI.parse(resource) rescue nil if uri.try(:host).present? [uri.host, [80, 443].include?(uri.port) ? nil : uri.port] else scheme_or_host, host_or_port, port_or_nil = resource.split('@').last.split('/').first.split(':') case host_or_port when nil, /\d+/ [scheme_or_host, host_or_port.try(:to_i)] else [host_or_port, port_or_nil.try(:to_i)] end end end def query_string query_params.join('&') end def query_params query_params = [{resource: resource}] relations.each do |relation| query_params << {rel: relation} end query_params.collect(&:to_query) end def handle_response raw_response = yield jrd = MultiJson.load(raw_response).with_indifferent_access Response.new jrd rescue HTTPClient::BadResponseError => e case e.res.try(:status) when nil raise e when 400 raise BadRequest.new('Bad Request', raw_response) when 401 raise Unauthorized.new('Unauthorized', raw_response) when 403 raise Forbidden.new('Forbidden', raw_response) when 404 raise NotFound.new('Not Found', raw_response) else raise HttpError.new(e.res.status, e.res.reason, raw_response) end end end endwebfinger-1.0.2/lib/webfinger/debugger/0000755000175600017570000000000012736020316017025 5ustar pravipraviwebfinger-1.0.2/lib/webfinger/debugger/request_filter.rb0000644000175600017570000000125712736020316022414 0ustar pravipravimodule WebFinger module Debugger class RequestFilter # Callback called in HTTPClient (before sending a request) # request:: HTTP::Message def filter_request(request) started = "======= [WebFinger] HTTP REQUEST STARTED =======" WebFinger.logger.info [started, request.dump].join("\n") end # Callback called in HTTPClient (after received a response) # request:: HTTP::Message # response:: HTTP::Message def filter_response(request, response) finished = "======= [WebFinger] HTTP REQUEST FINISHED =======" WebFinger.logger.info ['-' * 50, response.dump, finished].join("\n") end end end endwebfinger-1.0.2/lib/webfinger/debugger.rb0000644000175600017570000000012012736020316017343 0ustar pravipraviDir[File.dirname(__FILE__) + '/debugger/*.rb'].each do |file| require file endwebfinger-1.0.2/lib/webfinger/exception.rb0000644000175600017570000000143012736020316017562 0ustar pravipravimodule WebFinger class Exception < StandardError; end class HttpError < Exception attr_accessor :status, :response def initialize(status, message = nil, response = nil) super message @status = status @response = response end end class BadRequest < HttpError def initialize(message = nil, response = nil) super 400, message, response end end class Unauthorized < HttpError def initialize(message = nil, response = nil) super 401, message, response end end class Forbidden < HttpError def initialize(message = nil, response = nil) super 403, message, response end end class NotFound < HttpError def initialize(message = nil, response = nil) super 404, message, response end end endwebfinger-1.0.2/Rakefile0000644000175600017570000000062212736020316014170 0ustar pravipravirequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) namespace :coverage do desc "Open coverage report" task :report do require 'simplecov' `open "#{File.join SimpleCov.coverage_path, 'index.html'}"` end end task :spec do Rake::Task[:'coverage:report'].invoke unless ENV['TRAVIS_RUBY_VERSION'] end task :default => :specwebfinger-1.0.2/.gitignore0000644000175600017570000000023312736020316014511 0ustar pravipravi*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage* doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp