From 7b5961607cdd091d2ebcbd7a0f0fec7d3cc6fa2c Mon Sep 17 00:00:00 2001 From: Ryan Orlando Date: Tue, 6 Aug 2024 15:42:57 -0400 Subject: [PATCH] Add shibboleth single sign on --- Gemfile | 2 + Gemfile.lock | 8 ++++ app/controllers/omniauth_controller.rb | 12 +++++ .../omniauthcallbacks_controller.rb | 45 +++++++++++++++++++ app/models/user.rb | 22 ++++++++- config/initializers/devise.rb | 32 ++++++++----- config/routes.rb | 12 ++++- .../20240806194048_add_column_to_users.rb | 5 +++ 8 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 app/controllers/omniauth_controller.rb create mode 100644 app/controllers/omniauthcallbacks_controller.rb create mode 100644 db/migrate/20240806194048_add_column_to_users.rb diff --git a/Gemfile b/Gemfile index cfadb94..b34e008 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,8 @@ gem 'jbuilder', '~> 2.5' # gem 'redis', '~> 4.0' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' +gem 'omniauth', '1.9.1' +gem 'omniauth-shibboleth' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development diff --git a/Gemfile.lock b/Gemfile.lock index 9f6377c..3893ce1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -335,6 +335,7 @@ GEM tilt hamster (3.0.0) concurrent-ruby (~> 1.0) + hashie (5.0.0) hiredis (0.6.3) htmlentities (4.3.4) http_logger (0.6.0) @@ -592,6 +593,11 @@ GEM multi_xml (~> 0.5) rack (>= 1.2, < 3) okcomputer (1.18.4) + omniauth (1.9.1) + hashie (>= 3.4.6) + rack (>= 1.6.2, < 3) + omniauth-shibboleth (1.3.0) + omniauth (>= 1.0.0) open4 (1.3.4) openseadragon (0.6.0) rails (> 3.2.0) @@ -911,6 +917,8 @@ DEPENDENCIES mimemagic (= 0.3.10) mysql2 okcomputer + omniauth (= 1.9.1) + omniauth-shibboleth pretender puma (~> 4.3) rails (~> 5.2) diff --git a/app/controllers/omniauth_controller.rb b/app/controllers/omniauth_controller.rb new file mode 100644 index 0000000..bdc0056 --- /dev/null +++ b/app/controllers/omniauth_controller.rb @@ -0,0 +1,12 @@ + +# frozen_string_literal: true +class OmniauthController < Devise::SessionsController + def new + Rails.logger.debug "SessionsController#new: request.referer = #{request.referer}" + if Rails.env.production? || Rails.env.stage? + redirect_to user_shibboleth_omniauth_authorize_path + else + super + end + end +end \ No newline at end of file diff --git a/app/controllers/omniauthcallbacks_controller.rb b/app/controllers/omniauthcallbacks_controller.rb new file mode 100644 index 0000000..35b6837 --- /dev/null +++ b/app/controllers/omniauthcallbacks_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true +class OmniauthcallbacksController < Devise::OmniauthCallbacksController + # handle omniauth logins from shibboleth + def shibboleth + # auth_headers = %w(HTTP_AFFILIATION HTTP_AUTH_TYPE HTTP_COOKIE HTTP_HOST + # HTTP_PERSISTENT_ID HTTP_EPPN HTTP_REMOTE_USER HTTP_SHIB_APPLICATION_ID + # HTTP_SHIB_AUTHENTICATION_INSTANT HTTP_SHIB_AUTHENTICATION_METHOD + # HTTP_SHIB_AUTHNCONTEXT_CLASS HTTP_SHIB_HANDLER HTTP_SHIB_IDENTITY_PROVIDER + # HTTP_SHIB_SESSION_ID HTTP_SHIB_SESSION_INDEX HTTP_UNSCOPED_AFFILIATION) + # + auth_headers = { + uid: 'uid', + shib_session_id: 'Shib-Session-ID', + shib_application_id: 'Shib-Application-ID', + provider: 'Shib-Identity-Provider', + name: 'displayName', + mail: 'mail' + } + auth = {} + # Rails.logger.warn "request = #{request.env.inspect}" + auth_headers.each do |k, v| + auth[k] = request.env[v] + end + # Rails.logger.warn "request2 = #{auth.inspect}" + # if auth.fetch('unscoped_affiliation', nil) + # auth['affiliation'] = auth['unscoped_affiliation'].split(';').map(&:strip) + # end + auth.delete_if { |_k, v| v.blank? } + @user = User.from_omniauth(auth) + # capture data about the user from shib + # session['shib_user_data'] = auth + # sign_in_and_redirect @user, event: :authentication + set_flash_message :notice, :success, kind: "Shibboleth" + # logger.warn "auth_type :: #{current_user.inspect}" + # logger.warn "#{request.env["omniauth.auth"]}" + sign_in_and_redirect @user + end + + ## when shib login fails + def failure + ## redirect them to the devise local login page + # redirect_to new_local_user_session_path, :notice => "Shibboleth isn't available - local login only" + redirect_to root_path, notice: "Shibboleth isn't available - local login only" + end +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index e8be92c..a2fd2b2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,7 +19,13 @@ class User < ApplicationRecord include Blacklight::User # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable - devise :ldap_authenticatable, :rememberable, :validatable + if Rails.env.development? || Rails.env.test? + devise :ldap_authenticatable, :rememberable, :validatable + else + devise_modules = [:omniauthable, :rememberable, :trackable, omniauth_providers: [:shibboleth]] + ##devise_modules.prepend(:database_authenticatable) if AuthConfig.use_database_auth? + devise(*devise_modules) + end # Method added by Blacklight; Blacklight uses #to_s on your # user class to get a user-displayable login/identifier for @@ -51,6 +57,20 @@ def ldap_before_save self.email = Devise::LDAP::Adapter.get_ldap_param(username, "mail").first self.display_name = Devise::LDAP::Adapter.get_ldap_param(username, "tuftsEduDisplayNameLF").first end + + # 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) + Rails.logger.warn "auth = #{auth.inspect}" + # Uncomment the debugger above to capture what a shib auth object looks like for testing + user = where(username: auth[:uid]).first_or_create + user.display_name = auth[:name] + user.username = auth[:uid] + user.email = auth[:mail] + user.save + user + end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index a1dade9..86aa5ed 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,19 +1,22 @@ # frozen_string_literal: true +require "omniauth-shibboleth" # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. Devise.setup do |config| # ==> LDAP Configuration - config.ldap_logger = true - config.ldap_create_user = true - # config.ldap_update_password = true - config.ldap_config = Rails.root.join('config', 'ldap.yml') - # config.ldap_check_group_membership = false - # config.ldap_check_group_membership_without_admin = false - # config.ldap_check_attributes = false - # config.ldap_check_attributes_presence = false - config.ldap_use_admin_to_bind = false - # config.ldap_ad_group_check = false + if Rails.env.development? || Rails.env.test? + config.ldap_logger = true + config.ldap_create_user = true + # config.ldap_update_password = true + config.ldap_config = Rails.root.join('config', 'ldap.yml') + # config.ldap_check_group_membership = false + # config.ldap_check_group_membership_without_admin = false + # config.ldap_check_attributes = false + # config.ldap_check_attributes_presence = false + config.ldap_use_admin_to_bind = false + # config.ldap_ad_group_check = false + end # The secret key used by Devise. Devise uses this key to generate # random tokens. Changing this key will render invalid all existing @@ -312,4 +315,13 @@ # When set to false, does not sign a user in automatically after their password is # changed. Defaults to true, so a user is signed in automatically after changing a password. # config.sign_in_after_change_password = true + + if Rails.env.production? || Rails.env.stage? + config.omniauth :shibboleth, { + uid_field: 'uid', + info_fields: { display_name: 'displayName', uid: 'uid', mail: 'mail' }, + callback_url: '/users/auth/shibboleth/callback', + strategy_class: OmniAuth::Strategies::Shibboleth + } + end end diff --git a/config/routes.rb b/config/routes.rb index 5bfee6e..25d1c47 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,7 +11,17 @@ concerns :range_searchable end - devise_for :users + if Rails.env.production? || Rails.env.stage? + devise_for :users, controllers: { omniauth_callbacks: "omniauthcallbacks" }, skip: [:sessions] + devise_scope :user do + post 'sign_in', to: 'omniauth#new', as: :new_user_session + post 'sign_in', to: 'omniauth_callbacks#shibboleth', as: :new_session + get 'sign_out', to: 'devise/sessions#destroy', as: :destroy_user_session + end + else + devise_for :users + end + mount Hydra::RoleManagement::Engine => '/' mount Qa::Engine => '/authorities' diff --git a/db/migrate/20240806194048_add_column_to_users.rb b/db/migrate/20240806194048_add_column_to_users.rb new file mode 100644 index 0000000..6ac482f --- /dev/null +++ b/db/migrate/20240806194048_add_column_to_users.rb @@ -0,0 +1,5 @@ +class AddColumnToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :provider, :string + end +end