rubycas-server 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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