rubycas-server 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,23 @@
1
+ === 1.1.0 :: 2012-04-19
2
+
3
+ * NEW:
4
+ * Localization is now done using R18n instead of Gettext.
5
+ * Restored compatibility with Sinatra 1.2
6
+ * Now compatibile with Ruby 1.9.3
7
+ * Can now run without Bundler if all required dependencies are already installed.
8
+ * es_AR translations.
9
+
10
+ * CHANGED:
11
+ * It is no longer possible to select the locale by adding a 'lang=xx' attribute to the
12
+ request URL. The locale is selected using the 'Accept-Lanuage' header sent in the
13
+ request. However the old 'lang' functionality may be restored in a future version.
14
+ * Certain localized string keys have changed. If you are using your own custom views
15
+ you may need to modify them accordingly.
16
+
17
+ * FIXED:
18
+ * Removed unnecessary bcrypt requirement for encrypted sql authenticators.
19
+ * Single Sign Out requests should now work with SSL-enabled services.
20
+
1
21
  === 1.0.1 :: 2011-11-22
2
22
 
3
23
  * NEW:
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Portions of RubyCAS-Server contributed by Matt Zukowski are copyright (c) 2009 Urbacon Ltd.
1
+ Portions of RubyCAS-Server contributed by Matt Zukowski are copyright (c) 2011 Urbacon Ltd.
2
2
  Other portions are copyright of their respective authors.
3
3
 
4
4
  The MIT License
data/README.md CHANGED
@@ -1,5 +1,19 @@
1
- # MOVED!
1
+ # RubyCAS-Server ![http://stillmaintained.com/rubycas/rubycas-server](http://stillmaintained.com/rubycas/rubycas-server.png)
2
2
 
3
- This repo has been moved to https://github.com/rubycas/rubycas-server.
3
+ ## Copyright
4
4
 
5
- The fork you are looking at is no longer updated. Please change your git remotes to the new rubycas URL.
5
+ Portions contributed by Matt Zukowski are copyright (c) 2011 Urbacon Ltd.
6
+ Other portions are copyright of their respective authors.
7
+
8
+ ## Authors
9
+
10
+ See http://github.com/gunark/rubycas-server/commits/
11
+
12
+ ## Installation
13
+
14
+ See http://code.google.com/p/rubycas-server
15
+
16
+ ## License
17
+
18
+ RubyCAS-Server is licensed for use under the terms of the MIT License.
19
+ See the LICENSE file bundled with the official RubyCAS-Server distribution for details.
data/Rakefile CHANGED
@@ -1 +1,2 @@
1
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
2
+ task :default => :spec
data/config.ru CHANGED
@@ -1,5 +1,11 @@
1
1
  require 'rubygems'
2
- require 'bundler/setup'
2
+
3
+ # Assume all necessary gems are in place if bundler is not installed.
4
+ begin
5
+ require 'bundler/setup'
6
+ rescue LoadError => e
7
+ raise e unless e.message =~ /no such file to load -- bundler/
8
+ end
3
9
 
4
10
  $:.unshift "#{File.dirname(__FILE__)}/lib"
5
11
  require "casserver"
@@ -15,7 +15,7 @@
15
15
  # passenger -- served out by Apache via the mod_rails/mod_rack module
16
16
  # (see http://www.modrails.com/)
17
17
  #
18
- # The following are exampe configurations for each of these three methods:
18
+ # The following are example configurations for each of these three methods:
19
19
  #
20
20
 
21
21
 
@@ -3,7 +3,6 @@ require 'casserver/authenticators/sql'
3
3
  require 'digest/sha1'
4
4
  require 'digest/sha2'
5
5
  require 'crypt-isaac'
6
- require 'bcrypt'
7
6
 
8
7
  # This is a more secure version of the SQL authenticator. Passwords are encrypted
9
8
  # rather than being stored in plain text.
@@ -38,7 +38,7 @@ class CASServer::Authenticators::SQLRestAuth < CASServer::Authenticators::SQLEnc
38
38
  if results.size > 0
39
39
  $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
40
40
  user = results.first
41
- if user.crypted_password == user.encrypt(@password)
41
+ if user.crypted_password == user.encrypt(@password,@options[:site_key],@options[:digest_streches])
42
42
  unless @options[:extra_attributes].blank?
43
43
  extract_extra(user)
44
44
  log_extra
