rubycas-server 0.4.1 → 0.4.2

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/CHANGELOG.txt CHANGED
@@ -1,3 +1,24 @@
1
+ === 0.4.2 :: 2007-07-26
2
+
3
+ * The LDAP/AD authenticator has been largely re-written. The code is a bit
4
+ cleaner now, and should work better with non-Active Directory LDAP servers
5
+ (although this has yet to be tested since I don't have access to a non-AD
6
+ LDAP server).
7
+ * The validate() method in your authenticators now receives a :service element
8
+ (in addition to :username, and :password). This is simply the service
9
+ url (if any) specified in the user's CAS request. If you call
10
+ read_standard_credentials(credentials) at the top of your validator, the value
11
+ will also be available as @service along with @username and @password.
12
+ * By request, a :username_prefix option has been added to the ldap
13
+ configuration. If entered, this string will be automatically prefixed to
14
+ the username entered by the user.
15
+ * A bug having to do with handling authenticator errors has been fixed.
16
+ Any authenticator error messages should now be correctly shown on the
17
+ login page.
18
+ * Minor improvements to error messages having to do with login tickets.
19
+ They're a bit more prescriptive now, explaining to the user what steps
20
+ they should take to correct the error.
21
+
1
22
  === 0.4.1 :: 2007-06-07
2
23
 
3
24
  * This release restores compatiblity with older versions of rubygems
data/config.example.yml CHANGED
@@ -104,15 +104,17 @@ database:
104
104
  #
105
105
  #
106
106
  # ==> ActiveDirectory Authentication:
107
- # This method authenticates against Microsoft's ActiveDirectory using LDAP.
107
+ # This method authenticates against Microsoft's Active Directory using LDAP.
108
108
  # You must enter your ActiveDirectory server, and base DN. The port number
109
- # and LDAP filter are optional. You must also enter a username and password
109
+ # and LDAP filter are optional. You must also enter a CN and password
110
110
  # for an "authenticator" user. The authenticator users this account to
111
111
  # log in to the ActiveDirectory server and search LDAP. This does not have
112
- # to be an administrative account; it only has to be able to search for other
112
+ # to be an administrative account -- it only has to be able to search for other
113
113
  # users.
114
- #
115
- # Example:
114
+ #
115
+ # Note that the auth_user parameter must be the user's CN (Common Name)!
116
+ # In Active Directory, the CN is genarally the user's full name, which is not
117
+ # the same as their username (sAMAccountName).
116
118
  #
117
119
  #authenticator:
118
120
  # class: CASServer::Authenticators::ActiveDirectoryLDAP
@@ -124,6 +126,13 @@ database:
124
126
  # auth_user: authenticator
125
127
  # auth_password: itsasecret
126
128
  #
129
+ # It is possible to authenticate against Active Directory without the
130
+ # authenticator user, but this requires that users type in their CN as
131
+ # the username, rather than typing in their sAMAccountName. In other words
132
+ # users will likely have to authenticate by typing their full name,
133
+ # rather than their username. If you prefer to do this, then just
134
+ # omit the auth_user and auth_password values in the above example.
135
+ #
127
136
  #
128
137
  # ==> LDAP Authentication:
129
138
  # This is a more general version of the ActiveDirectory authenticator.
@@ -2,6 +2,8 @@ module CASServer
2
2
  module Authenticators
3
3
  class Base
4
4
  attr_accessor :options
5
+ attr_reader :username # make this accessible so that we can pick up any
6
+ # transformations done within the authenticator
5
7
 
6
8
  def validate(credentials)
7
9
  raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
@@ -16,6 +18,7 @@ module CASServer
16
18
  def read_standard_credentials(credentials)
17
19
  @username = credentials[:username]
18
20
  @password = credentials[:password]
21
+ @service = credentials[:service]
19
22
  end
20
23
  end
21
24
  end
@@ -18,27 +18,72 @@ class CASServer::Authenticators::LDAP < CASServer::Authenticators::Base
18
18
  raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless @options[:ldap]
19
19
  raise CASServer::AuthenticatorError, "You must specify an ldap server in the configuration!" unless @options[:ldap][:server]
20
20
 
