Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
32 changes: 0 additions & 32 deletions .github/workflows/create-deploy-pr.yml

This file was deleted.

18 changes: 0 additions & 18 deletions .github/workflows/create-release.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ GEM
pg (1.5.4)
pg_query (4.2.3)
google-protobuf (>= 3.22.3)
phonelib (0.8.6)
phonelib (0.8.7)
pkcs11 (0.3.4)
premailer (1.21.0)
addressable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Api
module Internal
module TwoFactorAuthentication
class AuthAppController < ApplicationController
include CsrfTokenConcern
include ReauthenticationRequiredConcern

before_action :render_unauthorized, unless: :recently_authenticated_2fa?

after_action :add_csrf_token_header_to_response

respond_to :json

def update
result = ::TwoFactorAuthentication::AuthAppUpdateForm.new(
user: current_user,
configuration_id: params[:id],
).submit(name: params[:name])

analytics.auth_app_update_name_submitted(**result.to_h)

if result.success?
render json: { success: true }
else
render json: { success: false, error: result.first_error_message }, status: :bad_request
end
end

def destroy
result = ::TwoFactorAuthentication::AuthAppDeleteForm.new(
user: current_user,
configuration_id: params[:id],
).submit

analytics.auth_app_delete_submitted(**result.to_h)

if result.success?
create_user_event(:authenticator_disabled)
revoke_remember_device(current_user)
event = PushNotification::RecoveryInformationChangedEvent.new(user: current_user)
PushNotification::HttpPush.deliver(event)
render json: { success: true }
else
render json: { success: false, error: result.first_error_message }, status: :bad_request
end
end

private

def render_unauthorized
render json: { error: 'Unauthorized' }, status: :unauthorized
end
end
end
end
end
1 change: 0 additions & 1 deletion app/controllers/idv/in_person/usps_locations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ class UspsLocationsController < ApplicationController
include RenderConditionConcern
include UspsInPersonProofing
include EffectiveUser
include UspsInPersonProofing

check_or_render_not_found -> { InPersonConfig.enabled? }

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/idv/welcome_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def show
call('welcome', :view, true)

@sp_name = decorated_sp_session.sp_name || APP_NAME
@title = t('doc_auth.headings.getting_started', sp_name: @sp_name)
@title = t('doc_auth.headings.welcome', sp_name: @sp_name)
end

def update
Expand Down
147 changes: 147 additions & 0 deletions app/controllers/test/oidc_test_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
require './spec/support/oidc_auth_helper'
module Test
class OidcTestController < ApplicationController
include OidcAuthHelper

BIOMETRIC_REQUIRED = 'biometric-comparison-required'

def initialize
@client_id = 'urn:gov:gsa:openidconnect:sp:test'
super
end

def index
# default to require
@start_url_selfie = "#{test_oidc_auth_request_url}?ial=biometric-comparison-required"
@start_url_ial2 = "#{test_oidc_auth_request_url}?ial=2"
@start_url_ial1 = "#{test_oidc_auth_request_url}?ial=1"
update_service_provider
end

def auth_request
ial = prepare_step_up_flow(ial: params[:ial])

idp_url = authorization_url(
ial: ial,
aal: params[:aal],
)

Rails.logger.info("Redirecting to #{idp_url}")

redirect_to(idp_url)
end

def auth_result
redirect_to('/')
end

def logout
redirect_to(logout_uri)
end

def authorization_url(ial:, aal: nil)
authorization_endpoint = openid_configuration[:authorization_endpoint]
params = ial2_params(
client_id: client_id,
acr_values: acr_values(ial: ial, aal: aal),
biometric_comparison_required: ial == BIOMETRIC_REQUIRED,
state: random_value,
nonce: random_value,
)
request_params = params.merge(
scope: scopes_for(ial),
redirect_uri: test_oidc_auth_result_url,
).compact.to_query
"#{authorization_endpoint}?#{request_params}"
end

def prepare_step_up_flow(ial:)
if ial == 'step-up'
ial = '1'
end
ial
end

def scopes_for(ial)
case ial
when '0'
'openid email social_security_number'
when '1', nil
'openid email'
when '2', BIOMETRIC_REQUIRED
'openid email profile social_security_number phone address'
else
raise ArgumentError.new("Unexpected IAL: #{ial.inspect}")
end
end

