Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blocked] Return correct AAL value in user_info JWT #11027

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
55 changes: 41 additions & 14 deletions app/forms/openid_connect_authorize_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ class OpenidConnectAuthorizeForm
validate :validate_verified_within_duration, if: :verified_within_allowed?

def initialize(params)
@acr_values = parse_to_values(params[:acr_values], Saml::Idp::Constants::VALID_AUTHN_CONTEXTS)
@acr_values = parse_acr_values(params[:acr_values], Saml::Idp::Constants::VALID_AUTHN_CONTEXTS)
@vtr = parse_vtr(params[:vtr])
SIMPLE_ATTRS.each { |key| instance_variable_set(:"@#{key}", params[key]) }
@prompt ||= 'select_account'
@scope = parse_to_values(params[:scope], scopes)
@scope = parse_scope(params[:scope], scopes)
@unauthorized_scope = check_for_unauthorized_scope(params)

if verified_within_allowed?
Expand Down Expand Up @@ -115,7 +115,7 @@ def link_identity_to_service_provider(
nonce: nonce,
rails_session_id: rails_session_id,
ial: ial,
acr_values: acr_values&.join(' '),
acr_values: Vot::AcrComponentValues.build(acr_values),
vtr: vtr,
requested_aal_value: requested_aal_value,
scope: scope.join(' '),
Expand All @@ -140,13 +140,14 @@ def aal_values
end

def requested_aal_value
highest_level_aal(aal_values) ||
Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF
highest_level_aal || default_aal_acr
end

private

attr_reader :identity, :success
# @return [ServiceProviderIdentity]
attr_reader :identity
attr_reader :success

def code
identity&.session_uuid
Expand All @@ -171,11 +172,15 @@ def parsed_vectors_of_trust
end
end

def parse_to_values(param_value, possible_values)
def parse_scope(param_value, possible_values)
return [] if param_value.blank?
param_value.split(' ').compact & possible_values
end

def parse_acr_values(param_value, possible_values = Saml::Idp::Constants::VALID_AUTHN_CONTEXTS)
Vot::AcrComponentValues.order_by_priority(param_value) & possible_values
end

def parse_vtr(param_value)
return if !IdentityConfig.store.use_vot_in_sp_requests
return if param_value.blank?
Expand Down Expand Up @@ -278,7 +283,7 @@ def extra_analytics_attributes
allow_prompt_login: service_provider&.allow_prompt_login,
redirect_uri: result_uri,
scope: scope&.sort&.join(' '),
acr_values: acr_values&.sort&.join(' '),
acr_values: Vot::AcrComponentValues.build(acr_values),
vtr: vtr,
unauthorized_scope: @unauthorized_scope,
code_digest: code ? Digest::SHA256.hexdigest(code) : nil,
Expand Down Expand Up @@ -335,28 +340,33 @@ def identity_proofing_requested?
if parsed_vectors_of_trust.present?
parsed_vectors_of_trust.any?(&:identity_proofing?)
else
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max] == 2
Vot::AcrComponentValues.
includes_requirements?(highest_level_ial, :identity_proofing)
end
end

def identity_proofing_service_provider?
service_provider&.ial.to_i >= 2
service_provider&.identity_proofing_allowed?
end

def ialmax_allowed_for_sp?
IdentityConfig.store.allowed_ialmax_providers.include?(client_id)
end

def ialmax_requested?
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max] == 0
Vot::AcrComponentValues.includes_requirements?(highest_level_ial, :ialmax)
end

def biometric_ial_requested?
ial_values.any? { |ial| Saml::Idp::Constants::BIOMETRIC_IAL_CONTEXTS.include? ial }
Vot::AcrComponentValues.includes_requirements?(highest_level_ial, :biometric_comparison)
end

def highest_level_ial
@highest_level_ial ||= Vot::AcrComponentValues.find_highest_priority(ial_values)
end

def highest_level_aal(aal_values)
AALS_BY_PRIORITY.find { |aal| aal_values.include?(aal) }
def highest_level_aal
@highest_level_aal ||= Vot::AcrComponentValues.find_highest_priority(aal_values)
end

def verified_within_allowed?
Expand All @@ -366,4 +376,21 @@ def verified_within_allowed?
def semantic_authn_contexts_requested?
Saml::Idp::Constants::SEMANTIC_ACRS.intersect?(acr_values)
end

def request_authn_context_resolver
@request_authn_context_resolver ||= AuthnContextResolver.new(
service_provider: service_provider,
user: nil,
vtr: nil,
acr_values: acr_values,
)
end

def requested_authn_context
@requested_authn_context ||= request_authn_context_resolver.result
end

