# ShibRack [![Gem Version][GV img]][Gem Version] [Gem Version]: https://rubygems.org/gems/shib-rack [GV img]: https://img.shields.io/gem/v/shib-rack.svg [Shibboleth SP](http://shibboleth.net) authentication plugin for Rack-based web applications. Contains Rails-specific extensions for consumption by Rails applications. ## Installation Add the `shib-rack` dependency to your application's `Gemfile`: ``` gem 'shib-rack' ``` Use Bundler to install the dependency: ``` bundle install ``` Then create a Receiver class which will receive the session attributes. In development mode, `shib-rack` uses a development handler to provide example identities that you can use. Optionally, you can also setup an error handler class. You will need to customise this to suit your usage. Below is an example using only a limited number of attributes. ```ruby # frozen_string_literal: true # Extends the Authentication Module from Shib-Rack module Authentication # Custom SubjectReceiver Class class SubjectReceiver include ShibRack::DefaultReceiver include ShibRack::AttributeMapping map_single_value shared_token: 'HTTP_AUEDUPERSONSHAREDTOKEN', targeted_id: 'HTTP_TARGETED_ID', principal_name: 'HTTP_PRINCIPALNAME' def subject(_env, attrs) Subject.transaction do identifier = attrs.slice(:targeted_id) subject = Subject.find_or_initialize_by(identifier) subject.update!(attrs) subject end end def finish(_env) redirect_to('/dashboard') end end end ``` ### Multiple attributes Some attributes are multi-valued, such as `affiliation` and `scoped_affiliation`. You can use the `map_multi_value` method for these attributes, but you will need to consider how to store them in your app. In the example below, a separate model has been used for `affiliations`. Multi-value attributes (as with other attributes) are appended to your HTTP headers after authentication occurs. Multi-value attributes are stored in your header as a single string with each value delimited by a `;`. This method splits the string into a ruby array based on this delimiting character. ```ruby # frozen_string_literal: true # Extends the Authentication Module from Shib-Rack module Authentication # Custom SubjectReceiver Class class SubjectReceiver include ShibRack::DefaultReceiver include ShibRack::AttributeMapping map_single_value shared_token: 'HTTP_AUEDUPERSONSHAREDTOKEN', targeted_id: 'HTTP_TARGETED_ID', principal_name: 'HTTP_PRINCIPALNAME' map_multi_value affiliation: 'HTTP_EDUPERSONAFFILIATION' def subject(_env, attrs) Subject.transaction do identifier = attrs.slice(:targeted_id) subject = Subject.find_or_initialize_by(identifier) subject.update!(attrs.except(:affiliation)) update_affiliations(subject, attrs) subject end end def finish(_env) redirect_to('/dashboard') end def update_affiliations(subject, attrs) touched = [] attrs[:affiliation].each do |value| touched << subject.affiliations.find_or_create_by!(value: value) end subject.affiliations.where.not(id: touched.map(&:id)).destroy_all end end end ``` ### Integrating with a Rails application Map the `ShibRack::Engine` app to a path in your application. It is strongly recommended that you use the default path of `/auth`. In `config/routes.rb` ```ruby Rails.application.routes.draw do mount ShibRack::Engine => '/auth' end ``` Configure the receiver, and optional error handler in `config/application.rb` ```ruby module MyApp class Application < Rails::Application # ... config.autoload_paths << Rails.root.join('lib') config.shib_rack.receiver = 'Authentication::SubjectReceiver' config.shib_rack.error_handler = 'Authentication::ErrorHandler' end end ``` Ensure the gem runs in development mode in `config/development.rb` ```ruby config.shib_rack.development_mode = true ``` And likewise ensure the gem runs in test mode in `config/test.rb` ```ruby config.shib_rack.test_mode = true ``` ### Using with Capybara-style tests Ensure that you have configured the gem to run in test mode in `config/test.rb` (See above) Once this has been setup, you can create a capybara-style test. See the example below which tests the login process using the test handler. This is an example only, and will vary based on your app implementation. ```ruby # frozen_string_literal: true require 'rails_helper' RSpec.feature 'Authentication Flow', type: :feature do let(:idp_domain) { Faker::Internet.domain_name } let(:idp) { "https://idp.#{idp_domain}/idp/shibboleth" } let(:sp) { "https://sp.#{Faker::Internet.domain_name}/shibboleth" } let(:name) { Faker::Name.name } let(:attrs) do { 'HTTP_AUEDUPERSONSHAREDTOKEN' => SecureRandom.urlsafe_base64(20), 'HTTP_TARGETED_ID' => "#{idp}!#{sp}!#{SecureRandom.uuid}", 'HTTP_PRINCIPALNAME' => "#{name}@#{idp_domain}", 'HTTP_EDUPERSONAFFILIATION' => 'staff;member' } end before do ShibRack::TestHandler.attributes = attrs visit '/auth/login' end it 'User is redirected to dashboard with details displayed on initial login' do expect(current_path).to eq('/dashboard') expect(page).to have_content("You're logged in. Here are your details") expect(page).to have_content(name) expect(page).to have_content(idp_domain) # Further checks for other attributes can be added here end end ``` ## Contributing Refer to [GitHub Flow](https://guides.github.com/introduction/flow/) for help contributing to this project.