├── .gitignore
├── CHANGELOG
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── bin
└── rubycas-server
├── config.ru
├── config
├── config.example.yml
└── unicorn.rb
├── db
├── .gitignore
└── migrate
│ └── 001_create_initial_structure.rb
├── lib
├── casserver.rb
└── casserver
│ ├── authenticators
│ ├── active_directory_ldap.rb
│ ├── active_resource.rb
│ ├── authlogic_crypto_providers
│ │ ├── aes256.rb
│ │ ├── bcrypt.rb
│ │ ├── md5.rb
│ │ ├── sha1.rb
│ │ └── sha512.rb
│ ├── base.rb
│ ├── client_certificate.rb
│ ├── google.rb
│ ├── ldap.rb
│ ├── ntlm.rb
│ ├── open_id.rb
│ ├── sql.rb
│ ├── sql_authlogic.rb
│ ├── sql_encrypted.rb
│ ├── sql_md5.rb
│ ├── sql_rest_auth.rb
│ └── test.rb
│ ├── cas.rb
│ ├── localization.rb
│ ├── model.rb
│ ├── server.rb
│ ├── utils.rb
│ └── views
│ ├── _login_form.erb
│ ├── layout.erb
│ ├── login.erb
│ ├── proxy.builder
│ ├── proxy_validate.builder
│ ├── service_validate.builder
│ └── validate.erb
├── log
└── .gitignore
├── po
├── de_DE
│ └── rubycas-server.po
├── es_ES
│ └── rubycas-server.po
├── fr_FR
│ └── rubycas-server.po
├── ja_JP
│ └── rubycas-server.po
├── pl_PL
│ └── rubycas-server.po
├── pt_BR
│ └── rubycas-server.po
├── ru_RU
│ └── rubycas-server.po
├── rubycas-server.pot
├── zh_CN
│ └── rubycas-server.po
└── zh_TW
│ └── rubycas-server.po
├── public
└── themes
│ ├── cas.css
│ ├── notice.png
│ ├── ok.png
│ ├── simple
│ ├── bg.png
│ ├── favicon.png
│ ├── login_box_bg.png
│ ├── logo.png
│ └── theme.css
│ ├── urbacon
│ ├── bg.png
│ ├── login_box_bg.png
│ ├── logo.png
│ └── theme.css
│ └── warning.png
├── resources
├── diagrams
│ ├── basic_cas_single_signon_mechanism_diagram.png
│ └── basic_cas_single_signon_mechanism_diagram.svg
└── init.d.sh
├── rubycas-server.gemspec
├── setup.rb
├── spec
├── alt_config.yml
├── authenticators
│ ├── active_resource_spec.rb
│ └── ldap_spec.rb
├── casserver_spec.rb
├── default_config.yml
├── model_spec.rb
├── spec.opts
├── spec_helper.rb
└── utils_spec.rb
└── tasks
├── bundler.rake
├── db
└── migrate.rake
├── localization.rake
└── spec.rake
/.gitignore:
--------------------------------------------------------------------------------
1 | .svn
2 | config.yml
3 | *.db
4 | *.sqlite3
5 | *.swp
6 | *~
7 | *.pidaproject
8 | *.log
9 | *.mo
10 | pkg
11 | ssl
12 | custom/*
13 | .DS_Store
14 | /nbproject
15 | .irbrc_history
16 | resources/dev
17 | .rvmrc
18 | .bundle
19 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | === 1.0.1 :: In Progress...
2 |
3 | * NEW:
4 | * On startup the server now checks for a config.yml file in its own root directory,
5 | then in /etc/rubycas-server.
6 |
7 | * FIXED:
8 | * Specs now pass under Active Record 2.3.12
9 |
10 | === 1.0.0 :: 2011-08-03
11 |
12 | * NEW:
13 | * Rewrite to replace Camping/Picnic with Sinatra
14 | * Support for Ruby 1.9.2
15 | * Support for Active Record 3
16 |
17 | * CHANGED:
18 | * Google authenticator proxy configuration has been changed (see config.example.yml)
19 |
20 | === 0.8.0
21 |
22 | * NEW:
23 | * Support for localization via Ruby-GetText.
24 | See http://code.google.com/p/rubycas-server/wiki/Localization
25 | for details. [antono]
26 | * Switched to Picnic 0.8.x, so RubyCAS-Server is now based on Rack
27 | and Camping 2.0 and is now compatible with Passenger Phusion
28 | * Change to authenticator API: every authenticator now has a class 'setup'
29 | method that gets called at server startup. This is where class-level
30 | configuration should be done (e.g. establishing a database connection).
31 | This is different from the 'configure' method which gets called on a per-
32 | instance basis for each authenticator. [godfat]
33 | * Database connections are now automatically released back to the connection
34 | pool at the end of each request. This should allow the server to handle
35 | many more concurrent requests, since database connections are no longer left
36 | checked out of the pool.
37 | * Added new SQL authenticator (sql_rest_auth) compatible with the
38 | restful_authentication Rails plugin. [antono]
39 | * Re-licensed under the MIT License.
40 |
41 | * FIXED:
42 | * Fixed weird problems with loading controllers when using older versions of
43 | activesupport and/or rubygems.
44 | * Failure to connect to a service during a single sign out request is now
45 | handled gracefully.
46 | * Required gem dependencies have been re-enabled in the gemspec.
47 | * Authlogic authenticator files added to gemspec. [rajiv]
48 | * Authenticators are now instantiated on a per-request basis (rather than
49 | once at startup) to ensure thread safety.
50 |
51 | === 0.7.1 :: 2008-11-10
52 |
53 | * Fixed dependency loading problems introduced by upstream changes in RubyGems
54 | 1.3.1.
55 |
56 | === 0.7.0 :: 2008-11-04
57 |
58 | * NEW:
59 | * Implemented single-sign-out functionality as specified in CAS 3.3. See
60 | http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out.
61 | * It is now possible to configure Authenticators to return extra attributes
62 | to CAS clients alongside the username. For an example of how to do this see
63 | the included SQL authenticator. Also have a look at:
64 | http://groups.google.com/group/rubycas-server/browse_thread/thread/5eade3793cb590e9
65 | Note that extra attributes of type other than String or Numeric are serialized
66 | into YAML format before being sent along to the client.
67 | * Added an MD5-password version of the SQL authenticator for Drupal and any other
68 | database that stores its passwords in hashed form (thanks malcolmm).
69 | * Added new Google authenticator for authenticating against Google/GMail
70 | accounts.
71 |
72 | * CHANGED:
73 | * Service URIs are now automatically normalized. For example, if the service
74 | URI given to the server has a 'ticket' parameter, the ticket will now be
75 | automatically stripped. This is to avert any possible issues raised by
76 | misbehaving CAS clients (the CAS ticket should never be part of the service
77 | URI). Same goes for other CAS-related parameters like 'service', 'renew',
78 | and 'gateway'. Additionally, the trailing '/' and '?' characters are
79 | automatically stripped from URLs, since, for example, "http://google.com/"
80 | is almost certainly equivalent to "http://google.com".
81 | * The expire_sessions config variable is now respected -- ticket granting
82 | ticket cookies are set with an expiry datetime, so that the SSO session
83 | is effectively terminated once the ticket_granting_ticket_expiry period
84 | is reached.
85 | * If present, the HTTP_X_FORWARDED_FOR header is used for recording the
86 | client's address. This is useful when the server is running behind a reverse
87 | proxy, but it should not be considered authoritative since it can be
88 | easily spoofed.
89 | * The 'service' field in the 'casserver_st' table has been changed from
90 | VARCHAR(255) to TEXT in order to accomodate service URIs longer than 255
91 | characters (fixes issue #46).
92 | * The CAS XML responses are no longer whitespace-formatted (i.e. Markaby's
93 | auto-indentation has been turned off). Apparently the whitespace was
94 | causing problems with mod_auth_cas. See:
95 | http://groups.google.com/group/rubycas-server/browse_thread/thread/e482fe09999b73d3
96 | * When used without pre-authentication, the LDAP authenticator now tries to
97 | bind by searching for the given username in the LDAP directory based on the
98 | configured username_attribute. Prior to this change the authenticator
99 | attempted to bind with the LDAP server by assuming that the username credential
100 | matches the user's CN. This is no longer the case.
101 | * CAS responses to invalid requests (for example where required parameters
102 | are missing or incorrect) will now have HTTP status code 422. Internal server
103 | errors (where the server rather than the client is at fault) have error 500.
104 | Previously most responses had error code 200, regardless of their contents.
105 |
106 | * FIXED:
107 | * Fixed logout action to work properly with ActiveRecord 2.1 (eager loading behaviour
108 | was changed upstream forcing a change to the way we look for ProxyGrantingTickets
109 | to delete on logout).
110 | * When running under Mongrel, the USR2 signal should now restart the server as
111 | expected -- however currently this only works when the server is running
112 | in the foregaround. When daemonized, USR2 will shut down the server without
113 | restarting (see issue #58).
114 | * Fixed activerecord/activesupport gem load problems, hopefully once and for all
115 | (however picnic-0.7.0 is now required).
116 |
117 | === 0.6.0 :: 2008-03-28
118 |
119 | * Much of the supporting functionality that makes RubyCAS-Server
120 | act as a well-behaved Linux service has been abstracted out
121 | into its own library. This new library is called Picnic and is
122 | now a gem dependency for RubyCAS-Server. You can find out more about
123 | it at http://code.google.com/p/camping-picnic/.
124 | * The logout action will now accept a 'destination' parameter in lieu of
125 | 'service'. This means that if a 'destination' parameter is given with
126 | some URL, the logout action will show the login form, allowing the user
127 | to immedietly log back in to the service specified by 'destination'.
128 | * The logout action will now accept a 'url' parameter. If given, the logout
129 | page will show a message indicating that the CAS session has been terminated
130 | and instructing the user to click on a link to follow the given URL. If the
131 | 'url' parameter is given, the login form will NOT be shown on the logout
132 | page (see above).
133 | * When an authentication failure occurs (because the user submitted
134 | invalid credentials or the login ticket is missing), the server
135 | now returns a 401 (Unauthorized) response instead of 200.
136 | * An encryption-enabled version of the SQL authenticator is now
137 | available. For more info have a look at:
138 | http://code.google.com/p/rubycas-server/wiki/UsingTheSQLEncryptedAuthenticator
139 | * Better compatibility with Oracle databases. The database migration
140 | no longer tries to create tables with long names when long
141 | table names are not supported by the underlying database connector
142 | (issue #15).
143 | * The server now automatically removes leading and trailing whitespace from
144 | the username entered by users. Passwords however are left intact, with no
145 | whitespace removed.
146 | * The server can now be configured to automatically downcase the
147 | username entered by users (dowcase_username option). So if a user
148 | enters "JSmith", the system will convert it to "jsmith" if the
149 | downcase_username option is set to true.
150 | * The server can now be made to bind to a specific address. See the
151 | :bind_address option in the config.example.yml file.
152 | * Fixed bug with ActiveRecord 2.0.2 where service tickets were not
153 | being given a type (issue #37).
154 |
155 | === 0.5.1 :: 2007-12-20
156 |
157 | * Tickets generated by the server should now be a lot more secure.
158 | The random string generator used for generating tickets now uses
159 | Crypt::ISAAC. Tickets have also been extended in length; STs, PTs
160 | and LTs can now extend up to 32 characters, and PGTs and PGT-IOUs
161 | up to 64.
162 |
163 | === 0.5.0 :: 2007-09-20
164 |
165 | * Gateway requests should now be handled correctly. When the request to the
166 | login page is made with gateway=true as one of the parameters, the CAS
167 | server will immediately redirect back to the target service along with
168 | a service ticket if an SSO session exists for the user (or without a
169 | service ticket if there is no pre-existing SSO session).
170 | Note that if you are using RubyCAS-Client and want gatewaying, you will
171 | need to upgrade it to 1.1.0 as gatewaying was broken in prior versions.
172 | * If gateway=true is specified as part of the logout URI, the server will
173 | log the user out and immediately redirect them back to the specified
174 | service. In other words, you can now do "gatewayed logouts" as well
175 | as logins.
176 | * A login ticket can now be remotely requested from the server by placing
177 | a POST request to '/loginTicket'.
178 | * The login view can now be made to return only the login form. This is
179 | done by adding the 'onlyLoginForm' parameter to the '/login' request.
180 | Optionally, a 'submitToURI' parameter can be supplied to force the login
181 | form to submit to the given URI (otherwise the server will try to figure
182 | out the full URI to its own login controller). This functionality may be
183 | useful when you want to embed the login form in some external page, as
184 | an IFRAME otherwise.
185 | * Custom views can now be used to override the default Markaby templates
186 | by specifying a 'custom_views_file' option in the configuration. See
187 | custom_views.example.rb. [jzylks]
188 | * Table names have been shortened to work with Oracle. A migration has
189 | been added that should do the shortening for you the first time you run
190 | this new RubyCAS-Server version.
191 | * Multiple authenticators can now be specified. During authentication,
192 | credentials are presented to the first authenticator, then the second,
193 | and so on, until the user is validated by any one authenticator or fails
194 | validation for all of them. [jzylks]
195 | * When using webrick, you can now run with SSL disabled by omitting the
196 | ssl_cert and ssl_key parameters.
197 | * Changed incorrect MySQL example database configuration -- option should
198 | be 'host:' not 'server:' (issue #22).
199 |
200 | === 0.4.2 :: 2007-07-26
201 |
202 | * The LDAP/AD authenticator has been largely re-written. The code is a bit
203 | cleaner now, and should work better with non-Active Directory LDAP servers
204 | (although this has yet to be tested since I don't have access to a non-AD
205 | LDAP server).
206 | * The validate() method in your authenticators now receives a :service element
207 | (in addition to :username, and :password). This is simply the service
208 | url (if any) specified in the user's CAS request. If you call
209 | read_standard_credentials(credentials) at the top of your validator, the value
210 | will also be available as @service along with @username and @password.
211 | * By request, a :username_prefix option has been added to the ldap
212 | configuration. If entered, this string will be automatically prefixed to
213 | the username entered by the user.
214 | * A bug having to do with handling authenticator errors has been fixed.
215 | Any authenticator error messages should now be correctly shown on the
216 | login page.
217 | * Minor improvements to error messages having to do with login tickets.
218 | They're a bit more prescriptive now, explaining to the user what steps
219 | they should take to correct the error.
220 |
221 | === 0.4.1 :: 2007-06-07
222 |
223 | * This release restores compatiblity with older versions of rubygems
224 | (pre-0.9.0). To achieve this, we alias the 'gem' method to the old
225 | 'require_gem' if 'gem' is not already defined.
226 | * rubycas-server-ctl will now quiety delete an orphaned .pid file
227 | instead complaining loudly and refusing to start up.
228 | * Fixed minor bug in rubycas-server-ctl that sometimes incorrectly reported
229 | startup problems when in fact the server had started just fine.
230 |
231 |
232 | === 0.4.0 :: 2007-06-05
233 |
234 | * Added rubycas-server-ctl script for controlling daemonized server.
235 | * rubygems-0.9.0 or later is now required.
236 | * Added system startup script to be used in /etc/init.d on Linux systems.
237 | * Authenticator can now be loaded from an external file using the 'source'
238 | configuration option.
239 | * Better preemptive detection of startup problems with mongrel.
240 | * User now sees an error message if the service URI is not a valid URI (i.e.
241 | if it's not URI-encoded or otherwise malformed).
242 |
243 |
244 | === 0.3.0 :: 2007-03-29
245 |
246 | * Fixed glaring security problem with LDAP/AD Authenticator where under some
247 | circumstances blank passwords were accepted as valid.
248 | * Autocomplete has been turned off on the password field for better security.
249 | In the future we may allow autocomplete to be re-enabled using a
250 | configuration setting.
251 | * When the user visits the login page and is already authenticated (i.e. they
252 | have a valid ticket granting cookie), a message is shown at the top
253 | indicating that they are already logged in.
254 | * sqlite3-ruby is no longer required by the gem as a dependency. The user
255 | must now install it manually prior to installing rubycas-server. The
256 | building of sqlite3 native extensions appears to be somewhat flakey
257 | and probably defeats the original purpose of using it (which was
258 | to have a CAS server up and running with no additional DB configuration).
259 | We will use MySQL as the default database adapter instead, since it does
260 | not require additional libraries and many users will have a MySQL server
261 | already available.
262 | * Fixed bug that was causing all proxy-granting tickets to be deleted whenever
263 | any user logged out. Only the PGTs for the user that is logging out are now
264 | being deleted.
265 | * Trailing slashes in service URLs are now ignored when validating service
266 | and proxy tickets (e.g. "http://www.google.com" and "http://www.google.com/"
267 | are now considered to be the same service URL).
268 | * Authenticators now raise AuthenticatorError exceptions when encountering
269 | a problem/error. This makes it easier to send feedback to the user.
270 | However, other exceptions should still be raised when errors ought
271 | not be recoverable (i.e. programming errors).
272 | * Fixed serious vulnerability in LDAP authenticator where under some
273 | cirumstances the user could just enter '*' as their username to match
274 | any username. The LDAP authenticator will now refuse to process logins
275 | with usernames that contain the characters * ( ) \ / and the NULL
276 | character \0.
277 | * Views are no longer xhtml-validated. Markaby's auto-validation was turned
278 | off to allow for use of the autocomplete property on inputs, since this is
279 | the only viable way of turning off password storage in IE and Firefox at
280 | the page level.
281 | * You can now limit the maximum length of a login session by setting the
282 | expire_sessions config setting to true.
283 | * Fixed some minor bugs in the login view.
284 |
285 |
286 | === 0.2.0 :: 2007-03-20
287 |
288 | * ruby-casserver now behaves more like a real command-line app, accepting
289 | various command line arguments including -h (help), -v (version), -c (use
290 | an alternate config.yml), and -d (daemonize, when using webrick or mongrel
291 | mode).
292 | * Special characters in CAS XML responses are now properly encoded into XML
293 | entities
294 | * CAS XML responses are no longer auto-indented... Markaby's indentation
295 | seemed to be causing problems with the PHP CAS client.
296 | * Misc minor bug fixes/cleanup.
297 |
298 |
299 | === 0.1.0 :: 2007-03-01
300 |
301 | * First public release.
302 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 | gemspec
3 |
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | rubycas-server (1.0)
5 | activerecord (~> 2.3.12)
6 | activesupport (~> 2.3.12)
7 | crypt-isaac (~> 0.9.1)
8 | gettext (~> 2.1.0)
9 | sinatra (~> 1.0)
10 |
11 | GEM
12 | remote: http://rubygems.org/
13 | specs:
14 | activerecord (2.3.12)
15 | activesupport (= 2.3.12)
16 | activeresource (2.3.12)
17 | activesupport (= 2.3.12)
18 | activesupport (2.3.12)
19 | capybara (0.4.1.2)
20 | celerity (>= 0.7.9)
21 | culerity (>= 0.2.4)
22 | mime-types (>= 1.16)
23 | nokogiri (>= 1.3.3)
24 | rack (>= 1.0.0)
25 | rack-test (>= 0.5.4)
26 | selenium-webdriver (>= 0.0.27)
27 | xpath (~> 0.1.3)
28 | celerity (0.8.8)
29 | childprocess (0.1.7)
30 | ffi (~> 0.6.3)
31 | crypt-isaac (0.9.1)
32 | culerity (0.2.15)
33 | diff-lcs (1.1.2)
34 | ffi (0.6.3)
35 | rake (>= 0.8.7)
36 | gettext (2.1.0)
37 | locale (>= 2.0.5)
38 | json_pure (1.5.1)
39 | locale (2.0.5)
40 | mime-types (1.16)
41 | net-ldap (0.1.1)
42 | nokogiri (1.4.4)
43 | rack (1.2.1)
44 | rack-test (0.5.7)
45 | rack (>= 1.0)
46 | rake (0.8.7)
47 | rspec (2.5.0)
48 | rspec-core (~> 2.5.0)
49 | rspec-expectations (~> 2.5.0)
50 | rspec-mocks (~> 2.5.0)
51 | rspec-core (2.5.1)
52 | rspec-expectations (2.5.0)
53 | diff-lcs (~> 1.1.2)
54 | rspec-mocks (2.5.0)
55 | rubyzip (0.9.4)
56 | selenium-webdriver (0.1.3)
57 | childprocess (~> 0.1.5)
58 | ffi (~> 0.6.3)
59 | json_pure
60 | rubyzip
61 | sinatra (1.1.3)
62 | rack (~> 1.1)
63 | tilt (< 2.0, >= 1.2.2)
64 | sqlite3 (1.3.3)
65 | tilt (1.2.2)
66 | xpath (0.1.3)
67 | nokogiri (~> 1.3)
68 |
69 | PLATFORMS
70 | ruby
71 |
72 | DEPENDENCIES
73 | activeresource (~> 2.3.12)
74 | capybara
75 | net-ldap (~> 0.1.1)
76 | rack-test
77 | rspec
78 | rspec-core
79 | rubycas-server!
80 | sqlite3 (~> 1.3.1)
81 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Portions of RubyCAS-Server contributed by Matt Zukowski are copyright (c) 2009 Urbacon Ltd.
2 | Other portions are copyright of their respective authors.
3 |
4 | The MIT License
5 |
6 | Permission is hereby granted, free of charge, to any person
7 | obtaining a copy of this software and associated documentation
8 | files (the "Software"), to deal in the Software without
9 | restriction, including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the
12 | Software is furnished to do so, subject to the following
13 | conditions:
14 |
15 | The above copyright notice and this permission notice shall be
16 | included in all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 | OTHER DEALINGS IN THE SOFTWARE.
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MOVED!
2 |
3 | This repo has been moved to https://github.com/rubycas/rubycas-server.
4 |
5 | The fork you are looking at is no longer updated. Please change your git remotes to the new rubycas URL.
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | Dir['tasks/**/*.rake'].each { |rake| load rake }
--------------------------------------------------------------------------------
/bin/rubycas-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Enables UTF-8 compatibility in ruby 1.8.
4 | $KCODE = 'u' if RUBY_VERSION < '1.9'
5 |
6 | require 'rubygems'
7 |
8 | $:.unshift File.dirname(__FILE__) + "/../lib"
9 |
10 | if ARGV.join.match('--debugger')
11 | require 'ruby-debug'
12 | puts
13 | puts "=> Debugger Enabled"
14 | end
15 |
16 | if ARGV.join.match('-c')
17 | c = ARGV.join.match(/-c\s*([^\s]+)/)
18 | if (c && c[1])
19 | ENV['CONFIG_FILE'] = c[1]
20 | puts
21 | puts "=> Using custom config file #{ENV['CONFIG_FILE'].inspect}"
22 | else
23 | $stderr.puts("To specify a custom config file use `rubycas-server -c path/to/config_file_name.yml`.")
24 | exit
25 | end
26 | end
27 |
28 | require 'casserver'
29 |
30 | CASServer::Server.run!
31 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 |
4 | $:.unshift "#{File.dirname(__FILE__)}/lib"
5 | require "casserver"
6 |
7 | use Rack::ShowExceptions
8 | use Rack::Runtime
9 | use Rack::CommonLogger
10 |
11 | run CASServer::Server.new
12 |
--------------------------------------------------------------------------------
/config/config.example.yml:
--------------------------------------------------------------------------------
1 | # IMPORTANT NOTE ABOUT YAML CONFIGURATION FILES
2 | # ---> Be sure to use spaces instead of tabs for indentation. YAML is
3 | # white-space sensitive!
4 |
5 | ##### SERVER SETUP ################################################################
6 |
7 | # There are several ways to run RubyCAS-Server:
8 | #
9 | # webrick -- stand-alone WEBrick server; should work out-of-the-box; this is
10 | # the default method, but probably not suited for high-traffic usage
11 | # mongrel -- stand-alone Mongrel server; fast, but you'll need to install
12 | # and compile Mongrel and run it behind an https reverse proxy like
13 | # Pound or Apache 2.2's mod_proxy (since Mongrel cannot serve out
14 | # over SSL on its own).
15 | # passenger -- served out by Apache via the mod_rails/mod_rack module
16 | # (see http://www.modrails.com/)
17 | #
18 | # The following are exampe configurations for each of these three methods:
19 | #
20 |
21 |
22 | ###
23 | ### WEBrick example
24 | ###
25 | # WEBrick is a simple, all-Ruby web server. This is the easiest method for running
26 | # RubyCAS-Server. All you need is an SSL certificate (enter its path under the
27 | # ssl_cert option). WEBrick is fine for sites with low to medium traffic, but for
28 | # high-performance scenarios you may want to look into deploying using Mongrel
29 | # or Passenger.
30 |
31 | server: webrick
32 | port: 443
33 | ssl_cert: /path/to/your/ssl.pem
34 |
35 | # If your private key is in a separate file from the cert
36 |
37 | #ssl_key: /path/to/your/private_key.pem
38 |
39 | # If you do not already have an SSL certificate and would like to automatically
40 | # generate one, run the "generate_ssl_certificate" rake task and use the following
41 | # settings:
42 |
43 | # ssl_cert: ssl/cert.pem
44 | # ssl_key: ssl/key.pem
45 |
46 |
47 | # By default the login page will be available at the root path
48 | # (e.g. https://login.example.net/). The uri_path option lets you serve it from a
49 | # different path (e.g. https://login.example.net/cas).
50 |
51 | #uri_path: /cas
52 |
53 |
54 | # This lets you bind the server to a specific address. Use 0.0.0.0 to listen on
55 | # all available interfaces (this is the default).
56 |
57 | #bind_address: 0.0.0.0
58 |
59 |
60 | ###
61 | ### Mongrel example
62 | ###
63 | # Mongrel is much faster than WEBrick, but there are two caveats:
64 | # 1. Since Mongrel can't serve out encrypted HTTP on its own (and CAS requires this),
65 | # you will have to set up a reverse proxy like Pound or Apache's mod_proxy and
66 | # route through it requests to the Mongrel server. So for example,
67 | # your Pound server will receive all of the requests to RubyCAS-Server on port 443,
68 | # and forward them to the Mongrel server listening on port 11011.
69 | # 2. Some of Mongrel's components are compiled into native binaries, so if you are
70 | # installing on Linux, make sure you have all of the standard build tools
71 | # available. The binaries should be automatically compiled for you when you
72 | # install the mogrel gem (if you're runnings Windows, pre-compiled
73 | # binaries will be downloaded and installed, so don't worry about this).
74 |
75 | #server: mongrel
76 | #port: 11011
77 |
78 |
79 | # Bind the server to a specific address. Use 0.0.0.0 to listen on all
80 | # available interfaces (this is the default).
81 |
82 | #bind_address: 0.0.0.0
83 |
84 | ### Reverse proxy configuration examples
85 | # If you're using mod_proxy, your Apache vhost config should look something like this:
86 | #
87 | # Listen 443
88 | #
89 | # ServerAdmin admin@example.net
90 | # ServerName login.example.net
91 | #
92 | # SSLEngine On
93 | # SSLCertificateFile /etc/apache2/ssl.crt/example.pem
94 | #
95 | # # Don't do forward proxying, we only want reverse proxying
96 | # ProxyRequests Off
97 | #
98 | #
99 | # Order allow,deny
100 | # Allow from all
101 | # BalancerMember http://127.0.0.1:11011
102 | #
103 | #
104 | #
105 | # For Pound, the config should be something like:
106 | #
107 | # ListenHTTPS
108 | # Address 0.0.0.0
109 | # Port 11011
110 | # Cert "/etc/ssl/example.pem"
111 | #
112 | # Service
113 | # BackEnd
114 | # Address localhost
115 | # Port 443
116 | # End
117 | # End
118 | # End
119 |
120 |
121 | ###
122 | ### Phusion Passenger (running under Apache configured for SSL)
123 | ###
124 |
125 | # No additional configuration is requried to run RubyCAS-Server under
126 | # passsenger. Just follow the normal instructions for a Passenger app
127 | # (see http://www.modrails.com/).
128 | #
129 | # Here's an example Apache vhost config for RubyCAS-Server and Passenger:
130 | #
131 | # Listen 443
132 | #
133 | # ServerAdmin admin@example.net
134 | # ServerName login.example.net
135 | #
136 | # SSLEngine On
137 | # SSLCertificateFile /etc/apache2/ssl.crt/example.pem
138 | #
139 | # RailsAutoDetect off
140 | #
141 | # DocumentRoot /usr/lib/ruby/gems/1.8/gems/rubycas-server-0.8.0/public
142 | #
143 | #
144 | # AllowOverride all
145 | # Allow from all
146 | #
147 | #
148 | #
149 |
150 |
151 | ##### DATABASE #################################################################
152 |
153 | # Set up the database connection. Make sure that this database is secure!
154 | #
155 | # By default, we use MySQL, since it is widely used and does not require any
156 | # additional ruby libraries besides ActiveRecord.
157 | #
158 | # With MySQL, your config would be something like the following:
159 | # (be sure to create the casserver database in MySQL beforehand,
160 | # i.e. `mysqladmin -u root create casserver`)
161 |
162 | database:
163 | adapter: mysql
164 | database: casserver
165 | username: root
166 | password:
167 | host: localhost
168 | reconnect: true
169 |
170 | # IMPORTANT! By default, the server can handle up to ~5 concurrent requests
171 | # (without queuing). You can increase this by setting the database connection
172 | # pool size to a higher number. For example, to handle up to ~10 concurrent
173 | # requests:
174 | #
175 | #database:
176 | # pool: 10
177 | # adapter: mysql
178 | # database: casserver
179 | # username: root
180 | # password:
181 | # host: localhost
182 |
183 | #
184 | # Instead of MySQL you can use SQLite3, PostgreSQL, MSSQL, or anything else
185 | # supported by ActiveRecord.
186 | #
187 | # With SQLite3 (which does not require a separate database server), your
188 | # configuration would look something like the following (don't forget to install
189 | # the sqlite3-ruby gem beforehand!):
190 |
191 | #database:
192 | # adapter: sqlite3
193 | # database: /var/lib/casserver.db
194 |
195 |
196 | # By default RubyCAS-Server will run migrations at every startup to ensure
197 | # that its database schema is up-to-date. To disable this behaviour set
198 | # the following option to true:
199 |
200 | #disable_auto_migrations: true
201 |
202 | ##### AUTHENTICATION ###########################################################
203 |
204 | # Configure how username/passwords are validated.
205 | #
206 | # !!! YOU MUST CONFIGURE AT LEAST ONE OF THESE AUTHENTICATION METHODS !!!
207 | #
208 | # There are several built-in methods for authentication:
209 | # SQL, ActiveDirectory, LDAP, and GoogleAccounts. If none of these work for you,
210 | # it is relatively easy to write your own custom Authenticator class (see below).
211 | #
212 | # === SQL Authentication =======================================================
213 | #
214 | # The simplest method is to validate against a SQL database. This assumes
215 | # that all of your users are stored in a table that has a 'username' column
216 | # and a 'password' column. When the user logs in, CAS connects to this database
217 | # and looks for a matching username/password in the users table. If a matching
218 | # username and password is found, authentication is successful.
219 | #
220 | # If you prefer to have your passwords stored in an encrypted form, have a
221 | # look at the SQLEncrypted authenticator:
222 | # http://code.google.com/p/rubycas-server/wiki/UsingTheSQLEncryptedAuthenticator
223 | #
224 | # If your users table stores passwords with MD5 hashing (for example as with
225 | # Drupal) try using the SQLMd5 version of the SQL authenticator.
226 | #
227 | # Example:
228 | #
229 | #authenticator:
230 | # class: CASServer::Authenticators::SQL
231 | # database:
232 | # adapter: mysql
233 | # database: some_database_with_users_table
234 | # username: root
235 | # password:
236 | # host: localhost
237 | # user_table: users
238 | # username_column: username
239 | # password_column: password
240 | #
241 | # When replying to a CAS client's validation request, the server will normally
242 | # provide the client with the authenticated user's username. However it is
243 | # possible for the server to provide the client with additional attributes.
244 | # You can configure the SQL authenticator to provide data from additional
245 | # columns in the users table by listing the names of the columns under the
246 | # 'extra_attributes' option. Note though that this functionality is experimental.
247 | # It should work with RubyCAS-Client, but may or may not work with other CAS
248 | # clients.
249 | #
250 | # For example, with this configuration, the 'full_name' and 'access_level'
251 | # columns will be provided to your CAS clients along with the username:
252 | #
253 | #authenticator:
254 | # class: CASServer::Authenticators::SQL
255 | # database:
256 | # adapter: mysql
257 | # database: some_database_with_users_table
258 | # user_table: users
259 | # username_column: username
260 | # password_column: password
261 | # extra_attributes: full_name, access_level
262 | #
263 | #
264 | #
265 | # === Google Authentication ====================================================
266 | #
267 | # The Google authenticator allows users to log in to your CAS server using
268 | # their Google account credentials (i.e. the same email and password they
269 | # would use to log in to Google services like Gmail). This authenticator
270 | # requires no special configuration -- just specify its class name:
271 | #
272 | #authenticator:
273 | # class: CASServer::Authenticators::Google
274 | #
275 | # If you are behind an http proxy, you can try specifying proxy settings as follows:
276 | #
277 | #authenticator:
278 | # class: CASServer::Authenticators::Google
279 | # proxy:
280 | # host: your-proxy-server
281 | # port: 8080
282 | # username: nil
283 | # password: nil
284 | #
285 | # Note that as with all authenticators, it is possible to use the Google
286 | # authenticator alongside other authenticators. For example, CAS can first
287 | # attempt to validate the account with Google, and if that fails, fall back
288 | # to some other local authentication mechanism.
289 | #
290 | # For example:
291 | #
292 | #authenticator:
293 | # - class: CASServer::Authenticators::Google
294 | # - class: CASServer::Authenticators::SQL
295 | # database:
296 | # adapter: mysql
297 | # database: some_database_with_users_table
298 | # username: root
299 | # password:
300 | # host: localhost
301 | # user_table: user
302 | # username_column: username
303 | # password_column: password
304 | #
305 | #
306 | # === ActiveDirectory Authentication ===========================================
307 | #
308 | # This method authenticates against Microsoft's Active Directory using LDAP.
309 | # You must configure the ActiveDirectory server, and base DN. The port number
310 | # and LDAP filter are optional. You must also enter a CN and password
311 | # for a special "authenticator" user. This account is used to log in to
312 | # the ActiveDirectory server and search LDAP. This does not have to be an
313 | # administrative account -- it only has to be able to search for other
314 | # users.
315 | #
316 | # Note that the auth_user parameter must be the user's CN (Common Name).
317 | # In Active Directory, the CN is genarally the user's full name, which is usually
318 | # NOT the same as their username (sAMAccountName).
319 | #
320 | # For example:
321 | #
322 | #authenticator:
323 | # class: CASServer::Authenticators::ActiveDirectoryLDAP
324 | # ldap:
325 | # host: ad.example.net
326 | # port: 389
327 | # base: dc=example,dc=net
328 | # filter: (objectClass=person)
329 | # auth_user: authenticator
330 | # auth_password: itsasecret
331 | #
332 | # A more complicated example, where the authenticator will use TLS encryption,
333 | # will ignore users with disabled accounts, and will pass on the 'cn' and 'mail'
334 | # attributes to CAS clients:
335 | #
336 | #authenticator:
337 | # class: CASServer::Authenticators::ActiveDirectoryLDAP
338 | # ldap:
339 | # host: ad.example.net
340 | # port: 636
341 | # base: dc=example,dc=net
342 | # filter: (objectClass=person) & !(msExchHideFromAddressLists=TRUE)
343 | # auth_user: authenticator
344 | # auth_password: itsasecret
345 | # encryption: simple_tls
346 | # extra_attributes: cn, mail
347 | #
348 | # It is possible to authenticate against Active Directory without the
349 | # authenticator user, but this requires that users type in their CN as
350 | # the username rather than typing in their sAMAccountName. In other words
351 | # users will likely have to authenticate by typing their full name,
352 | # rather than their username. If you prefer to do this, then just
353 | # omit the auth_user and auth_password values in the above example.
354 | #
355 | #
356 | # === LDAP Authentication ======================================================
357 | #
358 | # This is a more general version of the ActiveDirectory authenticator.
359 | # The configuration is similar, except you don't need an authenticator
360 | # username or password. The following example has been reported to work
361 | # for a basic OpenLDAP setup.
362 | #
363 | #authenticator:
364 | # class: CASServer::Authenticators::LDAP
365 | # ldap:
366 | # host: ldap.example.net
367 | # port: 389
368 | # base: dc=example,dc=net
369 | # username_attribute: uid
370 | # filter: (objectClass=person)
371 | #
372 | # If you need more secure connections via TSL, specify the 'encryption'
373 | # option and change the port. This example also forces the authenticator
374 | # to connect using a special "authenticator" user with the given
375 | # username and password (see the ActiveDirectoryLDAP authenticator
376 | # explanation above):
377 | #
378 | #authenticator:
379 | # class: CASServer::Authenticators::LDAP
380 | # ldap:
381 | # host: ldap.example.net
382 | # port: 636
383 | # base: dc=example,dc=net
384 | # filter: (objectClass=person)
385 | # encryption: simple_tls
386 | # auth_user: cn=admin,dc=example,dc=net
387 | # auth_password: secret
388 | #
389 | # If you need additional data about the user passed to the client (for example,
390 | # their 'cn' and 'mail' attributes, you can specify the list of attributes
391 | # under the extra_attributes config option:
392 | #
393 | #authenticator:
394 | # class: CASServer::Authenticators::LDAP
395 | # ldap:
396 | # host: ldap.example.net
397 | # port: 389
398 | # base: dc=example,dc=net
399 | # filter: (objectClass=person)
400 | # extra_attributes: cn, mail
401 | #
402 | # Note that the above functionality is somewhat limited by client compatibility.
403 | # See the SQL authenticator notes above for more info.
404 | #
405 | #
406 | # === Custom Authentication ====================================================
407 | #
408 | # It should be relatively easy to write your own Authenticator class. Have a look
409 | # at the built-in authenticators in the casserver/authenticators directory. Your
410 | # authenticator should extend the CASServer::Authenticators::Base class and must
411 | # implement a validate() method that takes a single hash argument. When the user
412 | # submits the login form, the username and password they entered is passed to
413 | # validate() as a hash under :username and :password keys. In the future, this
414 | # hash might also contain other data such as the domain that the user is logging
415 | # in to.
416 | #
417 | # To use your custom authenticator, specify it's class name and path to the
418 | # source file in the authenticator section of the config. Any other parameters
419 | # you specify in the authenticator configuration will be passed on to the
420 | # authenticator and made availabe in the validate() method as an @options hash.
421 | #
422 | # Example:
423 | #
424 | #authenticator:
425 | # class: FooModule::MyCustomAuthenticator
426 | # source: /path/to/source.rb
427 | # option_a: foo
428 | # another_option: yeeha
429 | #
430 | # === Multiple Authenticators ==================================================
431 | #
432 | # If you need to have more than one source for authentication, such as an LDAP
433 | # directory and a database, you can use multiple authenticators by making
434 | # :authenticator an array of authenticators.
435 | #
436 | #authenticator:
437 | # -
438 | # class: CASServer::Authenticators::ActiveDirectoryLDAP
439 | # ldap:
440 | # host: ad.example.net
441 | # port: 389
442 | # base: dc=example,dc=net
443 | # filter: (objectClass=person)
444 | # -
445 | # class: CASServer::Authenticators::SQL
446 | # database:
447 | # adapter: mysql
448 | # database: some_database_with_users_table
449 | # username: root
450 | # password:
451 | # host: localhost
452 | # user_table: user
453 | # username_column: username
454 | # password_column: password
455 | #
456 | # During authentication, the user credentials will be checked against the first
457 | # authenticator and on failure fall through to the second authenticator.
458 | #
459 |
460 |
461 | ##### LOOK & FEEL ##############################################################
462 |
463 | # Set the path to the theme directory that determines how your CAS pages look.
464 | #
465 | # Custom themes are not well supported yet, but will be in the near future. In
466 | # the meantime, if you want to create a custom theme, you can create a
467 | # subdirectory under the CASServer's themes dir (for example,
468 | # '/usr/lib/ruby/1.8/gems/casserver-xxx/public/themes', if you installed CASServer
469 | # on Linux as a gem). A theme is basically just a theme.css file that overrides
470 | # the themes/cas.css styles along with a collection of image files
471 | # like logo.png and bg.png.
472 | #
473 | # By default, we use the 'simple' theme which you can find in themes/simple.
474 | theme: simple
475 |
476 | # The name of your company/organization. This will show up on the login page.
477 | organization: CAS
478 |
479 | # A short bit of text that shows up on the login page. You can make this blank
480 | # if you prefer to have no extra text shown at the bottom of the login box.
481 | infoline: Powered by RubyCAS-Server
482 |
483 | # Custom views directory. If set, this will be used instead of 'lib/casserver/views'.
484 | #custom_views: /path/to/custom/views
485 |
486 | # Custom public directory. If set, static content (css, etc.) will be served from here rather
487 | # than from rubycas-server's internal 'public' directory (but be mindful of any overriding
488 | # settings you may have in your web server's config).
489 | #public_dir: /path/to/custom/public
490 |
491 | ##### LOCALIZATION (L10N) #######################################################
492 | # The server will attempt to detect the user's locale and show text in the
493 | # appropriate language based on:
494 | #
495 | # 1. The 'lang' URL parameter (if any)
496 | # 2. The 'lang' cookie (if any)
497 | # 3. The HTTP_ACCEPT_LANGUAGE header supplied by the user's browser.
498 | # 4. The HTTP_USER_AGENT header supplied by the user's browser.
499 | #
500 | # If the locale cannot be established based on one of the above checks (in the
501 | # shown order), then the below 'default_locale' option will be used.
502 | #
503 | # The format is the same as standard linux locales (langagecode_COUNTRYCODE):
504 | #
505 | # ru_RU - Russian, Russia
506 | # eo_AQ - Esperanto, Antarctica
507 | #
508 | # It will also work if you leave out the region (i.e. just "ru" for Russian,
509 | # "eo" for Esperanto).
510 | #
511 | # If you are interested in contributing new translations or have corrections
512 | # to the existing translations, see
513 | # http://code.google.com/p/rubycas-server/wiki/HowToContribueTranslations
514 | #
515 | default_locale: en
516 |
517 | ##### LOGGING ##################################################################
518 |
519 | # Configure general logging. This log is where you'll want to look in case of
520 | # problems.
521 | #
522 | # You may want to change the file to something like /var/log/casserver.log
523 | # Set the level to DEBUG if you want more detailed logging.
524 |
525 | log:
526 | file: /var/log/casserver.log
527 | level: INFO
528 |
529 |
530 | # If you want full database logging, uncomment this next section.
531 | # Every SQL query will be logged here. This is useful for debugging database
532 | # problems.
533 |
534 | #db_log:
535 | # file: /var/log/casserver_db.log
536 |
537 |
538 | # Setting the following option to true will disable CLI output to stdout.
539 | # i.e. this will get rid of messages like ">>> Redirecting RubyCAS-Server log..."
540 | # This is useful when, for example, you're running rspecs.
541 |
542 | #quiet: true
543 |
544 |
545 | ##### SINGLE SIGN-OUT ##########################################################
546 |
547 | # When a user logs in to a CAS-enabled client application, that application
548 | # generally opens its own local user session. When the user then logs out
549 | # through the CAS server, each of the CAS-enabled client applications need
550 | # to be notified so that they can close their own local sessions for that user.
551 | #
552 | # Up until recently this was not possible within CAS. However, a method for
553 | # performing this notification was recently added to the protocol (in CAS 3.1).
554 | # This works exactly as described above -- when the user logs out, the CAS
555 | # server individually contacts each client service and notifies it of the
556 | # logout. Currently not all client applications support this, so this
557 | # behaviour is disabled by default. To enable it, uncomment the following
558 | # configuration line. Note that currently it is not possible to enable
559 | # or disable single-sign-out on a per-service basis, but this functionality
560 | # is planned for a future release.
561 |
562 | #enable_single_sign_out: true
563 |
564 |
565 | ##### OTHER ####################################################################
566 |
567 | # You can set various ticket expiry times (specify the value in seconds).
568 |
569 | # Unused login and service tickets become unusable this many seconds after
570 | # they are created. (Defaults to 5 minutes)
571 |
572 | #maximum_unused_login_ticket_lifetime: 300
573 | #maximum_unused_service_ticket_lifetime: 300
574 |
575 | # The server must periodically delete old tickets (login tickets, service tickets
576 | # proxy-granting tickets, and ticket-granting tickets) to prevent buildup of
577 | # stale data. This effectively limits the maximum length of a CAS session to
578 | # the lifetime given here (in seconds). (Defaults to 48 hours)
579 | #
580 | # Note that this limit is not enforced on the client side; it refers only to the
581 | # the maximum lifetime of tickets on the CAS server.
582 |
583 | #maximum_session_lifetime: 172800
584 |
585 |
586 | # If you want the usernames entered on the login page to be automatically
587 | # downcased (converted to lowercase), enable the following option. When this
588 | # option is set to true, if the user enters "JSmith" as their username, the
589 | # system will automatically
590 | # convert this to "jsmith".
591 |
592 | #downcase_username: true
593 |
--------------------------------------------------------------------------------
/config/unicorn.rb:
--------------------------------------------------------------------------------
1 | # Sample configuration file for Unicorn (not Rack)
2 | #
3 | # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
4 | # documentation.
5 | SINATRA_ROOT = `pwd`.strip
6 |
7 | # Use at least one worker per core if you're on a dedicated server,
8 | # more will usually help for _short_ waits on databases/caches.
9 | worker_processes 3
10 |
11 | # Help ensure your application will always spawn in the symlinked
12 | # "current" directory that Capistrano sets up.
13 | working_directory SINATRA_ROOT # available in 0.94.0+
14 |
15 | # listen on both a Unix domain socket and a TCP port,
16 | # we use a shorter backlog for quicker failover when busy
17 | # listen "/tmp/.sock", :backlog => 64
18 | listen 18889, :tcp_nopush => true
19 |
20 | # nuke workers after 30 seconds instead of 60 seconds (the default)
21 | timeout 30
22 |
23 | # feel free to point this anywhere accessible on the filesystem
24 |
25 | pid "#{SINATRA_ROOT}/tmp/pids/unicorn.pid"
26 |
27 | # relative_path "/test_platform"
28 | # some applications/frameworks log to stderr or stdout, so prevent
29 | # them from going to /dev/null when daemonized here:
30 | stderr_path "#{SINATRA_ROOT}/log/unicorn.stderr.log"
31 | stdout_path "#{SINATRA_ROOT}/log/unicorn.stdout.log"
32 |
33 | # combine REE with "preload_app true" for memory savings
34 | # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
35 | preload_app false
36 | GC.respond_to?(:copy_on_write_friendly=) and
37 | GC.copy_on_write_friendly = true
38 |
39 | before_fork do |server, worker|
40 | # the following is highly recomended for Rails + "preload_app true"
41 | # as there's no need for the master process to hold a connection
42 | # defined?(ActiveRecord::Base) and
43 | # ActiveRecord::Base.connection.disconnect!
44 |
45 | # The following is only recommended for memory/DB-constrained
46 | # installations. It is not needed if your system can house
47 | # twice as many worker_processes as you have configured.
48 | #
49 | # # This allows a new master process to incrementally
50 | # # phase out the old master process with SIGTTOU to avoid a
51 | # # thundering herd (especially in the "preload_app false" case)
52 | # # when doing a transparent upgrade. The last worker spawned
53 | # # will then kill off the old master process with a SIGQUIT.
54 | old_pid = "#{server.config[:pid]}.oldbin"
55 |
56 | puts 'pid:'
57 | puts '-------------------'
58 | puts server.pid
59 | puts old_pid
60 | puts '---------------------'
61 |
62 | if old_pid != server.pid
63 | begin
64 | sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
65 | Process.kill(sig, File.read(old_pid).to_i)
66 | rescue Errno::ENOENT, Errno::ESRCH
67 | end
68 | end
69 | #
70 | # # *optionally* throttle the master from forking too quickly by sleeping
71 | sleep 1
72 | end
73 |
74 | after_fork do |server, worker|
75 | # per-process listener ports for debugging/admin/migrations
76 | # addr = "127.0.0.1:#{9293 + worker.nr}"
77 | # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
78 |
79 | # the following is *required* for Rails + "preload_app true",
80 | # defined?(ActiveRecord::Base) and
81 | # ActiveRecord::Base.establish_connection
82 |
83 | # if preload_app is true, then you may also want to check and
84 | # restart any other shared sockets/descriptors such as Memcached,
85 | # and Redis. TokyoCabinet file handles are safe to reuse
86 | # between any number of forked children (assuming your kernel
87 | # correctly implements pread()/pwrite() system calls)
88 | end
--------------------------------------------------------------------------------
/db/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gunark/rubycas-server/3595eae24605df63086cade3316d49520f6a2775/db/.gitignore
--------------------------------------------------------------------------------
/db/migrate/001_create_initial_structure.rb:
--------------------------------------------------------------------------------
1 | class CreateInitialStructure < ActiveRecord::Migration
2 | def self.up
3 | # Oracle table names cannot exceed 30 chars...
4 | # See http://code.google.com/p/rubycas-server/issues/detail?id=15
5 | create_table 'casserver_lt', :force => true do |t|
6 | t.string 'ticket', :null => false
7 | t.timestamp 'created_on', :null => false
8 | t.datetime 'consumed', :null => true
9 | t.string 'client_hostname', :null => false
10 | end
11 |
12 | create_table 'casserver_st', :force => true do |t|
13 | t.string 'ticket', :null => false
14 | t.text 'service', :null => false
15 | t.timestamp 'created_on', :null => false
16 | t.datetime 'consumed', :null => true
17 | t.string 'client_hostname', :null => false
18 | t.string 'username', :null => false
19 | t.string 'type', :null => false
20 | t.integer 'granted_by_pgt_id', :null => true
21 | t.integer 'granted_by_tgt_id', :null => true
22 | end
23 |
24 | create_table 'casserver_tgt', :force => true do |t|
25 | t.string 'ticket', :null => false
26 | t.timestamp 'created_on', :null => false
27 | t.string 'client_hostname', :null => false
28 | t.string 'username', :null => false
29 | t.text 'extra_attributes', :null => true
30 | end
31 |
32 | create_table 'casserver_pgt', :force => true do |t|
33 | t.string 'ticket', :null => false
34 | t.timestamp 'created_on', :null => false
35 | t.string 'client_hostname', :null => false
36 | t.string 'iou', :null => false
37 | t.integer 'service_ticket_id', :null => false
38 | end
39 | end # self.up
40 |
41 | def self.down
42 | drop_table 'casserver_pgt'
43 | drop_table 'casserver_tgt'
44 | drop_table 'casserver_st'
45 | drop_table 'casserver_lt'
46 | end # self.down
47 | end
48 |
--------------------------------------------------------------------------------
/lib/casserver.rb:
--------------------------------------------------------------------------------
1 | module CASServer; end
2 |
3 | require 'active_record'
4 | require 'active_support'
5 | require 'sinatra/base'
6 | require 'builder' # for XML views
7 | require 'logger'
8 | $LOG = Logger.new(STDOUT)
9 |
10 | require 'casserver/server'
11 |
12 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/active_directory_ldap.rb:
--------------------------------------------------------------------------------
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 |
12 | def extract_extra_attributes(ldap_entry)
13 | super(ldap_entry)
14 | if @extra_attributes["objectGUID"]
15 | @extra_attributes["guid"] = @extra_attributes["objectGUID"].to_s.unpack("H*").to_s
16 | end
17 | ldap_entry
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/active_resource.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/base'
2 |
3 | begin
4 | require 'active_resource'
5 | rescue LoadError
6 | require 'rubygems'
7 | begin
8 | gem 'activeresource', '~> 3.0.0'
9 | rescue Gem::LoadError
10 | $stderr.puts
11 | $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
12 | $stderr.puts
13 | $stderr.puts "To use the ActiveResource authenticator, you must first install the 'activeresource' gem."
14 | $stderr.puts
15 | $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
16 | exit 1
17 | end
18 | require 'active_resource'
19 | end
20 |
21 | module CASServer
22 | module Authenticators
23 |
24 | module Helpers
25 | class Identity < ActiveResource::Base
26 |
27 | # define method_name accessor
28 | cattr_accessor(:method_name)
29 | self.method_name = :authenticate
30 |
31 | def self.method_type
32 | @@method_type ||= :post
33 | end
34 |
35 | def self.method_type= type
36 | methods = [:get, :post, :put, :delete]
37 | raise ArgumentError, "Method type should be one of #{methods.map { |m| m.to_s.upcase }.join(', ')}" unless methods.include? type.to_sym
38 | @@method_type = type
39 | end
40 |
41 | # Autenticate an identity using the given method
42 | # @param [Hash] credentials
43 | def self.authenticate(credentials = {})
44 | response = send(method_type, method_name, credentials)
45 | new.from_authentication_data(response)
46 | end
47 |
48 | # Used to load object attributes from the given response
49 | def from_authentication_data response
50 | load_attributes_from_response(response)
51 | end
52 | end
53 | end
54 |
55 | class ActiveResource < Base
56 |
57 | # This is called at server startup.
58 | # Any class-wide initializiation for the authenticator should be done here.
59 | # (e.g. establish database connection).
60 | # You can leave this empty if you don't need to set up anything.
61 | def self.setup(options)
62 | raise AuthenticatorError, 'You must define at least site option' unless options[:site]
63 | # apply options to active resource object
64 | options.each do |method, arg|
65 | Helpers::Identity.send "#{method}=", arg if Helpers::Identity.respond_to? "#{method}="
66 | end
67 | $LOG.info "ActiveResource configuration loaded"
68 | end
69 |
70 | # Override this to implement your authentication credential validation.
71 | # This is called each time the user tries to log in. The credentials hash
72 | # holds the credentials as entered by the user (generally under :username
73 | # and :password keys; :service and :request are also included by default)
74 | #
75 | # Note that the standard credentials can be read in to instance variables
76 | # by calling #read_standard_credentials.
77 | def validate(credentials)
78 | begin
79 | $LOG.debug("Starting Active Resource authentication")
80 | result = Helpers::Identity.authenticate(credentials.except(:request))
81 | extract_extra_attributes(result) if result
82 | !!result
83 | rescue ::ActiveResource::ConnectionError => e
84 | if e.response.blank? # band-aid for ARes 2.3.x -- craps out if to_s is called without a response
85 | e = e.class.to_s
86 | end
87 | $LOG.warn("Error during authentication: #{e}")
88 | false
89 | end
90 | end
91 |
92 | private
93 |
94 | def extract_extra_attributes(resource)
95 | @extra_attributes = {}
96 | $LOG.debug("Parsing extra attributes")
97 | if @options[:extra_attributes]
98 | extra_attributes_to_extract.each do |attr|
99 | @extra_attributes[attr] = resource.send(attr).to_s
100 | end
101 | else
102 | @extra_attributes = resource.attributes
103 | end
104 | # do filtering
105 | extra_attributes_to_filter.each do |attr|
106 | @extra_attributes.delete(attr)
107 | end
108 | end
109 |
110 | # extract attributes to filter from the given configuration
111 | def extra_attributes_to_filter
112 | # default value if not set
113 | return ['password'] unless @options[:filter_attributes]
114 | # parse option value
115 | if @options[:filter_attributes].kind_of? Array
116 | attrs = @options[:filter_attributes]
117 | elsif @options[:filter_attributes].kind_of? String
118 | attrs = @options[:filter_attributes].split(',').collect { |col| col.strip }
119 | else
120 | $LOG.error("Can't figure out attribute list from #{@options[:filter_attributes].inspect}. This must be an Aarray of column names or a comma-separated list.")
121 | attrs = []
122 | end
123 | attrs
124 | end
125 | end
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb:
--------------------------------------------------------------------------------
1 | require "openssl"
2 |
3 | module Authlogic
4 | module CryptoProviders
5 | # This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
6 | # In an initializer, or before your application initializes, you should do the following:
7 | #
8 | # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrably a bunch of random characters"
9 | #
10 | # My final comment is that this is a strong encryption method, but its main weakness is that its reversible. If you do not need to reverse the hash
11 | # then you should consider Sha512 or BCrypt instead.
12 | #
13 | # Keep your key in a safe place, some even say the key should be stored on a separate server.
14 | # This won't hurt performance because the only time it will try and access the key on the separate server is during initialization, which only
15 | # happens once. The reasoning behind this is if someone does compromise your server they won't have the key also. Basically, you don't want to
16 | # store the key with the lock.
17 | class AES256
18 | class << self
19 | attr_writer :key
20 |
21 | def encrypt(*tokens)
22 | aes.encrypt
23 | aes.key = @key
24 | [aes.update(tokens.join) + aes.final].pack("m").chomp
25 | end
26 |
27 | def matches?(crypted, *tokens)
28 | aes.decrypt
29 | aes.key = @key
30 | (aes.update(crypted.unpack("m").first) + aes.final) == tokens.join
31 | rescue OpenSSL::CipherError
32 | false
33 | end
34 |
35 | private
36 | def aes
37 | raise ArgumentError.new("You must provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
38 | @aes ||= OpenSSL::Cipher::Cipher.new("AES-256-ECB")
39 | end
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb:
--------------------------------------------------------------------------------
1 | begin
2 | require "bcrypt"
3 | rescue LoadError
4 | end
5 |
6 | module Authlogic
7 | module CryptoProviders
8 | # For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
9 | # secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
10 | # password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
11 | # of the Sha algorithms. I did some benchmarking to save you some time with your decision:
12 | #
13 | # require "bcrypt"
14 | # require "digest"
15 | # require "benchmark"
16 | #
17 | # Benchmark.bm(18) do |x|
18 | # x.report("BCrypt (cost = 10:") { 100.times { BCrypt::Password.create("mypass", :cost => 10) } }
19 | # x.report("BCrypt (cost = 2:") { 100.times { BCrypt::Password.create("mypass", :cost => 2) } }
20 | # x.report("Sha512:") { 100.times { Digest::SHA512.hexdigest("mypass") } }
21 | # x.report("Sha1:") { 100.times { Digest::SHA1.hexdigest("mypass") } }
22 | # end
23 | #
24 | # user system total real
25 | # BCrypt (cost = 10): 10.780000 0.060000 10.840000 ( 11.100289)
26 | # BCrypt (cost = 2): 0.180000 0.000000 0.180000 ( 0.181914)
27 | # Sha512: 0.000000 0.000000 0.000000 ( 0.000829)
28 | # Sha1: 0.000000 0.000000 0.000000 ( 0.000395)
29 | #
30 | # You can play around with the cost to get that perfect balance between performance and security.
31 | #
32 | # Decided BCrypt is for you? Just insall the bcrypt gem:
33 | #
34 | # gem install bcrypt-ruby
35 | #
36 | # Tell acts_as_authentic to use it:
37 | #
38 | # acts_as_authentic do |c|
39 | # c.crypto_provider = Authlogic::CryptoProviders::BCrypt
40 | # end
41 | #
42 | # You are good to go!
43 | class BCrypt
44 | class << self
45 | # This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
46 | # Set this to whatever you want, play around with it to get that perfect balance between security and performance.
47 | def cost
48 | @cost ||= 10
49 | end
50 | attr_writer :cost
51 |
52 | # Creates a BCrypt hash for the password passed.
53 | def encrypt(*tokens)
54 | ::BCrypt::Password.create(join_tokens(tokens), :cost => cost)
55 | end
56 |
57 | # Does the hash match the tokens? Uses the same tokens that were used to encrypt.
58 | def matches?(hash, *tokens)
59 | $LOG.debug hash
60 | $LOG.debug tokens.inspect
61 |
62 | hash = new_from_hash(hash)
63 | return false if hash.blank?
64 | hash == join_tokens(tokens)
65 | end
66 |
67 | # This method is used as a flag to tell Authlogic to "resave" the password upon a successful login, using the new cost
68 | def cost_matches?(hash)
69 | hash = new_from_hash(hash)
70 | if hash.blank?
71 | false
72 | else
73 | hash.cost == cost
74 | end
75 | end
76 |
77 | private
78 | def join_tokens(tokens)
79 | tokens.flatten.join
80 | end
81 |
82 | def new_from_hash(hash)
83 | begin
84 | ::BCrypt::Password.new(hash)
85 | rescue ::BCrypt::Errors::InvalidHash
86 | return nil
87 | end
88 | end
89 | end
90 | end
91 | end
92 | end
93 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb:
--------------------------------------------------------------------------------
1 | require "digest/md5"
2 |
3 | module Authlogic
4 | module CryptoProviders
5 | # This class was made for the users transitioning from md5 based systems.
6 | # I highly discourage using this crypto provider as it superbly inferior
7 | # to your other options.
8 | #
9 | # Please use any other provider offered by Authlogic.
10 | class MD5
11 | class << self
12 | attr_accessor :join_token
13 |
14 | # The number of times to loop through the encryption.
15 | def stretches
16 | @stretches ||= 1
17 | end
18 | attr_writer :stretches
19 |
20 | # Turns your raw password into a MD5 hash.
21 | def encrypt(*tokens)
22 | digest = tokens.flatten.join(join_token)
23 | stretches.times { digest = Digest::MD5.hexdigest(digest) }
24 | digest
25 | end
26 |
27 | # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
28 | def matches?(crypted, *tokens)
29 | encrypt(*tokens) == crypted
30 | end
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb:
--------------------------------------------------------------------------------
1 | require "digest/sha1"
2 |
3 | module Authlogic
4 | module CryptoProviders
5 | # This class was made for the users transitioning from restful_authentication.
6 | # I highly discourage using this crypto provider as it inferior to your other options.
7 | # Please use any other provider offered by Authlogic.
8 | class Sha1
9 | class << self
10 | def join_token
11 | @join_token ||= "--"
12 | end
13 | attr_writer :join_token
14 |
15 | def digest_format=(format)
16 | @digest_format = format
17 | end
18 |
19 | # This is for "old style" authentication with a custom format of digest
20 | def digest(tokens)
21 | if @digest_format
22 | @digest_format.
23 | gsub('PASSWORD', tokens.first).
24 | gsub('SALT', tokens.last)
25 | else
26 | tokens.join(join_token)
27 | end
28 | end
29 |
30 | # The number of times to loop through the encryption.
31 | # This is ten because that is what restful_authentication defaults to.
32 |
33 | def stretches
34 | @stretches ||= 10
35 | end
36 | attr_writer :stretches
37 |
38 | # Turns your raw password into a Sha1 hash.
39 | def encrypt(*tokens)
40 | tokens = tokens.flatten
41 |
42 | if stretches > 1
43 | hash = tokens.shift
44 | stretches.times { hash = Digest::SHA1.hexdigest([hash, *tokens].join(join_token)) }
45 | else
46 | hash = Digest::SHA1.hexdigest( digest(tokens) )
47 | end
48 |
49 | hash
50 | end
51 |
52 | # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
53 | def matches?(crypted, *tokens)
54 | encrypt(*tokens) == crypted
55 | end
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb:
--------------------------------------------------------------------------------
1 | require "digest/sha2"
2 |
3 | module Authlogic
4 | # The acts_as_authentic method has a crypto_provider option. This allows you to use any type of encryption you like.
5 | # Just create a class with a class level encrypt and matches? method. See example below.
6 | #
7 | # === Example
8 | #
9 | # class MyAwesomeEncryptionMethod
10 | # def self.encrypt(*tokens)
11 | # # the tokens passed will be an array of objects, what type of object is irrelevant,
12 | # # just do what you need to do with them and return a single encrypted string.
13 | # # for example, you will most likely join all of the objects into a single string and then encrypt that string
14 | # end
15 | #
16 | # def self.matches?(crypted, *tokens)
17 | # # return true if the crypted string matches the tokens.
18 | # # depending on your algorithm you might decrypt the string then compare it to the token, or you might
19 | # # encrypt the tokens and make sure it matches the crypted string, its up to you
20 | # end
21 | # end
22 | module CryptoProviders
23 | # = Sha512
24 | #
25 | # Uses the Sha512 hash algorithm to encrypt passwords.
26 | class Sha512
27 | class << self
28 | attr_accessor :join_token
29 |
30 | # The number of times to loop through the encryption. This is ten because that is what restful_authentication defaults to.
31 | def stretches
32 | @stretches ||= 20
33 | end
34 | attr_writer :stretches
35 |
36 | # Turns your raw password into a Sha512 hash.
37 | def encrypt(*tokens)
38 | digest = tokens.flatten.join(join_token)
39 | stretches.times { digest = Digest::SHA512.hexdigest(digest) }
40 | digest
41 | end
42 |
43 | # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
44 | def matches?(crypted, *tokens)
45 | encrypt(*tokens) == crypted
46 | end
47 | end
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/base.rb:
--------------------------------------------------------------------------------
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 | # This is called at server startup.
9 | # Any class-wide initializiation for the authenticator should be done here.
10 | # (e.g. establish database connection).
11 | # You can leave this empty if you don't need to set up anything.
12 | def self.setup(options)
13 | end
14 |
15 | # This is called prior to #validate (i.e. each time the user tries to log in).
16 | # Any per-instance initialization for the authenticator should be done here.
17 | #
18 | # By default this makes the authenticator options hash available for #validate
19 | # under @options and initializes @extra_attributes to an empty hash.
20 | def configure(options)
21 | raise ArgumentError, "options must be a HashWithIndifferentAccess" unless options.kind_of? HashWithIndifferentAccess
22 | @options = options.dup
23 | @extra_attributes = {}
24 | end
25 |
26 | # Override this to implement your authentication credential validation.
27 | # This is called each time the user tries to log in. The credentials hash
28 | # holds the credentials as entered by the user (generally under :username
29 | # and :password keys; :service and :request are also included by default)
30 | #
31 | # Note that the standard credentials can be read in to instance variables
32 | # by calling #read_standard_credentials.
33 | def validate(credentials)
34 | raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
35 | end
36 |
37 | def extra_attributes
38 | @extra_attributes
39 | end
40 |
41 | protected
42 | def read_standard_credentials(credentials)
43 | @username = credentials[:username]
44 | @password = credentials[:password]
45 | @service = credentials[:service]
46 | @request = credentials[:request]
47 | end
48 |
49 | def extra_attributes_to_extract
50 | if @options[:extra_attributes].kind_of? Array
51 | attrs = @options[:extra_attributes]
52 | elsif @options[:extra_attributes].kind_of? String
53 | attrs = @options[:extra_attributes].split(',').collect{|col| col.strip}
54 | else
55 | $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.")
56 | attrs = []
57 | end
58 |
59 | $LOG.debug("#{self.class.name} will try to extract the following extra_attributes: #{attrs.inspect}")
60 | return attrs
61 | end
62 | end
63 | end
64 |
65 | class AuthenticatorError < Exception
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/client_certificate.rb:
--------------------------------------------------------------------------------
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 | raise NotImplementedError, "#{self.class.name}#validate NOT YET IMPLEMENTED!"
44 |
45 | return true # if SSL certificate is valid, false otherwise
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/google.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/base'
2 | require 'uri'
3 | require 'net/http'
4 | require 'net/https'
5 | require 'timeout'
6 |
7 | # Validates Google accounts against Google's authentication service -- in other
8 | # words, this authenticator allows users to log in to CAS using their
9 | # Gmail/Google accounts.
10 | class CASServer::Authenticators::Google < CASServer::Authenticators::Base
11 | def validate(credentials)
12 | read_standard_credentials(credentials)
13 |
14 | return false if @username.blank? || @password.blank?
15 |
16 | auth_data = {
17 | 'Email' => @username,
18 | 'Passwd' => @password,
19 | 'service' => 'xapi',
20 | 'source' => 'RubyCAS-Server',
21 | 'accountType' => 'HOSTED_OR_GOOGLE'
22 | }
23 |
24 | url = URI.parse('https://www.google.com/accounts/ClientLogin')
25 | if @options[:proxy]
26 | http = Net::HTTP.Proxy(@options[:proxy][:host], @options[:proxy][:port], @options[:proxy][:username], @options[:proxy][:password]).new(url.host, url.port)
27 | else
28 | http = Net::HTTP.new(url.host, url.port)
29 | end
30 | http.use_ssl = true
31 |
32 | # TODO: make the timeout configurable
33 | wait_seconds = 10
34 | begin
35 | timeout(wait_seconds) do
36 | res = http.start do |conn|
37 | req = Net::HTTP::Post.new(url.path)
38 | req.set_form_data(auth_data,'&')
39 | conn.request(req)
40 | end
41 |
42 | case res
43 | when Net::HTTPSuccess
44 | true
45 | when Net::HTTPForbidden
46 | false
47 | else
48 | $LOG.error("Unexpected response from Google while validating credentials: #{res.inspect} ==> #{res.body}.")
49 | raise CASServer::AuthenticatorError, "Unexpected response received from Google while validating credentials."
50 | end
51 | end
52 | rescue Timeout::Error
53 | $LOG.error("Google did not respond to the credential validation request. We waited for #{wait_seconds.inspect} seconds before giving up.")
54 | raise CASServer::AuthenticatorError, "Timeout while waiting for Google to validate credentials."
55 | end
56 |
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/ldap.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/base'
2 |
3 | begin
4 | require 'net/ldap'
5 | rescue LoadError
6 | require 'rubygems'
7 | begin
8 | gem 'net-ldap', '~> 0.1.1'
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 'net-ldap' gem."
14 | $stderr.puts " See http://github.com/RoryO/ruby-net-ldap for details."
15 | $stderr.puts
16 | $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
17 | exit 1
18 | end
19 | require 'net/ldap'
20 | end
21 |
22 | # Basic LDAP authenticator. Should be compatible with OpenLDAP and other similar LDAP servers,
23 | # although it hasn't been officially tested. See example config file for details on how
24 | # to configure it.
25 | class CASServer::Authenticators::LDAP < CASServer::Authenticators::Base
26 | def validate(credentials)
27 | read_standard_credentials(credentials)
28 |
29 | return false if @password.blank?
30 |
31 | raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options
32 | raise CASServer::AuthenticatorError, "Invalid LDAP authenticator configuration!" unless @options[:ldap]
33 | raise CASServer::AuthenticatorError, "You must specify a server host in the LDAP configuration!" unless @options[:ldap][:host] || @options[:ldap][:server]
34 |
35 | raise CASServer::AuthenticatorError, "The username '#{@username}' contains invalid characters." if (@username =~ /[*\(\)\0\/]/)
36 |
37 | preprocess_username
38 |
39 | @ldap = Net::LDAP.new
40 |
41 |
42 | @options[:ldap][:host] ||= @options[:ldap][:server]
43 | @ldap.host = @options[:ldap][:host]
44 | @ldap.port = @options[:ldap][:port] if @options[:ldap][:port]
45 | @ldap.encryption(@options[:ldap][:encryption].intern) if @options[:ldap][:encryption]
46 |
47 | begin
48 | if @options[:ldap][:auth_user]
49 | bind_success = bind_by_username_with_preauthentication
50 | else
51 | bind_success = bind_by_username
52 | end
53 |
54 | return false unless bind_success
55 |
56 | entry = find_user
57 | extract_extra_attributes(entry)
58 |
59 | return true
60 | rescue Net::LDAP::LdapError => e
61 | raise CASServer::AuthenticatorError,
62 | "LDAP authentication failed with '#{e}'. Check your authenticator configuration."
63 | end
64 | end
65 |
66 | protected
67 | def default_username_attribute
68 | "cn"
69 | end
70 |
71 | private
72 | # Add prefix to username, if :username_prefix was specified in the :ldap config.
73 | def preprocess_username
74 | @username = @options[:ldap][:username_prefix] + @username if @options[:ldap][:username_prefix]
75 | end
76 |
77 | # Attempt to bind with the LDAP server using the username and password entered by
78 | # the user. If a :filter was specified in the :ldap config, the filter will be
79 | # added to the LDAP query for the username.
80 | def bind_by_username
81 | username_attribute = options[:ldap][:username_attribute] || default_username_attribute
82 |
83 | @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter)
84 | end
85 |
86 | # If an auth_user is specified, we will connect ("pre-authenticate") with the
87 | # LDAP server using the authenticator account, and then attempt to bind as the
88 | # user who is actually trying to authenticate. Note that you need to set up
89 | # the special authenticator account first. Also, auth_user must be the authenticator
90 | # user's full CN, which is probably not the same as their username.
91 | #
92 | # This pre-authentication process is necessary because binding can only be done
93 | # using the CN, so having just the username is not enough. We connect as auth_user,
94 | # and then try to find the target user's CN based on the given username. Then we bind
95 | # as the target user to validate their credentials.
96 | def bind_by_username_with_preauthentication
97 | raise CASServer::AuthenticatorError, "A password must be specified in the configuration for the authenticator user!" unless
98 | @options[:ldap][:auth_password]
99 |
100 | @ldap.authenticate(@options[:ldap][:auth_user], @options[:ldap][:auth_password])
101 |
102 | @ldap.bind_as(:base => @options[:ldap][:base], :password => @password, :filter => user_filter)
103 | end
104 |
105 | # Combine the filter for finding the user with the optional extra filter specified in the config
106 | # (if any).
107 | def user_filter
108 | username_attribute = options[:ldap][:username_attribute] || default_username_attribute
109 |
110 | filter = Array(username_attribute).map { |ua| Net::LDAP::Filter.eq(ua, @username) }.reduce(:|)
111 | unless @options[:ldap][:filter].blank?
112 | filter &= Net::LDAP::Filter.construct(@options[:ldap][:filter])
113 | end
114 |
115 | filter
116 | end
117 |
118 | # Finds the user based on the user_filter (this is called after authentication).
119 | # We do this to make it possible to extract extra_attributes.
120 | def find_user
121 | results = @ldap.search( :base => options[:ldap][:base], :filter => user_filter)
122 | return results.first
123 | end
124 |
125 | def extract_extra_attributes(ldap_entry)
126 | @extra_attributes = {}
127 | extra_attributes_to_extract.each do |attr|
128 | v = ldap_entry[attr]
129 | next if !v || (v.respond_to?(:empty?) && v.empty?)
130 | if v.kind_of?(Array)
131 | @extra_attributes[attr] = []
132 | ldap_entry[attr].each do |a|
133 | @extra_attributes[attr] << a.to_s
134 | end
135 | else
136 | @extra_attributes[attr] = v.to_s
137 | end
138 | end
139 |
140 | if @extra_attributes.empty?
141 | $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
142 | else
143 | $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
144 | end
145 | ldap_entry
146 | end
147 | end
148 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/ntlm.rb:
--------------------------------------------------------------------------------
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
89 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/open_id.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/base'
2 |
3 | require 'openid'
4 | require 'openid/extensions/sreg'
5 | require 'openid/extensions/pape'
6 | require 'openid/store/memory'
7 |
8 |
9 | # CURRENTLY UNIMPLEMENTED
10 | # This is just starter code.
11 | class CASServer::Authenticators::OpenID < CASServer::Authenticators::Base
12 |
13 | def validate(credentials)
14 | raise NotImplementedError, "The OpenID authenticator is not yet implemented. "+
15 | "See http://code.google.com/p/rubycas-server/issues/detail?id=36 if you are interested in helping this along."
16 |
17 | read_standard_credentials(credentials)
18 |
19 | store = OpenID::Store::Memory.new
20 | consumer = OpenID::Consumer.new({}, store)
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/sql.rb:
--------------------------------------------------------------------------------
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 | # ignore_type_column: true # indicates if you want to ignore Single Table Inheritance 'type' field
54 | # extra_attributes: full_name, access_level
55 | #
56 | class CASServer::Authenticators::SQL < CASServer::Authenticators::Base
57 | def self.setup(options)
58 | raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless options[:database]
59 |
60 | user_model_name = "CASUser_#{options[:auth_index]}"
61 | $LOG.debug "CREATING USER MODEL #{user_model_name}"
62 |
63 | class_eval %{
64 | class #{user_model_name} < ActiveRecord::Base
65 | end
66 | }
67 |
68 | @user_model = const_get(user_model_name)
69 | @user_model.establish_connection(options[:database])
70 | @user_model.set_table_name(options[:user_table] || 'users')
71 | @user_model.inheritance_column = 'no_inheritance_column' if options[:ignore_type_column]
72 | end
73 |
74 | def self.user_model
75 | @user_model
76 | end
77 |
78 | def validate(credentials)
79 | read_standard_credentials(credentials)
80 | raise_if_not_configured
81 |
82 | user_model = self.class.user_model
83 |
84 | username_column = @options[:username_column] || 'username'
85 | password_column = @options[:password_column] || 'password'
86 |
87 | $LOG.debug "#{self.class}: [#{user_model}] " + "Connection pool size: #{user_model.connection_pool.instance_variable_get(:@checked_out).length}/#{user_model.connection_pool.instance_variable_get(:@connections).length}"
88 | results = user_model.find(:all, :conditions => ["#{username_column} = ? AND #{password_column} = ?", @username, @password])
89 | user_model.connection_pool.checkin(user_model.connection)
90 |
91 | if results.size > 0
92 | $LOG.warn("#{self.class}: Multiple matches found for user #{@username.inspect}") if results.size > 1
93 |
94 | unless @options[:extra_attributes].blank?
95 | if results.size > 1
96 | $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
97 | else
98 | user = results.first
99 |
100 | extract_extra(user)
101 | log_extra
102 | end
103 | end
104 |
105 | return true
106 | else
107 | return false
108 | end
109 | end
110 |
111 | protected
112 |
113 | def raise_if_not_configured
114 | raise CASServer::AuthenticatorError.new(
115 | "Cannot validate credentials because the authenticator hasn't yet been configured"
116 | ) unless @options
117 | end
118 |
119 | def extract_extra user
120 | @extra_attributes = {}
121 | extra_attributes_to_extract.each do |col|
122 | @extra_attributes[col] = user.send(col)
123 | end
124 | end
125 |
126 | def log_extra
127 | if @extra_attributes.empty?
128 | $LOG.warn("#{self.class}: Did not read any extra_attributes for user #{@username.inspect} even though an :extra_attributes option was provided.")
129 | else
130 | $LOG.debug("#{self.class}: Read the following extra_attributes for user #{@username.inspect}: #{@extra_attributes.inspect}")
131 | end
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/sql_authlogic.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/sql'
2 |
3 | # These were pulled directly from Authlogic, and new ones can be added
4 | # just by including new Crypto Providers
5 | require File.dirname(__FILE__) + '/authlogic_crypto_providers/aes256'
6 | require File.dirname(__FILE__) + '/authlogic_crypto_providers/bcrypt'
7 | require File.dirname(__FILE__) + '/authlogic_crypto_providers/md5'
8 | require File.dirname(__FILE__) + '/authlogic_crypto_providers/sha1'
9 | require File.dirname(__FILE__) + '/authlogic_crypto_providers/sha512'
10 |
11 | begin
12 | require 'active_record'
13 | rescue LoadError
14 | require 'rubygems'
15 | require 'active_record'
16 | end
17 |
18 | # This is a version of the SQL authenticator that works nicely with Authlogic.
19 | # Passwords are encrypted the same way as it done in Authlogic.
20 | # Before use you this, you MUST configure rest_auth_digest_streches and rest_auth_site_key in
21 | # config.
22 | #
23 | # Using this authenticator requires restful authentication plugin on rails (client) side.
24 | #
25 | # * git://github.com/binarylogic/authlogic.git
26 | #
27 | # Usage:
28 |
29 | # authenticator:
30 | # class: CASServer::Authenticators::SQLAuthlogic
31 | # database:
32 | # adapter: mysql
33 | # database: some_database_with_users_table
34 | # user: root
35 | # password:
36 | # server: localhost
37 | # user_table: user
38 | # username_column: login
39 | # password_column: crypted_password
40 | # salt_column: password_salt
41 | # encryptor: Sha1
42 | # encryptor_options:
43 | # digest_format: --SALT--PASSWORD--
44 | # stretches: 1
45 | #
46 | class CASServer::Authenticators::SQLAuthlogic < CASServer::Authenticators::SQL
47 |
48 | def validate(credentials)
49 | read_standard_credentials(credentials)
50 | raise_if_not_configured
51 |
52 | user_model = self.class.user_model
53 |
54 | username_column = @options[:username_column] || "login"
55 | password_column = @options[:password_column] || "crypted_password"
56 | salt_column = @options[:salt_column]
57 |
58 | $LOG.debug "#{self.class}: [#{user_model}] " + "Connection pool size: #{user_model.connection_pool.instance_variable_get(:@checked_out).length}/#{user_model.connection_pool.instance_variable_get(:@connections).length}"
59 | results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
60 | user_model.connection_pool.checkin(user_model.connection)
61 |
62 | begin
63 | encryptor = eval("Authlogic::CryptoProviders::" + @options[:encryptor] || "Sha512")
64 | rescue
65 | $LOG.warn("Could not initialize Authlogic crypto class for '#{@options[:encryptor]}'")
66 | encryptor = Authlogic::CryptoProviders::Sha512
67 | end
68 |
69 | @options[:encryptor_options].each do |name, value|
70 | encryptor.send("#{name}=", value) if encryptor.respond_to?("#{name}=")
71 | end
72 |
73 | if results.size > 0
74 | $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
75 | user = results.first
76 | tokens = [@password, (not salt_column.nil?) && user.send(salt_column) || nil].compact
77 | crypted = user.send(password_column)
78 |
79 | unless @options[:extra_attributes].blank?
80 | if results.size > 1
81 | $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
82 | else
83 | extract_extra(user)
84 | log_extra
85 | end
86 | end
87 |
88 | return encryptor.matches?(crypted, tokens)
89 | else
90 | return false
91 | end
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/sql_encrypted.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/sql'
2 |
3 | require 'digest/sha1'
4 | require 'digest/sha2'
5 | require 'crypt-isaac'
6 |
7 | # This is a more secure version of the SQL authenticator. Passwords are encrypted
8 | # rather than being stored in plain text.
9 | #
10 | # Based on code contributed by Ben Mabey.
11 | #
12 | # Using this authenticator requires some configuration on the client side. Please see
13 | # http://code.google.com/p/rubycas-server/wiki/UsingTheSQLEncryptedAuthenticator
14 | class CASServer::Authenticators::SQLEncrypted < CASServer::Authenticators::SQL
15 | # Include this module into your application's user model.
16 | #
17 | # Your model must have an 'encrypted_password' column where the password will be stored,
18 | # and an 'encryption_salt' column that will be populated with a random string before
19 | # the user record is first created.
20 | module EncryptedPassword
21 | def self.included(mod)
22 | raise "#{self} should be inclued in an ActiveRecord class!" unless mod.respond_to?(:before_save)
23 | mod.before_save :generate_encryption_salt
24 | end
25 |
26 | def encrypt(str)
27 | generate_encryption_salt unless encryption_salt
28 | Digest::SHA256.hexdigest("#{encryption_salt}::#{str}")
29 | end
30 |
31 | def password=(password)
32 | self[:encrypted_password] = encrypt(password)
33 | end
34 |
35 | def generate_encryption_salt
36 | self.encryption_salt = Digest::SHA1.hexdigest(Crypt::ISAAC.new.rand(2**31).to_s) unless
37 | encryption_salt
38 | end
39 | end
40 |
41 | def self.setup(options)
42 | super(options)
43 | user_model.__send__(:include, EncryptedPassword)
44 | end
45 |
46 | def validate(credentials)
47 | read_standard_credentials(credentials)
48 | raise_if_not_configured
49 |
50 | user_model = self.class.user_model
51 |
52 | username_column = @options[:username_column] || "username"
53 | encrypt_function = @options[:encrypt_function] || 'user.encrypted_password == Digest::SHA256.hexdigest("#{user.encryption_salt}::#{@password}")'
54 |
55 | $LOG.debug "#{self.class}: [#{user_model}] " + "Connection pool size: #{user_model.connection_pool.instance_variable_get(:@checked_out).length}/#{user_model.connection_pool.instance_variable_get(:@connections).length}"
56 | results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
57 | user_model.connection_pool.checkin(user_model.connection)
58 |
59 | if results.size > 0
60 | $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
61 | user = results.first
62 | unless @options[:extra_attributes].blank?
63 | if results.size > 1
64 | $LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
65 | else
66 | extract_extra(user)
67 | log_extra
68 | end
69 | end
70 | return eval(encrypt_function)
71 | else
72 | return false
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/sql_md5.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/sql'
2 |
3 | require 'digest/md5'
4 |
5 | # Essentially the same as the standard SQL authenticator, but this version
6 | # assumes that your password is stored as an MD5 hash.
7 | #
8 | # This was contributed by malcomm for Drupal authentication. To work with
9 | # Drupal, you should use 'name' for the :username_column config option, and
10 | # 'pass' for the :password_column.
11 | class CASServer::Authenticators::SQLMd5 < CASServer::Authenticators::SQL
12 |
13 | protected
14 | def read_standard_credentials(credentials)
15 | super
16 | @password = Digest::MD5.hexdigest(@password)
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/sql_rest_auth.rb:
--------------------------------------------------------------------------------
1 | require 'casserver/authenticators/sql_encrypted'
2 |
3 | require 'digest/sha1'
4 |
5 | begin
6 | require 'active_record'
7 | rescue LoadError
8 | require 'rubygems'
9 | require 'active_record'
10 | end
11 |
12 | # This is a version of the SQL authenticator that works nicely with RestfulAuthentication.
13 | # Passwords are encrypted the same way as it done in RestfulAuthentication.
14 | # Before use you this, you MUST configure rest_auth_digest_streches and rest_auth_site_key in
15 | # config.
16 | #
17 | # Using this authenticator requires restful authentication plugin on rails (client) side.
18 | #
19 | # * git://github.com/technoweenie/restful-authentication.git
20 | #
21 | class CASServer::Authenticators::SQLRestAuth < CASServer::Authenticators::SQLEncrypted
22 |
23 | def validate(credentials)
24 | read_standard_credentials(credentials)
25 | raise_if_not_configured
26 |
27 | raise CASServer::AuthenticatorError, "You must specify a 'site_key' in the SQLRestAuth authenticator's configuration!" unless @options[:site_key]
28 | raise CASServer::AuthenticatorError, "You must specify 'digest_streches' in the SQLRestAuth authenticator's configuration!" unless @options[:digest_streches]
29 |
30 | user_model = self.class.user_model
31 |
32 | username_column = @options[:username_column] || "email"
33 |
34 | $LOG.debug "#{self.class}: [#{user_model}] " + "Connection pool size: #{user_model.connection_pool.instance_variable_get(:@checked_out).length}/#{user_model.connection_pool.instance_variable_get(:@connections).length}"
35 | results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
36 | user_model.connection_pool.checkin(user_model.connection)
37 |
38 | if results.size > 0
39 | $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
40 | user = results.first
41 | if user.crypted_password == user.encrypt(@password)
42 | unless @options[:extra_attributes].blank?
43 | extract_extra(user)
44 | log_extra
45 | end
46 | return true
47 | else
48 | return false
49 | end
50 | else
51 | return false
52 | end
53 | end
54 |
55 | def self.setup(options)
56 | super(options)
57 | user_model.__send__(:include, EncryptedPassword)
58 | end
59 |
60 | module EncryptedPassword
61 |
62 | def self.included(mod)
63 | raise "#{self} should be inclued in an ActiveRecord class!" unless mod.respond_to?(:before_save)
64 | end
65 |
66 | def encrypt(password)
67 | password_digest(password, self.salt)
68 | end
69 |
70 | def secure_digest(*args)
71 | Digest::SHA1.hexdigest(args.flatten.join('--'))
72 | end
73 |
74 | def password_digest(password, salt)
75 | digest = @options[:site_key]
76 | @options[:digest_streches].times do
77 | digest = secure_digest(digest, salt, password, @options[:site_key])
78 | end
79 | digest
80 | end
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/lib/casserver/authenticators/test.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'casserver/authenticators/base'
3 |
4 | # Dummy authenticator used for testing.
5 | # Accepts any username as valid as long as the password is "testpassword"; otherwise authentication fails.
6 | # Raises an AuthenticationError when username is "do_error" (this is useful to test the Exception
7 | # handling functionality).
8 | class CASServer::Authenticators::Test < CASServer::Authenticators::Base
9 | def validate(credentials)
10 | read_standard_credentials(credentials)
11 |
12 | raise CASServer::AuthenticatorError, "Username is 'do_error'!" if @username == 'do_error'
13 |
14 | @extra_attributes[:test_utf_string] = "Ютф"
15 | @extra_attributes[:test_numeric] = 123.45
16 | @extra_attributes[:test_serialized] = {:foo => 'bar', :alpha => [1,2,3]}
17 |
18 | valid_password = options[:password] || "testpassword"
19 |
20 | return @password == valid_password
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/casserver/cas.rb:
--------------------------------------------------------------------------------
1 | require 'uri'
2 | require 'net/https'
3 |
4 | require 'casserver/model'
5 |
6 | # Encapsulates CAS functionality. This module is meant to be included in
7 | # the CASServer::Controllers module.
8 | module CASServer::CAS
9 |
10 | include CASServer::Model
11 |
12 | def generate_login_ticket
13 | # 3.5 (login ticket)
14 | lt = LoginTicket.new
15 | lt.ticket = "LT-" + CASServer::Utils.random_string
16 |
17 | lt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
18 | lt.save!
19 | $LOG.debug("Generated login ticket '#{lt.ticket}' for client" +
20 | " at '#{lt.client_hostname}'")
21 | lt
22 | end
23 |
24 | # Creates a TicketGrantingTicket for the given username. This is done when the user logs in
25 | # for the first time to establish their SSO session (after their credentials have been validated).
26 | #
27 | # The optional 'extra_attributes' parameter takes a hash of additional attributes
28 | # that will be sent along with the username in the CAS response to subsequent
29 | # validation requests from clients.
30 | def generate_ticket_granting_ticket(username, extra_attributes = {})
31 | # 3.6 (ticket granting cookie/ticket)
32 | tgt = TicketGrantingTicket.new
33 | tgt.ticket = "TGC-" + CASServer::Utils.random_string
34 | tgt.username = username
35 | tgt.extra_attributes = extra_attributes
36 | tgt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
37 | tgt.save!
38 | $LOG.debug("Generated ticket granting ticket '#{tgt.ticket}' for user" +
39 | " '#{tgt.username}' at '#{tgt.client_hostname}'" +
40 | (extra_attributes.blank? ? "" : " with extra attributes #{extra_attributes.inspect}"))
41 | tgt
42 | end
43 |
44 | def generate_service_ticket(service, username, tgt)
45 | # 3.1 (service ticket)
46 | st = ServiceTicket.new
47 | st.ticket = "ST-" + CASServer::Utils.random_string
48 | st.service = service
49 | st.username = username
50 | st.granted_by_tgt_id = tgt.id
51 | st.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
52 | st.save!
53 | $LOG.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" +
54 | " for user '#{st.username}' at '#{st.client_hostname}'")
55 | st
56 | end
57 |
58 | def generate_proxy_ticket(target_service, pgt)
59 | # 3.2 (proxy ticket)
60 | pt = ProxyTicket.new
61 | pt.ticket = "PT-" + CASServer::Utils.random_string
62 | pt.service = target_service
63 | pt.username = pgt.service_ticket.username
64 | pt.granted_by_pgt_id = pgt.id
65 | pt.granted_by_tgt_id = pgt.service_ticket.granted_by_tgt.id
66 | pt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
67 | pt.save!
68 | $LOG.debug("Generated proxy ticket '#{pt.ticket}' for target service '#{pt.service}'" +
69 | " for user '#{pt.username}' at '#{pt.client_hostname}' using proxy-granting" +
70 | " ticket '#{pgt.ticket}'")
71 | pt
72 | end
73 |
74 | def generate_proxy_granting_ticket(pgt_url, st)
75 | uri = URI.parse(pgt_url)
76 | https = Net::HTTP.new(uri.host,uri.port)
77 | https.use_ssl = true
78 |
79 | # Here's what's going on here:
80 | #
81 | # 1. We generate a ProxyGrantingTicket (but don't store it in the database just yet)
82 | # 2. Deposit the PGT and it's associated IOU at the proxy callback URL.
83 | # 3. If the proxy callback URL responds with HTTP code 200, store the PGT and return it;
84 | # otherwise don't save it and return nothing.
85 | #
86 | https.start do |conn|
87 | path = uri.path.empty? ? '/' : uri.path
88 | path += '?' + uri.query unless (uri.query.nil? || uri.query.empty?)
89 |
90 | pgt = ProxyGrantingTicket.new
91 | pgt.ticket = "PGT-" + CASServer::Utils.random_string(60)
92 | pgt.iou = "PGTIOU-" + CASServer::Utils.random_string(57)
93 | pgt.service_ticket_id = st.id
94 | pgt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
95 |
96 | # FIXME: The CAS protocol spec says to use 'pgt' as the parameter, but in practice
97 | # the JA-SIG and Yale server implementations use pgtId. We'll go with the
98 | # in-practice standard.
99 | path += (uri.query.nil? || uri.query.empty? ? '?' : '&') + "pgtId=#{pgt.ticket}&pgtIou=#{pgt.iou}"
100 |
101 | response = conn.request_get(path)
102 | # TODO: follow redirects... 2.5.4 says that redirects MAY be followed
103 | # NOTE: The following response codes are valid according to the JA-SIG implementation even without following redirects
104 |
105 | if %w(200 202 301 302 304).include?(response.code)
106 | # 3.4 (proxy-granting ticket IOU)
107 | pgt.save!
108 | $LOG.debug "PGT generated for pgt_url '#{pgt_url}': #{pgt.inspect}"
109 | pgt
110 | else
111 | $LOG.warn "PGT callback server responded with a bad result code '#{response.code}'. PGT will not be stored."
112 | nil
113 | end
114 | end
115 | end
116 |
117 | def validate_login_ticket(ticket)
118 | $LOG.debug("Validating login ticket '#{ticket}'")
119 |
120 | success = false
121 | if ticket.nil?
122 | error = _("Your login request did not include a login ticket. There may be a problem with the authentication system.")
123 | $LOG.warn "Missing login ticket."
124 | elsif lt = LoginTicket.find_by_ticket(ticket)
125 | if lt.consumed?
126 | error = _("The login ticket you provided has already been used up. Please try logging in again.")
127 | $LOG.warn "Login ticket '#{ticket}' previously used up"
128 | elsif Time.now - lt.created_on < settings.config[:maximum_unused_login_ticket_lifetime]
129 | $LOG.info "Login ticket '#{ticket}' successfully validated"
130 | else
131 | error = _("You took too long to enter your credentials. Please try again.")
132 | $LOG.warn "Expired login ticket '#{ticket}'"
133 | end
134 | else
135 | error = _("The login ticket you provided is invalid. There may be a problem with the authentication system.")
136 | $LOG.warn "Invalid login ticket '#{ticket}'"
137 | end
138 |
139 | lt.consume! if lt
140 |
141 | error
142 | end
143 |
144 | def validate_ticket_granting_ticket(ticket)
145 | $LOG.debug("Validating ticket granting ticket '#{ticket}'")
146 |
147 | if ticket.nil?
148 | error = "No ticket granting ticket given."
149 | $LOG.debug error
150 | elsif tgt = TicketGrantingTicket.find_by_ticket(ticket)
151 | if settings.config[:maximum_session_lifetime] && Time.now - tgt.created_on > settings.config[:maximum_session_lifetime]
152 | tgt.destroy
153 | error = "Your session has expired. Please log in again."
154 | $LOG.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' expired."
155 | else
156 | $LOG.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' successfully validated."
157 | end
158 | else
159 | error = "Invalid ticket granting ticket '#{ticket}' (no matching ticket found in the database)."
160 | $LOG.warn(error)
161 | end
162 |
163 | [tgt, error]
164 | end
165 |
166 | def validate_service_ticket(service, ticket, allow_proxy_tickets = false)
167 | $LOG.debug "Validating service/proxy ticket '#{ticket}' for service '#{service}'"
168 |
169 | if service.nil? or ticket.nil?
170 | error = Error.new(:INVALID_REQUEST, "Ticket or service parameter was missing in the request.")
171 | $LOG.warn "#{error.code} - #{error.message}"
172 | elsif st = ServiceTicket.find_by_ticket(ticket)
173 | if st.consumed?
174 | error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has already been used up.")
175 | $LOG.warn "#{error.code} - #{error.message}"
176 | elsif st.kind_of?(CASServer::Model::ProxyTicket) && !allow_proxy_tickets
177 | error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' is a proxy ticket, but only service tickets are allowed here.")
178 | $LOG.warn "#{error.code} - #{error.message}"
179 | elsif Time.now - st.created_on > settings.config[:maximum_unused_service_ticket_lifetime]
180 | error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has expired.")
181 | $LOG.warn "Ticket '#{ticket}' has expired."
182 | elsif !st.matches_service? service
183 | error = Error.new(:INVALID_SERVICE, "The ticket '#{ticket}' belonging to user '#{st.username}' is valid,"+
184 | " but the requested service '#{service}' does not match the service '#{st.service}' associated with this ticket.")
185 | $LOG.warn "#{error.code} - #{error.message}"
186 | else
187 | $LOG.info("Ticket '#{ticket}' for service '#{service}' for user '#{st.username}' successfully validated.")
188 | end
189 | else
190 | error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' not recognized.")
191 | $LOG.warn("#{error.code} - #{error.message}")
192 | end
193 |
194 | if st
195 | st.consume!
196 | end
197 |
198 |
199 | [st, error]
200 | end
201 |
202 | def validate_proxy_ticket(service, ticket)
203 | pt, error = validate_service_ticket(service, ticket, true)
204 |
205 | if pt.kind_of?(CASServer::Model::ProxyTicket) && !error
206 | if not pt.granted_by_pgt
207 | error = Error.new(:INTERNAL_ERROR, "Proxy ticket '#{pt}' belonging to user '#{pt.username}' is not associated with a proxy granting ticket.")
208 | elsif not pt.granted_by_pgt.service_ticket
209 | error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{pt.granted_by_pgt}'"+
210 | " (associated with proxy ticket '#{pt}' and belonging to user '#{pt.username}' is not associated with a service ticket.")
211 | end
212 | end
213 |
214 | [pt, error]
215 | end
216 |
217 | def validate_proxy_granting_ticket(ticket)
218 | if ticket.nil?
219 | error = Error.new(:INVALID_REQUEST, "pgt parameter was missing in the request.")
220 | $LOG.warn("#{error.code} - #{error.message}")
221 | elsif pgt = ProxyGrantingTicket.find_by_ticket(ticket)
222 | if pgt.service_ticket
223 | $LOG.info("Proxy granting ticket '#{ticket}' belonging to user '#{pgt.service_ticket.username}' successfully validated.")
224 | else
225 | error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{ticket}' is not associated with a service ticket.")
226 | $LOG.error("#{error.code} - #{error.message}")
227 | end
228 | else
229 | error = Error.new(:BAD_PGT, "Invalid proxy granting ticket '#{ticket}' (no matching ticket found in the database).")
230 | $LOG.warn("#{error.code} - #{error.message}")
231 | end
232 |
233 | [pgt, error]
234 | end
235 |
236 | # Takes an existing ServiceTicket object (presumably pulled from the database)
237 | # and sends a POST with logout information to the service that the ticket
238 | # was generated for.
239 | #
240 | # This makes possible the "single sign-out" functionality added in CAS 3.1.
241 | # See http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out
242 | def send_logout_notification_for_service_ticket(st)
243 | uri = URI.parse(st.service)
244 | uri.path = '/' if uri.path.empty?
245 | time = Time.now
246 | rand = CASServer::Utils.random_string
247 |
248 | begin
249 | response = Net::HTTP.post_form(uri, {'logoutRequest' => URI.escape(%{
250 |
251 | #{st.ticket}
252 | })})
253 | if response.kind_of? Net::HTTPSuccess
254 | $LOG.info "Logout notification successfully posted to #{st.service.inspect}."
255 | return true
256 | else
257 | $LOG.error "Service #{st.service.inspect} responed to logout notification with code '#{response.code}'!"
258 | return false
259 | end
260 | rescue Exception => e
261 | $LOG.error "Failed to send logout notification to service #{st.service.inspect} due to #{e}"
262 | return false
263 | end
264 | end
265 |
266 | def service_uri_with_ticket(service, st)
267 | raise ArgumentError, "Second argument must be a ServiceTicket!" unless st.kind_of? CASServer::Model::ServiceTicket
268 |
269 | # This will choke with a URI::InvalidURIError if service URI is not properly URI-escaped...
270 | # This exception is handled further upstream (i.e. in the controller).
271 | service_uri = URI.parse(service)
272 |
273 | if service.include? "?"
274 | if service_uri.query.empty?
275 | query_separator = ""
276 | else
277 | query_separator = "&"
278 | end
279 | else
280 | query_separator = "?"
281 | end
282 |
283 | service_with_ticket = service + query_separator + "ticket=" + st.ticket
284 | service_with_ticket
285 | end
286 |
287 | # Strips CAS-related parameters from a service URL and normalizes it,
288 | # removing trailing / and ?. Also converts any spaces to +.
289 | #
290 | # For example, "http://google.com?ticket=12345" will be returned as
291 | # "http://google.com". Also, "http://google.com/" would be returned as
292 | # "http://google.com".
293 | #
294 | # Note that only the first occurance of each CAS-related parameter is
295 | # removed, so that "http://google.com?ticket=12345&ticket=abcd" would be
296 | # returned as "http://google.com?ticket=abcd".
297 | def clean_service_url(dirty_service)
298 | return dirty_service if dirty_service.blank?
299 | clean_service = dirty_service.dup
300 | ['service', 'ticket', 'gateway', 'renew'].each do |p|
301 | clean_service.sub!(Regexp.new("&?#{p}=[^&]*"), '')
302 | end
303 |
304 | clean_service.gsub!(/[\/\?&]$/, '') # remove trailing ?, /, or &
305 | clean_service.gsub!('?&', '?')
306 | clean_service.gsub!(' ', '+')
307 |
308 | $LOG.debug("Cleaned dirty service URL #{dirty_service.inspect} to #{clean_service.inspect}") if
309 | dirty_service != clean_service
310 |
311 | return clean_service
312 | end
313 | module_function :clean_service_url
314 |
315 | end
316 |
--------------------------------------------------------------------------------
/lib/casserver/localization.rb:
--------------------------------------------------------------------------------
1 | require "gettext"
2 | require "gettext/cgi"
3 | require 'active_support'
4 |
5 | module CASServer
6 | module Localization
7 | def self.included(mod)
8 | mod.module_eval do
9 | include GetText
10 | end
11 | end
12 |
13 | include GetText
14 | bindtextdomain("rubycas-server", :path => File.join(File.dirname(File.expand_path(__FILE__)), "../../locale"))
15 |
16 | def determine_locale(request)
17 | source = nil
18 | lang = case
19 | when !request.params['lang'].blank?
20 | source = "'lang' request variable"
21 | request.cookies['lang'] = request.params['lang']
22 | request.params['lang']
23 | when !request.cookies['lang'].blank?
24 | source = "'lang' cookie"
25 | request.cookies['lang']
26 | when !request.env['HTTP_ACCEPT_LANGUAGE'].blank?
27 | source = "'HTTP_ACCEPT_LANGUAGE' header"
28 | lang = request.env['HTTP_ACCEPT_LANGUAGE']
29 | when !request.env['HTTP_USER_AGENT'].blank? && request.env['HTTP_USER_AGENT'] =~ /[^a-z]([a-z]{2}(-[a-z]{2})?)[^a-z]/i
30 | source = "'HTTP_USER_AGENT' header"
31 | $~[1]
32 | # when !$CONF['default_locale'].blank?
33 | # source = "'default_locale' config option"
34 | # $CONF[:default_locale]
35 | else
36 | source = "default"
37 | "en"
38 | end
39 |
40 | $LOG.debug "Detected locale is #{lang.inspect} (from #{source})"
41 |
42 | lang.gsub!('_','-')
43 |
44 | # TODO: Need to confirm that this method of splitting the accepted
45 | # language string is correct.
46 | if lang =~ /[,;\|]/
47 | langs = lang.split(/[,;\|]/)
48 | else
49 | langs = [lang]
50 | end
51 |
52 | # TODO: This method of selecting the desired language might not be
53 | # standards-compliant. For example, http://www.w3.org/TR/ltli/
54 | # suggests that de-de and de-*-DE might be acceptable identifiers
55 | # for selecting various wildcards. The algorithm below does not
56 | # currently support anything like this.
57 |
58 | available = available_locales
59 |
60 | if available.length == 1
61 | $LOG.warn "Only the #{available.first.inspect} localization is available. You should run `rake localization:mo` to compile support for additional languages!"
62 | elsif available.length == 0 # this should never actually happen
63 | $LOG.error "No localizations available! Run `rake localization:mo` to compile support for additional languages."
64 | end
65 |
66 | # Try to pick a locale exactly matching the desired identifier, otherwise
67 | # fall back to locale without region (i.e. given "en-US; de-DE", we would
68 | # first look for "en-US", then "en", then "de-DE", then "de").
69 |
70 | chosen_lang = nil
71 | langs.each do |l|
72 | a = available.find{ |a| a =~ Regexp.new("\\A#{l}\\Z", 'i') ||
73 | a =~ Regexp.new("#{l}-\w*", 'i') }
74 | if a
75 | chosen_lang = a
76 | break
77 | end
78 | end
79 |
80 | chosen_lang = "en" if chosen_lang.blank?
81 |
82 | $LOG.debug "Chosen locale is #{chosen_lang.inspect}"
83 |
84 | return chosen_lang
85 | end
86 |
87 | def available_locales
88 | (Dir.glob(File.join(File.dirname(File.expand_path(__FILE__)), "../../locale/[a-z]*")).map{|path| File.basename(path)} << "en").uniq.collect{|l| l.gsub('_','-')}
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/lib/casserver/model.rb:
--------------------------------------------------------------------------------
1 | require 'active_record'
2 | require 'active_record/base'
3 |
4 | module CASServer::Model
5 |
6 | module Consumable
7 | def consume!
8 | self.consumed = Time.now
9 | self.save!
10 | end
11 |
12 | def self.included(mod)
13 | mod.extend(ClassMethods)
14 | end
15 |
16 | module ClassMethods
17 | def cleanup(max_lifetime, max_unconsumed_lifetime)
18 | transaction do
19 | conditions = ["created_on < ? OR (consumed IS NULL AND created_on < ?)",
20 | Time.now - max_lifetime,
21 | Time.now - max_unconsumed_lifetime]
22 |
23 | expired_tickets_count = count(:conditions => conditions)
24 |
25 | $LOG.debug("Destroying #{expired_tickets_count} expired #{self.name.demodulize}"+
26 | "#{'s' if expired_tickets_count > 1}.") if expired_tickets_count > 0
27 |
28 | destroy_all(conditions)
29 | end
30 | end
31 | end
32 | end
33 |
34 | class Base < ActiveRecord::Base
35 | end
36 |
37 | class Ticket < Base
38 | def to_s
39 | ticket
40 | end
41 |
42 | def self.cleanup(max_lifetime)
43 | transaction do
44 | conditions = ["created_on < ?", Time.now - max_lifetime]
45 | expired_tickets_count = count(:conditions => conditions)
46 |
47 | $LOG.debug("Destroying #{expired_tickets_count} expired #{self.name.demodulize}"+
48 | "#{'s' if expired_tickets_count > 1}.") if expired_tickets_count > 0
49 |
50 | destroy_all(conditions)
51 | end
52 | end
53 | end
54 |
55 | class LoginTicket < Ticket
56 | set_table_name 'casserver_lt'
57 | include Consumable
58 | end
59 |
60 | class ServiceTicket < Ticket
61 | set_table_name 'casserver_st'
62 | include Consumable
63 |
64 | belongs_to :granted_by_tgt,
65 | :class_name => 'CASServer::Model::TicketGrantingTicket',
66 | :foreign_key => :granted_by_tgt_id
67 | has_one :proxy_granting_ticket,
68 | :foreign_key => :created_by_st_id
69 |
70 | def matches_service?(service)
71 | CASServer::CAS.clean_service_url(self.service) ==
72 | CASServer::CAS.clean_service_url(service)
73 | end
74 | end
75 |
76 | class ProxyTicket < ServiceTicket
77 | belongs_to :granted_by_pgt,
78 | :class_name => 'CASServer::Model::ProxyGrantingTicket',
79 | :foreign_key => :granted_by_pgt_id
80 | end
81 |
82 | class TicketGrantingTicket < Ticket
83 | set_table_name 'casserver_tgt'
84 |
85 | serialize :extra_attributes
86 |
87 | has_many :granted_service_tickets,
88 | :class_name => 'CASServer::Model::ServiceTicket',
89 | :foreign_key => :granted_by_tgt_id
90 | end
91 |
92 | class ProxyGrantingTicket < Ticket
93 | set_table_name 'casserver_pgt'
94 | belongs_to :service_ticket
95 | has_many :granted_proxy_tickets,
96 | :class_name => 'CASServer::Model::ProxyTicket',
97 | :foreign_key => :granted_by_pgt_id
98 | end
99 |
100 | class Error
101 | attr_reader :code, :message
102 |
103 | def initialize(code, message)
104 | @code = code
105 | @message = message
106 | end
107 |
108 | def to_s
109 | message
110 | end
111 | end
112 |
113 | # class CreateCASServer < V 0.1
114 | # def self.up
115 | # if ActiveRecord::Base.connection.table_alias_length > 30
116 | # $LOG.info("Creating database with long table names...")
117 | #
118 | # create_table :casserver_login_tickets, :force => true do |t|
119 | # t.column :ticket, :string, :null => false
120 | # t.column :created_on, :timestamp, :null => false
121 | # t.column :consumed, :datetime, :null => true
122 | # t.column :client_hostname, :string, :null => false
123 | # end
124 | #
125 | # create_table :casserver_service_tickets, :force => true do |t|
126 | # t.column :ticket, :string, :null => false
127 | # t.column :service, :string, :null => false
128 | # t.column :created_on, :timestamp, :null => false
129 | # t.column :consumed, :datetime, :null => true
130 | # t.column :client_hostname, :string, :null => false
131 | # t.column :username, :string, :null => false
132 | # t.column :type, :string, :null => false
133 | # t.column :proxy_granting_ticket_id, :integer, :null => true
134 | # end
135 | #
136 | # create_table :casserver_ticket_granting_tickets, :force => true do |t|
137 | # t.column :ticket, :string, :null => false
138 | # t.column :created_on, :timestamp, :null => false
139 | # t.column :client_hostname, :string, :null => false
140 | # t.column :username, :string, :null => false
141 | # end
142 | #
143 | # create_table :casserver_proxy_granting_tickets, :force => true do |t|
144 | # t.column :ticket, :string, :null => false
145 | # t.column :created_on, :timestamp, :null => false
146 | # t.column :client_hostname, :string, :null => false
147 | # t.column :iou, :string, :null => false
148 | # t.column :service_ticket_id, :integer, :null => false
149 | # end
150 | # end
151 | # end
152 | #
153 | # def self.down
154 | # if ActiveRecord::Base.connection.table_alias_length > 30
155 | # drop_table :casserver_proxy_granting_tickets
156 | # drop_table :casserver_ticket_granting_tickets
157 | # drop_table :casserver_service_tickets
158 | # drop_table :casserver_login_tickets
159 | # end
160 | # end
161 | # end
162 | #
163 | # # Oracle table names cannot exceed 30 chars...
164 | # # See http://code.google.com/p/rubycas-server/issues/detail?id=15
165 | # class ShortenTableNames < V 0.5
166 | # def self.up
167 | # if ActiveRecord::Base.connection.table_alias_length > 30
168 | # $LOG.info("Shortening table names")
169 | # rename_table :casserver_login_tickets, :casserver_lt
170 | # rename_table :casserver_service_tickets, :casserver_st
171 | # rename_table :casserver_ticket_granting_tickets, :casserver_tgt
172 | # rename_table :casserver_proxy_granting_tickets, :casserver_pgt
173 | # else
174 | # create_table :casserver_lt, :force => true do |t|
175 | # t.column :ticket, :string, :null => false
176 | # t.column :created_on, :timestamp, :null => false
177 | # t.column :consumed, :datetime, :null => true
178 | # t.column :client_hostname, :string, :null => false
179 | # end
180 | #
181 | # create_table :casserver_st, :force => true do |t|
182 | # t.column :ticket, :string, :null => false
183 | # t.column :service, :string, :null => false
184 | # t.column :created_on, :timestamp, :null => false
185 | # t.column :consumed, :datetime, :null => true
186 | # t.column :client_hostname, :string, :null => false
187 | # t.column :username, :string, :null => false
188 | # t.column :type, :string, :null => false
189 | # t.column :proxy_granting_ticket_id, :integer, :null => true
190 | # end
191 | #
192 | # create_table :casserver_tgt, :force => true do |t|
193 | # t.column :ticket, :string, :null => false
194 | # t.column :created_on, :timestamp, :null => false
195 | # t.column :client_hostname, :string, :null => false
196 | # t.column :username, :string, :null => false
197 | # end
198 | #
199 | # create_table :casserver_pgt, :force => true do |t|
200 | # t.column :ticket, :string, :null => false
201 | # t.column :created_on, :timestamp, :null => false
202 | # t.column :client_hostname, :string, :null => false
203 | # t.column :iou, :string, :null => false
204 | # t.column :service_ticket_id, :integer, :null => false
205 | # end
206 | # end
207 | # end
208 | #
209 | # def self.down
210 | # if ActiveRecord::Base.connection.table_alias_length > 30
211 | # rename_table :casserver_lt, :cassserver_login_tickets
212 | # rename_table :casserver_st, :casserver_service_tickets
213 | # rename_table :casserver_tgt, :casserver_ticket_granting_tickets
214 | # rename_table :casserver_pgt, :casserver_proxy_granting_tickets
215 | # else
216 | # drop_table :casserver_pgt
217 | # drop_table :casserver_tgt
218 | # drop_table :casserver_st
219 | # drop_table :casserver_lt
220 | # end
221 | # end
222 | # end
223 | #
224 | # class AddTgtToSt < V 0.7
225 | # def self.up
226 | # add_column :casserver_st, :tgt_id, :integer, :null => true
227 | # end
228 | #
229 | # def self.down
230 | # remove_column :casserver_st, :tgt_id, :integer
231 | # end
232 | # end
233 | #
234 | # class ChangeServiceToText < V 0.71
235 | # def self.up
236 | # # using change_column to change the column type from :string to :text
237 | # # doesn't seem to work, at least under MySQL, so we drop and re-create
238 | # # the column instead
239 | # remove_column :casserver_st, :service
240 | # say "WARNING: All existing service tickets are being deleted."
241 | # add_column :casserver_st, :service, :text
242 | # end
243 | #
244 | # def self.down
245 | # change_column :casserver_st, :service, :string
246 | # end
247 | # end
248 | #
249 | # class AddExtraAttributes < V 0.72
250 | # def self.up
251 | # add_column :casserver_tgt, :extra_attributes, :text
252 | # end
253 | #
254 | # def self.down
255 | # remove_column :casserver_tgt, :extra_attributes
256 | # end
257 | # end
258 | #
259 | # class RenamePgtForeignKeys < V 0.80
260 | # def self.up
261 | # rename_column :casserver_st, :proxy_granting_ticket_id, :granted_by_pgt_id
262 | # rename_column :casserver_st, :tgt_id, :granted_by_tgt_id
263 | # end
264 | #
265 | # def self.down
266 | # rename_column :casserver_st, :granted_by_pgt_id, :proxy_granting_ticket_id
267 | # rename_column :casserver_st, :granted_by_tgt_id, :tgt_id
268 | # end
269 | # end
270 | end
271 |
--------------------------------------------------------------------------------
/lib/casserver/utils.rb:
--------------------------------------------------------------------------------
1 | require 'crypt-isaac'
2 |
3 | # Misc utility function used throughout by the RubyCAS-Server.
4 | module CASServer
5 | module Utils
6 | def random_string(max_length = 29)
7 | rg = Crypt::ISAAC.new
8 | max = 4294619050
9 | r = "#{Time.now.to_i}r%X%X%X%X%X%X%X%X" %
10 | [rg.rand(max), rg.rand(max), rg.rand(max), rg.rand(max),
11 | rg.rand(max), rg.rand(max), rg.rand(max), rg.rand(max)]
12 | r[0..max_length-1]
13 | end
14 | module_function :random_string
15 |
16 | def log_controller_action(controller, params)
17 | $LOG << "\n"
18 |
19 | /`(.*)'/.match(caller[1])
20 | method = $~[1]
21 |
22 | if params.respond_to? :dup
23 | params2 = params.dup
24 | params2['password'] = '******' if params2['password']
25 | else
26 | params2 = params
27 | end
28 | $LOG.debug("Processing #{controller}::#{method} #{params2.inspect}")
29 | end
30 | module_function :log_controller_action
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/casserver/views/_login_form.erb:
--------------------------------------------------------------------------------
1 | <%# coding: UTF-8 -%>
2 |