lib/casserver/authenticators/ldap.rb in rubycas-server-0.6.0 vs lib/casserver/authenticators/ldap.rb in rubycas-server-0.7.0

- old
+ new

@@ -26,83 +26,113 @@ read_standard_credentials(credentials) return false if @password.blank? raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options - raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless @options[:ldap] - raise CASServer::AuthenticatorError, "You must specify an ldap server in the configuration!" unless @options[:ldap][:server] - + raise CASServer::AuthenticatorError, "Invalid LDAP authenticator configuration!" unless @options[:ldap] + raise CASServer::AuthenticatorError, "You must specify a server host in the LDAP configuration!" unless @options[:ldap][:host] || @options[:ldap][:server] + raise CASServer::AuthenticatorError, "The username '#{@username}' contains invalid characters." if (@username =~ /[*\(\)\0\/]/) preprocess_username @ldap = Net::LDAP.new - @ldap.host = @options[:ldap][:server] + + + @options[:ldap][:host] ||= @options[:ldap][:server] + @ldap.host = @options[:ldap][:host] @ldap.port = @options[:ldap][:port] if @options[:ldap][:port] + @ldap.encryption(@options[:ldap][:encryption].intern) if @options[:ldap][:encryption] begin if @options[:ldap][:auth_user] - bind_with_preauthentication + bind_success = bind_by_username_with_preauthentication else - bind_directly + bind_success = bind_by_username end + + return false unless bind_success + + entry = find_user + extract_extra_attributes(entry) + + return true rescue Net::LDAP::LdapError => e raise CASServer::AuthenticatorError, "LDAP authentication failed with '#{e}'. Check your authenticator configuration." end end protected def default_username_attribute - "uid" + "cn" end private + # Add prefix to username, if :username_prefix was specified in the :ldap config. def preprocess_username - # add prefix to username, if prefix was specified in the config @username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix] end - - def bind_with_preauthentication - # If an auth_user is specified, we will connect ("pre-authenticate") to the - # LDAP server using the authenticator account, and then attempt to bind as the - # user who is actually trying to authenticate. Note that you need to set up - # the special authenticator account first. Also, auth_user must be the authenticator - # user's full CN, which is probably not the same as their username. - # - # This pre-authentication process is necessary because binding can only be done - # using the CN, so having just the username is not enough. We connect as auth_user, - # and then try to find the target user's CN based on the given username. Then we bind - # as the target user to validate their credentials. + + # Attempt to bind with the LDAP server using the username and password entered by + # the user. If a :filter was specified in the :ldap config, the filter will be + # added to the LDAP query for the username. + def bind_by_username + username_attribute = options[:ldap][:username_attribute] || default_username_attribute + @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter) + end + + # If an auth_user is specified, we will connect ("pre-authenticate") with the + # LDAP server using the authenticator account, and then attempt to bind as the + # user who is actually trying to authenticate. Note that you need to set up + # the special authenticator account first. Also, auth_user must be the authenticator + # user's full CN, which is probably not the same as their username. + # + # This pre-authentication process is necessary because binding can only be done + # using the CN, so having just the username is not enough. We connect as auth_user, + # and then try to find the target user's CN based on the given username. Then we bind + # as the target user to validate their credentials. + def bind_by_username_with_preauthentication raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless @options[:ldap][:auth_password] @ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password]) + @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter) + end + + # Combine the filter for finding the user with the optional extra filter specified in the config + # (if any). + def user_filter username_attribute = options[:ldap][:username_attribute] || default_username_attribute - filter = Net::LDAP::Filter.construct(@options[:ldap][:filter]) if - @options[:ldap][:filter] && !@options[:ldap][:filter].blank? - username_filter = Net::LDAP::Filter.eq(username_attribute, @username) - if filter - filter &= username_filter - else - filter = username_filter + filter = Net::LDAP::Filter.eq(username_attribute, @username) + unless @options[:ldap][:filter].blank? + filter &= Net::LDAP::Filter.construct(@options[:ldap][:filter]) end - - @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => filter) end - - def bind_directly - # When no auth_user is specified, we will try to connect directly as the user - # who is trying to authenticate. Note that for this to work, the username must - # be equivalent to the user's CN, and this is often not the case (for example, - # in Active Directory, the username is the 'sAMAccountName' attribute, while the - # user's CN is generally their full name.) + + # Finds the user based on the user_filter (this is called after authentication). + # We do this to make it possible to extract extra_attributes. + def find_user + results = @ldap.search( :base => options[:ldap][:base], :filter => user_filter) + return results.first + end + + def extract_extra_attributes(ldap_entry) + @extra_attributes = {} + extra_attributes_to_extract.each do |attr| + v = !ldap_entry[attr].blank? && ldap_entry[attr].first + if v + @extra_attributes[attr] = v.to_s + end + end - cn = @username - - @ldap.authenticate(cn, @password) - @ldap.bind + if @extra_attributes.empty? + $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.") + else + $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}") + end + ldap_entry end end