Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions himari/lib/himari/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ def msg(key, default = nil)
issuer: config.issuer,
registration_endpoint: dynamic_clients_enabled? ? "#{config.issuer}/public/oidc/register" : nil,
client_id_metadata_document_supported: metadata_clients_enabled?,
scopes_supported: config.scopes_supported,
claims_supported: config.claims_supported,
).call(env)
end
# OpenID Connect Discovery 1.0
Expand Down
7 changes: 5 additions & 2 deletions himari/lib/himari/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

module Himari
class Config
def initialize(issuer:, storage:, providers: [], log_output: $stdout, log_level: Logger::INFO, preserve_rack_logger: false, custom_templates: {}, custom_messages: {}, release_fragment: nil)
def initialize(issuer:, storage:, providers: [], log_output: $stdout, log_level: Logger::INFO, preserve_rack_logger: false, custom_templates: {}, custom_messages: {}, release_fragment: nil, scopes_supported: [], claims_supported: [])
@issuer = issuer
@providers = providers
@storage = storage
Expand All @@ -19,9 +19,12 @@ def initialize(issuer:, storage:, providers: [], log_output: $stdout, log_level:
@custom_messages = custom_messages
@custom_templates = custom_templates
@release_fragment = release_fragment

@scopes_supported = scopes_supported
@claims_supported = claims_supported
end

attr_reader :issuer, :providers, :storage, :preserve_rack_logger, :custom_messages, :custom_templates, :release_fragment
attr_reader :issuer, :providers, :storage, :preserve_rack_logger, :custom_messages, :custom_templates, :release_fragment, :scopes_supported, :claims_supported

def logger
@logger ||= Logger.new(@log_output).tap do |l|
Expand Down
20 changes: 15 additions & 5 deletions himari/lib/himari/services/oidc_provider_metadata_endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,42 @@
module Himari
module Services
class OidcProviderMetadataEndpoint
# Scopes and claims Himari always advertises; configured values are merged on top.
DEFAULT_SCOPES_SUPPORTED = %w(openid refresh_token).freeze
DEFAULT_CLAIMS_SUPPORTED = %w(sub iss iat nbf exp).freeze

# @param signing_key_provider [Himari::ProviderChain<Himari::SigningKey>]
# @param registration_endpoint [String, nil] advertised when Dynamic Client Registration is enabled
# @param client_id_metadata_document_supported [Boolean] advertised when OAuth Client ID Metadata Document support is enabled
def initialize(signing_key_provider:, issuer:, registration_endpoint: nil, client_id_metadata_document_supported: false)
# @param scopes_supported [Array<String>] extra scopes to advertise alongside the defaults
# @param claims_supported [Array<String>] extra claims to advertise alongside the defaults
def initialize(signing_key_provider:, issuer:, registration_endpoint: nil, client_id_metadata_document_supported: false, scopes_supported: [], claims_supported: [])
@signing_key_provider = signing_key_provider
@issuer = issuer
@registration_endpoint = registration_endpoint
@client_id_metadata_document_supported = client_id_metadata_document_supported
@scopes_supported = scopes_supported
@claims_supported = claims_supported
end

def app
self
end

def call(env)
Handler.new(signing_key_provider: @signing_key_provider, issuer: @issuer, registration_endpoint: @registration_endpoint, client_id_metadata_document_supported: @client_id_metadata_document_supported, env: env).response
Handler.new(signing_key_provider: @signing_key_provider, issuer: @issuer, registration_endpoint: @registration_endpoint, client_id_metadata_document_supported: @client_id_metadata_document_supported, scopes_supported: @scopes_supported, claims_supported: @claims_supported, env: env).response
end

class Handler
class InvalidToken < StandardError; end

def initialize(signing_key_provider:, issuer:, env:, registration_endpoint: nil, client_id_metadata_document_supported: false)
def initialize(signing_key_provider:, issuer:, env:, registration_endpoint: nil, client_id_metadata_document_supported: false, scopes_supported: [], claims_supported: [])
@signing_key_provider = signing_key_provider
@issuer = issuer
@registration_endpoint = registration_endpoint
@client_id_metadata_document_supported = client_id_metadata_document_supported
@scopes_supported = scopes_supported
@claims_supported = claims_supported
@env = env
end

Expand All @@ -42,14 +52,14 @@ def metadata
jwks_uri: "#{@issuer}/public/jwks",
registration_endpoint: @registration_endpoint,
client_id_metadata_document_supported: @client_id_metadata_document_supported ? true : nil,
scopes_supported: %w(openid),
scopes_supported: (DEFAULT_SCOPES_SUPPORTED + @scopes_supported).uniq,
response_types_supported: ['code'], # violation: dynamic OpenID Provider MUST support code, id_token, token+id_token
grant_types_supported: %w(authorization_code refresh_token),
token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post none),
code_challenge_methods_supported: %w(S256 plain),
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: signing_keys.map(&:alg).uniq.sort,
claims_supported: %w(sub iss iat nbf exp),
claims_supported: (DEFAULT_CLAIMS_SUPPORTED + @claims_supported).uniq,
}.compact
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
let(:signing_key_provider) { double('chain', collect: keys) }
let(:registration_endpoint) { nil }
let(:client_id_metadata_document_supported) { false }
let(:app) { described_class.new(signing_key_provider: signing_key_provider, issuer: 'https://test.invalid', registration_endpoint: registration_endpoint, client_id_metadata_document_supported: client_id_metadata_document_supported) }
let(:scopes_supported) { [] }
let(:claims_supported) { [] }
let(:app) { described_class.new(signing_key_provider: signing_key_provider, issuer: 'https://test.invalid', registration_endpoint: registration_endpoint, client_id_metadata_document_supported: client_id_metadata_document_supported, scopes_supported: scopes_supported, claims_supported: claims_supported) }

context "with non-GET request" do
it "returns 404" do
Expand Down Expand Up @@ -70,6 +72,39 @@
expect(body[:client_id_metadata_document_supported]).to eq(true)
end
end

context "without additional scopes/claims" do
it "advertises the default scopes and claims" do
get '/.well-known/openid-configuration'
body = JSON.parse(last_response.body, symbolize_names: true)
expect(body[:scopes_supported]).to eq(%w(openid refresh_token))
expect(body[:claims_supported]).to eq(%w(sub iss iat nbf exp))
end
end

context "with additional scopes/claims" do
let(:scopes_supported) { %w(profile email) }
let(:claims_supported) { %w(name email) }

it "merges them with the defaults" do
get '/.well-known/openid-configuration'
body = JSON.parse(last_response.body, symbolize_names: true)
expect(body[:scopes_supported]).to eq(%w(openid refresh_token profile email))
expect(body[:claims_supported]).to eq(%w(sub iss iat nbf exp name email))
end
end

context "with additional scopes/claims overlapping the defaults" do
let(:scopes_supported) { %w(openid profile) }
let(:claims_supported) { %w(sub name) }

it "de-duplicates while preserving order" do
get '/.well-known/openid-configuration'
body = JSON.parse(last_response.body, symbolize_names: true)
expect(body[:scopes_supported]).to eq(%w(openid refresh_token profile))
expect(body[:claims_supported]).to eq(%w(sub iss iat nbf exp name))
end
end
end

context "with 2 keys" do
Expand Down