Forum: Ruby on Rails Shibboleth

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Rob L. (Guest)
on 2008-10-13 23:54
(Received via mailing list)
Can anyone direct me to a really good tutorial on Shibboleth integration
with Rails, or indeed some sample code? I've been tearing my hair out
all day on this one.

Thanks

RobL
Alex B. (Guest)
on 2008-10-14 09:11
(Received via mailing list)
Rob,

Turns out Shib is really easy to get working once you have shibd up
and kicking.

First, take a look at the restful_authentication plugin to see how
they handle authentication: Relevantly, there's a @current_user
instance variable with a setter that looks like this:

def current_user
  @current_user ||= user_from_database || user_from_session ||
user_from_login
end

As a before_filter, the system makes use of #logged_in? To determine
if a current user could be found. That code is a simple !!
current_user;

Now, shibboleth adds fields to the request headers of all incoming
traffic. Instead of doing the logged_in filter, we just set something
up to create or find a database record that corresponds to the user.
This looks like this (For the shib variable 'eppn'), assuming that
your User table has a field named eppn.

def current_user
  @current_user ||= User.find_or_create_by_eppn(request.env['eppn'])
end

That's it! Of course, you're going to need a way to ensure that
database record is meaningful in the context of your application - In
our situation, we test for the presence of contact information and
then prompt the user to add it if none exists, but it doesn't really
matter because the data in our IP to DNA lookup table is sorted by
their eppn anyway.

E-mail back if my sample code doesn't work!

-Alex
Rob L. (Guest)
on 2008-10-14 12:18
(Received via mailing list)
Thanks for the response Alex, to be honest I'm getting in a muddle
trying to figure out how this works / fits together, but I'm picking up
bits and pieces here and there. I'll take a look at RESTful
authentication and have a play.

Can I check I understand this right? From what I understand the user
tries to access and Shibboleth restricted resource, Apache redirects the
user away to the Identity Provider to authenticate themselves and then
when they are redirected back Apache adds this additional header to each
subsequent request to Mongrel? Or is it a header that is added by the
client and sent through with each subsequent request until they are
logged out?

I guess your application deals with the user once they are already
authenticated and that work is done outside of they rails app. Do you
have any sample configuration for the Shibboleth setup?  I've seen
something called saml2ruby which has been used to interface with the
service provider directly, and I guess setting those headers in another
way its not greatly documented and I guess not required if Apache can do
everything for you.

Is this just for Apache, I think this client uses Nginx and that might
be a problem?

Thanks again

RobL
Joe U. (Guest)
on 2010-05-20 00:26
Hi,

I also need to validate users via Shibboleth on a Ruby application using
Nginx and Mongrel.

I've searched but not found instructions on how to do this.

Would someone please let me know if they have a resource for
implementation of Shibboleth on a Ruby / Nginx / Mongrel setup?

Thank you!

Joe
Marnen L. (Guest)
on 2010-05-20 03:18
Rob L. wrote:
> Thanks for the response Alex, to be honest I'm getting in a muddle
> trying to figure out how this works / fits together, but I'm picking up
> bits and pieces here and there. I'll take a look at RESTful
> authentication and have a play.

Some concepts may be useful, but please don't ever use that piece of
garbage in your application (it relies too much on generated code).  You
may find it useful to compare how Authlogic does things.

Best,
--
Marnen Laibow-Koser
http://www.marnen.org
removed_email_address@domain.invalid
Joe U. (Guest)
on 2010-05-24 10:09
I still am unsure about Shibboleth with Nginx - if anyone has experience
or suggestions here, please let me know. Thank you!

Joe
Joe U. (Guest)
on 2011-03-05 00:37
Hi,

Just pinging the list (again, but a year later!) to see whether anyone
has advice on validating users via Shibboleth on a Ruby application
using Nginx and Mongrel.

I've searched but not found instructions on how to do this.

Would someone please let me know if they have a resource for
implementation of Shibboleth on a Ruby / Nginx / Mongrel setup?

Thank you!

Joe
Bryan C. (Guest)
on 2011-03-05 01:01
(Received via mailing list)
On Fri, Mar 4, 2011 at 4:37 PM, Joe U. <removed_email_address@domain.invalid> 
wrote:

>
> Thank you!
>
> Joe
>

Alex Bartlow's post in this thread same thread three years ago gives you
the
answer.

http://www.ruby-forum.com/topic/168155

Searching "Shibboleth Rails" in Google produced more information where
other
people have done this using Passenger.

B.
Michael M. (Guest)
on 2011-09-26 06:43
I think at this point, the Shibboleth Service Provider itself only
supports Apache and IIS.  The integration is straightforward with those
servers.  For rails specific authorization, plugging Shibboleth and
Passenger into Apache makes authentication pretty straightforward.
Toyokazu A. (Guest)
on 2012-01-07 03:36
If some of you are interested in using Shibboleth with Ruby and without
Apache or IIS, please try rack-saml.

https://github.com/toyokazu/rack-saml

While it supports only a part of Shibboleth SP functions, you can use it
without Apache or IIS frontend.

It was written to be used with omniauth-shibboleth.

https://github.com/toyokazu/omniauth-shibboleth

FYI

Best Regards,
--
Toyokazu AKIYAMA
alex (Guest)
on 2012-01-08 11:40
(Received via mailing list)
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.
This topic is locked and can not be replied to.