synapse-rubycas-server 1.1.3alpha

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.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG +353 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE +26 -0
  5. data/README.md +38 -0
  6. data/Rakefile +3 -0
  7. data/bin/rubycas-server +30 -0
  8. data/config/config.example.yml +552 -0
  9. data/config/unicorn.rb +88 -0
  10. data/config.ru +11 -0
  11. data/db/migrate/001_create_initial_structure.rb +47 -0
  12. data/db/migrate/002_add_indexes_for_performance.rb +15 -0
  13. data/lib/casserver/authenticators/active_directory_ldap.rb +17 -0
  14. data/lib/casserver/authenticators/active_resource.rb +113 -0
  15. data/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb +43 -0
  16. data/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb +92 -0
  17. data/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb +34 -0
  18. data/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb +59 -0
  19. data/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb +50 -0
  20. data/lib/casserver/authenticators/base.rb +70 -0
  21. data/lib/casserver/authenticators/client_certificate.rb +47 -0
  22. data/lib/casserver/authenticators/google.rb +62 -0
  23. data/lib/casserver/authenticators/ldap.rb +131 -0
  24. data/lib/casserver/authenticators/ntlm.rb +88 -0
  25. data/lib/casserver/authenticators/open_id.rb +19 -0
  26. data/lib/casserver/authenticators/sql.rb +158 -0
  27. data/lib/casserver/authenticators/sql_authlogic.rb +93 -0
  28. data/lib/casserver/authenticators/sql_bcrypt.rb +17 -0
  29. data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
  30. data/lib/casserver/authenticators/sql_md5.rb +19 -0
  31. data/lib/casserver/authenticators/sql_rest_auth.rb +82 -0
  32. data/lib/casserver/authenticators/test.rb +21 -0
  33. data/lib/casserver/base.rb +13 -0
  34. data/lib/casserver/cas.rb +324 -0
  35. data/lib/casserver/core_ext/directory_user.rb +81 -0
  36. data/lib/casserver/core_ext/securerandom.rb +17 -0
  37. data/lib/casserver/core_ext/string.rb +22 -0
  38. data/lib/casserver/core_ext.rb +12 -0
  39. data/lib/casserver/model/consumable.rb +31 -0
  40. data/lib/casserver/model/ticket.rb +19 -0
  41. data/lib/casserver/model.rb +248 -0
  42. data/lib/casserver/server.rb +796 -0
  43. data/lib/casserver/utils.rb +20 -0
  44. data/lib/casserver/views/_login_form.erb +42 -0
  45. data/lib/casserver/views/layout.erb +18 -0
  46. data/lib/casserver/views/login.erb +30 -0
  47. data/lib/casserver/views/proxy.builder +13 -0
  48. data/lib/casserver/views/proxy_validate.builder +31 -0
  49. data/lib/casserver/views/service_validate.builder +24 -0
  50. data/lib/casserver/views/validate.erb +2 -0
  51. data/lib/casserver.rb +19 -0
  52. data/locales/de.yml +27 -0
  53. data/locales/en.yml +26 -0
  54. data/locales/es.yml +26 -0
  55. data/locales/es_ar.yml +26 -0
  56. data/locales/fr.yml +26 -0
  57. data/locales/it.yml +26 -0
  58. data/locales/jp.yml +26 -0
  59. data/locales/pl.yml +26 -0
  60. data/locales/pt.yml +26 -0
  61. data/locales/ru.yml +26 -0
  62. data/locales/zh.yml +26 -0
  63. data/locales/zh_tw.yml +26 -0
  64. data/public/themes/cas.css +126 -0
  65. data/public/themes/notice.png +0 -0
  66. data/public/themes/ok.png +0 -0
  67. data/public/themes/simple/bg.png +0 -0
  68. data/public/themes/simple/favicon.png +0 -0
  69. data/public/themes/simple/login_box_bg.png +0 -0
  70. data/public/themes/simple/logo.png +0 -0
  71. data/public/themes/simple/theme.css +28 -0
  72. data/public/themes/warning.png +0 -0
  73. data/resources/init.d.sh +58 -0
  74. data/spec/casserver/authenticators/active_resource_spec.rb +116 -0
  75. data/spec/casserver/authenticators/ldap_spec.rb +57 -0
  76. data/spec/casserver/cas_spec.rb +148 -0
  77. data/spec/casserver/model_spec.rb +42 -0
  78. data/spec/casserver/utils_spec.rb +24 -0
  79. data/spec/casserver_spec.rb +221 -0
  80. data/spec/config/alt_config.yml +50 -0
  81. data/spec/config/default_config.yml +56 -0
  82. data/spec/core_ext/string_spec.rb +28 -0
  83. data/spec/spec.opts +4 -0
  84. data/spec/spec_helper.rb +126 -0
  85. data/tasks/bundler.rake +4 -0
  86. data/tasks/db/migrate.rake +12 -0
  87. data/tasks/spec.rake +10 -0
  88. metadata +405 -0
