relevance-rubycas-server 0.6.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.loadpath +5 -0
- data/.project +17 -0
- data/CHANGELOG.txt +1 -0
- data/History.txt +223 -0
- data/LICENSE.txt +504 -0
- data/Manifest.txt +61 -0
- data/README.txt +25 -0
- data/Rakefile +60 -0
- data/bin/rubycas-server +26 -0
- data/bin/rubycas-server-ctl +22 -0
- data/config.example.yml +363 -0
- data/custom_views.example.rb +11 -0
- data/lib/casserver.rb +110 -0
- data/lib/casserver/authenticators/active_directory_ldap.rb +11 -0
- data/lib/casserver/authenticators/base.rb +47 -0
- data/lib/casserver/authenticators/ldap.rb +108 -0
- data/lib/casserver/authenticators/ntlm.rb +88 -0
- data/lib/casserver/authenticators/sql.rb +102 -0
- data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
- data/lib/casserver/authenticators/test.rb +15 -0
- data/lib/casserver/cas.rb +307 -0
- data/lib/casserver/conf.rb +112 -0
- data/lib/casserver/controllers.rb +436 -0
- data/lib/casserver/environment.rb +23 -0
- data/lib/casserver/models.rb +218 -0
- data/lib/casserver/postambles.rb +174 -0
- data/lib/casserver/utils.rb +30 -0
- data/lib/casserver/version.rb +9 -0
- data/lib/casserver/views.rb +235 -0
- data/lib/rubycas-server.rb +1 -0
- data/lib/rubycas-server/version.rb +1 -0
- data/lib/themes/cas.css +121 -0
- data/lib/themes/notice.png +0 -0
- data/lib/themes/ok.png +0 -0
- data/lib/themes/simple/bg.png +0 -0
- data/lib/themes/simple/login_box_bg.png +0 -0
- data/lib/themes/simple/logo.png +0 -0
- data/lib/themes/simple/theme.css +28 -0
- data/lib/themes/urbacon/bg.png +0 -0
- data/lib/themes/urbacon/login_box_bg.png +0 -0
- data/lib/themes/urbacon/logo.png +0 -0
- data/lib/themes/urbacon/theme.css +33 -0
- data/lib/themes/warning.png +0 -0
- data/misc/basic_cas_single_signon_mechanism_diagram.png +0 -0
- data/misc/basic_cas_single_signon_mechanism_diagram.svg +652 -0
- data/resources/init.d.sh +58 -0
- data/setup.rb +1585 -0
- data/test/test_cas.rb +33 -0
- data/test/test_casserver.rb +125 -0
- data/vendor/isaac_0.9.1/LICENSE +26 -0
- data/vendor/isaac_0.9.1/README +78 -0
- data/vendor/isaac_0.9.1/TODO +3 -0
- data/vendor/isaac_0.9.1/VERSIONS +3 -0
- data/vendor/isaac_0.9.1/crypt/ISAAC.rb +171 -0
- data/vendor/isaac_0.9.1/isaac.gemspec +39 -0
- data/vendor/isaac_0.9.1/setup.rb +596 -0
- data/vendor/isaac_0.9.1/test/TC_ISAAC.rb +76 -0
- metadata +158 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
# Custom views file; add methods to the module definition below
|
2
|
+
|
3
|
+
module CASServer::Views
|
4
|
+
|
5
|
+
# Override views here, for example, a custom login form:
|
6
|
+
def login_form
|
7
|
+
# Add your custom login form here, using Markaby
|
8
|
+
# See the original views.rb file at lib/casserver/views.rb for method names and usage
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
data/lib/casserver.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
$: << File.dirname(File.expand_path(__FILE__))
|
2
|
+
require 'casserver/environment'
|
3
|
+
|
4
|
+
$APP_PATH ||= File.dirname(File.expand_path(__FILE__))
|
5
|
+
|
6
|
+
# change to current directory when invoked on its own
|
7
|
+
Dir.chdir($APP_PATH) if __FILE__ == $0
|
8
|
+
|
9
|
+
$: << $APP_PATH + "/../vendor/isaac_0.9.1"
|
10
|
+
require 'crypt/ISAAC'
|
11
|
+
|
12
|
+
require 'active_support'
|
13
|
+
require 'yaml'
|
14
|
+
|
15
|
+
|
16
|
+
# Camping.goes must be called after the authenticator class is loaded, otherwise weird things happen
|
17
|
+
Camping.goes :CASServer
|
18
|
+
|
19
|
+
$CONFIG_FILE ||= '/etc/rubycas-server/config.yml'
|
20
|
+
|
21
|
+
# for some reason this makes JRuby happy
|
22
|
+
class CASServer::Models::Base
|
23
|
+
end
|
24
|
+
|
25
|
+
CASServer.picnic!
|
26
|
+
|
27
|
+
$CONF[:expire_sessions] ||= false
|
28
|
+
$CONF[:login_ticket_expiry] ||= 5.minutes
|
29
|
+
$CONF[:service_ticket_expiry] ||= 5.minutes # CAS Protocol Spec, sec. 3.2.1 (recommended expiry time)
|
30
|
+
$CONF[:proxy_granting_ticket_expiry] ||= 48.hours
|
31
|
+
$CONF[:ticket_granting_ticket_expiry] ||= 48.hours
|
32
|
+
$CONF[:log] ||= {:file => 'casserver.log', :level => 'DEBUG'}
|
33
|
+
$CONF[:uri_path] ||= "/"
|
34
|
+
|
35
|
+
unless $CONF[:authenticator]
|
36
|
+
$stderr.puts
|
37
|
+
$stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
38
|
+
$stderr.puts
|
39
|
+
$stderr.puts "You have not yet defined an authenticator for your CAS server!"
|
40
|
+
$stderr.puts "Please consult your config file at #{$CONFIG_FILE.inspect} for details."
|
41
|
+
$stderr.puts
|
42
|
+
$stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'casserver/utils'
|
47
|
+
require 'casserver/models'
|
48
|
+
require 'casserver/cas'
|
49
|
+
require 'casserver/views'
|
50
|
+
require 'casserver/controllers'
|
51
|
+
|
52
|
+
if $CONF[:authenticator].instance_of? Array
|
53
|
+
$CONF[:authenticator].each_index do |auth_index|
|
54
|
+
$CONF[:authenticator][auth_index] = HashWithIndifferentAccess.new($CONF[:authenticator][auth_index])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
$AUTH = []
|
59
|
+
begin
|
60
|
+
# attempt to instantiate the authenticator
|
61
|
+
if $CONF[:authenticator].instance_of? Array
|
62
|
+
$CONF[:authenticator].each { |authenticator| $AUTH << authenticator[:class].constantize.new}
|
63
|
+
else
|
64
|
+
$AUTH << $CONF[:authenticator][:class].constantize.new
|
65
|
+
end
|
66
|
+
rescue NameError
|
67
|
+
if $CONF[:authenticator].instance_of? Array
|
68
|
+
$CONF[:authenticator].each do |authenticator|
|
69
|
+
if !authenticator[:source].nil?
|
70
|
+
# config.yml explicitly names source file
|
71
|
+
require authenticator[:source]
|
72
|
+
else
|
73
|
+
# the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
|
74
|
+
auth_rb = authenticator[:class].underscore.gsub('cas_server/', '')
|
75
|
+
require 'casserver/'+auth_rb
|
76
|
+
end
|
77
|
+
$AUTH << authenticator[:class].constantize.new
|
78
|
+
end
|
79
|
+
else
|
80
|
+
if !$CONF[:authenticator][:source].nil?
|
81
|
+
# config.yml explicitly names source file
|
82
|
+
require $CONF[:authenticator][:source]
|
83
|
+
else
|
84
|
+
# the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
|
85
|
+
auth_rb = $CONF[:authenticator][:class].underscore.gsub('cas_server/', '')
|
86
|
+
require 'casserver/'+auth_rb
|
87
|
+
end
|
88
|
+
|
89
|
+
$AUTH << $CONF[:authenticator][:class].constantize.new
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
$CONF[:public_dir] = {
|
94
|
+
:path => "/themes",
|
95
|
+
:dir => File.expand_path(File.dirname(__FILE__))+"/themes"
|
96
|
+
}
|
97
|
+
|
98
|
+
def CASServer.create
|
99
|
+
$LOG.info "Creating RubyCAS-Server..."
|
100
|
+
CASServer::Models::Base.establish_connection(CASServer::Conf.database)
|
101
|
+
CASServer::Models.create_schema
|
102
|
+
|
103
|
+
CASServer::Models::ServiceTicket.cleanup_expired(CASServer::Conf.service_ticket_expiry)
|
104
|
+
CASServer::Models::LoginTicket.cleanup_expired(CASServer::Conf.login_ticket_expiry)
|
105
|
+
CASServer::Models::ProxyGrantingTicket.cleanup_expired(CASServer::Conf.proxy_granting_ticket_expiry)
|
106
|
+
CASServer::Models::TicketGrantingTicket.cleanup_expired(CASServer::Conf.ticket_granting_ticket_expiry)
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
CASServer.start_picnic
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'casserver/authenticators/ldap'
|
2
|
+
|
3
|
+
# Slightly modified version of the LDAP authenticator for Microsoft's ActiveDirectory.
|
4
|
+
# The only difference is that the default_username_attribute for AD is 'sAMAccountName'
|
5
|
+
# rather than 'uid'.
|
6
|
+
class CASServer::Authenticators::ActiveDirectoryLDAP < CASServer::Authenticators::LDAP
|
7
|
+
protected
|
8
|
+
def default_username_attribute
|
9
|
+
"sAMAccountName"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CASServer
|
2
|
+
module Authenticators
|
3
|
+
class Base
|
4
|
+
attr_accessor :options
|
5
|
+
attr_reader :username # make this accessible so that we can pick up any
|
6
|
+
# transformations done within the authenticator
|
7
|
+
|
8
|
+
def validate(credentials)
|
9
|
+
raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure(options)
|
13
|
+
raise ArgumentError, "options must be a HashWithIndifferentAccess" unless options.kind_of? HashWithIndifferentAccess
|
14
|
+
@options = options.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def extra_attributes
|
18
|
+
@extra_attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def read_standard_credentials(credentials)
|
23
|
+
@username = credentials[:username]
|
24
|
+
@password = credentials[:password]
|
25
|
+
@service = credentials[:service]
|
26
|
+
@request = credentials[:request]
|
27
|
+
end
|
28
|
+
|
29
|
+
def extra_attributes_to_extract
|
30
|
+
if @options[:extra_attributes].kind_of? Array
|
31
|
+
attrs = @options[:extra_attributes]
|
32
|
+
elsif @options[:extra_attributes].kind_of? String
|
33
|
+
attrs = @options[:extra_attributes].split(',').collect{|col| col.strip}
|
34
|
+
else
|
35
|
+
$LOG.error("Can't figure out attribute list from #{@options[:extra_attributes].inspect}. This must be an Aarray of column names or a comma-separated list.")
|
36
|
+
attrs = []
|
37
|
+
end
|
38
|
+
|
39
|
+
$LOG.debug("#{self.class.name} will try to extract the following extra_attributes: #{attrs.inspect}")
|
40
|
+
return attrs
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class AuthenticatorError < Exception
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'casserver/authenticators/base'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'net/ldap'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
begin
|
8
|
+
gem 'ruby-net-ldap', '~> 0.0.4'
|
9
|
+
rescue Gem::LoadError
|
10
|
+
$stderr.puts
|
11
|
+
$stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
12
|
+
$stderr.puts
|
13
|
+
$stderr.puts "To use the LDAP/AD authenticator, you must first install the 'ruby-net-ldap' gem."
|
14
|
+
$stderr.puts
|
15
|
+
$stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
require 'net/ldap'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Basic LDAP authenticator. Should be compatible with OpenLDAP and other similar LDAP servers,
|
22
|
+
# although it hasn't been officially tested. See example config file for details on how
|
23
|
+
# to configure it.
|
24
|
+
class CASServer::Authenticators::LDAP < CASServer::Authenticators::Base
|
25
|
+
def validate(credentials)
|
26
|
+
read_standard_credentials(credentials)
|
27
|
+
|
28
|
+
return false if @password.blank?
|
29
|
+
|
30
|
+
raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options
|
31
|
+
raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless @options[:ldap]
|
32
|
+
raise CASServer::AuthenticatorError, "You must specify an ldap server in the configuration!" unless @options[:ldap][:server]
|
33
|
+
|
34
|
+
raise CASServer::AuthenticatorError, "The username '#{@username}' contains invalid characters." if (@username =~ /[*\(\)\0\/]/)
|
35
|
+
|
36
|
+
preprocess_username
|
37
|
+
|
38
|
+
@ldap = Net::LDAP.new
|
39
|
+
@ldap.host = @options[:ldap][:server]
|
40
|
+
@ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
|
41
|
+
|
42
|
+
begin
|
43
|
+
if @options[:ldap][:auth_user]
|
44
|
+
bind_with_preauthentication
|
45
|
+
else
|
46
|
+
bind_directly
|
47
|
+
end
|
48
|
+
rescue Net::LDAP::LdapError => e
|
49
|
+
raise CASServer::AuthenticatorError,
|
50
|
+
"LDAP authentication failed with '#{e}'. Check your authenticator configuration."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
def default_username_attribute
|
56
|
+
"uid"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def preprocess_username
|
61
|
+
# add prefix to username, if prefix was specified in the config
|
62
|
+
@username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix]
|
63
|
+
end
|
64
|
+
|
65
|
+
def bind_with_preauthentication
|
66
|
+
# If an auth_user is specified, we will connect ("pre-authenticate") to the
|
67
|
+
# LDAP server using the authenticator account, and then attempt to bind as the
|
68
|
+
# user who is actually trying to authenticate. Note that you need to set up
|
69
|
+
# the special authenticator account first. Also, auth_user must be the authenticator
|
70
|
+
# user's full CN, which is probably not the same as their username.
|
71
|
+
#
|
72
|
+
# This pre-authentication process is necessary because binding can only be done
|
73
|
+
# using the CN, so having just the username is not enough. We connect as auth_user,
|
74
|
+
# and then try to find the target user's CN based on the given username. Then we bind
|
75
|
+
# as the target user to validate their credentials.
|
76
|
+
|
77
|
+
raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless
|
78
|
+
@options[:ldap][:auth_password]
|
79
|
+
|
80
|
+
@ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
|
81
|
+
|
82
|
+
username_attribute = options[:ldap][:username_attribute] || default_username_attribute
|
83
|
+
|
84
|
+
filter = Net::LDAP::Filter.construct(@options[:ldap][:filter]) if
|
85
|
+
@options[:ldap][:filter] && !@options[:ldap][:filter].blank?
|
86
|
+
username_filter = Net::LDAP::Filter.eq(username_attribute, @username)
|
87
|
+
if filter
|
88
|
+
filter &= username_filter
|
89
|
+
else
|
90
|
+
filter = username_filter
|
91
|
+
end
|
92
|
+
|
93
|
+
@ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => filter)
|
94
|
+
end
|
95
|
+
|
96
|
+
def bind_directly
|
97
|
+
# When no auth_user is specified, we will try to connect directly as the user
|
98
|
+
# who is trying to authenticate. Note that for this to work, the username must
|
99
|
+
# be equivalent to the user's CN, and this is often not the case (for example,
|
100
|
+
# in Active Directory, the username is the 'sAMAccountName' attribute, while the
|
101
|
+
# user's CN is generally their full name.)
|
102
|
+
|
103
|
+
cn = @username
|
104
|
+
|
105
|
+
@ldap.authenticate(cn, @password)
|
106
|
+
@ldap.bind
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# THIS AUTHENTICATOR DOES NOT WORK (not even close!)
|
2
|
+
#
|
3
|
+
# I started working on this but run into a wall, so I am commiting what I've got
|
4
|
+
# done and leaving it here with hopes of one day finishing it.
|
5
|
+
#
|
6
|
+
# The main problem is that although I've got the Lan Manager/NTLM password hash,
|
7
|
+
# I'm not sure what to do with it. i.e. I need to check it against the AD or SMB
|
8
|
+
# server or something... maybe faking an SMB share connection and using the LM
|
9
|
+
# response for authentication might do the trick?
|
10
|
+
|
11
|
+
require 'casserver/authenticators/base'
|
12
|
+
|
13
|
+
# Ruby/NTLM package from RubyForge
|
14
|
+
require 'net/ntlm'
|
15
|
+
|
16
|
+
module CASServer
|
17
|
+
module Authenticators
|
18
|
+
class NTLM
|
19
|
+
# This will have to be somehow called by the top of the 'get' method
|
20
|
+
# in the Login controller (maybe via a hook?)... if this code fails
|
21
|
+
# then the controller should fall back to some other method of authentication
|
22
|
+
# (probably AD/LDAP or something).
|
23
|
+
def filter_for_top_of_login_get_controller_method
|
24
|
+
$LOG.debug @env.inspect
|
25
|
+
if @env['HTTP_AUTHORIZATION'] =~ /NTLM ([^\s]+)/
|
26
|
+
# if we're here, then the client has sent back a Type1 or Type3 message
|
27
|
+
# in reply to our NTLM challenge or our Type2 message
|
28
|
+
data_raw = Base64.decode64($~[1])
|
29
|
+
$LOG.debug "T1 RAW: #{t1_raw}"
|
30
|
+
t = Net::NTLM::Message::Message.parse(t1_raw)
|
31
|
+
if t.kind_of? Net::NTLM::Type1
|
32
|
+
t1 = t
|
33
|
+
elsif t.kind_of? Net::NTLM::Type3
|
34
|
+
t3 = t
|
35
|
+
else
|
36
|
+
raise "Invalid NTLM reply from client."
|
37
|
+
end
|
38
|
+
|
39
|
+
if t1
|
40
|
+
$LOG.debug "T1: #{t1.inspect}"
|
41
|
+
|
42
|
+
# now put together a Type2 message asking for the client to send
|
43
|
+
# back NTLM credentials (LM hash and such)
|
44
|
+
t2 = Net::NTLM::Message::Type2.new
|
45
|
+
t2.set_flag :UNICODE
|
46
|
+
t2.set_flag :NTLM
|
47
|
+
t2.context = 0x0000000000000000 # this can probably just be left unassigned
|
48
|
+
t2.challenge = 0x0123456789abcdef # this should be a random 8-byte integer
|
49
|
+
|
50
|
+
$LOG.debug "T2: #{t2.inspect}"
|
51
|
+
$LOG.debug "T2: #{t2.serialize}"
|
52
|
+
headers["WWW-Authenticate"] = "NTLM #{t2.encode64}"
|
53
|
+
|
54
|
+
# the client should respond to this with a Type3 message...
|
55
|
+
r('401', '', headers)
|
56
|
+
return
|
57
|
+
else
|
58
|
+
# NOTE: for some reason the server never receives the T3 response, even though monitoring
|
59
|
+
# the HTTP traffic I can see that the client does send it back... there's probably
|
60
|
+
# another bug hiding somewhere here
|
61
|
+
|
62
|
+
lm_response = t3.lm_response
|
63
|
+
ntlm_response = t3.ntlm_response
|
64
|
+
username = t3.user
|
65
|
+
# this is where we run up against a wall... we need some way to check the lm and/or ntlm
|
66
|
+
# reponse against the authentication server (probably Active Directory)... maybe a samba
|
67
|
+
# call would do it?
|
68
|
+
$LOG.debug "T3 LM: #{lm_response.inspect}"
|
69
|
+
$LOG.debug "T3 NTLM: #{ntlm_response.inspect}"
|
70
|
+
|
71
|
+
# assuming the authentication was successful, we'll now need to do something in the
|
72
|
+
# controller acting as if we'd received correct login credentials (i.e. proceed as if
|
73
|
+
# CAS authentication was successful).... if authentication failed, then we should
|
74
|
+
# just fall back to old-school web-based authentication, asking the user to enter
|
75
|
+
# their username and password the normal CAS way
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# this sends the initial NTLM challenge, asking the browser
|
79
|
+
# to send back a Type1 message
|
80
|
+
headers['WWW-Authenticate'] = "NTLM"
|
81
|
+
headers['Connection'] = "Close"
|
82
|
+
r('401', '', headers)
|
83
|
+
return
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'casserver/authenticators/base'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'active_record'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
require 'active_record'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Authenticates against a plain SQL table.
|
11
|
+
#
|
12
|
+
# This assumes that all of your users are stored in a table that has a 'username'
|
13
|
+
# column and a 'password' column. When the user logs in, CAS conects to the
|
14
|
+
# database and looks for a matching username/password in the users table. If a
|
15
|
+
# matching username and password is found, authentication is successful.
|
16
|
+
#
|
17
|
+
# Any database backend supported by ActiveRecord can be used.
|
18
|
+
#
|
19
|
+
# Config example:
|
20
|
+
#
|
21
|
+
# authenticator:
|
22
|
+
# class: CASServer::Authenticators::SQL
|
23
|
+
# database:
|
24
|
+
# adapter: mysql
|
25
|
+
# database: some_database_with_users_table
|
26
|
+
# username: root
|
27
|
+
# password:
|
28
|
+
# server: localhost
|
29
|
+
# user_table: users
|
30
|
+
# username_column: username
|
31
|
+
# password_column: password
|
32
|
+
#
|
33
|
+
# When replying to a CAS client's validation request, the server will normally
|
34
|
+
# provide the client with the authenticated user's username. However it is now
|
35
|
+
# possible for the server to provide the client with additional attributes.
|
36
|
+
# You can configure the SQL authenticator to provide data from additional
|
37
|
+
# columns in the users table by listing the names of the columns under the
|
38
|
+
# 'extra_attributes' option. Note though that this functionality is experimental.
|
39
|
+
# It should work with RubyCAS-Client, but may or may not work with other CAS
|
40
|
+
# clients.
|
41
|
+
#
|
42
|
+
# For example, with this configuration, the 'full_name' and 'access_level'
|
43
|
+
# columns will be provided to your CAS clients along with the username:
|
44
|
+
#
|
45
|
+
# authenticator:
|
46
|
+
# class: CASServer::Authenticators::SQL
|
47
|
+
# database:
|
48
|
+
# adapter: mysql
|
49
|
+
# database: some_database_with_users_table
|
50
|
+
# user_table: users
|
51
|
+
# username_column: username
|
52
|
+
# password_column: password
|
53
|
+
# extra_attributes: full_name, access_level
|
54
|
+
#
|
55
|
+
class CASServer::Authenticators::SQL < CASServer::Authenticators::Base
|
56
|
+
|
57
|
+
def validate(credentials)
|
58
|
+
read_standard_credentials(credentials)
|
59
|
+
|
60
|
+
raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options
|
61
|
+
raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless @options[:database]
|
62
|
+
|
63
|
+
CASUser.establish_connection @options[:database]
|
64
|
+
CASUser.set_table_name @options[:user_table] || "users"
|
65
|
+
|
66
|
+
username_column = @options[:username_column] || 'username'
|
67
|
+
password_column = @options[:password_column] || 'password'
|
68
|
+
|
69
|
+
results = CASUser.find(:all, :conditions => ["#{username_column} = ? AND #{password_column} = ?", @username, @password])
|
70
|
+
|
71
|
+
if results.size > 0
|
72
|
+
$LOG.warn("#{self.class}: Multiple matches found for user #{@username.inspect}") if results.size > 1
|
73
|
+
|
74
|
+
unless @options[:extra_attributes].blank?
|
75
|
+
if results.size > 1
|
76
|
+
$LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
|
77
|
+
else
|
78
|
+
user = results.first
|
79
|
+
|
80
|
+
@extra_attributes = {}
|
81
|
+
extra_attributes_to_extract.each do |col|
|
82
|
+
@extra_attributes[col] = user.send(col)
|
83
|
+
end
|
84
|
+
|
85
|
+
if @extra_attributes.empty?
|
86
|
+
$LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
|
87
|
+
else
|
88
|
+
$LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
return true
|
94
|
+
else
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class CASUser < ActiveRecord::Base
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|