Skip to content
55 changes: 52 additions & 3 deletions app/forms/openid_connect_authorize_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class OpenidConnectAuthorizeForm
ATTRS = [
:unauthorized_scope,
:acr_values,
:vtr,
:scope,
:verified_within,
:biometric_comparison_required,
Expand All @@ -37,7 +38,7 @@ class OpenidConnectAuthorizeForm
RANDOM_VALUE_MINIMUM_LENGTH = 22
MINIMUM_REPROOF_VERIFIED_WITHIN_DAYS = 30

validates :acr_values, presence: true
validates :acr_values, presence: true, if: ->(form) { form.vtr.empty? }
validates :client_id, presence: true
validates :redirect_uri, presence: true
validates :scope, presence: true
Expand All @@ -49,6 +50,7 @@ class OpenidConnectAuthorizeForm
validates :code_challenge_method, inclusion: { in: %w[S256] }, if: :code_challenge

validate :validate_acr_values
validate :validate_vtr
validate :validate_client_id
validate :validate_scope
validate :validate_unauthorized_scope
Expand All @@ -59,6 +61,7 @@ class OpenidConnectAuthorizeForm

def initialize(params)
@acr_values = parse_to_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)
Expand Down Expand Up @@ -119,15 +122,27 @@ def ial_context
end

def ial
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max]
if parsed_vector_of_trust&.identity_proofing?
2
elsif parsed_vector_of_trust.present?
1
else
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL[ial_values.sort.max]
end
end

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

def aal
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_AAL[requested_aal_value]
if parsed_vector_of_trust&.aal2?
2
elsif parsed_vector_of_trust.present?
1
else
Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_AAL[requested_aal_value]
end
end

def requested_aal_value
Expand Down Expand Up @@ -163,7 +178,18 @@ def parse_to_values(param_value, possible_values)
param_value.split(' ').compact & possible_values
end

def parse_vtr(param_value)
Comment thread
jmhooper marked this conversation as resolved.
return if !IdentityConfig.store.use_vot_in_sp_requests
return [] if param_value.blank?

JSON.parse(param_value)
rescue JSON::ParserError
nil
end

def validate_acr_values
return if vtr.present?

