├── .gitignore ├── .rvmrc ├── Gemfile ├── README.md ├── Rakefile ├── app ├── controllers │ └── user_sessions_controller.rb └── views │ └── user_sessions │ └── new.html.erb ├── config └── routes.rb ├── lib ├── shibboleth-rails.rb └── shibboleth-rails │ ├── controller_additions.rb │ ├── engine.rb │ ├── user_model_additions.rb │ └── version.rb ├── shibboleth-rails.gemspec └── spec └── controllers └── user_sessions_controller_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | *.gem 3 | .bundle 4 | Gemfile.lock 5 | pkg/* 6 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use 1.9.3@shibboleth-rails --create 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in shibboleth-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shibboleth-rails 2 | 3 | [![Gem Version](https://badge.fury.io/rb/shibboleth-rails.png)](http://badge.fury.io/rb/shibboleth-rails) 4 | 5 | The [Shibboleth](http://shibboleth.internet2.edu/) System is a standards based, open source software package for web single sign-on across or within organizational boundaries. It allows sites to make informed authorization decisions for individual access of protected online resources in a privacy-preserving manner. 6 | 7 | This library determines `current_user` from environment variables set by `mod_shib` in Apache. An interface to login as any user is also provided for running in development. 8 | 9 | 10 | ### Related projects 11 | 12 | [rack-shibboleth](https://github.com/alexcrichton/rack-shibboleth/) - nascent Rack-based implementation that works with Nginx and other non-Apache servers 13 | 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /app/controllers/user_sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class UserSessionsController < ApplicationController 2 | 3 | # skip CanCan authorization 4 | skip_authorization_check if respond_to?(:skip_authorization_check) 5 | 6 | skip_before_filter :require_shibboleth 7 | before_filter :not_in_production 8 | 9 | def new 10 | @users = User.all 11 | end 12 | 13 | def create 14 | session[:simulate_id] = params[:user_id] 15 | target = session.delete :target 16 | redirect_to target || root_url 17 | end 18 | 19 | def destroy 20 | session[:simulate_id] = nil 21 | redirect_to new_user_session_url, :notice => "Logout successful!" 22 | end 23 | 24 | private 25 | def not_in_production 26 | redirect_to root_url if Rails.env.production? or Rails.env.staging? 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /app/views/user_sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_tag user_session_path do %> 2 | Login as: 3 | <%= select_tag :user_id, options_from_collection_for_select(@users, :id, :name_n), :class => 'chosen autosubmit' %> 4 | <%= submit_tag "Login", :class => 'btn' %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | unless Rails.env.production? or Rails.env.staging? 3 | resource :user_session, :only => [:new, :create, :destroy] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/shibboleth-rails.rb: -------------------------------------------------------------------------------- 1 | require "shibboleth-rails/version" 2 | require 'shibboleth-rails/engine' 3 | require 'shibboleth-rails/controller_additions' 4 | require 'shibboleth-rails/user_model_additions' 5 | -------------------------------------------------------------------------------- /lib/shibboleth-rails/controller_additions.rb: -------------------------------------------------------------------------------- 1 | module Shibboleth::Rails 2 | 3 | module ControllerAdditions 4 | private 5 | 6 | def authenticated? 7 | request.env['employeeNumber'].present? 8 | end 9 | 10 | def shibboleth 11 | { 12 | :emplid => request.env['employeeNumber'], 13 | :name_n => request.env['REMOTE_USER'].chomp("@osu.edu"), 14 | :affiliations => request.env['affiliation'], 15 | :first_name => request.env['FIRST-NAME'] || request.env['givenName'], 16 | :last_name => request.env['LAST-NAME'] || request.env['sn'], 17 | } 18 | end 19 | 20 | def current_user 21 | return @current_user if defined?(@current_user) 22 | @current_user = if session[:simulate_id].present? 23 | User.find(session[:simulate_id]) 24 | elsif authenticated? 25 | User.find_or_create_from_shibboleth(shibboleth) 26 | elsif request.xhr? 27 | User.find_by_id(session[:user_id]) 28 | end 29 | end 30 | 31 | def require_shibboleth 32 | if current_user 33 | current_user.update_usage_stats(request, :login => session['new']) 34 | session.delete('new') 35 | session[:user_id] = current_user.id 36 | else 37 | session['new'] = true 38 | session.delete(:simulate_id) 39 | if request.xhr? 40 | render :json => {:login_url => login_url}, :status => 401 41 | else 42 | redirect_to login_url 43 | end 44 | end 45 | end 46 | 47 | def requested_url 48 | if request.respond_to? :url 49 | request.url 50 | else 51 | request.protocol + request.host + request.request_uri 52 | end 53 | end 54 | 55 | def login_url 56 | if Rails.env.production? || Rails.env.staging? 57 | [request.protocol, request.host, '/Shibboleth.sso/Login?target=', CGI.escape(requested_url)].join 58 | else 59 | session['target'] = requested_url 60 | new_user_session_url 61 | end 62 | end 63 | 64 | end 65 | 66 | end 67 | 68 | ActionController::Base.class_eval do 69 | include Shibboleth::Rails::ControllerAdditions 70 | helper_method :current_user 71 | end 72 | -------------------------------------------------------------------------------- /lib/shibboleth-rails/engine.rb: -------------------------------------------------------------------------------- 1 | module Shibboleth::Rails 2 | 3 | class Engine < ::Rails::Engine 4 | end 5 | 6 | end 7 | -------------------------------------------------------------------------------- /lib/shibboleth-rails/user_model_additions.rb: -------------------------------------------------------------------------------- 1 | module Shibboleth::Rails 2 | 3 | module ModelAdditions 4 | def authenticated_by_shibboleth 5 | extend ClassMethods 6 | include InstanceMethods 7 | end 8 | 9 | module ClassMethods 10 | def find_or_create_from_shibboleth(identity) 11 | affiliations = identity.delete(:affiliations) 12 | first_name = identity.delete(:first_name) 13 | last_name = identity.delete(:last_name) 14 | 15 | user = find_or_create_by_emplid(identity) 16 | 17 | # names change due to marriage, etc. 18 | # update_attribute is a NOOP if not different 19 | user.update_attribute(:name_n, identity[:name_n]) 20 | user.update_attribute(:emplid, identity[:emplid]) 21 | user.update_attribute(:first_name, first_name) if user.class.columns.map(&:name).include?('first_name') && first_name.present? 22 | user.update_attribute(:last_name, last_name) if user.class.columns.map(&:name).include?('last_name') && last_name.present? 23 | 24 | user.update_role(affiliations) if user.respond_to?(:update_role) 25 | user 26 | end 27 | end 28 | 29 | module InstanceMethods 30 | def update_usage_stats(request, args = {}) 31 | if args[:login] 32 | if self.respond_to?(:login_count) 33 | self.login_count ||= 0 34 | self.login_count += 1 35 | end 36 | 37 | if self.respond_to?(:current_login_at) 38 | self.last_login_at = self.current_login_at if self.respond_to?(:last_login_at) 39 | self.current_login_at = Time.now 40 | end 41 | 42 | if self.respond_to?(:current_login_ip) 43 | self.last_login_ip = self.current_login_ip if self.respond_to?(:last_login_ip) 44 | self.current_login_ip = request.remote_ip 45 | end 46 | 47 | self.login_callback(request, args) if self.respond_to?(:login_callback) 48 | 49 | save(:validate => false) 50 | 51 | end 52 | 53 | self.request_callback(request, args) if self.respond_to?(:request_callback) 54 | end 55 | end 56 | 57 | end 58 | end 59 | 60 | ::ActiveRecord::Base.send :extend, Shibboleth::Rails::ModelAdditions 61 | -------------------------------------------------------------------------------- /lib/shibboleth-rails/version.rb: -------------------------------------------------------------------------------- 1 | module Shibboleth 2 | module Rails 3 | VERSION = "0.7.5" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /shibboleth-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "shibboleth-rails/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "shibboleth-rails" 7 | s.version = Shibboleth::Rails::VERSION 8 | s.authors = ["mikegee"] 9 | s.email = ["gee.24@osu.edu"] 10 | s.homepage = "https://github.com/ASCTech/shibboleth-rails" 11 | s.summary = %q{This Rails plugin integrates Shibboletth single signon.} 12 | s.description = %q{Environment variables that Shibboleth sets are used to determine 'current_user'. An interface to login as any user is also provided for running in development.} 13 | 14 | s.rubyforge_project = "shibboleth-rails" 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | 21 | s.add_runtime_dependency "rails", '~> 3.0' 22 | end 23 | -------------------------------------------------------------------------------- /spec/controllers/user_sessions_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../spec_helper', __FILE__) 2 | 3 | describe UserSessionsController do 4 | before { @user = Factory(:user) } 5 | 6 | describe 'loading the login page' do 7 | before { get :new } 8 | it { should respond_with(:success) } 9 | it { should assign_to(:user_session) } 10 | it { should assign_to(:users), :with => [@user] } 11 | end 12 | 13 | describe 'loggin in' do 14 | before { post :create, :user_id => @user.id } 15 | it 'should login the user' do 16 | UserSession.find.user.should == @user 17 | end 18 | it { should respond_with(:redirect), :to => root_url } 19 | it { should set_the_flash.to("You are now logged in as #{@user.name_n}.") } 20 | end 21 | 22 | describe 'logging out' do 23 | before do 24 | UserSession.create(@user) 25 | delete :destroy 26 | end 27 | it 'should log out the user' do 28 | UserSession.find.should be_nil 29 | end 30 | it { should respond_with(:redirect), :to => new_user_session_url } 31 | it { should set_the_flash.to('Logout successful!') } 32 | end 33 | 34 | end 35 | --------------------------------------------------------------------------------