The problem with rack-saml <https://github.com/toyokazu/rack-saml> and 
similar is that they don't support encrypted responses. I ran into this 
issue while trying to work with an IdP and encryption enabled (the 
encryption was a requirement).

My stack was nginx (1 server frontend) + passengers (multiple servers). I 
tried lots of solutions but I ended up with shibd and Apache web server. 

So, shibd + Apache and a little shib.php file that would grab whatever 
shibd environment there is after successful authentication, put it in a 
memcache and redirect the browser to my original rails app. 

Here's how my shib.php looks like:

<?php 
$REDIRECT_URL = 'https://rails.app.example.org/_shib';
$MEMCACHE_HOST = 'memcache.host';
$MEMCACHE_PORT = 11211;

# connect to memcache server
$memcache = new Memcache;
$memcache->connect($MEMCACHE_HOST, $MEMCACHE_PORT) or die ("Could not 
connect");

# create a temp saml session object from shibd environment variables
$saml = new stdClass;
$saml->provider = $_SERVER['Shib-Identity-Provider'];
$saml->common_name = $_SERVER['CommonName'];
$saml->given_name = $_SERVER['givenName'];
$saml->surname = $_SERVER['surname'];
$saml->edu_person_scoped_affiliation = 
$_SERVER['eduPersonScopedAffiliation']; 
$saml->uid = $_SERVER['uid'];
$saml->email = $_SERVER['mail'];
$saml->principal_name = $_SERVER['principalName'];


$shib_session_id = $_SERVER['Shib-Session-ID'];

# store this object in the memcache with 60 sec expiration
$cache_key = 'samlsess:' . $shib_session_id;
$memcache->set($cache_key, json_encode($saml), 0, 60) or die ("Failed to 
store data in memcache");

# send redirect to the rails app that will fetch the above object from the 
memcache
header('Location: ' . $REDIRECT_URL . '?s=' . $shib_session_id);

?>


So, the browser gets redirected to a URL handled by my rails app, a custom 
Devise strategy specifically:


# lib/devise/strategies/shibboleth_authenticatable.rb

require 'json'

module Devise  
  module Strategies
    class ShibAuthError < RuntimeError; end
    
    class ShibbolethAuthenticatable < Devise::Strategies::Base
      # check public/samlsession/index.php for full set of attributes
      SAML_ATTRIBUTES = %w(provider given_name surname uid email idada)
      
      # The request should go something like 
http://HOST/login?s=_23418cd2aadf
      # where s parameter is a key of the auth (usually memcached) data 
coming from shibd
      def valid?
        params[:s].present?
      end
      
      # Method that actually decides whether we'll let the user in
      def authenticate!
        # fetch raw data from the cache
        shib_session = Rails.cache.read(params[:s], :raw => true).to_s
        auth_hash = JSON.parse shib_session
        
        # sometimes IdP wasn't returning all the attributes
        # so just to make sure we have them all
        validate_saml_attributes! auth_hash
        
        # find existing user or create a new one since we always trust
        # our IdP
        resource = 
mapping.to.find_or_initialize_by_ada_id(auth_hash['idada'])
        if resource.new_record?
          # set SAML_ATTRIBUTES in the newly built user
          resource.update_from_saml auth_hash
          
          # store the new user or raise an exception
          raise(ShibAuthError, 'An error occured during new user creation') 
unless resource.save
        end
        
        # successfully authenticated
        success! resource
      rescue JSON::ParserError, ShibAuthError
        Rails.logger.error("[ShibbolethAuthenticatable] ERROR during _shib 
authentication: #{$!}")
        fail(:invalid) unless halted?
      end
      
      private

      # Checks the presence of all the attributes,
      # otherwise raises an exception
      def validate_saml_attributes!(auth_hash)
        raise(ShibAuthError, "No SAML attributes provided") unless auth_hash
        SAML_ATTRIBUTES.each { |a| 
          raise(ShibAuthError, "#{a} SAML attribute is missing") if 
auth_hash[a].blank?
        }
      end
    end
    
  end
end


I can wrap this in a sample rails app and opensource it if enough people 
are interested.

-- 
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/rubyonrails-talk/-/rU2y9_fq8ZEJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en.

Reply via email to