Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.
Open
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
12 changes: 12 additions & 0 deletions app/assets/stylesheets/casino.scss
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,18 @@ table {
}
}

/// LOGIN EXTERNAL INLINE LIST ///
#external_list
{
margin: 0;
padding: 0;
margin-right: 10px;
list-style-type: none;
text-align: left;

li { display: inline-block; }
}

/// SESSIONS ///
.sessions, .logout {
width: 800px;
Expand Down
28 changes: 28 additions & 0 deletions app/authenticators/casino/static_external_authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'casino/external_authenticator'

# The external static authenticator is just a simple example.
# Never use this authenticator in a production environment!
class CASino::StaticExternalAuthenticator < CASino::Authenticator

# @param [Hash] options
def initialize(options)
@users = options[:users] || {}
end

def validate(params, cookies)
token = :"#{cookies[:token]}"
if @users.include?(token)
{
username: @users[token][:username],
extra_attributes: @users[token].except(:token)
}
else
false
end
end

def view
return nil
end

end
2 changes: 1 addition & 1 deletion app/controllers/casino/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def new
end

def create
processor(:LoginCredentialAcceptor).process(params, request.user_agent)
processor(:LoginCredentialAcceptor).process(params, cookies, request.user_agent)
end

def destroy
Expand Down
11 changes: 6 additions & 5 deletions app/listeners/casino/login_credential_acceptor_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ def two_factor_authentication_pending(ticket_granting_ticket)
@controller.render 'validate_otp'
end

def invalid_login_credentials(login_ticket)
def invalid_login_credentials(login_ticket, external_authenticators)
@controller.flash.now[:error] = I18n.t('login_credential_acceptor.invalid_login_credentials')
rerender_login_page(login_ticket)
rerender_login_page(login_ticket, external_authenticators)
end

def invalid_login_ticket(login_ticket)
def invalid_login_ticket(login_ticket, external_authenticators)
@controller.flash.now[:error] = I18n.t('login_credential_acceptor.invalid_login_ticket')
rerender_login_page(login_ticket)
rerender_login_page(login_ticket, external_authenticators)
end

def service_not_allowed(service)
Expand All @@ -31,8 +31,9 @@ def service_not_allowed(service)
end

private
def rerender_login_page(login_ticket)
def rerender_login_page(login_ticket, external_authenticators)
assign(:login_ticket, login_ticket)
assign(:external_authenticators, external_authenticators)
@controller.render 'new', status: 403
end
end
3 changes: 2 additions & 1 deletion app/listeners/casino/login_credential_requestor_listener.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
require_relative 'listener'

class CASino::LoginCredentialRequestorListener < CASino::Listener
def user_not_logged_in(login_ticket)
def user_not_logged_in(login_ticket, external_authenticators)
assign(:login_ticket, login_ticket)
assign(:external_authenticators, external_authenticators)
@controller.cookies.delete :tgt
end

Expand Down
23 changes: 18 additions & 5 deletions app/processors/casino/login_credential_acceptor_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,43 @@ class CASino::LoginCredentialAcceptorProcessor < CASino::Processor
# The second argument (String) is the ticket-granting ticket. It should be stored in a cookie named "tgt".
# The third argument (Time, optional, default = nil) is for "Remember Me" functionality.
# This is the cookies expiration date. If it is `nil`, the cookie should be a session cookie.
# * `#invalid_login_ticket` and `#invalid_login_credentials`: The first argument is a LoginTicket.
# * `#invalid_login_ticket` and `#invalid_login_credentials`: The first argument is a LoginTicket and the second argument is the collection of external authenticators
# See {CASino::LoginCredentialRequestorProcessor} for details.
# * `#service_not_allowed`: The user tried to access a service that this CAS server is not allowed to serve.
# * `#two_factor_authentication_pending`: The user should be asked to enter his OTP. The first argument (String) is the ticket-granting ticket. The ticket-granting ticket is not active yet. Use SecondFactorAuthenticatonAcceptor to activate it.
#
# @param [Hash] params parameters supplied by user
# @param [Hash] cookies cookies supplied by user
# @param [String] user_agent user-agent delivered by the client
def process(params = nil, user_agent = nil)
def process(params = nil, cookies = nil, user_agent = nil)
@params = params || {}
@cookies = cookies || {}
@user_agent = user_agent
if login_ticket_valid?(@params[:lt])
authenticate_user
else
@listener.invalid_login_ticket(acquire_login_ticket)
external_authenticators = authenticators(:external_authenticators)
@listener.invalid_login_ticket(acquire_login_ticket, external_authenticators)
end
end

protected
def validate_credentials
if @params[:external]
validate_external_credentials(@params, @cookies)
else
validate_login_credentials(@params[:username], @params[:password])
end
end

private
def authenticate_user
authentication_result = validate_login_credentials(@params[:username], @params[:password])
authentication_result = validate_credentials
if !authentication_result.nil?
user_logged_in(authentication_result)
else
@listener.invalid_login_credentials(acquire_login_ticket)
external_authenticators = authenticators(:external_authenticators)
@listener.invalid_login_credentials(acquire_login_ticket, external_authenticators)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class CASino::LoginCredentialRequestorProcessor < CASino::Processor
include CASino::ProcessorConcern::LoginTickets
include CASino::ProcessorConcern::ServiceTickets
include CASino::ProcessorConcern::TicketGrantingTickets
include CASino::ProcessorConcern::Authentication

# Use this method to process the request.
#
Expand Down Expand Up @@ -51,7 +52,8 @@ def handle_not_logged_in
@listener.user_logged_in(@service_url)
else
login_ticket = acquire_login_ticket
@listener.user_not_logged_in(login_ticket)
external_authenticators = authenticators(:external_authenticators)
@listener.user_not_logged_in(login_ticket, external_authenticators)
end
end

Expand Down
28 changes: 22 additions & 6 deletions app/processors/casino/processor_concern/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@ module ProcessorConcern
module Authentication

def validate_login_credentials(username, password)
validate :authenticators do |authenticator_name, authenticator|
authenticator.validate(username, password)
end
end

def validate_external_credentials(params, cookies)
validate :external_authenticators do |authenticator_name, authenticator|
if authenticator_name == params[:external]
authenticator.validate(params, cookies)
end
end
end

def validate(type, &validator)
authentication_result = nil
authenticators.each do |authenticator_name, authenticator|
authenticators(type).each do |authenticator_name, authenticator|
begin
data = authenticator.validate(username, password)
data = validator.call(authenticator_name, authenticator)
rescue CASino::Authenticator::AuthenticatorError => e
Rails.logger.error "Authenticator '#{authenticator_name}' (#{authenticator.class}) raised an error: #{e}"
end
Expand All @@ -21,9 +35,11 @@ def validate_login_credentials(username, password)
authentication_result
end

def authenticators
@authenticators ||= begin
CASino.config[:authenticators].each do |name, auth|
def authenticators(type)
@authenticators ||= {}
return @authenticators[type] if @authenticators.has_key?(type)
@authenticators[type] = begin
CASino.config[type].each do |name, auth|
next unless auth.is_a?(Hash)

authenticator = if auth[:class]
Expand All @@ -32,7 +48,7 @@ def authenticators
load_authenticator(auth[:authenticator])
end

CASino.config[:authenticators][name] = authenticator.new(auth[:options])
CASino.config[type][name] = authenticator.new(auth[:options])
end
end
end
Expand Down
15 changes: 15 additions & 0 deletions app/views/casino/sessions/_external.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<% if @external_authenticators.any? %>
<ul id="external_list">
<% @external_authenticators.each do |authenticator_name, authenticator| %>
<% unless authenticator.view.nil? %>
<li>
<%= form_tag(login_path, method: :post) do %>
<%= hidden_field_tag :lt, @login_ticket.ticket %>
<%= hidden_field_tag :external, authenticator_name %>
<%= render authenticator.view, :authenticator => authenticator %>
<% end %>
</li>
<% end %>
<% end %>
</ul>
<% end%>
1 change: 1 addition & 0 deletions app/views/casino/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<% end %>
<%= button_tag t('login.label_button'), :class => 'button' %>
<% end %>
<%= render 'external' %>
</div>
</div>
<%= render 'footer' %>
Expand Down
3 changes: 2 additions & 1 deletion lib/casino.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module CASino

defaults = {
authenticators: HashWithIndifferentAccess.new,
external_authenticators: HashWithIndifferentAccess.new,
logger: Rails.logger,
frontend: HashWithIndifferentAccess.new(
sso_name: 'CASino',
Expand Down Expand Up @@ -47,4 +48,4 @@ module CASino
}

self.config.merge! defaults.deep_dup
end
end
14 changes: 14 additions & 0 deletions lib/casino/external_authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module CASino
class ExternalAuthenticator
class ExternalAuthenticatorError < StandardError; end

def validate(params, cookies)
raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
end

def view
raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
end

end
end
19 changes: 19 additions & 0 deletions spec/authenticator/base_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'spec_helper'

require 'casino/authenticator'
require 'casino/external_authenticator'

describe CASino::Authenticator do
subject {
Expand All @@ -13,3 +14,21 @@
end
end
end

describe CASino::ExternalAuthenticator do
subject {
CASino::ExternalAuthenticator.new
}

context '#validate' do
it 'raises an error' do
expect { subject.validate(nil, nil) }.to raise_error(NotImplementedError)
end
end

context '#view' do
it 'raises an error' do
expect { subject.view }.to raise_error(NotImplementedError)
end
end
end
12 changes: 9 additions & 3 deletions spec/controllers/listener/login_credential_acceptor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
context "##{method}" do
let(:login_ticket) { Object.new }
let(:flash) { ActionDispatch::Flash::FlashHash.new }
let(:external_authenticators) { { :static => {} } }

before(:each) do
controller.stub(:render)
Expand All @@ -55,16 +56,21 @@

it 'tells the controller to render the new template' do
controller.should_receive(:render).with('new', status: 403)
listener.send(method, login_ticket)
listener.send(method, login_ticket, external_authenticators)
end

it 'assigns a new login ticket' do
listener.send(method, login_ticket)
listener.send(method, login_ticket, external_authenticators)
controller.instance_variable_get(:@login_ticket).should == login_ticket
end

it 'receives external authenticators' do
listener.send(method, login_ticket, external_authenticators)
controller.instance_variable_get(:@external_authenticators).should == external_authenticators
end

it 'should add an error message' do
listener.send(method, login_ticket)
listener.send(method, login_ticket, external_authenticators)
flash[:error].should == I18n.t("login_credential_acceptor.#{method}")
end
end
Expand Down
10 changes: 8 additions & 2 deletions spec/controllers/listener/login_credential_requestor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@
include CASino::Engine.routes.url_helpers
let(:controller) { Struct.new(:cookies).new(cookies: {}) }
let(:listener) { described_class.new(controller) }
let(:external_authenticators) { { :static => {} } }

describe '#user_not_logged_in' do
let(:login_ticket) { Object.new }
it 'assigns the login ticket' do
listener.user_not_logged_in(login_ticket)
listener.user_not_logged_in(login_ticket, external_authenticators)
controller.instance_variable_get(:@login_ticket).should == login_ticket
end

it 'receives external authenticators' do
listener.user_not_logged_in(login_ticket, external_authenticators)
controller.instance_variable_get(:@external_authenticators).should == external_authenticators
end

it 'deletes an existing ticket-granting ticket cookie' do
controller.cookies = { tgt: 'TGT-12345' }
listener.user_not_logged_in(login_ticket)
listener.user_not_logged_in(login_ticket, external_authenticators)
controller.cookies[:tgt].should be_nil
end
end
Expand Down
9 changes: 8 additions & 1 deletion spec/dummy/config/cas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ defaults: &defaults
testuser:
password: "foobar123"
name: "Test User"

external_authenticators:
external_static:
class: "CASino::StaticExternalAuthenticator"
options:
users:
foobar123:
username: "foobar"
name: "Test User"
development:
<<: *defaults

Expand Down
Loading