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 +20 -0
- data/LICENSE +1 -1
- data/README.md +17 -3
- data/Rakefile +2 -1
- data/config.ru +7 -1
- data/config/config.example.yml +1 -1
- data/lib/casserver/authenticators/sql_encrypted.rb +0 -1
- data/lib/casserver/authenticators/sql_rest_auth.rb +7 -7
- data/lib/casserver/cas.rb +23 -15
- data/lib/casserver/localization.rb +4 -82
- data/lib/casserver/server.rb +24 -17
- data/lib/casserver/views/_login_form.erb +4 -4
- data/lib/casserver/views/layout.erb +1 -1
- data/lib/casserver/views/login.erb +1 -1
- data/locales/de.yml +27 -0
- data/locales/en.yml +26 -0
- data/locales/es.yml +26 -0
- data/locales/es_ar.yml +26 -0
- data/locales/fr.yml +26 -0
- data/locales/jp.yml +26 -0
- data/locales/pl.yml +26 -0
- data/locales/pt.yml +26 -0
- data/locales/ru.yml +26 -0
- data/locales/zh.yml +26 -0
- data/locales/zh_tw.yml +26 -0
- data/public/themes/cas.css +6 -1
- data/rubycas-server.gemspec +7 -11
- data/spec/casserver_spec.rb +12 -5
- data/spec/spec_helper.rb +6 -5
- data/tasks/spec.rake +4 -4
- metadata +60 -48
- data/po/de_DE/rubycas-server.po +0 -127
- data/po/es_ES/rubycas-server.po +0 -123
- data/po/fr_FR/rubycas-server.po +0 -128
- data/po/ja_JP/rubycas-server.po +0 -126
- data/po/pl_PL/rubycas-server.po +0 -123
- data/po/pt_BR/rubycas-server.po +0 -123
- data/po/ru_RU/rubycas-server.po +0 -118
- data/po/rubycas-server.pot +0 -112
- data/po/zh_CN/rubycas-server.po +0 -113
- data/po/zh_TW/rubycas-server.po +0 -113
- data/tasks/localization.rake +0 -13
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
data/README.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
-
#
|
1
|
+
# RubyCAS-Server ![http://stillmaintained.com/rubycas/rubycas-server](http://stillmaintained.com/rubycas/rubycas-server.png)
|
2
2
|
|
3
|
-
|
3
|
+
## Copyright
|
4
4
|
|
5
|
-
|
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
|
-
|
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"
|
data/config/config.example.yml
CHANGED
@@ -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
|
18
|
+
# The following are example configurations for each of these three methods:
|
19
19
|
#
|
20
20
|
|
21
21
|
|
@@ -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 =
|
76
|
-
|
77
|
-
digest = secure_digest(digest, salt, password,
|
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
|
data/lib/casserver/cas.rb
CHANGED
@@ -119,20 +119,20 @@ module CASServer::CAS
|
|
119
119
|
|
120
120
|
success = false
|
121
121
|
if ticket.nil?
|
122
|
-
error =
|
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 =
|
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 =
|
131
|
+
error = t.error.login_timeout
|
132
132
|
$LOG.warn "Expired login ticket '#{ticket}'"
|
133
133
|
end
|
134
134
|
else
|
135
|
-
error =
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
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
|
-
|
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
|
data/lib/casserver/server.rb
CHANGED
@@ -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
|
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
|
-
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
563
|
+
@message = {:type => 'confirmation', :message => t.notice.success_logged_out}
|
557
564
|
|
558
|
-
@message[:message] +=
|
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
|
-
|
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='<%=
|
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
|
-
<%=
|
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
|
-
<%=
|
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="<%=
|
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>
|