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
51 changes: 5 additions & 46 deletions spec/features/multiple_emails/sp_sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
fill_in_code_with_last_phone_otp
click_submit_default
click_agree_and_continue if current_path == sign_up_completed_path
decoded_id_token = fetch_oidc_id_token_info
expect(decoded_id_token[:email]).to eq(emails.first)
expect(decoded_id_token[:all_emails]).to be_nil
expect(oidc_decoded_id_token[:email]).to eq(emails.first)
expect(oidc_decoded_id_token[:all_emails]).to be_nil

Capybara.reset_session!
end
Expand All @@ -41,8 +40,7 @@

expect(current_path).to eq(sign_up_completed_path)
click_agree_and_continue
decoded_id_token = fetch_oidc_id_token_info
expect(decoded_id_token[:email]).to eq(emails.second)
expect(oidc_decoded_id_token[:email]).to eq(emails.second)
end

scenario 'signing in with OIDC after deleting email linked to identity' do
Expand All @@ -69,8 +67,7 @@
# Sign in again to partner application
visit_idp_from_oidc_sp(scope: 'openid email')

decoded_id_token = fetch_oidc_id_token_info
expect(decoded_id_token[:email]).to eq(email1.email)
expect(oidc_decoded_id_token[:email]).to eq(email1.email)
end

scenario 'signing in with SAML sends the email address used to sign in' do
Expand Down Expand Up @@ -161,8 +158,7 @@
click_submit_default
click_agree_and_continue

decoded_id_token = fetch_oidc_id_token_info
expect(decoded_id_token[:all_emails]).to match_array(emails)
expect(oidc_decoded_id_token[:all_emails]).to match_array(emails)
end

scenario 'signing in with SAML sends all emails' do
Expand Down Expand Up @@ -206,41 +202,4 @@ def visit_idp_from_oidc_sp(scope:)
nonce: SecureRandom.hex,
)
end

def fetch_oidc_id_token_info
redirect_uri = URI(oidc_redirect_url)
redirect_params = Rack::Utils.parse_query(redirect_uri.query).with_indifferent_access
code = redirect_params[:code]

jwt_payload = {
iss: 'urn:gov:gsa:openidconnect:sp:server',
sub: 'urn:gov:gsa:openidconnect:sp:server',
aud: api_openid_connect_token_url,
jti: SecureRandom.hex,
exp: 5.minutes.from_now.to_i,
}

client_assertion = JWT.encode(jwt_payload, client_private_key, 'RS256')
client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

page.driver.post(
api_openid_connect_token_path,
grant_type: 'authorization_code',
code: code,
client_assertion_type: client_assertion_type,
client_assertion: client_assertion,
)

token_response = JSON.parse(page.body).with_indifferent_access
id_token = token_response[:id_token]
JWT.decode(id_token, nil, false).first.with_indifferent_access
end

def client_private_key
@client_private_key ||= begin
OpenSSL::PKey::RSA.new(
File.read(Rails.root.join('keys', 'saml_test_sp.key')),
)
end
end
end
3 changes: 1 addition & 2 deletions spec/support/features/session_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,7 @@ def perform_in_browser(name)
end