@@ -63,18 +63,18 @@ class CASServer::Authenticators::SQLRestAuth < CASServer::Authenticators::SQLEnc
63
63
  raise "#{self} should be inclued in an ActiveRecord class!" unless mod.respond_to?(:before_save)
64
64
  end
65
65
 
66
- def encrypt(password)
67
- password_digest(password, self.salt)
66
+ def encrypt(password,site_key,digest_streches)
67
+ password_digest(password, self.salt,site_key,digest_streches)
68
68
  end
69
69
 
70
70
  def secure_digest(*args)
71
71
  Digest::SHA1.hexdigest(args.flatten.join('--'))
72
72
  end
73
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])
74
+ def password_digest(password, salt,site_key,digest_streches)
75
+ digest = site_key
76
+ digest_streches.times do
77
+ digest = secure_digest(digest, salt, password, site_key)
78
78
  end
79
79
  digest
80
80
  end
@@ -119,20 +119,20 @@ module CASServer::CAS
119
119
 
120
120
  success = false
121
121
  if ticket.nil?
122
- error = _("Your login request did not include a login ticket. There may be a problem with the authentication system.")
122
+ error = t.error.no_login_ticket
123
123
  $LOG.warn "Missing login ticket."
124
124
  elsif lt = LoginTicket.find_by_ticket(ticket)
125
125
  if lt.consumed?
126
- error = _("The login ticket you provided has already been used up. Please try logging in again.")
126
+ error = t.error.login_ticket_already_used
127
127
  $LOG.warn "Login ticket '#{ticket}' previously used up"
128
128
  elsif Time.now - lt.created_on < settings.config[:maximum_unused_login_ticket_lifetime]
129
129
  $LOG.info "Login ticket '#{ticket}' successfully validated"
130
130
  else
131
- error = _("You took too long to enter your credentials. Please try again.")
131
+ error = t.error.login_timeout
132
132
  $LOG.warn "Expired login ticket '#{ticket}'"
133
133
  end
134
134
  else
135
- error = _("The login ticket you provided is invalid. There may be a problem with the authentication system.")
135
+ error = t.error.invalid_login_ticket
136
136
  $LOG.warn "Invalid login ticket '#{ticket}'"
137
137
  end
138
138
 
@@ -244,18 +244,26 @@ module CASServer::CAS
244
244
  uri.path = '/' if uri.path.empty?
245
245
  time = Time.now
246
246
  rand = CASServer::Utils.random_string
247
-
247
+ path = uri.path
248
+ req = Net::HTTP::Post.new(path)
249
+ req.set_form_data('logoutRequest' => %{<samlp:LogoutRequest ID="#{rand}" Version="2.0" IssueInstant="#{time.rfc2822}">
250
+ <saml:NameID></saml:NameID>
251
+ <samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
252
+ </samlp:LogoutRequest>})
253
+
248
254
  begin
249
- response = Net::HTTP.post_form(uri, {'logoutRequest' => URI.escape(%{<samlp:LogoutRequest ID="#{rand}" Version="2.0" IssueInstant="#{time.rfc2822}">
250
- <saml:NameID></saml:NameID>
251
- <samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
252
- </samlp:LogoutRequest>})})
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
255
+ http = Net::HTTP.new(uri.host, uri.port)
256
+ http.use_ssl = true if uri.scheme =='https'
257
+
258
+ http.start do |conn|
259
+ response = conn.request(req)
260
+ if response.kind_of? Net::HTTPSuccess
261
+ $LOG.info "Logout notification successfully posted to #{st.service.inspect}."
262
+ return true
263
+ else
264
+ $LOG.error "Service #{st.service.inspect} responed to logout notification with code '#{response.code}'!"
265
+ return false
266
+ end
259
267
  end
260
268
  rescue Exception => e
261
269
  $LOG.error "Failed to send logout notification to service #{st.service.inspect} due to #{e}"
@@ -1,91 +1,13 @@
1
- require "gettext"
2
- require "gettext/cgi"
3
- require 'active_support'
1
+ require 'sinatra/r18n'
4
2
 
5
3
  module CASServer
6
4
  module Localization
7
5
  def self.included(mod)
8
6
  mod.module_eval do
9
- include GetText
7
+ register Sinatra::R18n
8
+ set :default_locale, 'en'
9
+ set :translations, './locales'
10
10
  end
11
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
12
  end
