gunark-rubycas-server 0.6.99.336

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 (72) hide show
  1. data/CHANGELOG.txt +1 -0
  2. data/History.txt +245 -0
  3. data/LICENSE.txt +504 -0
  4. data/Manifest.txt +74 -0
  5. data/PostInstall.txt +3 -0
  6. data/README.txt +25 -0
  7. data/Rakefile +4 -0
  8. data/bin/rubycas-server +26 -0
  9. data/bin/rubycas-server-ctl +22 -0
  10. data/config.example.yml +442 -0
  11. data/config/hoe.rb +76 -0
  12. data/config/requirements.rb +15 -0
  13. data/custom_views.example.rb +11 -0
  14. data/lib/casserver.rb +111 -0
  15. data/lib/casserver/authenticators/active_directory_ldap.rb +11 -0
  16. data/lib/casserver/authenticators/base.rb +48 -0
  17. data/lib/casserver/authenticators/client_certificate.rb +46 -0
  18. data/lib/casserver/authenticators/ldap.rb +138 -0
  19. data/lib/casserver/authenticators/ntlm.rb +88 -0
  20. data/lib/casserver/authenticators/open_id.rb +22 -0
  21. data/lib/casserver/authenticators/sql.rb +102 -0
  22. data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
  23. data/lib/casserver/authenticators/sql_md5.rb +19 -0
  24. data/lib/casserver/authenticators/test.rb +19 -0
  25. data/lib/casserver/cas.rb +308 -0
  26. data/lib/casserver/conf.rb +112 -0
  27. data/lib/casserver/controllers.rb +452 -0
  28. data/lib/casserver/environment.rb +26 -0
  29. data/lib/casserver/models.rb +218 -0
  30. data/lib/casserver/postambles.rb +174 -0
  31. data/lib/casserver/utils.rb +30 -0
  32. data/lib/casserver/version.rb +9 -0
  33. data/lib/casserver/views.rb +243 -0
  34. data/lib/rubycas-server.rb +1 -0
  35. data/lib/rubycas-server/version.rb +1 -0
  36. data/lib/themes/cas.css +121 -0
  37. data/lib/themes/notice.png +0 -0
  38. data/lib/themes/ok.png +0 -0
  39. data/lib/themes/simple/bg.png +0 -0
  40. data/lib/themes/simple/login_box_bg.png +0 -0
  41. data/lib/themes/simple/logo.png +0 -0
  42. data/lib/themes/simple/theme.css +28 -0
  43. data/lib/themes/urbacon/bg.png +0 -0
  44. data/lib/themes/urbacon/login_box_bg.png +0 -0
  45. data/lib/themes/urbacon/logo.png +0 -0
  46. data/lib/themes/urbacon/theme.css +33 -0
  47. data/lib/themes/warning.png +0 -0
  48. data/misc/basic_cas_single_signon_mechanism_diagram.png +0 -0
  49. data/misc/basic_cas_single_signon_mechanism_diagram.svg +652 -0
  50. data/resources/init.d.sh +58 -0
  51. data/script/console +10 -0
  52. data/script/destroy +14 -0
  53. data/script/generate +14 -0
  54. data/script/txt2html +82 -0
  55. data/setup.rb +1585 -0
  56. data/tasks/deployment.rake +34 -0
  57. data/tasks/environment.rake +7 -0
  58. data/tasks/website.rake +17 -0
  59. data/vendor/isaac_0.9.1/LICENSE +26 -0
  60. data/vendor/isaac_0.9.1/README +78 -0
  61. data/vendor/isaac_0.9.1/TODO +3 -0
  62. data/vendor/isaac_0.9.1/VERSIONS +3 -0
  63. data/vendor/isaac_0.9.1/crypt/ISAAC.rb +171 -0
  64. data/vendor/isaac_0.9.1/isaac.gemspec +39 -0
  65. data/vendor/isaac_0.9.1/setup.rb +596 -0
  66. data/vendor/isaac_0.9.1/test/TC_ISAAC.rb +76 -0
  67. data/website/index.html +40 -0
  68. data/website/index.txt +3 -0
  69. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  70. data/website/stylesheets/screen.css +138 -0
  71. data/website/template.html.erb +40 -0
  72. metadata +146 -0