def acknowledge_and_confirm_personal_key
checkbox_header = t('forms.personal_key.required_checkbox')
find('label', text: /#{checkbox_header}/).click
check t('forms.personal_key.required_checkbox')
click_continue
end

Expand Down
55 changes: 3 additions & 52 deletions spec/support/idv_examples/sp_handoff.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
RSpec.shared_examples 'sp handoff after identity verification' do |sp|
include SamlAuthHelper
include OidcAuthHelper
include IdvHelper
include JavascriptDriverHelper

Expand Down Expand Up @@ -134,43 +135,10 @@ def expect_csp_headers_to_be_present
end

def expect_successful_oidc_handoff
redirect_uri = URI(current_url)
redirect_params = Rack::Utils.parse_query(redirect_uri.query).with_indifferent_access

expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result')
expect(redirect_params[:state]).to eq(@state)

code = redirect_params[:code]
expect(code).to be_present

jwt_payload = {
iss: @client_id,
sub: @client_id,
aud: api_openid_connect_token_url,
jti: SecureRandom.hex,
exp: 5.minutes.from_now.to_i,
}

client_assertion = JWT.encode(jwt_payload, client_private_key, 'RS256')
client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
token_response = oidc_decoded_token
decoded_id_token = oidc_decoded_id_token

Capybara.using_driver(:desktop_rack_test) do
page.driver.post api_openid_connect_token_path,
grant_type: 'authorization_code',
code: code,
client_assertion_type: client_assertion_type,
client_assertion: client_assertion

expect(page.status_code).to eq(200)
token_response = JSON.parse(page.body).with_indifferent_access

id_token = token_response[:id_token]
expect(id_token).to be_present

decoded_id_token, _headers = JWT.decode(
id_token, sp_public_key, true, algorithm: 'RS256'
).map(&:with_indifferent_access)

sub = decoded_id_token[:sub]
expect(sub).to be_present
expect(decoded_id_token[:nonce]).to eq(@nonce)
Expand Down Expand Up @@ -209,21 +177,4 @@ def expect_successful_saml_handoff
end
expect(xmldoc.phone_number.children.children.to_s).to eq(Phonelib.parse(profile_phone).e164)
end

def client_private_key
@client_private_key ||= begin
OpenSSL::PKey::RSA.new(
File.read(Rails.root.join('keys', 'saml_test_sp.key')),
)
end
end

def sp_public_key
page.driver.get api_openid_connect_certs_path

expect(page.status_code).to eq(200)
certs_response = JSON.parse(page.body).with_indifferent_access

JWT::JWK.import(certs_response[:keys].first).public_key
end
end
48 changes: 48 additions & 0 deletions spec/support/oidc_auth_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
require_relative 'features/javascript_driver_helper'

module OidcAuthHelper
include JavascriptDriverHelper

OIDC_ISSUER = 'urn:gov:gsa:openidconnect:sp:server'.freeze
OIDC_IAL1_ISSUER = 'urn:gov:gsa:openidconnect:sp:server_ial1'.freeze
OIDC_AAL3_ISSUER = 'urn:gov:gsa:openidconnect:sp:server_requiring_aal3'.freeze
Expand Down Expand Up @@ -155,6 +159,9 @@ def extract_redirect_url
end

def oidc_redirect_url
# Page will redirect automatically if JavaScript is enabled
return current_url if javascript_enabled?

case IdentityConfig.store.openid_connect_redirect
when 'client_side'
extract_meta_refresh_url
Expand All @@ -164,4 +171,45 @@ def oidc_redirect_url
current_url
end
end

def oidc_decoded_token
return @oidc_decoded_token if defined?(@oidc_decoded_token)
redirect_uri = URI(oidc_redirect_url)
redirect_params = Rack::Utils.parse_query(redirect_uri.query).with_indifferent_access
code = redirect_params[:code]

jwt_payload = {
iss: 'urn:gov:gsa:openidconnect:sp:server',
sub: 'urn:gov:gsa:openidconnect:sp:server',
aud: api_openid_connect_token_url,
jti: SecureRandom.hex,
exp: 5.minutes.from_now.to_i,
}

client_private_key = OpenSSL::PKey::RSA.new(
File.read(Rails.root.join('keys', 'saml_test_sp.key')),
)
client_assertion = JWT.encode(jwt_payload, client_private_key, 'RS256')
client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

Capybara.using_driver(:desktop_rack_test) do
page.driver.post(
api_openid_connect_token_url,
grant_type: 'authorization_code',
code:,
client_assertion_type:,
client_assertion:,
)
@oidc_decoded_token = JSON.parse(page.body).with_indifferent_access
end
end

def oidc_decoded_id_token
@oidc_decoded_id_token ||= JWT.decode(
oidc_decoded_token[:id_token],
AppArtifacts.store.oidc_public_key,
true,
algorithm: 'RS256',
).first.with_indifferent_access
end
end