def default_aal_acr
request_authn_context_resolver.default_aal_acr
end
end
2 changes: 2 additions & 0 deletions app/models/federated_protocols/oidc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module FederatedProtocols
class Oidc
# @param request [OpenidConnectAuthorizeForm]
def initialize(request)
@request = request
end
Expand Down Expand Up @@ -36,6 +37,7 @@ def service_provider

private

# @return [OpenidConnectAuthorizeForm]
attr_reader :request
end
end
12 changes: 10 additions & 2 deletions app/presenters/openid_connect_user_info_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def user_info
info.merge!(x509_attributes) if scoper.x509_scopes_requested?
info[:verified_at] = verified_at if scoper.verified_at_requested?
if identity.vtr.nil?
info[:ial] = authn_context_resolver.asserted_ial_acr
info[:aal] = identity.requested_aal_value
info[:ial] = asserted_ial_value
info[:aal] = asserted_aal_value
else
info[:vot] = vot_values
info[:vtm] = IdentityConfig.store.vtm_url
Expand All @@ -40,6 +40,14 @@ def url_options

private

def asserted_ial_value
authn_context_resolver.asserted_ial_acr
end

def asserted_aal_value
identity.requested_aal_value.presence || authn_context_resolver.asserted_aal_acr
end

def vot_values
AuthnContextResolver.new(
user: identity.user,
Expand Down
92 changes: 74 additions & 18 deletions app/services/authn_context_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,30 @@
class AuthnContextResolver
attr_reader :user, :service_provider, :vtr, :acr_values

AALS_BY_PRIORITY = [
Saml::Idp::Constants::AAL2_HSPD12_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::AAL1_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF,
].freeze
IALS_BY_PRIORITY = [
Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF,
Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF,
].freeze

def initialize(user:, service_provider:, vtr:, acr_values:)
@user = user
@service_provider = service_provider
@vtr = vtr
@acr_values = acr_values
@acr_values = Vot::AcrComponentValues.build(acr_values)
end

def result
Expand All @@ -32,6 +51,33 @@ def asserted_ial_acr
end
end

def asserted_aal_acr
return if vtr.present?
if acr_aal_component_values.present?
highest_aal_acr(acr_aal_component_values) || acr_aal_component_values.first.name
# elsif service_provider&.default_aal.to_i >= 3
# Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF
# elsif service_provider&.default_aal.to_i == 2
# Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
# elsif acr_result.identity_proofing_or_ialmax?
# Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
else
default_aal_acr
end
end

def default_aal_acr
if service_provider&.default_aal.to_i >= 3
Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF
elsif service_provider&.default_aal.to_i == 2
Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
elsif acr_result_with_sp_defaults.identity_proofing_or_ialmax?
Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
else
Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF
end
end

private

def selected_vtr_parser_result_from_vtr_list
Expand Down Expand Up @@ -83,19 +129,11 @@ def acr_result_with_sp_defaults
end

def acr_result_without_sp_defaults
@acr_result_without_sp_defaults ||= Vot::Parser.new(acr_values: acr_values).parse
end

def result_with_sp_aal_defaults(result)
if acr_aal_component_values.any?
result
elsif service_provider&.default_aal.to_i == 2
result.with(aal2?: true)
elsif service_provider&.default_aal.to_i >= 3
result.with(aal2?: true, phishing_resistant?: true)
else
result
end
@acr_result_without_sp_defaults ||= if acr_values.present?
Vot::Parser.new(acr_values: acr_values).parse
else
Vot::Parser::Result.no_sp_result
end
end

def decorate_acr_result_with_user_context(result)
Expand All @@ -121,13 +159,27 @@ def result_with_sp_ial_defaults(result)
end
end

def acr_aal_component_values
acr_result_without_sp_defaults.component_values.filter do |component_value|
component_value.name.include?('aal') ||
component_value.name == Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF
def result_with_sp_aal_defaults(result)
if acr_aal_component_values.any?
result
elsif service_provider&.default_aal.to_i == 2
result.with(aal2?: true)
elsif service_provider&.default_aal.to_i >= 3
result.with(aal2?: true, phishing_resistant?: true)
else
result
end
end

def acr_aal_component_values
@acr_aal_component_values ||=
Vot::AcrComponentValues.order_by_priority(
Vot::AcrComponentValues.aal_component_values(
acr_result_without_sp_defaults.component_values,
),
)
end

def acr_ial_component_values
acr_result_without_sp_defaults.component_values.filter do |component_value|
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL.include?(component_value.name)
Expand All @@ -147,4 +199,8 @@ def use_semantic_authn_contexts?
@use_semantic_authn_contexts ||= service_provider&.semantic_authn_contexts_allowed? &&
Vot::AcrComponentValues.any_semantic_acrs?(acr_values)
end

def highest_aal_acr(aals)
Vot::AcrComponentValues.find_highest_priority(aals)
end
end
Loading