swd-1.0.1/0000755000175600017570000000000012736015045011352 5ustar pravipraviswd-1.0.1/spec/0000755000175600017570000000000012736015045012304 5ustar pravipraviswd-1.0.1/spec/swd_spec.rb0000644000175600017570000000732012736015045014442 0ustar pravipravirequire 'spec_helper' describe SWD do after { SWD.debugging = false } its(:logger) { should be_a Logger } its(:debugging?) { should == false } its(:cache) { should be_a SWD::Cache } describe '#discover!' do it 'should return SWD::Response' do mock_json "https://example.com/.well-known/simple-web-discovery", 'success', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do SWD.discover!( :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar', :host => 'example.com' ).should be_a SWD::Response end end context 'when port specified' do it 'should use it' do mock_json "https://example.com:8080/.well-known/simple-web-discovery", 'success', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do SWD.discover!( :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar', :host => 'example.com', :port => 8080 ).should be_a SWD::Response end end context 'when redirected to different host' do context 'with port' do it 'should success' do mock_json "https://example.com:8080/.well-known/simple-web-discovery", 'redirect_with_port', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do mock_json "https://swd.proseware.com:8080/swd_server", 'success', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do SWD.discover!( :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar', :host => 'example.com', :port => 8080 ).should be_a SWD::Response end end end end context 'without port' do it 'should success' do mock_json "https://example.com:8080/.well-known/simple-web-discovery", 'redirect', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do mock_json "https://swd.proseware.com/swd_server", 'success', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do SWD.discover!( :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar', :host => 'example.com', :port => 8080 ).should be_a SWD::Response end end end end end end end describe '.debug!' do before { SWD.debug! } its(:debugging?) { should == true } end describe '.debug' do it 'should enable debugging within given block' do SWD.debug do SWD.debugging?.should == true end SWD.debugging?.should == false end it 'should not force disable debugging' do SWD.debug! SWD.debug do SWD.debugging?.should == true end SWD.debugging?.should == true end end describe '.http_client' do context 'with http_config' do before do SWD.http_config do |config| config.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE end end it 'should configure http_client' do SWD.http_client.ssl_config.verify_mode.should == OpenSSL::SSL::VERIFY_NONE end end end endswd-1.0.1/spec/spec_helper.rb0000644000175600017570000000040012736015045015114 0ustar pravipravirequire 'simplecov' SimpleCov.start do add_filter 'spec' end require 'rspec' require 'rspec/its' require 'swd' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end require 'helpers/webmock_helper'swd-1.0.1/spec/helpers/0000755000175600017570000000000012736015045013746 5ustar pravipraviswd-1.0.1/spec/helpers/webmock_helper.rb0000644000175600017570000000145612736015045017267 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 = {} request[:query] = options[:query] 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!swd-1.0.1/spec/swd/0000755000175600017570000000000012736015045013101 5ustar pravipraviswd-1.0.1/spec/swd/resource_spec.rb0000644000175600017570000001252312736015045016272 0ustar pravipravirequire 'spec_helper' describe SWD::Resource do subject { resource } let(:resource) { SWD::Resource.new attributes } let(:attributes) do { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar', :host => 'example.com' } end its(:path) { should == '/.well-known/simple-web-discovery' } [:principal, :service, :host].each do |key| it "should require #{key}" do expect do SWD::Resource.new attributes.merge(key => nil) end.to raise_error AttrRequired::AttrMissing end end describe '#discover!' do context 'when succeeded' do it 'should return SWD::Response' do mock_json resource.endpoint, 'success' do res = resource.discover! res.should be_a SWD::Response res.locations == ['http://calendars.proseware.com/calendars/joseph'] res.location == 'http://calendars.proseware.com/calendars/joseph' res.raw == { 'locations' => ['http://calendars.proseware.com/calendars/joseph'] } end end end context 'when redirected' do it 'should follow redirect' do expect(resource).to receive(:redirect_to).with( 'https://swd.proseware.com/swd_server', nil ) mock_json resource.endpoint, 'redirect' do resource.discover! end end context 'when expired' do it 'should return SWD::Response' do mock_json resource.endpoint, 'redirect_expired' do expect { res = resource.discover! }.to raise_error SWD::Resource::Expired end end end context 'otherwise' do it 'should return SWD::Response' do mock_json resource.endpoint, 'redirect' do mock_json 'https://swd.proseware.com/swd_server', 'success', :query => { :principal => 'mailto:joe@example.com', :service => 'urn:adatum.com:calendar' } do res = resource.discover! res.should be_a SWD::Response end end end end end describe 'error handling' do before(:all) do module SWD class << self module HttpClientWithCached def http_client @http_client ||= super end end prepend HttpClientWithCached end end end after(:all) do module SWD class << self module HttpClientWithCacheCleared def http_client @http_client = nil super end end prepend HttpClientWithCacheCleared end end end context 'when invalid SSL cert' do it do expect(SWD.http_client).to receive(:get_content).and_raise(OpenSSL::SSL::SSLError) expect { res = resource.discover! }.to raise_error SWD::Exception end end context 'when invalid JSON' do it do expect(SWD.http_client).to receive(:get_content).and_raise(JSON::ParserError) expect { res = resource.discover! }.to raise_error SWD::Exception end end context 'when SocketError' do it do expect(SWD.http_client).to receive(:get_content).and_raise(SocketError) expect { res = resource.discover! }.to raise_error SWD::Exception end end context 'when BadResponseError without response' do it do expect(SWD.http_client).to receive(:get_content).and_raise(HTTPClient::BadResponseError.new('')) expect { res = resource.discover! }.to raise_error SWD::Exception end end context 'when bad request' do it 'should raise SWD::BadRequest' do mock_json resource.endpoint, 'blank', :status => 400 do expect { res = resource.discover! }.to raise_error SWD::BadRequest end end end context 'when unauthorized' do it 'should raise SWD::Unauthorized' do mock_json resource.endpoint, 'blank', :status => 401 do expect { res = resource.discover! }.to raise_error SWD::Unauthorized end end end context 'when forbidden' do it 'should raise SWD::Forbidden' do mock_json resource.endpoint, 'blank', :status => 403 do expect { res = resource.discover! }.to raise_error SWD::Forbidden end end end context 'when not found' do it 'should raise SWD::NotFound' do mock_json resource.endpoint, 'blank', :status => 404 do expect { res = resource.discover! }.to raise_error SWD::NotFound end end end context 'when other error happened' do it 'should raise SWD::HttpError' do mock_json resource.endpoint, 'blank', :status => 500 do expect { res = resource.discover! }.to raise_error SWD::HttpError end end end end end describe '#endpoint' do its(:endpoint) { should be_instance_of URI::HTTPS } context 'with URI::HTTP builder' do before do @original_url_builder = SWD.url_builder SWD.url_builder = URI::HTTP end after do SWD.url_builder = @original_url_builder end its(:endpoint) { should be_instance_of URI::HTTP } end end endswd-1.0.1/spec/swd/debugger/0000755000175600017570000000000012736015045014665 5ustar pravipraviswd-1.0.1/spec/swd/debugger/request_filter_spec.rb0000644000175600017570000000166012736015045021264 0ustar pravipravirequire 'spec_helper' describe SWD::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) { SWD::Debugger::RequestFilter.new } describe '#filter_request' do it 'should log request' do expect(SWD.logger).to receive(:info).with( "======= [SWD] HTTP REQUEST STARTED =======\n" + request.dump ) request_filter.filter_request(request) end end describe '#filter_response' do it 'should log response' do expect(SWD.logger).to receive(:info).with( "--------------------------------------------------\n" + response.dump + "\n======= [SWD] HTTP REQUEST FINISHED =======" ) request_filter.filter_response(request, response) end end endswd-1.0.1/spec/swd/exception_spec.rb0000644000175600017570000000115512736015045016440 0ustar pravipravirequire 'spec_helper' describe SWD::HttpError do subject do SWD::HttpError.new 400, 'Bad Request', HTTP::Message.new_response('') end its(:status) { should == 400 } its(:message) { should == 'Bad Request' } its(:response) { should be_a HTTP::Message } end describe SWD::BadRequest do its(:status) { should == 400 } its(:message) { should == 'SWD::BadRequest' } end describe SWD::Unauthorized do its(:status) { should == 401 } its(:message) { should == 'SWD::Unauthorized' } end describe SWD::Forbidden do its(:status) { should == 403 } its(:message) { should == 'SWD::Forbidden' } endswd-1.0.1/spec/mock_json/0000755000175600017570000000000012736015045014266 5ustar pravipraviswd-1.0.1/spec/mock_json/redirect_expired.json0000644000175600017570000000016512736015045020504 0ustar pravipravi{ "SWD_service_redirect": { "location": "https://swd.proseware.com/swd_server", "expires": 1300752001 } }swd-1.0.1/spec/mock_json/blank.json0000644000175600017570000000000012736015045016236 0ustar pravipraviswd-1.0.1/spec/mock_json/redirect_with_port.json0000644000175600017570000000013712736015045021062 0ustar pravipravi{ "SWD_service_redirect": { "location": "https://swd.proseware.com:8080/swd_server" } }swd-1.0.1/spec/mock_json/success.json0000644000175600017570000000010612736015045016626 0ustar pravipravi{ "locations": ["http://calendars.proseware.com/calendars/joseph"] }swd-1.0.1/spec/mock_json/redirect.json0000644000175600017570000000013212736015045016756 0ustar pravipravi{ "SWD_service_redirect": { "location": "https://swd.proseware.com/swd_server" } }swd-1.0.1/LICENSE0000644000175600017570000000203612736015045012360 0ustar pravipraviCopyright (c) 2011 nov matake 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. swd-1.0.1/VERSION0000644000175600017570000000000512736015045012415 0ustar pravipravi1.0.1swd-1.0.1/.rspec0000644000175600017570000000003712736015045012467 0ustar pravipravi--color --format=documentation swd-1.0.1/README.rdoc0000644000175600017570000000232712736015045013164 0ustar pravipravi= SWD SWD (Simple Web Discovery) Client Library NOTE: SWD was designed to be the core of OpenID Connect Discovery, but it's replaced with WebFinger. So I'm no longer supporting this gem. == Installation gem install swd == Resources * View Source on GitHub (https://github.com/nov/swd) * Report Issues on GitHub (https://github.com/nov/swd/issues) == Examples begin res = SWD.discover!( :principal => 'nov@matake.jp', :service => 'http://openid.net/specs/connect/1.0/issuer', :host => 'openid-connect.herokuapp.com' ) # => SWD::Response puts res.location, res.locations, res.raw rescue SWD::HttpError => e puts "#{e.status} #{e.message}" rescue SWD::Exception => e puts e.message end == Note on Patches/Pull Requests * Fork the project. * Make your feature addition or bug fix. * Add tests for it. This is important so I don't break it in a future version unintentionally. * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) * Send me a pull request. Bonus points for topic branches. == Copyright Copyright (c) 2011 nov matake. See LICENSE for details. swd-1.0.1/swd.gemspec0000644000175600017570000000176412736015045013524 0ustar pravipraviGem::Specification.new do |s| s.name = "swd" s.version = File.read("VERSION") s.authors = ["nov matake"] s.email = ["nov@matake.jp"] s.homepage = "https://github.com/nov/swd" s.summary = %q{SWD (Simple Web Discovery) Client Library} s.description = %q{SWD (Simple Web Discovery) Client Library} s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_runtime_dependency "json", ">= 1.4.3" s.add_runtime_dependency "httpclient", ">= 2.4" s.add_runtime_dependency "activesupport", ">= 3" s.add_runtime_dependency "i18n" s.add_runtime_dependency "attr_required", ">= 0.0.5" s.add_development_dependency "rake" s.add_development_dependency "rspec" s.add_development_dependency 'rspec-its' s.add_development_dependency "webmock" s.add_development_dependency 'simplecov' endswd-1.0.1/.travis.yml0000644000175600017570000000011712736015045013462 0ustar pravipravibefore_install: - gem install bundler rvm: - 2.0 - 2.1 - 2.2 - 2.3.0swd-1.0.1/Gemfile0000644000175600017570000000013612736015045012645 0ustar pravipravisource "http://rubygems.org" platform :jruby do gem 'jruby-openssl', '>= 0.7' end gemspec swd-1.0.1/lib/0000755000175600017570000000000012736015045012120 5ustar pravipraviswd-1.0.1/lib/swd/0000755000175600017570000000000012736015045012715 5ustar pravipraviswd-1.0.1/lib/swd/response.rb0000644000175600017570000000031612736015045015100 0ustar pravipravimodule SWD class Response attr_accessor :locations, :location, :raw def initialize(hash) @locations = hash[:locations] @location = @locations.first @raw = hash end end endswd-1.0.1/lib/swd/debugger/0000755000175600017570000000000012736015045014501 5ustar pravipraviswd-1.0.1/lib/swd/debugger/request_filter.rb0000644000175600017570000000122112736015045020057 0ustar pravipravimodule SWD module Debugger class RequestFilter # Callback called in HTTPClient (before sending a request) # request:: HTTP::Message def filter_request(request) started = "======= [SWD] HTTP REQUEST STARTED =======" SWD.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 = "======= [SWD] HTTP REQUEST FINISHED =======" SWD.logger.info ['-' * 50, response.dump, finished].join("\n") end end end endswd-1.0.1/lib/swd/cache.rb0000644000175600017570000000013512736015045014304 0ustar pravipravimodule SWD class Cache def fetch(cache_key, options = {}) yield end end endswd-1.0.1/lib/swd/debugger.rb0000644000175600017570000000012012736015045015017 0ustar pravipraviDir[File.dirname(__FILE__) + '/debugger/*.rb'].each do |file| require file endswd-1.0.1/lib/swd/resource.rb0000644000175600017570000000417112736015045015074 0ustar pravipravimodule SWD class Resource include AttrRequired, AttrOptional attr_required :principal, :service, :host, :path attr_optional :port class Expired < Exception; end def initialize(attributes = {}) (optional_attributes + required_attributes).each do |key| self.send "#{key}=", attributes[key] end @path ||= '/.well-known/simple-web-discovery' attr_missing! end def discover!(cache_options = {}) SWD.cache.fetch(cache_key, cache_options) do handle_response do SWD.http_client.get_content endpoint.to_s end end end def endpoint SWD.url_builder.build [nil, host, port, path, { :principal => principal, :service => service }.to_query, nil] rescue URI::Error => e raise Exception.new(e.message) end private def handle_response res = JSON.parse(yield).with_indifferent_access if redirect = res[:SWD_service_redirect] redirect_to redirect[:location], redirect[:expires] else to_response_object(res) end rescue HTTPClient::BadResponseError => e case e.res.try(:status) when nil raise Exception.new(e.message) when 400 raise BadRequest.new('Bad Request', res) when 401 raise Unauthorized.new('Unauthorized', res) when 403 raise Forbidden.new('Forbidden', res) when 404 raise NotFound.new('Not Found', res) else raise HttpError.new(e.res.status, e.res.reason, res) end rescue JSON::ParserError, OpenSSL::SSL::SSLError, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e raise Exception.new(e.message) end def to_response_object(hash) Response.new hash end def redirect_to(location, expires) uri = URI.parse(location) @host, @path, @port = uri.host, uri.path, uri.port raise Expired if expires && expires.to_i < Time.now.utc.to_i discover! end def cache_key md5 = Digest::MD5.hexdigest [ principal, service, host ].join(' ') "swd:resource:#{md5}" end end endswd-1.0.1/lib/swd/exception.rb0000644000175600017570000000142212736015045015237 0ustar pravipravimodule SWD 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 endswd-1.0.1/lib/swd.rb0000644000175600017570000000300412736015045013237 0ustar pravipravirequire 'logger' require 'digest/md5' require 'json' require 'active_support' require 'active_support/core_ext' require 'httpclient' require 'attr_required' require 'attr_optional' module SWD VERSION = ::File.read( ::File.join(::File.dirname(__FILE__), '../VERSION') ) def self.cache=(cache) @@cache = cache end def self.cache @@cache end def self.discover!(attributes = {}) Resource.new(attributes).discover!(attributes[:cache]) end def self.logger @@logger end def self.logger=(logger) @@logger = logger end self.logger = ::Logger.new(STDOUT) self.logger.progname = 'SWD' def self.debugging? @@debugging end def self.debugging=(boolean) @@debugging = boolean end def self.debug! self.debugging = true end def self.debug(&block) original = self.debugging? self.debugging = true yield ensure self.debugging = original end self.debugging = false def self.http_client _http_client_ = HTTPClient.new( :agent_name => "SWD (#{VERSION})" ) _http_client_.request_filter << Debugger::RequestFilter.new if debugging? http_config.try(:call, _http_client_) _http_client_ end def self.http_config(&block) @@http_config ||= block end def self.url_builder @@url_builder ||= URI::HTTPS end def self.url_builder=(builder) @@url_builder = builder end end require 'swd/cache' require 'swd/exception' require 'swd/resource' require 'swd/response' require 'swd/debugger' SWD.cache = SWD::Cache.newswd-1.0.1/Rakefile0000644000175600017570000000057012736015045013021 0ustar pravipravirequire 'bundler/gem_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: :specswd-1.0.1/.gitignore0000644000175600017570000000022312736015045013337 0ustar pravipravi## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg ## PROJECT::SPECIFIC