openid_connect-1.2.0/0000755000004100000410000000000013704151201014523 5ustar www-datawww-dataopenid_connect-1.2.0/.travis.yml0000644000004100000410000000011413704151201016630 0ustar www-datawww-databefore_install: - gem install bundler rvm: - 2.3.6 - 2.4.3 - 2.5.0 openid_connect-1.2.0/.rspec0000644000004100000410000000003713704151201015640 0ustar www-datawww-data--color --format=documentation openid_connect-1.2.0/spec/0000755000004100000410000000000013704151201015455 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect_spec.rb0000644000004100000410000000332113704151201022002 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect do after { OpenIDConnect.debugging = false } its(:logger) { should be_a Logger } its(:debugging?) { should == false } describe '.debug!' do before { OpenIDConnect.debug! } its(:debugging?) { should == true } end describe '.debug' do it 'should enable debugging within given block' do OpenIDConnect.debug do SWD.debugging?.should == true WebFinger.debugging?.should == true Rack::OAuth2.debugging?.should == true OpenIDConnect.debugging?.should == true end SWD.debugging?.should == false Rack::OAuth2.debugging?.should == false OpenIDConnect.debugging?.should == false end it 'should not force disable debugging' do SWD.debug! WebFinger.debug! Rack::OAuth2.debug! OpenIDConnect.debug! OpenIDConnect.debug do SWD.debugging?.should == true WebFinger.debugging?.should == true Rack::OAuth2.debugging?.should == true OpenIDConnect.debugging?.should == true end SWD.debugging?.should == true WebFinger.debugging?.should == true Rack::OAuth2.debugging?.should == true OpenIDConnect.debugging?.should == true end end describe '.http_client' do context 'with http_config' do before do OpenIDConnect.http_config do |config| config.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE end end it 'should configure OpenIDConnect, SWD and Rack::OAuth2\'s http_client' do [OpenIDConnect, SWD, WebFinger, Rack::OAuth2].each do |klass| klass.http_client.ssl_config.verify_mode.should == OpenSSL::SSL::VERIFY_NONE end end end end endopenid_connect-1.2.0/spec/helpers/0000755000004100000410000000000013704151201017117 5ustar www-datawww-dataopenid_connect-1.2.0/spec/helpers/webmock_helper.rb0000644000004100000410000000204113704151201022427 0ustar www-datawww-datarequire 'webmock/rspec' module WebMockHelper def mock_json(method, endpoint, response_file, options = {}) stub_request(method, endpoint).with( request_for(method, options) ).to_return( response_for(response_file, options) ) result = yield a_request(method, endpoint).with( request_for(method, options) ).should have_been_made.once result end private def request_for(method, options = {}) request = {} case method when :post, :put request[:body] = options[:params] else request[:query] = options[:params] end if options[:request_header] request[:headers] = options[:request_header] end request end def response_for(response_file, options = {}) response = {} response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_response', "#{response_file}.#{options[:format] || :json}")) if options[:status] response[:status] = options[:status] end response end end include WebMockHelper WebMock.disable_net_connect!openid_connect-1.2.0/spec/helpers/crypto_spec_helper.rb0000644000004100000410000000117013704151201023334 0ustar www-datawww-datamodule CryptoSpecHelper def rsa_key @rsa_key ||= OpenSSL::PKey::RSA.generate 2048 end def public_key @public_key ||= rsa_key.public_key end def private_key @private_key ||= OpenSSL::PKey::RSA.new rsa_key.export(OpenSSL::Cipher.new('DES-EDE3-CBC'), 'pass-phrase'), 'pass-phrase' end def ec_key @ec_key ||= OpenSSL::PKey::EC.new('prime256v1').generate_key end def ec_public_key unless @ec_public_key @ec_public_key = OpenSSL::PKey::EC.new ec_key @ec_public_key.private_key = nil end @ec_public_key end def ec_private_key ec_key end end include CryptoSpecHelperopenid_connect-1.2.0/spec/rack/0000755000004100000410000000000013704151201016375 5ustar www-datawww-dataopenid_connect-1.2.0/spec/rack/oauth2/0000755000004100000410000000000013704151201017577 5ustar www-datawww-dataopenid_connect-1.2.0/spec/rack/oauth2/server/0000755000004100000410000000000013704151201021105 5ustar www-datawww-dataopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/0000755000004100000410000000000013704151201023117 5ustar www-datawww-dataopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/request_with_connect_params_spec.rb0000644000004100000410000000211513704151201032254 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::RequestWithConnectParams do let(:base_params) do { client_id: 'client_id', redirect_uri: 'https://client.example.com/callback' } end let(:env) { Rack::MockRequest.env_for("/authorize?#{base_params.to_query}&#{params.to_query}") } let(:request) { Rack::OAuth2::Server::Authorize::Request.new env } subject { request } describe 'prompt' do context 'when a space-delimited string given' do let(:params) do {prompt: 'login consent'} end its(:prompt) { should == ['login', 'consent']} end context 'when a single string given' do let(:params) do {prompt: 'login'} end its(:prompt) { should == ['login']} end end describe 'max_age' do context 'when numeric value given' do let(:params) do {max_age: '5'} end its(:max_age) { should == 5} end context 'when non-numeric string given' do let(:params) do {max_age: 'foo'} end its(:max_age) { should == 0} end end endopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/extension/0000755000004100000410000000000013704151201025133 5ustar www-datawww-dataopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/extension/id_token_and_token_spec.rb0000644000004100000410000000440213704151201032310 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::IdTokenAndToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get('/?response_type=token%20id_token&client_id=client&state=state') } let(:redirect_uri) { 'http://client.example.com/callback' } let(:bearer_token) { Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context 'when id_token is given' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.access_token = bearer_token response.id_token = id_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "access_token=#{bearer_token.access_token}" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include "token_type=#{bearer_token.token_type}" } its(:location) { should include 'state=state' } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:location) { should include 'id_token=id_token' } end end context 'otherwise' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.access_token = bearer_token response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::IdTokenAndToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end endopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.rb0000644000004100000410000000460213704151201034126 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::CodeAndIdTokenAndToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get('/?response_type=code%20id_token%20token&client_id=client&state=state') } let(:redirect_uri) { 'http://client.example.com/callback' } let(:bearer_token) { Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') } let(:code) { 'authorization_code' } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context 'when id_token is given' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.code = code response.id_token = id_token response.access_token = bearer_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "access_token=#{bearer_token.access_token}" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include "token_type=#{bearer_token.token_type}" } its(:location) { should include "code=#{code}" } its(:location) { should include 'state=state' } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:location) { should include 'id_token=id_token' } end end context 'otherwise' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'access_token', 'code', 'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::CodeAndIdTokenAndToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end endopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/extension/id_token_spec.rb0000644000004100000410000000432113704151201030266 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::IdToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get('/?response_type=id_token&client_id=client&state=state') } let(:redirect_uri) { 'http://client.example.com/callback' } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context 'when id_token is given' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.id_token = id_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include 'state=state' } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:location) { should include 'id_token=id_token' } end end context 'when id_token is missing' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::IdToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end context 'when openid scope given' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id&scope=openid") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::IdToken::Request.new env } it do request.openid_connect_request?.should == true end end endopenid_connect-1.2.0/spec/rack/oauth2/server/authorize/extension/code_and_id_token_spec.rb0000644000004100000410000000412113704151201032100 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::CodeAndIdToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get("/?response_type=code%20id_token&client_id=client&state=state") } let(:redirect_uri) { 'http://client.example.com/callback' } let(:code) { 'authorization_code' } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context "when id_token is given" do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.code = code response.id_token = id_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "code=#{code}" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include "state=state" } context 'when id_token is String' do let(:id_token) { 'non_jwt_string' } its(:location) { should include "id_token=non_jwt_string" } end end context "otherwise" do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.code = code response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::CodeAndIdToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end endopenid_connect-1.2.0/spec/rack/oauth2/server/token/0000755000004100000410000000000013704151201022225 5ustar www-datawww-dataopenid_connect-1.2.0/spec/rack/oauth2/server/token/authorization_code_spec.rb0000644000004100000410000000273613704151201027466 0ustar www-datawww-datarequire 'spec_helper.rb' describe Rack::OAuth2::Server::Token::AuthorizationCode do subject { response } let(:request) { Rack::MockRequest.new app } let :response do request.post('/', params: { grant_type: 'authorization_code', client_id: 'client_id', code: 'authorization_code', redirect_uri: 'http://client.example.com/callback' }) end let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', exp: 1313424327, iat: 1313420327, nonce: 'nonce', secret: 'secret' ).to_jwt private_key end context "when id_token is given" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') response.id_token = id_token end end its(:status) { should == 200 } its(:body) { should include "\"id_token\":\"#{id_token}\"" } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:body) { should include "\"id_token\":\"id_token\"" } end end context "otherwise" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') end end its(:status) { should == 200 } its(:body) { should_not include "id_token" } end endopenid_connect-1.2.0/spec/rack/oauth2/server/token/refresh_token_spec.rb0000644000004100000410000000264113704151201026425 0ustar www-datawww-datarequire 'spec_helper.rb' describe Rack::OAuth2::Server::Token::RefreshToken do subject { response } let(:request) { Rack::MockRequest.new app } let :response do request.post('/', params: { grant_type: "refresh_token", client_id: "client_id", refresh_token: "refresh_token" }) end let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', exp: 1313424327, iat: 1313420327, nonce: 'nonce', secret: 'secret' ).to_jwt private_key end context "when id_token is given" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') response.id_token = id_token end end its(:status) { should == 200 } its(:body) { should include "\"id_token\":\"#{id_token}\"" } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:body) { should include "\"id_token\":\"id_token\"" } end end context "otherwise" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') end end its(:status) { should == 200 } its(:body) { should_not include "id_token" } end end openid_connect-1.2.0/spec/mock_response/0000755000004100000410000000000013704151201020324 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/client/0000755000004100000410000000000013704151201021602 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/client/updated.json0000644000004100000410000000004713704151201024124 0ustar www-datawww-data{ "client_id": "client.example.com" }openid_connect-1.2.0/spec/mock_response/client/rotated.json0000644000004100000410000000014513704151201024137 0ustar www-datawww-data{ "client_id": "client.example.com", "client_secret": "new_client_secret", "expires_in": 3600 }openid_connect-1.2.0/spec/mock_response/client/registered.json0000644000004100000410000000014113704151201024626 0ustar www-datawww-data{ "client_id": "client.example.com", "client_secret": "client_secret", "expires_in": 3600 }openid_connect-1.2.0/spec/mock_response/userinfo/0000755000004100000410000000000013704151201022156 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/userinfo/openid.json0000644000004100000410000000113713704151201024331 0ustar www-datawww-data{ "id": "90125", "name": "Jonathan Q. Doe", "given_name": "Jonathan", "middle_name": "Q.", "family_name": "Doe", "nickname": "John", "email": "johndoe@example.com", "verified": true, "profile": "http://example.com/johndoe/", "picture": "http://example.com/johndoe/me.jpg", "website": "http://john.doe.blogs.example.net/", "gender": "male", "birthdate": "05/02/0000", "zoneinfo": "America/Los_Angeles", "locale": "en_US", "phone_number": "+1 (425) 555-1212", "address": { "region": "WA", "country": "United States" }, "last_updated": "2011-06-29T21:10:22+0000" }openid_connect-1.2.0/spec/mock_response/errors/0000755000004100000410000000000013704151201021640 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/errors/unknown.json0000644000004100000410000000002413704151201024226 0ustar www-datawww-dataFuckin Unknown Erroropenid_connect-1.2.0/spec/mock_response/errors/insufficient_scope.json0000644000004100000410000000004313704151201026407 0ustar www-datawww-data{ "error": "insufficient_scope" }openid_connect-1.2.0/spec/mock_response/errors/invalid_access_token.json0000644000004100000410000000004513704151201026701 0ustar www-datawww-data{ "error": "invalid_access_token" }openid_connect-1.2.0/spec/mock_response/errors/invalid_request.json0000644000004100000410000000004013704151201025723 0ustar www-datawww-data{ "error": "invalid_request" }openid_connect-1.2.0/spec/mock_response/id_token.json0000644000004100000410000000022513704151201023012 0ustar www-datawww-data{ "iss": "https://server.example.com", "aud": "client_id", "user_id": "user_id", "nonce": "nonce", "exp": 1303852880, "iat": 1303850880 }openid_connect-1.2.0/spec/mock_response/discovery/0000755000004100000410000000000013704151201022333 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/discovery/config_with_invalid_issuer.json0000644000004100000410000000135513704151201030632 0ustar www-datawww-data{ "issuer": "https://attacker.example.com", "authorization_endpoint": "https://connect-op.heroku.com/authorizations/new", "token_endpoint": "https://connect-op.heroku.com/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com/userinfo", "registration_endpoint": "https://connect-op.heroku.com/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-1.2.0/spec/mock_response/discovery/config_with_custom_port.json0000644000004100000410000000140713704151201030166 0ustar www-datawww-data{ "issuer": "https://connect-op.heroku.com:8080", "authorization_endpoint": "https://connect-op.heroku.com:8080/authorizations/new", "token_endpoint": "https://connect-op.heroku.com:8080/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com:8080/userinfo", "registration_endpoint": "https://connect-op.heroku.com:8080/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-1.2.0/spec/mock_response/discovery/config_without_issuer.json0000644000004100000410000000127713704151201027657 0ustar www-datawww-data{ "authorization_endpoint": "https://connect-op.heroku.com/authorizations/new", "token_endpoint": "https://connect-op.heroku.com/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com/userinfo", "registration_endpoint": "https://connect-op.heroku.com/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-1.2.0/spec/mock_response/discovery/swd.json0000644000004100000410000000006113704151201024020 0ustar www-datawww-data{ "locations": ["https://server.example.com"] }openid_connect-1.2.0/spec/mock_response/discovery/config.json0000644000004100000410000000135613704151201024500 0ustar www-datawww-data{ "issuer": "https://connect-op.heroku.com", "authorization_endpoint": "https://connect-op.heroku.com/authorizations/new", "token_endpoint": "https://connect-op.heroku.com/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com/userinfo", "registration_endpoint": "https://connect-op.heroku.com/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-1.2.0/spec/mock_response/discovery/config_with_path.json0000644000004100000410000000137013704151201026543 0ustar www-datawww-data{ "issuer": "https://connect.openid4.us/abop", "authorization_endpoint": "https://connect.openid4.us/abop/authorizations/new", "token_endpoint": "https://connect.openid4.us/abop/access_tokens", "userinfo_endpoint": "https://connect.openid4.us/abop/userinfo", "registration_endpoint": "https://connect.openid4.us/abop/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-1.2.0/spec/mock_response/discovery/webfinger.json0000644000004100000410000000031213704151201025172 0ustar www-datawww-data{ "expires": "2013-03-09T06:43:23Z", "links": [{ "rel": "http://openid.net/specs/connect/1.0/issuer", "href": "https://server.example.com" }], "subject": "acct:foo@server.example.com" } openid_connect-1.2.0/spec/mock_response/access_token/0000755000004100000410000000000013704151201022765 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/access_token/bearer_with_id_token.json0000644000004100000410000000021513704151201030025 0ustar www-datawww-data{ "access_token":"access_token", "id_token":"id_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid_connect-1.2.0/spec/mock_response/access_token/without_token_type.json0000644000004100000410000000004413704151201027622 0ustar www-datawww-data{ "access_token":"access_token" } openid_connect-1.2.0/spec/mock_response/access_token/mac.json0000644000004100000410000000026013704151201024416 0ustar www-datawww-data{ "token_type": "mac", "mac_algorithm": "hmac-sha-256", "expires_in": 3600, "mac_key": "secret", "refresh_token": "refresh_token", "access_token": "access_token" } openid_connect-1.2.0/spec/mock_response/access_token/bearer.json0000644000004100000410000000016413704151201025121 0ustar www-datawww-data{ "access_token":"access_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid_connect-1.2.0/spec/mock_response/access_token/invalid_json.json0000644000004100000410000000003113704151201026331 0ustar www-datawww-dataaccess_token=access_tokenopenid_connect-1.2.0/spec/mock_response/public_keys/0000755000004100000410000000000013704151201022635 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/public_keys/jwks.json0000644000004100000410000000063313704151201024510 0ustar www-datawww-data{ "keys": [{ "kty": "RSA", "e": "AQAB", "n": "u4liYNFzgsRr1ERdUY7CY6r4nefi3RzIhK5fdPgdZSMEEflACWAuJu21_TcDpbZ1-6Kbq7zShFsVTAnBkWdO7EP1Rsn11fZpi9m_zEq_uRY-4RpNwp3S9xSdoQ4F3-js1EMaDQ6km0-c0gvr_TyhFqDj_6w_Bb0vFptfGXwfKewPPnhsi7GJ62ihZ32PzxOvEIYcaoXr9xaeudYD3BzWSDmjKGA7PMaEuBhScdUAoibCmsKB-yAGsz2amHnUhcl4B_EBs6wk65Y7ge0ZQJUOGPdUQL49VuALKmr7cMhHKh5KuQmPAi_20K2uZL_EFDaObDWZrclx98s0DmfTRKINtw" }] } openid_connect-1.2.0/spec/mock_response/request_object/0000755000004100000410000000000013704151201023342 5ustar www-datawww-dataopenid_connect-1.2.0/spec/mock_response/request_object/signed.jwt0000644000004100000410000000103513704151201025340 0ustar www-datawww-dataeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJjbGllbnRfaWQiLCJyZXNwb25zZV90eXBlIjoidG9rZW4gaWRfdG9rZW4iLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLmNvbSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIiwic3RhdGUiOiJzdGF0ZTEyMzQiLCJub25jZSI6Im5vbmNlMTIzNCIsImRpc3BsYXkiOiJ0b3VjaCIsInByb21wdCI6Im5vbmUiLCJpZF90b2tlbiI6eyJjbGFpbXMiOnsiYWNyIjp7InZhbHVlcyI6WyIyIiwiMyIsIjQiXX19LCJtYXhfYWdlIjoxMH0sInVzZXJpbmZvIjp7ImNsYWltcyI6eyJuYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6ZmFsc2V9fX19.MLTDQVPdhAdkJhboM06IRtjHJrvamJ_H2vFGRupXmTAopenid_connect-1.2.0/spec/spec_helper.rb0000644000004100000410000000046013704151201020273 0ustar www-datawww-datarequire 'simplecov' SimpleCov.start do add_filter 'spec' end require 'rspec' require 'rspec/its' require 'openid_connect' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end require 'helpers/crypto_spec_helper' require 'helpers/webmock_helper'openid_connect-1.2.0/spec/openid_connect/0000755000004100000410000000000013704151201020444 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/client/0000755000004100000410000000000013704151201021722 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/client/registrar_spec.rb0000644000004100000410000001511213704151201025263 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Client::Registrar do subject { instance } let(:attributes) { minimum_attributes } let(:minimum_attributes) do { redirect_uris: ['https://client.example.com/callback'] } end let(:instance) { OpenIDConnect::Client::Registrar.new(endpoint, attributes) } let(:endpoint) { 'https://server.example.com/clients' } context 'when endpoint given' do context 'when required attributes given' do let(:attributes) do minimum_attributes end it { should be_valid } end context 'otherwise' do let(:instance) { OpenIDConnect::Client::Registrar.new(endpoint) } it { should_not be_valid } end end context 'otherwise' do let(:endpoint) { '' } it { should_not be_valid } end describe '#initialize' do it 'creates attribute writers for all attributes' do described_class.metadata_attributes.each do |attr| expect(subject).to respond_to("#{attr}=") end end end describe '#sector_identifier' do context 'when sector_identifier_uri given' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'https://client2.example.com/sector_identifier.json' ) end its(:sector_identifier) { should == 'client2.example.com' } context 'when sector_identifier_uri is invalid URI' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'invalid' ) end it { should_not be_valid } end end context 'otherwise' do let(:attributes) do minimum_attributes.merge( redirect_uris: redirect_uris ) end context 'when redirect_uris includes only one host' do let(:redirect_uris) do [ 'https://client.example.com/callback/op1', 'https://client.example.com/callback/op2' ] end its(:sector_identifier) { should == 'client.example.com' } end context 'when redirect_uris includes multiple hosts' do let(:redirect_uris) do [ 'https://client1.example.com/callback', 'https://client2.example.com/callback' ] end its(:sector_identifier) { should be_nil } context 'when subject_type=pairwise' do let(:attributes) do minimum_attributes.merge( redirect_uris: redirect_uris, subject_type: :pairwise ) end it { should_not be_valid } end end context 'when redirect_uris includes invalid URL' do let(:redirect_uris) do [ 'invalid' ] end its(:sector_identifier) { should be_nil } end end end describe '#redirect_uris' do let(:base_url) { 'http://client.example.com/callback' } let(:attributes) { minimum_attributes.merge(redirect_uris: [redirect_uri]) } context 'when query included' do let(:redirect_uri) { [base_url, '?foo=bar'].join } it { should be_valid } its(:redirect_uris) { should == [redirect_uri] } end context 'when fragment included' do let(:redirect_uri) { [base_url, '#foo=bar'].join } it { should be_valid } end end describe '#contacts' do context 'when contacts given' do let(:attributes) do minimum_attributes.merge( contacts: contacts ) end context 'when invalid email included' do let(:contacts) do [ 'invalid', 'nov@matake.jp' ] end it { should_not be_valid } end context 'when localhost address included' do let(:contacts) do [ 'nov@localhost', 'nov@matake.jp' ] end it { should_not be_valid } end context 'otherwise' do let(:contacts) do ['nov@matake.jp'] end it { should be_valid } end end end describe '#as_json' do context 'when valid' do its(:as_json) do should == minimum_attributes end end context 'otherwise' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'invalid' ) end it do expect do instance.as_json end.to raise_error OpenIDConnect::ValidationFailed end end end describe '#register!' do it 'should return OpenIDConnect::Client' do client = mock_json :post, endpoint, 'client/registered', params: minimum_attributes do instance.register! end client.should be_instance_of OpenIDConnect::Client client.identifier.should == 'client.example.com' client.secret.should == 'client_secret' client.expires_in.should == 3600 end context 'when failed' do it 'should raise OpenIDConnect::Client::Registrar::RegistrationFailed' do mock_json :post, endpoint, 'errors/unknown', params: minimum_attributes, status: 400 do expect do instance.register! end.to raise_error OpenIDConnect::Client::Registrar::RegistrationFailed end end end end describe '#validate!' do context 'when valid' do it do expect do instance.validate! end.not_to raise_error { |e| e.should be_a OpenIDConnect::ValidationFailed } end end context 'otherwise' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'invalid' ) end it do expect do instance.validate! end.to raise_error OpenIDConnect::ValidationFailed end end end describe 'http_client' do subject { instance.send(:http_client) } context 'when initial_access_token given' do let(:attributes) do minimum_attributes.merge( initial_access_token: initial_access_token ) end context 'when Rack::OAuth2::AccessToken::Bearer given' do let(:initial_access_token) do Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') end it { should be_instance_of Rack::OAuth2::AccessToken::Bearer } its(:access_token) { should == 'access_token' } end context 'otherwise' do let(:initial_access_token) { 'access_token' } it { should be_instance_of Rack::OAuth2::AccessToken::Bearer } its(:access_token) { should == 'access_token' } end end context 'otherwise' do it { should be_instance_of HTTPClient } end end end openid_connect-1.2.0/spec/openid_connect/debugger/0000755000004100000410000000000013704151201022230 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/debugger/request_filter_spec.rb0000644000004100000410000000206613704151201026630 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::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) { OpenIDConnect::Debugger::RequestFilter.new } describe '#filter_request' do it 'should log request' do [ "======= [OpenIDConnect] HTTP REQUEST STARTED =======", request.dump ].each do |output| expect(OpenIDConnect.logger).to receive(:info).with output end request_filter.filter_request(request) end end describe '#filter_response' do it 'should log response' do [ "--------------------------------------------------", response.dump, "======= [OpenIDConnect] HTTP REQUEST FINISHED =======" ].each do |output| expect(OpenIDConnect.logger).to receive(:info).with output end request_filter.filter_response(request, response) end end endopenid_connect-1.2.0/spec/openid_connect/exception_spec.rb0000644000004100000410000000122013704151201023774 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::HttpError do subject do OpenIDConnect::HttpError.new 400, 'Bad Request' end its(:status) { should == 400 } its(:message) { should == 'Bad Request' } its(:response) { should be_nil } end describe OpenIDConnect::BadRequest do its(:status) { should == 400 } its(:message) { should == 'OpenIDConnect::BadRequest' } end describe OpenIDConnect::Unauthorized do its(:status) { should == 401 } its(:message) { should == 'OpenIDConnect::Unauthorized' } end describe OpenIDConnect::Forbidden do its(:status) { should == 403 } its(:message) { should == 'OpenIDConnect::Forbidden' } endopenid_connect-1.2.0/spec/openid_connect/discovery/0000755000004100000410000000000013704151201022453 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/discovery/provider/0000755000004100000410000000000013704151201024305 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/discovery/provider/config_spec.rb0000644000004100000410000000761713704151201027124 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider::Config do let(:provider) { 'https://connect-op.heroku.com' } let(:endpoint) { 'https://connect-op.heroku.com/.well-known/openid-configuration' } describe 'discover!' do it 'should setup given attributes' do mock_json :get, endpoint, 'discovery/config' do config = OpenIDConnect::Discovery::Provider::Config.discover! provider config.should be_instance_of OpenIDConnect::Discovery::Provider::Config::Response config.issuer.should == 'https://connect-op.heroku.com' config.authorization_endpoint.should == 'https://connect-op.heroku.com/authorizations/new' config.token_endpoint.should == 'https://connect-op.heroku.com/access_tokens' config.userinfo_endpoint.should == 'https://connect-op.heroku.com/userinfo' config.jwks_uri.should == 'https://connect-op.heroku.com/jwks.json' config.registration_endpoint.should == 'https://connect-op.heroku.com/connect/client' config.scopes_supported.should == ['openid', 'profile', 'email', 'address'] config.response_types_supported.should == ['code', 'token', 'id_token', 'code token', 'code id_token', 'id_token token'] config.acr_values_supported.should be_nil config.subject_types_supported.should == ['public', 'pairwise'] config.claims_supported.should == ['sub', 'iss', 'name', 'email'] config.id_token_signing_alg_values_supported.should == ['RS256'] end end context 'when OP identifier includes custom port' do let(:provider) { 'https://connect-op.heroku.com:8080' } let(:endpoint) { 'https://connect-op.heroku.com:8080/.well-known/openid-configuration' } it 'should construct well-known URI with given port' do mock_json :get, endpoint, 'discovery/config_with_custom_port' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end end context 'when OP identifier includes path' do let(:provider) { 'https://connect.openid4.us/abop' } let(:endpoint) { 'https://connect.openid4.us/abop/.well-known/openid-configuration' } it 'should construct well-known URI with given port' do mock_json :get, endpoint, 'discovery/config_with_path' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end end context 'when SWD::Exception raised' do it do expect do mock_json :get, endpoint, 'errors/unknown', status: [404, 'Not Found'] do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end describe 'when response include invalid issuer' do context 'with normal configuration' do it do expect do mock_json :get, endpoint, 'discovery/config_with_invalid_issuer' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end context 'when issuer validation is disabled.' do before :each do OpenIDConnect.validate_discovery_issuer = false end after :each do OpenIDConnect.validate_discovery_issuer = true end it do expect do mock_json :get, endpoint, 'discovery/config_with_invalid_issuer' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.not_to raise_error end end end context 'when response include no issuer' do it do expect do mock_json :get, endpoint, 'discovery/config_without_issuer' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end end end openid_connect-1.2.0/spec/openid_connect/discovery/provider/config/0000755000004100000410000000000013704151201025552 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/discovery/provider/config/resource_spec.rb0000644000004100000410000000071213704151201030740 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider::Config::Resource do let(:resource) do uri = URI.parse 'http://server.example.com' OpenIDConnect::Discovery::Provider::Config::Resource.new uri end describe '#endpoint' do context 'when invalid host' do before do resource.host = 'hoge*hoge' end it do expect { resource.endpoint }.to raise_error SWD::Exception end end end endopenid_connect-1.2.0/spec/openid_connect/discovery/provider/config/response_spec.rb0000644000004100000410000000506213704151201030752 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider::Config::Response do let :instance do OpenIDConnect::Discovery::Provider::Config::Response.new attributes end let :jwks_uri do 'https://server.example.com/jwks.json' end let :minimum_attributes do { issuer: 'https://server.example.com', authorization_endpoint: 'https://server.example.com/authorize', jwks_uri: jwks_uri, response_types_supported: [ :code, :id_token, 'token id_token' ], subject_types_supported: [ :public, :pairwise ], id_token_signing_alg_values_supported: [ :RS256 ] } end let :attributes do minimum_attributes end subject { instance } context 'when required attributes missing' do let :attributes do {} end it { should_not be_valid } end context 'when end_session_endpoint given' do let(:end_session_endpoint) { 'https://server.example.com/end_session' } let :attributes do minimum_attributes.merge( end_session_endpoint: end_session_endpoint ) end it { should be_valid } its(:end_session_endpoint) { should == end_session_endpoint } end context 'when check_session_iframe given' do let(:check_session_iframe) { 'https://server.example.com/check_session_iframe.html' } let :attributes do minimum_attributes.merge( check_session_iframe: check_session_iframe ) end it { should be_valid } its(:check_session_iframe) { should == check_session_iframe } end describe '#as_json' do subject { instance.as_json } it { should == minimum_attributes } end describe '#validate!' do context 'when required attributes missing' do let :attributes do {} end it do expect do instance.validate! end.to raise_error OpenIDConnect::ValidationFailed end end context 'otherwise' do it do expect do instance.validate! end.not_to raise_error{ |e| e.should be_a OpenIDConnect::ValidationFailed } end end end describe '#jwks' do it do jwks = mock_json :get, jwks_uri, 'public_keys/jwks' do instance.jwks end jwks.should be_instance_of JSON::JWK::Set end end describe '#public_keys' do it do public_keys = mock_json :get, jwks_uri, 'public_keys/jwks' do instance.public_keys end public_keys.should be_instance_of Array public_keys.first.should be_instance_of OpenSSL::PKey::RSA end end end openid_connect-1.2.0/spec/openid_connect/discovery/provider_spec.rb0000644000004100000410000000375513704151201025656 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider do let(:provider) { 'https://server.example.com' } let(:discover) { OpenIDConnect::Discovery::Provider.discover! identifier } let(:endpoint) { "https://#{host}/.well-known/webfinger" } let(:query) do { rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, resource: resource } end shared_examples_for :discover_provider do it "should succeed" do mock_json :get, endpoint, 'discovery/webfinger', params: query do res = discover res.should be_a WebFinger::Response res.issuer.should == provider end end end describe '#discover!' do let(:host) { 'server.example.com' } context 'when URI is given' do let(:resource) { identifier } context 'when scheme included' do context 'when HTTPS' do let(:identifier) { "https://#{host}" } it_behaves_like :discover_provider end context 'otherwise' do let(:identifier) { "http://#{host}" } it_behaves_like :discover_provider it 'should access to https://**' do endpoint.should match /^https:\/\// end end end context 'when only host is given' do let(:identifier) { host } let(:resource) { "https://#{host}" } it_behaves_like :discover_provider end end context 'when Email is given' do let(:identifier) { "nov@#{host}" } let(:resource) { "acct:#{identifier}" } it_behaves_like :discover_provider end context 'when error occured' do let(:identifier) { host } let(:resource) { "https://#{host}" } it 'should raise OpenIDConnect::Discovery::DiscoveryFailed' do mock_json :get, endpoint, 'discovery/webfinger', params: query, status: [404, 'Not Found'] do expect do discover end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end end end endopenid_connect-1.2.0/spec/openid_connect/connect_object_spec.rb0000644000004100000410000000507713704151201024773 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ConnectObject do class OpenIDConnect::ConnectObject::SubClass < OpenIDConnect::ConnectObject attr_required :required attr_optional :optional validates :required, inclusion: {in: ['Required', 'required']}, length: 1..10 end subject { instance } let(:klass) { OpenIDConnect::ConnectObject::SubClass } let(:instance) { klass.new attributes } let :attributes do {required: 'Required', optional: 'Optional'} end context 'when required attributes are given' do context 'when optional attributes are given' do its(:required) { should == 'Required' } its(:optional) { should == 'Optional' } end context 'otherwise' do let :attributes do {required: 'Required'} end its(:required) { should == 'Required' } its(:optional) { should == nil } end end context 'otherwise' do context 'when optional attributes are given' do let :attributes do {optional: 'Optional'} end it do expect { klass.new attributes }.to raise_error AttrRequired::AttrMissing end end context 'otherwise' do it do expect { klass.new }.to raise_error AttrRequired::AttrMissing end end end describe '#as_json' do context 'when valid' do its(:as_json) do should == attributes end end context 'otherwise' do let :attributes do {required: 'Out of List and Too Long'} end it 'should raise OpenIDConnect::ValidationFailed with ActiveModel::Errors owner' do expect { instance.as_json }.to raise_error(OpenIDConnect::ValidationFailed) { |e| e.message.should include 'Required is not included in the list' e.message.should include 'Required is too long (maximum is 10 characters)' e.object.errors.should be_a ActiveModel::Errors } end end end describe '#validate!' do context 'when valid' do subject { instance.validate! } it { should == true } end context 'otherwise' do let :attributes do {required: 'Out of List and Too Long'} end it 'should raise OpenIDConnect::ValidationFailed with ActiveModel::Errors owner' do expect { instance.validate! }.to raise_error(OpenIDConnect::ValidationFailed) { |e| e.message.should include 'Required is not included in the list' e.message.should include 'Required is too long (maximum is 10 characters)' e.object.errors.should be_a ActiveModel::Errors } end end end end openid_connect-1.2.0/spec/openid_connect/access_token_spec.rb0000644000004100000410000000602413704151201024446 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::AccessToken do subject { access_token } let :client do OpenIDConnect::Client.new( identifier:'client_id', host: 'server.example.com' ) end let :access_token do OpenIDConnect::AccessToken.new( access_token: 'access_token', client: client ) end its(:token_type) { should == :bearer } its(:optional_attributes) { should include :id_token } context 'when id_token is given' do subject { access_token } let :access_token do OpenIDConnect::AccessToken.new( access_token: 'access_token', id_token: id_token, client: client ) end context 'when IdToken object' do let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1.week.from_now, iat: Time.now ) end its(:id_token) { should be_a OpenIDConnect::ResponseObject::IdToken } its(:token_response) { should_not include :id_token } end context 'when JWT string' do let(:id_token) { 'id_token' } its(:id_token) { should == 'id_token' } its(:token_response) { should_not include :id_token } end end shared_examples_for :access_token_error_handling do context 'when bad_request' do it 'should raise OpenIDConnect::Forbidden' do mock_json :get, endpoint, 'errors/invalid_request', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 400 do expect { request }.to raise_error OpenIDConnect::BadRequest end end end context 'when unauthorized' do it 'should raise OpenIDConnect::Unauthorized' do mock_json :get, endpoint, 'errors/invalid_access_token', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 401 do expect { request }.to raise_error OpenIDConnect::Unauthorized end end end context 'when forbidden' do it 'should raise OpenIDConnect::Forbidden' do mock_json :get, endpoint, 'errors/insufficient_scope', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 403 do expect { request }.to raise_error OpenIDConnect::Forbidden end end end context 'when unknown' do it 'should raise OpenIDConnect::HttpError' do mock_json :get, endpoint, 'errors/unknown', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 500 do expect { request }.to raise_error OpenIDConnect::HttpError end end end end describe '#userinfo!' do it do userinfo = mock_json :get, client.userinfo_uri, 'userinfo/openid', :HTTP_AUTHORIZATION => 'Bearer access_token' do access_token.userinfo! end userinfo.should be_instance_of OpenIDConnect::ResponseObject::UserInfo end describe 'error handling' do let(:endpoint) { client.userinfo_uri } let(:request) { access_token.userinfo! } it_behaves_like :access_token_error_handling end end endopenid_connect-1.2.0/spec/openid_connect/client_spec.rb0000644000004100000410000001235413704151201023266 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Client do subject { client } let(:client) { OpenIDConnect::Client.new attributes } let(:attributes) { required_attributes } let :required_attributes do { identifier: 'client_id' } end describe 'endpoints' do context 'when host info is given' do let :attributes do required_attributes.merge( host: 'server.example.com' ) end its(:authorization_uri) { should include 'https://server.example.com/oauth2/authorize' } its(:authorization_uri) { should include 'scope=openid' } its(:userinfo_uri) { should == 'https://server.example.com/userinfo' } end context 'otherwise' do [:authorization_uri, :userinfo_uri].each do |endpoint| describe endpoint do it do expect { client.send endpoint }.to raise_error 'No Host Info' end end end end end describe '#authorization_uri' do let(:scope) { nil } let(:prompt) { nil } let(:response_type) { nil } let(:query) do params = { scope: scope, prompt: prompt, response_type: response_type }.reject do |k,v| v.blank? end query = URI.parse(client.authorization_uri params).query Rack::Utils.parse_query(query).with_indifferent_access end let :attributes do required_attributes.merge( host: 'server.example.com' ) end describe 'response_type' do subject do query[:response_type] end it { should == 'code' } context 'when response_type is given' do context 'when array given' do let(:response_type) { [:code, :token] } it { should == 'code token' } end context 'when scalar given' do let(:response_type) { :token } it { should == 'token' } end end context 'as default' do it { should == 'code' } end end describe 'scope' do subject do query[:scope] end context 'when scope is given' do context 'when openid scope is included' do let(:scope) { [:openid, :email] } it { should == 'openid email' } end context 'otherwise' do let(:scope) { :email } it { should == 'email openid' } end end context 'as default' do it { should == 'openid' } end end describe 'prompt' do subject do query[:prompt] end context 'when prompt is a scalar value' do let(:prompt) { :login } it { should == 'login' } end context 'when prompt is a space-delimited string' do let(:prompt) { 'login consent' } it { should == 'login consent' } end context 'when prompt is an array' do let(:prompt) { [:login, :consent] } it { should == 'login consent' } end end end describe '#access_token!' do let :attributes do required_attributes.merge( secret: 'client_secret', token_endpoint: 'http://server.example.com/access_tokens' ) end let :protocol_params do { grant_type: 'authorization_code', code: 'code' } end let :header_params do { 'Authorization' => 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=', 'Content-Type' => 'application/x-www-form-urlencoded' } end let :access_token do client.authorization_code = 'code' client.access_token! end context 'when bearer token is returned' do it 'should return OpenIDConnect::AccessToken' do mock_json :post, client.token_endpoint, 'access_token/bearer', request_header: header_params, params: protocol_params do access_token.should be_a OpenIDConnect::AccessToken end end context 'when id_token is returned' do it 'should include id_token' do mock_json :post, client.token_endpoint, 'access_token/bearer_with_id_token', request_header: header_params, params: protocol_params do access_token.id_token.should == 'id_token' end end end end context 'when invalid JSON is returned' do it 'should raise OpenIDConnect::Exception' do mock_json :post, client.token_endpoint, 'access_token/invalid_json', request_header: header_params, params: protocol_params do expect do access_token end.to raise_error OpenIDConnect::Exception, 'Unknown Token Type' end end end context 'otherwise' do it 'should raise Unexpected Token Type exception' do mock_json :post, client.token_endpoint, 'access_token/mac', request_header: header_params, params: protocol_params do expect { access_token }.to raise_error OpenIDConnect::Exception, 'Unexpected Token Type: mac' end end context 'when token_type is forced' do before { client.force_token_type! :bearer } it 'should use forced token_type' do mock_json :post, client.token_endpoint, 'access_token/without_token_type', request_header: header_params, params: protocol_params do access_token.should be_a OpenIDConnect::AccessToken end end end end end end openid_connect-1.2.0/spec/openid_connect/response_object/0000755000004100000410000000000013704151201023630 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/response_object/user_info_spec.rb0000644000004100000410000000547513704151201027173 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ResponseObject::UserInfo do let(:klass) { OpenIDConnect::ResponseObject::UserInfo } let(:instance) { klass.new attributes } subject { instance } describe 'attributes' do subject { klass } its(:required_attributes) { should == [] } its(:optional_attributes) do should == [ :sub, :name, :given_name, :family_name, :middle_name, :nickname, :preferred_username, :profile, :picture, :website, :email, :email_verified, :gender, :birthdate, :zoneinfo, :locale, :phone_number, :phone_number_verified, :address, :updated_at ] end end describe 'validations' do subject do _instance_ = instance _instance_.valid? _instance_ end context 'when all attributes are blank' do let :attributes do {} end its(:valid?) { should == false } its(:errors) { should include :base } end context 'when email is invalid' do let :attributes do {email: 'nov@localhost'} end its(:valid?) { should == false } its(:errors) { should include :email } end [:email_verified, :zoneinfo].each do |one_of_list| context "when #{one_of_list} is invalid" do let :attributes do {one_of_list => 'Out of List'} end its(:valid?) { should == false } its(:errors) { should include one_of_list } end end context "when locale is invalid" do it :TODO end [:profile, :picture, :website].each do |url| context "when #{url} is invalid" do let :attributes do {url => 'Invalid'} end its(:valid?) { should == false } its(:errors) { should include url } end end context 'when address is blank' do let :attributes do {address: {}} end its(:valid?) { should == false } its(:errors) { should include :address } end end describe '#address=' do context 'when Hash is given' do let :attributes do {address: {}} end its(:address) { should be_a OpenIDConnect::ResponseObject::UserInfo::Address } end context 'when Address is given' do let :attributes do {address: OpenIDConnect::ResponseObject::UserInfo::Address.new} end its(:address) { should be_a OpenIDConnect::ResponseObject::UserInfo::Address } end end describe '#to_json' do let :attributes do { sub: 'nov.matake#12345', address: { formatted: 'Tokyo, Japan' } } end its(:to_json) { should include '"sub":"nov.matake#12345"'} its(:to_json) { should include '"address":{"formatted":"Tokyo, Japan"}'} end endopenid_connect-1.2.0/spec/openid_connect/response_object/user_info/0000755000004100000410000000000013704151201025621 5ustar www-datawww-dataopenid_connect-1.2.0/spec/openid_connect/response_object/user_info/address_spec.rb0000644000004100000410000000126513704151201030611 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ResponseObject::UserInfo::Address do let(:klass) { OpenIDConnect::ResponseObject::UserInfo::Address } describe 'attributes' do subject { klass } its(:required_attributes) { should == [] } its(:optional_attributes) { should == [:formatted, :street_address, :locality, :region, :postal_code, :country] } end describe 'validations' do subject do instance = klass.new attributes instance.valid? instance end context 'when all attributes are blank' do let :attributes do {} end its(:valid?) { should == false } its(:errors) { should include :base } end end endopenid_connect-1.2.0/spec/openid_connect/response_object/id_token_spec.rb0000644000004100000410000002730213704151201026767 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ResponseObject::IdToken do let(:klass) { OpenIDConnect::ResponseObject::IdToken } let(:id_token) { klass.new attributes } let(:attributes) { required_attributes } let(:ext) { 10.minutes.from_now } let(:iat) { Time.now } let :required_attributes do { iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', exp: ext, iat: iat } end describe 'attributes' do subject { klass } its(:required_attributes) { should == [:iss, :sub, :aud, :exp, :iat] } its(:optional_attributes) { should == [:acr, :amr, :azp, :jti, :sid, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash, :s_hash] } describe 'auth_time' do subject { id_token.auth_time } context 'when Time object given' do let(:attributes) do required_attributes.merge(auth_time: Time.now) end it do should be_a Numeric end end end end describe '#verify!' do context 'when both issuer, client_id are valid' do it do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud] ).should == true end context 'when aud(ience) is an array of identifiers' do let(:client_id) { 'client_id' } let(:attributes) { required_attributes.merge(aud: ['some_other_identifier', client_id]) } it do id_token.verify!( issuer: attributes[:iss], client_id: client_id ).should == true end end context 'when expired' do let(:ext) { 10.minutes.ago } it do expect do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end end context 'when issuer is invalid' do it do expect do id_token.verify!( issuer: 'invalid_issuer', client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when issuer is missing' do it do expect do id_token.verify!( client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when client_id is invalid' do it do expect do id_token.verify!( issuer: attributes[:iss], client_id: 'invalid_client' ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when client_id is missing' do it do expect do id_token.verify!( issuer: attributes[:iss] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when nonce is given' do let(:attributes) { required_attributes.merge(nonce: 'nonce') } context 'when nonce is valid' do it do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud], nonce: attributes[:nonce] ).should == true end end context 'when nonce is invalid' do it do expect do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud], nonce: 'invalid_nonce' ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when nonce is missing' do it do expect do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end end end describe '#to_jwt' do subject { id_token.to_jwt private_key } it { should be_a String } context 'when block given' do it 'should allow add additional headers' do t = id_token.to_jwt private_key do |t| t.header[:x5u] = "http://server.example.com/x5u" end h = Base64.urlsafe_decode64 t.split('.').first h.should include 'x5u' end end context 'when access_token is given' do shared_examples_for :id_token_with_at_hash do it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should include :at_hash jwt.should_not include :c_hash jwt[:at_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('access_token')[0, 128 / 8], padding: false ) end end context 'when access_token is a Rack::OAuth2::AccessToken' do before { id_token.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') } it_should_behave_like :id_token_with_at_hash end context 'when access_token is a String' do before { id_token.access_token = 'access_token' } it_should_behave_like :id_token_with_at_hash end end context 'when code is given' do before { id_token.code = 'authorization_code' } it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should_not include :at_hash jwt.should include :c_hash jwt[:c_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('authorization_code')[0, 128 / 8], padding: false ) end end context 'when both access_token and code are given' do before do id_token.access_token = 'access_token' id_token.code = 'authorization_code' end it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should include :at_hash jwt.should include :c_hash jwt[:at_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('access_token')[0, 128 / 8], padding: false ) jwt[:c_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('authorization_code')[0, 128 / 8], padding: false ) end end context 'when neither access_token nor code are given' do it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should_not include :at_hash, :c_hash end end end describe '#as_json' do subject { id_token.as_json } let(:attributes) { required_attributes } it do hash = required_attributes hash[:exp] = required_attributes[:exp].to_i should == hash end end describe '.decode' do subject { klass.decode id_token.to_jwt(private_key), public_key } let(:attributes) { required_attributes } it { should be_a klass } [:iss, :sub, :aud].each do |key| its(key) { should == attributes[key] } end its(:exp) { should == attributes[:exp].to_i } its(:raw_attributes) { should be_instance_of JSON::JWS } context 'when self-issued' do context 'when valid' do let(:self_issued) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lIiwic3ViIjoiMmdDUWFLUmJkY0RaeUlDTE92ODJJR2EtdHBSVU52QW1ZN3BnZ3Z5NGdENCIsImF1ZCI6ImNsaWVudC5leGFtcGxlLmNvbSIsImV4cCI6MTQ0MjQ4Mjc4MiwiaWF0IjoxNDQxODc3OTgyLCJzdWJfandrIjp7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwibiI6IjN1RzNiSTV6MTFhM1hlOXUyZFVJNDBpcWZrVl9vTmFQVmNlalN4V3l0YnMybTZKMGMzNjJESlJQNGtyUl9TZjNtQXJ3Qjd6Qm5UWExkbW1tZW85VzloSDhsSnFGOUthMTY3dHBTQWJCajB1MjhyaTgwZFZ4NUxzblJTX19uUUd6Y3dNa2sxTTBERUx2X0FXbVYwU2JudDhJZEpSeFhwdG5xRE5tWXJ0cmItMkk0a1lwRHlwN2pvTXd0bDNXeGp2cnkwbENLNExqOU9SeXdod05zYUU2MHFsako5aHBGZV8wTmpmaThzaVBlMDRJSkFaUjl3NXo0TnAtQS1HbWdmeTNJTmNZVFYyQ25FekNSY29HSGl5OGduRzA1a015TnRtZTFVdV8xanBhdF9lcF9QUG9PWEJ6Q1NwbzB5QlRNSWhmdEJTQ3p2a2V1ZFdhNks2aW5LMkYxdyJ9fQ.wchF80oFxdjEcOEwPZ9TUlV6R96Vz8XK9MzednMOsZmEMnNSEqKKTyO0Mhp9lijJPZX8J7lTtAGkz4gfsjyoYBIHQOTf0qHRHSx9RTeC31whw1TJ9x5V6UXpKN0EW1EhjAEGIZ0EyFJ-cRTgVs0V7PT7e63JOUYyW6LqqHa4MV9SdK8BdnaN0D4-402Pf7yFqjneSHq3KZbXcgjUPT_hszsGvnn9qEyuIHQqON6YnDt55z5SvP_RfKtBfUe2VY-yglJT41LfhkIgpvjLYdYYRPh9G9ftJr17qht5RtHSNpTp4FPw7BR7rCnptb4xTxyq-sLu7qjSLRtqQ35Xpi_6qQ' end context 'when key == :self_issued' do it do expect do klass.decode self_issued, :self_issued end.not_to raise_error end end context 'when key == public_key' do it do expect do klass.decode self_issued, public_key end.to raise_error JSON::JWS::VerificationFailed end end end context 'when invalid subject' do let(:self_issued) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lIiwic3ViIjoiUFdFYXFfVnlUd1hTSFR4QVlSZHdWTjNMN2s0UnNxOVBwaTZ4WHZ6ZGZWTSIsImF1ZCI6InRhcGlkLnRhcGlkZW50aXR5LmNvbSIsImV4cCI6MTM2MjI3OTkwMCwiaWF0IjoxMzYyMjc2MzAwLCJzdWJfandrIjp7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwibiI6IjRGTWl5M08zbFlOd2RzeC15aXVjemRsek81eU11d1p4WFlzSDgydmM0RkM0QXgyMGpNVV94emJHSUhWVUtFQ0pndFp3clBlajhRSWUtZFZFYXQtaGxjNTB5TXluM0h3cmtJVjVZOTdET1E2Sks4azk2QTFqVWxPLW5sRjl4ZUx2VDlwYTJXRTZtYm1KOG5EQW5mR0d6bmRNd3VKNzVLZDI2YmZHY21wcm5qUUJLTkVrakdJbW9MMEhFODFUcjROeC1tN1lsYkRGaVFNRDVpYjhCY3N4S0tvMTZTeG5tSi1EeUY2c094Y2JtV1ZrdkZBa3FKWFBnVFVoNXVYT3YwYk9nN0I2d2RHdUMtWnpJUl8tdUx3YlcxN2V4NGx3ZTFPb0ppdFJ3SFczYlo3NEc3RkdoSmhfTUp4YzB3WXBkbW5uNVpjRFFOWl9sWVRvMHYzaU1PUWk3USJ9fQ.DZKaSne22DjKFSpSUphsTeCMkcMWDexQCm8BPb1nI1PzQYsEAOfwumDajt85UA0x28y2zuOevMj29VpwTzbpRDkduv2NWAI4MHw8DYEsIN__-QGANmdU1sKmthET2iFmeFySwWomLqFvYIaNmVYVLkD53Zqfct5qH3Wznd_hrK8T1d6Cxg-gyZlAeqEu2V8EL2yuz8Gdaeze4b78l5Ux-B_5FQhZ3UkXbL1B2gzKJQVKAQdFJb9zUfzmCeIiUmeM9mw_VU64tAvFDRiTKS1P6b62Gxuyx1DhMLFg2evDaTJERJOta9ywtPfdcLH3qcIiUBffP2-FnAz44bOlKzJorQ' end it do expect do klass.decode self_issued, :self_issued end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken, 'Invalid subject' end end context 'when no sub_jwk' do let(:self_issued) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkZXZpY2VfdG9rZW4iOiI2NjYxNmI2NTJkNjQ2NTc2Njk2MzY1MmQ3NDZmNmI2NTZlIiwiaXNzIjoiaHR0cHM6Ly9zZWxmLWlzc3VlZC5tZSIsInN1YiI6IlBXRWFxX1Z5VHdYU0hUeEFZUmR3Vk4zTDdrNFJzcTlQcGk2eFh2emRmVk0iLCJhdWQiOiJ0YXBpZC50YXBpZGVudGl0eS5jb20iLCJleHAiOjEzNjIyODAxNDQsImlhdCI6MTM2MjI3NjU0NH0.HtkguN4xOzJ-yh_kd2JCmG6fgDEiVY5VCgTWUD9l8YOgHjTT7LRZC3b1sNDgkdwBteX3eQIQOVxaYWp4-ftczaIlrznB0jxldqdEdB1Tr591YsiDcyOqmemo1ZYzOKhe_q1l68bdKKeHLc83BzlsJpS659uFDuixvF7G_HIJpCdwckX7x6H3KK73hCLzoYCOVgr_lkFRVVHHAJXzxiUuERLD7JIvg5jCbgmqxArP-jYBdbscHHx8i-UP3WYFBEORBM2rXJuJzGvk4sLhZ4NVGBWyr0DJlE-aWKTyeg-_-4kLPd3d68-k3nLJ82iCwcap-BU_5otSmXufN3_ffq_tTw' end it do expect do klass.decode self_issued, :self_issued end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken, 'Missing sub_jwk' end end end end describe '.self_issued' do subject { self_issued } let(:sub_jwk) { JSON::JWK.new(public_key) } let(:self_issued) do klass.self_issued( public_key: public_key, aud: 'client.example.com', exp: 1.week.from_now, iat: Time.now ) end [:iss, :sub, :aud, :exp, :iat, :sub_jwk].each do |attribute| its(attribute) { should be_present } end its(:iss) { should == 'https://self-issued.me' } its(:sub_jwk) { should == sub_jwk} its(:subject) { should == sub_jwk.thumbprint } end end openid_connect-1.2.0/spec/openid_connect/request_object_spec.rb0000644000004100000410000000652713704151201025033 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::RequestObject do subject { request_object } let(:request_object) { OpenIDConnect::RequestObject.new attributes } context 'with all attributes' do let(:attributes) do { client_id: 'client_id', response_type: 'token id_token', redirect_uri: 'https://client.example.com', scope: 'openid email', state: 'state1234', nonce: 'nonce1234', display: 'touch', prompt: 'none', id_token: { max_age: 10, claims: { acr: { values: ['2', '3', '4'] } } }, userinfo: { claims: { name: :required, email: :optional } } } end let(:jwtnized) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJjbGllbnRfaWQiLCJyZXNwb25zZV90eXBlIjoidG9rZW4gaWRfdG9rZW4iLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLmNvbSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIiwic3RhdGUiOiJzdGF0ZTEyMzQiLCJub25jZSI6Im5vbmNlMTIzNCIsImRpc3BsYXkiOiJ0b3VjaCIsInByb21wdCI6Im5vbmUiLCJ1c2VyaW5mbyI6eyJjbGFpbXMiOnsibmFtZSI6eyJlc3NlbnRpYWwiOnRydWV9LCJlbWFpbCI6eyJlc3NlbnRpYWwiOmZhbHNlfX19LCJpZF90b2tlbiI6eyJjbGFpbXMiOnsiYWNyIjp7InZhbHVlcyI6WyIyIiwiMyIsIjQiXX19LCJtYXhfYWdlIjoxMH19.yOc76jnkDusf5ZUzI5Gq7vnteTeOVUXd2Fr1EBZFNYU' end let(:jsonized) do { client_id: "client_id", response_type: "token id_token", redirect_uri: "https://client.example.com", scope: "openid email", state: "state1234", nonce: "nonce1234", display: "touch", prompt: "none", id_token: { claims: { acr: { values: ['2', '3', '4'] } }, max_age: 10 }, userinfo: { claims: { name: { essential: true }, email: { essential: false } } } } end it { should be_valid } its(:as_json) do should == jsonized.with_indifferent_access end describe '#to_jwt' do it do request_object.to_jwt('secret', :HS256).should == jwtnized end end describe '.decode' do it do OpenIDConnect::RequestObject.decode(jwtnized, 'secret').as_json.should == jsonized.with_indifferent_access end end describe '.fetch' do let(:endpoint) { 'https://client.example.com/request.jwk' } it do mock_json :get, endpoint, 'request_object/signed', format: :jwt do request_object = OpenIDConnect::RequestObject.fetch endpoint, 'secret' request_object.as_json.should == jsonized.with_indifferent_access end end end describe '#required?' do it do request_object.userinfo.required?(:name).should == true request_object.userinfo.optional?(:name).should == false end end describe '#optional' do it do request_object.userinfo.required?(:email).should == false request_object.userinfo.optional?(:email).should == true end end end context 'with no attributes' do let(:attributes) do {} end it { should_not be_valid } it do expect do request_object.as_json end.to raise_error OpenIDConnect::ValidationFailed end end endopenid_connect-1.2.0/.gitignore0000644000004100000410000000024013704151201016507 0ustar www-datawww-data## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg Gemfile.lock ## PROJECT::SPECIFIC openid_connect-1.2.0/LICENSE0000644000004100000410000000205213704151201015527 0ustar www-datawww-dataCopyright (c) 2011 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.openid_connect-1.2.0/openid_connect.gemspec0000644000004100000410000000232313704151201021057 0ustar www-datawww-dataGem::Specification.new do |s| s.name = "openid_connect" s.version = File.read("VERSION") s.authors = ["nov matake"] s.email = ["nov@matake.jp"] s.homepage = "https://github.com/nov/openid_connect" s.summary = %q{OpenID Connect Server & Client Library} s.description = %q{OpenID Connect Server & Client Library} s.license = 'MIT' 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 "tzinfo" s.add_runtime_dependency "attr_required", ">= 1.0.0" s.add_runtime_dependency "activemodel" s.add_runtime_dependency "validate_url" s.add_runtime_dependency "validate_email" s.add_runtime_dependency "json-jwt", ">= 1.5.0" s.add_runtime_dependency "swd", ">= 1.0.0" s.add_runtime_dependency "webfinger", ">= 1.0.1" s.add_runtime_dependency "rack-oauth2", ">= 1.6.1" 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" end openid_connect-1.2.0/Rakefile0000644000004100000410000000062213704151201016170 0ustar www-datawww-datarequire '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 => :specopenid_connect-1.2.0/lib/0000755000004100000410000000000013704151201015271 5ustar www-datawww-dataopenid_connect-1.2.0/lib/rack/0000755000004100000410000000000013704151201016211 5ustar www-datawww-dataopenid_connect-1.2.0/lib/rack/oauth2/0000755000004100000410000000000013704151201017413 5ustar www-datawww-dataopenid_connect-1.2.0/lib/rack/oauth2/server/0000755000004100000410000000000013704151201020721 5ustar www-datawww-dataopenid_connect-1.2.0/lib/rack/oauth2/server/id_token_response.rb0000644000004100000410000000124013704151201024755 0ustar www-datawww-datamodule Rack::OAuth2::Server module IdTokenResponse def self.prepended(klass) klass.send :attr_optional, :id_token end def protocol_params_location :fragment end def protocol_params super.merge( id_token: id_token ) end end Token::Response.send :prepend, IdTokenResponse end require 'rack/oauth2/server/authorize/extension/code_and_id_token' require 'rack/oauth2/server/authorize/extension/code_and_token' require 'rack/oauth2/server/authorize/extension/code_and_id_token_and_token' require 'rack/oauth2/server/authorize/extension/id_token' require 'rack/oauth2/server/authorize/extension/id_token_and_token'openid_connect-1.2.0/lib/rack/oauth2/server/authorize/0000755000004100000410000000000013704151201022733 5ustar www-datawww-dataopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/request_with_connect_params.rb0000644000004100000410000000133213704151201031056 0ustar www-datawww-dataclass Rack::OAuth2::Server::Authorize module RequestWithConnectParams CONNECT_EXT_PARAMS = [ :nonce, :display, :prompt, :max_age, :ui_locales, :claims_locales, :id_token_hint, :login_hint, :acr_values, :claims, :request, :request_uri ] def self.prepended(klass) klass.send :attr_optional, *CONNECT_EXT_PARAMS end def initialize(env) super CONNECT_EXT_PARAMS.each do |attribute| self.send :"#{attribute}=", params[attribute.to_s] end self.prompt = Array(prompt.to_s.split(' ')) self.max_age = max_age.try(:to_i) end def openid_connect_request? scope.include?('openid') end end Request.send :prepend, RequestWithConnectParams endopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/error_with_connect_ext.rb0000644000004100000410000000266413704151201030045 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module ErrorWithConnectExt DEFAULT_DESCRIPTION = { invalid_redirect_uri: 'The redirect_uri in the request does not match any of pre-registered redirect_uris.', interaction_required: 'End-User interaction required.', login_required: 'End-User authentication required.', session_selection_required: 'The End-User is required to select a session at the Authorization Server.', consent_required: 'End-User consent required.', invalid_request_uri: 'The request_uri in the request returns an error or invalid data.', invalid_openid_request_object: 'The request parameter contains an invalid OpenID Request Object.' } def self.included(klass) DEFAULT_DESCRIPTION.each do |error, default_description| # NOTE: # Connect Message spec doesn't say anything about HTTP status code for each error code. # It probably means "use 400". error_method = :bad_request! klass.class_eval <<-ERROR def #{error}!(description = "#{default_description}", options = {}) #{error_method} :#{error}, description, options end ERROR end end end Request.send :include, ErrorWithConnectExt end end end endopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/extension/0000755000004100000410000000000013704151201024747 5ustar www-datawww-dataopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/extension/code_and_id_token_and_token.rb0000644000004100000410000000170413704151201032730 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class CodeAndIdTokenAndToken < Abstract::Handler class << self def response_type_for?(response_type) response_type.split.sort == ['code', 'id_token', 'token'] end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Extension::CodeAndToken::Request def initialize(env) super @response_type = [:code, :id_token, :token] attr_missing! end end class Response < Authorize::Extension::CodeAndToken::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/extension/code_and_id_token.rb0000644000004100000410000000173213704151201030707 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class CodeAndIdToken < Abstract::Handler class << self def response_type_for?(response_type) response_type.split.sort == ['code', 'id_token'] end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Code::Request def initialize(env) super @response_type = [:code, :id_token] attr_missing! end def error_params_location :fragment end end class Response < Authorize::Code::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/extension/id_token_and_token.rb0000644000004100000410000000161213704151201031112 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class IdTokenAndToken < Abstract::Handler class << self def response_type_for?(response_type) response_type.split.sort == ['id_token', 'token'] end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Token::Request def initialize(env) super @response_type = [:id_token, :token] attr_missing! end end class Response < Authorize::Token::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-1.2.0/lib/rack/oauth2/server/authorize/extension/id_token.rb0000644000004100000410000000165113704151201027073 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class IdToken < Abstract::Handler class << self def response_type_for?(response_type) response_type == 'id_token' end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Request def initialize(env) super @response_type = :id_token attr_missing! end def error_params_location :fragment end end class Response < Authorize::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-1.2.0/lib/openid_connect.rb0000644000004100000410000000446513704151201020616 0ustar www-datawww-datarequire 'json' require 'logger' require 'swd' require 'webfinger' require 'active_model' require 'tzinfo' require 'validate_url' require 'validate_email' require 'attr_required' require 'attr_optional' require 'json/jwt' require 'rack/oauth2' require 'rack/oauth2/server/authorize/error_with_connect_ext' require 'rack/oauth2/server/authorize/request_with_connect_params' require 'rack/oauth2/server/id_token_response' module OpenIDConnect VERSION = ::File.read( ::File.join(::File.dirname(__FILE__), '../VERSION') ).chomp def self.logger @@logger end def self.logger=(logger) @@logger = logger end self.logger = Logger.new(STDOUT) self.logger.progname = 'OpenIDConnect' @sub_protocols = [ SWD, WebFinger, Rack::OAuth2 ] def self.debugging? @@debugging end def self.debugging=(boolean) @sub_protocols.each do |klass| klass.debugging = boolean end @@debugging = boolean end def self.debug! @sub_protocols.each do |klass| klass.debug! end self.debugging = true end def self.debug(&block) sub_protocol_originals = @sub_protocols.inject({}) do |sub_protocol_originals, klass| sub_protocol_originals.merge!(klass => klass.debugging?) end original = self.debugging? debug! yield ensure @sub_protocols.each do |klass| klass.debugging = sub_protocol_originals[klass] end self.debugging = original end self.debugging = false def self.http_client _http_client_ = HTTPClient.new( agent_name: "OpenIDConnect (#{VERSION})" ) _http_client_.request_filter << Debugger::RequestFilter.new if debugging? http_config.try(:call, _http_client_) _http_client_ end def self.http_config(&block) @sub_protocols.each do |klass| klass.http_config(&block) unless klass.http_config end @@http_config ||= block end def self.validate_discovery_issuer=(boolean) @@validate_discovery_issuer = boolean end def self.validate_discovery_issuer @@validate_discovery_issuer end self.validate_discovery_issuer = true end require 'openid_connect/exception' require 'openid_connect/client' require 'openid_connect/access_token' require 'openid_connect/jwtnizable' require 'openid_connect/connect_object' require 'openid_connect/discovery' require 'openid_connect/debugger' openid_connect-1.2.0/lib/openid_connect/0000755000004100000410000000000013704151201020260 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/client/0000755000004100000410000000000013704151201021536 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/client/registrar.rb0000644000004100000410000001250213704151201024065 0ustar www-datawww-datamodule OpenIDConnect class Client class Registrar include ActiveModel::Validations, AttrRequired, AttrOptional class RegistrationFailed < HttpError; end cattr_accessor :plural_uri_attributes, :metadata_attributes singular_uri_attributes = [ :logo_uri, :client_uri, :policy_uri, :tos_uri, :jwks_uri, :sector_identifier_uri, :initiate_login_uri ] singular_attributes = [ :application_type, :client_name, :jwks, :subject_type, :id_token_signed_response_alg, :id_token_encrypted_response_alg, :id_token_encrypted_response_enc, :userinfo_signed_response_alg, :userinfo_encrypted_response_alg, :userinfo_encrypted_response_enc, :request_object_signing_alg, :request_object_encryption_alg, :request_object_encryption_enc, :token_endpoint_auth_method, :token_endpoint_auth_signing_alg, :default_max_age, :require_auth_time ] + singular_uri_attributes self.plural_uri_attributes = [ :redirect_uris, :request_uris ] plural_attributes = [ :response_types, :grant_types, :contacts, :default_acr_values, ] + plural_uri_attributes self.metadata_attributes = singular_attributes + plural_attributes required_metadata_attributes = [ :redirect_uris ] attr_required :endpoint attr_optional :initial_access_token attr_required(*required_metadata_attributes) attr_optional(*(metadata_attributes - required_metadata_attributes)) validates(*required_attributes, presence: true) validates :sector_identifier_uri, presence: {if: :sector_identifier_required?} validates(*singular_uri_attributes, url: true, allow_nil: true) validate :validate_plural_uri_attributes validate :validate_contacts def initialize(endpoint, attributes = {}) self.endpoint = endpoint self.initial_access_token = attributes[:initial_access_token] self.class.metadata_attributes.each do |_attr_| self.send "#{_attr_}=", attributes[_attr_] end end def sector_identifier if valid_uri?(sector_identifier_uri) URI.parse(sector_identifier_uri).host else hosts = redirect_uris.collect do |redirect_uri| if valid_uri?(redirect_uri, nil) URI.parse(redirect_uri).host else nil end end.compact.uniq if hosts.size == 1 hosts.first else nil end end end def as_json(options = {}) validate! self.class.metadata_attributes.inject({}) do |hash, _attr_| value = self.send _attr_ hash.merge! _attr_ => value unless value.nil? hash end end def register! handle_response do http_client.post endpoint, to_json, 'Content-Type' => 'application/json' end end def read # TODO: Do we want this feature even if we don't have rotate secret nor update metadata support? end def validate! valid? or raise ValidationFailed.new(self) end private def sector_identifier_required? subject_type.to_s == 'pairwise' && sector_identifier.blank? end def valid_uri?(uri, schemes = ['http', 'https']) # NOTE: specify nil for schemes to allow any schemes URI::regexp(schemes).match(uri).present? end def validate_contacts if contacts include_invalid = contacts.any? do |contact| begin mail = Mail::Address.new(contact) mail.address != contact || mail.domain.split(".").length <= 1 rescue :invalid end end errors.add :contacts, 'includes invalid email' if include_invalid end end def validate_plural_uri_attributes self.class.plural_uri_attributes.each do |_attr_| if (uris = self.send(_attr_)) include_invalid = uris.any? do |uri| !valid_uri?(uri, nil) end errors.add _attr_, 'includes invalid URL' if include_invalid end end end def http_client case initial_access_token when nil OpenIDConnect.http_client when Rack::OAuth2::AccessToken::Bearer initial_access_token else Rack::OAuth2::AccessToken::Bearer.new( access_token: initial_access_token ) end end def handle_response response = yield case response.status when 200..201 handle_success_response response else handle_error_response response end end def handle_success_response(response) credentials = JSON.parse(response.body).with_indifferent_access Client.new( identifier: credentials[:client_id], secret: credentials[:client_secret], expires_in: credentials[:expires_in] ) end def handle_error_response(response) raise RegistrationFailed.new(response.status, 'Client Registration Failed', response) end end end end openid_connect-1.2.0/lib/openid_connect/debugger/0000755000004100000410000000000013704151201022044 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/debugger/request_filter.rb0000644000004100000410000000140713704151201025430 0ustar www-datawww-datamodule OpenIDConnect module Debugger class RequestFilter # Callback called in HTTPClient (before sending a request) # request:: HTTP::Message def filter_request(request) started = "======= [OpenIDConnect] HTTP REQUEST STARTED =======" log started, request.dump end # Callback called in HTTPClient (after received a response) # request:: HTTP::Message # response:: HTTP::Message def filter_response(request, response) finished = "======= [OpenIDConnect] HTTP REQUEST FINISHED =======" log '-' * 50, response.dump, finished end private def log(*outputs) outputs.each do |output| OpenIDConnect.logger.info output end end end end endopenid_connect-1.2.0/lib/openid_connect/connect_object.rb0000644000004100000410000000246313704151201023571 0ustar www-datawww-datamodule OpenIDConnect class ConnectObject include ActiveModel::Validations, AttrRequired, AttrOptional attr_accessor :raw_attributes def initialize(attributes = {}) all_attributes.each do |_attr_| self.send :"#{_attr_}=", attributes[_attr_] end self.raw_attributes = attributes attr_missing! end def self.all_attributes required_attributes + optional_attributes end def all_attributes self.class.all_attributes end def require_at_least_one_attributes all_blank = all_attributes.all? do |key| self.send(key).blank? end errors.add :base, 'At least one attribute is required' if all_blank end def as_json(options = {}) options ||= {} # options can be nil when to_json is called without options validate! unless options[:skip_validation] all_attributes.inject({}) do |hash, _attr_| value = self.send(_attr_) hash.merge! _attr_ => case value when ConnectObject value.as_json options else value end end.delete_if do |key, value| value.nil? end end def validate! valid? or raise ValidationFailed.new(self) end end end require 'openid_connect/request_object' require 'openid_connect/response_object'openid_connect-1.2.0/lib/openid_connect/jwtnizable.rb0000644000004100000410000000053513704151201022761 0ustar www-datawww-datamodule OpenIDConnect module JWTnizable def to_jwt(key, algorithm = :RS256, &block) as_jwt(key, algorithm, &block).to_s end def as_jwt(key, algorithm = :RS256, &block) token = JSON::JWT.new as_json yield token if block_given? token = token.sign key, algorithm if algorithm != :none token end end endopenid_connect-1.2.0/lib/openid_connect/debugger.rb0000644000004100000410000000012013704151201022362 0ustar www-datawww-dataDir[File.dirname(__FILE__) + '/debugger/*.rb'].each do |file| require file endopenid_connect-1.2.0/lib/openid_connect/client.rb0000644000004100000410000000227413704151201022070 0ustar www-datawww-datamodule OpenIDConnect class Client < Rack::OAuth2::Client attr_optional :userinfo_endpoint, :expires_in def initialize(attributes = {}) super attributes self.userinfo_endpoint ||= '/userinfo' end def authorization_uri(params = {}) params[:scope] = setup_required_scope params[:scope] params[:prompt] = Array(params[:prompt]).join(' ') super end def userinfo_uri absolute_uri_for userinfo_endpoint end private def setup_required_scope(scopes) _scopes_ = Array(scopes).join(' ').split(' ') _scopes_ << 'openid' unless _scopes_.include?('openid') _scopes_ end def handle_success_response(response) token_hash = JSON.parse(response.body).with_indifferent_access token_type = (@forced_token_type || token_hash[:token_type]).try(:downcase) case token_type when 'bearer' AccessToken.new token_hash.merge(client: self) else raise Exception.new("Unexpected Token Type: #{token_type}") end rescue JSON::ParserError raise Exception.new("Unknown Token Type") end end end Dir[File.dirname(__FILE__) + '/client/*.rb'].each do |file| require file end openid_connect-1.2.0/lib/openid_connect/discovery/0000755000004100000410000000000013704151201022267 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/discovery/provider/0000755000004100000410000000000013704151201024121 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/discovery/provider/config/0000755000004100000410000000000013704151201025366 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/discovery/provider/config/resource.rb0000644000004100000410000000170713704151201027547 0ustar www-datawww-datarequire "openssl" module OpenIDConnect module Discovery module Provider class Config class Resource < SWD::Resource undef_required_attributes :principal, :service class Expired < SWD::Resource::Expired; end def initialize(uri) @host = uri.host @port = uri.port unless [80, 443].include?(uri.port) @path = File.join uri.path, '.well-known/openid-configuration' attr_missing! end def endpoint SWD.url_builder.build [nil, host, port, path, nil, nil] rescue URI::Error => e raise SWD::Exception.new(e.message) end private def to_response_object(hash) Response.new(hash) end def cache_key sha256 = OpenSSL::Digest::SHA256.hexdigest host "swd:resource:opneid-conf:#{sha256}" end end end end end endopenid_connect-1.2.0/lib/openid_connect/discovery/provider/config/response.rb0000644000004100000410000000701213704151201027551 0ustar www-datawww-datamodule OpenIDConnect module Discovery module Provider class Config class Response include ActiveModel::Validations, AttrRequired, AttrOptional cattr_accessor :metadata_attributes attr_reader :raw attr_accessor :expected_issuer uri_attributes = { required: [ :issuer, :authorization_endpoint, :jwks_uri ], optional: [ :token_endpoint, :userinfo_endpoint, :registration_endpoint, :end_session_endpoint, :service_documentation, :check_session_iframe, :op_policy_uri, :op_tos_uri ] } attr_required(*(uri_attributes[:required] + [ :response_types_supported, :subject_types_supported, :id_token_signing_alg_values_supported ])) attr_optional(*(uri_attributes[:optional] + [ :scopes_supported, :response_modes_supported, :grant_types_supported, :acr_values_supported, :id_token_encryption_alg_values_supported, :id_token_encryption_enc_values_supported, :userinfo_signing_alg_values_supported, :userinfo_encryption_alg_values_supported, :userinfo_encryption_enc_values_supported, :request_object_signing_alg_values_supported, :request_object_encryption_alg_values_supported, :request_object_encryption_enc_values_supported, :token_endpoint_auth_methods_supported, :token_endpoint_auth_signing_alg_values_supported, :display_values_supported, :claim_types_supported, :claims_supported, :claims_locales_supported, :ui_locales_supported, :claims_parameter_supported, :request_parameter_supported, :request_uri_parameter_supported, :require_request_uri_registration ])) validates(*required_attributes, presence: true) validates(*uri_attributes.values.flatten, url: true, allow_nil: true) validates :issuer, with: :validate_issuer_matching def initialize(hash) (required_attributes + optional_attributes).each do |key| self.send "#{key}=", hash[key] end @raw = hash end def as_json(options = {}) validate! (required_attributes + optional_attributes).inject({}) do |hash, _attr_| value = self.send _attr_ hash.merge! _attr_ => value unless value.nil? hash end end def validate! valid? or raise ValidationFailed.new(self) end def jwks @jwks ||= JSON.parse( OpenIDConnect.http_client.get_content(jwks_uri) ).with_indifferent_access JSON::JWK::Set.new @jwks[:keys] end def public_keys @public_keys ||= jwks.collect(&:to_key) end private def validate_issuer_matching if expected_issuer.present? && issuer != expected_issuer if OpenIDConnect.validate_discovery_issuer errors.add :issuer, 'mismatch' else OpenIDConnect.logger.warn 'ignoring issuer mismach.' end end end end end end end end openid_connect-1.2.0/lib/openid_connect/discovery/provider/config.rb0000644000004100000410000000113213704151201025710 0ustar www-datawww-datamodule OpenIDConnect module Discovery module Provider class Config def self.discover!(identifier, cache_options = {}) uri = URI.parse(identifier) Resource.new(uri).discover!(cache_options).tap do |response| response.expected_issuer = identifier response.validate! end rescue SWD::Exception, ValidationFailed => e raise DiscoveryFailed.new(e.message) end end end end end require 'openid_connect/discovery/provider/config/resource' require 'openid_connect/discovery/provider/config/response'openid_connect-1.2.0/lib/openid_connect/discovery/provider.rb0000644000004100000410000000141513704151201024447 0ustar www-datawww-datamodule OpenIDConnect module Discovery module Provider module Issuer REL_VALUE = 'http://openid.net/specs/connect/1.0/issuer' def issuer self.link_for(REL_VALUE)[:href] end end def self.discover!(identifier) resource = case identifier when /^acct:/, /https?:\/\// identifier when /@/ "acct:#{identifier}" else "https://#{identifier}" end response = WebFinger.discover!( resource, rel: Issuer::REL_VALUE ) response.extend Issuer response rescue WebFinger::Exception => e raise DiscoveryFailed.new(e.message) end end end end require 'openid_connect/discovery/provider/config'openid_connect-1.2.0/lib/openid_connect/response_object.rb0000644000004100000410000000023613704151201023772 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject < ConnectObject end end Dir[File.dirname(__FILE__) + '/response_object/*.rb'].each do |file| require file endopenid_connect-1.2.0/lib/openid_connect/discovery.rb0000644000004100000410000000026613704151201022620 0ustar www-datawww-datamodule OpenIDConnect module Discovery class InvalidIdentifier < Exception; end class DiscoveryFailed < Exception; end end end require 'openid_connect/discovery/provider'openid_connect-1.2.0/lib/openid_connect/request_object.rb0000644000004100000410000000200313704151201023616 0ustar www-datawww-datamodule OpenIDConnect class RequestObject < ConnectObject include JWTnizable attr_optional :client_id, :response_type, :redirect_uri, :scope, :state, :nonce, :display, :prompt, :userinfo, :id_token validate :require_at_least_one_attributes undef :id_token= def id_token=(attributes = {}) @id_token = IdToken.new(attributes) if attributes.present? end undef :userinfo= def userinfo=(attributes = {}) @userinfo = UserInfo.new(attributes) if attributes.present? end def as_json(options = {}) super.with_indifferent_access end class << self def decode(jwt_string, key = nil) new JSON::JWT.decode(jwt_string, key) end def fetch(request_uri, key = nil) jwt_string = OpenIDConnect.http_client.get_content(request_uri) decode jwt_string, key end end end end require 'openid_connect/request_object/claimable' require 'openid_connect/request_object/id_token' require 'openid_connect/request_object/user_info' openid_connect-1.2.0/lib/openid_connect/response_object/0000755000004100000410000000000013704151201023444 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/response_object/user_info/0000755000004100000410000000000013704151201025435 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/response_object/user_info/address.rb0000644000004100000410000000041613704151201027410 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject class UserInfo class Address < ConnectObject attr_optional :formatted, :street_address, :locality, :region, :postal_code, :country validate :require_at_least_one_attributes end end end endopenid_connect-1.2.0/lib/openid_connect/response_object/user_info.rb0000644000004100000410000000365713704151201025775 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject class UserInfo < ConnectObject attr_optional( :sub, :name, :given_name, :family_name, :middle_name, :nickname, :preferred_username, :profile, :picture, :website, :email, :email_verified, :gender, :birthdate, :zoneinfo, :locale, :phone_number, :phone_number_verified, :address, :updated_at ) alias_method :subject, :sub alias_method :subject=, :sub= validates :email_verified, :phone_number_verified, allow_nil: true, inclusion: {in: [true, false]} validates :zoneinfo, allow_nil: true, inclusion: {in: TZInfo::TimezoneProxy.all.collect(&:name)} validates :profile, :picture, :website, allow_nil: true, url: true validates :email, allow_nil: true, email: true validates :updated_at, allow_nil: true, numericality: {only_integer: true} validate :validate_address validate :require_at_least_one_attributes # TODO: validate locale def initialize(attributes = {}) super (all_attributes - [:email_verified, :phone_number_verified, :address, :updated_at]).each do |key| self.send "#{key}=", self.send(key).try(:to_s) end self.updated_at = updated_at.try(:to_i) end def validate_address errors.add :address, address.errors.full_messages.join(', ') if address.present? && !address.valid? end undef :address= def address=(hash_or_address) @address = case hash_or_address when Hash Address.new hash_or_address when Address hash_or_address end end end end end Dir[File.dirname(__FILE__) + '/user_info/*.rb'].each do |file| require file end openid_connect-1.2.0/lib/openid_connect/response_object/id_token.rb0000644000004100000410000000622013704151201025565 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject class IdToken < ConnectObject class InvalidToken < Exception; end class ExpiredToken < InvalidToken; end class InvalidIssuer < InvalidToken; end class InvalidNonce < InvalidToken; end class InvalidAudience < InvalidToken; end attr_required :iss, :sub, :aud, :exp, :iat attr_optional :acr, :amr, :azp, :jti, :sid, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash, :s_hash attr_accessor :access_token, :code, :state alias_method :subject, :sub alias_method :subject=, :sub= def initialize(attributes = {}) super (all_attributes - [:aud, :exp, :iat, :auth_time, :sub_jwk]).each do |key| self.send "#{key}=", self.send(key).try(:to_s) end self.auth_time = auth_time.to_i unless auth_time.nil? end def verify!(expected = {}) raise ExpiredToken.new('Invalid ID token: Expired token') unless exp.to_i > Time.now.to_i raise InvalidIssuer.new('Invalid ID token: Issuer does not match') unless iss == expected[:issuer] raise InvalidNonce.new('Invalid ID Token: Nonce does not match') unless nonce == expected[:nonce] # aud(ience) can be a string or an array of strings unless Array(aud).include?(expected[:audience] || expected[:client_id]) raise InvalidAudience.new('Invalid ID token: Audience does not match') end true end include JWTnizable def to_jwt(key, algorithm = :RS256, &block) hash_length = algorithm.to_s[2, 3].to_i if access_token token = case access_token when Rack::OAuth2::AccessToken access_token.access_token else access_token end self.at_hash = left_half_hash_of token, hash_length end if code self.c_hash = left_half_hash_of code, hash_length end if state self.s_hash = left_half_hash_of state, hash_length end super end private def left_half_hash_of(string, hash_length) digest = OpenSSL::Digest.new("SHA#{hash_length}").digest string Base64.urlsafe_encode64 digest[0, hash_length / (2 * 8)], padding: false end class << self def decode(jwt_string, key) if key == :self_issued decode_self_issued jwt_string else new JSON::JWT.decode jwt_string, key end end def decode_self_issued(jwt_string) jwt = JSON::JWT.decode jwt_string, :skip_verification jwk = JSON::JWK.new jwt[:sub_jwk] raise InvalidToken.new('Missing sub_jwk') if jwk.blank? raise InvalidToken.new('Invalid subject') unless jwt[:sub] == jwk.thumbprint jwt.verify! jwk new jwt end def self_issued(attributes = {}) attributes[:sub_jwk] ||= JSON::JWK.new attributes.delete(:public_key) _attributes_ = { iss: 'https://self-issued.me', sub: JSON::JWK.new(attributes[:sub_jwk]).thumbprint }.merge(attributes) new _attributes_ end end end end end openid_connect-1.2.0/lib/openid_connect/exception.rb0000644000004100000410000000151613704151201022606 0ustar www-datawww-datamodule OpenIDConnect class Exception < StandardError; end class ValidationFailed < Exception attr_reader :object def initialize(object) super object.errors.full_messages.to_sentence @object = object end 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 endopenid_connect-1.2.0/lib/openid_connect/access_token.rb0000644000004100000410000000152713704151201023253 0ustar www-datawww-datamodule OpenIDConnect class AccessToken < Rack::OAuth2::AccessToken::Bearer attr_required :client attr_optional :id_token def initialize(attributes = {}) super @token_type = :bearer end def userinfo!(params = {}) hash = resource_request do get client.userinfo_uri, params end ResponseObject::UserInfo.new hash end private def resource_request res = yield case res.status when 200 JSON.parse(res.body).with_indifferent_access when 400 raise BadRequest.new('API Access Faild', res) when 401 raise Unauthorized.new('Access Token Invalid or Expired', res) when 403 raise Forbidden.new('Insufficient Scope', res) else raise HttpError.new(res.status, 'Unknown HttpError', res) end end end endopenid_connect-1.2.0/lib/openid_connect/request_object/0000755000004100000410000000000013704151201023276 5ustar www-datawww-dataopenid_connect-1.2.0/lib/openid_connect/request_object/claimable.rb0000644000004100000410000000236013704151201025535 0ustar www-datawww-datamodule OpenIDConnect class RequestObject module Claimable def self.included(klass) klass.send :attr_optional, :claims end def initialize(attributes = {}) super if claims.present? _claims_ = {} claims.each do |key, value| _claims_[key] = case value when :optional, :voluntary { essential: false } when :required, :essential { essential: true } else value end end self.claims = _claims_.with_indifferent_access end end def as_json(options = {}) keys = claims.try(:keys) hash = super Array(keys).each do |key| hash[:claims][key] ||= nil end hash end def required?(claim) accessible?(claim) && claims[claim].is_a?(Hash) && claims[claim][:essential] end alias_method :essential?, :required? def optional?(claim) accessible?(claim) && !required?(claim) end alias_method :voluntary?, :optional? def accessible?(claim) claims.try(:include?, claim) end end end endopenid_connect-1.2.0/lib/openid_connect/request_object/user_info.rb0000644000004100000410000000016713704151201025620 0ustar www-datawww-datamodule OpenIDConnect class RequestObject class UserInfo < ConnectObject include Claimable end end endopenid_connect-1.2.0/lib/openid_connect/request_object/id_token.rb0000644000004100000410000000022313704151201025414 0ustar www-datawww-datamodule OpenIDConnect class RequestObject class IdToken < ConnectObject include Claimable attr_optional :max_age end end endopenid_connect-1.2.0/README.rdoc0000644000004100000410000000274213704151201016336 0ustar www-datawww-data= OpenIDConnect OpenID Connect Server & Client Library {}[http://travis-ci.org/nov/openid_connect] == Installation gem install openid_connect == Resources * View Source on GitHub (https://github.com/nov/openid_connect) * Report Issues on GitHub (https://github.com/nov/openid_connect/issues) * Subscribe Update Info (https://www.facebook.com/OpenIDConnect.rb) == Examples === Provider * Running on Heroku (https://connect-op.herokuapp.com) * Source on GitHub (https://github.com/nov/openid_connect_sample) * Simpler Version (https://github.com/nov/openid_connect_sample2) === Relying Party * Running on Heroku (https://connect-rp.herokuapp.com) * Source on GitHub (https://github.com/nov/openid_connect_sample_rp) There is also OpenID Foudation Certified RP implementation using this gem below. * Running on Heroku (https://connect-rp-certified.herokuapp.com) * Source on GitHub (https://github.com/nov/connect-rp-certified) == 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. openid_connect-1.2.0/Gemfile0000644000004100000410000000004613704151201016016 0ustar www-datawww-datasource 'https://rubygems.org' gemspec openid_connect-1.2.0/TODOs0000644000004100000410000000031413704151201015374 0ustar www-datawww-data## Discovery * WebFinger User Input Normalization ## Dynamic Client Registration * Update Registration Response Format * Client Metadata "Read" Call Support ## Message * Update UserInfo OpenID Schemaopenid_connect-1.2.0/VERSION0000644000004100000410000000000513704151201015566 0ustar www-datawww-data1.2.0