def acr_values(ial:, aal:)
ial_value = {
'0' => 'http://idmanagement.gov/ns/assurance/ial/0',
nil => 'http://idmanagement.gov/ns/assurance/ial/1',
'' => 'http://idmanagement.gov/ns/assurance/ial/1',
'1' => 'http://idmanagement.gov/ns/assurance/ial/1',
'2' => 'http://idmanagement.gov/ns/assurance/ial/2',
'biometric-comparison-required' => 'http://idmanagement.gov/ns/assurance/ial/2',
}[ial]
aal_value = {
'2' => 'http://idmanagement.gov/ns/assurance/aal/2',
'2-phishing_resistant' => 'http://idmanagement.gov/ns/assurance/aal/2?phishing_resistant=true',
'2-hspd12' => 'http://idmanagement.gov/ns/assurance/aal/2?hspd12=true',
}[aal]
[ial_value, aal_value].compact.join(' ')
end

def json(response)
JSON.parse(response.to_s).with_indifferent_access
end

def random_value
SecureRandom.hex
end

def client_id
@client_id
end

private

def logout_uri
endpoint = openid_configuration[:end_session_endpoint]
request_params = {
client_id: client_id,
post_logout_redirect_uri: '/',
state: SecureRandom.hex,
}.to_query

"#{endpoint}?#{request_params}"
end

def openid_configuration
@openid_configuration ||= OpenidConnectConfigurationPresenter.new.configuration
end

def idp_public_key
@idp_public_key ||= load_idp_public_key
end

def load_idp_public_key
keys = OpenidConnectCertsPresenter.new.certs[:keys]
JSON::JWK.new(keys.first).to_key
end

def update_service_provider
return @service_provider if defined?(@service_provider)
@service_provider = ServiceProvider.find_by(issuer: client_id)
# inject root url
changed = false
[test_oidc_logout_url, test_oidc_auth_result_url, root_url].each do |url|
if @service_provider&.redirect_uris && !@service_provider.redirect_uris.include?(url)
@service_provider.redirect_uris.append(url)
changed = true
end
end
@service_provider.save! if changed
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class WebauthnVerificationController < ApplicationController
include TwoFactorAuthenticatable

before_action :check_sp_required_mfa
before_action :check_if_device_supports_platform_auth, only: :show
before_action :confirm_webauthn_enabled, only: :show

def show
Expand Down Expand Up @@ -33,6 +34,17 @@ def confirm

private

def check_if_device_supports_platform_auth
return unless user_session.has_key?(:platform_authenticator_available)
if platform_authenticator? && !device_supports_webauthn_platform?
redirect_to login_two_factor_options_url
end
end

def device_supports_webauthn_platform?
user_session.delete(:platform_authenticator_available) == true
end

def handle_webauthn_result(result)
if result.success?
handle_valid_webauthn
Expand Down
65 changes: 65 additions & 0 deletions app/controllers/users/auth_app_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module Users
class AuthAppController < ApplicationController
include ReauthenticationRequiredConcern

before_action :confirm_two_factor_authenticated
before_action :confirm_recently_authenticated_2fa
before_action :set_form
before_action :validate_configuration_exists

def edit; end

def update
result = form.submit(name: params.dig(:form, :name))

analytics.auth_app_update_name_submitted(**result.to_h)

if result.success?
flash[:success] = t('two_factor_authentication.auth_app.renamed')
redirect_to account_path
else
flash.now[:error] = result.first_error_message
render :edit
end
end

def destroy
result = form.submit

analytics.auth_app_delete_submitted(**result.to_h)

if result.success?
flash[:success] = t('two_factor_authentication.auth_app.deleted')
create_user_event(:authenticator_disabled)
revoke_remember_device(current_user)
event = PushNotification::RecoveryInformationChangedEvent.new(user: current_user)
PushNotification::HttpPush.deliver(event)
redirect_to account_path
else
flash[:error] = result.first_error_message
redirect_to edit_auth_app_path(id: params[:id])
end
end

private

def form
@form ||= form_class.new(user: current_user, configuration_id: params[:id])
end

alias_method :set_form, :form

def form_class
case action_name
when 'edit', 'update'
TwoFactorAuthentication::AuthAppUpdateForm
when 'destroy'
TwoFactorAuthentication::AuthAppDeleteForm
end
end

def validate_configuration_exists
render_not_found if form.configuration.blank?
end
end
end
2 changes: 2 additions & 0 deletions app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ def handle_valid_authentication
user_id: current_user.id,
email: auth_params[:email],
)
user_session[:platform_authenticator_available] =
params[:platform_authenticator_available] == 'true'
redirect_to next_url_after_valid_authentication
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Users
class TwoFactorAuthenticationController < ApplicationController
include TwoFactorAuthenticatable
include ApplicationHelper
include ActionView::Helpers::DateHelper

before_action :check_remember_device_preference
Expand Down
Loading