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 all 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
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