data/config/hoe.rb ADDED
@@ -0,0 +1,76 @@
1
+ require 'rubycas-server/version'
2
+
3
+ AUTHOR = 'Matt Zukowski' # can also be an array of Authors
4
+ EMAIL = "matt@zukowski.ca"
5
+ DESCRIPTION = "Provides single sign-on authentication for web applications using the CAS protocol."
6
+ GEM_NAME = 'rubycas-server' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'rubycas-server' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+ EXTRA_DEPENDENCIES = [
11
+ ['activesupport', '>= 2.0.2'],
12
+ ['activerecord', '>= 2.0.2'],
13
+ ['picnic', '>= 0.6.5']
14
+ ] # An array of rubygem dependencies [name, version]
15
+
16
+ @config_file = "~/.rubyforge/user-config.yml"
17
+ @config = nil
18
+ RUBYFORGE_USERNAME = "unknown"
19
+ def rubyforge_username
20
+ unless @config
21
+ begin
22
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
23
+ rescue
24
+ puts <<-EOS
25
+ ERROR: No rubyforge config file found: #{@config_file}
26
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
27
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
28
+ EOS
29
+ exit
30
+ end
31
+ end
32
+ RUBYFORGE_USERNAME.replace @config["username"]
33
+ end
34
+
35
+ ENV['NODOT'] = '1'
36
+
37
+ #REV = nil
38
+ # UNCOMMENT IF REQUIRED:
39
+ REV = YAML.load(`svn info`)['Revision']
40
+ VERS = CASServer::VERSION::STRING + (REV ? ".#{REV}" : "")
41
+ RDOC_OPTS = ['--quiet', '--title', 'rubycas-server documentation',
42
+ "--opname", "index.html",
43
+ "--line-numbers",
44
+ "--main", "README",
45
+ "--inline-source"]
46
+
47
+ class Hoe
48
+ def extra_deps
49
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
50
+ @extra_deps
51
+ end
52
+ end
53
+
54
+ # Generate all the Rake tasks
55
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
56
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
57
+ p.developer(AUTHOR, EMAIL)
58
+ p.description = DESCRIPTION
59
+ p.summary = DESCRIPTION
60
+ p.url = HOMEPATH
61
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
62
+ p.test_globs = ["test/**/test_*.rb"]
63
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
64
+
65
+ # == Optional
66
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
67
+ #p.extra_deps = EXTRA_DEPENDENCIES
68
+
69
+ p.spec_extras = {:executables => ['rubycas-server', 'rubycas-server-ctl']} # A hash of extra values to set in the gemspec.
70
+ end
71
+
72
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
73
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}"
74
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
75
+ $hoe.rsync_args = '-av --delete --ignore-errors'
76
+ $hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
@@ -0,0 +1,11 @@
1
+ # Custom views file; add methods to the module definition below
2
+
3
+ module CASServer::Views
4
+
5
+ # Override views here, for example, a custom login form:
6
+ def login_form
7
+ # Add your custom login form here, using Markaby
8
+ # See the original views.rb file at lib/casserver/views.rb for method names and usage
9
+ end
10
+
11
+ end
data/lib/casserver.rb ADDED
@@ -0,0 +1,111 @@
1
+ $: << File.dirname(File.expand_path(__FILE__))
2
+ require 'casserver/environment'
3
+
4
+ $APP_PATH ||= File.dirname(File.expand_path(__FILE__))
5
+
6
+ # change to current directory when invoked on its own
7
+ Dir.chdir($APP_PATH) if __FILE__ == $0
8
+
9
+ $: << $APP_PATH + "/../vendor/isaac_0.9.1"
10
+ require 'crypt/ISAAC'
11
+
12
+
13
+ require 'active_support'
14
+ require 'yaml'
15
+
16
+
17
+ # Camping.goes must be called after the authenticator class is loaded, otherwise weird things happen
18
+ Camping.goes :CASServer
19
+
20
+ $CONFIG_FILE ||= '/etc/rubycas-server/config.yml'
21
+
22
+ # for some reason this makes JRuby happy
23
+ class CASServer::Models::Base
24
+ end
25
+
26
+ CASServer.picnic!
27
+
28
+ $CONF[:expire_sessions] ||= false
29
+ $CONF[:login_ticket_expiry] ||= 5.minutes
30
+ $CONF[:service_ticket_expiry] ||= 5.minutes # CAS Protocol Spec, sec. 3.2.1 (recommended expiry time)
31
+ $CONF[:proxy_granting_ticket_expiry] ||= 48.hours
32
+ $CONF[:ticket_granting_ticket_expiry] ||= 48.hours
33
+ $CONF[:log] ||= {:file => 'casserver.log', :level => 'DEBUG'}
34
+ $CONF[:uri_path] ||= "/"
35
+
36
+ unless $CONF[:authenticator]
37
+ $stderr.puts
38
+ $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
39
+ $stderr.puts
40
+ $stderr.puts "You have not yet defined an authenticator for your CAS server!"
41
+ $stderr.puts "Please consult your config file at #{$CONFIG_FILE.inspect} for details."
42
+ $stderr.puts
43
+ $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
44
+ exit 1
45
+ end
46
+
47
+ require 'casserver/utils'
48
+ require 'casserver/models'
49
+ require 'casserver/cas'
50
+ require 'casserver/views'
51
+ require 'casserver/controllers'
52
+
53
+ if $CONF[:authenticator].instance_of? Array
54
+ $CONF[:authenticator].each_index do |auth_index|
55
+ $CONF[:authenticator][auth_index] = HashWithIndifferentAccess.new($CONF[:authenticator][auth_index])
56
+ end
57
+ end
58
+
59
+ $AUTH = []
60
+ begin
61
+ # attempt to instantiate the authenticator
62
+ if $CONF[:authenticator].instance_of? Array
63
+ $CONF[:authenticator].each { |authenticator| $AUTH << authenticator[:class].constantize.new}
64
+ else
65
+ $AUTH << $CONF[:authenticator][:class].constantize.new
66
+ end
67
+ rescue NameError
68
+ if $CONF[:authenticator].instance_of? Array
69
+ $CONF[:authenticator].each do |authenticator|
70
+ if !authenticator[:source].nil?
71
+ # config.yml explicitly names source file
72
+ require authenticator[:source]
73
+ else
74
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
75
+ auth_rb = authenticator[:class].underscore.gsub('cas_server/', '')
76
+ require 'casserver/'+auth_rb
77
+ end
78
+ $AUTH << authenticator[:class].constantize.new
79
+ end
80
+ else
81
+ if !$CONF[:authenticator][:source].nil?
82
+ # config.yml explicitly names source file
83
+ require $CONF[:authenticator][:source]
84
+ else
85
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
86
+ auth_rb = $CONF[:authenticator][:class].underscore.gsub('cas_server/', '')
87
+ require 'casserver/'+auth_rb
88
+ end
89
+
90
+ $AUTH << $CONF[:authenticator][:class].constantize.new
91
+ end
92
+ end
93
+
94
+ $CONF[:public_dir] = {
95
+ :path => "/themes",
96
+ :dir => File.expand_path(File.dirname(__FILE__))+"/themes"
97
+ }
98
+
99
+ def CASServer.create
100
+ $LOG.info "Creating RubyCAS-Server..."
101
+ CASServer::Models::Base.establish_connection(CASServer::Conf.database)
102
+ CASServer::Models.create_schema
103
+
104
+ CASServer::Models::ServiceTicket.cleanup_expired(CASServer::Conf.service_ticket_expiry)
105
+ CASServer::Models::LoginTicket.cleanup_expired(CASServer::Conf.login_ticket_expiry)
106
+ CASServer::Models::ProxyGrantingTicket.cleanup_expired(CASServer::Conf.proxy_granting_ticket_expiry)
107
+ CASServer::Models::TicketGrantingTicket.cleanup_expired(CASServer::Conf.ticket_granting_ticket_expiry)
108
+ end
109
+
110
+
111
+ CASServer.start_picnic
@@ -0,0 +1,11 @@
1
+ require 'casserver/authenticators/ldap'
2
+
3
+ # Slightly modified version of the LDAP authenticator for Microsoft's ActiveDirectory.
4
+ # The only difference is that the default_username_attribute for AD is 'sAMAccountName'
5
+ # rather than 'uid'.
6
+ class CASServer::Authenticators::ActiveDirectoryLDAP < CASServer::Authenticators::LDAP
7
+ protected
8
+ def default_username_attribute
9
+ "sAMAccountName"
10
+ end
11
+ end
@@ -0,0 +1,48 @@
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
+ def validate(credentials)
9
+ raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
10
+ end
11
+
12
+ def configure(options)
13
+ raise ArgumentError, "options must be a HashWithIndifferentAccess" unless options.kind_of? HashWithIndifferentAccess
14
+ @options = options.dup
15
+ @extra_attributes = {}
16
+ end
17
+
18
+ def extra_attributes
19
+ @extra_attributes
20
+ end
21
+
22
+ protected
23
+ def read_standard_credentials(credentials)
24
+ @username = credentials[:username]
25
+ @password = credentials[:password]
26
+ @service = credentials[:service]
27
+ @request = credentials[:request]
28
+ end
29
+
30
+ def extra_attributes_to_extract
31
+ if @options[:extra_attributes].kind_of? Array
32
+ attrs = @options[:extra_attributes]
33
+ elsif @options[:extra_attributes].kind_of? String
34
+ attrs = @options[:extra_attributes].split(',').collect{|col| col.strip}
35
+ else
36
+ $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.")
37
+ attrs = []
38
+ end
39
+
40
+ $LOG.debug("#{self.class.name} will try to extract the following extra_attributes: #{attrs.inspect}")
41
+ return attrs
42
+ end
43
+ end
44
+ end
45
+
46
+ class AuthenticatorError < Exception
47
+ end
48
+ end
@@ -0,0 +1,46 @@
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
+
44
+ return true # if SSL certificate is valid, false otherwise
45
+ end
46
+ end
@@ -0,0 +1,138 @@
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 = Net::LDAP::Filter.eq(username_attribute, @username)
110
+ unless @options[:ldap][:filter].blank?
111
+ filter &= Net::LDAP::Filter.construct(@options[:ldap][:filter])
112
+ end
113
+ end
114
+
115
+ # Finds the user based on the user_filter (this is called after authentication).
116
+ # We do this to make it possible to extract extra_attributes.
117
+ def find_user
118
+ results = @ldap.search( :base => options[:ldap][:base], :filter => user_filter)
119
+ return results.first
120
+ end
121
+
122
+ def extract_extra_attributes(ldap_entry)
123
+ @extra_attributes = {}
124
+ extra_attributes_to_extract.each do |attr|
125
+ v = !ldap_entry[attr].blank? && ldap_entry[attr].first
126
+ if v
127
+ @extra_attributes[attr] = v.to_s
128
+ end
129
+ end
130
+
131
+ if @extra_attributes.empty?
132
+ $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
133
+ else
134
+ $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
135
+ end
136
+ ldap_entry
137
+ end
138
+ end