Skip to content

Commit

Permalink
chore: merge feature flag changes
Browse files Browse the repository at this point in the history
  • Loading branch information
yetti committed Oct 17, 2023
2 parents 24c53d8 + f66cf16 commit dac96ed
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 63 deletions.
26 changes: 17 additions & 9 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ class User < PatronsRecord

# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :timeoutable, :omniauthable, omniauth_providers: %i[catalogue_patron 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 @@ -97,14 +102,17 @@ def self.from_keycloak_patron(auth)
# the account.
def to_s
name = "#{name_given} #{name_family}"
if provider == "catalogue_sol"
"#{name} (SOL)"
elsif provider == "catalogue_spl"
"#{name} (SPL)"
elsif provider == "catalogue_shared"
"#{name} (TOL)"
else
name
if provider.present?
name = if provider == "catalogue_sol"
"#{name} (SOL)"
elsif provider == "catalogue_spl"
"#{name} (SPL)"
elsif provider == "catalogue_shared"
"#{name} (TOL)"
else
name
end
end
name
end
end
22 changes: 21 additions & 1 deletion app/views/users/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
<h1>Login</h1>

<%= button_to t("auth.patron_login"), user_catalogue_patron_omniauth_authorize_path, class: "btn btn-primary", data: { turbo: false } %>
<% if ENV["KC_PATRON_REALM"] %>
<%= button_to t("auth.patron_login"), user_catalogue_patron_omniauth_authorize_path, 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>

<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 %>
<% end %>

<%= render "users/shared/links" %>
68 changes: 59 additions & 9 deletions config/initializers/devise.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# frozen_string_literal: true

class CatalogueFailureApp < Devise::FailureApp
def recall
header_info = if relative_url_root?
base_path = Pathname.new(relative_url_root)
full_path = Pathname.new(attempted_path)

{"SCRIPT_NAME" => relative_url_root,
"PATH_INFO" => "/" + full_path.relative_path_from(base_path).to_s}
else
{"PATH_INFO" => attempted_path}
end

header_info.each do |var, value|
if request.respond_to?(:set_header)
request.set_header(var, value)
else
request.env[var] = value
end
end

message = I18n.t("devise.failure.no_credentials", url: ENV["ASK_LIBRARIAN_URL"]).html_safe
flash.now[:alert] = i18n_message(message) if is_flashing_format?
self.response = recall_app(warden_options[:recall]).call(request.env).tap { |response|
response[0] = Rack::Utils.status_code(
response[0].in?(300..399) ? Devise.responder.redirect_status : Devise.responder.error_status
)
}
end
end

Devise.add_module(:getalibrarycard_authenticatable, {
strategy: true,
controller: :sessions,
model: "devise/models/getalibrarycard_authenticatable",
route: :session
})

Devise.add_module(:user_reg_authenticatable, {
strategy: true,
controller: :sessions,
model: "devise/models/user_reg_authenticatable",
route: :session
})

# Assuming you have not yet modified this file, each configuration option below
# is set to its default value. Note that some are commented out while others
# are not: uncommented lines are intended to protect your configuration from
Expand All @@ -14,7 +58,7 @@
# confirmation, reset password and unlock tokens in the database.
# Devise will use the `secret_key_base` as its `secret_key`
# by default. You can change it below and use your own secret key.
# config.secret_key = 'b0381e2e65fbb4ae91e67940ada6f0798d37dfed9e0cc74f5aa16587f6c6f5a2cc41d7ab86d5908f5eb67e5c481bc9ab86b13f01fc63bf02803526a5268ede3b'
# config.secret_key = '0c97e745c71f8e98a4795c38e08c1099147b591da8cd41ffcef1591b0d842f99e77f02b72e0cac0fe3c9d69bb9cbba43d4ccc6f6901089536ee1a48b953eba08'

# ==> Controller configuration
# Configure the parent class to the devise controllers.
Expand Down Expand Up @@ -46,7 +90,9 @@
# 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 = [:email]
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 @@ -63,7 +109,9 @@
# 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 = [:email]
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 @@ -126,7 +174,7 @@
config.stretches = Rails.env.test? ? 1 : 12

# Set up a pepper to generate the hashed password.
# config.pepper = 'a4d2d0b4d065678ac151abcc34342a4af2086f5b45ff704dc2bcef23efe9bc6b5a1bb8b85acffbadaafef827eec324e9748a6d0b811980a29b870c79ac7b7bd1'
# config.pepper = 'c9ee4c0658e1c6d2b8d347958245cfe5040389fcf78ed6a71db76ee4be52802ee17f3d6e18d4d431522ba2330b5d25a4a16b13680b5689ad61902c4b7b0fd000'

# Send a notification to the original email when the user's email is changed.
# config.send_email_changed_notification = false
Expand Down Expand Up @@ -266,7 +314,7 @@
# config.navigational_formats = ['*/*', :html, :turbo_stream]

# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete
# config.sign_out_via = :delete

# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
Expand All @@ -277,10 +325,12 @@
# 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.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
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
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
Expand Down
6 changes: 4 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
}

devise_scope(:user) do
get "sign_in", to: "users/sessions#new", as: :new_user_session
delete "sign_out", to: "users/sessions#destroy", as: :destroy_user_session
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
5 changes: 4 additions & 1 deletion spec/dummy/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class ApplicationController < ActionController::Base
# Attempt to find the mapped route for devise based on request path
include AuthSessionConcern

if ENV["KC_PATRON_REALM"]
include AuthSessionConcern
end
end
2 changes: 1 addition & 1 deletion spec/dummy/app/views/pages/home.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
<p>Find me in app/views/pages/home.html.erb</p>

