Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial saml implementation #612

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
168e51b
initial saml implementation
alexBLR Nov 4, 2024
ff39ec5
updates functionality for saml
alexBLR Nov 7, 2024
eb0b063
fixes rubocop errors
alexBLR Nov 7, 2024
d3c9fcd
sets database_auth to true
alexBLR Nov 7, 2024
421200a
sets checks for environment
alexBLR Nov 10, 2024
a175d13
sets empty variables
alexBLR Nov 10, 2024
d09e8bb
fixing routes
alexBLR Nov 10, 2024
161aa16
removes devise_saml_authenticatable
alexBLR Nov 10, 2024
bc2881b
fixing circle ci
alexBLR Nov 11, 2024
af78904
fixes user registrations
alexBLR Nov 11, 2024
c619d05
fixing user factory
alexBLR Nov 13, 2024
bf50cbc
fixing rubocop
alexBLR Nov 13, 2024
a53d152
fixing rubocop
alexBLR Nov 13, 2024
a8a61bb
adds more tests
alexBLR Nov 13, 2024
2a6bfdc
adds more tests
alexBLR Nov 13, 2024
cef9084
fixes coverage
alexBLR Nov 18, 2024
92dfba7
fixes rubocop
alexBLR Nov 18, 2024
70ec304
adds all params to new user
alexBLR Nov 18, 2024
3794bf5
removes config
alexBLR Nov 18, 2024
af9ab71
Merge branch 'main' into omniauth-saml
alexBLR Nov 22, 2024
abfa3a4
Merge remote-tracking branch 'origin/main' into omniauth-saml
alexBLR Nov 25, 2024
75599dd
Merge branch 'omniauth-saml' of https://github.com/emory-libraries/dl…
alexBLR Nov 25, 2024
bd34107
adds requierd gem for devise
alexBLR Dec 2, 2024
89e301f
updates devise settings
alexBLR Jan 13, 2025
61db034
Merge remote-tracking branch 'origin/main' into omniauth-saml
alexBLR Jan 13, 2025
ac7eac9
fixes rubocop
alexBLR Jan 14, 2025
77f5ecd
omniauth/devise saml bug fixes
alexBLR Jan 27, 2025
fa22b46
add password required
alexBLR Jan 28, 2025
70897d7
Merge branch 'main' into omniauth-saml
alexBLR Jan 29, 2025
b1fdbd7
merges into main and fixes bugs
alexBLR Jan 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
HYRAX_VALKYRIE: true
RACK_ENV: test
RAILS_ENV: test
DATABASE_AUTH: true
SOLR_TEST_URL: http://127.0.0.1:8983/solr/dlp-selfdeposit-test
SPEC_OPTS: >-
--profile 10 --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
Expand Down
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ gem 'coffee-rails', '~> 4.2'
gem 'dalli'
gem 'devise'
gem 'devise-guests', '~> 0.8'
gem "devise_saml_authenticatable", git: "https://github.com/apokalipto/devise_saml_authenticatable"
gem 'dotenv-rails'
gem 'hydra-role-management'
gem 'hyrax', git: 'https://github.com/samvera/hyrax', ref: '9c58751'
gem 'jbuilder', '~> 2.5'
gem 'jquery-rails'
gem 'mutex_m', '~> 0.2.0'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-saml'
gem 'pg', '~> 1.3'
gem 'puma'
gem 'rails', '~> 6.1'
Expand Down
21 changes: 12 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
GIT
remote: https://github.com/apokalipto/devise_saml_authenticatable
revision: f8a85c3f3ed069fd40701424e2eef075219ef17f
specs:
devise_saml_authenticatable (1.9.1)
devise (> 2.0.0)
ruby-saml (~> 1.7)

GIT
remote: https://github.com/brentd/xray-rails
revision: f121814718c9907b20058dc9357b80a53afab821
Expand Down Expand Up @@ -792,6 +784,16 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 4)
omniauth (2.1.2)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-saml (2.2.1)
omniauth (~> 2.1)
ruby-saml (~> 1.17)
openseadragon (0.7.0)
rails (> 6.1.0)
orm_adapter (0.5.0)
Expand Down Expand Up @@ -1264,7 +1266,6 @@ DEPENDENCIES
debug (>= 1.0.0)
devise
devise-guests (~> 0.8)
devise_saml_authenticatable!
dotenv-rails
ed25519 (>= 1.2, < 2.0)
factory_bot_rails
Expand All @@ -1274,6 +1275,8 @@ DEPENDENCIES
jquery-rails
listen (>= 3.0.5, < 3.2)
mutex_m (~> 0.2.0)
omniauth-rails_csrf_protection
omniauth-saml
pg (~> 1.3)
pry-doc
pry-rails
Expand Down
10 changes: 10 additions & 0 deletions app/controllers/omniauth_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true
class OmniauthController < Devise::SessionsController
def new
if Rails.env.production?
redirect_to user_saml_omniauth_authorize_path
else
super
end
end
end
79 changes: 0 additions & 79 deletions app/controllers/saml_sessions_controller.rb

This file was deleted.

22 changes: 22 additions & 0 deletions app/controllers/users/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token, only: [:saml, :failure]

def saml
@user = User.from_omniauth(request.env["omniauth.auth"])

