Adding Omniauth Saml To Rdms

Adding Omniauth-SAML to ReSeeD

Reference: https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview https://github.com/omniauth/omniauth-saml#devise-integration

  1. Add omniauth-saml gem to ReSeeD and run bundle install

    # Gemfile
    gem 'omniauth-saml', '~> 2.1'
    gem "omniauth-rails_csrf_protection"
    
    $ bundle install
    
  2. Add rails migration to add columns provider and uid to the database

    $ rails g migration AddOmniauthToUsers provider:string uid:string
    $ rake db:migrate
    
  3. Declare the provider in the devise config file

    # config/initializers/devise.rb  
    
    config.omniauth :saml,
        idp_sso_service_url: ENV['SAML_IDP_SERVICE_URL'],
        sp_entity_id: ENV['SAML_SP_ID'],
        SAML_SERVICE_URL=http://rdms.cottagelabs.com/users/auth/saml/callback
        idp_cert_fingerprint: ENV['SAML_IDP_CERT_FINGERPRINT'],
    

    Other config options may include the following

    config.omniauth :saml,
        idp_sso_service_url: ENV['SAML_IDP_SERVICE_URL'],
        sp_entity_id: ENV['SAML_SP_ID']
        idp_cert_fingerprint: ENV['SAML_IDP_CERT_FINGERPRINT'],
        # idp_cert: "",
        # idp_cert_multi: {
        #   signing: ["ADD signing cert 1 here",
        #             "ADD signing cert 1 here"],
        #   encryption: ["Add encryption cert 1 here"]
        # },
        assertion_consumer_service_url: ENV['SAML_SERVICE_URL'],
        name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
        single_value_compatibility: false,
        attribute_statements: {
          email: ['urn:oid:1.3.6.1.4.1.5923.1.1.1.6'],
          name: ['urn:oid:2.16.840.1.113730.3.1.241'],
        }
    

    sp_entity_id: <Name of application|RDMS at RUB>

    idp_sso_service_url: <The URL to which the authentication request should be sent|https://samltest.id/saml/idp>.

    idp_cert_fingerprint:

    assertion_consumer_service_url: <http://example.com/auth/saml/callback | http://example.com/users/auth/saml/callback>

    Note: We need one of idp_cert_fingerprint or idp_cert or idp_cert_multi

  4. Make user model omniauthable

    Add :omniauthable, :omniauth_providers => [:saml] to devise.

    # hyrax/app/models/user.rb
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :validatable,
             :omniauthable, :omniauth_providers => [:saml]
    

    Add method to user model to find or create saml user

      # allow omniauth (including shibboleth) logins
      #   this will create a local user based on an omniauth/shib login
      #   if they haven't logged in before
      def self.from_omniauth(auth)
        where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
          # Uncomment the debugger below to capture what a auth object looks like for testing
          # Rails.logger.debug "auth = #{auth.inspect}"
          # Rails.logger.debug "auth = #{auth}"
          user.uid = auth.uid
          user.email = auth.info.email
          user.password = Devise.friendly_token[0, 20]
          user.name = auth.info.name   # assuming the user model has a name
          # If you are using confirmable and the provider(s) you use validate emails,
          # uncomment the line below to skip the confirmation emails.
          # user.skip_confirmation!
        end
      end
    

    Note: Depending on the information obtained from Shibboleth, we would likely need to update the self.from_omniauth method.

  5. Add custom omniauth controller to devise routes

    # hyrax/config/routes.rb
    devise_for :users, controllers: { omniauth_callbacks: 'callbacks' }
    
  6. Implement the callback controller

    Add a controller with the method saml at hyrax/app/controllers/callbacks_controller.rb

    # frozen_string_literal: true
    
    class CallbacksController < Devise::OmniauthCallbacksController
      # skip_before_action :verify_authenticity_token
    
      def saml
        if current_user
          redirect_to Hyrax::Engine.routes.url_helpers.dashboard_path
        else
          @user = User.from_omniauth(request.env["omniauth.auth"])
          sign_in_saml_user
        end
      end
    
      def failure
        set_flash_message! :alert, :failure, kind: OmniAuth::Utils.camelize(failed_strategy.name), reason: failure_message
        redirect_to after_omniauth_failure_path_for(resource_name)
      end
    
      private
    
      def sign_in_saml_user
        sign_in_and_redirect @user, event: :authentication # this will throw if @user is not activated
        cookies[:login_type] = "saml"
        flash[:notice] = "You are now signed in as #{@user.name} (#{@user.email})"
      end
    
    end
    

The Service provider metadata URL

This should be found in /users/auth/saml/metadata

Signing and decrypting messages

The idp certificate is straightforward. It can be configured in the config/initializers/devise.rb.

As for the private key, is at as described here, under the part Generate a secure private key?

Is the IDP going to use the SPs public certificate to encrypt message and the SP, then uses the private key, to decrypt the message from the IDP?

If so, from the onelogin ruby-saml wiki (which omniauth-saml uses) we just need to add these two to the settings

  settings.certificate = "SP public certificate"
  settings.private_key = "SP private certificate key"

The configuration in devise, would then look like this, and fingers crossed this does the trick.

config.omniauth :saml,
                  idp_sso_service_url: ENV['SAML_IDP_SERVICE_URL'],
                  sp_entity_id: ENV['SAML_SP_ID'],
                  idp_cert_fingerprint: ENV['SAML_IDP_CERT_FINGERPRINT'],
                  # idp_cert: "",
                  # idp_cert_multi: {
                  #   signing: ["ADD signing cert 1 here",
                  #             "ADD signing cert 1 here"],
                  #   encryption: ["Add encryption cert 1 here"]
                  # },
                  name_identifier_format: ENV['SAML_NAME_ID_FORMAT'],
                  assertion_consumer_service_url: ENV['SAML_SERVICE_URL'],
                  certificate: "SP public certificate",
                  private_key: "SP private certificate key"

Attempts to solve authenticity error