<% if current_user.present? %>
Logged in as <%= current_user %>
Logged in as <%= current_user %>
<% end %>
11 changes: 8 additions & 3 deletions spec/factories/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@
factory :user do
sequence(:email) { |n| "test-#{n.to_s.rjust(3, "0")}@example.com" }
password { "123456" }
uid { SecureRandom.uuid }
provider { "catalogue_patron" }
folio_id { SecureRandom.uuid }
name_given { "Test" }
name_family { "User" }
session_token { SecureRandom.hex }

if ENV["KC_PATRON_REALM"]
uid { SecureRandom.uuid }
provider { "catalogue_patron" }
session_token { SecureRandom.hex }
end

trait :staff do
provider { "catalogue_sol" }
uid { "603e26dd-b2d4-4a88-ad9d-406eaec31463" }
name_given { "Staff" }
name_family { "User" }
sequence(:email) { |n| "staff-#{n.to_s.rjust(3, "0")}@nla.gov.au" }
session_token { SecureRandom.hex }
end

created_at { Time.current }
Expand Down
19 changes: 19 additions & 0 deletions spec/features/active_link_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,24 @@

expect(page).to have_css("a.active", text: "Blacklight Test")
end

context "when Keycloak patron authentication is enabled" do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with("KC_PATRON_REALM").and_return("patron_realm")
end

it "returns active given the controller and action" do
visit root_path
click_link "Login"
expect(page).to have_content("Login")

click_button "Patron Login"

visit account_path

expect(page).to have_css("a.active", text: "Blacklight Test")
end
end
end
end
105 changes: 68 additions & 37 deletions spec/features/login_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,94 @@
require "rails_helper"

RSpec.describe "Login" do
it "displays the patron login link" do
visit root_path
click_link "Login"
expect(page).to have_content(I18n.t("auth.patron_login"))
before do
# rubocop:disable RSpec/AnyInstance
allow_any_instance_of(PatronHelper).to receive(:user_location).and_return(:offsite)
# rubocop:enable RSpec/AnyInstance
end

context "when in public network" do
# rubocop:disable RSpec/AnyInstance
context "when Keycloak patron authentication is enabled" do
before do
allow_any_instance_of(PatronHelper).to receive(:user_location).and_return(:offsite)
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with("KC_PATRON_REALM").and_return("patron_realm")
end
# rubocop:enable RSpec/AnyInstance

it "does not display staff login links" do
it "displays a login link" do
visit root_path
click_link "Login"
expect(page).to have_content("Login")

expect(page).not_to have_content(I18n.t("auth.staff.sol_login"))
expect(page).not_to have_content(I18n.t("auth.staff.spl_login"))
expect(page).not_to have_content(I18n.t("auth.staff.shared_login"))
expect(page).to have_link("Login", href: new_user_session_path)
end
end

context "when inside local network" do
# rubocop:disable RSpec/AnyInstance
before do
allow_any_instance_of(PatronHelper).to receive(:user_location).and_return(:onsite)
it "displays the patron login button" do
visit root_path
click_link "Login"
expect(page).to have_content(I18n.t("auth.patron_login"))
end
# rubocop:enable RSpec/AnyInstance
end

it "does not display staff login links" do
context "when Keycloak patron authentication is disabled" do
it "creates a new session for the user" do
visit root_path
click_link "Login"
expect(page).to have_content("Login")

expect(page).not_to have_content(I18n.t("auth.staff.sol_login"))
expect(page).not_to have_content(I18n.t("auth.staff.spl_login"))
expect(page).not_to have_content(I18n.t("auth.staff.shared_login"))
fill_in "user_username", with: "bltest"
fill_in "user_password", with: "test"
click_button "Login"

expect(page).to have_content(I18n.t("devise.sessions.signed_in"))
expect(page).to have_content("blacklight test")
end
end

context "when inside staff network" do
# rubocop:disable RSpec/AnyInstance
before do
allow_any_instance_of(PatronHelper).to receive(:user_location).and_return(:staff)
it "displays a registration link" do
visit new_user_session_path

expect(page).to have_link("here", href: ENV["NATIONAL_LIBRARY_CARD_URL"])
end
# rubocop:enable RSpec/AnyInstance

it "displays staff login links" do
visit root_path
click_link "Login"
expect(page).to have_content("Login")
it "disables Turbo" do
visit new_user_session_path

expect(page).to have_css("form[data-turbo]")
end

context "when user is inactive" do
it "displays an error message" do
visit root_path
click_link "Login"
expect(page).to have_content("Login")

fill_in "user_username", with: "bltest"
fill_in "user_password", with: "blacklight"
click_button "Login"

expect(page).to have_content(I18n.t("devise.failure.expired"))
expect(page).to have_content("Login")
end
end

context "when user has the wrong credentials" do
it "displays an error message" do
visit root_path
click_link "Login"
expect(page).to have_content("Login")

fill_in "user_username", with: "0000"
fill_in "user_password", with: "failure"
click_button "Login"

expect(page).to have_link("Ask a Librarian", href: ENV["ASK_LIBRARIAN_URL"])
expect(page).to have_content("Login")
end
end

context "when user does not enter a username or password" do
it "displays an error message" do
visit new_user_session_path
click_button "Login"

expect(page).to have_content(I18n.t("auth.staff.sol_login"))
expect(page).to have_content(I18n.t("auth.staff.spl_login"))
expect(page).to have_content(I18n.t("auth.staff.shared_login"))
expect(page).to have_link("Ask a Librarian", href: ENV["ASK_LIBRARIAN_URL"])
end
end
end
end

0 comments on commit dac96ed

Please sign in to comment.