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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +58 -0
  3. data/CODE_OF_CONDUCT.md +13 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +227 -0
  6. data/Rakefile +33 -0
  7. data/app/assets/config/ndr_authenticate_manifest.js +2 -0
  8. data/app/assets/images/ndr_authenticate/.keep +0 -0
  9. data/app/assets/javascripts/ndr_authenticate/ndr_authenticate.js +14 -0
  10. data/app/assets/stylesheets/ndr_authenticate/ndr_authenticate.scss +1 -0
  11. data/app/controllers/concerns/ndr_authenticate/authenticatable.rb +21 -0
  12. data/app/controllers/concerns/ndr_authenticate/devise_helpers.rb +17 -0
  13. data/app/controllers/concerns/ndr_authenticate/turbolinks.rb +31 -0
  14. data/app/controllers/concerns/ndr_authenticate/yubikey/authenticatable.rb +94 -0
  15. data/app/controllers/concerns/ndr_authenticate/yubikey/protectable.rb +103 -0
  16. data/app/controllers/ndr_authenticate/application_controller.rb +22 -0
  17. data/app/controllers/ndr_authenticate/authentication_controller.rb +27 -0
  18. data/app/controllers/ndr_authenticate/saml_sessions_controller.rb +46 -0
  19. data/app/controllers/ndr_authenticate/sessions_controller.rb +22 -0
  20. data/app/helpers/ndr_authenticate/application_helper.rb +22 -0
  21. data/app/helpers/ndr_authenticate/authentication_helper.rb +4 -0
  22. data/app/jobs/ndr_authenticate/application_job.rb +4 -0
  23. data/app/mailers/ndr_authenticate/application_mailer.rb +6 -0
  24. data/app/models/ndr_authenticate/application_record.rb +5 -0
  25. data/app/views/devise/passwords/edit.html.erb +23 -0
  26. data/app/views/devise/passwords/new.html.erb +18 -0
  27. data/app/views/devise/sessions/new.html.erb +21 -0
  28. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  29. data/app/views/devise/shared/_links.html.erb +25 -0
  30. data/app/views/layouts/ndr_authenticate/ndr_authenticate.html.erb +47 -0
  31. data/app/views/ndr_authenticate/authentication/check_active.html.erb +30 -0
  32. data/app/views/ndr_authenticate/shared/_legal_notice.html.erb +13 -0
  33. data/app/views/ndr_authenticate/yubikey/protectable/_form.html.erb +44 -0
  34. data/app/views/ndr_authenticate/yubikey/protectable/_modal.html.erb +10 -0
  35. data/app/views/ndr_authenticate/yubikey/protectable/_panel.html.erb +13 -0
  36. data/app/views/ndr_authenticate/yubikey/protectable/challenge.html.erb +9 -0
  37. data/app/views/ndr_authenticate/yubikey/protectable/challenge.js.erb +41 -0
  38. data/app/views/shared/_flash_messages.html.erb +17 -0
  39. data/config/certificates/saml/certificate.pem +23 -0
  40. data/config/certificates/saml/encryption.phe.adfs.pem +18 -0
  41. data/config/certificates/saml/signing.phe.adfs.pem +19 -0
  42. data/config/initializers/devise.rb +37 -0
  43. data/config/locales/en.yml +15 -0
  44. data/config/routes.rb +24 -0
  45. data/lib/generators/ndr_authenticate/install/USAGE +10 -0
  46. data/lib/generators/ndr_authenticate/install/install_generator.rb +20 -0
  47. data/lib/generators/ndr_authenticate/install/templates/attribute-map.yml +77 -0
  48. data/lib/generators/ndr_authenticate/install/templates/migration.rb +13 -0
  49. data/lib/generators/ndr_authenticate/install/templates/ndr_authenticate.rb +31 -0
  50. data/lib/ndr_authenticate/connector.rb +45 -0
  51. data/lib/ndr_authenticate/engine.rb +76 -0
  52. data/lib/ndr_authenticate/saml_config.rb +39 -0
  53. data/lib/ndr_authenticate/version.rb +3 -0
  54. data/lib/ndr_authenticate/yubikey/verify.rb +26 -0
  55. data/lib/ndr_authenticate/yubikey.rb +7 -0
  56. data/lib/ndr_authenticate.rb +127 -0
  57. data/lib/tasks/ndr_authenticate_tasks.rake +4 -0
  58. metadata +287 -0
