openid_connect-2.3.1/0000755000004100000410000000000014723350730014540 5ustar www-datawww-dataopenid_connect-2.3.1/openid_connect.gemspec0000644000004100000410000000257314723350730021103 0ustar www-datawww-dataGem::Specification.new do |s| s.name = "openid_connect" s.version = File.read("VERSION") s.authors = ["nov matake"] s.email = ["nov@matake.jp"] s.homepage = "https://github.com/nov/openid_connect" s.summary = %q{OpenID Connect Server & Client Library} s.description = %q{OpenID Connect Server & Client Library} s.license = 'MIT' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_runtime_dependency "tzinfo" s.add_runtime_dependency "attr_required", ">= 1.0.0" s.add_runtime_dependency "activemodel" s.add_runtime_dependency "validate_url" s.add_runtime_dependency "email_validator" s.add_runtime_dependency "mail" s.add_runtime_dependency 'faraday', '~> 2.0' s.add_runtime_dependency 'faraday-follow_redirects' s.add_runtime_dependency "json-jwt", ">= 1.16" s.add_runtime_dependency "swd", "~> 2.0" s.add_runtime_dependency "webfinger", "~> 2.0" s.add_runtime_dependency "rack-oauth2", "~> 2.2" 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" s.add_development_dependency "rexml" end openid_connect-2.3.1/.gitignore0000644000004100000410000000024014723350730016524 0ustar www-datawww-data## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## PROJECT::GENERAL coverage* rdoc pkg Gemfile.lock ## PROJECT::SPECIFIC openid_connect-2.3.1/README.rdoc0000644000004100000410000000256414723350730016355 0ustar www-datawww-data= OpenIDConnect OpenID Connect Server & Client Library == Installation gem install openid_connect == Resources * View Source on GitHub (https://github.com/nov/openid_connect) * Report Issues on GitHub (https://github.com/nov/openid_connect/issues) * Subscribe Update Info (https://www.facebook.com/OpenIDConnect.rb) == Examples === Provider * Running on Heroku (https://connect-op.herokuapp.com) * Source on GitHub (https://github.com/nov/openid_connect_sample) * Simpler Version (https://github.com/nov/openid_connect_sample2) === Relying Party * Running on Heroku (https://connect-rp.herokuapp.com) * Source on GitHub (https://github.com/nov/openid_connect_sample_rp) There is also OpenID Foudation Certified RP implementation using this gem below. * Running on Heroku (https://connect-rp-certified.herokuapp.com) * Source on GitHub (https://github.com/nov/connect-rp-certified) == Note on Patches/Pull Requests * Fork the project. * Make your feature addition or bug fix. * Add tests for it. This is important so I don't break it in a future version unintentionally. * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) * Send me a pull request. Bonus points for topic branches. == Copyright Copyright (c) 2011 nov matake. See LICENSE for details. openid_connect-2.3.1/.github/0000755000004100000410000000000014723350730016100 5ustar www-datawww-dataopenid_connect-2.3.1/.github/workflows/0000755000004100000410000000000014723350730020135 5ustar www-datawww-dataopenid_connect-2.3.1/.github/workflows/spec.yml0000644000004100000410000000110314723350730021605 0ustar www-datawww-dataname: Spec on: push: branches: - main pull_request: permissions: contents: read jobs: spec: strategy: matrix: os: ['ubuntu-20.04', 'ubuntu-22.04'] ruby-version: ['3.1', '3.2', '3.3'] include: - os: 'ubuntu-20.04' ruby-version: '3.0' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - name: Run Specs run: bundle exec rake spec openid_connect-2.3.1/.github/FUNDING.yml0000644000004100000410000000007314723350730017715 0ustar www-datawww-data# These are supported funding model platforms github: nov openid_connect-2.3.1/lib/0000755000004100000410000000000014723350730015306 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect.rb0000644000004100000410000000472414723350730020631 0ustar www-datawww-datarequire 'json' require 'logger' require 'faraday' require 'faraday/follow_redirects' require 'swd' require 'webfinger' require 'active_model' require 'tzinfo' require 'validate_url' require 'email_validator/strict' require 'mail' require 'attr_required' require 'attr_optional' require 'json/jwt' require 'rack/oauth2' require 'rack/oauth2/server/authorize/error_with_connect_ext' require 'rack/oauth2/server/authorize/request_with_connect_params' require 'rack/oauth2/server/id_token_response' module OpenIDConnect VERSION = ::File.read( ::File.join(::File.dirname(__FILE__), '../VERSION') ).chomp def self.logger @@logger end def self.logger=(logger) @@logger = logger end self.logger = Logger.new(STDOUT) self.logger.progname = 'OpenIDConnect' @sub_protocols = [ SWD, WebFinger, Rack::OAuth2 ] def self.debugging? @@debugging end def self.debugging=(boolean) @sub_protocols.each do |klass| klass.debugging = boolean end @@debugging = boolean end def self.debug! @sub_protocols.each do |klass| klass.debug! end self.debugging = true end def self.debug(&block) sub_protocol_originals = @sub_protocols.inject({}) do |sub_protocol_originals, klass| sub_protocol_originals.merge!(klass => klass.debugging?) end original = self.debugging? debug! yield ensure @sub_protocols.each do |klass| klass.debugging = sub_protocol_originals[klass] end self.debugging = original end self.debugging = false def self.http_client Faraday.new(headers: {user_agent: "OpenIDConnect (#{VERSION})"}) do |faraday| faraday.request :url_encoded faraday.request :json faraday.response :json faraday.adapter Faraday.default_adapter http_config&.call(faraday) faraday.response :logger, OpenIDConnect.logger, {bodies: true} if debugging? end end def self.http_config(&block) @sub_protocols.each do |klass| klass.http_config(&block) unless klass.http_config end @@http_config ||= block end def self.validate_discovery_issuer=(boolean) @@validate_discovery_issuer = boolean end def self.validate_discovery_issuer @@validate_discovery_issuer end self.validate_discovery_issuer = true end require 'openid_connect/exception' require 'openid_connect/client' require 'openid_connect/access_token' require 'openid_connect/jwtnizable' require 'openid_connect/connect_object' require 'openid_connect/discovery' openid_connect-2.3.1/lib/openid_connect/0000755000004100000410000000000014723350730020275 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/client/0000755000004100000410000000000014723350730021553 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/client/registrar.rb0000644000004100000410000001251214723350730024103 0ustar www-datawww-datamodule OpenIDConnect class Client class Registrar include ActiveModel::Validations, AttrRequired, AttrOptional class RegistrationFailed < HttpError; end cattr_accessor :plural_uri_attributes, :metadata_attributes singular_uri_attributes = [ :logo_uri, :client_uri, :policy_uri, :tos_uri, :jwks_uri, :sector_identifier_uri, :initiate_login_uri ] singular_attributes = [ :application_type, :client_name, :jwks, :subject_type, :id_token_signed_response_alg, :id_token_encrypted_response_alg, :id_token_encrypted_response_enc, :userinfo_signed_response_alg, :userinfo_encrypted_response_alg, :userinfo_encrypted_response_enc, :request_object_signing_alg, :request_object_encryption_alg, :request_object_encryption_enc, :token_endpoint_auth_method, :token_endpoint_auth_signing_alg, :default_max_age, :require_auth_time ] + singular_uri_attributes self.plural_uri_attributes = [ :redirect_uris, :request_uris ] plural_attributes = [ :response_types, :grant_types, :contacts, :default_acr_values, ] + plural_uri_attributes self.metadata_attributes = singular_attributes + plural_attributes required_metadata_attributes = [ :redirect_uris ] attr_required :endpoint attr_optional :initial_access_token attr_required(*required_metadata_attributes) attr_optional(*(metadata_attributes - required_metadata_attributes)) validates(*required_attributes, presence: true) validates :sector_identifier_uri, presence: {if: :sector_identifier_required?} validates(*singular_uri_attributes, url: true, allow_nil: true) validate :validate_plural_uri_attributes validate :validate_contacts def initialize(endpoint, attributes = {}) self.endpoint = endpoint self.initial_access_token = attributes[:initial_access_token] self.class.metadata_attributes.each do |_attr_| self.send "#{_attr_}=", attributes[_attr_] end end def sector_identifier if valid_uri?(sector_identifier_uri) URI.parse(sector_identifier_uri).host else hosts = redirect_uris.collect do |redirect_uri| if valid_uri?(redirect_uri, nil) URI.parse(redirect_uri).host else nil end end.compact.uniq if hosts.size == 1 hosts.first else nil end end end def as_json(options = {}) validate! self.class.metadata_attributes.inject({}) do |hash, _attr_| value = self.send _attr_ hash.merge! _attr_ => value unless value.nil? hash end end def register! handle_response do http_client.post endpoint, to_json, 'Content-Type' => 'application/json' end end def read # TODO: Do we want this feature even if we don't have rotate secret nor update metadata support? end def validate! valid? or raise ValidationFailed.new(self) end private def sector_identifier_required? subject_type.to_s == 'pairwise' && sector_identifier.blank? end def valid_uri?(uri, schemes = ['http', 'https']) # NOTE: specify nil for schemes to allow any schemes URI::DEFAULT_PARSER.make_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 = 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-2.3.1/lib/openid_connect/discovery.rb0000644000004100000410000000026614723350730022635 0ustar www-datawww-datamodule OpenIDConnect module Discovery class InvalidIdentifier < Exception; end class DiscoveryFailed < Exception; end end end require 'openid_connect/discovery/provider'openid_connect-2.3.1/lib/openid_connect/access_token/0000755000004100000410000000000014723350730022736 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/access_token/mtls.rb0000644000004100000410000000044414723350730024244 0ustar www-datawww-datamodule OpenIDConnect class AccessToken::MTLS < AccessToken def initialize(attributes = {}) super http_client.ssl.client_key = attributes[:private_key] || client.private_key http_client.ssl.client_cert = attributes[:certificate] || client.certificate end end endopenid_connect-2.3.1/lib/openid_connect/discovery/0000755000004100000410000000000014723350730022304 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/discovery/provider.rb0000644000004100000410000000141514723350730024464 0ustar www-datawww-datamodule OpenIDConnect module Discovery module Provider module Issuer REL_VALUE = 'http://openid.net/specs/connect/1.0/issuer' def issuer self.link_for(REL_VALUE)[:href] end end def self.discover!(identifier) resource = case identifier when /^acct:/, /https?:\/\// identifier when /@/ "acct:#{identifier}" else "https://#{identifier}" end response = WebFinger.discover!( resource, rel: Issuer::REL_VALUE ) response.extend Issuer response rescue WebFinger::Exception => e raise DiscoveryFailed.new(e.message) end end end end require 'openid_connect/discovery/provider/config'openid_connect-2.3.1/lib/openid_connect/discovery/provider/0000755000004100000410000000000014723350730024136 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/discovery/provider/config.rb0000644000004100000410000000113214723350730025725 0ustar www-datawww-datamodule OpenIDConnect module Discovery module Provider class Config def self.discover!(identifier, cache_options = {}) uri = URI.parse(identifier) Resource.new(uri).discover!(cache_options).tap do |response| response.expected_issuer = identifier response.validate! end rescue SWD::Exception, ValidationFailed => e raise DiscoveryFailed.new(e.message) end end end end end require 'openid_connect/discovery/provider/config/resource' require 'openid_connect/discovery/provider/config/response'openid_connect-2.3.1/lib/openid_connect/discovery/provider/config/0000755000004100000410000000000014723350730025403 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/discovery/provider/config/resource.rb0000644000004100000410000000170714723350730027564 0ustar www-datawww-datarequire "openssl" module OpenIDConnect module Discovery module Provider class Config class Resource < SWD::Resource undef_required_attributes :principal, :service class Expired < SWD::Resource::Expired; end def initialize(uri) @host = uri.host @port = uri.port unless [80, 443].include?(uri.port) @path = File.join uri.path, '.well-known/openid-configuration' attr_missing! end def endpoint SWD.url_builder.build [nil, host, port, path, nil, nil] rescue URI::Error => e raise SWD::Exception.new(e.message) end private def to_response_object(hash) Response.new(hash) end def cache_key sha256 = OpenSSL::Digest::SHA256.hexdigest host "swd:resource:opneid-conf:#{sha256}" end end end end end endopenid_connect-2.3.1/lib/openid_connect/discovery/provider/config/response.rb0000644000004100000410000000715314723350730027574 0ustar www-datawww-datamodule OpenIDConnect module Discovery module Provider class Config class Response include ActiveModel::Validations, AttrRequired, AttrOptional cattr_accessor :metadata_attributes attr_reader :raw attr_accessor :expected_issuer uri_attributes = { required: [ :issuer, :authorization_endpoint, :jwks_uri ], optional: [ :token_endpoint, :userinfo_endpoint, :registration_endpoint, :end_session_endpoint, :service_documentation, :check_session_iframe, :op_policy_uri, :op_tos_uri ] } attr_required(*(uri_attributes[:required] + [ :response_types_supported, :subject_types_supported, :id_token_signing_alg_values_supported ])) attr_optional(*(uri_attributes[:optional] + [ :scopes_supported, :response_modes_supported, :grant_types_supported, :acr_values_supported, :id_token_encryption_alg_values_supported, :id_token_encryption_enc_values_supported, :userinfo_signing_alg_values_supported, :userinfo_encryption_alg_values_supported, :userinfo_encryption_enc_values_supported, :request_object_signing_alg_values_supported, :request_object_encryption_alg_values_supported, :request_object_encryption_enc_values_supported, :token_endpoint_auth_methods_supported, :token_endpoint_auth_signing_alg_values_supported, :display_values_supported, :claim_types_supported, :claims_supported, :claims_locales_supported, :ui_locales_supported, :claims_parameter_supported, :request_parameter_supported, :request_uri_parameter_supported, :require_request_uri_registration ])) validates(*required_attributes, presence: true) validates(*uri_attributes.values.flatten, url: true, allow_nil: true) validates :issuer, with: :validate_issuer_matching def initialize(hash) (required_attributes + optional_attributes).each do |key| self.send "#{key}=", hash[key] end @raw = hash end def as_json(options = {}) validate! (required_attributes + optional_attributes).inject({}) do |hash, _attr_| value = self.send _attr_ hash.merge! _attr_ => value unless value.nil? hash end end def validate! valid? or raise ValidationFailed.new(self) end def jwks @jwks ||= OpenIDConnect.http_client.get(jwks_uri).body.with_indifferent_access JSON::JWK::Set.new @jwks[:keys] end def jwk(kid) @jwks ||= {} @jwks[kid] ||= JSON::JWK::Set::Fetcher.fetch(jwks_uri, kid: kid) end def public_keys @public_keys ||= jwks.collect(&:to_key) end private def validate_issuer_matching if expected_issuer.present? && issuer != expected_issuer if OpenIDConnect.validate_discovery_issuer errors.add :issuer, 'mismatch' else OpenIDConnect.logger.warn 'ignoring issuer mismach.' end end end end end end end end openid_connect-2.3.1/lib/openid_connect/response_object.rb0000644000004100000410000000023614723350730024007 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject < ConnectObject end end Dir[File.dirname(__FILE__) + '/response_object/*.rb'].each do |file| require file endopenid_connect-2.3.1/lib/openid_connect/connect_object.rb0000644000004100000410000000246314723350730023606 0ustar www-datawww-datamodule OpenIDConnect class ConnectObject include ActiveModel::Validations, AttrRequired, AttrOptional attr_accessor :raw_attributes def initialize(attributes = {}) all_attributes.each do |_attr_| self.send :"#{_attr_}=", attributes[_attr_] end self.raw_attributes = attributes attr_missing! end def self.all_attributes required_attributes + optional_attributes end def all_attributes self.class.all_attributes end def require_at_least_one_attributes all_blank = all_attributes.all? do |key| self.send(key).blank? end errors.add :base, 'At least one attribute is required' if all_blank end def as_json(options = {}) options ||= {} # options can be nil when to_json is called without options validate! unless options[:skip_validation] all_attributes.inject({}) do |hash, _attr_| value = self.send(_attr_) hash.merge! _attr_ => case value when ConnectObject value.as_json options else value end end.delete_if do |key, value| value.nil? end end def validate! valid? or raise ValidationFailed.new(self) end end end require 'openid_connect/request_object' require 'openid_connect/response_object'openid_connect-2.3.1/lib/openid_connect/access_token.rb0000644000004100000410000000206014723350730023261 0ustar www-datawww-datamodule OpenIDConnect class AccessToken < Rack::OAuth2::AccessToken::Bearer attr_required :client attr_optional :id_token def initialize(attributes = {}) super @token_type = :bearer end def userinfo!(params = {}) hash = resource_request do get client.userinfo_uri, params end ResponseObject::UserInfo.new hash end def to_mtls(attributes = {}) (required_attributes + optional_attributes).each do |key| attributes[key] = self.send(key) end MTLS.new attributes end private def resource_request res = yield case res.status when 200 res.body.with_indifferent_access when 400 raise BadRequest.new('API Access Failed', 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 end require 'openid_connect/access_token/mtls' openid_connect-2.3.1/lib/openid_connect/jwtnizable.rb0000644000004100000410000000053514723350730022776 0ustar www-datawww-datamodule OpenIDConnect module JWTnizable def to_jwt(key, algorithm = :RS256, &block) as_jwt(key, algorithm, &block).to_s end def as_jwt(key, algorithm = :RS256, &block) token = JSON::JWT.new as_json yield token if block_given? token = token.sign key, algorithm if algorithm != :none token end end endopenid_connect-2.3.1/lib/openid_connect/request_object/0000755000004100000410000000000014723350730023313 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/request_object/claimable.rb0000644000004100000410000000236014723350730025552 0ustar www-datawww-datamodule OpenIDConnect class RequestObject module Claimable def self.included(klass) klass.send :attr_optional, :claims end def initialize(attributes = {}) super if claims.present? _claims_ = {} claims.each do |key, value| _claims_[key] = case value when :optional, :voluntary { essential: false } when :required, :essential { essential: true } else value end end self.claims = _claims_.with_indifferent_access end end def as_json(options = {}) keys = claims.try(:keys) hash = super Array(keys).each do |key| hash[:claims][key] ||= nil end hash end def required?(claim) accessible?(claim) && claims[claim].is_a?(Hash) && claims[claim][:essential] end alias_method :essential?, :required? def optional?(claim) accessible?(claim) && !required?(claim) end alias_method :voluntary?, :optional? def accessible?(claim) claims.try(:include?, claim) end end end endopenid_connect-2.3.1/lib/openid_connect/request_object/user_info.rb0000644000004100000410000000016714723350730025635 0ustar www-datawww-datamodule OpenIDConnect class RequestObject class UserInfo < ConnectObject include Claimable end end endopenid_connect-2.3.1/lib/openid_connect/request_object/id_token.rb0000644000004100000410000000022314723350730025431 0ustar www-datawww-datamodule OpenIDConnect class RequestObject class IdToken < ConnectObject include Claimable attr_optional :max_age end end endopenid_connect-2.3.1/lib/openid_connect/response_object/0000755000004100000410000000000014723350730023461 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/response_object/user_info/0000755000004100000410000000000014723350730025452 5ustar www-datawww-dataopenid_connect-2.3.1/lib/openid_connect/response_object/user_info/address.rb0000644000004100000410000000041614723350730027425 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject class UserInfo class Address < ConnectObject attr_optional :formatted, :street_address, :locality, :region, :postal_code, :country validate :require_at_least_one_attributes end end end endopenid_connect-2.3.1/lib/openid_connect/response_object/user_info.rb0000644000004100000410000000365714723350730026012 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject class UserInfo < ConnectObject attr_optional( :sub, :name, :given_name, :family_name, :middle_name, :nickname, :preferred_username, :profile, :picture, :website, :email, :email_verified, :gender, :birthdate, :zoneinfo, :locale, :phone_number, :phone_number_verified, :address, :updated_at ) alias_method :subject, :sub alias_method :subject=, :sub= validates :email_verified, :phone_number_verified, allow_nil: true, inclusion: {in: [true, false]} validates :zoneinfo, allow_nil: true, inclusion: {in: TZInfo::TimezoneProxy.all.collect(&:name)} validates :profile, :picture, :website, allow_nil: true, url: true validates :email, allow_nil: true, email: true validates :updated_at, allow_nil: true, numericality: {only_integer: true} validate :validate_address validate :require_at_least_one_attributes # TODO: validate locale def initialize(attributes = {}) super (all_attributes - [:email_verified, :phone_number_verified, :address, :updated_at]).each do |key| self.send "#{key}=", self.send(key).try(:to_s) end self.updated_at = updated_at.try(:to_i) end def validate_address errors.add :address, address.errors.full_messages.join(', ') if address.present? && !address.valid? end undef :address= def address=(hash_or_address) @address = case hash_or_address when Hash Address.new hash_or_address when Address hash_or_address end end end end end Dir[File.dirname(__FILE__) + '/user_info/*.rb'].each do |file| require file end openid_connect-2.3.1/lib/openid_connect/response_object/id_token.rb0000644000004100000410000000661114723350730025606 0ustar www-datawww-datamodule OpenIDConnect class ResponseObject class IdToken < ConnectObject class InvalidToken < Exception; end class ExpiredToken < InvalidToken; end class InvalidIssuer < InvalidToken; end class InvalidNonce < InvalidToken; end class InvalidAudience < InvalidToken; end attr_required :iss, :sub, :aud, :exp, :iat attr_optional :acr, :amr, :azp, :jti, :sid, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash, :s_hash attr_accessor :access_token, :code, :state alias_method :subject, :sub alias_method :subject=, :sub= def initialize(attributes = {}) super (all_attributes - [:aud, :exp, :iat, :auth_time, :sub_jwk]).each do |key| self.send "#{key}=", self.send(key).try(:to_s) end self.auth_time = auth_time.to_i unless auth_time.nil? end def verify!(expected = {}) raise ExpiredToken.new('Invalid ID token: Expired token') unless exp.to_i > Time.now.to_i raise InvalidIssuer.new('Invalid ID token: Issuer does not match') unless iss == expected[:issuer] raise InvalidNonce.new('Invalid ID Token: Nonce does not match') unless nonce == expected[:nonce] # aud(ience) can be a string or an array of strings unless Array(aud).include?(expected[:audience] || expected[:client_id]) raise InvalidAudience.new('Invalid ID token: Audience does not match') end true end include JWTnizable def to_jwt(key, algorithm = :RS256, &block) hash_length = algorithm.to_s[2, 3].to_i if access_token token = case access_token when Rack::OAuth2::AccessToken access_token.access_token else access_token end self.at_hash = left_half_hash_of token, hash_length end if code self.c_hash = left_half_hash_of code, hash_length end if state self.s_hash = left_half_hash_of state, hash_length end super end private def left_half_hash_of(string, hash_length) digest = OpenSSL::Digest.new("SHA#{hash_length}").digest string Base64.urlsafe_encode64 digest[0, hash_length / (2 * 8)], padding: false end class << self def decode(jwt_string, key_or_config) case key_or_config when :self_issued decode_self_issued jwt_string when OpenIDConnect::Discovery::Provider::Config::Response jwt = JSON::JWT.decode jwt_string, :skip_verification jwt.verify! key_or_config.jwk(jwt.kid) new jwt else new JSON::JWT.decode jwt_string, key_or_config end end def decode_self_issued(jwt_string) jwt = JSON::JWT.decode jwt_string, :skip_verification jwk = JSON::JWK.new jwt[:sub_jwk] raise InvalidToken.new('Missing sub_jwk') if jwk.blank? raise InvalidToken.new('Invalid subject') unless jwt[:sub] == jwk.thumbprint jwt.verify! jwk new jwt end def self_issued(attributes = {}) attributes[:sub_jwk] ||= JSON::JWK.new attributes.delete(:public_key) _attributes_ = { iss: 'https://self-issued.me', sub: JSON::JWK.new(attributes[:sub_jwk]).thumbprint }.merge(attributes) new _attributes_ end end end end end openid_connect-2.3.1/lib/openid_connect/exception.rb0000644000004100000410000000151614723350730022623 0ustar www-datawww-datamodule OpenIDConnect class Exception < StandardError; end class ValidationFailed < Exception attr_reader :object def initialize(object) super object.errors.full_messages.to_sentence @object = object end end class HttpError < Exception attr_accessor :status, :response def initialize(status, message = nil, response = nil) super message @status = status @response = response end end class BadRequest < HttpError def initialize(message = nil, response = nil) super 400, message, response end end class Unauthorized < HttpError def initialize(message = nil, response = nil) super 401, message, response end end class Forbidden < HttpError def initialize(message = nil, response = nil) super 403, message, response end end endopenid_connect-2.3.1/lib/openid_connect/client.rb0000644000004100000410000000214314723350730022100 0ustar www-datawww-datamodule OpenIDConnect class Client < Rack::OAuth2::Client attr_optional :userinfo_endpoint, :expires_in def initialize(attributes = {}) super attributes self.userinfo_endpoint ||= '/userinfo' end def authorization_uri(params = {}) params[:scope] = setup_required_scope params[:scope] params[:prompt] = Array(params[:prompt]).join(' ') super end def userinfo_uri absolute_uri_for userinfo_endpoint end private def setup_required_scope(scopes) _scopes_ = Array(scopes).join(' ').split(' ') _scopes_ << 'openid' unless _scopes_.include?('openid') _scopes_ end def handle_success_response(response) token_hash = response.body.with_indifferent_access token_type = (@forced_token_type || token_hash[:token_type]).try(:downcase) case token_type when 'bearer' AccessToken.new token_hash.merge(client: self) else raise Exception.new("Unexpected Token Type: #{token_type}") end end end end Dir[File.dirname(__FILE__) + '/client/*.rb'].each do |file| require file end openid_connect-2.3.1/lib/openid_connect/request_object.rb0000644000004100000410000000200014723350730023630 0ustar www-datawww-datamodule OpenIDConnect class RequestObject < ConnectObject include JWTnizable attr_optional :client_id, :response_type, :redirect_uri, :scope, :state, :nonce, :display, :prompt, :userinfo, :id_token validate :require_at_least_one_attributes undef :id_token= def id_token=(attributes = {}) @id_token = IdToken.new(attributes) if attributes.present? end undef :userinfo= def userinfo=(attributes = {}) @userinfo = UserInfo.new(attributes) if attributes.present? end def as_json(options = {}) super.with_indifferent_access end class << self def decode(jwt_string, key = nil) new JSON::JWT.decode(jwt_string, key) end def fetch(request_uri, key = nil) jwt_string = OpenIDConnect.http_client.get(request_uri).body 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-2.3.1/lib/rack/0000755000004100000410000000000014723350730016226 5ustar www-datawww-dataopenid_connect-2.3.1/lib/rack/oauth2/0000755000004100000410000000000014723350730017430 5ustar www-datawww-dataopenid_connect-2.3.1/lib/rack/oauth2/server/0000755000004100000410000000000014723350730020736 5ustar www-datawww-dataopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/0000755000004100000410000000000014723350730022750 5ustar www-datawww-dataopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/request_with_connect_params.rb0000644000004100000410000000133214723350730031073 0ustar www-datawww-dataclass Rack::OAuth2::Server::Authorize module RequestWithConnectParams CONNECT_EXT_PARAMS = [ :nonce, :display, :prompt, :max_age, :ui_locales, :claims_locales, :id_token_hint, :login_hint, :acr_values, :claims, :request, :request_uri ] def self.prepended(klass) klass.send :attr_optional, *CONNECT_EXT_PARAMS end def initialize(env) super CONNECT_EXT_PARAMS.each do |attribute| self.send :"#{attribute}=", params[attribute.to_s] end self.prompt = Array(prompt.to_s.split(' ')) self.max_age = max_age.try(:to_i) end def openid_connect_request? scope.include?('openid') end end Request.send :prepend, RequestWithConnectParams endopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/error_with_connect_ext.rb0000644000004100000410000000266414723350730030062 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module ErrorWithConnectExt DEFAULT_DESCRIPTION = { invalid_redirect_uri: 'The redirect_uri in the request does not match any of pre-registered redirect_uris.', interaction_required: 'End-User interaction required.', login_required: 'End-User authentication required.', session_selection_required: 'The End-User is required to select a session at the Authorization Server.', consent_required: 'End-User consent required.', invalid_request_uri: 'The request_uri in the request returns an error or invalid data.', invalid_openid_request_object: 'The request parameter contains an invalid OpenID Request Object.' } def self.included(klass) DEFAULT_DESCRIPTION.each do |error, default_description| # NOTE: # Connect Message spec doesn't say anything about HTTP status code for each error code. # It probably means "use 400". error_method = :bad_request! klass.class_eval <<-ERROR def #{error}!(description = "#{default_description}", options = {}) #{error_method} :#{error}, description, options end ERROR end end end Request.send :include, ErrorWithConnectExt end end end endopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/extension/0000755000004100000410000000000014723350730024764 5ustar www-datawww-dataopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/extension/code_and_id_token_and_token.rb0000644000004100000410000000170414723350730032745 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class CodeAndIdTokenAndToken < Abstract::Handler class << self def response_type_for?(response_type) response_type.split.sort == ['code', 'id_token', 'token'] end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Extension::CodeAndToken::Request def initialize(env) super @response_type = [:code, :id_token, :token] attr_missing! end end class Response < Authorize::Extension::CodeAndToken::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/extension/code_and_id_token.rb0000644000004100000410000000173214723350730030724 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class CodeAndIdToken < Abstract::Handler class << self def response_type_for?(response_type) response_type.split.sort == ['code', 'id_token'] end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Code::Request def initialize(env) super @response_type = [:code, :id_token] attr_missing! end def error_params_location :fragment end end class Response < Authorize::Code::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/extension/id_token_and_token.rb0000644000004100000410000000161214723350730031127 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class IdTokenAndToken < Abstract::Handler class << self def response_type_for?(response_type) response_type.split.sort == ['id_token', 'token'] end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Token::Request def initialize(env) super @response_type = [:id_token, :token] attr_missing! end end class Response < Authorize::Token::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-2.3.1/lib/rack/oauth2/server/authorize/extension/id_token.rb0000644000004100000410000000165114723350730027110 0ustar www-datawww-datamodule Rack module OAuth2 module Server class Authorize module Extension class IdToken < Abstract::Handler class << self def response_type_for?(response_type) response_type == 'id_token' end end def _call(env) @request = Request.new env @response = Response.new request super end class Request < Authorize::Request def initialize(env) super @response_type = :id_token attr_missing! end def error_params_location :fragment end end class Response < Authorize::Response include IdTokenResponse attr_required :id_token end end end end end end endopenid_connect-2.3.1/lib/rack/oauth2/server/id_token_response.rb0000644000004100000410000000124014723350730024772 0ustar www-datawww-datamodule Rack::OAuth2::Server module IdTokenResponse def self.prepended(klass) klass.send :attr_optional, :id_token end def protocol_params_location :fragment end def protocol_params super.merge( id_token: id_token ) end end Token::Response.send :prepend, IdTokenResponse end require 'rack/oauth2/server/authorize/extension/code_and_id_token' require 'rack/oauth2/server/authorize/extension/code_and_token' require 'rack/oauth2/server/authorize/extension/code_and_id_token_and_token' require 'rack/oauth2/server/authorize/extension/id_token' require 'rack/oauth2/server/authorize/extension/id_token_and_token'openid_connect-2.3.1/spec/0000755000004100000410000000000014723350730015472 5ustar www-datawww-dataopenid_connect-2.3.1/spec/spec_helper.rb0000644000004100000410000000046014723350730020310 0ustar www-datawww-datarequire 'simplecov' SimpleCov.start do add_filter 'spec' end require 'rspec' require 'rspec/its' require 'openid_connect' RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = [:should, :expect] end end require 'helpers/crypto_spec_helper' require 'helpers/webmock_helper'openid_connect-2.3.1/spec/helpers/0000755000004100000410000000000014723350730017134 5ustar www-datawww-dataopenid_connect-2.3.1/spec/helpers/webmock_helper.rb0000644000004100000410000000224414723350730022451 0ustar www-datawww-datarequire 'webmock/rspec' module WebMockHelper def mock_json(method, endpoint, response_file, options = {}) stub_request(method, endpoint).with( request_for(method, options) ).to_return( response_for(response_file, options) ) result = yield a_request(method, endpoint).with( request_for(method, options) ).should have_been_made.once result end private def request_for(method, options = {}) request = {} case method when :post, :put request[:body] = options[:params] else request[:query] = options[:params] end if options[:request_header] request[:headers] = options[:request_header] end request end def response_for(response_file, options = {}) response = {} format = options[:format] || :json if format == :json response[:headers] = { 'Content-Type': 'application/json' } end response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_response', "#{response_file}.#{format}")) if options[:status] response[:status] = options[:status] end response end end include WebMockHelper WebMock.disable_net_connect!openid_connect-2.3.1/spec/helpers/crypto_spec_helper.rb0000644000004100000410000000117014723350730023351 0ustar www-datawww-datamodule CryptoSpecHelper def rsa_key @rsa_key ||= OpenSSL::PKey::RSA.generate 2048 end def public_key @public_key ||= rsa_key.public_key end def private_key @private_key ||= OpenSSL::PKey::RSA.new rsa_key.export(OpenSSL::Cipher.new('DES-EDE3-CBC'), 'pass-phrase'), 'pass-phrase' end def ec_key @ec_key ||= OpenSSL::PKey::EC.new('prime256v1').generate_key end def ec_public_key unless @ec_public_key @ec_public_key = OpenSSL::PKey::EC.new ec_key @ec_public_key.private_key = nil end @ec_public_key end def ec_private_key ec_key end end include CryptoSpecHelperopenid_connect-2.3.1/spec/openid_connect_spec.rb0000644000004100000410000000322114723350730022016 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect do after { OpenIDConnect.debugging = false } its(:logger) { should be_a Logger } its(:debugging?) { should == false } describe '.debug!' do before { OpenIDConnect.debug! } its(:debugging?) { should == true } end describe '.debug' do it 'should enable debugging within given block' do OpenIDConnect.debug do SWD.debugging?.should == true WebFinger.debugging?.should == true Rack::OAuth2.debugging?.should == true OpenIDConnect.debugging?.should == true end SWD.debugging?.should == false Rack::OAuth2.debugging?.should == false OpenIDConnect.debugging?.should == false end it 'should not force disable debugging' do SWD.debug! WebFinger.debug! Rack::OAuth2.debug! OpenIDConnect.debug! OpenIDConnect.debug do SWD.debugging?.should == true WebFinger.debugging?.should == true Rack::OAuth2.debugging?.should == true OpenIDConnect.debugging?.should == true end SWD.debugging?.should == true WebFinger.debugging?.should == true Rack::OAuth2.debugging?.should == true OpenIDConnect.debugging?.should == true end end describe '.http_client' do context 'with http_config' do before do OpenIDConnect.http_config do |config| config.ssl.verify = false 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.verify.should be_falsy end end end end endopenid_connect-2.3.1/spec/openid_connect/0000755000004100000410000000000014723350730020461 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/client/0000755000004100000410000000000014723350730021737 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/client/registrar_spec.rb0000644000004100000410000001512314723350730025302 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Client::Registrar do subject { instance } let(:attributes) { minimum_attributes } let(:minimum_attributes) do { redirect_uris: ['https://client.example.com/callback'] } end let(:instance) { OpenIDConnect::Client::Registrar.new(endpoint, attributes) } let(:endpoint) { 'https://server.example.com/clients' } context 'when endpoint given' do context 'when required attributes given' do let(:attributes) do minimum_attributes end it { should be_valid } end context 'otherwise' do let(:instance) { OpenIDConnect::Client::Registrar.new(endpoint) } it { should_not be_valid } end end context 'otherwise' do let(:endpoint) { '' } it { should_not be_valid } end describe '#initialize' do it 'creates attribute writers for all attributes' do described_class.metadata_attributes.each do |attr| expect(subject).to respond_to("#{attr}=") end end end describe '#sector_identifier' do context 'when sector_identifier_uri given' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'https://client2.example.com/sector_identifier.json' ) end its(:sector_identifier) { should == 'client2.example.com' } context 'when sector_identifier_uri is invalid URI' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'invalid' ) end it { should_not be_valid } end end context 'otherwise' do let(:attributes) do minimum_attributes.merge( redirect_uris: redirect_uris ) end context 'when redirect_uris includes only one host' do let(:redirect_uris) do [ 'https://client.example.com/callback/op1', 'https://client.example.com/callback/op2' ] end its(:sector_identifier) { should == 'client.example.com' } end context 'when redirect_uris includes multiple hosts' do let(:redirect_uris) do [ 'https://client1.example.com/callback', 'https://client2.example.com/callback' ] end its(:sector_identifier) { should be_nil } context 'when subject_type=pairwise' do let(:attributes) do minimum_attributes.merge( redirect_uris: redirect_uris, subject_type: :pairwise ) end it { should_not be_valid } end end context 'when redirect_uris includes invalid URL' do let(:redirect_uris) do [ 'invalid' ] end its(:sector_identifier) { should be_nil } end end end describe '#redirect_uris' do let(:base_url) { 'http://client.example.com/callback' } let(:attributes) { minimum_attributes.merge(redirect_uris: [redirect_uri]) } context 'when query included' do let(:redirect_uri) { [base_url, '?foo=bar'].join } it { should be_valid } its(:redirect_uris) { should == [redirect_uri] } end context 'when fragment included' do let(:redirect_uri) { [base_url, '#foo=bar'].join } it { should be_valid } end end describe '#contacts' do context 'when contacts given' do let(:attributes) do minimum_attributes.merge( contacts: contacts ) end context 'when invalid email included' do let(:contacts) do [ 'invalid', 'nov@matake.jp' ] end it { should_not be_valid } end context 'when localhost address included' do let(:contacts) do [ 'nov@localhost', 'nov@matake.jp' ] end it { should_not be_valid } end context 'otherwise' do let(:contacts) do ['nov@matake.jp'] end it { should be_valid } end end end describe '#as_json' do context 'when valid' do its(:as_json) do should == minimum_attributes end end context 'otherwise' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'invalid' ) end it do expect do instance.as_json end.to raise_error OpenIDConnect::ValidationFailed end end end describe '#register!' do it 'should return OpenIDConnect::Client' do client = mock_json :post, endpoint, 'client/registered', params: minimum_attributes do instance.register! end client.should be_instance_of OpenIDConnect::Client client.identifier.should == 'client.example.com' client.secret.should == 'client_secret' client.expires_in.should == 3600 end context 'when failed' do it 'should raise OpenIDConnect::Client::Registrar::RegistrationFailed' do mock_json :post, endpoint, 'errors/unknown', params: minimum_attributes, status: 400 do expect do instance.register! end.to raise_error OpenIDConnect::Client::Registrar::RegistrationFailed end end end end describe '#validate!' do context 'when valid' do it do expect do instance.validate! end.not_to raise_error { |e| e.should be_a OpenIDConnect::ValidationFailed } end end context 'otherwise' do let(:attributes) do minimum_attributes.merge( sector_identifier_uri: 'invalid' ) end it do expect do instance.validate! end.to raise_error OpenIDConnect::ValidationFailed end end end describe 'http_client' do subject { instance.send(:http_client) } context 'when initial_access_token given' do let(:attributes) do minimum_attributes.merge( initial_access_token: initial_access_token ) end context 'when Rack::OAuth2::AccessToken::Bearer given' do let(:initial_access_token) do Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') end it { should be_instance_of Rack::OAuth2::AccessToken::Bearer } its(:access_token) { should == 'access_token' } end context 'otherwise' do let(:initial_access_token) { 'access_token' } it { should be_instance_of Rack::OAuth2::AccessToken::Bearer } its(:access_token) { should == 'access_token' } end end context 'otherwise' do it { should be_instance_of Faraday::Connection } end end end openid_connect-2.3.1/spec/openid_connect/client_spec.rb0000644000004100000410000001155214723350730023302 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Client do subject { client } let(:client) { OpenIDConnect::Client.new attributes } let(:attributes) { required_attributes } let :required_attributes do { identifier: 'client_id' } end describe 'endpoints' do context 'when host info is given' do let :attributes do required_attributes.merge( host: 'server.example.com' ) end its(:authorization_uri) { should include 'https://server.example.com/oauth2/authorize' } its(:authorization_uri) { should include 'scope=openid' } its(:userinfo_uri) { should == 'https://server.example.com/userinfo' } end context 'otherwise' do [:authorization_uri, :userinfo_uri].each do |endpoint| describe endpoint do it do expect { client.send endpoint }.to raise_error 'No Host Info' end end end end end describe '#authorization_uri' do let(:scope) { nil } let(:prompt) { nil } let(:response_type) { nil } let(:query) do params = { scope: scope, prompt: prompt, response_type: response_type }.reject do |k,v| v.blank? end query = URI.parse(client.authorization_uri params).query Rack::Utils.parse_query(query).with_indifferent_access end let :attributes do required_attributes.merge( host: 'server.example.com' ) end describe 'response_type' do subject do query[:response_type] end it { should == 'code' } context 'when response_type is given' do context 'when array given' do let(:response_type) { [:code, :token] } it { should == 'code token' } end context 'when scalar given' do let(:response_type) { :token } it { should == 'token' } end end context 'as default' do it { should == 'code' } end end describe 'scope' do subject do query[:scope] end context 'when scope is given' do context 'when openid scope is included' do let(:scope) { [:openid, :email] } it { should == 'openid email' } end context 'otherwise' do let(:scope) { :email } it { should == 'email openid' } end end context 'as default' do it { should == 'openid' } end end describe 'prompt' do subject do query[:prompt] end context 'when prompt is a scalar value' do let(:prompt) { :login } it { should == 'login' } end context 'when prompt is a space-delimited string' do let(:prompt) { 'login consent' } it { should == 'login consent' } end context 'when prompt is an array' do let(:prompt) { [:login, :consent] } it { should == 'login consent' } end end end describe '#access_token!' do let :attributes do required_attributes.merge( secret: 'client_secret', token_endpoint: 'http://server.example.com/access_tokens' ) end let :protocol_params do { grant_type: 'authorization_code', code: 'code' } end let :header_params do { 'Authorization' => 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=', 'Content-Type' => 'application/x-www-form-urlencoded' } end let :access_token do client.authorization_code = 'code' client.access_token! end context 'when bearer token is returned' do it 'should return OpenIDConnect::AccessToken' do mock_json :post, client.token_endpoint, 'access_token/bearer', request_header: header_params, params: protocol_params do access_token.should be_a OpenIDConnect::AccessToken end end context 'when id_token is returned' do it 'should include id_token' do mock_json :post, client.token_endpoint, 'access_token/bearer_with_id_token', request_header: header_params, params: protocol_params do access_token.id_token.should == 'id_token' end end end end context 'otherwise' do it 'should raise Unexpected Token Type exception' do mock_json :post, client.token_endpoint, 'access_token/mac', request_header: header_params, params: protocol_params do expect { access_token }.to raise_error OpenIDConnect::Exception, 'Unexpected Token Type: mac' end end context 'when token_type is forced' do before { client.force_token_type! :bearer } it 'should use forced token_type' do mock_json :post, client.token_endpoint, 'access_token/without_token_type', request_header: header_params, params: protocol_params do access_token.should be_a OpenIDConnect::AccessToken end end end end end end openid_connect-2.3.1/spec/openid_connect/connect_object_spec.rb0000644000004100000410000000507714723350730025010 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ConnectObject do class OpenIDConnect::ConnectObject::SubClass < OpenIDConnect::ConnectObject attr_required :required attr_optional :optional validates :required, inclusion: {in: ['Required', 'required']}, length: 1..10 end subject { instance } let(:klass) { OpenIDConnect::ConnectObject::SubClass } let(:instance) { klass.new attributes } let :attributes do {required: 'Required', optional: 'Optional'} end context 'when required attributes are given' do context 'when optional attributes are given' do its(:required) { should == 'Required' } its(:optional) { should == 'Optional' } end context 'otherwise' do let :attributes do {required: 'Required'} end its(:required) { should == 'Required' } its(:optional) { should == nil } end end context 'otherwise' do context 'when optional attributes are given' do let :attributes do {optional: 'Optional'} end it do expect { klass.new attributes }.to raise_error AttrRequired::AttrMissing end end context 'otherwise' do it do expect { klass.new }.to raise_error AttrRequired::AttrMissing end end end describe '#as_json' do context 'when valid' do its(:as_json) do should == attributes end end context 'otherwise' do let :attributes do {required: 'Out of List and Too Long'} end it 'should raise OpenIDConnect::ValidationFailed with ActiveModel::Errors owner' do expect { instance.as_json }.to raise_error(OpenIDConnect::ValidationFailed) { |e| e.message.should include 'Required is not included in the list' e.message.should include 'Required is too long (maximum is 10 characters)' e.object.errors.should be_a ActiveModel::Errors } end end end describe '#validate!' do context 'when valid' do subject { instance.validate! } it { should == true } end context 'otherwise' do let :attributes do {required: 'Out of List and Too Long'} end it 'should raise OpenIDConnect::ValidationFailed with ActiveModel::Errors owner' do expect { instance.validate! }.to raise_error(OpenIDConnect::ValidationFailed) { |e| e.message.should include 'Required is not included in the list' e.message.should include 'Required is too long (maximum is 10 characters)' e.object.errors.should be_a ActiveModel::Errors } end end end end openid_connect-2.3.1/spec/openid_connect/discovery/0000755000004100000410000000000014723350730022470 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/discovery/provider/0000755000004100000410000000000014723350730024322 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/discovery/provider/config_spec.rb0000644000004100000410000000761714723350730027141 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider::Config do let(:provider) { 'https://connect-op.heroku.com' } let(:endpoint) { 'https://connect-op.heroku.com/.well-known/openid-configuration' } describe 'discover!' do it 'should setup given attributes' do mock_json :get, endpoint, 'discovery/config' do config = OpenIDConnect::Discovery::Provider::Config.discover! provider config.should be_instance_of OpenIDConnect::Discovery::Provider::Config::Response config.issuer.should == 'https://connect-op.heroku.com' config.authorization_endpoint.should == 'https://connect-op.heroku.com/authorizations/new' config.token_endpoint.should == 'https://connect-op.heroku.com/access_tokens' config.userinfo_endpoint.should == 'https://connect-op.heroku.com/userinfo' config.jwks_uri.should == 'https://connect-op.heroku.com/jwks.json' config.registration_endpoint.should == 'https://connect-op.heroku.com/connect/client' config.scopes_supported.should == ['openid', 'profile', 'email', 'address'] config.response_types_supported.should == ['code', 'token', 'id_token', 'code token', 'code id_token', 'id_token token'] config.acr_values_supported.should be_nil config.subject_types_supported.should == ['public', 'pairwise'] config.claims_supported.should == ['sub', 'iss', 'name', 'email'] config.id_token_signing_alg_values_supported.should == ['RS256'] end end context 'when OP identifier includes custom port' do let(:provider) { 'https://connect-op.heroku.com:8080' } let(:endpoint) { 'https://connect-op.heroku.com:8080/.well-known/openid-configuration' } it 'should construct well-known URI with given port' do mock_json :get, endpoint, 'discovery/config_with_custom_port' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end end context 'when OP identifier includes path' do let(:provider) { 'https://connect.openid4.us/abop' } let(:endpoint) { 'https://connect.openid4.us/abop/.well-known/openid-configuration' } it 'should construct well-known URI with given port' do mock_json :get, endpoint, 'discovery/config_with_path' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end end context 'when SWD::Exception raised' do it do expect do mock_json :get, endpoint, 'errors/unknown', status: [404, 'Not Found'] do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end describe 'when response include invalid issuer' do context 'with normal configuration' do it do expect do mock_json :get, endpoint, 'discovery/config_with_invalid_issuer' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end context 'when issuer validation is disabled.' do before :each do OpenIDConnect.validate_discovery_issuer = false end after :each do OpenIDConnect.validate_discovery_issuer = true end it do expect do mock_json :get, endpoint, 'discovery/config_with_invalid_issuer' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.not_to raise_error end end end context 'when response include no issuer' do it do expect do mock_json :get, endpoint, 'discovery/config_without_issuer' do OpenIDConnect::Discovery::Provider::Config.discover! provider end end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end end end openid_connect-2.3.1/spec/openid_connect/discovery/provider/config/0000755000004100000410000000000014723350730025567 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/discovery/provider/config/resource_spec.rb0000644000004100000410000000071214723350730030755 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider::Config::Resource do let(:resource) do uri = URI.parse 'http://server.example.com' OpenIDConnect::Discovery::Provider::Config::Resource.new uri end describe '#endpoint' do context 'when invalid host' do before do resource.host = 'hoge*hoge' end it do expect { resource.endpoint }.to raise_error SWD::Exception end end end endopenid_connect-2.3.1/spec/openid_connect/discovery/provider/config/response_spec.rb0000644000004100000410000000506214723350730030767 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider::Config::Response do let :instance do OpenIDConnect::Discovery::Provider::Config::Response.new attributes end let :jwks_uri do 'https://server.example.com/jwks.json' end let :minimum_attributes do { issuer: 'https://server.example.com', authorization_endpoint: 'https://server.example.com/authorize', jwks_uri: jwks_uri, response_types_supported: [ :code, :id_token, 'token id_token' ], subject_types_supported: [ :public, :pairwise ], id_token_signing_alg_values_supported: [ :RS256 ] } end let :attributes do minimum_attributes end subject { instance } context 'when required attributes missing' do let :attributes do {} end it { should_not be_valid } end context 'when end_session_endpoint given' do let(:end_session_endpoint) { 'https://server.example.com/end_session' } let :attributes do minimum_attributes.merge( end_session_endpoint: end_session_endpoint ) end it { should be_valid } its(:end_session_endpoint) { should == end_session_endpoint } end context 'when check_session_iframe given' do let(:check_session_iframe) { 'https://server.example.com/check_session_iframe.html' } let :attributes do minimum_attributes.merge( check_session_iframe: check_session_iframe ) end it { should be_valid } its(:check_session_iframe) { should == check_session_iframe } end describe '#as_json' do subject { instance.as_json } it { should == minimum_attributes } end describe '#validate!' do context 'when required attributes missing' do let :attributes do {} end it do expect do instance.validate! end.to raise_error OpenIDConnect::ValidationFailed end end context 'otherwise' do it do expect do instance.validate! end.not_to raise_error{ |e| e.should be_a OpenIDConnect::ValidationFailed } end end end describe '#jwks' do it do jwks = mock_json :get, jwks_uri, 'public_keys/jwks' do instance.jwks end jwks.should be_instance_of JSON::JWK::Set end end describe '#public_keys' do it do public_keys = mock_json :get, jwks_uri, 'public_keys/jwks' do instance.public_keys end public_keys.should be_instance_of Array public_keys.first.should be_instance_of OpenSSL::PKey::RSA end end end openid_connect-2.3.1/spec/openid_connect/discovery/provider_spec.rb0000644000004100000410000000375514723350730025673 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::Discovery::Provider do let(:provider) { 'https://server.example.com' } let(:discover) { OpenIDConnect::Discovery::Provider.discover! identifier } let(:endpoint) { "https://#{host}/.well-known/webfinger" } let(:query) do { rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, resource: resource } end shared_examples_for :discover_provider do it "should succeed" do mock_json :get, endpoint, 'discovery/webfinger', params: query do res = discover res.should be_a WebFinger::Response res.issuer.should == provider end end end describe '#discover!' do let(:host) { 'server.example.com' } context 'when URI is given' do let(:resource) { identifier } context 'when scheme included' do context 'when HTTPS' do let(:identifier) { "https://#{host}" } it_behaves_like :discover_provider end context 'otherwise' do let(:identifier) { "http://#{host}" } it_behaves_like :discover_provider it 'should access to https://**' do endpoint.should match /^https:\/\// end end end context 'when only host is given' do let(:identifier) { host } let(:resource) { "https://#{host}" } it_behaves_like :discover_provider end end context 'when Email is given' do let(:identifier) { "nov@#{host}" } let(:resource) { "acct:#{identifier}" } it_behaves_like :discover_provider end context 'when error occured' do let(:identifier) { host } let(:resource) { "https://#{host}" } it 'should raise OpenIDConnect::Discovery::DiscoveryFailed' do mock_json :get, endpoint, 'discovery/webfinger', params: query, status: [404, 'Not Found'] do expect do discover end.to raise_error OpenIDConnect::Discovery::DiscoveryFailed end end end end endopenid_connect-2.3.1/spec/openid_connect/exception_spec.rb0000644000004100000410000000122014723350730024011 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::HttpError do subject do OpenIDConnect::HttpError.new 400, 'Bad Request' end its(:status) { should == 400 } its(:message) { should == 'Bad Request' } its(:response) { should be_nil } end describe OpenIDConnect::BadRequest do its(:status) { should == 400 } its(:message) { should == 'OpenIDConnect::BadRequest' } end describe OpenIDConnect::Unauthorized do its(:status) { should == 401 } its(:message) { should == 'OpenIDConnect::Unauthorized' } end describe OpenIDConnect::Forbidden do its(:status) { should == 403 } its(:message) { should == 'OpenIDConnect::Forbidden' } endopenid_connect-2.3.1/spec/openid_connect/response_object/0000755000004100000410000000000014723350730023645 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/response_object/id_token_spec.rb0000644000004100000410000003237714723350730027014 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ResponseObject::IdToken do let(:klass) { OpenIDConnect::ResponseObject::IdToken } let(:id_token) { klass.new attributes } let(:attributes) { required_attributes } let(:ext) { 10.minutes.from_now } let(:iat) { Time.now } let :required_attributes do { iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', exp: ext, iat: iat } end describe 'attributes' do subject { klass } its(:required_attributes) { should == [:iss, :sub, :aud, :exp, :iat] } its(:optional_attributes) { should == [:acr, :amr, :azp, :jti, :sid, :auth_time, :nonce, :sub_jwk, :at_hash, :c_hash, :s_hash] } describe 'auth_time' do subject { id_token.auth_time } context 'when Time object given' do let(:attributes) do required_attributes.merge(auth_time: Time.now) end it do should be_a Numeric end end end end describe '#verify!' do context 'when both issuer, client_id are valid' do it do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud] ).should == true end context 'when aud(ience) is an array of identifiers' do let(:client_id) { 'client_id' } let(:attributes) { required_attributes.merge(aud: ['some_other_identifier', client_id]) } it do id_token.verify!( issuer: attributes[:iss], client_id: client_id ).should == true end end context 'when expired' do let(:ext) { 10.minutes.ago } it do expect do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end end context 'when issuer is invalid' do it do expect do id_token.verify!( issuer: 'invalid_issuer', client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when issuer is missing' do it do expect do id_token.verify!( client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when client_id is invalid' do it do expect do id_token.verify!( issuer: attributes[:iss], client_id: 'invalid_client' ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when client_id is missing' do it do expect do id_token.verify!( issuer: attributes[:iss] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when nonce is given' do let(:attributes) { required_attributes.merge(nonce: 'nonce') } context 'when nonce is valid' do it do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud], nonce: attributes[:nonce] ).should == true end end context 'when nonce is invalid' do it do expect do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud], nonce: 'invalid_nonce' ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end context 'when nonce is missing' do it do expect do id_token.verify!( issuer: attributes[:iss], client_id: attributes[:aud] ) end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken end end end end describe '#to_jwt' do subject { id_token.to_jwt private_key } it { should be_a String } context 'when block given' do it 'should allow add additional headers' do t = id_token.to_jwt private_key do |t| t.header[:x5u] = "http://server.example.com/x5u" end h = Base64.urlsafe_decode64 t.split('.').first h.should include 'x5u' end end context 'when access_token is given' do shared_examples_for :id_token_with_at_hash do it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should include :at_hash jwt.should_not include :c_hash jwt[:at_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('access_token')[0, 128 / 8], padding: false ) end end context 'when access_token is a Rack::OAuth2::AccessToken' do before { id_token.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') } it_should_behave_like :id_token_with_at_hash end context 'when access_token is a String' do before { id_token.access_token = 'access_token' } it_should_behave_like :id_token_with_at_hash end end context 'when code is given' do before { id_token.code = 'authorization_code' } it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should_not include :at_hash jwt.should include :c_hash jwt[:c_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('authorization_code')[0, 128 / 8], padding: false ) end end context 'when both access_token and code are given' do before do id_token.access_token = 'access_token' id_token.code = 'authorization_code' end it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should include :at_hash jwt.should include :c_hash jwt[:at_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('access_token')[0, 128 / 8], padding: false ) jwt[:c_hash].should == Base64.urlsafe_encode64( OpenSSL::Digest::SHA256.digest('authorization_code')[0, 128 / 8], padding: false ) end end context 'when neither access_token nor code are given' do it 'should include at_hash' do t = id_token.to_jwt private_key jwt = JSON::JWT.decode t, public_key jwt.should_not include :at_hash, :c_hash end end end describe '#as_json' do subject { id_token.as_json } let(:attributes) { required_attributes } it do hash = required_attributes hash[:exp] = required_attributes[:exp].to_i should == hash end end describe '.decode' do subject { klass.decode id_token.to_jwt(private_key), public_key } let(:attributes) { required_attributes } it { should be_a klass } [:iss, :sub, :aud].each do |key| its(key) { should == attributes[key] } end its(:exp) { should == attributes[:exp].to_i } its(:raw_attributes) { should be_instance_of JSON::JWS } context 'when IdP config is given' do subject { klass.decode id_token.to_jwt(private_key), idp_config } let(:jwks) do jwk_str = File.read(File.join(__dir__, '../../mock_response/public_keys/jwks_with_private_key.json')) jwk = JSON::JWK::Set.new JSON.parse(jwk_str) end let(:idp_config) do OpenIDConnect::Discovery::Provider::Config::Response.new( issuer: attributes[:issuer], authorization_endpoint: File.join(attributes[:iss], 'authorize'), jwks_uri: File.join(attributes[:iss], 'jwks'), response_types_supported: ['code'], subject_types_supported: ['public'], id_token_signing_alg_values_supported: ['RS256'] ) end context 'when id_token has kid' do let(:private_key) do OpenSSL::PKey::RSA.new( File.read(File.join(__dir__, '../../mock_response/public_keys/private_key.pem')) ).to_jwk end it do mock_json :get, idp_config.jwks_uri, 'public_keys/jwks_with_private_key' do should be_a klass end end end context 'otherwise' do let(:private_key) do OpenSSL::PKey::RSA.new( File.read(File.join(__dir__, '../../mock_response/public_keys/private_key.pem')) ) end it do mock_json :get, idp_config.jwks_uri, 'public_keys/jwks_with_private_key' do expect do should end.to raise_error JSON::JWK::Set::KidNotFound end end end end context 'when self-issued' do context 'when valid' do let(:self_issued) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lIiwic3ViIjoiMmdDUWFLUmJkY0RaeUlDTE92ODJJR2EtdHBSVU52QW1ZN3BnZ3Z5NGdENCIsImF1ZCI6ImNsaWVudC5leGFtcGxlLmNvbSIsImV4cCI6MTQ0MjQ4Mjc4MiwiaWF0IjoxNDQxODc3OTgyLCJzdWJfandrIjp7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwibiI6IjN1RzNiSTV6MTFhM1hlOXUyZFVJNDBpcWZrVl9vTmFQVmNlalN4V3l0YnMybTZKMGMzNjJESlJQNGtyUl9TZjNtQXJ3Qjd6Qm5UWExkbW1tZW85VzloSDhsSnFGOUthMTY3dHBTQWJCajB1MjhyaTgwZFZ4NUxzblJTX19uUUd6Y3dNa2sxTTBERUx2X0FXbVYwU2JudDhJZEpSeFhwdG5xRE5tWXJ0cmItMkk0a1lwRHlwN2pvTXd0bDNXeGp2cnkwbENLNExqOU9SeXdod05zYUU2MHFsako5aHBGZV8wTmpmaThzaVBlMDRJSkFaUjl3NXo0TnAtQS1HbWdmeTNJTmNZVFYyQ25FekNSY29HSGl5OGduRzA1a015TnRtZTFVdV8xanBhdF9lcF9QUG9PWEJ6Q1NwbzB5QlRNSWhmdEJTQ3p2a2V1ZFdhNks2aW5LMkYxdyJ9fQ.wchF80oFxdjEcOEwPZ9TUlV6R96Vz8XK9MzednMOsZmEMnNSEqKKTyO0Mhp9lijJPZX8J7lTtAGkz4gfsjyoYBIHQOTf0qHRHSx9RTeC31whw1TJ9x5V6UXpKN0EW1EhjAEGIZ0EyFJ-cRTgVs0V7PT7e63JOUYyW6LqqHa4MV9SdK8BdnaN0D4-402Pf7yFqjneSHq3KZbXcgjUPT_hszsGvnn9qEyuIHQqON6YnDt55z5SvP_RfKtBfUe2VY-yglJT41LfhkIgpvjLYdYYRPh9G9ftJr17qht5RtHSNpTp4FPw7BR7rCnptb4xTxyq-sLu7qjSLRtqQ35Xpi_6qQ' end context 'when key == :self_issued' do it do expect do klass.decode self_issued, :self_issued end.not_to raise_error end end context 'when key == public_key' do it do expect do klass.decode self_issued, public_key end.to raise_error JSON::JWS::VerificationFailed end end end context 'when invalid subject' do let(:self_issued) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlbGYtaXNzdWVkLm1lIiwic3ViIjoiUFdFYXFfVnlUd1hTSFR4QVlSZHdWTjNMN2s0UnNxOVBwaTZ4WHZ6ZGZWTSIsImF1ZCI6InRhcGlkLnRhcGlkZW50aXR5LmNvbSIsImV4cCI6MTM2MjI3OTkwMCwiaWF0IjoxMzYyMjc2MzAwLCJzdWJfandrIjp7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwibiI6IjRGTWl5M08zbFlOd2RzeC15aXVjemRsek81eU11d1p4WFlzSDgydmM0RkM0QXgyMGpNVV94emJHSUhWVUtFQ0pndFp3clBlajhRSWUtZFZFYXQtaGxjNTB5TXluM0h3cmtJVjVZOTdET1E2Sks4azk2QTFqVWxPLW5sRjl4ZUx2VDlwYTJXRTZtYm1KOG5EQW5mR0d6bmRNd3VKNzVLZDI2YmZHY21wcm5qUUJLTkVrakdJbW9MMEhFODFUcjROeC1tN1lsYkRGaVFNRDVpYjhCY3N4S0tvMTZTeG5tSi1EeUY2c094Y2JtV1ZrdkZBa3FKWFBnVFVoNXVYT3YwYk9nN0I2d2RHdUMtWnpJUl8tdUx3YlcxN2V4NGx3ZTFPb0ppdFJ3SFczYlo3NEc3RkdoSmhfTUp4YzB3WXBkbW5uNVpjRFFOWl9sWVRvMHYzaU1PUWk3USJ9fQ.DZKaSne22DjKFSpSUphsTeCMkcMWDexQCm8BPb1nI1PzQYsEAOfwumDajt85UA0x28y2zuOevMj29VpwTzbpRDkduv2NWAI4MHw8DYEsIN__-QGANmdU1sKmthET2iFmeFySwWomLqFvYIaNmVYVLkD53Zqfct5qH3Wznd_hrK8T1d6Cxg-gyZlAeqEu2V8EL2yuz8Gdaeze4b78l5Ux-B_5FQhZ3UkXbL1B2gzKJQVKAQdFJb9zUfzmCeIiUmeM9mw_VU64tAvFDRiTKS1P6b62Gxuyx1DhMLFg2evDaTJERJOta9ywtPfdcLH3qcIiUBffP2-FnAz44bOlKzJorQ' end it do expect do klass.decode self_issued, :self_issued end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken, 'Invalid subject' end end context 'when no sub_jwk' do let(:self_issued) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkZXZpY2VfdG9rZW4iOiI2NjYxNmI2NTJkNjQ2NTc2Njk2MzY1MmQ3NDZmNmI2NTZlIiwiaXNzIjoiaHR0cHM6Ly9zZWxmLWlzc3VlZC5tZSIsInN1YiI6IlBXRWFxX1Z5VHdYU0hUeEFZUmR3Vk4zTDdrNFJzcTlQcGk2eFh2emRmVk0iLCJhdWQiOiJ0YXBpZC50YXBpZGVudGl0eS5jb20iLCJleHAiOjEzNjIyODAxNDQsImlhdCI6MTM2MjI3NjU0NH0.HtkguN4xOzJ-yh_kd2JCmG6fgDEiVY5VCgTWUD9l8YOgHjTT7LRZC3b1sNDgkdwBteX3eQIQOVxaYWp4-ftczaIlrznB0jxldqdEdB1Tr591YsiDcyOqmemo1ZYzOKhe_q1l68bdKKeHLc83BzlsJpS659uFDuixvF7G_HIJpCdwckX7x6H3KK73hCLzoYCOVgr_lkFRVVHHAJXzxiUuERLD7JIvg5jCbgmqxArP-jYBdbscHHx8i-UP3WYFBEORBM2rXJuJzGvk4sLhZ4NVGBWyr0DJlE-aWKTyeg-_-4kLPd3d68-k3nLJ82iCwcap-BU_5otSmXufN3_ffq_tTw' end it do expect do klass.decode self_issued, :self_issued end.to raise_error OpenIDConnect::ResponseObject::IdToken::InvalidToken, 'Missing sub_jwk' end end end end describe '.self_issued' do subject { self_issued } let(:sub_jwk) { JSON::JWK.new(public_key) } let(:self_issued) do klass.self_issued( public_key: public_key, aud: 'client.example.com', exp: 1.week.from_now, iat: Time.now ) end [:iss, :sub, :aud, :exp, :iat, :sub_jwk].each do |attribute| its(attribute) { should be_present } end its(:iss) { should == 'https://self-issued.me' } its(:sub_jwk) { should == sub_jwk} its(:subject) { should == sub_jwk.thumbprint } end end openid_connect-2.3.1/spec/openid_connect/response_object/user_info/0000755000004100000410000000000014723350730025636 5ustar www-datawww-dataopenid_connect-2.3.1/spec/openid_connect/response_object/user_info/address_spec.rb0000644000004100000410000000126514723350730030626 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ResponseObject::UserInfo::Address do let(:klass) { OpenIDConnect::ResponseObject::UserInfo::Address } describe 'attributes' do subject { klass } its(:required_attributes) { should == [] } its(:optional_attributes) { should == [:formatted, :street_address, :locality, :region, :postal_code, :country] } end describe 'validations' do subject do instance = klass.new attributes instance.valid? instance end context 'when all attributes are blank' do let :attributes do {} end its(:valid?) { should == false } its(:errors) { should include :base } end end endopenid_connect-2.3.1/spec/openid_connect/response_object/user_info_spec.rb0000644000004100000410000000547514723350730027210 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::ResponseObject::UserInfo do let(:klass) { OpenIDConnect::ResponseObject::UserInfo } let(:instance) { klass.new attributes } subject { instance } describe 'attributes' do subject { klass } its(:required_attributes) { should == [] } its(:optional_attributes) do should == [ :sub, :name, :given_name, :family_name, :middle_name, :nickname, :preferred_username, :profile, :picture, :website, :email, :email_verified, :gender, :birthdate, :zoneinfo, :locale, :phone_number, :phone_number_verified, :address, :updated_at ] end end describe 'validations' do subject do _instance_ = instance _instance_.valid? _instance_ end context 'when all attributes are blank' do let :attributes do {} end its(:valid?) { should == false } its(:errors) { should include :base } end context 'when email is invalid' do let :attributes do {email: 'nov@localhost'} end its(:valid?) { should == false } its(:errors) { should include :email } end [:email_verified, :zoneinfo].each do |one_of_list| context "when #{one_of_list} is invalid" do let :attributes do {one_of_list => 'Out of List'} end its(:valid?) { should == false } its(:errors) { should include one_of_list } end end context "when locale is invalid" do it :TODO end [:profile, :picture, :website].each do |url| context "when #{url} is invalid" do let :attributes do {url => 'Invalid'} end its(:valid?) { should == false } its(:errors) { should include url } end end context 'when address is blank' do let :attributes do {address: {}} end its(:valid?) { should == false } its(:errors) { should include :address } end end describe '#address=' do context 'when Hash is given' do let :attributes do {address: {}} end its(:address) { should be_a OpenIDConnect::ResponseObject::UserInfo::Address } end context 'when Address is given' do let :attributes do {address: OpenIDConnect::ResponseObject::UserInfo::Address.new} end its(:address) { should be_a OpenIDConnect::ResponseObject::UserInfo::Address } end end describe '#to_json' do let :attributes do { sub: 'nov.matake#12345', address: { formatted: 'Tokyo, Japan' } } end its(:to_json) { should include '"sub":"nov.matake#12345"'} its(:to_json) { should include '"address":{"formatted":"Tokyo, Japan"}'} end endopenid_connect-2.3.1/spec/openid_connect/access_token_spec.rb0000644000004100000410000000602414723350730024463 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::AccessToken do subject { access_token } let :client do OpenIDConnect::Client.new( identifier:'client_id', host: 'server.example.com' ) end let :access_token do OpenIDConnect::AccessToken.new( access_token: 'access_token', client: client ) end its(:token_type) { should == :bearer } its(:optional_attributes) { should include :id_token } context 'when id_token is given' do subject { access_token } let :access_token do OpenIDConnect::AccessToken.new( access_token: 'access_token', id_token: id_token, client: client ) end context 'when IdToken object' do let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1.week.from_now, iat: Time.now ) end its(:id_token) { should be_a OpenIDConnect::ResponseObject::IdToken } its(:token_response) { should_not include :id_token } end context 'when JWT string' do let(:id_token) { 'id_token' } its(:id_token) { should == 'id_token' } its(:token_response) { should_not include :id_token } end end shared_examples_for :access_token_error_handling do context 'when bad_request' do it 'should raise OpenIDConnect::Forbidden' do mock_json :get, endpoint, 'errors/invalid_request', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 400 do expect { request }.to raise_error OpenIDConnect::BadRequest end end end context 'when unauthorized' do it 'should raise OpenIDConnect::Unauthorized' do mock_json :get, endpoint, 'errors/invalid_access_token', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 401 do expect { request }.to raise_error OpenIDConnect::Unauthorized end end end context 'when forbidden' do it 'should raise OpenIDConnect::Forbidden' do mock_json :get, endpoint, 'errors/insufficient_scope', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 403 do expect { request }.to raise_error OpenIDConnect::Forbidden end end end context 'when unknown' do it 'should raise OpenIDConnect::HttpError' do mock_json :get, endpoint, 'errors/unknown', :HTTP_AUTHORIZATION => 'Bearer access_token', status: 500 do expect { request }.to raise_error OpenIDConnect::HttpError end end end end describe '#userinfo!' do it do userinfo = mock_json :get, client.userinfo_uri, 'userinfo/openid', :HTTP_AUTHORIZATION => 'Bearer access_token' do access_token.userinfo! end userinfo.should be_instance_of OpenIDConnect::ResponseObject::UserInfo end describe 'error handling' do let(:endpoint) { client.userinfo_uri } let(:request) { access_token.userinfo! } it_behaves_like :access_token_error_handling end end endopenid_connect-2.3.1/spec/openid_connect/request_object_spec.rb0000644000004100000410000000652714723350730025050 0ustar www-datawww-datarequire 'spec_helper' describe OpenIDConnect::RequestObject do subject { request_object } let(:request_object) { OpenIDConnect::RequestObject.new attributes } context 'with all attributes' do let(:attributes) do { client_id: 'client_id', response_type: 'token id_token', redirect_uri: 'https://client.example.com', scope: 'openid email', state: 'state1234', nonce: 'nonce1234', display: 'touch', prompt: 'none', id_token: { max_age: 10, claims: { acr: { values: ['2', '3', '4'] } } }, userinfo: { claims: { name: :required, email: :optional } } } end let(:jwtnized) do 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJjbGllbnRfaWQiLCJyZXNwb25zZV90eXBlIjoidG9rZW4gaWRfdG9rZW4iLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLmNvbSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIiwic3RhdGUiOiJzdGF0ZTEyMzQiLCJub25jZSI6Im5vbmNlMTIzNCIsImRpc3BsYXkiOiJ0b3VjaCIsInByb21wdCI6Im5vbmUiLCJ1c2VyaW5mbyI6eyJjbGFpbXMiOnsibmFtZSI6eyJlc3NlbnRpYWwiOnRydWV9LCJlbWFpbCI6eyJlc3NlbnRpYWwiOmZhbHNlfX19LCJpZF90b2tlbiI6eyJjbGFpbXMiOnsiYWNyIjp7InZhbHVlcyI6WyIyIiwiMyIsIjQiXX19LCJtYXhfYWdlIjoxMH19.yOc76jnkDusf5ZUzI5Gq7vnteTeOVUXd2Fr1EBZFNYU' end let(:jsonized) do { client_id: "client_id", response_type: "token id_token", redirect_uri: "https://client.example.com", scope: "openid email", state: "state1234", nonce: "nonce1234", display: "touch", prompt: "none", id_token: { claims: { acr: { values: ['2', '3', '4'] } }, max_age: 10 }, userinfo: { claims: { name: { essential: true }, email: { essential: false } } } } end it { should be_valid } its(:as_json) do should == jsonized.with_indifferent_access end describe '#to_jwt' do it do request_object.to_jwt('secret', :HS256).should == jwtnized end end describe '.decode' do it do OpenIDConnect::RequestObject.decode(jwtnized, 'secret').as_json.should == jsonized.with_indifferent_access end end describe '.fetch' do let(:endpoint) { 'https://client.example.com/request.jwk' } it do mock_json :get, endpoint, 'request_object/signed', format: :jwt do request_object = OpenIDConnect::RequestObject.fetch endpoint, 'secret' request_object.as_json.should == jsonized.with_indifferent_access end end end describe '#required?' do it do request_object.userinfo.required?(:name).should == true request_object.userinfo.optional?(:name).should == false end end describe '#optional' do it do request_object.userinfo.required?(:email).should == false request_object.userinfo.optional?(:email).should == true end end end context 'with no attributes' do let(:attributes) do {} end it { should_not be_valid } it do expect do request_object.as_json end.to raise_error OpenIDConnect::ValidationFailed end end endopenid_connect-2.3.1/spec/mock_response/0000755000004100000410000000000014723350730020341 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/client/0000755000004100000410000000000014723350730021617 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/client/updated.json0000644000004100000410000000004714723350730024141 0ustar www-datawww-data{ "client_id": "client.example.com" }openid_connect-2.3.1/spec/mock_response/client/rotated.json0000644000004100000410000000014514723350730024154 0ustar www-datawww-data{ "client_id": "client.example.com", "client_secret": "new_client_secret", "expires_in": 3600 }openid_connect-2.3.1/spec/mock_response/client/registered.json0000644000004100000410000000014114723350730024643 0ustar www-datawww-data{ "client_id": "client.example.com", "client_secret": "client_secret", "expires_in": 3600 }openid_connect-2.3.1/spec/mock_response/access_token/0000755000004100000410000000000014723350730023002 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/access_token/invalid_json.json0000644000004100000410000000003114723350730026346 0ustar www-datawww-dataaccess_token=access_tokenopenid_connect-2.3.1/spec/mock_response/access_token/without_token_type.json0000644000004100000410000000004414723350730027637 0ustar www-datawww-data{ "access_token":"access_token" } openid_connect-2.3.1/spec/mock_response/access_token/bearer.json0000644000004100000410000000016414723350730025136 0ustar www-datawww-data{ "access_token":"access_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid_connect-2.3.1/spec/mock_response/access_token/bearer_with_id_token.json0000644000004100000410000000021514723350730030042 0ustar www-datawww-data{ "access_token":"access_token", "id_token":"id_token", "refresh_token":"refresh_token", "token_type":"bearer", "expires_in":3600 }openid_connect-2.3.1/spec/mock_response/access_token/mac.json0000644000004100000410000000026014723350730024433 0ustar www-datawww-data{ "token_type": "mac", "mac_algorithm": "hmac-sha-256", "expires_in": 3600, "mac_key": "secret", "refresh_token": "refresh_token", "access_token": "access_token" } openid_connect-2.3.1/spec/mock_response/userinfo/0000755000004100000410000000000014723350730022173 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/userinfo/openid.json0000644000004100000410000000113714723350730024346 0ustar www-datawww-data{ "id": "90125", "name": "Jonathan Q. Doe", "given_name": "Jonathan", "middle_name": "Q.", "family_name": "Doe", "nickname": "John", "email": "johndoe@example.com", "verified": true, "profile": "http://example.com/johndoe/", "picture": "http://example.com/johndoe/me.jpg", "website": "http://john.doe.blogs.example.net/", "gender": "male", "birthdate": "05/02/0000", "zoneinfo": "America/Los_Angeles", "locale": "en_US", "phone_number": "+1 (425) 555-1212", "address": { "region": "WA", "country": "United States" }, "last_updated": "2011-06-29T21:10:22+0000" }openid_connect-2.3.1/spec/mock_response/discovery/0000755000004100000410000000000014723350730022350 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/discovery/config_with_invalid_issuer.json0000644000004100000410000000135514723350730030647 0ustar www-datawww-data{ "issuer": "https://attacker.example.com", "authorization_endpoint": "https://connect-op.heroku.com/authorizations/new", "token_endpoint": "https://connect-op.heroku.com/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com/userinfo", "registration_endpoint": "https://connect-op.heroku.com/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-2.3.1/spec/mock_response/discovery/config.json0000644000004100000410000000135614723350730024515 0ustar www-datawww-data{ "issuer": "https://connect-op.heroku.com", "authorization_endpoint": "https://connect-op.heroku.com/authorizations/new", "token_endpoint": "https://connect-op.heroku.com/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com/userinfo", "registration_endpoint": "https://connect-op.heroku.com/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-2.3.1/spec/mock_response/discovery/swd.json0000644000004100000410000000006114723350730024035 0ustar www-datawww-data{ "locations": ["https://server.example.com"] }openid_connect-2.3.1/spec/mock_response/discovery/config_without_issuer.json0000644000004100000410000000127714723350730027674 0ustar www-datawww-data{ "authorization_endpoint": "https://connect-op.heroku.com/authorizations/new", "token_endpoint": "https://connect-op.heroku.com/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com/userinfo", "registration_endpoint": "https://connect-op.heroku.com/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-2.3.1/spec/mock_response/discovery/config_with_path.json0000644000004100000410000000137014723350730026560 0ustar www-datawww-data{ "issuer": "https://connect.openid4.us/abop", "authorization_endpoint": "https://connect.openid4.us/abop/authorizations/new", "token_endpoint": "https://connect.openid4.us/abop/access_tokens", "userinfo_endpoint": "https://connect.openid4.us/abop/userinfo", "registration_endpoint": "https://connect.openid4.us/abop/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-2.3.1/spec/mock_response/discovery/webfinger.json0000644000004100000410000000031214723350730025207 0ustar www-datawww-data{ "expires": "2013-03-09T06:43:23Z", "links": [{ "rel": "http://openid.net/specs/connect/1.0/issuer", "href": "https://server.example.com" }], "subject": "acct:foo@server.example.com" } openid_connect-2.3.1/spec/mock_response/discovery/config_with_custom_port.json0000644000004100000410000000140714723350730030203 0ustar www-datawww-data{ "issuer": "https://connect-op.heroku.com:8080", "authorization_endpoint": "https://connect-op.heroku.com:8080/authorizations/new", "token_endpoint": "https://connect-op.heroku.com:8080/access_tokens", "userinfo_endpoint": "https://connect-op.heroku.com:8080/userinfo", "registration_endpoint": "https://connect-op.heroku.com:8080/connect/client", "scopes_supported": ["openid", "profile", "email", "address"], "response_types_supported": ["code", "token", "id_token", "code token", "code id_token", "id_token token"], "subject_types_supported": ["public", "pairwise"], "claims_supported": ["sub", "iss", "name", "email"], "jwks_uri": "https://connect-op.heroku.com/jwks.json", "id_token_signing_alg_values_supported": ["RS256"] }openid_connect-2.3.1/spec/mock_response/id_token.json0000644000004100000410000000022514723350730023027 0ustar www-datawww-data{ "iss": "https://server.example.com", "aud": "client_id", "user_id": "user_id", "nonce": "nonce", "exp": 1303852880, "iat": 1303850880 }openid_connect-2.3.1/spec/mock_response/request_object/0000755000004100000410000000000014723350730023357 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/request_object/signed.jwt0000644000004100000410000000103514723350730025355 0ustar www-datawww-dataeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGllbnRfaWQiOiJjbGllbnRfaWQiLCJyZXNwb25zZV90eXBlIjoidG9rZW4gaWRfdG9rZW4iLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLmNvbSIsInNjb3BlIjoib3BlbmlkIGVtYWlsIiwic3RhdGUiOiJzdGF0ZTEyMzQiLCJub25jZSI6Im5vbmNlMTIzNCIsImRpc3BsYXkiOiJ0b3VjaCIsInByb21wdCI6Im5vbmUiLCJpZF90b2tlbiI6eyJjbGFpbXMiOnsiYWNyIjp7InZhbHVlcyI6WyIyIiwiMyIsIjQiXX19LCJtYXhfYWdlIjoxMH0sInVzZXJpbmZvIjp7ImNsYWltcyI6eyJuYW1lIjp7ImVzc2VudGlhbCI6dHJ1ZX0sImVtYWlsIjp7ImVzc2VudGlhbCI6ZmFsc2V9fX19.MLTDQVPdhAdkJhboM06IRtjHJrvamJ_H2vFGRupXmTAopenid_connect-2.3.1/spec/mock_response/public_keys/0000755000004100000410000000000014723350730022652 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/public_keys/jwks_with_private_key.json0000644000004100000410000000072414723350730030163 0ustar www-datawww-data{ "keys": [{ "kty": "RSA", "e": "AQAB", "n": "vWr1S4T0jBnYU9PIpUYxT48Ca8HK8aitbmqbTM3t3Zzl1GNpIlyePnwXSL6SgNcVbeRhTfvXZUzH4pP8HzPJdpUHnAeYyCzjz9UNykdFCp2YW676wpLDzMkaU7bYLJxGjZlpHU-UJVIm5KX9-NfMyGbFUOuw4AY-OWp8GxrqwAF4U6bJ86TpO24wMxmgm0Vl72aRMGVJkRz66YLYOPNVjXjOI4bUuxg_o3Px5QASxvDCawMeLR3pLCoQcLAZn6WZx7nX3Wu6QzcY0QCqhqUAeY49QRT83Jdg7WUsNa2Rbegi3jJGJf-t9hEcJPmrI6q9zl6WArUueQHS-XUQWq5ptw", "kid": "DCmKamGtkGAWz-uujePOp-UeATAeT4fi3KouR78r44I" }] }openid_connect-2.3.1/spec/mock_response/public_keys/jwks.json0000644000004100000410000000063314723350730024525 0ustar www-datawww-data{ "keys": [{ "kty": "RSA", "e": "AQAB", "n": "u4liYNFzgsRr1ERdUY7CY6r4nefi3RzIhK5fdPgdZSMEEflACWAuJu21_TcDpbZ1-6Kbq7zShFsVTAnBkWdO7EP1Rsn11fZpi9m_zEq_uRY-4RpNwp3S9xSdoQ4F3-js1EMaDQ6km0-c0gvr_TyhFqDj_6w_Bb0vFptfGXwfKewPPnhsi7GJ62ihZ32PzxOvEIYcaoXr9xaeudYD3BzWSDmjKGA7PMaEuBhScdUAoibCmsKB-yAGsz2amHnUhcl4B_EBs6wk65Y7ge0ZQJUOGPdUQL49VuALKmr7cMhHKh5KuQmPAi_20K2uZL_EFDaObDWZrclx98s0DmfTRKINtw" }] } openid_connect-2.3.1/spec/mock_response/public_keys/private_key.pem0000644000004100000410000000321614723350730025701 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAvWr1S4T0jBnYU9PIpUYxT48Ca8HK8aitbmqbTM3t3Zzl1GNp IlyePnwXSL6SgNcVbeRhTfvXZUzH4pP8HzPJdpUHnAeYyCzjz9UNykdFCp2YW676 wpLDzMkaU7bYLJxGjZlpHU+UJVIm5KX9+NfMyGbFUOuw4AY+OWp8GxrqwAF4U6bJ 86TpO24wMxmgm0Vl72aRMGVJkRz66YLYOPNVjXjOI4bUuxg/o3Px5QASxvDCawMe LR3pLCoQcLAZn6WZx7nX3Wu6QzcY0QCqhqUAeY49QRT83Jdg7WUsNa2Rbegi3jJG Jf+t9hEcJPmrI6q9zl6WArUueQHS+XUQWq5ptwIDAQABAoIBAHvDWBUJAVRNSsiy 90XuECggk/9ed0Dg6rjblS9g2kvTyWO1tKsMAyVmpTwVsNnYLxtHfsCajcmVmoEU Gkc06iy+AWPUnuIkWpGgbss9OAJQqI03Toc1qBO1TqtmK+cyEPNSSpkpNu4PuHPr dX9TWW2ToNdXuJEX4y5WwlJfiwT6kPdK86IKpPCql1+X/N2nKbn+5OWHTDuW3jLF H4UoJlUU77VgPedQLF9xr9NXGZbgYdTtsg3GU3k7/xhcetNq22Dtr8vYnX8LcIsZ 9VW+KBRGOwgXTMLuj25VxkFUsJejEoq5+WyHTsSsa4w8Fxyc50GPfZJKh8J2jHiG 8weJUNECgYEA5CoQmUz+8saVg1IwnEgZBSMF1rthMgvuDPhD8PJNaugUCyo9tg0O AXo9EMOUHmr2vCN8h2MZZuuW0D5np/Z9T102N99mJU6tVMSabBPDUTfxThq4xY48 VZvS6EOzSomeEbrIDciJghqJIvPxEoqLXY3Zg7kDef7YiqybhZFdlS8CgYEA1IbH MHKfcL+LAo88y4tgOe6Wn8FRG1K7MHvdR+KErgxBg63I9zmolPsyznjNVKpB9syt zqkDxBg/jTIctgeziMQNSODQoqRKcgEDePwcu+wBvuV+LJFJoIWFrvIPyZ5yKzeb Vm1lRMgQfoeAQE4nVYAJG+oTTsFTdEtrHkOW4fkCgYEAsNHcnUFrTvARDH1UiLjj EvUKYFhEwck3CbwYwxC0aIZEikaJHp3NXd3Cl0xKbKxOXI1Pw4hMNlObQ/Uo1aUT hb7h9rjda0omz8uxNNK4CihFjFbvHMLXBS1GbJOSzdAKvQi4Yt4nmrk/z+Omzsyp pq34hLmL9S5H2Ghd+kwmbycCgYBiC1N1PEvl3depdJ8dX80irLj8NljOfBozQdFR ymRfTvQiZVfjBcyJ/mDv87b2Kh2IV+CPCFXebzlSUB4CtAbVP2zJhD176sMVWPZb KCOxZi1f/ct5kAUhcre7f5xc7SXKXjrhYlJnqsxBMw2tnOB0hz6sjA4gNPvlGK3w JkpDMQKBgQCgPoqSjmbroWC9oq5iDwRtx6f6fJG7CE91ZFJulunQj6YWOC3zNHEa XvPPGM8fZpJS4e8LiPClkk8nsOoC50neEVGZeEuhdP6m6WNPN3SlP7bXozHOJTh0 mHrk2bUHFlQn8f5KWfLQbdyKBzs7WqCRTOR/gIbfxBlUOs0BN37xhw== -----END RSA PRIVATE KEY-----openid_connect-2.3.1/spec/mock_response/errors/0000755000004100000410000000000014723350730021655 5ustar www-datawww-dataopenid_connect-2.3.1/spec/mock_response/errors/invalid_request.json0000644000004100000410000000004014723350730025740 0ustar www-datawww-data{ "error": "invalid_request" }openid_connect-2.3.1/spec/mock_response/errors/invalid_access_token.json0000644000004100000410000000004514723350730026716 0ustar www-datawww-data{ "error": "invalid_access_token" }openid_connect-2.3.1/spec/mock_response/errors/unknown.json0000644000004100000410000000003214723350730024242 0ustar www-datawww-data{ "unknown": "unknown" }openid_connect-2.3.1/spec/mock_response/errors/insufficient_scope.json0000644000004100000410000000004314723350730026424 0ustar www-datawww-data{ "error": "insufficient_scope" }openid_connect-2.3.1/spec/rack/0000755000004100000410000000000014723350730016412 5ustar www-datawww-dataopenid_connect-2.3.1/spec/rack/oauth2/0000755000004100000410000000000014723350730017614 5ustar www-datawww-dataopenid_connect-2.3.1/spec/rack/oauth2/server/0000755000004100000410000000000014723350730021122 5ustar www-datawww-dataopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/0000755000004100000410000000000014723350730023134 5ustar www-datawww-dataopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/extension/0000755000004100000410000000000014723350730025150 5ustar www-datawww-dataopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/extension/id_token_spec.rb0000644000004100000410000000432114723350730030303 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::IdToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get('/?response_type=id_token&client_id=client&state=state') } let(:redirect_uri) { 'http://client.example.com/callback' } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context 'when id_token is given' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.id_token = id_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include 'state=state' } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:location) { should include 'id_token=id_token' } end end context 'when id_token is missing' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::IdToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end context 'when openid scope given' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id&scope=openid") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::IdToken::Request.new env } it do request.openid_connect_request?.should == true end end endopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/extension/code_and_id_token_spec.rb0000644000004100000410000000412114723350730032115 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::CodeAndIdToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get("/?response_type=code%20id_token&client_id=client&state=state") } let(:redirect_uri) { 'http://client.example.com/callback' } let(:code) { 'authorization_code' } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context "when id_token is given" do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.code = code response.id_token = id_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "code=#{code}" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include "state=state" } context 'when id_token is String' do let(:id_token) { 'non_jwt_string' } its(:location) { should include "id_token=non_jwt_string" } end end context "otherwise" do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.code = code response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::CodeAndIdToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end endopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/extension/id_token_and_token_spec.rb0000644000004100000410000000440214723350730032325 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::IdTokenAndToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get('/?response_type=token%20id_token&client_id=client&state=state') } let(:redirect_uri) { 'http://client.example.com/callback' } let(:bearer_token) { Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context 'when id_token is given' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.access_token = bearer_token response.id_token = id_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "access_token=#{bearer_token.access_token}" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include "token_type=#{bearer_token.token_type}" } its(:location) { should include 'state=state' } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:location) { should include 'id_token=id_token' } end end context 'otherwise' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.access_token = bearer_token response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::IdTokenAndToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end endopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/extension/code_and_id_token_and_token_spec.rb0000644000004100000410000000460214723350730034143 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::Extension::CodeAndIdTokenAndToken do subject { response } let(:request) { Rack::MockRequest.new app } let(:response) { request.get('/?response_type=code%20id_token%20token&client_id=client&state=state') } let(:redirect_uri) { 'http://client.example.com/callback' } let(:bearer_token) { Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') } let(:code) { 'authorization_code' } let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', nonce: 'nonce', exp: 1313424327, iat: 1313420327 ).to_jwt private_key end context 'when id_token is given' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.code = code response.id_token = id_token response.access_token = bearer_token response.approve! end end its(:status) { should == 302 } its(:location) { should include "#{redirect_uri}#" } its(:location) { should include "access_token=#{bearer_token.access_token}" } its(:location) { should include "id_token=#{id_token}" } its(:location) { should include "token_type=#{bearer_token.token_type}" } its(:location) { should include "code=#{code}" } its(:location) { should include 'state=state' } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:location) { should include 'id_token=id_token' } end end context 'otherwise' do let :app do Rack::OAuth2::Server::Authorize.new do |request, response| response.redirect_uri = redirect_uri response.approve! end end it do expect { response }.to raise_error AttrRequired::AttrMissing, "'access_token', 'code', 'id_token' required." end end context 'when error response' do let(:env) { Rack::MockRequest.env_for("/authorize?client_id=client_id") } let(:request) { Rack::OAuth2::Server::Authorize::Extension::CodeAndIdTokenAndToken::Request.new env } it 'should set protocol_params_location = :fragment' do expect { request.bad_request! }.to raise_error(Rack::OAuth2::Server::Authorize::BadRequest) { |e| e.protocol_params_location.should == :fragment } end end endopenid_connect-2.3.1/spec/rack/oauth2/server/authorize/request_with_connect_params_spec.rb0000644000004100000410000000211514723350730032271 0ustar www-datawww-datarequire 'spec_helper' describe Rack::OAuth2::Server::Authorize::RequestWithConnectParams do let(:base_params) do { client_id: 'client_id', redirect_uri: 'https://client.example.com/callback' } end let(:env) { Rack::MockRequest.env_for("/authorize?#{base_params.to_query}&#{params.to_query}") } let(:request) { Rack::OAuth2::Server::Authorize::Request.new env } subject { request } describe 'prompt' do context 'when a space-delimited string given' do let(:params) do {prompt: 'login consent'} end its(:prompt) { should == ['login', 'consent']} end context 'when a single string given' do let(:params) do {prompt: 'login'} end its(:prompt) { should == ['login']} end end describe 'max_age' do context 'when numeric value given' do let(:params) do {max_age: '5'} end its(:max_age) { should == 5} end context 'when non-numeric string given' do let(:params) do {max_age: 'foo'} end its(:max_age) { should == 0} end end endopenid_connect-2.3.1/spec/rack/oauth2/server/token/0000755000004100000410000000000014723350730022242 5ustar www-datawww-dataopenid_connect-2.3.1/spec/rack/oauth2/server/token/refresh_token_spec.rb0000644000004100000410000000264114723350730026442 0ustar www-datawww-datarequire 'spec_helper.rb' describe Rack::OAuth2::Server::Token::RefreshToken do subject { response } let(:request) { Rack::MockRequest.new app } let :response do request.post('/', params: { grant_type: "refresh_token", client_id: "client_id", refresh_token: "refresh_token" }) end let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', exp: 1313424327, iat: 1313420327, nonce: 'nonce', secret: 'secret' ).to_jwt private_key end context "when id_token is given" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') response.id_token = id_token end end its(:status) { should == 200 } its(:body) { should include "\"id_token\":\"#{id_token}\"" } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:body) { should include "\"id_token\":\"id_token\"" } end end context "otherwise" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') end end its(:status) { should == 200 } its(:body) { should_not include "id_token" } end end openid_connect-2.3.1/spec/rack/oauth2/server/token/authorization_code_spec.rb0000644000004100000410000000273614723350730027503 0ustar www-datawww-datarequire 'spec_helper.rb' describe Rack::OAuth2::Server::Token::AuthorizationCode do subject { response } let(:request) { Rack::MockRequest.new app } let :response do request.post('/', params: { grant_type: 'authorization_code', client_id: 'client_id', code: 'authorization_code', redirect_uri: 'http://client.example.com/callback' }) end let :id_token do OpenIDConnect::ResponseObject::IdToken.new( iss: 'https://server.example.com', sub: 'user_id', aud: 'client_id', exp: 1313424327, iat: 1313420327, nonce: 'nonce', secret: 'secret' ).to_jwt private_key end context "when id_token is given" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') response.id_token = id_token end end its(:status) { should == 200 } its(:body) { should include "\"id_token\":\"#{id_token}\"" } context 'when id_token is String' do let(:id_token) { 'id_token' } its(:body) { should include "\"id_token\":\"id_token\"" } end end context "otherwise" do let :app do Rack::OAuth2::Server::Token.new do |request, response| response.access_token = Rack::OAuth2::AccessToken::Bearer.new(access_token: 'access_token') end end its(:status) { should == 200 } its(:body) { should_not include "id_token" } end endopenid_connect-2.3.1/TODOs0000644000004100000410000000031414723350730015411 0ustar www-datawww-data## Discovery * WebFinger User Input Normalization ## Dynamic Client Registration * Update Registration Response Format * Client Metadata "Read" Call Support ## Message * Update UserInfo OpenID Schemaopenid_connect-2.3.1/.rspec0000644000004100000410000000003714723350730015655 0ustar www-datawww-data--color --format=documentation openid_connect-2.3.1/Rakefile0000644000004100000410000000062214723350730016205 0ustar www-datawww-datarequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) namespace :coverage do desc "Open coverage report" task :report do require 'simplecov' `open "#{File.join SimpleCov.coverage_path, 'index.html'}"` end end task :spec do Rake::Task[:'coverage:report'].invoke unless ENV['TRAVIS_RUBY_VERSION'] end task :default => :specopenid_connect-2.3.1/Gemfile0000644000004100000410000000004614723350730016033 0ustar www-datawww-datasource 'https://rubygems.org' gemspec openid_connect-2.3.1/LICENSE0000644000004100000410000000205214723350730015544 0ustar www-datawww-dataCopyright (c) 2011 nov matake MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.openid_connect-2.3.1/VERSION0000644000004100000410000000000514723350730015603 0ustar www-datawww-data2.3.1openid_connect-2.3.1/CHANGELOG.md0000644000004100000410000000066314723350730016356 0ustar www-datawww-data## [Unreleased] ## [2.2.0] - 2022-10-11 ### Changed - automatic json response decoding by @nov in https://github.com/nov/openid_connect/pull/77 ## [2.1.0] - 2022-10-10 ### Changed - mTLS access token by @nov in https://github.com/nov/openid_connect/pull/76 ## [2.0.0] - 2022-10-09 ### Added - start recording CHANGELOG ### Changed - replace httpclient with faraday v2 by @nov in https://github.com/nov/openid_connect/pull/75