uchouhan-rubycas-server 1.0.a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/CHANGELOG +289 -0
  2. data/LICENSE +26 -0
  3. data/README.md +19 -0
  4. data/Rakefile +1 -0
  5. data/bin/rubycas-server +16 -0
  6. data/bin/rubycas-server-ctl +9 -0
  7. data/lib/casserver.rb +13 -0
  8. data/lib/casserver/authenticators/active_directory_ldap.rb +19 -0
  9. data/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb +43 -0
  10. data/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb +92 -0
  11. data/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb +34 -0
  12. data/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb +59 -0
  13. data/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb +50 -0
  14. data/lib/casserver/authenticators/base.rb +67 -0
  15. data/lib/casserver/authenticators/client_certificate.rb +47 -0
  16. data/lib/casserver/authenticators/google.rb +58 -0
  17. data/lib/casserver/authenticators/ldap.rb +147 -0
  18. data/lib/casserver/authenticators/ntlm.rb +88 -0
  19. data/lib/casserver/authenticators/open_id.rb +22 -0
  20. data/lib/casserver/authenticators/sql.rb +133 -0
  21. data/lib/casserver/authenticators/sql_authlogic.rb +93 -0
  22. data/lib/casserver/authenticators/sql_encrypted.rb +77 -0
  23. data/lib/casserver/authenticators/sql_md5.rb +19 -0
  24. data/lib/casserver/authenticators/sql_rest_auth.rb +85 -0
  25. data/lib/casserver/authenticators/tacc.rb +67 -0
  26. data/lib/casserver/authenticators/test.rb +21 -0
  27. data/lib/casserver/cas.rb +327 -0
  28. data/lib/casserver/localization.rb +91 -0
  29. data/lib/casserver/model.rb +269 -0
  30. data/lib/casserver/server.rb +623 -0
  31. data/lib/casserver/utils.rb +32 -0
  32. data/lib/casserver/views/_login_form.erb +41 -0
  33. data/lib/casserver/views/layout.erb +17 -0
  34. data/lib/casserver/views/login.erb +29 -0
  35. data/lib/casserver/views/proxy.builder +11 -0
  36. data/lib/casserver/views/proxy_validate.builder +26 -0
  37. data/lib/casserver/views/service_validate.builder +19 -0
  38. data/lib/casserver/views/validate.erb +1 -0
  39. data/po/de_DE/rubycas-server.po +127 -0
  40. data/po/es_ES/rubycas-server.po +123 -0
  41. data/po/fr_FR/rubycas-server.po +128 -0
  42. data/po/ja_JP/rubycas-server.po +126 -0
  43. data/po/pl_PL/rubycas-server.po +123 -0
  44. data/po/pt_BR/rubycas-server.po +123 -0
  45. data/po/ru_RU/rubycas-server.po +118 -0
  46. data/po/rubycas-server.pot +112 -0
  47. data/po/zh_CN/rubycas-server.po +113 -0
  48. data/po/zh_TW/rubycas-server.po +113 -0
  49. data/public/themes/cas.css +121 -0
  50. data/public/themes/notice.png +0 -0
  51. data/public/themes/ok.png +0 -0
  52. data/public/themes/simple/bg.png +0 -0
  53. data/public/themes/simple/favicon.png +0 -0
  54. data/public/themes/simple/login_box_bg.png +0 -0
  55. data/public/themes/simple/logo.png +0 -0
  56. data/public/themes/simple/theme.css +28 -0
  57. data/public/themes/tadnet/bg.png +0 -0
  58. data/public/themes/tadnet/button.png +0 -0
  59. data/public/themes/tadnet/favicon.png +0 -0
  60. data/public/themes/tadnet/login_box_bg.png +0 -0
  61. data/public/themes/tadnet/logo.png +0 -0
  62. data/public/themes/tadnet/theme.css +55 -0
  63. data/public/themes/urbacon/bg.png +0 -0
  64. data/public/themes/urbacon/login_box_bg.png +0 -0
  65. data/public/themes/urbacon/logo.png +0 -0
  66. data/public/themes/urbacon/theme.css +33 -0
  67. data/public/themes/warning.png +0 -0
  68. data/resources/config.example.yml +574 -0
  69. data/resources/config.ru +42 -0
  70. data/resources/custom_views.example.rb +11 -0
  71. data/resources/init.d.sh +58 -0
  72. data/rubycas-server.gemspec +40 -0
  73. data/setup.rb +1585 -0
  74. data/spec/alt_config.yml +46 -0
  75. data/spec/casserver_spec.rb +114 -0
  76. data/spec/default_config.yml +46 -0
  77. data/spec/spec.opts +4 -0
  78. data/spec/spec_helper.rb +89 -0
  79. data/tasks/bundler.rake +4 -0
  80. data/tasks/db/migrate.rake +12 -0
  81. data/tasks/localization.rake +13 -0
  82. data/tasks/spec.rake +10 -0
  83. metadata +172 -0