21
- raise CASServer::AuthenticatorError, "The username '#{@username}' contains invalid characters." if (@username =~ /[*\(\)\\\0\/]/)
21
+ raise CASServer::AuthenticatorError, "The username '#{@username}' contains invalid characters." if (@username =~ /[*\(\)\0\/]/)
22
22
 
23
- ldap = Net::LDAP.new
24
- ldap.host = @options[:ldap][:server]
25
- ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
23
+ preprocess_username
26
24
 
27
- if @options[:ldap][:auth_user]
28
- raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless @options[:ldap][:auth_password]
29
- ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
30
- end
31
-
32
- filter = "(#{@options[:ldap][:username_attribute] || default_username_attribute}=#{@username})"
33
- filter += " & (#{@options[:ldap][:filter]})" if @options[:ldap][:filter]
34
-
35
- result = ldap.bind_as(:base => @options[:ldap][:base], :filter => filter, :password => @password)
25
+ @ldap = Net::LDAP.new
26
+ @ldap.host = @options[:ldap][:server]
27
+ @ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
36
28
 
37
- return result
29
+ begin
30
+ if @options[:ldap][:auth_user]
31
+ bind_with_preauthentication
32
+ else
33
+ bind_directly
34
+ end
35
+ rescue Net::LDAP::LdapError => e
36
+ raise CASServer::AuthenticatorError,
37
+ "LDAP authentication failed with '#{e}'. Check your authenticator configuration."
38
+ end
38
39
  end
39
40
 
40
41
  protected
41
- def default_username_attribute
42
- "uid"
43
- end
42
+ def default_username_attribute
43
+ "uid"
44
+ end
45
+
46
+ private
47
+ def preprocess_username
48
+ # add prefix to username, if prefix was specified in the config
49
+ @username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix]
50
+ end
51
+
52
+ def bind_with_preauthentication
53
+ # If an auth_user is specified, we will connect ("pre-authenticate") to the
54
+ # LDAP server using the authenticator account, and then attempt to bind as the
55
+ # user who is actually trying to authenticate. Note that you need to set up
56
+ # the special authenticator account first. Also, auth_user must be the authenticator
57
+ # user's full CN, which is probably not the same as their username.
58
+ #
59
+ # This pre-authentication process is necessary because binding can only be done
60
+ # using the CN, so having just the username is not enough. We connect as auth_user,
61
+ # and then try to find the target user's CN based on the given username. Then we bind
62
+ # as the target user to validate their credentials.
63
+
64
+ raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless
65
+ @options[:ldap][:auth_password]
66
+
67
+ @ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
68
+
69
+ username_attribute = options[:ldap][:username_attribute] || default_username_attribute
70
+
71
+ filter = Net::LDAP::Filter.construct(@options[:ldap][:filter]) &
72
+ Net::LDAP::Filter.eq(username_attribute, @username)
73
+
74
+ @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => filter)
75
+ end
76
+
77
+ def bind_directly
78
+ # When no auth_user is specified, we will try to connect directly as the user
79
+ # who is trying to authenticate. Note that for this to work, the username must
80
+ # be equivalent to the user's CN, and this is often not the case (for example,
81
+ # in Active Directory, the username is the 'sAMAccountName' attribute, while the
82
+ # user's CN is generally their full name.)
83
+
84
+ cn = @username
85
+
86
+ @ldap.authenticate(cn, @password)
87
+ @ldap.bind
88
+ end
44
89
  end
data/lib/casserver/cas.rb CHANGED
@@ -103,20 +103,20 @@ module CASServer::CAS
103
103
 
104
104
  success = false
105
105
  if ticket.nil?
106
- error = "Your login request did not include a login ticket."
106
+ error = "Your login request did not include a login ticket. There may be a problem with the authentication system."
107
107
  $LOG.warn("Missing login ticket.")
108
108
  elsif lt = LoginTicket.find_by_ticket(ticket)
109
109
  if lt.consumed?
110
- error = "The login ticket you provided has already been used up."
110
+ error = "The login ticket you provided has already been used up. Please try logging in again."
111
111
  $LOG.warn("Login ticket '#{ticket}' previously used up")
112
112
  elsif Time.now - lt.created_on < CASServer::Conf.login_ticket_expiry
113
113
  $LOG.info("Login ticket '#{ticket}' successfully validated")
114
114
  else
