Skip to content

Commit

Permalink
wip: centralizing IAL/AAL handling and sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
lmgeorge committed Aug 7, 2024
1 parent 33c0ec9 commit 6d874ee
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 53 deletions.
37 changes: 19 additions & 18 deletions app/forms/openid_connect_authorize_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@ class OpenidConnectAuthorizeForm
*SIMPLE_ATTRS,
].freeze

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].freeze

attr_reader(*ATTRS)

RANDOM_VALUE_MINIMUM_LENGTH = 22
Expand Down Expand Up @@ -101,7 +95,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.join(acr_values),
vtr: vtr,
requested_aal_value: requested_aal_value,
scope: scope.join(' '),
Expand All @@ -117,15 +111,15 @@ def success_redirect_uri
end

def ial_values
acr_values.filter { |acr| acr.include?('ial') || acr.include?('loa') }
Vot::AcrComponentValues.ial_values(acr_values)
end

def aal_values
acr_values.filter { |acr| acr.include?('aal') }
Vot::AcrComponentValues.aal_values(acr_values)
end

def requested_aal_value
highest_level_aal(aal_values) || default_aal_acr
highest_level_aal || default_aal_acr
end

private
Expand Down Expand Up @@ -157,7 +151,7 @@ def parsed_vectors_of_trust

def parse_to_values(param_value, possible_values)
return [] if param_value.blank?
param_value.split(' ').compact & possible_values
Vot::AcrComponentValues.order_by_priority(param_value) & possible_values
end

def parse_vtr(param_value)
Expand Down Expand Up @@ -262,7 +256,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.join(acr_values),
vtr: vtr,
unauthorized_scope: @unauthorized_scope,
code_digest: code ? Digest::SHA256.hexdigest(code) : nil,
Expand Down Expand Up @@ -318,28 +312,35 @@ 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.
by_name[highest_level_ial]&.
requirements&.
include?(: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
ial_values.include?(Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF)
end

def biometric_ial_requested?
ial_values.any? { |ial| Saml::Idp::Constants::BIOMETRIC_IAL_CONTEXTS.include? ial }
ial_values.intersect?(Saml::Idp::Constants::BIOMETRIC_IAL_CONTEXTS)
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 Down
1 change: 1 addition & 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
4 changes: 4 additions & 0 deletions app/models/service_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ def biometric_ial_allowed?
IdentityConfig.store.allowed_biometric_ial_providers.include?(issuer)
end

def identity_proofing_allowed?
ial == 2
end

private

# @return [String,nil]
Expand Down
66 changes: 44 additions & 22 deletions app/services/authn_context_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
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
Expand Down Expand Up @@ -34,15 +52,15 @@ def asserted_ial_acr
def asserted_aal_acr
return if vtr.present?
if acr_aal_component_values.any?
acr_aal_component_values.first.name
highest_aal_acr(acr_aal_component_values.map(&:name)) || acr_aal_component_values.first.name
elsif service_provider&.default_aal.to_i >= 3
Vot::AcrComponentValues::AAL3.name
Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF
elsif service_provider&.default_aal.to_i == 2
Vot::AcrComponentValues::AAL2.name
Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
elsif acr_result.identity_proofing_or_ialmax?
Vot::AcrComponentValues::AAL2.name
Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF
else
Vot::AcrComponentValues::DEFAULT_AAL.name
Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF
end
end

Expand Down Expand Up @@ -104,18 +122,6 @@ def acr_result_without_sp_defaults
end
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
end

def decorate_acr_result_with_user_context(result)
return result unless result.biometric_comparison?

Expand All @@ -139,16 +145,28 @@ 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 ||=
acr_result_without_sp_defaults.component_values.filter do |component_value|
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_AAL.include?(component_value.name)
end
end

def acr_ial_component_values
acr_result_without_sp_defaults.component_values.filter do |component_value|
component_value.name.include?('ial') || component_value.name.include?('loa')
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL.include?(component_value.name)
end
end

Expand All @@ -158,4 +176,8 @@ def biometric_is_required?(result)
map(&:name).
include?(Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF)
end

def highest_aal_acr(aals)
AALS_BY_PRIORITY.find { |aal| aals.include?(aal) }
end
end
Loading

0 comments on commit 6d874ee

Please sign in to comment.