ndr_authenticate 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +58 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/MIT-LICENSE +20 -0
- data/README.md +227 -0
- data/Rakefile +33 -0
- data/app/assets/config/ndr_authenticate_manifest.js +2 -0
- data/app/assets/images/ndr_authenticate/.keep +0 -0
- data/app/assets/javascripts/ndr_authenticate/ndr_authenticate.js +14 -0
- data/app/assets/stylesheets/ndr_authenticate/ndr_authenticate.scss +1 -0
- data/app/controllers/concerns/ndr_authenticate/authenticatable.rb +21 -0
- data/app/controllers/concerns/ndr_authenticate/devise_helpers.rb +17 -0
- data/app/controllers/concerns/ndr_authenticate/turbolinks.rb +31 -0
- data/app/controllers/concerns/ndr_authenticate/yubikey/authenticatable.rb +94 -0
- data/app/controllers/concerns/ndr_authenticate/yubikey/protectable.rb +103 -0
- data/app/controllers/ndr_authenticate/application_controller.rb +22 -0
- data/app/controllers/ndr_authenticate/authentication_controller.rb +27 -0
- data/app/controllers/ndr_authenticate/saml_sessions_controller.rb +46 -0
- data/app/controllers/ndr_authenticate/sessions_controller.rb +22 -0
- data/app/helpers/ndr_authenticate/application_helper.rb +22 -0
- data/app/helpers/ndr_authenticate/authentication_helper.rb +4 -0
- data/app/jobs/ndr_authenticate/application_job.rb +4 -0
- data/app/mailers/ndr_authenticate/application_mailer.rb +6 -0
- data/app/models/ndr_authenticate/application_record.rb +5 -0
- data/app/views/devise/passwords/edit.html.erb +23 -0
- data/app/views/devise/passwords/new.html.erb +18 -0
- data/app/views/devise/sessions/new.html.erb +21 -0
- data/app/views/devise/shared/_error_messages.html.erb +15 -0
- data/app/views/devise/shared/_links.html.erb +25 -0
- data/app/views/layouts/ndr_authenticate/ndr_authenticate.html.erb +47 -0
- data/app/views/ndr_authenticate/authentication/check_active.html.erb +30 -0
- data/app/views/ndr_authenticate/shared/_legal_notice.html.erb +13 -0
- data/app/views/ndr_authenticate/yubikey/protectable/_form.html.erb +44 -0
- data/app/views/ndr_authenticate/yubikey/protectable/_modal.html.erb +10 -0
- data/app/views/ndr_authenticate/yubikey/protectable/_panel.html.erb +13 -0
- data/app/views/ndr_authenticate/yubikey/protectable/challenge.html.erb +9 -0
- data/app/views/ndr_authenticate/yubikey/protectable/challenge.js.erb +41 -0
- data/app/views/shared/_flash_messages.html.erb +17 -0
- data/config/certificates/saml/certificate.pem +23 -0
- data/config/certificates/saml/encryption.phe.adfs.pem +18 -0
- data/config/certificates/saml/signing.phe.adfs.pem +19 -0
- data/config/initializers/devise.rb +37 -0
- data/config/locales/en.yml +15 -0
- data/config/routes.rb +24 -0
- data/lib/generators/ndr_authenticate/install/USAGE +10 -0
- data/lib/generators/ndr_authenticate/install/install_generator.rb +20 -0
- data/lib/generators/ndr_authenticate/install/templates/attribute-map.yml +77 -0
- data/lib/generators/ndr_authenticate/install/templates/migration.rb +13 -0
- data/lib/generators/ndr_authenticate/install/templates/ndr_authenticate.rb +31 -0
- data/lib/ndr_authenticate/connector.rb +45 -0
- data/lib/ndr_authenticate/engine.rb +76 -0
- data/lib/ndr_authenticate/saml_config.rb +39 -0
- data/lib/ndr_authenticate/version.rb +3 -0
- data/lib/ndr_authenticate/yubikey/verify.rb +26 -0
- data/lib/ndr_authenticate/yubikey.rb +7 -0
- data/lib/ndr_authenticate.rb +127 -0
- data/lib/tasks/ndr_authenticate_tasks.rake +4 -0
- metadata +287 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'rails/generators/active_record/migration'
|
|
2
|
+
|
|
3
|
+
module NdrAuthenticate
|
|
4
|
+
# Installs configuration files required by NdrAuthenticate into host application.
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
include ActiveRecord::Generators::Migration
|
|
7
|
+
|
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
|
9
|
+
|
|
10
|
+
def copy_config_files
|
|
11
|
+
config_path = Pathname.new(destination_root).join('config')
|
|
12
|
+
migrations_path = Pathname.new(db_migrate_path)
|
|
13
|
+
|
|
14
|
+
copy_file 'ndr_authenticate.rb', config_path.join('initializers', 'ndr_authenticate.rb')
|
|
15
|
+
copy_file 'attribute-map.yml', config_path.join('attribute-map.yml')
|
|
16
|
+
|
|
17
|
+
migration_template 'migration.rb', migrations_path.join('add_ndr_authenticate.rb')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
phe: &phe
|
|
2
|
+
objectGuid: object_guid
|
|
3
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress:
|
|
4
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname:
|
|
5
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name:
|
|
6
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn:
|
|
7
|
+
# http://schemas.xmlsoap.org/claims/CommonName:
|
|
8
|
+
# http://schemas.xmlsoap.org/claims/EmailAddress:
|
|
9
|
+
# http://schemas.xmlsoap.org/claims/Group:
|
|
10
|
+
# http://schemas.xmlsoap.org/claims/UPN:
|
|
11
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/role:
|
|
12
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname:
|
|
13
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier:
|
|
14
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier:
|
|
15
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant:
|
|
16
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod:
|
|
17
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid:
|
|
18
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid:
|
|
19
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid:
|
|
20
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid:
|
|
21
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid:
|
|
22
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid:
|
|
23
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname:
|
|
24
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser:
|
|
25
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier:
|
|
26
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/registrationid:
|
|
27
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/displayname:
|
|
28
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/ostype:
|
|
29
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/osversion:
|
|
30
|
+
# http://schemas.microsoft.com/2012/01/devicecontext/claims/ismanaged:
|
|
31
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip:
|
|
32
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application:
|
|
33
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent:
|
|
34
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-ip:
|
|
35
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path:
|
|
36
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy:
|
|
37
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid:
|
|
38
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/applicationpolicy:
|
|
39
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/authoritykeyidentifier:
|
|
40
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/basicconstraints:
|
|
41
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku:
|
|
42
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/issuer:
|
|
43
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/issuername:
|
|
44
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/keyusage:
|
|
45
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/notafter:
|
|
46
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/notbefore:
|
|
47
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatepolicy:
|
|
48
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa:
|
|
49
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/rawdata:
|
|
50
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/san:
|
|
51
|
+
# http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber:
|
|
52
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/signaturealgorithm:
|
|
53
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/subject:
|
|
54
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/subjectkeyidentifier:
|
|
55
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/subjectname:
|
|
56
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplateinformation:
|
|
57
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplatename:
|
|
58
|
+
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint:
|
|
59
|
+
# http://schemas.microsoft.com/2012/12/certificatecontext/field/x509version:
|
|
60
|
+
# http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork:
|
|
61
|
+
# http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime:
|
|
62
|
+
# http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays:
|
|
63
|
+
# http://schemas.microsoft.com/ws/2012/01/passwordchangeurl:
|
|
64
|
+
# http://schemas.microsoft.com/claims/authnmethodsreferences:
|
|
65
|
+
# http://schemas.microsoft.com/2012/01/requestcontext/claims/client-request-id:
|
|
66
|
+
# http://schemas.microsoft.com/ws/2013/11/alternateloginid:
|
|
67
|
+
|
|
68
|
+
default: &default {}
|
|
69
|
+
|
|
70
|
+
development:
|
|
71
|
+
<<: *default
|
|
72
|
+
|
|
73
|
+
test:
|
|
74
|
+
<<: *default
|
|
75
|
+
|
|
76
|
+
production:
|
|
77
|
+
<<: *default
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class AddNdrAuthenticate < ActiveRecord::Migration[5.2]
|
|
2
|
+
class User < NdrAuthenticate.user_class.constantize; end
|
|
3
|
+
|
|
4
|
+
def change
|
|
5
|
+
add_column User.table_name, saml_session_index_key, :string
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def saml_session_index_key
|
|
11
|
+
Devise.saml_session_index_key || :session_index
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
NdrAuthenticate.configure do |config|
|
|
2
|
+
# Name of the user class within the host application.
|
|
3
|
+
config.user_class = 'User'
|
|
4
|
+
|
|
5
|
+
# Callable object that controls availability of SSO logins.
|
|
6
|
+
config.sso_enabled = ->(_request) { Rails.env.production? }
|
|
7
|
+
|
|
8
|
+
# Additional parameters to be filtered from Rails logs.
|
|
9
|
+
# config.filter_parameters = %i[
|
|
10
|
+
# SAMLRequest
|
|
11
|
+
# SAMLResponse
|
|
12
|
+
# password
|
|
13
|
+
# password_confirmation
|
|
14
|
+
# reset_password_token
|
|
15
|
+
# ]
|
|
16
|
+
|
|
17
|
+
# Credentials for Yubico Web Services (https://upgrade.yubico.com/getapikey/)
|
|
18
|
+
# config.yubikey_api_id = nil
|
|
19
|
+
# config.yubikey_api_key = nil
|
|
20
|
+
|
|
21
|
+
# Strategies for validating OTPWs for protected actions.
|
|
22
|
+
# Host applications are invited to add their own challenges. These should be `call`able objects
|
|
23
|
+
# that accept OTPW, user and controller context objects and return a boolean value.
|
|
24
|
+
# config.invalid_otp_reasons = {
|
|
25
|
+
# missing: ->(otp, _user, _context) { otp.blank? },
|
|
26
|
+
# invalid: ->(otp, _user, _context) { !Yubikey::Verify.call(otp) }
|
|
27
|
+
# }
|
|
28
|
+
|
|
29
|
+
# Apply pre-configured PHE ADFS SAML configuration.
|
|
30
|
+
config.load_defaults :phe
|
|
31
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'net/ldap'
|
|
2
|
+
module NdrAuthenticate
|
|
3
|
+
# Logic to connect to PHE AD via ldap protocol to find out if user exists
|
|
4
|
+
# and has an active/enabled account
|
|
5
|
+
class Connector
|
|
6
|
+
def configure_ldap
|
|
7
|
+
config = YAML.load_file(Rails.root.join('config', 'ldap.yml'))[Rails.env]
|
|
8
|
+
@hosts = config.fetch(:hosts)
|
|
9
|
+
@username = config.fetch(:username)
|
|
10
|
+
@password = config.fetch(:password)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def search_for(email)
|
|
14
|
+
configure_ldap
|
|
15
|
+
|
|
16
|
+
ldap = Net::LDAP.new(hosts: @hosts,
|
|
17
|
+
auth: {
|
|
18
|
+
method: :simple,
|
|
19
|
+
username: @username,
|
|
20
|
+
password: @password
|
|
21
|
+
},
|
|
22
|
+
encryption: {
|
|
23
|
+
method: :simple_tls,
|
|
24
|
+
tls_options: {
|
|
25
|
+
ca_file: Rails.root.join('config', 'phe_cacert.pem').to_s,
|
|
26
|
+
ssl_version: 'TLSv1_1'
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
email_filter = Net::LDAP::Filter.eq('userPrincipalName', email)
|
|
31
|
+
category_filter = Net::LDAP::Filter.eq('objectCategory', 'person')
|
|
32
|
+
objectclass_filter = Net::LDAP::Filter.eq('objectClass', 'user')
|
|
33
|
+
enable_filter = ~Net::LDAP::Filter.present('userAccountControl:1.2.840.113556.1.4.803:=2')
|
|
34
|
+
filter = email_filter & category_filter & objectclass_filter & enable_filter
|
|
35
|
+
treebase = 'OU=User Objects - Employee Accounts,OU=Administrated Objects,DC=phe,DC=gov,DC=uk'
|
|
36
|
+
|
|
37
|
+
user = ldap.search(base: treebase,
|
|
38
|
+
filter: filter,
|
|
39
|
+
attributes: %w[sAMAccountName], # can use attribute to ensure looking \
|
|
40
|
+
# at right person
|
|
41
|
+
return_result: true)
|
|
42
|
+
user.size == 1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# We need to require these explicitly here, to avoid Rails zeitwerk autoload issues
|
|
2
|
+
require File.join(__dir__, '../../app/controllers/concerns/ndr_authenticate/devise_helpers')
|
|
3
|
+
require File.join(__dir__, '../../app/controllers/concerns/ndr_authenticate/yubikey/protectable')
|
|
4
|
+
|
|
5
|
+
module NdrAuthenticate
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
isolate_namespace NdrAuthenticate
|
|
8
|
+
|
|
9
|
+
initializer 'ndr_authenticate.assets.precompile' do |app|
|
|
10
|
+
app.config.assets.precompile += %w[
|
|
11
|
+
ndr_authenticate/ndr_authenticate.css
|
|
12
|
+
ndr_authenticate/ndr_authenticate.js
|
|
13
|
+
ndr_authenticate/bootstrap/glyphicons-halflings-regular*
|
|
14
|
+
]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Patch DeviseSamlAuthenticatable with the ability to configure IdP/SP automagically.
|
|
18
|
+
initializer 'ndr_authenticate.devise_saml_authenticatable' do
|
|
19
|
+
DeviseSamlAuthenticatable::SamlConfig.prepend(NdrAuthenticate::SamlConfig)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer 'ndr_authenticate.action_controller' do
|
|
23
|
+
ActiveSupport.on_load(:action_controller, run_once: true) do
|
|
24
|
+
include NdrAuthenticate::DeviseHelpers
|
|
25
|
+
include NdrAuthenticate::Yubikey::Protectable
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
initializer 'ndr_authenticate.filter_parameters' do |app|
|
|
30
|
+
app.config.filter_parameters += NdrAuthenticate.filter_parameters
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
initializer 'ndr_authenticate.yubikey.configure' do
|
|
34
|
+
::Yubikey.configure do |config|
|
|
35
|
+
config.url = NdrAuthenticate.yubikey_api_url || ::Yubikey::Configuration::DEFAULT_API_URL
|
|
36
|
+
config.api_id = NdrAuthenticate.yubikey_api_id
|
|
37
|
+
config.api_key = NdrAuthenticate.yubikey_api_key
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Patch intended as a workaround for issues (hardcoded certs, SNI requirement) with the
|
|
41
|
+
# Yubikey gem follwing the YubiCloud service upgrade.
|
|
42
|
+
# See https://status.yubico.com/2019/11/21/2019-11-21-yubicloud-service-upgrade/
|
|
43
|
+
# TODO: When time allows, we should probably look at dropping the dependency and
|
|
44
|
+
# rolling our own implementation.
|
|
45
|
+
::Yubikey::OTP::Verify.prepend(Module.new do
|
|
46
|
+
def verify(args)
|
|
47
|
+
query = "id=#{@api_id}&otp=#{args[:otp]}&nonce=#{@nonce}"
|
|
48
|
+
|
|
49
|
+
uri = URI.parse(@url) + 'verify'
|
|
50
|
+
uri.query = query
|
|
51
|
+
|
|
52
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
53
|
+
http.cert_store = OpenSSL::X509::Store.new.tap(&:set_default_paths)
|
|
54
|
+
|
|
55
|
+
result = http.get(uri.request_uri)
|
|
56
|
+
@status = result.body[/status=(.*)$/, 1].strip
|
|
57
|
+
|
|
58
|
+
if @status == 'BAD_OTP' || @status == 'BACKEND_ERROR'
|
|
59
|
+
raise ::Yubikey::OTP::InvalidOTPError, "Received error: #{@status}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@status = 'BAD_RESPONSE' unless verify_response(result.body)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
initializer 'ndr_authenticate.turbolinks' do
|
|
69
|
+
ActiveSupport.on_load(:action_controller) do
|
|
70
|
+
if const_defined?('Turbolinks')
|
|
71
|
+
NdrAuthenticate::SamlSessionsController.include(NdrAuthenticate::Turbolinks)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module NdrAuthenticate
|
|
2
|
+
# Patch for DeviseSamlAuthenticatable::SamlConfig.
|
|
3
|
+
# Allows for SAML configuration to be composed and cached.
|
|
4
|
+
module SamlConfig
|
|
5
|
+
include Engine.routes.url_helpers
|
|
6
|
+
|
|
7
|
+
mattr_accessor :cache
|
|
8
|
+
self.cache = {}
|
|
9
|
+
|
|
10
|
+
def saml_config(idp_entity_id = nil, request = nil)
|
|
11
|
+
cache.fetch(idp_entity_id) do |idp|
|
|
12
|
+
settings = super
|
|
13
|
+
|
|
14
|
+
settings.issuer ||= saml_metadata_url
|
|
15
|
+
settings.assertion_consumer_service_url ||= saml_user_session_url
|
|
16
|
+
settings.assertion_consumer_logout_service_url ||= idp_destroy_saml_user_session_url
|
|
17
|
+
|
|
18
|
+
idp_settings.each { |key, value| settings.try("#{key}=", value) }
|
|
19
|
+
|
|
20
|
+
cache.store(idp, settings)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def default_url_options
|
|
27
|
+
{ host: request.host_with_port, protocol: request.protocol }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def idp_settings
|
|
31
|
+
return {} unless NdrAuthenticate.idp_metadata_url
|
|
32
|
+
|
|
33
|
+
OneLogin::RubySaml::IdpMetadataParser.new.
|
|
34
|
+
parse_remote_to_hash(NdrAuthenticate.idp_metadata_url)
|
|
35
|
+
rescue OneLogin::RubySaml::HttpError
|
|
36
|
+
{}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module NdrAuthenticate
|
|
2
|
+
module Yubikey
|
|
3
|
+
# Performs basic validity check of YubiKey OTPWs.
|
|
4
|
+
class Verify
|
|
5
|
+
attr_reader :otp
|
|
6
|
+
|
|
7
|
+
def self.call(otp)
|
|
8
|
+
new(otp).valid?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(otp)
|
|
12
|
+
@otp = otp
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid?
|
|
16
|
+
verify.valid?
|
|
17
|
+
rescue ::Yubikey::OTP::InvalidOTPError
|
|
18
|
+
false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def verify
|
|
22
|
+
@verify ||= ::Yubikey::OTP::Verify.new(otp: otp)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require 'devise'
|
|
2
|
+
require 'devise_saml_authenticatable'
|
|
3
|
+
require 'yubikey'
|
|
4
|
+
require 'ndr_ui'
|
|
5
|
+
require 'ndr_authenticate/engine'
|
|
6
|
+
require 'ndr_authenticate/saml_config'
|
|
7
|
+
require 'ndr_authenticate/yubikey'
|
|
8
|
+
|
|
9
|
+
# Configuration and convenience methods.
|
|
10
|
+
module NdrAuthenticate
|
|
11
|
+
# Name of the user class within the host application.
|
|
12
|
+
mattr_accessor :user_class
|
|
13
|
+
self.user_class = 'User'
|
|
14
|
+
|
|
15
|
+
# Callable object that controls availability of SSO logins.
|
|
16
|
+
# Set to false to disable entirely.
|
|
17
|
+
mattr_accessor :sso_enabled
|
|
18
|
+
self.sso_enabled = ->(_request) { Rails.env.production? }
|
|
19
|
+
|
|
20
|
+
# URL for the IdP metadata endpoint. When set allows IdP to be configured automagically,
|
|
21
|
+
# providing the URL is reachable.
|
|
22
|
+
mattr_accessor :idp_metadata_url
|
|
23
|
+
self.idp_metadata_url = nil
|
|
24
|
+
|
|
25
|
+
# Additional parameters to be filtered from Rails logs.
|
|
26
|
+
mattr_accessor :filter_parameters
|
|
27
|
+
self.filter_parameters = %i[
|
|
28
|
+
SAMLRequest
|
|
29
|
+
SAMLResponse
|
|
30
|
+
password
|
|
31
|
+
password_confirmation
|
|
32
|
+
reset_password_token
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Configuration for Yubico Web Services (https://upgrade.yubico.com/getapikey/)
|
|
36
|
+
mattr_accessor :yubikey_api_url
|
|
37
|
+
mattr_accessor :yubikey_api_id
|
|
38
|
+
mattr_accessor :yubikey_api_key
|
|
39
|
+
|
|
40
|
+
# TODO: There's likely to be significant overlap with :invalid_otp_reasons - can we DRY
|
|
41
|
+
# this is up somehow?
|
|
42
|
+
mattr_accessor :invalid_key_reasons
|
|
43
|
+
self.invalid_key_reasons = {
|
|
44
|
+
missing: ->(key, _user, _context) { key.blank? }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Strategies for validating OTPWs for protected actions.
|
|
48
|
+
# Host applications are invited to add their own challenges. These should be `call`able objects
|
|
49
|
+
# that accept OTPW, user and controller context objects and return a boolean value.
|
|
50
|
+
mattr_accessor :invalid_otp_reasons
|
|
51
|
+
self.invalid_otp_reasons = {
|
|
52
|
+
missing: ->(otp, _user, _context) { otp.blank? },
|
|
53
|
+
invalid: ->(otp, _user, _context) { !Yubikey::Verify.call(otp) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
# Syntactic sugar for configuring NdrAuthenticate in an initializer file within host app.
|
|
58
|
+
def configure
|
|
59
|
+
yield(self)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def devise
|
|
63
|
+
Devise
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def default_certificate
|
|
67
|
+
File.read(saml_certificates_path.join('certificate.pem'))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_defaults(name = :phe)
|
|
71
|
+
case name.to_sym
|
|
72
|
+
when :phe
|
|
73
|
+
load_phe_defaults
|
|
74
|
+
else
|
|
75
|
+
raise "Unknown configuration: #{name}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def sso_enabled_ever?
|
|
80
|
+
sso_enabled.present?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def saml_certificates_path
|
|
86
|
+
@saml_certificates_path ||= Pathname.new(Engine.root.join('config', 'certificates', 'saml'))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def load_phe_defaults
|
|
90
|
+
devise.setup do |config|
|
|
91
|
+
config.saml_default_user_key = :object_guid
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
devise.saml_configure do |config|
|
|
95
|
+
# General
|
|
96
|
+
config.name_identifier_format = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
|
|
97
|
+
|
|
98
|
+
# IdP (ADFS)
|
|
99
|
+
config.idp_entity_id = 'http://fs.phe.gov.uk/adfs/services/trust'
|
|
100
|
+
config.idp_sso_target_url = 'https://fs.phe.gov.uk/adfs/ls/'
|
|
101
|
+
config.idp_slo_target_url = 'https://fs.phe.gov.uk/adfs/ls/'
|
|
102
|
+
|
|
103
|
+
# Privacy/Security
|
|
104
|
+
security = config.security
|
|
105
|
+
security[:authn_requests_signed] = true
|
|
106
|
+
security[:logout_requests_signed] = true
|
|
107
|
+
security[:logout_responses_signed] = true
|
|
108
|
+
security[:want_assertions_signed] = true
|
|
109
|
+
security[:want_assertions_encrypted] = true
|
|
110
|
+
security[:want_name_id] = true
|
|
111
|
+
security[:metadata_signed] = true
|
|
112
|
+
security[:embed_sign] = true
|
|
113
|
+
security[:check_idp_cert_expiration] = true
|
|
114
|
+
security[:check_sp_cert_expiration] = true
|
|
115
|
+
security[:digest_method] = 'http://www.w3.org/2001/04/xmlenc#sha256'
|
|
116
|
+
security[:signature_method] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
|
117
|
+
|
|
118
|
+
# Certificates
|
|
119
|
+
config.certificate = default_certificate
|
|
120
|
+
config.idp_cert_multi = {
|
|
121
|
+
encryption: [File.read(saml_certificates_path.join('encryption.phe.adfs.pem'))],
|
|
122
|
+
signing: [File.read(saml_certificates_path.join('signing.phe.adfs.pem'))]
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|