@@ -0,0 +1,131 @@
1
+ begin
2
+ require 'net/ldap'
3
+ rescue LoadError
4
+ $stderr.puts "To use the LDAP/AD authenticator, you must first install gems from ldap group. See: Gemfile"
5
+ end
6
+
7
+ # Basic LDAP authenticator. Should be compatible with OpenLDAP and other similar LDAP servers,
8
+ # although it hasn't been officially tested. See example config file for details on how
9
+ # to configure it.
10
+ class CASServer::Authenticators::LDAP < CASServer::Authenticators::Base
11
+ def validate(credentials)
12
+ read_standard_credentials(credentials)
13
+
14
+ return false if @password.blank?
15
+
16
+ raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options
17
+ raise CASServer::AuthenticatorError, "Invalid LDAP authenticator configuration!" unless @options[:ldap]
18
+ raise CASServer::AuthenticatorError, "You must specify a server host in the LDAP configuration!" unless @options[:ldap][:host] || @options[:ldap][:server]
19
+
20
+ raise CASServer::AuthenticatorError, "The username '#{@username}' contains invalid characters." if (@username =~ /[*\(\)\0\/]/)
21
+
22
+ preprocess_username
23
+
24
+ @ldap = Net::LDAP.new
25
+
26
+
27
+ @options[:ldap][:host] ||= @options[:ldap][:server]
28
+ @ldap.host = @options[:ldap][:host]
29
+ @ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
30
+ @ldap.encryption(@options[:ldap][:encryption].intern) if @options[:ldap][:encryption]
31
+
32
+ begin
33
+ if @options[:ldap][:auth_user]
34
+ bind_success = bind_by_username_with_preauthentication
35
+ else
36
+ bind_success = bind_by_username
37
+ end
38
+
39
+ return false unless bind_success
40
+
41
+ entry = find_user
42
+ extract_extra_attributes(entry)
43
+
44
+ return true
45
+ rescue Net::LDAP::LdapError => e
46
+ raise CASServer::AuthenticatorError,
47
+ "LDAP authentication failed with '#{e}'. Check your authenticator configuration."
48
+ end
49
+ end
50
+
51
+ protected
52
+ def default_username_attribute
53
+ "cn"
54
+ end
55
+
56
+ private
57
+ # Add prefix to username, if :username_prefix was specified in the :ldap config.
58
+ def preprocess_username
59
+ @username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix]
60
+ @username = @username + @options[:ldap][:username_postfix] if @options[:ldap][:username_postfix]
61
+ end
62
+
63
+ # Attempt to bind with the LDAP server using the username and password entered by
64
+ # the user. If a :filter was specified in the :ldap config, the filter will be
65
+ # added to the LDAP query for the username.
66
+ def bind_by_username
67
+ @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter)
68
+ end
69
+
70
+ # If an auth_user is specified, we will connect ("pre-authenticate") with the
71
+ # LDAP server using the authenticator account, and then attempt to bind as the
72
+ # user who is actually trying to authenticate. Note that you need to set up
73
+ # the special authenticator account first. Also, auth_user must be the authenticator
74
+ # user's full CN, which is probably not the same as their username.
75
+ #
76
+ # This pre-authentication process is necessary because binding can only be done
77
+ # using the CN, so having just the username is not enough. We connect as auth_user,
78
+ # and then try to find the target user's CN based on the given username. Then we bind
79
+ # as the target user to validate their credentials.
80
+ def bind_by_username_with_preauthentication
81
+ raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless
82
+ @options[:ldap][:auth_password]
83
+
84
+ @ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
85
+
86
+ @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter)
87
+ end
88
+
89
+ # Combine the filter for finding the user with the optional extra filter specified in the config
90
+ # (if any).
91
+ def user_filter
92
+ username_attribute = options[:ldap][:username_attribute] || default_username_attribute
93
+
94
+ filter = Array(username_attribute).map { |ua| Net::LDAP::Filter.eq(ua, @username) }.reduce(:|)
95
+ unless @options[:ldap][:filter].blank?
96
+ filter &= Net::LDAP::Filter.construct(@options[:ldap][:filter])
97
+ end
98
+
99
+ filter
100
+ end
101
+
102
+ # Finds the user based on the user_filter (this is called after authentication).
103
+ # We do this to make it possible to extract extra_attributes.
104
+ def find_user
105
+ results = @ldap.search( :base => options[:ldap][:base], :filter => user_filter)
106
+ return results.first
107
+ end
108
+
109
+ def extract_extra_attributes(ldap_entry)
110
+ @extra_attributes = {}
111
+ extra_attributes_to_extract.each do |attr|
112
+ v = ldap_entry[attr]
113
+ next if !v || (v.respond_to?(:empty?) && v.empty?)
114
+ if v.kind_of?(Array)
115
+ @extra_attributes[attr] = []
116
+ ldap_entry[attr].each do |a|
117
+ @extra_attributes[attr] << a.to_s
118
+ end
119
+ else
120
+ @extra_attributes[attr] = v.to_s
121
+ end
122
+ end
123
+
124
+ if @extra_attributes.empty?
125
+ $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
126
+ else
127
+ $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
128
+ end
129
+ ldap_entry
130
+ end
131
+ 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,19 @@
1
+ require 'openid'
2
+ require 'openid/extensions/sreg'
3
+ require 'openid/extensions/pape'
4
+ require 'openid/store/memory'
5
+
6
+ # CURRENTLY UNIMPLEMENTED
7
+ # This is just starter code.
8
+ class CASServer::Authenticators::OpenID < CASServer::Authenticators::Base
9
+
10
+ def validate(credentials)
11
+ raise NotImplementedError, "The OpenID authenticator is not yet implemented. "+
12
+ "See http://code.google.com/p/rubycas-server/issues/detail?id=36 if you are interested in helping this along."
13
+
14
+ read_standard_credentials(credentials)
15
+
16
+ store = OpenID::Store::Memory.new
17
+ consumer = OpenID::Consumer.new({}, store)
18
+ end
19
+ end
@@ -0,0 +1,158 @@
1
+ begin
2
+ require 'active_record'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ end
7
+
8
+ # Authenticates against a plain SQL table.
9
+ #
10
+ # This assumes that all of your users are stored in a table that has a 'username'
11
+ # column and a 'password' column. When the user logs in, CAS conects to the
12
+ # database and looks for a matching username/password in the users table. If a
13
+ # matching username and password is found, authentication is successful.
14
+ #
15
+ # Any database backend supported by ActiveRecord can be used.
16
+ #
17
+ # Config example:
18
+ #
19
+ # authenticator:
20
+ # class: CASServer::Authenticators::SQL
21
+ # database:
22
+ # adapter: mysql
23
+ # database: some_database_with_users_table
24
+ # username: root
25
+ # password:
26
+ # server: localhost
27
+ # user_table: users
28
+ # username_column: username
29
+ # password_column: password
30
+ #
31
+ # When replying to a CAS client's validation request, the server will normally
32
+ # provide the client with the authenticated user's username. However it is now
33
+ # possible for the server to provide the client with additional attributes.
34
+ # You can configure the SQL authenticator to provide data from additional
35
+ # columns in the users table by listing the names of the columns under the
36
+ # 'extra_attributes' option. Note though that this functionality is experimental.
37
+ # It should work with RubyCAS-Client, but may or may not work with other CAS
38
+ # clients.
39
+ #
40
+ # For example, with this configuration, the 'full_name' and 'access_level'
41
+ # columns will be provided to your CAS clients along with the username:
42
+ #
43
+ # authenticator:
44
+ # class: CASServer::Authenticators::SQL
45
+ # database:
46
+ # adapter: mysql
47
+ # database: some_database_with_users_table
48
+ # user_table: users
49
+ # username_column: username
50
+ # password_column: password
51
+ # ignore_type_column: true # indicates if you want to ignore Single Table Inheritance 'type' field
52
+ # extra_attributes: full_name, access_level
53
+ #
54
+ class CASServer::Authenticators::SQL < CASServer::Authenticators::Base
55
+ def self.setup(options)
56
+ raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless options[:database]
57
+
58
+ user_model_name = "CASUser_#{options[:auth_index]}"
59
+ $LOG.debug "CREATING USER MODEL #{user_model_name}"
60
+
61
+ class_eval %{
62
+ class #{user_model_name} < ActiveRecord::Base
63
+ end
64
+ }
65
+
66
+ @user_model = const_get(user_model_name)
67
+ @user_model.establish_connection(options[:database])
68
+ if ActiveRecord::VERSION::STRING >= '3.2'
69
+ @user_model.table_name = (options[:user_table] || 'users')
70
+ else
71
+ @user_model.set_table_name(options[:user_table] || 'users')
72
+ end
73
+ @user_model.inheritance_column = 'no_inheritance_column' if options[:ignore_type_column]
74
+ begin
75
+ @user_model.connection
76
+ rescue => e
77
+ $LOG.debug e
78
+ raise "SQL Authenticator can not connect to database"
79
+ end
80
+ end
81
+
82
+ def self.user_model
83
+ @user_model
84
+ end
85
+
86
+ def validate(credentials)
87
+ read_standard_credentials(credentials)
88
+ raise_if_not_configured
89
+
90
+ log_connection_pool_size
91
+ user_model.connection_pool.checkin(user_model.connection)
92
+
93
+ if matching_users.size > 0
94
+ $LOG.warn("#{self.class}: Multiple matches found for user #{@username.inspect}") if matching_users.size > 1
95
+
96
+ unless @options[:extra_attributes].blank?
97
+ if matching_users.size > 1
98
+ $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
99
+ else
100
+ user = matching_users.first
101
+
102
+ extract_extra(user)
103
+ log_extra
104
+ end
105
+ end
106
+
107
+ return true
108
+ else
109
+ return false
110
+ end
111
+ end
112
+
113
+ protected
114
+
115
+ def user_model
116
+ self.class.user_model
117
+ end
118
+
119
+ def username_column
120
+ @options[:username_column] || 'username'
121
+ end
122
+
123
+ def password_column
124
+ @options[:password_column] || 'password'
125
+ end
126
+
127
+ def raise_if_not_configured
128
+ raise CASServer::AuthenticatorError.new(
129
+ "Cannot validate credentials because the authenticator hasn't yet been configured"
130
+ ) unless @options
131
+ end
132
+
133
+ def extract_extra user
134
+ @extra_attributes = {}
135
+ extra_attributes_to_extract.each do |col|
136
+ @extra_attributes[col] = user.send(col)
137
+ end
138
+ end
139
+
140
+ def log_extra
141
+ if @extra_attributes.empty?
142
+ $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
143
+ else
144
+ $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
145
+ end
146
+ end
147
+
148
+ def log_connection_pool_size
149
+ log_msg = "#{self.class}: [#{user_model}] "
150
+ log_msg += "Connection pool size: #{user_model.connection_pool.connections.length}"
151
+ log_msg += "/#{user_model.connection_pool.instance_variable_get(:@size)}"
152
+ $LOG.debug log_msg
153
+ end
154
+
155
+ def matching_users
156
+ user_model.find(:all, :conditions => ["#{username_column} = ? AND #{password_column} = ?", @username, @password])
157
+ end
158
+ end
@@ -0,0 +1,93 @@
1
+ require 'casserver/authenticators/sql'
2
+
3
+ # These were pulled directly from Authlogic, and new ones can be added
4
+ # just by including new Crypto Providers
5
+ require File.dirname(__FILE__) + '/authlogic_crypto_providers/aes256'
6
+ require File.dirname(__FILE__) + '/authlogic_crypto_providers/bcrypt'
7
+ require File.dirname(__FILE__) + '/authlogic_crypto_providers/md5'
8
+ require File.dirname(__FILE__) + '/authlogic_crypto_providers/sha1'
9
+ require File.dirname(__FILE__) + '/authlogic_crypto_providers/sha512'
10
+
11
+ begin
12
+ require 'active_record'
13
+ rescue LoadError
14
+ require 'rubygems'
15
+ require 'active_record'
16
+ end
17
+
18
+ # This is a version of the SQL authenticator that works nicely with Authlogic.
19
+ # Passwords are encrypted the same way as it done in Authlogic.
20
+ # Before use you this, you MUST configure rest_auth_digest_streches and rest_auth_site_key in
21
+ # config.
22
+ #
23
+ # Using this authenticator requires restful authentication plugin on rails (client) side.
24
+ #
25
+ # * git://github.com/binarylogic/authlogic.git
26
+ #
27
+ # Usage:
28
+
29
+ # authenticator:
30
+ # class: CASServer::Authenticators::SQLAuthlogic
31
+ # database:
32
+ # adapter: mysql
33
+ # database: some_database_with_users_table
34
+ # user: root
35
+ # password:
36
+ # server: localhost
37
+ # user_table: user
38
+ # username_column: login
39
+ # password_column: crypted_password
40
+ # salt_column: password_salt
41
+ # encryptor: Sha1
42
+ # encryptor_options:
43
+ # digest_format: --SALT--PASSWORD--
44
+ # stretches: 1
45
+ #
46
+ class CASServer::Authenticators::SQLAuthlogic < CASServer::Authenticators::SQL
47
+
48
+ def validate(credentials)
49
+ read_standard_credentials(credentials)
50
+ raise_if_not_configured
51
+
52
+ user_model = self.class.user_model
53
+
54
+ username_column = @options[:username_column] || "login"
55
+ password_column = @options[:password_column] || "crypted_password"
56
+ salt_column = @options[:salt_column]
57
+
58
+ log_connection_pool_size
59
+ results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
60
+ user_model.connection_pool.checkin(user_model.connection)
61
+
62
+ begin
63
+ encryptor = eval("Authlogic::CryptoProviders::" + @options[:encryptor] || "Sha512")
64
+ rescue
65
+ $LOG.warn("Could not initialize Authlogic crypto class for '#{@options[:encryptor]}'")
66
+ encryptor = Authlogic::CryptoProviders::Sha512
67
+ end
68
+
69
+ @options[:encryptor_options].each do |name, value|
70
+ encryptor.send("#{name}=", value) if encryptor.respond_to?("#{name}=")
71
+ end
72
+
73
+ if results.size > 0
74
+ $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
75
+ user = results.first
76
+ tokens = [@password, (not salt_column.nil?) && user.send(salt_column) || nil].compact
77
+ crypted = user.send(password_column)
78
+
79
+ unless @options[:extra_attributes].blank?
80
+ if results.size > 1
81
+ $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
82
+ else
83
+ extract_extra(user)
84
+ log_extra
85
+ end
86
+ end
87
+
88
+ return encryptor.matches?(crypted, tokens)
89
+ else
90
+ return false
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,17 @@
1
+ require 'casserver/authenticators/sql'
2
+
3
+ require 'bcrypt'
4
+
5
+ # Essentially the same as the standard SQL authenticator but assumes that
6
+ # BCrypt has been used to encrypt the password. If you're using
7
+ # has_secure_password, then this is probably for you.
8
+ class CASServer::Authenticators::SQLBcrypt < CASServer::Authenticators::SQL
9
+
10
+ protected
11
+
12
+ def matching_users
13
+ results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
14
+ results.select { |user| BCrypt::Password.new(user.send(password_column.to_sym)) == @password }
15
+ end
16
+
17
+ end
@@ -0,0 +1,75 @@
1
+ require 'casserver/authenticators/sql'
2
+
3
+ require 'digest/sha1'
4
+ require 'digest/sha2'
5
+ require 'crypt-isaac'
6
+
7
+ # This is a more secure version of the SQL authenticator. Passwords are encrypted
8
+ # rather than being stored in plain text.
9
+ #
10
+ # Based on code contributed by Ben Mabey.
11
+ #
12
+ # Using this authenticator requires some configuration on the client side. Please see
13
+ # http://code.google.com/p/rubycas-server/wiki/UsingTheSQLEncryptedAuthenticator
14
+ class CASServer::Authenticators::SQLEncrypted < CASServer::Authenticators::SQL
15
+ # Include this module into your application's user model.
16
+ #
17
+ # Your model must have an 'encrypted_password' column where the password will be stored,
18
+ # and an 'encryption_salt' column that will be populated with a random string before
19
+ # the user record is first created.
20
+ module EncryptedPassword
21
+ def self.included(mod)
22
+ raise "#{self} should be inclued in an ActiveRecord class!" unless mod.respond_to?(:before_save)
23
+ mod.before_save :generate_encryption_salt
24
+ end
25
+
26
+ def encrypt(str)
27
+ generate_encryption_salt unless encryption_salt
28
+ Digest::SHA256.hexdigest("#{encryption_salt}::#{str}")
29
+ end
30
+
31
+ def password=(password)
32
+ self[:encrypted_password] = encrypt(password)
33
+ end
34
+
35
+ def generate_encryption_salt
36
+ self.encryption_salt = Digest::SHA1.hexdigest(Crypt::ISAAC.new.rand(2**31).to_s) unless
37
+ encryption_salt
38
+ end
39
+ end
40
+
41
+ def self.setup(options)
42
+ super(options)
43
+ user_model.__send__(:include, EncryptedPassword)
44
+ end
45
+
46
+ def validate(credentials)
47
+ read_standard_credentials(credentials)
48
+ raise_if_not_configured
49
+
50
+ user_model = self.class.user_model
51
+
52
+ username_column = @options[:username_column] || "username"
53
+ encrypt_function = @options[:encrypt_function] || 'user.encrypted_password == Digest::SHA256.hexdigest("#{user.encryption_salt}::#{@password}")'
54
+
55
+ log_connection_pool_size
56
+ results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
57
+ user_model.connection_pool.checkin(user_model.connection)
58
+
59
+ if results.size > 0
60
+ $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
61
+ user = results.first
62
+ unless @options[:extra_attributes].blank?
63
+ if results.size > 1
64
+ $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
65
+ else
66
+ extract_extra(user)
67
+ log_extra
68
+ end
69
+ end
70
+ return eval(encrypt_function)
71
+ else
72
+ return false
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,19 @@
1
+ require 'casserver/authenticators/sql'
2
+
3
+ require 'digest/md5'
4
+
5
+ # Essentially the same as the standard SQL authenticator, but this version
6
+ # assumes that your password is stored as an MD5 hash.
7
+ #
8
+ # This was contributed by malcomm for Drupal authentication. To work with
9
+ # Drupal, you should use 'name' for the :username_column config option, and
10
+ # 'pass' for the :password_column.
11
+ class CASServer::Authenticators::SQLMd5 < CASServer::Authenticators::SQL
12
+
13
+ protected
14
+ def read_standard_credentials(credentials)
15
+ super
16
+ @password = Digest::MD5.hexdigest(@password)
17
+ end
18
+
19
+ end
@@ -0,0 +1,82 @@
1
+ require 'casserver/authenticators/sql_encrypted'
2
+
3
+ require 'digest/sha1'
4
+
5
+ begin
6
+ require 'active_record'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ require 'active_record'
10
+ end
11
+
12
+ # This is a version of the SQL authenticator that works nicely with RestfulAuthentication.
13
+ # Passwords are encrypted the same way as it done in RestfulAuthentication.
14
+ # Before use you this, you MUST configure rest_auth_digest_streches and rest_auth_site_key in
15
+ # config.
16
+ #
17
+ # Using this authenticator requires restful authentication plugin on rails (client) side.
18
+ #
19
+ # * git://github.com/technoweenie/restful-authentication.git
20
+ #
21
+ class CASServer::Authenticators::SQLRestAuth < CASServer::Authenticators::SQLEncrypted
22
+
23
+ def validate(credentials)
24
+ read_standard_credentials(credentials)
25
+ raise_if_not_configured
26
+
27
+ raise CASServer::AuthenticatorError, "You must specify a 'site_key' in the SQLRestAuth authenticator's configuration!" unless @options[:site_key]
28
+ raise CASServer::AuthenticatorError, "You must specify 'digest_streches' in the SQLRestAuth authenticator's configuration!" unless @options[:digest_streches]
29
+
30
+ user_model = self.class.user_model
31
+
32
+ username_column = @options[:username_column] || "email"
33
+
34
+ log_connection_pool_size
35
+ results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
36
+ user_model.connection_pool.checkin(user_model.connection)
37
+
38
+ if results.size > 0
39
+ $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
40
+ user = results.first
41
+ if user.crypted_password == user.encrypt(@password,@options[:site_key],@options[:digest_streches])
42
+ unless @options[:extra_attributes].blank?
43
+ extract_extra(user)
44
+ log_extra
45
+ end
46
+ return true
47
+ else
48
+ return false
49
+ end
50
+ else
51
+ return false
52
+ end
53
+ end
54
+
55
+ def self.setup(options)
56
+ super(options)
57
+ user_model.__send__(:include, EncryptedPassword)
58
+ end
59
+
60
+ module EncryptedPassword
61
+
62
+ def self.included(mod)
63
+ raise "#{self} should be inclued in an ActiveRecord class!" unless mod.respond_to?(:before_save)
64
+ end
65
+
66
+ def encrypt(password,site_key,digest_streches)
67
+ password_digest(password, self.salt,site_key,digest_streches)
68
+ end
69
+
70
+ def secure_digest(*args)
71
+ Digest::SHA1.hexdigest(args.flatten.join('--'))
72
+ end
73
+
74
+ def password_digest(password, salt,site_key,digest_streches)
75
+ digest = site_key
76
+ digest_streches.times do
77
+ digest = secure_digest(digest, salt, password, site_key)
78
+ end
79
+ digest
80
+ end
81
+ end
82
+ end