relevance-rubycas-server 0.6.99

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 (58) hide show
  1. data/.loadpath +5 -0
  2. data/.project +17 -0
  3. data/CHANGELOG.txt +1 -0
  4. data/History.txt +223 -0
  5. data/LICENSE.txt +504 -0
  6. data/Manifest.txt +61 -0
  7. data/README.txt +25 -0
  8. data/Rakefile +60 -0
  9. data/bin/rubycas-server +26 -0
  10. data/bin/rubycas-server-ctl +22 -0
  11. data/config.example.yml +363 -0
  12. data/custom_views.example.rb +11 -0
  13. data/lib/casserver.rb +110 -0
  14. data/lib/casserver/authenticators/active_directory_ldap.rb +11 -0
  15. data/lib/casserver/authenticators/base.rb +47 -0
  16. data/lib/casserver/authenticators/ldap.rb +108 -0
  17. data/lib/casserver/authenticators/ntlm.rb +88 -0
  18. data/lib/casserver/authenticators/sql.rb +102 -0
  19. data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
  20. data/lib/casserver/authenticators/test.rb +15 -0
  21. data/lib/casserver/cas.rb +307 -0
  22. data/lib/casserver/conf.rb +112 -0
  23. data/lib/casserver/controllers.rb +436 -0
  24. data/lib/casserver/environment.rb +23 -0
  25. data/lib/casserver/models.rb +218 -0
  26. data/lib/casserver/postambles.rb +174 -0
  27. data/lib/casserver/utils.rb +30 -0
  28. data/lib/casserver/version.rb +9 -0
  29. data/lib/casserver/views.rb +235 -0
  30. data/lib/rubycas-server.rb +1 -0
  31. data/lib/rubycas-server/version.rb +1 -0
  32. data/lib/themes/cas.css +121 -0
  33. data/lib/themes/notice.png +0 -0
  34. data/lib/themes/ok.png +0 -0
  35. data/lib/themes/simple/bg.png +0 -0
  36. data/lib/themes/simple/login_box_bg.png +0 -0
  37. data/lib/themes/simple/logo.png +0 -0
  38. data/lib/themes/simple/theme.css +28 -0
  39. data/lib/themes/urbacon/bg.png +0 -0
  40. data/lib/themes/urbacon/login_box_bg.png +0 -0
  41. data/lib/themes/urbacon/logo.png +0 -0
  42. data/lib/themes/urbacon/theme.css +33 -0
  43. data/lib/themes/warning.png +0 -0
  44. data/misc/basic_cas_single_signon_mechanism_diagram.png +0 -0
  45. data/misc/basic_cas_single_signon_mechanism_diagram.svg +652 -0
  46. data/resources/init.d.sh +58 -0
  47. data/setup.rb +1585 -0
  48. data/test/test_cas.rb +33 -0
  49. data/test/test_casserver.rb +125 -0
  50. data/vendor/isaac_0.9.1/LICENSE +26 -0
  51. data/vendor/isaac_0.9.1/README +78 -0
  52. data/vendor/isaac_0.9.1/TODO +3 -0
  53. data/vendor/isaac_0.9.1/VERSIONS +3 -0
  54. data/vendor/isaac_0.9.1/crypt/ISAAC.rb +171 -0
  55. data/vendor/isaac_0.9.1/isaac.gemspec +39 -0
  56. data/vendor/isaac_0.9.1/setup.rb +596 -0
  57. data/vendor/isaac_0.9.1/test/TC_ISAAC.rb +76 -0
  58. metadata +158 -0