if acr_values.empty?
errors.add(
:acr_values, t('openid_connect.authorization.errors.no_valid_acr_values'),
Expand All @@ -177,6 +203,15 @@ def validate_acr_values
end
end

def validate_vtr
return if vtr.blank?
return if parsed_vector_of_trust.present?
errors.add(
:vtr, t('openid_connect.authorization.errors.no_valid_vtr'),
type: :no_valid_vtr
)
end

# This checks that the SP matches something in the database
# OpenidConnect::AuthorizationController#check_sp_active checks that it's currently active
def validate_client_id
Expand Down Expand Up @@ -246,6 +281,7 @@ def extra_analytics_attributes
redirect_uri: result_uri,
scope: scope&.sort&.join(' '),
acr_values: acr_values&.sort&.join(' '),
vtr: vtr,
Comment thread
jmhooper marked this conversation as resolved.
unauthorized_scope: @unauthorized_scope,
code_digest: code ? Digest::SHA256.hexdigest(code) : nil,
code_challenge_present: code_challenge.present?,
Expand Down Expand Up @@ -275,6 +311,19 @@ def scopes
OpenidConnectAttributeScoper::VALID_IAL1_SCOPES
end

def parsed_vector_of_trust
return @parsed_vector_of_trust if defined?(@parsed_vector_of_trust)
return @parsed_vector_of_trust = nil if vtr.blank?

@parsed_vector_of_trust = begin
if vtr.is_a?(Array) && !vtr.empty?
Vot::Parser.new(vector_of_trust: vtr.first).parse
end
rescue Vot::Parser::ParseException
nil
Comment on lines +322 to +323
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would we want to catch this exception and add an error for it? or no

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validate_vtr param has kind of a roundabout way of doing it. It looks like see if the param is present and this is nil.

I would like to find a more direct and elegant way of doing it. The trick with this getting reliably re-evaluated with valid? calls could get messy.

end
end

def validate_privileges
if (ial2_requested? && !ial_context.ial2_service_provider?) ||
(ial_context.ialmax_requested? &&
Expand Down
8 changes: 8 additions & 0 deletions app/models/federated_protocols/oidc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ def aal
request.aal_values.sort.max
end

def acr_values
[aal, ial].compact.join(' ')
end

def vtr
request.vtr
end

def requested_attributes
OpenidConnectAttributeScoper.new(request.scope).requested_attributes
end
Expand Down
8 changes: 8 additions & 0 deletions app/models/federated_protocols/saml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ def aal
request.requested_aal_authn_context
end

def acr_values
[aal, ial].compact.join(' ')
end

def vtr
nil
end

def requested_attributes
@requested_attributes ||= SamlRequestPresenter.new(
request: request, service_provider: current_service_provider,
Expand Down
8 changes: 5 additions & 3 deletions app/models/service_provider_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class ServiceProviderRequest
# since these objects are serialized to/from Redis and may be present
# upon deployment
attr_accessor :uuid, :issuer, :url, :ial, :aal, :requested_attributes,
:biometric_comparison_required
:biometric_comparison_required, :acr_values, :vtr

def initialize(
uuid: nil,
Expand All @@ -13,8 +13,8 @@ def initialize(
aal: nil,
requested_attributes: [],
biometric_comparison_required: false,
acr_values: nil, # rubocop:disable Lint/UnusedMethodArgument
vtr: nil # rubocop:disable Lint/UnusedMethodArgument
acr_values: nil,
vtr: nil
)
@uuid = uuid
@issuer = issuer
Expand All @@ -23,6 +23,8 @@ def initialize(
@aal = aal
@requested_attributes = requested_attributes&.map(&:to_s)
@biometric_comparison_required = biometric_comparison_required
@acr_values = acr_values
@vtr = vtr
end

def ==(other)
Expand Down
3 changes: 3 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3636,12 +3636,14 @@ def openid_connect_bearer_token(success:, ial:, client_id:, errors:, **extra)
# @param [String] client_id
# @param [String] scope
# @param [Array] acr_values
# @param [Array] vtr
# @param [Boolean] unauthorized_scope
# @param [Boolean] user_fully_authenticated
def openid_connect_request_authorization(
client_id:,
scope:,
acr_values:,
vtr:,
unauthorized_scope:,
user_fully_authenticated:,
**extra
Expand All @@ -3651,6 +3653,7 @@ def openid_connect_request_authorization(
client_id: client_id,
scope: scope,
acr_values: acr_values,
vtr: vtr,
unauthorized_scope: unauthorized_scope,
user_fully_authenticated: user_fully_authenticated,
**extra,
Expand Down
2 changes: 2 additions & 0 deletions app/services/service_provider_request_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ def attributes
issuer: protocol.issuer,
ial: protocol.ial,
aal: protocol.aal,
acr_values: protocol.acr_values,
vtr: protocol.vtr,
requested_attributes: protocol.requested_attributes,
biometric_comparison_required: protocol.biometric_comparison_required?,
uuid: request_id,
Expand Down
13 changes: 11 additions & 2 deletions app/services/service_provider_request_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def self.find_or_create_by(uuid:)
spr = ServiceProviderRequest.new(
uuid: uuid, issuer: nil, url: nil, ial: nil,
aal: nil, requested_attributes: nil,
biometric_comparison_required: false
biometric_comparison_required: false, acr_values: nil, vtr: nil
)
yield(spr)
create(
Expand All @@ -45,13 +45,22 @@ def self.find_or_create_by(uuid:)
aal: spr.aal,
requested_attributes: spr.requested_attributes,
biometric_comparison_required: spr.biometric_comparison_required,
acr_values: spr.acr_values,
vtr: spr.vtr,
)
end

def self.create(hash)
uuid = hash[:uuid]
obj = hash.slice(
:issuer, :url, :ial, :aal, :requested_attributes, :biometric_comparison_required
:issuer,
:url,
:ial,
:aal,
:requested_attributes,
:biometric_comparison_required,
:acr_values,
:vtr,
)
write(obj, uuid)
hash_to_spr(obj, uuid)
Expand Down
2 changes: 2 additions & 0 deletions app/services/store_sp_metadata_in_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def update_session
request_id: sp_request.uuid,
requested_attributes: sp_request.requested_attributes,
biometric_comparison_required: sp_request.biometric_comparison_required,
acr_values: sp_request.acr_values,
vtr: sp_request.vtr,
}
end

Expand Down
2 changes: 2 additions & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ team_ursula_email: ''
test_ssn_allowed_list: ''
totp_code_interval: 30
unauthorized_scope_enabled: false
use_vot_in_sp_requests: true
usps_upload_enabled: false
usps_upload_sftp_timeout: 5
valid_authn_contexts: '["http://idmanagement.gov/ns/assurance/loa/1", "http://idmanagement.gov/ns/assurance/loa/3", "http://idmanagement.gov/ns/assurance/ial/1", "http://idmanagement.gov/ns/assurance/ial/2", "http://idmanagement.gov/ns/assurance/ial/0", "http://idmanagement.gov/ns/assurance/ial/2?strict=true", "urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo", "http://idmanagement.gov/ns/assurance/aal/2", "http://idmanagement.gov/ns/assurance/aal/3", "http://idmanagement.gov/ns/assurance/aal/3?hspd12=true","http://idmanagement.gov/ns/assurance/aal/2?phishing_resistant=true","http://idmanagement.gov/ns/assurance/aal/2?hspd12=true"]'
Expand Down Expand Up @@ -494,6 +495,7 @@ production:
state_tracking_enabled: false
telephony_adapter: pinpoint
use_kms: true
use_vot_in_sp_requests: false
usps_auth_token_refresh_job_enabled: true
usps_confirmation_max_days: 30
usps_upload_sftp_directory: ''
Expand Down
1 change: 1 addition & 0 deletions config/locales/openid_connect/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ en:
no_auth: The acr_values are not authorized
no_valid_acr_values: No acceptable acr_values found
no_valid_scope: No valid scope values found
no_valid_vtr: No acceptable vots found
prompt_invalid: No valid prompt values found
redirect_uri_invalid: redirect_uri is invalid
redirect_uri_no_match: redirect_uri does not match registered redirect_uri
Expand Down
1 change: 1 addition & 0 deletions config/locales/openid_connect/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ es:
no_auth: Los acr_values no están autorizados
no_valid_acr_values: ial_valores encontrados no aceptables
no_valid_scope: No se han encontrado valores de magnitud válidos
no_valid_vtr: vots encontrados no aceptables
prompt_invalid: Prompt no es válido
redirect_uri_invalid: Redirect_uri no es válido
redirect_uri_no_match: Redirect_uri no coincide con redirect_uri registrado
Expand Down
1 change: 1 addition & 0 deletions config/locales/openid_connect/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fr:
no_auth: Les acr_values ne sont pas autorisées
no_valid_acr_values: Valeurs acr_values inacceptables trouvées
no_valid_scope: Aucune étendue de données valide trouvée
no_valid_vtr: vots encontrados no aceptables
prompt_invalid: prompt est non valide
redirect_uri_invalid: redirect_uri est non valide
redirect_uri_no_match: redirect_uri ne correspond pas au redirect_uri enregistré
Expand Down
1 change: 1 addition & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ def self.build_store(config_map)
config.add(:unauthorized_scope_enabled, type: :boolean)
config.add(:use_dashboard_service_providers, type: :boolean)
config.add(:use_kms, type: :boolean)
config.add(:use_vot_in_sp_requests, type: :boolean)
config.add(:usps_auth_token_refresh_job_enabled, type: :boolean)
config.add(:usps_confirmation_max_days, type: :integer)
config.add(:usps_ipp_client_id, type: :string)
Expand Down
Loading