diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..dc859079 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +ruby 2.7.8 +nodejs 18.10.0 diff --git a/Gemfile b/Gemfile index f3cbd8e9..69ed6407 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,8 @@ gem 'jbuilder' gem 'jquery-rails' gem 'mimemagic', github: 'mimemagicrb/mimemagic', ref: '01f92d86d15d85cfd0f20dabd025dcbd36a8a60f' gem 'mysql2' -gem 'omniauth-nyulibraries', git: 'https://github.com/NYULibraries/omniauth-nyulibraries', branch: 'v2.2.0' +gem 'omniauth' +gem 'omniauth-oauth2' gem 'rails' gem 'rainbow' gem 'rsolr' diff --git a/Gemfile.lock b/Gemfile.lock index 35d96178..026c5b53 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,3 @@ -GIT - remote: https://github.com/NYULibraries/omniauth-nyulibraries - revision: 2a77afefc346669e5a50ff5c84e48a012490dfbc - branch: v2.2.0 - specs: - omniauth-nyulibraries (2.2.0) - omniauth-oauth2 (~> 1.2.0) - GIT remote: https://github.com/mimemagicrb/mimemagic.git revision: 01f92d86d15d85cfd0f20dabd025dcbd36a8a60f @@ -434,7 +426,8 @@ DEPENDENCIES jquery-rails mimemagic! mysql2 - omniauth-nyulibraries! + omniauth + omniauth-oauth2 puma rails rainbow diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index cc6cf74e..0f67efa9 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,4 @@ //= link_tree ../images //= link_tree ../javascripts +//= link_tree ../stylesheets //= link_directory ../stylesheets .css diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f39937f4..77d7fb38 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -35,7 +35,7 @@ def after_sign_out_path_for(resource_or_scope) end def logout_path - 'https://login.library.nyu.edu/logout' + Settings.LOGOUT_URL || 'https://qa.auth.it.nyu.edu/oidc/logout' end private :logout_path end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb new file mode 100644 index 00000000..3d99ef2d --- /dev/null +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +class OmniauthCallbacksController < Devise::OmniauthCallbacksController + before_action :require_valid_omniauth, only: :shibboleth + + def shibboleth + Rails.logger.info("OmniauthCallbacksController#shibboleth: #{omniauth.inspect}") + set_user + if @user.persisted? + log_in + else + redirect_to_login + end + end + + def failure + redirect_to root_path + end + + private + + def redirect_to_login + Rails.logger.error(find_message(:failure, kind: 'NYU Shibboleth', reason: 'User not persisted')) + session['devise.shibboleth_data'] = request.env['omniauth.auth'] + redirect_to root_path + end + + def log_in + sign_in_and_redirect @user, event: :authentication + set_flash_message(:notice, :success, kind: 'NYU Shibboleth') + logger.info(find_message(:success, kind: 'NYU Shibboleth')) + end + + def require_valid_omniauth + head :bad_request unless valid_omniauth? + end + + def valid_omniauth? + omniauth.present? + end + + def omniauth + @omniauth ||= request.env['omniauth.auth'] + end + + def omniauth_provider + @omniauth_provider ||= omniauth.provider + end + + def attributes_from_omniauth + { + provider: omniauth.provider, + username: omniauth.uid, + email: omniauth_email, + firstname: omniauth_firstname, + lastname: omniauth_lastname + } + end + + def omniauth_email + @omniauth_email ||= omniauth.info.email + end + + def omniauth_firstname + @omniauth_firstname ||= omniauth.info.first_name + end + + def omniauth_lastname + @omniauth_lastname ||= omniauth.info.last_name + end + + def set_user + # Find existing or initialize new user, + # and save new attributes each time + @user = find_user + @user.update_attributes(attributes_from_omniauth) + end + + def find_user + @find_user ||= User.create_from_provider_data(request.env['omniauth.auth']) + end +end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb deleted file mode 100644 index 467beab6..00000000 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ /dev/null @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -module Users - class OmniauthCallbacksController < Devise::OmniauthCallbacksController - before_action :require_valid_omniauth, only: :nyulibraries - - def nyulibraries - set_user - if @user.persisted? - sign_in_and_redirect @user, event: :authentication - logger.info(find_message(:success, kind: 'NYU Libraries')) - else - session['devise.nyulibraries_data'] = request.env['omniauth.auth'] - redirect_to root_path - end - end - - def find_user - @find_user ||= find_user_with_provider.present? ? find_user_with_provider : find_user_without_provider - end - - def find_user_with_provider - @find_user_with_provider ||= User.where(username: omniauth.uid, provider: omniauth.provider) - end - - def find_user_without_provider - @find_user_without_provider ||= User.where(username: omniauth.uid, provider: '') - end - - def require_valid_omniauth - head :bad_request unless valid_omniauth? - end - - def valid_omniauth? - omniauth.present? && omniauth.provider.to_s == 'nyulibraries' && !omniauth_aleph_identity.blank? - # Only accept users with an Aleph ID, authenticated via nyulibraries - end - - def omniauth - @omniauth ||= request.env['omniauth.auth'] - end - - def omniauth_provider - @omniauth_provider ||= omniauth.provider - end - - def attributes_from_omniauth - { - provider: omniauth_provider, - email: omniauth_email, - firstname: omniauth_firstname, - lastname: omniauth_lastname, - institution_code: omniauth_institution, - aleph_id: omniauth_aleph_id, - patron_status: omniauth_patron_status - } - end - - def omniauth_email - @omniauth_email ||= omniauth.info.email - end - - def omniauth_firstname - @omniauth_firstname ||= omniauth.info.first_name - end - - def omniauth_lastname - @omniauth_lastname ||= omniauth.info.last_name - end - - def omniauth_institution - @omniauth_institution ||= omniauth.extra.institution_code - end - - def omniauth_identities - # byebug - @omniauth_identities ||= omniauth.extra.identities - end - - def omniauth_aleph_identity - @omniauth_aleph_identity ||= omniauth_identities.find do |omniauth_identity| - omniauth_identity.provider == 'aleph' - end - end - - def omniauth_aleph_id - @omniauth_aleph_id ||= omniauth_aleph_identity.uid unless omniauth_aleph_identity.blank? - end - - def omniauth_patron_status - @omniauth_patron_status ||= omniauth_aleph_identity.properties.patron_status unless omniauth_aleph_identity.blank? - end - - def failure - redirect_to root_path - end - - private - - def set_user - # Find existing or initialize new user, - # and save new attributes each time - @user = find_user.first_or_initialize(attributes_from_omniauth) - @user.update_attributes(attributes_from_omniauth) - end - end -end diff --git a/app/models/user.rb b/app/models/user.rb index ae162b1b..96f639ae 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -7,7 +7,7 @@ class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable - devise :omniauthable, omniauth_providers: [:nyulibraries] + devise :omniauthable, omniauth_providers: [:shibboleth] # Method added by Blacklight; Blacklight uses #to_s on your # user class to get a user-displayable login/identifier for @@ -15,4 +15,13 @@ class User < ActiveRecord::Base def to_s email end + + def self.create_from_provider_data(provider_data) + where(provider: provider_data.provider, + username: provider_data.uid) + .first_or_create do |user| + user.email = provider_data.info.email + user.username = provider_data.uid + end + end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 83212a83..fb9c2474 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -10,8 +10,9 @@ config.password_length = 8..128 config.reset_password_within = 60.minutes config.sign_out_via = :get - config.omniauth :nyulibraries, Settings.APP_ID, Settings.APP_SECRET, client_options: { - site: Settings.LOGIN_URL, - authorize_path: '/oauth/authorize' + config.omniauth :shibboleth, Settings.APP_ID, Settings.APP_SECRET, client_options: { + site: (Settings.LOGIN_URL || "https://qa.auth.it.nyu.edu"), + authorize_url: "/oauth2/authorize", + token_url: "/oauth2/token", } end diff --git a/config/initializers/omniauth/strategies/shibboleth.rb b/config/initializers/omniauth/strategies/shibboleth.rb new file mode 100644 index 00000000..9eb6136c --- /dev/null +++ b/config/initializers/omniauth/strategies/shibboleth.rb @@ -0,0 +1,46 @@ +module OmniAuth + module Strategies + require 'omniauth-oauth2' + class Shibboleth < OmniAuth::Strategies::OAuth2 + if defined?(::Rails) && ::Rails.env.development? + silence_warnings do + OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE + end + end + option :name, :shibboleth + option :authorize_params, { scope: "openid" } + + uid do + raw_info["sub"] + end + + info do + { + email: raw_info["sub"], + last_name: last_name, + first_name: first_name + } + end + + def raw_info + response = access_token.get("/oauth2/userinfo?schema=openid") + Rails.logger.info("Shibboleth raw_info: #{response.parsed}") + @raw_info ||= response.parsed + end + + # Extract Last Name from identity + def last_name + @last_name ||= raw_info["lastname"] rescue nil + end + + # Extract First Name from identity + def first_name + @first_name ||= raw_info["firstname"] rescue nil + end + + def log(level, message) + Rails.logger.send(level, "(#{name}) #{message}") + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 49ea1330..986f0063 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,8 @@ Rails.application.routes.draw do - devise_for :users, controllers: {omniauth_callbacks: 'users/omniauth_callbacks'} + devise_for :users, path: '', controllers: {omniauth_callbacks: 'omniauth_callbacks'} devise_scope :user do get 'logout', to: 'devise/sessions#destroy', as: :logout - get 'login', to: redirect { |params, request| "#{Rails.application.config.relative_url_root}/users/auth/nyulibraries?#{request.query_string}" }, as: :login + get 'login', to: redirect { |params, request| "#{Rails.application.config.relative_url_root}/auth/shibboleth?#{request.query_string}" }, as: :login resources :suggest, only: :index, defaults: {format: 'json'} @@ -51,4 +51,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/routing/user_routing_spec.rb b/spec/routing/user_routing_spec.rb index 884a82fc..b7e5eea4 100644 --- a/spec/routing/user_routing_spec.rb +++ b/spec/routing/user_routing_spec.rb @@ -1,33 +1,33 @@ # frozen_string_literal: true describe 'routes for users' do - describe 'GET /users/auth/nyulibraries' do - subject { get('/users/auth/nyulibraries') } + describe 'GET /auth/shibboleth' do + subject { get('/auth/shibboleth') } it do should route_to({ - controller: 'users/omniauth_callbacks', + controller: 'omniauth_callbacks', action: 'passthru' }) end end - describe 'POST /users/auth/nyulibraries' do - subject { post('/users/auth/nyulibraries') } + describe 'POST /auth/shibboleth' do + subject { post('/auth/shibboleth') } it do should route_to({ - controller: 'users/omniauth_callbacks', + controller: 'omniauth_callbacks', action: 'passthru' }) end end - describe 'GET /users/auth/nyulibraries/callback' do - subject { get('/users/auth/nyulibraries/callback') } - it { should route_to({ controller: 'users/omniauth_callbacks', action: 'nyulibraries' }) } + describe 'GET /auth/shibboleth/callback' do + subject { get('/auth/shibboleth/callback') } + it { should route_to({ controller: 'omniauth_callbacks', action: 'shibboleth' }) } end - describe 'POST /users/auth/nyulibraries/callback' do - subject { post('/users/auth/nyulibraries/callback') } - it { should route_to({ controller: 'users/omniauth_callbacks', action: 'nyulibraries' }) } + describe 'POST /auth/shibboleth/callback' do + subject { post('/auth/shibboleth/callback') } + it { should route_to({ controller: 'omniauth_callbacks', action: 'shibboleth' }) } end end