115
- error = "Your login ticket has expired."
115
+ error = "Your login ticket has expired. Please try logging in again."
116
116
  $LOG.warn("Expired login ticket '#{ticket}'")
117
117
  end
118
118
  else
119
- error = "The login ticket you provided is invalid."
119
+ error = "The login ticket you provided is invalid. Please try logging in again."
120
120
  $LOG.warn("Invalid login ticket '#{ticket}'")
121
121
  end
122
122
 
@@ -74,18 +74,18 @@ module CASServer::Controllers
74
74
  $LOG.debug("Logging in with username: #{@username}, lt: #{@lt}, service: #{@service}, auth: #{$AUTH}")
75
75
 
76
76
  begin
77
- credentials_are_valid = $AUTH.validate(:username => @username, :password => @password)
78
- rescue AuthenticatorError => e
77
+ credentials_are_valid = $AUTH.validate(:username => @username, :password => @password, :service => @service)
78
+ rescue CASServer::AuthenticatorError => e
79
79
  $LOG.error(e)
80
80
  @message = {:type => 'mistake', :message => e.to_s}
81
- render :login and return
81
+ return render(:login)
82
82
  end
83
83
 
84
84
  if credentials_are_valid
85
- $LOG.info("Credentials for username '#{@username}' successfully validated")
85
+ $LOG.info("Credentials for username '#{$AUTH.username}' successfully validated")
86
86
 
87
87
  # 3.6 (ticket-granting cookie)
88
- tgt = generate_ticket_granting_ticket(@username)
88
+ tgt = generate_ticket_granting_ticket($AUTH.username)
89
89
 
90
90
  if CASServer::Conf.expire_sessions
91
91
  expires = CASServer::Conf.ticket_granting_ticket_expiry.to_i.from_now
@@ -98,17 +98,17 @@ module CASServer::Controllers
98
98
  # seem to be an easy way to set cookie expire times in Camping :(
99
99
  @cookies[:tgt] = tgt.to_s
100
100
 
101
- $LOG.debug("Ticket granting cookie '#{@cookies[:tgt]}' granted to '#{@username}'. #{expiry_info}")
101
+ $LOG.debug("Ticket granting cookie '#{@cookies[:tgt]}' granted to '#{$AUTH.username}'. #{expiry_info}")
102
102
 
103
103
  if @service.blank?
104
- $LOG.info("Successfully authenticated user '#{@username}' at '#{tgt.client_hostname}'. No service param was given, so we will not redirect.")
104
+ $LOG.info("Successfully authenticated user '#{$AUTH.username}' at '#{tgt.client_hostname}'. No service param was given, so we will not redirect.")
105
105
  @message = {:type => 'confirmation', :message => "You have successfully logged in."}
106
106
  else
107
- @st = generate_service_ticket(@service, @username)
107
+ @st = generate_service_ticket(@service, $AUTH.username)
108
108
  begin
109
109
  service_with_ticket = service_uri_with_ticket(@service, @st)
110
110
 
111
- $LOG.info("Redirecting authenticated user '#{@username}' at '#{@st.client_hostname}' to service '#{@service}'")
111
+ $LOG.info("Redirecting authenticated user '#{$AUTH.username}' at '#{@st.client_hostname}' to service '#{@service}'")
112
112
  return redirect(service_with_ticket, :status => 303) # response code 303 means "See Other" (see Appendix B in CAS Protocol spec)
113
113
  rescue URI::InvalidURIError
114
114
  $LOG.error("The service '#{@service}' is not a valid URI!")
@@ -116,7 +116,7 @@ module CASServer::Controllers
116
116
  end
117
117
  end
118
118
  else
119
- $LOG.warn("Invalid credentials given for user '#{@username}'")
119
+ $LOG.warn("Invalid credentials given for user '#{$AUTH.username}'")
120
120
  @message = {:type => 'mistake', :message => "Incorrect username or password."}
121
121
  end
122
122
 
@@ -2,7 +2,7 @@ module CASServer
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 4
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: rubycas-server
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.1
7
- date: 2007-06-07 00:00:00 -04:00
6
+ version: 0.4.2
7
+ date: 2007-07-26 00:00:00 -04:00
8
8
  summary: Provides single sign on for web applications using the CAS protocol.
9
9
  require_paths:
10
10
  - lib