91
13
  end
@@ -19,8 +19,14 @@ module CASServer
19
19
  include CASServer::CAS # CAS protocol helpers
20
20
  include Localization
21
21
 
22
+ # Use :public_folder for Sinatra >= 1.3, and :public for older versions.
23
+ def self.use_public_folder?
24
+ Sinatra.const_defined?("VERSION") && Gem::Version.new(Sinatra::VERSION) >= Gem::Version.new("1.3.0")
25
+ end
26
+
22
27
  set :app_file, __FILE__
23
- set :public_folder, Proc.new { settings.config[:public_dir] || File.join(root, "..", "..", "public") }
28
+ set( use_public_folder? ? :public_folder : :public, # Workaround for differences in Sinatra versions.
29
+ Proc.new { settings.config[:public_dir] || File.join(root, "..", "..", "public") } )
24
30
 
25
31
  config = HashWithIndifferentAccess.new(
26
32
  :maximum_unused_login_ticket_lifetime => 5.minutes,
@@ -34,11 +40,13 @@ module CASServer
34
40
  def self.uri_path
35
41
  config[:uri_path]
36
42
  end
37
-
43
+
38
44
  # Strip the config.uri_path from the request.path_info...
39
45
  # FIXME: do we really need to override all of Sinatra's #static! to make this happen?
40
46
  def static!
41
- return if (public_dir = settings.public_folder).nil?
47
+ # Workaround for differences in Sinatra versions.
48
+ public_dir = Server.use_public_folder? ? settings.public_folder : settings.public
49
+ return if public_dir.nil?
42
50
  public_dir = File.expand_path(public_dir)
43
51
 
44
52
  path = File.expand_path(public_dir + unescape(request.path_info.gsub(/^#{settings.config[:uri_path]}/,'')))
@@ -281,7 +289,6 @@ module CASServer
281
289
  end
282
290
 
283
291
  before do
284
- GetText.locale = determine_locale(request)
285
292
  content_type :html, 'charset' => 'utf-8'
286
293
  @theme = settings.config[:theme]
287
294
  @organization = settings.config[:organization]
@@ -320,7 +327,7 @@ module CASServer
320
327
 
321
328
  if tgt and !tgt_error
322
329
  @message = {:type => 'notice',
323
- :message => _("You are currently logged in as '%s'. If this is not you, please log in below.") % tgt.username }
330
+ :message => t.notice.logged_in_as(tgt.username)}
324
331
  elsif tgt_error
325
332
  $LOG.debug("Ticket granting cookie could not be validated: #{tgt_error}")
326
333
  elsif !tgt
@@ -329,7 +336,7 @@ module CASServer
329
336
 
330
337
  if params['redirection_loop_intercepted']
331
338
  @message = {:type => 'mistake',
332
- :message => _("The client and server are unable to negotiate authentication. Please try logging in again later.")}
339
+ :message => t.error.unable_to_authenticate}
333
340
  end
334
341
 
335
342
  begin
@@ -351,14 +358,14 @@ module CASServer
351
358
  elsif @gateway
352
359
  $LOG.error("This is a gateway request but no service parameter was given!")
353
360
  @message = {:type => 'mistake',
354
- :message => _("The server cannot fulfill this gateway request because no service parameter was given.")}
361
+ :message => t.error.no_service_parameter_given}
355
362
  else
356
363
  $LOG.info("Proceeding with CAS login without a target service.")
357
364
  end
358
365
  rescue URI::InvalidURIError
359
366
  $LOG.error("The service '#{@service}' is not a valid URI!")
360
367
  @message = {:type => 'mistake',
361
- :message => _("The target service your browser supplied appears to be invalid. Please contact your system administrator for help.")}
368
+ :message => t.error.invalid_target_service}
362
369
  end
363
370
 
364
371
  lt = generate_login_ticket
@@ -387,7 +394,7 @@ module CASServer
387
394
  render :login_form
388
395
  else
389
396
  status 500
390
- render _("Could not guess the CAS login URI. Please supply a submitToURI parameter with your request.")
397
+ render t.error.invalid_submit_to_uri
391
398
  end
392
399
  else
393
400
  render @template_engine, :login
@@ -420,7 +427,7 @@ module CASServer
420
427
  # generate another login ticket to allow for re-submitting the form
421
428
  @lt = generate_login_ticket.ticket
422
429
  status 500
