Skip to content

Commit

Permalink
Merge pull request #32 from nla/feat/blac-580-patron-keycloak
Browse files Browse the repository at this point in the history
feat: patron keycloak
  • Loading branch information
yetti authored Oct 17, 2023
2 parents 6ccd872 + bdc42a9 commit 2f06439
Show file tree
Hide file tree
Showing 22 changed files with 247 additions and 195 deletions.
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
35 changes: 33 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ 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]
if ENV["KC_PATRON_REALM"]
devise :timeoutable, :omniauthable, omniauth_providers: %i[catalogue_patron catalogue_sol catalogue_spl catalogue_shared]
else
devise :user_reg_authenticatable, :timeoutable,
:omniauthable, omniauth_providers: %i[catalogue_patron catalogue_sol catalogue_spl catalogue_shared]
end

attr_accessor :username, :password, :session_token

Expand Down Expand Up @@ -68,6 +72,31 @@ 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.
Expand All @@ -80,6 +109,8 @@ def to_s
"#{name} (SPL)"
elsif provider == "catalogue_shared"
"#{name} (TOL)"
else
name
end
end
name
Expand Down
30 changes: 17 additions & 13 deletions app/views/users/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
<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>
<% if ENV["KC_PATRON_REALM"] %>
<%= button_to t("auth.patron_login"), user_catalogue_patron_omniauth_authorize_path, class: "btn btn-primary", data: { turbo: false } %>
<% else %>
<%= 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>
<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>
<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>
<div class="actions">
<%= f.submit "Login", class: "btn btn-primary" %>
</div>
<% end %>
<% end %>

<%= render "users/shared/links" %>
16 changes: 11 additions & 5 deletions config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ def recall
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
config.authentication_keys = {username: true, password: true}
unless ENV["KC_PATRON_REALM"]
config.authentication_keys = {username: true, password: true}
end

# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
Expand All @@ -107,7 +109,9 @@ def recall
# Configure which authentication keys should have whitespace stripped.
# These keys will have whitespace before and after removed upon creating or
# modifying a user and when used to authenticate or find a user. Default is :email.
config.strip_whitespace_keys = [:username, :password]
unless ENV["KC_PATRON_REALM"]
config.strip_whitespace_keys = [:username, :password]
end

# Tell if authentication through request.params is enabled. True by default.
# It can be set to an array that will enable params authentication only for the
Expand Down Expand Up @@ -321,9 +325,11 @@ def recall
# If you want to use other strategies, that are not supported by Devise, or
# change the failure app, you can configure them inside the config.warden block.
#
config.warden do |manager|
manager.failure_app = CatalogueFailureApp
manager.default_strategies(scope: :user).unshift :user_reg_authenticatable
unless ENV["KC_PATRON_REALM"]
config.warden do |manager|
manager.failure_app = CatalogueFailureApp
manager.default_strategies(scope: :user).unshift :user_reg_authenticatable
end
end

# ==> Mountable engine configurations
Expand Down
8 changes: 8 additions & 0 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@
strategy_class: OmniAuth::Strategies::KeycloakOpenId,
authorize_params: {scope: "openid"},
name: "catalogue_shared"

provider :keycloak_openid,
ENV.fetch("KC_PATRON_CLIENT", "patron"),
ENV.fetch("KC_PATRON_SECRET", "default secret"),
client_options: {site: ENV.fetch("KEYCLOAK_URL", "http://localhost:9090"), realm: ENV.fetch("KC_PATRON_REALM", "nla-patron")},
strategy_class: OmniAuth::Strategies::KeycloakOpenId,
authorize_params: {scope: "openid"},
name: "catalogue_patron"
end
1 change: 1 addition & 0 deletions config/locales/auth.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ en:
sol_login: "Staff Official Loan"
spl_login: "Staff Personal Loan"
shared_login: "Team Official Loan"
patron_login: "Patron Login"
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
}

devise_scope(:user) do
if ENV["KC_PATRON_REALM"]
get "sign_in", to: "users/sessions#new", as: :new_user_session
delete "sign_out", to: "users/sessions#destroy", as: :destroy_user_session
end
post "/backchannel_logout", to: "users/sessions#backchannel_logout", as: :backchannel_logout
end
end
4 changes: 4 additions & 0 deletions spec/dummy/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
class ApplicationController < ActionController::Base
# Attempt to find the mapped route for devise based on request path

if ENV["KC_PATRON_REALM"]
include AuthSessionConcern
end
end
4 changes: 2 additions & 2 deletions spec/dummy/app/views/pages/about.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
<h1>Pages#about</h1>
<p>Find me in app/views/pages/about.html.erb</p>
4 changes: 4 additions & 0 deletions spec/dummy/app/views/pages/home.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>

<% if current_user.present? %>
Logged in as <%= current_user %>
<% end %>
2 changes: 1 addition & 1 deletion spec/dummy/app/views/shared/_user_links.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<li><%= link_to "About", about_path, class: "#{active_link_class_controller(%w[pages])}" %></li>
<% if current_user %>
<li>
<%= link_to t('blacklight.header_links.logout'), destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %>
<%= link_to t('blacklight.header_links.logout'), destroy_user_session_url, method: :delete, data: { turbo_method: :delete } %>
</li>
<% unless current_user.to_s.blank? %>
<li>
Expand Down
Loading

0 comments on commit 2f06439

Please sign in to comment.