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

feat: patron keycloak #32

Merged
merged 9 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
50 changes: 25 additions & 25 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ GEM
base64 (0.1.1)
bcrypt (3.1.19)
bindata (2.4.15)
blacklight (7.33.1)
blacklight (7.34.0)
deprecation
globalid
hashdiff
Expand All @@ -107,7 +107,7 @@ GEM
kaminari (>= 0.15)
ostruct (>= 0.3.2)
rails (>= 5.1, < 7.1)
view_component (~> 2.66)
view_component (>= 2.66, < 4)
brakeman (6.0.1)
builder (3.2.4)
bundler-audit (0.9.1)
Expand All @@ -134,7 +134,7 @@ GEM
reline (>= 0.3.1)
deprecation (1.1.0)
activesupport
devise (4.9.2)
devise (4.9.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
Expand Down Expand Up @@ -171,7 +171,7 @@ GEM
i18n (1.14.1)
concurrent-ruby (~> 1.0)
io-console (0.6.0)
irb (1.8.1)
irb (1.8.3)
rdoc
reline (>= 0.3.8)
jbuilder (2.11.5)
Expand Down Expand Up @@ -199,7 +199,7 @@ GEM
kaminari-core (1.2.2)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
loofah (2.21.3)
loofah (2.21.4)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
Expand All @@ -215,14 +215,14 @@ GEM
multi_json (1.15.0)
multi_xml (0.6.0)
mysql2 (0.5.5)
net-imap (0.3.7)
net-imap (0.4.1)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
timeout
net-smtp (0.3.3)
net-smtp (0.4.0)
net-protocol
nio4r (2.5.9)
nokogiri (1.15.4-arm64-darwin)
Expand Down Expand Up @@ -254,13 +254,13 @@ GEM
orm_adapter (0.5.0)
ostruct (0.5.5)
parallel (1.23.0)
parser (3.2.2.3)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
psych (5.1.0)
psych (5.1.1)
stringio
public_suffix (5.0.3)
puma (6.3.1)
puma (6.4.0)
nio4r (~> 2.0)
racc (1.7.1)
rack (2.2.8)
Expand Down Expand Up @@ -306,10 +306,10 @@ GEM
rake (13.0.6)
rdoc (6.5.0)
psych (>= 4.0.0)
regexp_parser (2.8.1)
reline (0.3.8)
regexp_parser (2.8.2)
reline (0.3.9)
io-console (~> 0.5)
responders (3.1.0)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.6)
Expand All @@ -333,7 +333,7 @@ GEM
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-support (3.12.1)
rubocop (1.56.3)
rubocop (1.56.4)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
Expand All @@ -354,18 +354,18 @@ GEM
rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.21.1)
rubocop-rails (2.21.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-rspec (2.24.0)
rubocop-rspec (2.24.1)
rubocop (~> 1.33)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
selenium-webdriver (4.12.0)
selenium-webdriver (4.14.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
Expand All @@ -390,28 +390,28 @@ GEM
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
standard (1.31.1)
standard (1.31.2)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.56.2)
rubocop (~> 1.56.4)
standard-custom (~> 1.0.0)
standard-performance (~> 1.2)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.2.0)
standard-performance (1.2.1)
lint_roller (~> 1.1)
rubocop-performance (~> 1.19.0)
rubocop-performance (~> 1.19.1)
stringio (3.0.8)
strong_migrations (1.6.2)
strong_migrations (1.6.3)
activerecord (>= 5.2)
thor (1.2.2)
timeout (0.4.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
unicode-display_width (2.5.0)
version_gem (1.1.3)
view_component (2.82.0)
view_component (3.6.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
Expand All @@ -427,7 +427,7 @@ GEM
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.11)
zeitwerk (2.6.12)
zk (1.10.0)
zookeeper (~> 1.5.0)
zookeeper (1.5.5)
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/concerns/auth_session_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module AuthSessionConcern
extend ActiveSupport::Concern

def new_session_path(_scope)
new_user_session_path
end
end
7 changes: 7 additions & 0 deletions app/controllers/users/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ def catalogue_shared
sign_in_and_redirect @user, event: :authentication
end

def catalogue_patron
Rails.logger.debug(request.env["omniauth.auth"])
@user = User.from_keycloak_patron(request.env["omniauth.auth"])
store_keycloak_data(request.env["omniauth.auth"])
sign_in_and_redirect @user, event: :authentication
end

# Keycloak will display its own error page when there is a failure to login.
# :nocov:
def failure
Expand Down
43 changes: 8 additions & 35 deletions app/controllers/users/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
# frozen_string_literal: true

class Users::SessionsController < Devise::SessionsController
before_action :configure_sign_in_params, only: [:create]

skip_before_action :verify_authenticity_token, only: [:backchannel_logout]

def create
super
end

def destroy
if session[:iss].present?
# Keycloak logout. Keycloak will send a POST to "/devise_logout" to perform a
# backchannel logout that terminates the Devise session.
keycloak_logout
else
# There is no Keycloak session identifier, so destroy the Devise session.
devise_logout
end
# Keycloak logout. Keycloak will send a POST to "/backchannel_logout" to perform a
# backchannel logout that terminates the Devise session.
iss = session[:iss]
id_token = session[:id_token]

signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message! :notice, :signed_out if signed_out
redirect_to("#{iss}/protocol/openid-connect/logout?id_token_hint=#{id_token}&post_logout_redirect_uri=#{root_url}", allow_other_host: true)
end

def backchannel_logout
Expand All @@ -30,25 +24,4 @@ def backchannel_logout
user = User.find_by(session_token: session_id)
user.update_column(:session_token, SecureRandom.hex)
end

protected

def devise_logout
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message! :notice, :signed_out if signed_out
respond_to_on_destroy
end

def keycloak_logout
iss = session[:iss]
id_token = session[:id_token]

signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message! :notice, :signed_out if signed_out
redirect_to("#{iss}/protocol/openid-connect/logout?id_token_hint=#{id_token}&post_logout_redirect_uri=#{root_url}", allow_other_host: true)
end

def configure_sign_in_params
devise_parameter_sanitizer.permit(:sign_in, keys: [user: [:username, :password]])
end
end
45 changes: 34 additions & 11 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ class User < PatronsRecord

# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :user_reg_authenticatable, :timeoutable,
:omniauthable, omniauth_providers: %i[catalogue_sol catalogue_spl catalogue_shared]
devise :timeoutable, :omniauthable, omniauth_providers: %i[catalogue_patron catalogue_sol catalogue_spl catalogue_shared]

attr_accessor :username, :password, :session_token

Expand Down Expand Up @@ -68,20 +67,44 @@ def self.from_keycloak(auth)
end
end

def self.from_keycloak_patron(auth)
ActiveRecord::Base.transaction do
user = find_or_create_by!(folio_id: auth.extra.raw_info.preferred_username) do |user|
# We don't really care about the password since auth is via Keycloak, so we're just
# putting a dummy value here.
user.encrypted_password = SecureRandom.hex(14)
end
# set/update values from Keycloak in case they've changed
user.email = auth.info.email.present? ? auth.info.email : ""
user.provider = auth.provider
user.uid = auth.uid
user.name_given = auth.info.first_name
user.name_family = auth.info.last_name

# TODO: send request to catalogue services to determine active state of user account in FOLIO

# this is required for backchannel logout
user.session_token = auth.extra.raw_info.sid

# reload user with updated values from database
user.save!
user.reload
end
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
name = "#{name_given} #{name_family}"
if provider.present?
name = if provider == "catalogue_sol"
"#{name} (SOL)"
elsif provider == "catalogue_spl"
"#{name} (SPL)"
elsif provider == "catalogue_shared"
"#{name} (TOL)"
end
if provider == "catalogue_sol"
"#{name} (SOL)"
elsif provider == "catalogue_spl"
"#{name} (SPL)"
elsif provider == "catalogue_shared"
"#{name} (TOL)"
else
name
end
name
end
end
18 changes: 1 addition & 17 deletions app/views/users/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
<h1>Login</h1>

<%= form_for(resource, as: resource_name, html: {'data-turbo' => "false"}, url: session_path(resource_name)) do |f| %>
<div class="field form-group">
<%= f.label :username, "User ID" %>
<%= f.text_field :username, autofocus: true, autocomplete: "username",class: "form-control col-md-4" %>
</div>

<div class="field form-group">
<%= f.label :password, "Family Name" %>
<%= f.text_field :password, autocomplete: "off",class: "form-control col-md-4" %>
</div>

<p><%= t("devise.registrations.register", url: ENV["NATIONAL_LIBRARY_CARD_URL"]).html_safe %></p>

<div class="actions">
<%= f.submit "Login", class: "btn btn-primary" %>
</div>
<% end %>
<%= button_to t("auth.patron_login"), user_catalogue_patron_omniauth_authorize_path, class: "btn btn-primary", data: { turbo: false } %>

<%= render "users/shared/links" %>
Loading