if @user.persisted?
sign_in @user
redirect_to request.env["omniauth.origin"] || hyrax.dashboard_path
set_flash_message(:notice, :success, kind: "SAML")
else
redirect_to root_path
set_flash_message(:notice, :failure, kind: "SAML", reason: "you aren't authorized to use this application.")
end
end

def failure
redirect_to root_path
end
end
9 changes: 9 additions & 0 deletions app/models/auth_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true
class AuthConfig
# In production, we use Shibboleth for user authentication,
# but in development mode, you may want to use local database
# authentication instead.
def self.use_database_auth?
ENV['DATABASE_AUTH'] == 'true'
end
end
87 changes: 45 additions & 42 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,71 @@ class User < ApplicationRecord
include Hydra::User
# Connects this user object to Role-management behaviors.
include Hydra::RoleManagement::UserRoles
# Connects this user object to Hyrax behaviors.

include Hyrax::User
include Hyrax::UserUsageStats

class NilSamlUserError < RuntimeError
attr_accessor :auth

def initialize(message = nil, auth = nil)
super(message)
self.auth = auth
end
end

# Connects this user object to Blacklights Bookmarks.
include Blacklight::User
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :saml_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
# temporary fix for SAML login while we have a hybrid login
validates :password, presence: true, if: :password_required?
devise_modules = [:registerable, :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:saml]]
devise_modules.prepend(:database_authenticatable, :registerable) # if AuthConfig.use_database_auth?
devise(*devise_modules)

def password_required?
return false if saml_authenticatable?
return false if provider == 'saml'
super
end

def saml_authenticatable?
uid.present?
end
# Method added by Blacklight; Blacklight uses #to_s on your
# user class to get a user-displayable login/identifier for
# the account.

def to_s
email
end

def self.from_saml(auth)
return unless Rails.env.production?
user = find_or_initialize_by(email: auth.info.mail)
set_user_attributes(user, auth)

if user.save
user
else
log_saml_error(auth)
User.new
def self.from_omniauth(auth)
begin
user = find_by!(provider: auth.provider, ppid: auth.info.ppid)
rescue ActiveRecord::RecordNotFound
log_omniauth_error(auth)
user = User.new
if auth.info.role == 'Staff'
assign_user_attributes(user, auth)
user.save
user
end
end

assign_user_attributes(user, auth)
user.save
user
end

def self.assign_user_attributes(user, auth)
user.assign_attributes(
display_name: auth.info.display_name,
ppid: auth.info.ppid,
provider: auth.provider,
uid: auth.info.net_id
)

user.email = "#{auth.info.net_id}@emory.edu" unless auth.info.net_id == 'tezprox'
end

def self.log_saml_error(auth)
if auth.info.mail.blank?
Rails.logger.error "Nil user detected: SAML didn't pass an email for #{auth.inspect}"
def self.log_omniauth_error(auth)
if auth.info.net_id.blank?
Rails.logger.error "Nil user detected: SAML didn't pass a net_id for #{auth.inspect}"
else
Rails.logger.error "Failed to create/update user: #{auth.inspect}"
# Log unauthorized logins to error
Rails.logger.error "Unauthorized user attempted login: #{auth.inspect}"
end
end
end

private

def set_user_attributes(user, auth)
password = SecureRandom.hex(16)
user.assign_attributes(
display_name: auth.info.displayName,
department: auth.info.ou,
title: auth.info.title,
uid: auth.uid,
# set a random password temporarily to allow a hybrid login
password:,
password_confirmation: password
)
end
2 changes: 1 addition & 1 deletion app/views/_user_util_links.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<% end %>
</li>
<li class="nav-item">
<%= link_to "Shib", '/users/saml/sign_in', method: :get, class: 'nav-link' %>
<%= link_to "Shib", main_app.user_saml_omniauth_authorize_path, method: :post, class: 'nav-link' %>
</li>
<% end %>
</ul>
29 changes: 29 additions & 0 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,33 @@

# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false

# OmniAuth configuration settings
config.sp_entity_id = ENV['SP_ENTITY']
config.idp_slo_target_url = ENV['IDP_SLO_TARGET_URL']
config.assertion_consumer_service_url = ENV['ASSERTION_CS_URL']
config.assertion_consumer_logout_service_url = ENV['ASSERTION_LOGOUT_URL']
config.issuer = ENV['ISSUER']
config.idp_sso_target_url = ENV['IDP_SSO_TARGET_URL']
config.idp_cert = ENV['IDP_CERT']
config.certificate = ENV['SP_CERT']
config.private_key = ENV['SP_KEY']
config.attribute_statements = {
net_id: ["urn:oid:0.9.2342.19200300.100.1.1"],
display_name: ["urn:oid:1.3.6.1.4.1.5923.1.1.1.2"],
last_name: ["urn:oid:2.5.4.4"],
title: ["urn:oid:2.5.4.12"],
email: ["urn:oid:0.9.2342.19200300.100.1.3"],
department: ["urn:oid:2.5.4.11"],
status: ["urn:oid:0.9.2342.19200300.100.1.45"],
ppid: ["urn:oid:2.5.4.5"],
role: ["urn:oid:0.9.2342.19200300.100.1.45"]
}
config.uid_attribute = "urn:oid:2.5.4.5"
config.security = {
want_assertions_encrypted: true,
want_assertions_signed: true,
digest_method: XMLSecurity::Document::SHA1,
signature_method: XMLSecurity::Document::RSA_SHA1
}
end
Loading