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
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Metrics/ClassLength:
- app/controllers/users/confirmations_controller.rb
- app/controllers/users/sessions_controller.rb
- app/controllers/devise/two_factor_authentication_controller.rb
- app/decorators/service_provider_session_decorator.rb
- app/decorators/user_decorator.rb
- app/services/analytics.rb
- app/services/idv/session.rb
Expand Down
9 changes: 9 additions & 0 deletions app/decorators/service_provider_session_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ def sp_logo
sp.logo || DEFAULT_LOGO
end

def sp_logo_url
logo = sp_logo
if RemoteSettingsService.remote?(logo)
logo
else
ActionController::Base.helpers.image_path("sp-logos/#{logo}")
end
end

def return_to_service_provider_partial
if sp_return_url.present?
'devise/sessions/return_to_service_provider'
Expand Down
2 changes: 2 additions & 0 deletions app/decorators/session_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def sp_agency; end

def sp_logo; end

def sp_logo_url; end

def sp_redirect_uris; end

def sp_return_url; end
Expand Down
6 changes: 6 additions & 0 deletions app/models/remote_setting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class RemoteSetting < ApplicationRecord
validates :url, format: {
with:
%r{\A(https://raw.githubusercontent.com/18F/identity-idp/|https://login.gov).+\z},
}
end
17 changes: 11 additions & 6 deletions app/models/service_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ def metadata
def ssl_cert
@ssl_cert ||= begin
return if cert.blank?

cert_file = Rails.root.join('certs', 'sp', "#{cert}.crt")

return OpenSSL::X509::Certificate.new(cert) unless File.exist?(cert_file)

OpenSSL::X509::Certificate.new(File.read(cert_file))
OpenSSL::X509::Certificate.new(load_cert(cert))
end
end

Expand All @@ -49,6 +44,16 @@ def live?

private

def load_cert(cert)
if RemoteSettingsService.remote?(cert)
RemoteSettingsService.load(cert)
else
cert_file = Rails.root.join('certs', 'sp', "#{cert}.crt")
return OpenSSL::X509::Certificate.new(cert) unless File.exist?(cert_file)
File.read(cert_file)
end
end

def redirect_uris_are_parsable
return if redirect_uris.blank?

Expand Down
7 changes: 6 additions & 1 deletion app/services/agency_seeder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ def run
attr_reader :rails_env, :deploy_env

def agencies
content = ERB.new(Rails.root.join('config', 'agencies.yml').read).result
file = remote_setting || Rails.root.join('config', 'agencies.yml').read
content = ERB.new(file).result
YAML.safe_load(content).fetch(rails_env, {})
end

def remote_setting
RemoteSetting.find_by(name: 'agencies.yml')&.contents
end
end
33 changes: 33 additions & 0 deletions app/services/remote_settings_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class RemoteSettingsService
def self.load_yml_erb(location)
result = ERB.new(load(location)).result
begin
YAML.safe_load(result.to_s)
rescue StandardError
raise "Error parsing yml file: #{location}"
end
result
end

def self.load(location)
raise "Location must begin with 'https://': #{location}" unless remote?(location)
response = HTTParty.get(
location, headers:
{ 'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1' }
)
raise "Error retrieving: #{location}" unless response.code == 200
response.body
end

def self.update_setting(name, url)
remote_setting = RemoteSetting.where(name: name).first_or_initialize
remote_setting.url = url
raise "url not whitelisted: #{url}" unless remote_setting.valid?
remote_setting.contents = RemoteSettingsService.load(remote_setting.url)
remote_setting.save
end

def self.remote?(location)
location.to_s.starts_with?('https://')
end
end
7 changes: 6 additions & 1 deletion app/services/service_provider_seeder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ def run
attr_reader :rails_env, :deploy_env

def service_providers
content = ERB.new(Rails.root.join('config', 'service_providers.yml').read).result
file = remote_setting || Rails.root.join('config', 'service_providers.yml').read
content = ERB.new(file).result
YAML.safe_load(content).fetch(rails_env, {})
end

def remote_setting
RemoteSetting.find_by(name: 'service_providers.yml')&.contents
end

def write_service_provider?(config)
return true if rails_env != 'production'

Expand Down
2 changes: 1 addition & 1 deletion app/views/shared/_nav_branded.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ nav.nav-branded.vertical-align.bg-light-blue.center.relative
alt: APP_NAME, class: 'inline-block align-middle')
.px-12p.inline-block
span.absolute.top-0.bottom-0.border-right.my1.sm-my2
= image_tag(asset_url("sp-logos/#{decorated_session.sp_logo}"), height: 40,
= image_tag(decorated_session.sp_logo_url, height: 40,
alt: decorated_session.sp_name, class: 'inline-block align-middle')
2 changes: 1 addition & 1 deletion config/initializers/secure_headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'*.google-analytics.com',
],
font_src: ["'self'", 'data:'],
img_src: ["'self'", 'data:'],
img_src: ["'self'", 'data:', 'login.gov'],
media_src: ["'self'"],
object_src: ["'none'"],
script_src: [
Expand Down
11 changes: 11 additions & 0 deletions db/migrate/20180607144007_create_remote_settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateRemoteSettings < ActiveRecord::Migration[5.1]
def change
create_table :remote_settings do |t|
t.string "name", null: false
t.string "url", null: false
t.text "contents", null: false
t.timestamps
end
add_index :remote_settings, ["name"], name: "index_remote_settings_on_name", unique: true, using: :btree
end
end
11 changes: 10 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20180601145643) do
ActiveRecord::Schema.define(version: 20180607144007) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -99,6 +99,15 @@
t.index ["user_id"], name: "index_profiles_on_user_id"
end

create_table "remote_settings", force: :cascade do |t|
t.string "name", null: false
t.string "url", null: false
t.text "contents", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["name"], name: "index_remote_settings_on_name", unique: true
end

create_table "service_provider_requests", force: :cascade do |t|
t.string "issuer", null: false
t.string "loa", null: false
Expand Down
26 changes: 26 additions & 0 deletions lib/tasks/remote_settings.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace :remote_settings do
task :update, [:name, :url] => [:environment] do |task, args|
RemoteSettingsService.update_setting(args[:name], args[:url])
Kernel.puts "Update successful"
end

task :view, [:name] => [:environment] do |task, args|
Kernel.puts RemoteSetting.find_by(name: args[:name])&.contents
end

task list: :environment do
RemoteSetting.all.each do |rec|
Kernel.puts "name=#{rec.name} url=#{rec.url}"
end
end

task :delete, [:name] => [:environment] do |task, args|
RemoteSetting.where(name: args[:name]).delete_all
Kernel.puts "Delete successful"
end
end

# example invocations:
# rake "remote_settings:update[agencies.yml,https://raw.githubusercontent.com/18F/identity-idp/master/config/agencies.yml]"
# rake "remote_settings:update[agencies.yml,https://login.gov/assets/idp/config/agencies.yml"
# rake "remote_settings:update[service_providers.yml,https://raw.githubusercontent.com/18F/identity-idp/master/config/service_providers.yml]"
49 changes: 49 additions & 0 deletions spec/decorators/service_provider_session_decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,55 @@
end
end

describe '#sp_logo_url' do
context 'service provider has a logo' do
it 'returns the logo' do
sp_logo = 'real_logo.svg'
sp = build_stubbed(:service_provider, logo: sp_logo)

subject = ServiceProviderSessionDecorator.new(
sp: sp,
view_context: view_context,
sp_session: {},
service_provider_request: ServiceProviderRequest.new
)

expect(subject.sp_logo_url).to end_with("/sp-logos/#{sp_logo}")
end
end

context 'service provider does not have a logo' do
it 'returns the default logo' do
sp = build_stubbed(:service_provider, logo: nil)

subject = ServiceProviderSessionDecorator.new(
sp: sp,
view_context: view_context,
sp_session: {},
service_provider_request: ServiceProviderRequest.new
)

expect(subject.sp_logo_url).to match(%r{/sp-logos/generic-.+\.svg})
end
end

context 'service provider has a remote logo' do
it 'returns the remote logo' do
logo = 'https://raw.githubusercontent.com/18F/identity-idp/master/app/assets/images/sp-logos/generic.svg'
sp = build_stubbed(:service_provider, logo: logo)

subject = ServiceProviderSessionDecorator.new(
sp: sp,
view_context: view_context,
sp_session: {},
service_provider_request: ServiceProviderRequest.new
)

expect(subject.sp_logo_url).to eq(logo)
end
end
end

describe '#cancel_link_url' do
subject(:decorator) do
ServiceProviderSessionDecorator.new(
Expand Down
23 changes: 23 additions & 0 deletions spec/models/remote_setting_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'rails_helper'

describe RemoteSetting do
describe 'validations' do
it 'validates that our github repo is white listed' do
location = 'https://raw.githubusercontent.com/18F/identity-idp/master/config/agencies.yml'
valid_setting = RemoteSetting.create(name: 'agencies.yml', url: location, contents:'')
expect(valid_setting).to be_valid
end

it 'validates that the login.gov static site is white listed' do
location = 'https://login.gov/agencies.yml'
valid_setting = RemoteSetting.create(name: 'agencies.yml', url: location, contents:'')
expect(valid_setting).to be_valid
end

it 'does not accept http' do
location = 'http://login.gov/agencies.yml'
valid_setting = RemoteSetting.create(name: 'agencies.yml', url: location, contents:'')
expect(valid_setting).to_not be_valid
end
end
end
13 changes: 13 additions & 0 deletions spec/models/service_provider_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,17 @@
end
end
end

describe '#ssl_cert' do
it 'returns the remote setting cert' do
WebMock.allow_net_connect!
sp = create(:service_provider, issuer: 'foo', cert: 'https://raw.githubusercontent.com/18F/identity-idp/master/certs/sp/saml_test_sp.crt')
expect(sp.ssl_cert.class).to be(OpenSSL::X509::Certificate)
end

it 'returns the local cert' do
sp = create(:service_provider, issuer: 'foo', cert: 'saml_test_sp')
expect(sp.ssl_cert.class).to be(OpenSSL::X509::Certificate)
end
end
end
20 changes: 20 additions & 0 deletions spec/services/agency_seeder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,25 @@
expect(Agency.find_by(id: 1).name).to eq('CBP')
end
end

context 'when agencies.yml has a remote setting' do
before do
location = 'https://raw.githubusercontent.com/18F/identity-idp/master/config/agencies.yml'
RemoteSetting.create(name: 'agencies.yml', url:location, contents: "test:\n 1:\n name: 'CBP'")
end

it 'updates the attributes based on the current value of the yml file' do
Agency.create(id: 1, name: 'FOO')
expect(Agency.find_by(id: 1).name).to eq('FOO')
run
expect(Agency.find_by(id: 1).name).to eq('CBP')
end

it 'insert the attributes based on the contents of the remote setting' do
run
expect(Agency.find_by(id: 1).name).to eq('CBP')
expect(Agency.count).to eq(1)
end
end
end
end
Loading