@@ -0,0 +1,27 @@
1
+ require_relative 'application_controller'
2
+ require 'ndr_authenticate/connector'
3
+
4
+ module NdrAuthenticate
5
+ class AuthenticationController < ApplicationController
6
+ # To check if user is active on AD
7
+ def check_active
8
+ email = params[:user_mail]
9
+ @status = ''
10
+ flash.clear
11
+ if email.present?
12
+ if email.match?(/\b[A-Z0-9._%a-z\-]+@phe\.gov\.uk\z/)
13
+ begin
14
+ conn = NdrAuthenticate::Connector.new
15
+ status = conn.search_for(params[:user_mail])
16
+ @status = status ? 'Active' : 'Inactive'
17
+ rescue => e
18
+ flash[:error] = e.message
19
+ end
20
+ else
21
+ flash[:error] = 'Please provide PHE email address only'
22
+ end
23
+ end
24
+ render :check_active
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ module NdrAuthenticate
2
+ # Manages SAML sessions.
3
+ class SamlSessionsController < Devise::SamlSessionsController
4
+ include Authenticatable
5
+
6
+ before_action :redirect_unless_sso_enabled, only: %i[new create]
7
+
8
+ def new
9
+ super
10
+ end
11
+
12
+ def create
13
+ super
14
+ end
15
+
16
+ # Capture NameID and SessionIndex values needed for the SLO request before terminating the
17
+ # local user session.
18
+ def destroy
19
+ @sessionindex = current_user.try(Devise.saml_session_index_key)
20
+ @nameid = current_user.try(Devise.saml_default_user_key)
21
+
22
+ super
23
+ end
24
+
25
+ private
26
+
27
+ def redirect_unless_sso_enabled
28
+ redirect_to new_user_session_path unless sso_enabled?
29
+ end
30
+
31
+ # Ensure that the SAML logout request contains the correct NameID and SessionIndex values.
32
+ # DeviseSamlAuthenticatable doesn't appear to do this by default.
33
+ def after_sign_out_path_for(*)
34
+ idp_entity_id = get_idp_entity_id(params)
35
+ settings = saml_config(idp_entity_id)
36
+
37
+ logout_request = OneLogin::RubySaml::Logoutrequest.new
38
+ logout_request.create(
39
+ settings.clone.tap do |request_settings|
40
+ request_settings.sessionindex = @sessionindex
41
+ request_settings.name_identifier_value = @nameid
42
+ end
43
+ )
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ module NdrAuthenticate
2
+ # Handles user sessions.
3
+ class SessionsController < Devise::SessionsController
4
+ include Authenticatable
5
+
6
+ before_action :redirect_if_sso_enabled, only: %i[new create]
7
+
8
+ def new
9
+ super
10
+ end
11
+
12
+ def create
13
+ super
14
+ end
15
+
16
+ private
17
+
18
+ def redirect_if_sso_enabled
19
+ redirect_to new_saml_user_session_path if sso_enabled?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module NdrAuthenticate
2
+ # NdrAuthenticate view helpers.
3
+ module ApplicationHelper
4
+ def sign_in_link(text = 'Sign In', **options)
5
+ return if user_signed_in?
6
+
7
+ link_to text, ndr_authenticate.new_user_session_path, options
8
+ end
9
+
10
+ def sign_out_link(text = 'Sign Out', **options)
11
+ return unless user_signed_in?
12
+
13
+ path = if user_authenticated_with?(:saml_authenticatable)
14
+ ndr_authenticate.destroy_saml_user_session_path
15
+ else
16
+ ndr_authenticate.destroy_user_session_path
17
+ end
18
+
19
+ link_to text, path, options.merge(method: :delete)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module NdrAuthenticate
2
+ module AuthenticationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module NdrAuthenticate
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module NdrAuthenticate
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module NdrAuthenticate
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ <h3>Reset your password</h3>
2
+
3
+ <div class="well">
4
+ <%= bootstrap_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }, horizontal: 4) do |f| %>
5
+ <%= f.hidden_field :reset_password_token %>
6
+
7
+ <%= f.control_group :password, 'New password', {}, { class: 'col-md-4' } do %>
8
+ <%= f.password_field :password, autofocus: true, autocomplete: 'off', :'data-suggestible' => true %>
9
+ <% end %>
10
+
11
+ <%= f.control_group :password_confirmation, 'Confirm new password', {}, { class: 'col-md-4' } do %>
12
+ <%= f.password_field :password_confirmation, autocomplete: 'off', :'data-suggestible' => true %>
13
+ <% end %>
14
+
15
+ <%= f.control_group nil, nil, {}, { class: 'col-md-4' } do %>
16
+ <%= f.submit 'Change my password', class: 'btn btn-primary btn-block' %>
17
+
18
+ <div style="text-align:center;margin-top:1em;">
19
+ <%= render "devise/shared/links" %>
20
+ </div>
21
+ <% end %>
22
+ <% end %>
23
+ </div>
@@ -0,0 +1,18 @@
1
+ <h3>Forgot your password?</h3>
2
+
3
+ <div class="well">
4
+ <%= bootstrap_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }, horizontal: 4) do |f| %>
5
+
6
+ <%= f.control_group :email, nil, {}, { class: 'col-md-4' } do %>
7
+ <%= f.text_field :email, autofocus: true %>
8
+ <% end %>
9
+
10
+ <%= f.control_group nil, nil, {}, { class: 'col-md-4' } do %>
11
+ <%= f.submit 'Send me reset password instructions', class: 'btn btn-primary btn-block' %>
12
+
13
+ <div style="text-align:center;margin-top:1em;">
14
+ <%= render "devise/shared/links" %>
15
+ </div>
16
+ <% end %>
17
+ <% end %>
18
+ </div>
@@ -0,0 +1,21 @@
1
+ <%= render 'ndr_authenticate/shared/legal_notice' %>
2
+
3
+ <div class="well">
4
+ <%= bootstrap_form_for(resource, as: resource_name, url: session_path(resource_name), horizontal: 4) do |f| %>
5
+ <%= f.control_group :email, nil, {}, { class: 'col-md-4' } do %>
6
+ <%= f.text_field :email %>
7
+ <% end %>
8
+
9
+ <%= f.control_group :password, nil, {}, { class: 'col-md-4' } do %>
10
+ <%= f.password_field :password, autocomplete: 'off' %>
11
+ <% end %>
12
+
13
+ <%= f.control_group nil, nil, {}, { class: 'col-md-4' } do %>
14
+ <%= f.submit 'Log in', class: 'btn btn-primary btn-block' %>
15
+
16
+ <div style="text-align:center;margin-top:1em;">
17
+ <%= render "devise/shared/links" %>
18
+ </div>
19
+ <% end %>
20
+ <% end %>
21
+ </div>
@@ -0,0 +1,15 @@
1
+ <% if resource.errors.any? %>
2
+ <div id="error_explanation">
3
+ <h2>
4
+ <%= I18n.t("errors.messages.not_saved",
5
+ count: resource.errors.count,
6
+ resource: resource.class.model_name.human.downcase)
7
+ %>
8
+ </h2>
9
+ <ul>
10
+ <% resource.errors.full_messages.each do |message| %>
11
+ <li><%= message %></li>
12
+ <% end %>
13
+ </ul>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <%- if controller_name != 'sessions' %>
2
+ <%= link_to "Log in", new_session_path(resource_name) %><br />
3
+ <% end %>
4
+
5
+ <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6
+ <%= link_to "Sign up", new_registration_path(resource_name) %><br />
7
+ <% end %>
8
+
9
+ <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
10
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
11
+ <% end %>
12
+
13
+ <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
14
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
15
+ <% end %>
16
+
17
+ <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
19
+ <% end %>
20
+
21
+ <%- if devise_mapping.omniauthable? %>
22
+ <%- resource_class.omniauth_providers.each do |provider| %>
23
+ <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
24
+ <% end %>
25
+ <% end %>
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>NdrAuthenticate</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "ndr_authenticate/ndr_authenticate", media: "all" %>
9
+ <%= javascript_include_tag "ndr_authenticate/ndr_authenticate" %>
10
+ </head>
11
+ <body class="bootstrap">
12
+
13
+ <div class="navbar navbar-default navbar-fixed-top" role="navigation">
14
+ <div class="container-fluid">
15
+ <!-- Brand and toggle get grouped for better mobile display -->
16
+ <div class="navbar-header">
17
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
18
+ <span class="sr-only">Toggle navigation</span>
19
+ <span class="icon-bar"></span>
20
+ <span class="icon-bar"></span>
21
+ <span class="icon-bar"></span>
22
+ </button>
23
+
24
+ <%= link_to t('system.name', default: '&#171; Back to App'.html_safe), '/', class: "navbar-brand" %>
25
+ </div>
26
+ <ul class="nav navbar-nav navbar-right">
27
+ <li><p class="navbar-text">NDR Authentication</p></li>
28
+ </div>
29
+ </div>
30
+
31
+
32
+
33
+ <div class="container">
34
+ <!-- An empty div -->
35
+ <div>
36
+ <p>&nbsp;</p>
37
+ <p>&nbsp;</p>
38
+ </div>
39
+ <div>
40
+ <%= render 'shared/flash_messages' %>
41
+ </div>
42
+
43
+ <%= yield %>
44
+ </div>
45
+
46
+ </body>
47
+ </html>
@@ -0,0 +1,30 @@
1
+ <h3>
2
+ Active directory user search
3
+ </h3>
4
+
5
+ <%= form_tag({ action: 'check_active' }, class: 'form form-horizontal') do %>
6
+ <div class="form-group">
7
+ <%= label_tag 'User PHE e-mail address', nil ,class: 'control-label col-md-3' %>
8
+ <div class="col-md-3">
9
+ <%= text_field_tag(:user_mail, params[:user_mail], class: 'form-control', placeholder: 'xxx@phe.gov.uk') %>
10
+ </div>
11
+ </div>
12
+ <%= button_control_group do %>
13
+ <%= submit_tag 'Search', 'data-disable-with': 'Searching...' , class: 'btn btn-primary bob'%>
14
+ <% end %>
15
+ <% end %>
16
+
17
+ <% unless @status.empty? %>
18
+ <table class="table table-hover">
19
+ <tr>
20
+ <th colspan="2">
21
+ Status in Active Directory
22
+ <th>
23
+ </tr>
24
+ <tr>
25
+ <td class='info' colspan='2'>
26
+ <%= @status %>
27
+ </td>
28
+ </tr>
29
+ <table>
30
+ <%end%>
@@ -0,0 +1,13 @@
1
+ <%= bootstrap_alert_tag(:info, dismissible: false) do %>
2
+ <strong>Legal Notice</strong>
3
+ This system is a restricted access system; only personnel authorised by
4
+ <%= t :organisation_abbreviation, scope: :system, default: 'PHE' %> may access this system.
5
+ Unauthorised access is prohibited and is contrary to the
6
+ <%= link_to 'Computer Misuse Act 1990', 'http://www.legislation.gov.uk/ukpga/1990/18/contents', :target => :new, :class => 'alert-link' %>,
7
+ which may result in criminal offences and a claim for damages.
8
+ All activity on this system is subject to monitoring.
9
+ If information collected reveals possible criminal activity or
10
+ activity that exceeds privileges, evidence of such activity may
11
+ be provided to the relevant authorities for further action.
12
+ <strong>By continuing past this point you expressly consent to this monitoring.</strong>
13
+ <% end %>
@@ -0,0 +1,44 @@
1
+ <%
2
+ url = url_for(controller: params[:controller], action: params[:action])
3
+ method = params[:_method] || request.method
4
+ relay = params.dig(:ndr_authenticate, :relay) || Base64.urlsafe_encode64(
5
+ params.except(:authenticity_token).to_json
6
+ )
7
+ i18n_scope = %i[ndr_authenticate yubikey protectable challenge]
8
+ %>
9
+
10
+ <% content_for :header do %>
11
+ <div class="text-center">
12
+ <h4>
13
+ <span class="glyphicon glyphicon-lock"></span>
14
+ <strong><%= t(:title, scope: i18n_scope) %></strong>
15
+ </h4>
16
+ </div>
17
+ <% end %>
18
+
19
+ <% content_for :body do %>
20
+ <% if params.dig(:ndr_authenticate, :otp) %>
21
+ <%= bootstrap_alert_tag(:danger, t(:invalid_otp, scope: i18n_scope)) %>
22
+ <% end %>
23
+
24
+ <div class="text-center">
25
+ <p class="lead"><%= t(:lead, scope: i18n_scope) %></p>
26
+ <p><%= t(:instructions, scope: i18n_scope) %></p>
27
+ <br />
28
+ </div>
29
+
30
+ <%= bootstrap_form_with(url: url, scope: :ndr_authenticate, method: method, local: local) do |form| %>
31
+ <%= form.hidden_field(:relay, value: relay) %>
32
+
33
+ <%= form.control_group nil do %>
34
+ <%= form.password_field(:otp, autofocus: true) %>
35
+ <% end %>
36
+
37
+ <br />
38
+ <div class="text-center">
39
+ <%= form.submit 'Submit', class: 'btn btn-primary' %>
40
+ </div>
41
+ <% end %>
42
+ <% end %>
43
+
44
+ <%= render(layout: layout) {} %>
@@ -0,0 +1,10 @@
1
+ <div class="modal" id="yubikey-challenge" tabindex="-1" role="dialog">
2
+ <div class="modal-dialog" role="document">
3
+ <div class="modal-content">
4
+ <%#= bootstrap_modal_dialog_tag id: 'yubikey-challenge' do %>
5
+ <%= bootstrap_modal_header_tag yield(:header), dismissible: true %>
6
+ <%= bootstrap_modal_body_tag yield(:body) %>
7
+ <%# end %>
8
+ </div>
9
+ </div>
10
+ </div>
@@ -0,0 +1,13 @@
1
+ <% if defined?(bootstrap_panel_tag) %><%# Old bootstrap 3 codepath %>
2
+ <%= bootstrap_panel_tag yield(:header), id: 'yubikey-challenge', style: 'margin-top: 50px;' do %>
3
+ <%= bootstrap_panel_body_tag do %>
4
+ <%= yield(:body) %>
5
+ <% end %>
6
+ <% end %>
7
+ <% else %><%# New bootstrap 5 codepath %>
8
+ <%= bootstrap_card_tag yield(:header), nil, id: 'yubikey-challenge', style: 'margin-top: 50px;' do %>
9
+ <%= bootstrap_card_body_tag do %>
10
+ <%= yield(:body) %>
11
+ <% end %>
12
+ <% end %>
13
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <%
2
+ path = 'ndr_authenticate/yubikey/protectable'
3
+ %>
4
+
5
+ <div class="row">
6
+ <div class="col-sm-6 col-sm-offset-3">
7
+ <%= render "#{path}/form", layout: "#{path}/panel", local: true %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,41 @@
1
+ <%
2
+ # TODO: This could/should be packaged up as a Stimulus controller.
3
+ path = 'ndr_authenticate/yubikey/protectable'
4
+ form = render "#{path}/form", layout: "#{path}/modal", local: false
5
+ alert = bootstrap_alert_tag(:danger, t('ndr_authenticate.yubikey.protectable.challenge.invalid_otp'))
6
+ %>
7
+
8
+ var form = '<%= j(form) %>';
9
+ var challenge = '#yubikey-challenge';
10
+ var input = 'input[name="ndr_authenticate[otp]"';
11
+
12
+ // New challenge form; add it to the DOM and set up some event listeners...
13
+ if($(challenge).length == 0) {
14
+ $('body').append(form);
15
+
16
+ // Remove the challenge form once it's served it's purpose (or user backs out); we'll replace it
17
+ // wholesale the next time it's needed.
18
+ $(challenge).on('hidden.bs.modal', function() {
19
+ $(challenge).remove();
20
+ });
21
+
22
+ // Seems like Rails UJS doesn't allow these events to bubble?
23
+ $('form', challenge).on('ajax:complete', function onChallenge(event) {
24
+ var xhr = (arguments.length == 1 ? event.detail[0] : arguments[1]);
25
+
26
+ // If the response has the `yubikey-required` header then a bad OTP must have been submitted
27
+ // and we should clear form and provide some user feedback...
28
+ if(xhr.getResponseHeader('yubikey-required')) {
29
+ if($('.modal-body .alert', challenge).length == 0)
30
+ $('.modal-body', challenge).prepend('<%= j(alert) %>');
31
+
32
+ $(input).val(null);
33
+ } else {
34
+ // Successful challenge; close (and trigger removal of) the modal form.
35
+ $(challenge).modal('hide');
36
+ }
37
+ });
38
+ };
39
+
40
+ $(challenge).modal('show');
41
+ $(input).focus();
@@ -0,0 +1,17 @@
1
+ <div>
2
+ <% if flash[:notice].present? %>
3
+ <%= bootstrap_alert_tag(:info, safe_join(Array(flash[:notice]), tag(:br))) %>
4
+ <% end %>
5
+
6
+ <% if flash[:error].present? %>
7
+ <%= bootstrap_alert_tag(:danger, safe_join(Array(flash[:error]), tag(:br))) %>
8
+ <% end %>
9
+
10
+ <% if flash[:alert].present? %>
11
+ <%= bootstrap_alert_tag(:danger, safe_join(Array(flash[:alert]), tag(:br))) %>
12
+ <% end %>
13
+
14
+ <% if flash[:warning].present? %>
15
+ <%= bootstrap_alert_tag(:warning, safe_join(Array(flash[:alert]), tag(:br))) %>
16
+ <% end %>
17
+ </div>
@@ -0,0 +1,23 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDvjCCAqYCCQCR3CjZjsYCszANBgkqhkiG9w0BAQsFADCBoDELMAkGA1UEBhMC
3
+ R0IxDjAMBgNVBAgMBUNhbWJzMRIwEAYDVQQHDAlDYW1icmlkZ2UxDDAKBgNVBAoM
4
+ A1BIRTEMMAoGA1UECwwDTkRSMSYwJAYDVQQDDB1ORFIgQXV0aGVudGljYXRlIFNB
5
+ TUwgU2lnbmluZzEpMCcGCSqGSIb3DQEJARYaYWRtaW5pc3RyYXRvckBlY3JpYy5u
6
+ aHMudWswHhcNMjAwOTA5MDkyNTEzWhcNMzAwOTA3MDkyNTEzWjCBoDELMAkGA1UE
7
+ BhMCR0IxDjAMBgNVBAgMBUNhbWJzMRIwEAYDVQQHDAlDYW1icmlkZ2UxDDAKBgNV
8
+ BAoMA1BIRTEMMAoGA1UECwwDTkRSMSYwJAYDVQQDDB1ORFIgQXV0aGVudGljYXRl
9
+ IFNBTUwgU2lnbmluZzEpMCcGCSqGSIb3DQEJARYaYWRtaW5pc3RyYXRvckBlY3Jp
10
+ Yy5uaHMudWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXJyFcZER7
11
+ NjBJA93YYdZK3Ck4Yca9td1e++wysP7mpAIKC6wwkcmkkpKQgLvmxIwFhrswpRLm
12
+ HBhex0q3xE1GWOqHO3Ve8c6yki4PxbkLHWhHhaCrrvFAZt0mS5fReezNjAElfVZv
13
+ ZmreH/GqQ4HH7ZR66OkCK3EqlMHSfICDKpjrHswP4GjeXvxf859+r6OC0W+PEnfm
14
+ sxmRdpyX+XKuf13DZ3zZfEaEPwb1Kg74YtaE/d0WdeZGH/d42O4cWGlyCL6xAoE7
15
+ AOHi96ytTqvd6QDcQpUOwQKG4iytehHFtkPZTMD2HL6GUmce6mbQDWyR8/7sI1tg
16
+ Opn+B5/+WpndAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHv8y6VayX7AW473mhso
17
+ jFHh/PQxcj3iLXIT2xC96tfUiz93RQw1ekdSQqcdRD9sRPx6V+RB8rhCBr3TtUXz
18
+ iyVFXWWtIdVRR4I3iLaw1SQXKhYaWZvaD5fZVl38aqBPzXiF0wdPm138BBo1PjDL
19
+ u8JOh1ArbTsJk7wkmweRsl2NmoBhFGxpvRvN0QqlMsHJIKHw4R7k7i16ETQfLkJo
20
+ WYDj5kGylb8V2aQTf7peZy81VdfQnc2z+/9SvsHQjwDUmjZK8iqirrPgtbxQF/3h
21
+ vO7EkLIbEitDdkvBD5RecEqqpyCR8Z1oTGvQPM28644yMVop3MjSlvwWb06uxlcY
22
+ Vzk=
23
+ -----END CERTIFICATE-----
@@ -0,0 +1,18 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIC3DCCAcSgAwIBAgIQKl6Cdeu33JVEhYdcQiJWxzANBgkqhkiG9w0BAQsFADAq
3
+ MSgwJgYDVQQDEx9BREZTIEVuY3J5cHRpb24gLSBmcy5waGUuZ292LnVrMB4XDTIw
4
+ MDYyMTIzMjA1NVoXDTIxMDYyMTIzMjA1NVowKjEoMCYGA1UEAxMfQURGUyBFbmNy
5
+ eXB0aW9uIC0gZnMucGhlLmdvdi51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
6
+ AQoCggEBAKumzkThSDUSzBMUPqM7hqk7nHKwZf9fsUBhYI+b7kKbjC6+bJFEIPBX
7
+ RKZPkDKb9oCCoFAogZIcxE/RLLAmK4ZC7sfFNd9SYp89bLAoL3sKFM9HEpMYFDKz
8
+ isFH83btch1vXygovL8XdzmIyv7C+jNwviU5bpUDeA+BlNBl0OFb7792HY+dBoGX
9
+ OsoUMsNRrk8n1dj1CnOJp3PvnYgv9YeeF2bN/R/KUn3ejdNdCCfAatpB+8kN2MHq
10
+ vRJXaeJowubf8IUy59iTEP+R4+8r65cjsizTqxyo75GRlLJxDHa8vOjluHvveR0x
11
+ EAbi1G6AFuI2vw6WXtlntca/toEZjSsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA
12
+ d8BEGKqsg5OcMio3UJZo/CTJjtGBd6thCuAFPGrfno4Et206GqB7MzThzuvI10Vc
13
+ eEx/QLIP9WiVflg8Cq7tZWM8t10gRLW2VeZ/wIqtaoVeHGSRKOJwOPg0ENhHZLod
14
+ AQsIo659STd6BNypNT3p387aNS2vER/mqJC/OgDiqLtSGGcYJQRb6RmWymTo7GM0
15
+ NLwaW/CE3VReiifBKt9s26bs3tinhOMsd2f1r5iamJP0p/bp3dJ/aerkZDgWNaGp
16
+ QYoPT2ywkqa2kSqg3K7McFMbdtC8LdvyOECwbCKUeSIzaCFSx/KaUY7PWGtVDSnf
17
+ S8/WUIRuSH8QoDY5KxxoMw==
18
+ -----END CERTIFICATE-----
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIC1jCCAb6gAwIBAgIQJURsk+h0Fo5A+bf3mxtwNDANBgkqhkiG9w0BAQsFADAn
3
+ MSUwIwYDVQQDExxBREZTIFNpZ25pbmcgLSBmcy5waGUuZ292LnVrMB4XDTIwMDYy
4
+ MTIzMjA1N1oXDTIxMDYyMTIzMjA1N1owJzElMCMGA1UEAxMcQURGUyBTaWduaW5n
5
+ IC0gZnMucGhlLmdvdi51azCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
6
+ AL/DhLdxZobVXZQI858Fo1K1so1StuaygMbqIu3IaMHnpNEN3lXFaUFjJeo17k0B
7
+ V3A6E7A7O/h7Tu//sCUc6tN66+jAOXwB/yVgaMW/DFou01EJMWlBZqzBKkz5nSWJ
8
+ kNlR6f2XB/NoGKyULFbWAt5I8onkjCuawsDP/FameDasMMmpBPKoFqNrHmdcJGAw
9
+ K1JLxdo/Oc6HOImrWgexBiH6xV0KrXf4OE5JrqUC44H5BDM/bxnLQWca2plbVRtf
10
+ yDnqAJ2KmaXZjIYQScRMK1/UocqYmUvPIvCy9gdCQ++4kLbJUtrvxNg8RUEHD9oO
11
+ 7RY7mMnmfTyWc78J1CD6lHUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAp1MngXjt
12
+ IRnSqVHoSnzRcQwZ+wxzP/Apgr0yjkno2/0JXDxZWkksIxU5UoBOdZDde0/vl+vS
13
+ VoZmUzv/SPtrdNNJE86TJ6cYbj+VfF6LLhyzr2OAzmgqa2qkDktvTMO5RyksPFXY
14
+ Vx89rkh39SHzi5i5ZNFeOLRBJlyP3rT+aWmX1WgHXdM/OCnqLO7v4NicTsV1woys
15
+ fBN95lmIxM6Mom5WuVNs/LSSASATcyXsfsMtAyNWFB58r+L9vp7QuMSQw3KPGLtX
16
+ Jo+1YldmQNZamd5GAM2NOHNFUKGk473qA8ZYr5wVZX/E5rBU8TNZcYVAob8vbOXJ
17
+ Dm+MNclHHtFmkA==
18
+ -----END CERTIFICATE-----
19
+
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Use this hook to configure devise mailer, warden hooks and so forth.
4
+ # Many of these configuration options can be set straight in your model.
5
+ Devise.setup do |config|
6
+ # ==> Controller configuration
7
+ # Configure the parent class to the devise controllers.
8
+ # config.parent_controller = 'DeviseController'
9
+ config.parent_controller = 'NdrAuthenticate::ApplicationController'
10
+
11
+ # ==> Mountable engine configurations
12
+ # When using Devise inside an engine, let's call it `MyEngine`, and this engine
13
+ # is mountable, there are some extra configurations to be taken into account.
14
+ # The following options are available, assuming the engine is mounted as:
15
+ #
16
+ # mount MyEngine, at: '/my_engine'
17
+ #
18
+ # The router that invoked `devise_for`, in the example above, would be:
19
+ # config.router_name = :my_engine
20
+ config.router_name = :ndr_authenticate
21
+
22
+ # ==> Configuration for :saml_authenticatable
23
+
24
+ # Create user if the user does not exist. (Default is false)
25
+ config.saml_create_user = true
26
+
27
+ # Update the attributes of the user after a successful login. (Default is false)
28
+ config.saml_update_user = true
29
+
30
+ # Set the default user key. The user will be looked up by this key. Make
31
+ # sure that the Authentication Response includes the attribute.
32
+ config.saml_default_user_key = :email
33
+
34
+ # Optional. This stores the session index defined by the IDP during login. If provided it will
35
+ # be used as a salt for the user's session to facilitate an IDP initiated logout request.
36
+ config.saml_session_index_key = :session_index
37
+ end
@@ -0,0 +1,15 @@
1
+ en:
2
+ ndr_authenticate:
3
+ yubikey:
4
+ protectable:
5
+ challenge:
6
+ title: Authentication Required
7
+ lead: The action you are trying to perform requires YubiKey authentication.
8
+ instructions: >
9
+ Press your YubiKey’s gold contact to insert a one-time password into the field below
10
+ and then click "Submit" to continue.
11
+ invalid_otp: Invalid one-time password
12
+ devise:
13
+ failure:
14
+ user:
15
+ second_factor_failure: There is a problem with your yubikey.
data/config/routes.rb ADDED
@@ -0,0 +1,24 @@
1
+ NdrAuthenticate::Engine.routes.draw do
2
+ get 'check_active', to: 'authentication#check_active'
3
+ post 'check_active', to: 'authentication#check_active'
4
+
5
+ if NdrAuthenticate.sso_enabled_ever?
6
+ devise_for :users, class_name: NdrAuthenticate.user_class,
7
+ module: :devise,
8
+ skip: %i[sessions saml_authenticatable]
9
+
10
+ devise_scope :user do
11
+ get :sign_in, to: 'sessions#new', as: :new_user_session
12
+ post :sign_in, to: 'sessions#create', as: :user_session
13
+ delete :sign_out, to: 'sessions#destroy', as: :destroy_user_session
14
+
15
+ scope :saml, controller: :saml_sessions do
16
+ get :metadata, action: :metadata, as: :saml_metadata
17
+ get :sign_in, action: :new, as: :new_saml_user_session
18
+ post :auth, action: :create, as: :saml_user_session
19
+ delete :sign_out, action: :destroy, as: :destroy_saml_user_session
20
+ match '/idp_sign_out', action: :idp_sign_out, as: :idp_destroy_saml_user_session, via: %i[get post delete]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ Description:
2
+ Creates boilerplate configuration for NdrAuthenticate.
3
+
4
+ Example:
5
+ rails generate ndr_authenticate:install
6
+
7
+ This will create:
8
+ config/initializers/ndr_authenticate.rb
9
+ config/initializers/saml_authenticatable.rb
10
+ config/attribute-map.yml