openid_connect-1.1.6/0000755000004100000410000000000013312105731014532 5ustar www-datawww-dataopenid_connect-1.1.6/.travis.yml0000644000004100000410000000011413312105731016637 0ustar www-datawww-databefore_install: - gem install bundler rvm: - 2.3.6 - 2.4.3 - 2.5.0 openid_connect-1.1.6/.rspec0000644000004100000410000000003713312105731015647 0ustar www-datawww-data--color --format=documentation openid_connect-1.1.6/spec/0000755000004100000410000000000013312105731015464 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect_spec.rb0000644000004100000410000000332113312105731022011 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.1.6/spec/helpers/0000755000004100000410000000000013312105731017126 5ustar www-datawww-dataopenid_connect-1.1.6/spec/helpers/webmock_helper.rb0000644000004100000410000000204113312105731022436 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.1.6/spec/helpers/crypto_spec_helper.rb0000644000004100000410000000117013312105731023343 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.1.6/spec/rack/0000755000004100000410000000000013312105731016404 5ustar www-datawww-dataopenid_connect-1.1.6/spec/rack/oauth2/0000755000004100000410000000000013312105731017606 5ustar www-datawww-dataopenid_connect-1.1.6/spec/rack/oauth2/server/0000755000004100000410000000000013312105731021114 5ustar www-datawww-dataopenid_connect-1.1.6/spec/rack/oauth2/server/authorize/0000755000004100000410000000000013312105731023126 5ustar www-datawww-dataopenid_connect-1.1.6/spec/rack/oauth2/server/authorize/request_with_connect_params_spec.rb0000644000004100000410000000211513312105731032263 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.1.6/spec/rack/oauth2/server/authorize/extension/0000755000004100000410000000000013312105731025142 5ustar www-datawww-dataopenid_connect-1.1.6/spec/rack/oauth2/server/authorize/extension/id_token_and_token_spec.rb0000644000004100000410000000440213312105731032317 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.1.6/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.rb0000644000004100000410000000460213312105731034135 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.1.6/spec/rack/oauth2/server/authorize/extension/id_token_spec.rb0000644000004100000410000000432113312105731030275 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.1.6/spec/rack/oauth2/server/authorize/extension/code_and_id_token_spec.rb0000644000004100000410000000412113312105731032107 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.1.6/spec/rack/oauth2/server/token/0000755000004100000410000000000013312105731022234 5ustar www-datawww-dataopenid_connect-1.1.6/spec/rack/oauth2/server/token/authorization_code_spec.rb0000644000004100000410000000273613312105731027475 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.1.6/spec/rack/oauth2/server/token/refresh_token_spec.rb0000644000004100000410000000264113312105731026434 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.1.6/spec/mock_response/0000755000004100000410000000000013312105731020333 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/client/0000755000004100000410000000000013312105731021611 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/client/updated.json0000644000004100000410000000004713312105731024133 0ustar www-datawww-data{ "client_id": "client.example.com" }openid_connect-1.1.6/spec/mock_response/client/rotated.json0000644000004100000410000000014513312105731024146 0ustar www-datawww-data{ "client_id": "client.example.com", "client_secret": "new_client_secret", "expires_in": 3600 }openid_connect-1.1.6/spec/mock_response/client/registered.json0000644000004100000410000000014113312105731024635 0ustar www-datawww-data{ "client_id": "client.example.com", "client_secret": "client_secret", "expires_in": 3600 }openid_connect-1.1.6/spec/mock_response/userinfo/0000755000004100000410000000000013312105731022165 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/userinfo/openid.json0000644000004100000410000000113713312105731024340 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.1.6/spec/mock_response/errors/0000755000004100000410000000000013312105731021647 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/errors/unknown.json0000644000004100000410000000002413312105731024235 0ustar www-datawww-dataFuckin Unknown Erroropenid_connect-1.1.6/spec/mock_response/errors/insufficient_scope.json0000644000004100000410000000004313312105731026416 0ustar www-datawww-data{ "error": "insufficient_scope" }openid_connect-1.1.6/spec/mock_response/errors/invalid_access_token.json0000644000004100000410000000004513312105731026710 0ustar www-datawww-data{ "error": "invalid_access_token" }openid_connect-1.1.6/spec/mock_response/errors/invalid_request.json0000644000004100000410000000004013312105731025732 0ustar www-datawww-data{ "error": "invalid_request" }openid_connect-1.1.6/spec/mock_response/id_token.json0000644000004100000410000000022513312105731023021 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.1.6/spec/mock_response/discovery/0000755000004100000410000000000013312105731022342 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/discovery/config_with_invalid_issuer.json0000644000004100000410000000135513312105731030641 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.1.6/spec/mock_response/discovery/config_with_custom_port.json0000644000004100000410000000140713312105731030175 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.1.6/spec/mock_response/discovery/config_without_issuer.json0000644000004100000410000000127713312105731027666 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.1.6/spec/mock_response/discovery/swd.json0000644000004100000410000000006113312105731024027 0ustar www-datawww-data{ "locations": ["https://server.example.com"] }openid_connect-1.1.6/spec/mock_response/discovery/config.json0000644000004100000410000000135613312105731024507 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.1.6/spec/mock_response/discovery/config_with_path.json0000644000004100000410000000137013312105731026552 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.1.6/spec/mock_response/discovery/webfinger.json0000644000004100000410000000031213312105731025201 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.1.6/spec/mock_response/access_token/0000755000004100000410000000000013312105731022774 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/access_token/bearer_with_id_token.json0000644000004100000410000000021513312105731030034 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.1.6/spec/mock_response/access_token/without_token_type.json0000644000004100000410000000004413312105731027631 0ustar www-datawww-data{ "access_token":"access_token" } openid_connect-1.1.6/spec/mock_response/access_token/mac.json0000644000004100000410000000026013312105731024425 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.1.6/spec/mock_response/access_token/bearer.json0000644000004100000410000000016413312105731025130 0ustar www-datawww-data{ "access_token":"access_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid_connect-1.1.6/spec/mock_response/access_token/invalid_json.json0000644000004100000410000000003113312105731026340 0ustar www-datawww-dataaccess_token=access_tokenopenid_connect-1.1.6/spec/mock_response/public_keys/0000755000004100000410000000000013312105731022644 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/public_keys/jwks.json0000644000004100000410000000063313312105731024517 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.1.6/spec/mock_response/request_object/0000755000004100000410000000000013312105731023351 5ustar www-datawww-dataopenid_connect-1.1.6/spec/mock_response/request_object/signed.jwt0000644000004100000410000000103513312105731025347 0ustar www-datawww-dataeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJjbGllbnRfaWQiLCJyZXNwb25zZV90eXBlIjoidG9rZW4gaWRfdG9rZW4iLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLmNvbSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIiwic3RhdGUiOiJzdGF0ZTEyMzQiLCJub25jZSI6Im5vbmNlMTIzNCIsImRpc3BsYXkiOiJ0b3VjaCIsInByb21wdCI6Im5vbmUiLCJpZF90b2tlbiI6eyJjbGFpbXMiOnsiYWNyIjp7InZhbHVlcyI6WyIyIiwiMyIsIjQiXX19LCJtYXhfYWdlIjoxMH0sInVzZXJpbmZvIjp7ImNsYWltcyI6eyJuYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6ZmFsc2V9fX19.MLTDQVPdhAdkJhboM06IRtjHJrvamJ_H2vFGRupXmTAopenid_connect-1.1.6/spec/spec_helper.rb0000644000004100000410000000046013312105731020302 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.1.6/spec/openid_connect/0000755000004100000410000000000013312105731020453 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/client/0000755000004100000410000000000013312105731021731 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/client/registrar_spec.rb0000644000004100000410000001511213312105731025272 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.1.6/spec/openid_connect/debugger/0000755000004100000410000000000013312105731022237 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/debugger/request_filter_spec.rb0000644000004100000410000000206613312105731026637 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.1.6/spec/openid_connect/exception_spec.rb0000644000004100000410000000122013312105731024003 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.1.6/spec/openid_connect/discovery/0000755000004100000410000000000013312105731022462 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/discovery/provider/0000755000004100000410000000000013312105731024314 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/discovery/provider/config_spec.rb0000644000004100000410000000761713312105731027133 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.1.6/spec/openid_connect/discovery/provider/config/0000755000004100000410000000000013312105731025561 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/discovery/provider/config/resource_spec.rb0000644000004100000410000000071213312105731030747 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.1.6/spec/openid_connect/discovery/provider/config/response_spec.rb0000644000004100000410000000506213312105731030761 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.1.6/spec/openid_connect/discovery/provider_spec.rb0000644000004100000410000000375513312105731025665 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.1.6/spec/openid_connect/connect_object_spec.rb0000644000004100000410000000507713312105731025002 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.1.6/spec/openid_connect/access_token_spec.rb0000644000004100000410000000602413312105731024455 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.1.6/spec/openid_connect/client_spec.rb0000644000004100000410000001235413312105731023275 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.1.6/spec/openid_connect/response_object/0000755000004100000410000000000013312105731023637 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/response_object/user_info_spec.rb0000644000004100000410000000547513312105731027202 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.1.6/spec/openid_connect/response_object/user_info/0000755000004100000410000000000013312105731025630 5ustar www-datawww-dataopenid_connect-1.1.6/spec/openid_connect/response_object/user_info/address_spec.rb0000644000004100000410000000126513312105731030620 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.1.6/spec/openid_connect/response_object/id_token_spec.rb0000644000004100000410000002730213312105731026776 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, :events] } 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.1.6/spec/openid_connect/request_object_spec.rb0000644000004100000410000000652713312105731025042 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.1.6/.gitignore0000644000004100000410000000024013312105731016516 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.1.6/LICENSE0000644000004100000410000000205213312105731015536 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.1.6/openid_connect.gemspec0000644000004100000410000000232313312105731021066 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.1.6/Rakefile0000644000004100000410000000062213312105731016177 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.1.6/lib/0000755000004100000410000000000013312105731015300 5ustar www-datawww-dataopenid_connect-1.1.6/lib/rack/0000755000004100000410000000000013312105731016220 5ustar www-datawww-dataopenid_connect-1.1.6/lib/rack/oauth2/0000755000004100000410000000000013312105731017422 5ustar www-datawww-dataopenid_connect-1.1.6/lib/rack/oauth2/server/0000755000004100000410000000000013312105731020730 5ustar www-datawww-dataopenid_connect-1.1.6/lib/rack/oauth2/server/id_token_response.rb0000644000004100000410000000124013312105731024764 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.1.6/lib/rack/oauth2/server/authorize/0000755000004100000410000000000013312105731022742 5ustar www-datawww-dataopenid_connect-1.1.6/lib/rack/oauth2/server/authorize/request_with_connect_params.rb0000644000004100000410000000133213312105731031065 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.1.6/lib/rack/oauth2/server/authorize/error_with_connect_ext.rb0000644000004100000410000000266413312105731030054 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.1.6/lib/rack/oauth2/server/authorize/extension/0000755000004100000410000000000013312105731024756 5ustar www-datawww-dataopenid_connect-1.1.6/lib/rack/oauth2/server/authorize/extension/code_and_id_token_and_token.rb0000644000004100000410000000170413312105731032737 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.1.6/lib/rack/oauth2/server/authorize/extension/code_and_id_token.rb0000644000004100000410000000173213312105731030716 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.1.6/lib/rack/oauth2/server/authorize/extension/id_token_and_token.rb0000644000004100000410000000161213312105731031121 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.1.6/lib/rack/oauth2/server/authorize/extension/id_token.rb0000644000004100000410000000165113312105731027102 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.1.6/lib/openid_connect.rb0000644000004100000410000000446413312105731020624 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.1.6/lib/openid_connect/0000755000004100000410000000000013312105731020267 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/client/0000755000004100000410000000000013312105731021545 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/client/registrar.rb0000644000004100000410000001247613312105731024106 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.1.6/lib/openid_connect/debugger/0000755000004100000410000000000013312105731022053 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/debugger/request_filter.rb0000644000004100000410000000140713312105731025437 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.1.6/lib/openid_connect/connect_object.rb0000644000004100000410000000246313312105731023600 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.1.6/lib/openid_connect/jwtnizable.rb0000644000004100000410000000053513312105731022770 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.1.6/lib/openid_connect/debugger.rb0000644000004100000410000000012013312105731022371 0ustar www-datawww-dataDir[File.dirname(__FILE__) + '/debugger/*.rb'].each do |file| require file endopenid_connect-1.1.6/lib/openid_connect/client.rb0000644000004100000410000000227413312105731022077 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.1.6/lib/openid_connect/discovery/0000755000004100000410000000000013312105731022276 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/discovery/provider/0000755000004100000410000000000013312105731024130 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/discovery/provider/config/0000755000004100000410000000000013312105731025375 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/discovery/provider/config/resource.rb0000644000004100000410000000164213312105731027554 0ustar www-datawww-datamodule 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 md5 = Digest::MD5.hexdigest host "swd:resource:opneid-conf:#{md5}" end end end end end endopenid_connect-1.1.6/lib/openid_connect/discovery/provider/config/response.rb0000644000004100000410000000700613312105731027563 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.1.6/lib/openid_connect/discovery/provider/config.rb0000644000004100000410000000113213312105731025717 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.1.6/lib/openid_connect/discovery/provider.rb0000644000004100000410000000141513312105731024456 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.1.6/lib/openid_connect/response_object.rb0000644000004100000410000000023613312105731024001 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.1.6/lib/openid_connect/discovery.rb0000644000004100000410000000026613312105731022627 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.1.6/lib/openid_connect/request_object.rb0000644000004100000410000000173113312105731023634 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 def id_token=(attributes = {}) @id_token = IdToken.new(attributes) if attributes.present? end 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.1.6/lib/openid_connect/response_object/0000755000004100000410000000000013312105731023453 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/response_object/user_info/0000755000004100000410000000000013312105731025444 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/response_object/user_info/address.rb0000644000004100000410000000041613312105731027417 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.1.6/lib/openid_connect/response_object/user_info.rb0000644000004100000410000000363113312105731025774 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 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.1.6/lib/openid_connect/response_object/id_token.rb0000644000004100000410000000605613312105731025603 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, :events attr_accessor :access_token, :code 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 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.1.6/lib/openid_connect/exception.rb0000644000004100000410000000151613312105731022615 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.1.6/lib/openid_connect/access_token.rb0000644000004100000410000000152713312105731023262 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.1.6/lib/openid_connect/request_object/0000755000004100000410000000000013312105731023305 5ustar www-datawww-dataopenid_connect-1.1.6/lib/openid_connect/request_object/claimable.rb0000644000004100000410000000236013312105731025544 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.1.6/lib/openid_connect/request_object/user_info.rb0000644000004100000410000000016713312105731025627 0ustar www-datawww-datamodule OpenIDConnect class RequestObject class UserInfo < ConnectObject include Claimable end end endopenid_connect-1.1.6/lib/openid_connect/request_object/id_token.rb0000644000004100000410000000022313312105731025423 0ustar www-datawww-datamodule OpenIDConnect class RequestObject class IdToken < ConnectObject include Claimable attr_optional :max_age end end endopenid_connect-1.1.6/README.rdoc0000644000004100000410000000274213312105731016345 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.1.6/Gemfile0000644000004100000410000000004613312105731016025 0ustar www-datawww-datasource 'https://rubygems.org' gemspec openid_connect-1.1.6/TODOs0000644000004100000410000000031413312105731015403 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.1.6/VERSION0000644000004100000410000000000513312105731015575 0ustar www-datawww-data1.1.6