@@ -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
@@ -0,0 +1,110 @@
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
+ require 'active_support'
13
+ require 'yaml'
14
+
15
+
16
+ # Camping.goes must be called after the authenticator class is loaded, otherwise weird things happen
17
+ Camping.goes :CASServer
18
+
19
+ $CONFIG_FILE ||= '/etc/rubycas-server/config.yml'
20
+
21
+ # for some reason this makes JRuby happy
22
+ class CASServer::Models::Base
23
+ end
24
+
25
+ CASServer.picnic!
26
+
27
+ $CONF[:expire_sessions] ||= false
28
+ $CONF[:login_ticket_expiry] ||= 5.minutes
29
+ $CONF[:service_ticket_expiry] ||= 5.minutes # CAS Protocol Spec, sec. 3.2.1 (recommended expiry time)
30
+ $CONF[:proxy_granting_ticket_expiry] ||= 48.hours
31
+ $CONF[:ticket_granting_ticket_expiry] ||= 48.hours
32
+ $CONF[:log] ||= {:file => 'casserver.log', :level => 'DEBUG'}
33
+ $CONF[:uri_path] ||= "/"
34
+
35
+ unless $CONF[:authenticator]
36
+ $stderr.puts
37
+ $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
38
+ $stderr.puts
39
+ $stderr.puts "You have not yet defined an authenticator for your CAS server!"
40
+ $stderr.puts "Please consult your config file at #{$CONFIG_FILE.inspect} for details."
41
+ $stderr.puts
42
+ $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
43
+ exit 1
44
+ end
45
+
46
+ require 'casserver/utils'
47
+ require 'casserver/models'
48
+ require 'casserver/cas'
49
+ require 'casserver/views'
50
+ require 'casserver/controllers'
51
+
52
+ if $CONF[:authenticator].instance_of? Array
53
+ $CONF[:authenticator].each_index do |auth_index|
54
+ $CONF[:authenticator][auth_index] = HashWithIndifferentAccess.new($CONF[:authenticator][auth_index])
55
+ end
56
+ end
57
+
58
+ $AUTH = []
59
+ begin
60
+ # attempt to instantiate the authenticator
61
+ if $CONF[:authenticator].instance_of? Array
62
+ $CONF[:authenticator].each { |authenticator| $AUTH << authenticator[:class].constantize.new}
63
+ else
64
+ $AUTH << $CONF[:authenticator][:class].constantize.new
65
+ end
66
+ rescue NameError
67
+ if $CONF[:authenticator].instance_of? Array
68
+ $CONF[:authenticator].each do |authenticator|
69
+ if !authenticator[:source].nil?
70
+ # config.yml explicitly names source file
71
+ require authenticator[:source]
72
+ else
73
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
74
+ auth_rb = authenticator[:class].underscore.gsub('cas_server/', '')
75
+ require 'casserver/'+auth_rb
76
+ end
77
+ $AUTH << authenticator[:class].constantize.new
78
+ end
79
+ else
80
+ if !$CONF[:authenticator][:source].nil?
81
+ # config.yml explicitly names source file
82
+ require $CONF[:authenticator][:source]
83
+ else
84
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
85
+ auth_rb = $CONF[:authenticator][:class].underscore.gsub('cas_server/', '')
86
+ require 'casserver/'+auth_rb
87
+ end
88
+
89
+ $AUTH << $CONF[:authenticator][:class].constantize.new
90
+ end
91
+ end
92
+
93
+ $CONF[:public_dir] = {
94
+ :path => "/themes",
95
+ :dir => File.expand_path(File.dirname(__FILE__))+"/themes"
96
+ }
97
+
98
+ def CASServer.create
99
+ $LOG.info "Creating RubyCAS-Server..."
100
+ CASServer::Models::Base.establish_connection(CASServer::Conf.database)
101
+ CASServer::Models.create_schema
102
+
103
+ CASServer::Models::ServiceTicket.cleanup_expired(CASServer::Conf.service_ticket_expiry)
104
+ CASServer::Models::LoginTicket.cleanup_expired(CASServer::Conf.login_ticket_expiry)
105
+ CASServer::Models::ProxyGrantingTicket.cleanup_expired(CASServer::Conf.proxy_granting_ticket_expiry)
106
+ CASServer::Models::TicketGrantingTicket.cleanup_expired(CASServer::Conf.ticket_granting_ticket_expiry)
107
+ end
108
+
109
+
110
+ 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,47 @@
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
+ end
16
+
17
+ def extra_attributes
18
+ @extra_attributes
19
+ end
20
+
21
+ protected
22
+ def read_standard_credentials(credentials)
23
+ @username = credentials[:username]
24
+ @password = credentials[:password]
25
+ @service = credentials[:service]
26
+ @request = credentials[:request]
27
+ end
28
+
29
+ def extra_attributes_to_extract
30
+ if @options[:extra_attributes].kind_of? Array
31
+ attrs = @options[:extra_attributes]
32
+ elsif @options[:extra_attributes].kind_of? String
33
+ attrs = @options[:extra_attributes].split(',').collect{|col| col.strip}
34
+ else
35
+ $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.")
36
+ attrs = []
37
+ end
38
+
39
+ $LOG.debug("#{self.class.name} will try to extract the following extra_attributes: #{attrs.inspect}")
40
+ return attrs
41
+ end
42
+ end
43
+ end
44
+
45
+ class AuthenticatorError < Exception
46
+ end
47
+ end
@@ -0,0 +1,108 @@
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 authenticator configuration!" unless @options[:ldap]
32
+ raise CASServer::AuthenticatorError, "You must specify an ldap server in the configuration!" unless @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
+ @ldap.host = @options[:ldap][:server]
40
+ @ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
41
+
42
+ begin
43
+ if @options[:ldap][:auth_user]
44
+ bind_with_preauthentication
45
+ else
46
+ bind_directly
47
+ end
48
+ rescue Net::LDAP::LdapError => e
49
+ raise CASServer::AuthenticatorError,
50
+ "LDAP authentication failed with '#{e}'. Check your authenticator configuration."
51
+ end
52
+ end
53
+
54
+ protected
55
+ def default_username_attribute
56
+ "uid"
57
+ end
58
+
59
+ private
60
+ def preprocess_username
61
+ # add prefix to username, if prefix was specified in the config
62
+ @username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix]
63
+ end
64
+
65
+ def bind_with_preauthentication
66
+ # If an auth_user is specified, we will connect ("pre-authenticate") to the
67
+ # LDAP server using the authenticator account, and then attempt to bind as the
68
+ # user who is actually trying to authenticate. Note that you need to set up
69
+ # the special authenticator account first. Also, auth_user must be the authenticator
70
+ # user's full CN, which is probably not the same as their username.
71
+ #
72
+ # This pre-authentication process is necessary because binding can only be done
73
+ # using the CN, so having just the username is not enough. We connect as auth_user,
74
+ # and then try to find the target user's CN based on the given username. Then we bind
75
+ # as the target user to validate their credentials.
76
+
77
+ raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless
78
+ @options[:ldap][:auth_password]
79
+
80
+ @ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
81
+
82
+ username_attribute = options[:ldap][:username_attribute] || default_username_attribute
83
+
84
+ filter = Net::LDAP::Filter.construct(@options[:ldap][:filter]) if
85
+ @options[:ldap][:filter] && !@options[:ldap][:filter].blank?
86
+ username_filter = Net::LDAP::Filter.eq(username_attribute, @username)
87
+ if filter
88
+ filter &= username_filter
89
+ else
90
+ filter = username_filter
91
+ end
92
+
93
+ @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => filter)
94
+ end
95
+
96
+ def bind_directly
97
+ # When no auth_user is specified, we will try to connect directly as the user
98
+ # who is trying to authenticate. Note that for this to work, the username must
99
+ # be equivalent to the user's CN, and this is often not the case (for example,
100
+ # in Active Directory, the username is the 'sAMAccountName' attribute, while the
101
+ # user's CN is generally their full name.)
102
+
103
+ cn = @username
104
+
105
+ @ldap.authenticate(cn, @password)
106
+ @ldap.bind
107
+ end
108
+ 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,102 @@
1
+ require 'casserver/authenticators/base'
2
+
3
+ begin
4
+ require 'active_record'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'active_record'
8
+ end
9
+
10
+ # Authenticates against a plain SQL table.
11
+ #
12
+ # This assumes that all of your users are stored in a table that has a 'username'
13
+ # column and a 'password' column. When the user logs in, CAS conects to the
14
+ # database and looks for a matching username/password in the users table. If a
15
+ # matching username and password is found, authentication is successful.
16
+ #
17
+ # Any database backend supported by ActiveRecord can be used.
18
+ #
19
+ # Config example:
20
+ #
21
+ # authenticator:
22
+ # class: CASServer::Authenticators::SQL
23
+ # database:
24
+ # adapter: mysql
25
+ # database: some_database_with_users_table
26
+ # username: root
27
+ # password:
28
+ # server: localhost
29
+ # user_table: users
30
+ # username_column: username
31
+ # password_column: password
32
+ #
33
+ # When replying to a CAS client's validation request, the server will normally
34
+ # provide the client with the authenticated user's username. However it is now
35
+ # possible for the server to provide the client with additional attributes.
36
+ # You can configure the SQL authenticator to provide data from additional
37
+ # columns in the users table by listing the names of the columns under the
38
+ # 'extra_attributes' option. Note though that this functionality is experimental.
39
+ # It should work with RubyCAS-Client, but may or may not work with other CAS
40
+ # clients.
41
+ #
42
+ # For example, with this configuration, the 'full_name' and 'access_level'
43
+ # columns will be provided to your CAS clients along with the username:
44
+ #
45
+ # authenticator:
46
+ # class: CASServer::Authenticators::SQL
47
+ # database:
48
+ # adapter: mysql
49
+ # database: some_database_with_users_table
50
+ # user_table: users
51
+ # username_column: username
52
+ # password_column: password
53
+ # extra_attributes: full_name, access_level
54
+ #
55
+ class CASServer::Authenticators::SQL < CASServer::Authenticators::Base
56
+
57
+ def validate(credentials)
58
+ read_standard_credentials(credentials)
59
+
60
+ raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options
61
+ raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless @options[:database]
62
+
63
+ CASUser.establish_connection @options[:database]
64
+ CASUser.set_table_name @options[:user_table] || "users"
65
+
66
+ username_column = @options[:username_column] || 'username'
67
+ password_column = @options[:password_column] || 'password'
68
+
69
+ results = CASUser.find(:all, :conditions => ["#{username_column} = ? AND #{password_column} = ?", @username, @password])
70
+
71
+ if results.size > 0
72
+ $LOG.warn("#{self.class}: Multiple matches found for user #{@username.inspect}") if results.size > 1
73
+
74
+ unless @options[:extra_attributes].blank?
75
+ if results.size > 1
76
+ $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
77
+ else
78
+ user = results.first
79
+
80
+ @extra_attributes = {}
81
+ extra_attributes_to_extract.each do |col|
82
+ @extra_attributes[col] = user.send(col)
83
+ end
84
+
85
+ if @extra_attributes.empty?
86
+ $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
87
+ else
88
+ $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
89
+ end
90
+ end
91
+ end
92
+
93
+ return true
94
+ else
95
+ return false
96
+ end
97
+ end
98
+
99
+ class CASUser < ActiveRecord::Base
100
+ end
101
+
102
+ end