@@ -0,0 +1,92 @@
1
+ begin
2
+ require "bcrypt"
3
+ rescue LoadError
4
+ end
5
+
6
+ module Authlogic
7
+ module CryptoProviders
8
+ # For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
9
+ # secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
10
+ # password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
11
+ # of the Sha algorithms. I did some benchmarking to save you some time with your decision:
12
+ #
13
+ # require "bcrypt"
14
+ # require "digest"
15
+ # require "benchmark"
16
+ #
17
+ # Benchmark.bm(18) do |x|
18
+ # x.report("BCrypt (cost = 10:") { 100.times { BCrypt::Password.create("mypass", :cost => 10) } }
19
+ # x.report("BCrypt (cost = 2:") { 100.times { BCrypt::Password.create("mypass", :cost => 2) } }
20
+ # x.report("Sha512:") { 100.times { Digest::SHA512.hexdigest("mypass") } }
21
+ # x.report("Sha1:") { 100.times { Digest::SHA1.hexdigest("mypass") } }
22
+ # end
23
+ #
24
+ # user system total real
25
+ # BCrypt (cost = 10): 10.780000 0.060000 10.840000 ( 11.100289)
26
+ # BCrypt (cost = 2): 0.180000 0.000000 0.180000 ( 0.181914)
27
+ # Sha512: 0.000000 0.000000 0.000000 ( 0.000829)
28
+ # Sha1: 0.000000 0.000000 0.000000 ( 0.000395)
29
+ #
30
+ # You can play around with the cost to get that perfect balance between performance and security.
31
+ #
32
+ # Decided BCrypt is for you? Just insall the bcrypt gem:
33
+ #
34
+ # gem install bcrypt-ruby
35
+ #
36
+ # Tell acts_as_authentic to use it:
37
+ #
38
+ # acts_as_authentic do |c|
39
+ # c.crypto_provider = Authlogic::CryptoProviders::BCrypt
40
+ # end
41
+ #
42
+ # You are good to go!
43
+ class BCrypt
44
+ class << self
45
+ # This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
46
+ # Set this to whatever you want, play around with it to get that perfect balance between security and performance.
47
+ def cost
48
+ @cost ||= 10
49
+ end
50
+ attr_writer :cost
51
+
52
+ # Creates a BCrypt hash for the password passed.
53
+ def encrypt(*tokens)
54
+ ::BCrypt::Password.create(join_tokens(tokens), :cost => cost)
55
+ end
56
+
57
+ # Does the hash match the tokens? Uses the same tokens that were used to encrypt.
58
+ def matches?(hash, *tokens)
59
+ $LOG.debug hash
60
+ $LOG.debug tokens.inspect
61
+
62
+ hash = new_from_hash(hash)
63
+ return false if hash.blank?
64
+ hash == join_tokens(tokens)
65
+ end
66
+
67
+ # This method is used as a flag to tell Authlogic to "resave" the password upon a successful login, using the new cost
68
+ def cost_matches?(hash)
69
+ hash = new_from_hash(hash)
70
+ if hash.blank?
71
+ false
72
+ else
73
+ hash.cost == cost
74
+ end
75
+ end
76
+
77
+ private
78
+ def join_tokens(tokens)
79
+ tokens.flatten.join
80
+ end
81
+
82
+ def new_from_hash(hash)
83
+ begin
84
+ ::BCrypt::Password.new(hash)
85
+ rescue ::BCrypt::Errors::InvalidHash
86
+ return nil
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,34 @@
1
+ require "digest/md5"
2
+
3
+ module Authlogic
4
+ module CryptoProviders
5
+ # This class was made for the users transitioning from md5 based systems.
6
+ # I highly discourage using this crypto provider as it superbly inferior
7
+ # to your other options.
8
+ #
9
+ # Please use any other provider offered by Authlogic.
10
+ class MD5
11
+ class << self
12
+ attr_accessor :join_token
13
+
14
+ # The number of times to loop through the encryption.
15
+ def stretches
16
+ @stretches ||= 1
17
+ end
18
+ attr_writer :stretches
19
+
20
+ # Turns your raw password into a MD5 hash.
21
+ def encrypt(*tokens)
22
+ digest = tokens.flatten.join(join_token)
23
+ stretches.times { digest = Digest::MD5.hexdigest(digest) }
24
+ digest
25
+ end
26
+
27
+ # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
28
+ def matches?(crypted, *tokens)
29
+ encrypt(*tokens) == crypted
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ require "digest/sha1"
2
+
3
+ module Authlogic
4
+ module CryptoProviders
5
+ # This class was made for the users transitioning from restful_authentication.
6
+ # I highly discourage using this crypto provider as it inferior to your other options.
7
+ # Please use any other provider offered by Authlogic.
8
+ class Sha1
9
+ class << self
10
+ def join_token
11
+ @join_token ||= "--"
12
+ end
13
+ attr_writer :join_token
14
+
15
+ def digest_format=(format)
16
+ @digest_format = format
17
+ end
18
+
19
+ # This is for "old style" authentication with a custom format of digest
20
+ def digest(tokens)
21
+ if @digest_format
22
+ @digest_format.
23
+ gsub('PASSWORD', tokens.first).
24
+ gsub('SALT', tokens.last)
25
+ else
26
+ tokens.join(join_token)
27
+ end
28
+ end
29
+
30
+ # The number of times to loop through the encryption.
31
+ # This is ten because that is what restful_authentication defaults to.
32
+
33
+ def stretches
34
+ @stretches ||= 10
35
+ end
36
+ attr_writer :stretches
37
+
38
+ # Turns your raw password into a Sha1 hash.
39
+ def encrypt(*tokens)
40
+ tokens = tokens.flatten
41
+
42
+ if stretches > 1
43
+ hash = tokens.shift
44
+ stretches.times { hash = Digest::SHA1.hexdigest([hash, *tokens].join(join_token)) }
45
+ else
46
+ hash = Digest::SHA1.hexdigest( digest(tokens) )
47
+ end
48
+
49
+ hash
50
+ end
51
+
52
+ # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
53
+ def matches?(crypted, *tokens)
54
+ encrypt(*tokens) == crypted
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ require "digest/sha2"
2
+
3
+ module Authlogic
4
+ # The acts_as_authentic method has a crypto_provider option. This allows you to use any type of encryption you like.
5
+ # Just create a class with a class level encrypt and matches? method. See example below.
6
+ #
7
+ # === Example
8
+ #
9
+ # class MyAwesomeEncryptionMethod
10
+ # def self.encrypt(*tokens)
11
+ # # the tokens passed will be an array of objects, what type of object is irrelevant,
12
+ # # just do what you need to do with them and return a single encrypted string.
13
+ # # for example, you will most likely join all of the objects into a single string and then encrypt that string
14
+ # end
15
+ #
16
+ # def self.matches?(crypted, *tokens)
17
+ # # return true if the crypted string matches the tokens.
18
+ # # depending on your algorithm you might decrypt the string then compare it to the token, or you might
19
+ # # encrypt the tokens and make sure it matches the crypted string, its up to you
20
+ # end
21
+ # end
22
+ module CryptoProviders
23
+ # = Sha512
24
+ #
25
+ # Uses the Sha512 hash algorithm to encrypt passwords.
26
+ class Sha512
27
+ class << self
28
+ attr_accessor :join_token
29
+
30
+ # The number of times to loop through the encryption. This is ten because that is what restful_authentication defaults to.
31
+ def stretches
32
+ @stretches ||= 20
33
+ end
34
+ attr_writer :stretches
35
+
36
+ # Turns your raw password into a Sha512 hash.
37
+ def encrypt(*tokens)
38
+ digest = tokens.flatten.join(join_token)
39
+ stretches.times { digest = Digest::SHA512.hexdigest(digest) }
40
+ digest
41
+ end
42
+
43
+ # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
44
+ def matches?(crypted, *tokens)
45
+ encrypt(*tokens) == crypted
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,67 @@
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
+ # This is called at server startup.
9
+ # Any class-wide initializiation for the authenticator should be done here.
10
+ # (e.g. establish database connection).
11
+ # You can leave this empty if you don't need to set up anything.
12
+ def self.setup(options)
13
+ end
14
+
15
+ # This is called prior to #validate (i.e. each time the user tries to log in).
16
+ # Any per-instance initialization for the authenticator should be done here.
17
+ #
18
+ # By default this makes the authenticator options hash available for #validate
19
+ # under @options and initializes @extra_attributes to an empty hash.
20
+ def configure(options)
21
+ raise ArgumentError, "options must be a HashWithIndifferentAccess" unless options.kind_of? HashWithIndifferentAccess
22
+ @options = options.dup
23
+ @extra_attributes = {}
24
+ end
25
+
26
+ # Override this to implement your authentication credential validation.
27
+ # This is called each time the user tries to log in. The credentials hash
28
+ # holds the credentials as entered by the user (generally under :username
29
+ # and :password keys; :service and :request are also included by default)
30
+ #
31
+ # Note that the standard credentials can be read in to instance variables
32
+ # by calling #read_standard_credentials.
33
+ def validate(credentials)
34
+ raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
35
+ end
36
+
37
+ def extra_attributes
38
+ @extra_attributes
39
+ end
40
+
41
+ protected
42
+ def read_standard_credentials(credentials)
43
+ @username = credentials[:username]
44
+ @password = credentials[:password]
45
+ @service = credentials[:service]
46
+ @request = credentials[:request]
47
+ end
48
+
49
+ def extra_attributes_to_extract
50
+ if @options[:extra_attributes].kind_of? Array
51
+ attrs = @options[:extra_attributes]
52
+ elsif @options[:extra_attributes].kind_of? String
53
+ attrs = @options[:extra_attributes].split(',').collect{|col| col.strip}
54
+ else
55
+ $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.")
56
+ attrs = []
57
+ end
58
+
59
+ $LOG.debug("#{self.class.name} will try to extract the following extra_attributes: #{attrs.inspect}")
60
+ return attrs
61
+ end
62
+ end
63
+ end
64
+
65
+ class AuthenticatorError < Exception
66
+ end
67
+ end
@@ -0,0 +1,47 @@
1
+ require 'casserver/authenticators/base'
2
+
3
+ # NOT YET IMPLEMENTED
4
+ #
5
+ # This authenticator will authenticate the user based on a client SSL certificate.
6
+ #
7
+ # You will probably want to use this along with another authenticator, chaining
8
+ # it so that if the client does not provide a certificate, the server can
9
+ # fall back to some other authentication mechanism.
10
+ #
11
+ # Here's an example of how to use two chained authenticators in the config.yml
12
+ # file. The server will first use the ClientCertificate authenticator, and
13
+ # only fall back to the SQL authenticator of the first one fails:
14
+ #
15
+ # authenticator:
16
+ # -
17
+ # class: CASServer::Authenticators::ClientCertificate
18
+ # -
19
+ # class: CASServer::Authenticators::SQL
20
+ # database:
21
+ # adapter: mysql
22
+ # database: some_database_with_users_table
23
+ # user: root
24
+ # password:
25
+ # server: localhost
26
+ # user_table: user
27
+ # username_column: username
28
+ # password_column: password
29
+ #
30
+ class CASServer::Authenticators::ClientCertificate < CASServer::Authenticators::Base
31
+ def validate(credentials)
32
+ read_standard_credentials(credentials)
33
+
34
+ @client_cert = credentials[:request]['SSL_CLIENT_CERT']
35
+
36
+ # note that I haven't actually tested to see if SSL_CLIENT_CERT gets
37
+ # filled with data when a client cert is provided, but this should be
38
+ # the case at least in theory :)
39
+
40
+ return false if @client_cert.blank?
41
+
42
+ # IMPLEMENT SSL CERTIFICATE VALIDATION CODE HERE
43
+ raise NotImplementedError, "#{self.class.name}#validate NOT YET IMPLEMENTED!"
44
+
45
+ return true # if SSL certificate is valid, false otherwise
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ require 'casserver/authenticators/base'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'timeout'
6
+
7
+ # Validates Google accounts against Google's authentication service -- in other
8
+ # words, this authenticator allows users to log in to CAS using their
9
+ # Gmail/Google accounts.
10
+ class CASServer::Authenticators::Google < CASServer::Authenticators::Base
11
+ def validate(credentials)
12
+ read_standard_credentials(credentials)
13
+
14
+ return false if @username.blank? || @password.blank?
15
+
16
+ auth_data = {
17
+ 'Email' => @username,
18
+ 'Passwd' => @password,
19
+ 'service' => 'xapi',
20
+ 'source' => 'RubyCAS-Server',
21
+ 'accountType' => 'HOSTED_OR_GOOGLE'
22
+ }
23
+
24
+ url = URI.parse('https://www.google.com/accounts/ClientLogin')
25
+ if $CONF.proxy_host
26
+ http = Net::HTTP.Proxy($CONF.proxy_host, $CONF.proxy_port, $CONF.proxy_username, $CONF.proxy_password).new(url.host, url.port)
27
+ else
28
+ http = Net::HTTP.new(url.host, url.port)
29
+ end
30
+ http.use_ssl = true
31
+
32
+ # TODO: make the timeout configurable
33
+ wait_seconds = 10
34
+ begin
35
+ timeout(wait_seconds) do
36
+ res = http.start do |conn|
37
+ req = Net::HTTP::Post.new(url.path)
38
+ req.set_form_data(auth_data,'&')
39
+ conn.request(req)
40
+ end
41
+
42
+ case res
43
+ when Net::HTTPSuccess
44
+ true
45
+ when Net::HTTPForbidden
46
+ false
47
+ else
48
+ $LOG.error("Unexpected response from Google while validating credentials: #{res.inspect} ==> #{res.body}.")
49
+ raise CASServer::AuthenticatorError, "Unexpected response received from Google while validating credentials."
50
+ end
51
+ end
52
+ rescue Timeout::Error
53
+ $LOG.error("Google did not respond to the credential validation request. We waited for #{wait_seconds.inspect} seconds before giving up.")
54
+ raise CASServer::AuthenticatorError, "Timeout while waiting for Google to validate credentials."
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,147 @@
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 LDAP authenticator configuration!" unless @options[:ldap]
32
+ raise CASServer::AuthenticatorError, "You must specify a server host in the LDAP configuration!" unless @options[:ldap][:host] || @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
+
40
+
41
+ @options[:ldap][:host] ||= @options[:ldap][:server]
42
+ @ldap.host = @options[:ldap][:host]
43
+ @ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
44
+ @ldap.encryption(@options[:ldap][:encryption].intern) if @options[:ldap][:encryption]
45
+
46
+ begin
47
+ if @options[:ldap][:auth_user]
48
+ bind_success = bind_by_username_with_preauthentication
49
+ else
50
+ bind_success = bind_by_username
51
+ end
52
+
53
+ return false unless bind_success
54
+
55
+ entry = find_user
56
+ extract_extra_attributes(entry)
57
+
58
+ return true
59
+ rescue Net::LDAP::LdapError => e
60
+ raise CASServer::AuthenticatorError,
61
+ "LDAP authentication failed with '#{e}'. Check your authenticator configuration."
62
+ end
63
+ end
64
+
65
+ protected
66
+ def default_username_attribute
67
+ "cn"
68
+ end
69
+
70
+ private
71
+ # Add prefix to username, if :username_prefix was specified in the :ldap config.
72
+ def preprocess_username
73
+ @username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix]
74
+ end
75
+
76
+ # Attempt to bind with the LDAP server using the username and password entered by
77
+ # the user. If a :filter was specified in the :ldap config, the filter will be
78
+ # added to the LDAP query for the username.
79
+ def bind_by_username
80
+ username_attribute = options[:ldap][:username_attribute] || default_username_attribute
81
+
82
+ @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter)
83
+ end
84
+
85
+ # If an auth_user is specified, we will connect ("pre-authenticate") with the
86
+ # LDAP server using the authenticator account, and then attempt to bind as the
87
+ # user who is actually trying to authenticate. Note that you need to set up
88
+ # the special authenticator account first. Also, auth_user must be the authenticator
89
+ # user's full CN, which is probably not the same as their username.
90
+ #
91
+ # This pre-authentication process is necessary because binding can only be done
92
+ # using the CN, so having just the username is not enough. We connect as auth_user,
93
+ # and then try to find the target user's CN based on the given username. Then we bind
94
+ # as the target user to validate their credentials.
95
+ def bind_by_username_with_preauthentication
96
+ raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless
97
+ @options[:ldap][:auth_password]
98
+
99
+ @ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
100
+
101
+ @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter)
102
+ end
103
+
104
+ # Combine the filter for finding the user with the optional extra filter specified in the config
105
+ # (if any).
106
+ def user_filter
107
+ username_attribute = options[:ldap][:username_attribute] || default_username_attribute
108
+
109
+ filter = Array(username_attribute).map { |ua| Net::LDAP::Filter.eq(ua, @username) }.reduce(:|)
110
+ unless @options[:ldap][:filter].blank?
111
+ filter &= Net::LDAP::Filter.construct(@options[:ldap][:filter])
112
+ end
113
+
114
+ filter
115
+ end
116
+
117
+ # Finds the user based on the user_filter (this is called after authentication).
118
+ # We do this to make it possible to extract extra_attributes.
119
+ def find_user
120
+ results = @ldap.search( :base => options[:ldap][:base], :filter => user_filter)
121
+ return results.first
122
+ end
123
+
124
+ def extract_extra_attributes(ldap_entry)
125
+ @extra_attributes = {}
126
+ extra_attributes_to_extract.each do |attr|
127
+ v = !ldap_entry[attr].blank? && ldap_entry[attr].first
128
+ if v
129
+ if ldap_entry[attr].kind_of?(Array)
130
+ @extra_attributes[attr] = []
131
+ ldap_entry[attr].each do |a|
132
+ @extra_attributes[attr] << a
133
+ end
134
+ else
135
+ @extra_attributes[attr] = v.to_s
136
+ end
137
+ end
138
+ end
139
+
140
+ if @extra_attributes.empty?
141
+ $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
142
+ else
143
+ $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
144
+ end
145
+ ldap_entry
146
+ end
147
+ end