Skip to content

Commit 23b9fc7

Browse files
committed
add external authenticator
1 parent 9ebf812 commit 23b9fc7

11 files changed

Lines changed: 154 additions & 21 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
.rails_generators~
1717

1818
/coverage
19-
19+
.idea
2020
/pkg
2121

2222
# http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/

app/assets/stylesheets/casino.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,18 @@ table {
270270
}
271271
}
272272

273+
/// LOGIN EXTERNAL INLINE LIST ///
274+
#external_list
275+
{
276+
margin: 0;
277+
padding: 0;
278+
margin-right: 10px;
279+
list-style-type: none;
280+
text-align: left;
281+
282+
li { display: inline-block; }
283+
}
284+
273285
/// SESSIONS ///
274286
.sessions, .logout {
275287
width: 800px;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require 'casino/external_authenticator'
2+
3+
# The external static authenticator is just a simple example.
4+
# Never use this authenticator in a production environment!
5+
class CASino::StaticExternalAuthenticator < CASino::Authenticator
6+
7+
# @param [Hash] options
8+
def initialize(options)
9+
@users = options[:users] || {}
10+
end
11+
12+
def validate(params, cookies)
13+
token = :"#{cookies[:token]}"
14+
if @users.include?(token)
15+
{
16+
username: @users[token][:username],
17+
extra_attributes: @users[token].except(:token)
18+
}
19+
else
20+
false
21+
end
22+
end
23+
24+
def view
25+
return nil
26+
end
27+
28+
end

app/controllers/casino/sessions_controller.rb

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ class CASino::SessionsController < CASino::ApplicationController
33
include CASino::AuthenticationProcessor
44
include CASino::TwoFactorAuthenticatorProcessor
55

6-
before_action :validate_login_ticket, only: [:create]
76
before_action :ensure_service_allowed, only: [:new, :create]
87
before_action :load_ticket_granting_ticket_from_parameter, only: [:validate_otp]
98
before_action :ensure_signed_in, only: [:index, :destroy]
@@ -15,18 +14,21 @@ def index
1514
end
1615

1716
def new
17+
@external_authenticators = authenticators(:external_authenticators)
18+
@login_ticket = CASino::LoginTicket.create.ticket
1819
tgt = current_ticket_granting_ticket
1920
return handle_signed_in(tgt) unless params[:renew] || tgt.nil?
2021
redirect_to(params[:service]) if params[:gateway] && params[:service].present?
2122
end
2223

2324
def create
24-
validation_result = validate_login_credentials(params[:username], params[:password])
25-
if !validation_result
26-
log_failed_login params[:username]
27-
show_login_error I18n.t('login_credential_acceptor.invalid_login_credentials')
25+
if CASino::LoginTicket.consume(params[:lt])
26+
logger.debug "params[:lt]: #{params[:lt]} successfully validated"
27+
authenticate_user
2828
else
29-
sign_in(validation_result, long_term: params[:rememberMe], credentials_supplied: true)
29+
external_authenticators = authenticators(:external_authenticators)
30+
log_failed_login params[:username]
31+
show_login_error(I18n.t('login_credential_acceptor.invalid_login_credentials'), external_authenticators)
3032
end
3133
end
3234

@@ -62,17 +64,31 @@ def validate_otp
6264

6365
private
6466

65-
def show_login_error(message)
66-
flash.now[:error] = message
67-
render :new, status: :forbidden
67+
def validate_credentials
68+
if params[:external]
69+
validate_external_credentials(params, cookies)
70+
else
71+
validate_login_credentials(params[:username], params[:password])
72+
end
6873
end
6974

70-
def validate_login_ticket
71-
unless CASino::LoginTicket.consume(params[:lt])
72-
show_login_error I18n.t('login_credential_acceptor.invalid_login_ticket')
75+
def authenticate_user
76+
validation_result = validate_credentials
77+
if !validation_result.nil?
78+
sign_in(validation_result, long_term: params[:rememberMe], credentials_supplied: true)
79+
else
80+
external_authenticators = authenticators(:external_authenticators)
81+
log_failed_login params[:username]
82+
show_login_error(I18n.t('login_credential_acceptor.invalid_login_credentials'), external_authenticators)
7383
end
7484
end
7585

86+
def show_login_error(message, external_authenticators)
87+
flash.now[:error] = message
88+
@external_authenticators = external_authenticators
89+
render :new, status: :forbidden
90+
end
91+
7692
def ensure_service_allowed
7793
if params[:service].present? && !service_allowed?(params[:service])
7894
render 'service_not_allowed', status: :forbidden

app/processors/casino/authentication_processor.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,28 @@ module CASino::AuthenticationProcessor
44
extend ActiveSupport::Concern
55

66
def validate_login_credentials(username, password)
7+
validate :authenticators do |authenticator_name, authenticator|
8+
authenticator.validate(username, password)
9+
end
10+
end
11+
12+
def validate_external_credentials(params, cookies)
13+
validate :external_authenticators do |authenticator_name, authenticator|
14+
if authenticator_name == params[:external]
15+
authenticator.validate(params, cookies)
16+
end
17+
end
18+
end
19+
20+
def validate(type, &validator)
721
authentication_result = nil
8-
authenticators.each do |authenticator_name, authenticator|
22+
authenticators(type).each do |authenticator_name, authenticator|
923
begin
10-
data = authenticator.validate(username, password)
24+
data = validator.call(authenticator_name, authenticator)
1125
rescue CASino::Authenticator::AuthenticatorError => e
1226
Rails.logger.error "Authenticator '#{authenticator_name}' (#{authenticator.class}) raised an error: #{e}"
1327
end
28+
1429
if data
1530
authentication_result = { authenticator: authenticator_name, user_data: data }
1631
Rails.logger.info("Credentials for username '#{data[:username]}' successfully validated using authenticator '#{authenticator_name}' (#{authenticator.class})")
@@ -21,15 +36,17 @@ def validate_login_credentials(username, password)
2136
end
2237

2338
def load_user_data(authenticator_name, username)
24-
authenticator = authenticators[authenticator_name]
39+
authenticator = authenticators(:authenticators)[authenticator_name]
2540
return nil if authenticator.nil?
2641
return nil unless authenticator.respond_to?(:load_user_data)
2742
authenticator.load_user_data(username)
2843
end
2944

30-
def authenticators
31-
@authenticators ||= {}.tap do |authenticators|
32-
CASino.config[:authenticators].each do |name, auth|
45+
def authenticators(type)
46+
authenticators ||= {}
47+
return authenticators[type] if authenticators.has_key?(type)
48+
authenticators[type] = begin
49+
CASino.config[type].each do |name, auth|
3350
next unless auth.is_a?(Hash)
3451

3552
authenticator = if auth[:class]
@@ -38,7 +55,7 @@ def authenticators
3855
load_authenticator(auth[:authenticator])
3956
end
4057

41-
authenticators[name] = authenticator.new(auth[:options])
58+
CASino.config[type][name] = authenticator.new(auth[:options])
4259
end
4360
end
4461
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<% unless @external_authenticators.nil? %>
2+
<% if @external_authenticators.any? %>
3+
<ul id="external_list">
4+
<% @external_authenticators.each do |authenticator_name, authenticator| %>
5+
<% unless authenticator.view.nil? %>
6+
<li>
7+
<%= form_tag(login_path, method: :post) do %>
8+
<%= hidden_field_tag :lt, @login_ticket %>
9+
<%= hidden_field_tag :external, authenticator_name %>
10+
<%= render authenticator.view, :authenticator => authenticator %>
11+
<% end %>
12+
</li>
13+
<% end %>
14+
<% end %>
15+
</ul>
16+
<% end%>
17+
<% end%>

app/views/casino/sessions/new.html.erb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<div class="form">
1515
<%= form_tag(login_path, method: :post, id: 'login-form') do %>
16-
<%= hidden_field_tag :lt, CASino::LoginTicket.create.ticket %>
16+
<%= hidden_field_tag :lt, @login_ticket %>
1717
<%= hidden_field_tag :service, params[:service] unless params[:service].nil? %>
1818
<%= label_tag :username, t('login.label_username') %>
1919
<%= text_field_tag :username, params[:username], autofocus:true %>
@@ -24,6 +24,7 @@
2424
<% end %>
2525
<%= button_tag t('login.label_button'), :class => 'button' %>
2626
<% end %>
27+
<%= render 'external' %>
2728
</div>
2829
</div>
2930
<%= render 'footer' %>

lib/casino.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module CASino
66

77
defaults = {
88
authenticators: HashWithIndifferentAccess.new,
9+
external_authenticators: HashWithIndifferentAccess.new,
910
require_service_rules: false,
1011
logger: Rails.logger,
1112
frontend: HashWithIndifferentAccess.new(
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module CASino
2+
class ExternalAuthenticator
3+
class ExternalAuthenticatorError < StandardError; end
4+
5+
def validate(params, cookies)
6+
raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
7+
end
8+
9+
def view
10+
raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
11+
end
12+
13+
end
14+
end

spec/authenticator/base_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'spec_helper'
22

33
require 'casino/authenticator'
4+
require 'casino/external_authenticator'
45

56
describe CASino::Authenticator do
67
subject {
@@ -13,3 +14,21 @@
1314
end
1415
end
1516
end
17+
18+
describe CASino::ExternalAuthenticator do
19+
subject {
20+
CASino::ExternalAuthenticator.new
21+
}
22+
23+
context '#validate' do
24+
it 'raises an error' do
25+
expect { subject.validate(nil, nil) }.to raise_error(NotImplementedError)
26+
end
27+
end
28+
29+
context '#view' do
30+
it 'raises an error' do
31+
expect { subject.view }.to raise_error(NotImplementedError)
32+
end
33+
end
34+
end

0 commit comments

Comments
 (0)