openid-connect-0.12.0/0000755000175100017510000000000012757060301013216 5ustar srudsrudopenid-connect-0.12.0/openid_connect.gemspec0000644000175100017510000000240012757060301017546 0ustar srudsrudGem::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 "json", ">= 1.4.3" 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.3.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" endopenid-connect-0.12.0/lib/0000755000175100017510000000000012757060301013764 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect.rb0000644000175100017510000000411412757060301017300 0ustar srudsrudrequire '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 '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 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-0.12.0/lib/rack/0000755000175100017510000000000012757060301014704 5ustar srudsrudopenid-connect-0.12.0/lib/rack/oauth2/0000755000175100017510000000000012757060301016106 5ustar srudsrudopenid-connect-0.12.0/lib/rack/oauth2/server/0000755000175100017510000000000012757060301017414 5ustar srudsrudopenid-connect-0.12.0/lib/rack/oauth2/server/authorize/0000755000175100017510000000000012757060301021426 5ustar srudsrudopenid-connect-0.12.0/lib/rack/oauth2/server/authorize/request_with_connect_params.rb0000644000175100017510000000125012757060301027550 0ustar srudsrudclass 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.max_age = max_age.try(:to_i) end def openid_connect_request? scope.include?('openid') end end Request.send :prepend, RequestWithConnectParams endopenid-connect-0.12.0/lib/rack/oauth2/server/authorize/extension/0000755000175100017510000000000012757060301023442 5ustar srudsrudopenid-connect-0.12.0/lib/rack/oauth2/server/authorize/extension/code_and_id_token_and_token.rb0000644000175100017510000000170312757060301031422 0ustar srudsrudmodule 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-0.12.0/lib/rack/oauth2/server/authorize/extension/id_token.rb0000644000175100017510000000165012757060301025565 0ustar srudsrudmodule 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-0.12.0/lib/rack/oauth2/server/authorize/extension/code_and_id_token.rb0000644000175100017510000000173112757060301027401 0ustar srudsrudmodule 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-0.12.0/lib/rack/oauth2/server/authorize/extension/id_token_and_token.rb0000644000175100017510000000161112757060301027604 0ustar srudsrudmodule 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-0.12.0/lib/rack/oauth2/server/authorize/error_with_connect_ext.rb0000644000175100017510000000266412757060301026540 0ustar srudsrudmodule 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-0.12.0/lib/rack/oauth2/server/id_token_response.rb0000644000175100017510000000124012757060301023450 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/0000755000175100017510000000000012757060301016753 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/discovery/0000755000175100017510000000000012757060301020762 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/discovery/provider.rb0000644000175100017510000000141512757060301023142 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/discovery/provider/0000755000175100017510000000000012757060301022614 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/discovery/provider/config/0000755000175100017510000000000012757060301024061 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/discovery/provider/config/response.rb0000644000175100017510000000612112757060301026244 0ustar srudsrudmodule OpenIDConnect module Discovery module Provider class Config class Response include ActiveModel::Validations, AttrRequired, AttrOptional cattr_accessor :metadata_attributes attr_reader :raw uri_attributes = { required: [ :issuer, :authorization_endpoint, :jwks_uri ], optional: [ :token_endpoint, :userinfo_endpoint, :registration_endpoint, :service_documentation, :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 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!(expected_issuer = nil) valid? && ( expected_issuer.blank? || issuer == expected_issuer ) 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 end end end end endopenid-connect-0.12.0/lib/openid_connect/discovery/provider/config/resource.rb0000644000175100017510000000164212757060301026240 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/discovery/provider/config.rb0000644000175100017510000000106312757060301024406 0ustar srudsrudmodule 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.validate! identifier 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-0.12.0/lib/openid_connect/jwtnizable.rb0000644000175100017510000000053512757060301021454 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/client/0000755000175100017510000000000012757060301020231 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/client/registrar.rb0000644000175100017510000001247612757060301022572 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/client.rb0000644000175100017510000000215112757060301020555 0ustar srudsrudmodule 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] super end def userinfo_uri absolute_uri_for userinfo_endpoint end private def setup_required_scope(scopes) _scopes_ = Array(scopes).collect(&:to_s).join(' ').split(' ') _scopes_ << 'openid' unless _scopes_.include?('openid') _scopes_ end def handle_success_response(response) token_hash = JSON.parse(response.body).with_indifferent_access case token_type = token_hash[:token_type].try(:downcase) 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 endopenid-connect-0.12.0/lib/openid_connect/request_object.rb0000644000175100017510000000173112757060301022320 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/discovery.rb0000644000175100017510000000026612757060301021313 0ustar srudsrudmodule OpenIDConnect module Discovery class InvalidIdentifier < Exception; end class DiscoveryFailed < Exception; end end end require 'openid_connect/discovery/provider'openid-connect-0.12.0/lib/openid_connect/response_object/0000755000175100017510000000000012757060301022137 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/response_object/user_info/0000755000175100017510000000000012757060301024130 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/response_object/user_info/address.rb0000644000175100017510000000041612757060301026103 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/response_object/id_token.rb0000644000175100017510000000476312757060301024272 0ustar srudsrudrequire 'json/jwt' module OpenIDConnect class ResponseObject class IdToken < ConnectObject class InvalidToken < Exception; end attr_required :iss, :sub, :aud, :exp, :iat attr_optional :acr, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash 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 end def verify!(expected = {}) exp.to_i > Time.now.to_i && iss == expected[:issuer] && Array(aud).include?(expected[:client_id]) && # aud(ience) can be a string or an array of strings nonce == expected[:nonce] or raise InvalidToken.new('Invalid ID Token') 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 UrlSafeBase64.encode64 digest[0, hash_length / (2 * 8)] 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 endopenid-connect-0.12.0/lib/openid_connect/response_object/user_info.rb0000644000175100017510000000363112757060301024460 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/debugger.rb0000644000175100017510000000012012757060301021055 0ustar srudsrudDir[File.dirname(__FILE__) + '/debugger/*.rb'].each do |file| require file endopenid-connect-0.12.0/lib/openid_connect/access_token.rb0000644000175100017510000000152712757060301021746 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/request_object/0000755000175100017510000000000012757060301021771 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/request_object/id_token.rb0000644000175100017510000000022312757060301024107 0ustar srudsrudmodule OpenIDConnect class RequestObject class IdToken < ConnectObject include Claimable attr_optional :max_age end end endopenid-connect-0.12.0/lib/openid_connect/request_object/user_info.rb0000644000175100017510000000016712757060301024313 0ustar srudsrudmodule OpenIDConnect class RequestObject class UserInfo < ConnectObject include Claimable end end endopenid-connect-0.12.0/lib/openid_connect/request_object/claimable.rb0000644000175100017510000000236012757060301024230 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/debugger/0000755000175100017510000000000012757060301020537 5ustar srudsrudopenid-connect-0.12.0/lib/openid_connect/debugger/request_filter.rb0000644000175100017510000000140712757060301024123 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/exception.rb0000644000175100017510000000151612757060301021301 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/connect_object.rb0000644000175100017510000000246312757060301022264 0ustar srudsrudmodule 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-0.12.0/lib/openid_connect/response_object.rb0000644000175100017510000000023612757060301022465 0ustar srudsrudmodule OpenIDConnect class ResponseObject < ConnectObject end end Dir[File.dirname(__FILE__) + '/response_object/*.rb'].each do |file| require file endopenid-connect-0.12.0/.travis.yml0000644000175100017510000000011712757060301015326 0ustar srudsrudbefore_install: - gem install bundler rvm: - 2.0 - 2.1 - 2.2 - 2.3.0openid-connect-0.12.0/TODOs0000644000175100017510000000031412757060301014067 0ustar srudsrud## Discovery * WebFinger User Input Normalization ## Dynamic Client Registration * Update Registration Response Format * Client Metadata "Read" Call Support ## Message * Update UserInfo OpenID Schemaopenid-connect-0.12.0/README.rdoc0000644000175100017510000000241512757060301015026 0ustar srudsrud= 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) == 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-0.12.0/.gitignore0000644000175100017510000000024012757060301015202 0ustar srudsrud## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg Gemfile.lock ## PROJECT::SPECIFIC openid-connect-0.12.0/VERSION0000644000175100017510000000000612757060301014262 0ustar srudsrud0.12.0openid-connect-0.12.0/LICENSE0000644000175100017510000000205212757060301014222 0ustar srudsrudCopyright (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-0.12.0/Rakefile0000644000175100017510000000062212757060301014663 0ustar srudsrudrequire '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-0.12.0/Gemfile0000644000175100017510000000004612757060301014511 0ustar srudsrudsource 'https://rubygems.org' gemspec openid-connect-0.12.0/.rspec0000644000175100017510000000003712757060301014333 0ustar srudsrud--color --format=documentation openid-connect-0.12.0/spec/0000755000175100017510000000000012757060301014150 5ustar srudsrudopenid-connect-0.12.0/spec/rack/0000755000175100017510000000000012757060301015070 5ustar srudsrudopenid-connect-0.12.0/spec/rack/oauth2/0000755000175100017510000000000012757060301016272 5ustar srudsrudopenid-connect-0.12.0/spec/rack/oauth2/server/0000755000175100017510000000000012757060301017600 5ustar srudsrudopenid-connect-0.12.0/spec/rack/oauth2/server/authorize/0000755000175100017510000000000012757060301021612 5ustar srudsrudopenid-connect-0.12.0/spec/rack/oauth2/server/authorize/extension/0000755000175100017510000000000012757060301023626 5ustar srudsrudopenid-connect-0.12.0/spec/rack/oauth2/server/authorize/extension/id_token_and_token_spec.rb0000644000175100017510000000346712757060301031015 0ustar srudsrudrequire '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 end././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootopenid-connect-0.12.0/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.rbopenid-connect-0.12.0/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.r0000644000175100017510000000366012757060301032462 0ustar srudsrudrequire '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 endopenid-connect-0.12.0/spec/rack/oauth2/server/authorize/extension/id_token_spec.rb0000644000175100017510000000432112757060301026761 0ustar srudsrudrequire '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-0.12.0/spec/rack/oauth2/server/authorize/extension/code_and_id_token_spec.rb0000644000175100017510000000320712757060301030577 0ustar srudsrudrequire '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 endopenid-connect-0.12.0/spec/rack/oauth2/server/token/0000755000175100017510000000000012757060301020720 5ustar srudsrudopenid-connect-0.12.0/spec/rack/oauth2/server/token/refresh_token_spec.rb0000644000175100017510000000264112757060301025120 0ustar srudsrudrequire '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-0.12.0/spec/rack/oauth2/server/token/authorization_code_spec.rb0000644000175100017510000000273612757060301026161 0ustar srudsrudrequire '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-0.12.0/spec/helpers/0000755000175100017510000000000012757060301015612 5ustar srudsrudopenid-connect-0.12.0/spec/helpers/crypto_spec_helper.rb0000644000175100017510000000120012757060301022021 0ustar srudsrudmodule 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::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-0.12.0/spec/helpers/webmock_helper.rb0000644000175100017510000000204112757060301021122 0ustar srudsrudrequire '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-0.12.0/spec/mock_response/0000755000175100017510000000000012757060301017017 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/discovery/0000755000175100017510000000000012757060301021026 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/discovery/config.json0000644000175100017510000000135612757060301023173 0ustar srudsrud{ "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-0.12.0/spec/mock_response/discovery/config_without_issuer.json0000644000175100017510000000127712757060301026352 0ustar srudsrud{ "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-0.12.0/spec/mock_response/discovery/config_with_custom_port.json0000644000175100017510000000140712757060301026661 0ustar srudsrud{ "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-0.12.0/spec/mock_response/discovery/config_with_path.json0000644000175100017510000000137012757060301025236 0ustar srudsrud{ "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-0.12.0/spec/mock_response/discovery/webfinger.json0000644000175100017510000000031212757060301023665 0ustar srudsrud{ "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-0.12.0/spec/mock_response/discovery/swd.json0000644000175100017510000000006112757060301022513 0ustar srudsrud{ "locations": ["https://server.example.com"] }openid-connect-0.12.0/spec/mock_response/discovery/config_with_invalid_issuer.json0000644000175100017510000000135512757060301027325 0ustar srudsrud{ "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-0.12.0/spec/mock_response/client/0000755000175100017510000000000012757060301020275 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/client/registered.json0000644000175100017510000000014112757060301023321 0ustar srudsrud{ "client_id": "client.example.com", "client_secret": "client_secret", "expires_in": 3600 }openid-connect-0.12.0/spec/mock_response/client/rotated.json0000644000175100017510000000014512757060301022632 0ustar srudsrud{ "client_id": "client.example.com", "client_secret": "new_client_secret", "expires_in": 3600 }openid-connect-0.12.0/spec/mock_response/client/updated.json0000644000175100017510000000004712757060301022617 0ustar srudsrud{ "client_id": "client.example.com" }openid-connect-0.12.0/spec/mock_response/public_keys/0000755000175100017510000000000012757060301021330 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/public_keys/jwks.json0000644000175100017510000000063312757060301023203 0ustar srudsrud{ "keys": [{ "kty": "RSA", "e": "AQAB", "n": "u4liYNFzgsRr1ERdUY7CY6r4nefi3RzIhK5fdPgdZSMEEflACWAuJu21_TcDpbZ1-6Kbq7zShFsVTAnBkWdO7EP1Rsn11fZpi9m_zEq_uRY-4RpNwp3S9xSdoQ4F3-js1EMaDQ6km0-c0gvr_TyhFqDj_6w_Bb0vFptfGXwfKewPPnhsi7GJ62ihZ32PzxOvEIYcaoXr9xaeudYD3BzWSDmjKGA7PMaEuBhScdUAoibCmsKB-yAGsz2amHnUhcl4B_EBs6wk65Y7ge0ZQJUOGPdUQL49VuALKmr7cMhHKh5KuQmPAi_20K2uZL_EFDaObDWZrclx98s0DmfTRKINtw" }] } openid-connect-0.12.0/spec/mock_response/userinfo/0000755000175100017510000000000012757060301020651 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/userinfo/openid.json0000644000175100017510000000113712757060301023024 0ustar srudsrud{ "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-0.12.0/spec/mock_response/request_object/0000755000175100017510000000000012757060301022035 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/request_object/signed.jwt0000644000175100017510000000103512757060301024033 0ustar srudsrudeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJjbGllbnRfaWQiLCJyZXNwb25zZV90eXBlIjoidG9rZW4gaWRfdG9rZW4iLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLmNvbSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIiwic3RhdGUiOiJzdGF0ZTEyMzQiLCJub25jZSI6Im5vbmNlMTIzNCIsImRpc3BsYXkiOiJ0b3VjaCIsInByb21wdCI6Im5vbmUiLCJpZF90b2tlbiI6eyJjbGFpbXMiOnsiYWNyIjp7InZhbHVlcyI6WyIyIiwiMyIsIjQiXX19LCJtYXhfYWdlIjoxMH0sInVzZXJpbmZvIjp7ImNsYWltcyI6eyJuYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6ZmFsc2V9fX19.MLTDQVPdhAdkJhboM06IRtjHJrvamJ_H2vFGRupXmTAopenid-connect-0.12.0/spec/mock_response/id_token.json0000644000175100017510000000022512757060301021505 0ustar srudsrud{ "iss": "https://server.example.com", "aud": "client_id", "user_id": "user_id", "nonce": "nonce", "exp": 1303852880, "iat": 1303850880 }openid-connect-0.12.0/spec/mock_response/access_token/0000755000175100017510000000000012757060301021460 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/access_token/bearer.json0000644000175100017510000000016412757060301023614 0ustar srudsrud{ "access_token":"access_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid-connect-0.12.0/spec/mock_response/access_token/mac.json0000644000175100017510000000026012757060301023111 0ustar srudsrud{ "token_type": "mac", "mac_algorithm": "hmac-sha-256", "expires_in": 3600, "mac_key": "secret", "refresh_token": "refresh_token", "access_token": "access_token" } openid-connect-0.12.0/spec/mock_response/access_token/bearer_with_id_token.json0000644000175100017510000000021512757060301026520 0ustar srudsrud{ "access_token":"access_token", "id_token":"id_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid-connect-0.12.0/spec/mock_response/access_token/invalid_json.json0000644000175100017510000000003112757060301025024 0ustar srudsrudaccess_token=access_tokenopenid-connect-0.12.0/spec/mock_response/errors/0000755000175100017510000000000012757060301020333 5ustar srudsrudopenid-connect-0.12.0/spec/mock_response/errors/insufficient_scope.json0000644000175100017510000000004312757060301025102 0ustar srudsrud{ "error": "insufficient_scope" }openid-connect-0.12.0/spec/mock_response/errors/unknown.json0000644000175100017510000000002412757060301022721 0ustar srudsrudFuckin Unknown Erroropenid-connect-0.12.0/spec/mock_response/errors/invalid_request.json0000644000175100017510000000004012757060301024416 0ustar srudsrud{ "error": "invalid_request" }openid-connect-0.12.0/spec/mock_response/errors/invalid_access_token.json0000644000175100017510000000004512757060301025374 0ustar srudsrud{ "error": "invalid_access_token" }openid-connect-0.12.0/spec/openid_connect_spec.rb0000644000175100017510000000332112757060301020475 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/0000755000175100017510000000000012757060301017137 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/discovery/0000755000175100017510000000000012757060301021146 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/discovery/provider/0000755000175100017510000000000012757060301023000 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/discovery/provider/config/0000755000175100017510000000000012757060301024245 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/discovery/provider/config/resource_spec.rb0000644000175100017510000000071212757060301027433 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/discovery/provider/config/response_spec.rb0000644000175100017510000000356112757060301027447 0ustar srudsrudrequire '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 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 endopenid-connect-0.12.0/spec/openid_connect/discovery/provider/config_spec.rb0000644000175100017510000000651712757060301025615 0ustar srudsrudrequire '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 context 'when response include invalid issuer' 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 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 endopenid-connect-0.12.0/spec/openid_connect/discovery/provider_spec.rb0000644000175100017510000000375512757060301024351 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/request_object_spec.rb0000644000175100017510000000652712757060301023526 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/client/0000755000175100017510000000000012757060301020415 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/client/registrar_spec.rb0000644000175100017510000001511212757060301023756 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/access_token_spec.rb0000644000175100017510000000602412757060301023141 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/exception_spec.rb0000644000175100017510000000122012757060301022467 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/response_object/0000755000175100017510000000000012757060301022323 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/response_object/user_info/0000755000175100017510000000000012757060301024314 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/response_object/user_info/address_spec.rb0000644000175100017510000000126512757060301027304 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/response_object/id_token_spec.rb0000644000175100017510000002643112757060301025464 0ustar srudsrudrequire '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, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash] } 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 = UrlSafeBase64.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 == UrlSafeBase64.encode64( OpenSSL::Digest::SHA256.digest('access_token')[0, 128 / 8] ) 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 == UrlSafeBase64.encode64( OpenSSL::Digest::SHA256.digest('authorization_code')[0, 128 / 8] ) 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 == UrlSafeBase64.encode64( OpenSSL::Digest::SHA256.digest('access_token')[0, 128 / 8] ) jwt[:c_hash].should == UrlSafeBase64.encode64( OpenSSL::Digest::SHA256.digest('authorization_code')[0, 128 / 8] ) 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 endopenid-connect-0.12.0/spec/openid_connect/response_object/user_info_spec.rb0000644000175100017510000000547512757060301025666 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/client_spec.rb0000644000175100017510000001050412757060301021754 0ustar srudsrudrequire '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(:response_type) { nil } let(:query) do params = { scope: scope, 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 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 end end endopenid-connect-0.12.0/spec/openid_connect/debugger/0000755000175100017510000000000012757060301020723 5ustar srudsrudopenid-connect-0.12.0/spec/openid_connect/debugger/request_filter_spec.rb0000644000175100017510000000206612757060301025323 0ustar srudsrudrequire '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-0.12.0/spec/openid_connect/connect_object_spec.rb0000644000175100017510000000507712757060301023466 0ustar srudsrudrequire '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-0.12.0/spec/spec_helper.rb0000644000175100017510000000046012757060301016766 0ustar srudsrudrequire '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'