diff --git a/.env.example b/.env.example deleted file mode 100644 index 3654c8a..0000000 --- a/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -IDP_SP_URL: 'http://localhost:3000' - -# IDP with basic authentication -IDP_USER: 'USERNAME' -IDP_PASSWORD: 'PASSWORD' - -ACR_VALUES: 'http://idmanagement.gov/ns/assurance/loa/1' -REDIRECT_URI: 'http://localhost:9292/' -CLIENT_ID: 'urn:gov:gsa:openidconnect:sp:sinatra' diff --git a/.gitignore b/.gitignore index 19f1797..b5de3d5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' -.env +/config/application.yml diff --git a/Gemfile b/Gemfile index 8b70087..e9611cf 100644 --- a/Gemfile +++ b/Gemfile @@ -6,13 +6,13 @@ source 'https://rubygems.org' ruby '~> 2.3.5' -gem 'activesupport' -gem 'dotenv' -gem 'httparty' +gem 'activesupport', '~> 5.2' +gem 'aws-sdk-secretsmanager', '~> 1.21' +gem 'httparty', '~> 0.16' gem 'identity-hostdata', github: '18F/identity-hostdata', branch: 'master' -gem 'json-jwt' -gem 'jwt' -gem 'sinatra' +gem 'json-jwt', '~> 1.9.4' +gem 'jwt', '~> 2.1' +gem 'sinatra', '~> 2.0', '>= 2.0.2' group :development do gem 'pry-byebug' @@ -22,7 +22,7 @@ end group :test do gem 'fakefs', require: 'fakefs/safe' - gem 'nokogiri' + gem 'nokogiri', '~> 1.10' gem 'rack-test' gem 'rspec', '~> 3.5.0' gem 'webmock' diff --git a/Gemfile.lock b/Gemfile.lock index 13158c9..6fa93c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,89 +1,109 @@ GIT remote: https://github.com/18F/identity-hostdata.git - revision: 2751e7df330986c7effbb66d8c7ecf8f039ab767 + revision: b5587588601670f762bbc79f0f4a8468064d9401 branch: master specs: - identity-hostdata (0.3.2) - aws-sdk-core (~> 2.10.23) + identity-hostdata (0.3.3) + aws-sdk-s3 (~> 1.8) GEM remote: https://rubygems.org/ specs: - activesupport (5.1.4) + activesupport (5.2.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.5.2) + addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) aes_key_wrap (1.0.1) ast (2.4.0) - aws-sdk-core (2.10.118) + aws-eventstream (1.0.1) + aws-partitions (1.142.0) + aws-sdk-core (3.46.2) + aws-eventstream (~> 1.0) + aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sigv4 (1.0.2) + aws-sdk-kms (1.13.0) + aws-sdk-core (~> 3, >= 3.39.0) + aws-sigv4 (~> 1.0) + aws-sdk-s3 (1.30.1) + aws-sdk-core (~> 3, >= 3.39.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.0) + aws-sdk-secretsmanager (1.21.0) + aws-sdk-core (~> 3, >= 3.39.0) + aws-sigv4 (~> 1.0) + aws-sigv4 (1.0.3) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - bindata (2.4.3) - byebug (10.0.2) + bindata (2.4.4) + byebug (11.0.0) codeclimate-engine-rb (0.4.1) virtus (~> 1.0) coderay (1.1.2) coercible (1.0.0) descendants_tracker (~> 0.0.1) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.4) crack (0.4.3) safe_yaml (~> 1.0.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.3) - dotenv (2.2.2) equalizer (0.0.11) - fakefs (0.13.3) - hashdiff (0.3.7) - httparty (0.16.2) + fakefs (0.19.2) + hashdiff (0.3.8) + httparty (0.16.4) + mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (0.9.1) + i18n (1.6.0) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - jmespath (1.3.1) - json-jwt (1.9.2) + jaro_winkler (1.5.2) + jmespath (1.4.0) + json-jwt (1.9.4) activesupport aes_key_wrap bindata - securecompare - url_safe_base64 jwt (2.1.0) - method_source (0.9.0) - mini_portile2 (2.3.0) - minitest (5.11.1) + kwalify (0.7.2) + method_source (0.9.2) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2018.0812) + mini_portile2 (2.4.0) + minitest (5.11.3) multi_xml (0.6.0) - mustermann (1.0.2) - nokogiri (1.8.2) - mini_portile2 (~> 2.3.0) - parallel (1.12.1) - parser (2.5.0.5) + mustermann (1.0.3) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + parallel (1.14.0) + parser (2.6.0.0) ast (~> 2.4.0) - powerpack (0.1.1) - pry (0.11.3) + powerpack (0.1.2) + pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) - pry-byebug (3.6.0) - byebug (~> 10.0) + pry-byebug (3.7.0) + byebug (~> 11.0) pry (~> 0.10) - public_suffix (3.0.2) - rack (2.0.4) - rack-protection (2.0.1) + psych (3.1.0) + public_suffix (3.0.3) + rack (2.0.6) + rack-protection (2.0.5) rack - rack-test (1.0.0) + rack-test (1.1.0) rack (>= 1.0, < 3) rainbow (3.0.0) - reek (4.8.0) + reek (5.3.1) codeclimate-engine-rb (~> 0.4.0) - parser (>= 2.5.0.0, < 2.6) - rainbow (~> 3.0) + kwalify (~> 0.7.0) + parser (>= 2.5.0.0, < 2.7, != 2.5.1.1) + psych (~> 3.1.0) + rainbow (>= 2.0, < 4.0) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) @@ -97,33 +117,33 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-support (3.5.0) - rubocop (0.52.1) + rubocop (0.65.0) + jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.4.0.2, < 3.0) + parser (>= 2.5, != 2.5.1.1) powerpack (~> 0.1) + psych (>= 3.1.0) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) - ruby-progressbar (1.9.0) - safe_yaml (1.0.4) - securecompare (1.0.0) - sinatra (2.0.1) + unicode-display_width (~> 1.4.0) + ruby-progressbar (1.10.0) + safe_yaml (1.0.5) + sinatra (2.0.5) mustermann (~> 1.0) rack (~> 2.0) - rack-protection (= 2.0.1) + rack-protection (= 2.0.5) tilt (~> 2.0) thread_safe (0.3.6) - tilt (2.0.8) - tzinfo (1.2.4) + tilt (2.0.9) + tzinfo (1.2.5) thread_safe (~> 0.1) - unicode-display_width (1.3.0) - url_safe_base64 (0.2.2) + unicode-display_width (1.4.1) virtus (1.0.5) axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - webmock (3.3.0) + webmock (3.5.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff @@ -132,24 +152,24 @@ PLATFORMS ruby DEPENDENCIES - activesupport - dotenv + activesupport (~> 5.2) + aws-sdk-secretsmanager (~> 1.21) fakefs - httparty + httparty (~> 0.16) identity-hostdata! - json-jwt - jwt - nokogiri + json-jwt (~> 1.9.4) + jwt (~> 2.1) + nokogiri (~> 1.10) pry-byebug rack-test reek rspec (~> 3.5.0) rubocop - sinatra + sinatra (~> 2.0, >= 2.0.2) webmock RUBY VERSION ruby 2.3.5p376 BUNDLED WITH - 1.16.0 + 1.17.1 diff --git a/Makefile b/Makefile index e588ce0..ccc3ea4 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,6 @@ all: check setup: bundle check || bundle install - [ -f .env ] && echo ".env exists" || cat .env.example >> .env - -.env: setup check: lint test @@ -25,5 +22,5 @@ lint: run: bundle exec rackup -p $(PORT) -test: .env $(CONFIG) +test: $(CONFIG) bundle exec rspec diff --git a/app.rb b/app.rb index 150f26e..a8ba915 100644 --- a/app.rb +++ b/app.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'dotenv/load' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/object/to_query' require 'erb' @@ -12,11 +11,13 @@ require 'sinatra/base' require 'time' -class OpenidConnectRelyingParty < Sinatra::Base - SERVICE_PROVIDER = ENV['IDP_SP_URL'] - CLIENT_ID = ENV['CLIENT_ID'] - BASIC_AUTH = { username: ENV['IDP_USER'], password: ENV['IDP_PASSWORD'] }.freeze - REDIRECT_URI = ENV['REDIRECT_URI'] +require_relative './config' + +module LoginGov::OidcSinatra; class OpenidConnectRelyingParty < Sinatra::Base + + def config + @config ||= Config.new + end get '/' do if openid_configuration @@ -49,17 +50,28 @@ class OpenidConnectRelyingParty < Sinatra::Base def authorization_url(loa) openid_configuration[:authorization_endpoint] + '?' + { - client_id: CLIENT_ID, + client_id: config.client_id, response_type: 'code', acr_values: 'http://idmanagement.gov/ns/assurance/loa/' + loa.to_s, - scope: 'openid email', - redirect_uri: File.join(REDIRECT_URI, '/auth/result'), + scope: scopes_for(loa), + redirect_uri: File.join(config.redirect_uri, '/auth/result'), state: random_value, nonce: random_value, prompt: 'select_account', }.to_query end + def scopes_for(loa) + case loa + when 1 + 'openid email' + when 3 + 'openid email profile social_security_number phone' + else + raise ArgumentError.new("Unexpected LOA: #{loa.inspect}") + end + end + def openid_configuration @openid_configuration ||= begin response = openid_configuration_response @@ -72,20 +84,17 @@ def openid_configuration end def openid_configuration_response - HTTParty.get( - URI.join(SERVICE_PROVIDER, '/.well-known/openid-configuration'), - basic_auth: BASIC_AUTH - ) + HTTParty.get(URI.join(config.idp_url, '/.well-known/openid-configuration')) end def openid_configuration_error response_code = openid_configuration_response.code if response_code == 401 - "Error: #{SERVICE_PROVIDER} responded with #{response_code}. - Check basic authentication in IDP_USER and IDP_PASSSWORD environment variables." + "Error: #{config.idp_url} responded with #{response_code}. + Perhaps we need to reimplement HTTP Basic Auth." else - "Error: #{SERVICE_PROVIDER} responded with #{response_code}." + "Error: #{config.idp_url} responded with #{response_code}." end end @@ -97,22 +106,21 @@ def token(code) code: code, client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', client_assertion: client_assertion_jwt, - }, - basic_auth: BASIC_AUTH + } ).body end def client_assertion_jwt jwt_payload = { - iss: CLIENT_ID, - sub: CLIENT_ID, + iss: config.client_id, + sub: config.client_id, aud: openid_configuration[:token_endpoint], jti: random_value, nonce: random_value, exp: Time.now.to_i + 1000, } - JWT.encode(jwt_payload, sp_private_key, 'RS256') + JWT.encode(jwt_payload, config.sp_private_key, 'RS256') end def userinfo(id_token) @@ -124,7 +132,7 @@ def userinfo(id_token) def logout_uri(id_token) openid_configuration[:end_session_endpoint] + '?' + { id_token_hint: id_token, - post_logout_redirect_uri: REDIRECT_URI, + post_logout_redirect_uri: config.redirect_uri, state: SecureRandom.hex, }.to_query end @@ -135,19 +143,21 @@ def json(response) def idp_public_key certs_response = json( - HTTParty.get( - openid_configuration[:jwks_uri], - basic_auth: BASIC_AUTH - ).body + HTTParty.get(openid_configuration[:jwks_uri]).body ) JSON::JWK.new(certs_response[:keys].first).to_key end - def sp_private_key - @sp_private_key ||= OpenSSL::PKey::RSA.new(File.read('config/demo_sp.key')) - end - def random_value SecureRandom.hex end -end + + def maybe_redact_ssn(ssn) + if config.redact_ssn? + # redact all characters since they're all sensitive + ssn = ssn.gsub(/\d/, '#') + end + + ssn + end +end; end diff --git a/config.rb b/config.rb new file mode 100644 index 0000000..222d257 --- /dev/null +++ b/config.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'json' +require 'login_gov/hostdata' +require 'aws-sdk-secretsmanager' + +module LoginGov::OidcSinatra + # Class holding configuration for this sample app. Defaults come from + # `#default_config`, with keys overridden by data from + # `config/application.yml` if it exists. + class Config + # @param [String] config_file Location of application.yml + def initialize(config_file: nil) + @config = default_config + + config_file ||= File.dirname(__FILE__) + '/config/application.yml' + + if File.exist?(config_file) + STDERR.puts("Loading config from #{config_file.inspect}") + @config.merge!(YAML.safe_load(File.read(config_file))) + end + end + + def idp_url + @config.fetch('idp_url') + end + + def acr_values + @config.fetch('acr_values') + end + + def redirect_uri + @config.fetch('redirect_uri') + end + + def client_id + @config.fetch('client_id') + end + + def redact_ssn? + @config.fetch('redact_ssn') + end + + # @return [OpenSSL::PKey::RSA] + def sp_private_key + key = get_sp_private_key_raw(@config.fetch('sp_private_key_path')) + OpenSSL::PKey::RSA.new(key) + end + + # Define the default configuration values. If application.yml exists, those + # values will be merged in overriding defaults. + # + # @return [Hash] + # + def default_config + data = { + 'acr_values' => 'http://idmanagement.gov/ns/assurance/loa/1', + 'client_id' => 'urn:gov:gsa:openidconnect:sp:sinatra', + } + + if LoginGov::Hostdata.in_datacenter? + # EC2 deployment defaults + if LoginGov::Hostdata.env == 'prod' + data['idp_url'] = "https://secure.#{LoginGov::Hostdata.domain}" + else + data['idp_url'] = "https://idp.#{LoginGov::Hostdata.env}.#{LoginGov::Hostdata.domain}" + end + data['redirect_uri'] = "https://sp-oidc-sinatra.#{LoginGov::Hostdata.env}.#{LoginGov::Hostdata.domain}/" + data['sp_private_key_path'] = "aws-secretsmanager:#{LoginGov::Hostdata.env}/sp-oidc-sinatra/oidc.key" + data['redact_ssn'] = true + else + # local dev defaults + data['idp_url'] = 'http://localhost:3000' + data['redirect_uri'] = 'http://localhost:9292/' + data['sp_private_key_path'] = demo_private_key_path + data['redact_ssn'] = false + end + + data + end + + private + + def get_sp_private_key_raw(path) + if path.start_with?('aws-secretsmanager:') + secret_id = path.split(':', 2).fetch(1) + smc = Aws::SecretsManager::Client.new + begin + return smc.get_secret_value(secret_id: secret_id).secret_string + rescue Aws::SecretsManager::Errors::ResourceNotFoundException + if LoginGov::Hostdata.domain == 'login.gov' || LoginGov::Hostdata.env == 'prod' + raise + end + end + + STDERR.puts "#{secret_id.inspect}: not found in AWS Secrets Manager, using demo key" + get_sp_private_key_raw(demo_private_key_path) + else + File.read(path) + end + end + + def demo_private_key_path + if LoginGov::Hostdata.domain == 'login.gov' || LoginGov::Hostdata.env == 'prod' + raise 'Refusing to use demo key in production' + end + File.dirname(__FILE__) + '/config/demo_sp.key' + end + end +end diff --git a/config.ru b/config.ru index a98f7fb..31b1778 100644 --- a/config.ru +++ b/config.ru @@ -1,4 +1,4 @@ # frozen_string_literal: true require './app' -run OpenidConnectRelyingParty.new +run LoginGov::OidcSinatra::OpenidConnectRelyingParty.new diff --git a/config/application.yml.example b/config/application.yml.example new file mode 100644 index 0000000..a8cfb10 --- /dev/null +++ b/config/application.yml.example @@ -0,0 +1,5 @@ +--- +idp_sp_url: 'http://localhost:3000' +acr_values: 'http://idmanagement.gov/ns/assurance/loa/1' +redirect_uri: 'http://localhost:9292/' +client_id: 'urn:gov:gsa:openidconnect:sp:sinatra' diff --git a/deploy/activate b/deploy/activate deleted file mode 100755 index adb1fb5..0000000 --- a/deploy/activate +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env ruby -require 'bundler' -Bundler.setup(:default, :production) - -require 'logger' -require 'login_gov/hostdata' - -module Deploy - class Activate - attr_reader :logger, :s3_client - - def initialize(logger: default_logger, s3_client: nil) - @logger = logger - @s3_client = s3_client - end - - def run - logger.info "app root: #{root}" - - LoginGov::Hostdata.s3(logger: logger, s3_client: s3_client).download_configs( - '/%s/sp-oidc-sinatra/v1/.env' => File.join(root, '.env') - ) - end - - def root - File.expand_path('../../', __FILE__) - end - - def default_logger - logger = Logger.new(STDOUT) - logger.progname = 'deploy/activate' - logger - end - end -end - -Deploy::Activate.new.run if $PROGRAM_NAME == __FILE__ diff --git a/spec/app_spec.rb b/spec/app_spec.rb index d399c06..c21820d 100644 --- a/spec/app_spec.rb +++ b/spec/app_spec.rb @@ -2,22 +2,25 @@ require 'nokogiri' require 'securerandom' -RSpec.describe OpenidConnectRelyingParty do +RSpec.describe LoginGov::OidcSinatra::OpenidConnectRelyingParty do let(:host) { 'http://localhost:3000' } let(:authorization_endpoint) { "#{host}/openid/authorize" } let(:token_endpoint) { "#{host}/api/openid/token" } let(:jwks_uri) { "#{host}/api/openid/certs" } let(:end_session_endpoint) { "#{host}/openid/logout" } + let(:client_id) { 'urn:gov:gsa:openidconnect:sp:sinatra' } before do stub_request(:get, "#{host}/.well-known/openid-configuration"). - with(basic_auth: ENV.values_at('IDP_USER', 'IDP_PASSWORD')). to_return(body: { authorization_endpoint: authorization_endpoint, token_endpoint: token_endpoint, jwks_uri: jwks_uri, end_session_endpoint: end_session_endpoint, }.to_json) + + # use the default local dev config + allow(LoginGov::Hostdata).to receive(:in_datacenter?).and_return(false) end context '/' do @@ -34,7 +37,7 @@ expect(auth_uri_params[:redirect_uri]).to eq('http://localhost:9292/auth/result') expect(auth_uri_params[:client_id]).to_not be_empty - expect(auth_uri_params[:client_id]).to eq(ENV['CLIENT_ID']) + expect(auth_uri_params[:client_id]).to eq(client_id) expect(auth_uri_params[:response_type]).to eq('code') expect(auth_uri_params[:prompt]).to eq('select_account') expect(auth_uri_params[:nonce].length).to be >= 32 @@ -43,23 +46,21 @@ it 'renders an error if basic auth credentials are wrong' do stub_request(:get, "#{host}/.well-known/openid-configuration"). - with(basic_auth: ENV.values_at('IDP_USER', 'IDP_PASSWORD')). to_return(body: '', status: 401) get '/' expect(last_response.body).to include( - 'Check basic authentication in IDP_USER and IDP_PASSSWORD environment variables.' + 'Perhaps we need to reimplement HTTP Basic Auth' ) end it 'renders an error if the app fails to get oidc configuration' do stub_request(:get, "#{host}/.well-known/openid-configuration"). - with(basic_auth: ENV.values_at('IDP_USER', 'IDP_PASSWORD')). to_return(body: '', status: 400) get '/' - error_string = "Error: #{ENV['IDP_SP_URL']} responded with 400." + error_string = "Error: #{host} responded with 400." expect(last_response.body).to include(error_string) end end @@ -68,21 +69,25 @@ let(:code) { SecureRandom.uuid } let(:email) { 'foobar@bar.com' } - let(:id_token) { JWT.encode({ email: email }, idp_private_key, 'RS256') } + let(:id_token) { + JWT.encode( + { email: email, acr: 'http://idmanagement.gov/ns/assurance/loa/1' }, + idp_private_key, + 'RS256' + ) + } let(:idp_private_key) { OpenSSL::PKey::RSA.new(2048) } let(:idp_public_key) { idp_private_key.public_key } before do stub_request(:get, jwks_uri). - with(basic_auth: ENV.values_at('IDP_USER', 'IDP_PASSWORD')). to_return(body: { keys: [JSON::JWK.new(idp_public_key)], }.to_json) stub_request(:post, token_endpoint). with( - basic_auth: ENV.values_at('IDP_USER', 'IDP_PASSWORD'), body: { grant_type: 'authorization_code', code: code, @@ -101,6 +106,7 @@ get '/auth/result', code: code expect(last_response.body).to include(email) + expect(last_response.body).to include('LOA1') end it 'has a logout link back to the SP-initiated logout URL' do @@ -131,5 +137,43 @@ expect(doc.text).to include('missing callback param: code') end + + context 'LOA3 /auth/result' do + let(:id_token) { + JWT.encode( + { + email: email, + acr: 'http://idmanagement.gov/ns/assurance/loa/3', + social_security_number: '012-34-5678', + phone: '0125551212', + }, + idp_private_key, + 'RS256' + ) + } + + it 'renders expected LOA3 data when redaction is not enabled' do + # disable redaction + expect_any_instance_of(LoginGov::OidcSinatra::Config).to receive(:redact_ssn?).at_least(:once).and_return(false) + + get '/auth/result', code: code + + expect(last_response.body).to include('012-34-5678') + expect(last_response.body).to include('0125551212') + expect(last_response.body).to include('LOA3') + end + + it 'renders redacted SSN LOA3 data when redaction is enabled' do + # enable redaction + expect_any_instance_of(LoginGov::OidcSinatra::Config).to receive(:redact_ssn?).at_least(:once).and_return(true) + + get '/auth/result', code: code + + expect(last_response.body).to_not include('012-34-5678') + expect(last_response.body).to include('###-##-####') + expect(last_response.body).to include('0125551212') + expect(last_response.body).to include('LOA3') + end + end end end diff --git a/spec/deploy/activate_spec.rb b/spec/deploy/activate_spec.rb deleted file mode 100644 index 6196c4e..0000000 --- a/spec/deploy/activate_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -require 'spec_helper' -require 'fakefs/spec_helpers' -require 'login_gov/hostdata/fake_s3_client' - -load File.expand_path('../../deploy/activate', File.dirname(__FILE__)) - -RSpec.describe 'deploy/activate' do - let(:app_root) { File.expand_path('../../', File.dirname(__FILE__)) } - - around(:each) do |ex| - LoginGov::Hostdata.reset! - - @logger = Logger.new('/dev/null') - - FakeFS do - FakeFS::FileSystem.clone(app_root) - - ex.run - end - end - - subject(:script) { Deploy::Activate.new(logger: logger, s3_client: s3_client) } - - let(:logger) { @logger } - let(:s3_client) { LoginGov::Hostdata::FakeS3Client.new } - - describe '#run' do - context 'in a deployed production environment' do - before do - stub_request(:get, 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document'). - to_return(body: { - 'region' => 'us-west-1', - 'accountId' => '12345', - }.to_json) - - s3_client.put_object( - bucket: 'login-gov.app-secrets.12345-us-west-1', - key: '/int/sp-oidc-sinatra/v1/.env', - body: dot_env - ) - - FileUtils.mkdir_p('/etc/login.gov/info') - File.open('/etc/login.gov/info/env', 'w') { |file| file.puts 'int' } - end - - let(:dot_env) { "FOO=bar\n" } - - it 'downloads configs from s3' do - script.run - - expect(File.read(File.join(app_root, '.env'))).to eq(dot_env) - end - end - - context 'outside a deployed production environment' do - before do - stub_request(:get, 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document'). - to_timeout - end - - it 'errors' do - expect { script.run }.to raise_error(Net::OpenTimeout) - end - end - end - - describe '#default_logger' do - it 'sets the progname' do - expect(script.default_logger.progname).to eq('deploy/activate') - end - end -end diff --git a/views/user_info.erb b/views/user_info.erb index 7014ec3..c2dc642 100644 --- a/views/user_info.erb +++ b/views/user_info.erb @@ -7,13 +7,27 @@ Success! <% if userinfo %> Email: <%= userinfo[:email] %> - <% if userinfo[:first_name] %> + | + <% case userinfo.fetch('acr') + when 'http://idmanagement.gov/ns/assurance/loa/3' %> + Login type: LOA3 | - Name: <%= userinfo[:first_name] %> <%= userinfo[:last_name] %> - <% end %> - <% if userinfo[:social_security_number] %> - | - SSN: ###-##-<%= userinfo[:social_security_number].split(//).last(4).join %> + SSN: <%= maybe_redact_ssn(userinfo[:social_security_number]) %> + <% when 'http://idmanagement.gov/ns/assurance/loa/1' %> + Login type: LOA1 + <% else %> + Login type: Unexpected ACR: <%= userinfo.fetch('acr') %> <% end %> + + <% end %> + + +
+ Received user info: +