423
- render @template_engine, :login
430
+ return render @template_engine, :login
424
431
  end
425
432
 
426
433
  # generate another login ticket to allow for re-submitting the form after a post
@@ -468,7 +475,7 @@ module CASServer
468
475
 
469
476
  if @service.blank?
470
477
  $LOG.info("Successfully authenticated user '#{@username}' at '#{tgt.client_hostname}'. No service param was given, so we will not redirect.")
471
- @message = {:type => 'confirmation', :message => _("You have successfully logged in.")}
478
+ @message = {:type => 'confirmation', :message => t.notice.success_logged_in}
472
479
  else
473
480
  @st = generate_service_ticket(@service, @username, tgt)
474
481
 
@@ -481,20 +488,20 @@ module CASServer
481
488
  $LOG.error("The service '#{@service}' is not a valid URI!")
482
489
  @message = {
483
490
  :type => 'mistake',
484
- :message => _("The target service your browser supplied appears to be invalid. Please contact your system administrator for help.")
491
+ :message => t.error.invalid_target_service
485
492
  }
486
493
  end
487
494
  end
488
495
  else
489
496
  $LOG.warn("Invalid credentials given for user '#{@username}'")
490
- @message = {:type => 'mistake', :message => _("Incorrect username or password.")}
497
+ @message = {:type => 'mistake', :message => t.error.incorrect_username_or_password}
491
498
  status 401
492
499
  end
493
500
  rescue CASServer::AuthenticatorError => e
494
501
  $LOG.error(e)
495
502
  # generate another login ticket to allow for re-submitting the form
496
503
  @lt = generate_login_ticket.ticket
497
- @message = {:type => 'mistake', :message => _(e.to_s)}
504
+ @message = {:type => 'mistake', :message => e.to_s}
498
505
  status 401
499
506
  end
500
507
 
@@ -553,9 +560,9 @@ module CASServer
553
560
  $LOG.warn("User tried to log out without a valid ticket-granting ticket.")
554
561
  end
555
562
 
556
- @message = {:type => 'confirmation', :message => _("You have successfully logged out.")}
563
+ @message = {:type => 'confirmation', :message => t.notice.success_logged_out}
557
564
 
558
- @message[:message] +=_(" Please click on the following link to continue:") if @continue_url
565
+ @message[:message] += t.notice.click_to_continue if @continue_url
559
566
 
560
567
  @lt = generate_login_ticket
561
568
 
@@ -581,7 +588,7 @@ module CASServer
581
588
 
582
589
  status 422
583
590
 
584
- "To generate a login ticket, you must make a POST request."
591
+ t.error.login_ticket_needs_post_request
585
592
  end
586
593
 
587
594
 
@@ -1,11 +1,11 @@
1
1
  <%# coding: UTF-8 -%>
2
2
  <form method="post" action="<%= @form_action || "login" %>" id="login-form"
3
- onsubmit="submitbutton = document.getElementById('login-submit'); submitbutton.value='<%= _("Please wait...") %>'; submitbutton.disabled=true; return true;">
3
+ onsubmit="submitbutton = document.getElementById('login-submit'); submitbutton.value='<%= t.notice.please_wait %>'; submitbutton.disabled=true; return true;">
4
4
  <table id="form-layout">
5
5
  <tr>
6
6
  <td id="username-label-container">
7
7
  <label id="username-label" for="username">
8
- <%= _("Username") %>
8
+ <%= t.label.username %>
9
9
  </label>
10
10
  </td>
11
11
  <td id="username-container">
@@ -16,7 +16,7 @@
16
16
  <tr>
17
17
  <td id="password-label-container">
18
18
  <label id="password-label" for="password">
19
- <%= _("Password") %>
19
+ <%= t.label.password %>
20
20
  </label>
21
21
  </td>
22
22
  <td id="password-container">
@@ -29,7 +29,7 @@
29
29
  <td id="submit-container">
30
30
  <input type="hidden" id="lt" name="lt" value="<%= escape_html @lt %>" />
31
31
  <input type="hidden" id="service" name="service" value="<%= escape_html @service %>" />
32
- <input type="submit" class="button" accesskey="l" value="<%= _("LOGIN") %>"
32
+ <input type="submit" class="button" accesskey="l" value="<%= t.button.login %>"
33
33
  tabindex="4" id="login-submit" />
34
34
  </td>
35
35
  </tr>