rubycas-server 0.7.1.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/CHANGELOG +292 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +26 -0
  4. data/README.md +36 -0
  5. data/Rakefile +0 -3
  6. data/bin/rubycas-server +24 -19
  7. data/lib/casserver.rb +7 -110
  8. data/lib/casserver/authenticators/active_directory_ldap.rb +8 -0
  9. data/lib/casserver/authenticators/active_resource.rb +125 -0
  10. data/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb +43 -0
  11. data/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb +92 -0
  12. data/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb +34 -0
  13. data/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb +59 -0
  14. data/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb +50 -0
  15. data/lib/casserver/authenticators/base.rb +30 -11
  16. data/lib/casserver/authenticators/client_certificate.rb +7 -6
  17. data/lib/casserver/authenticators/google.rb +13 -9
  18. data/lib/casserver/authenticators/ldap.rb +37 -28
  19. data/lib/casserver/authenticators/ntlm.rb +9 -9
  20. data/lib/casserver/authenticators/open_id.rb +3 -3
  21. data/lib/casserver/authenticators/sql.rb +65 -34
  22. data/lib/casserver/authenticators/sql_authlogic.rb +93 -0
  23. data/lib/casserver/authenticators/sql_encrypted.rb +44 -44
  24. data/lib/casserver/authenticators/sql_md5.rb +2 -2
  25. data/lib/casserver/authenticators/sql_rest_auth.rb +82 -0
  26. data/lib/casserver/authenticators/test.rb +10 -7
  27. data/lib/casserver/cas.rb +94 -94
  28. data/lib/casserver/localization.rb +91 -0
  29. data/lib/casserver/model.rb +270 -0
  30. data/lib/casserver/server.rb +745 -0
  31. data/lib/casserver/utils.rb +9 -7
  32. data/lib/casserver/views/_login_form.erb +42 -0
  33. data/lib/casserver/views/layout.erb +18 -0
  34. data/lib/casserver/views/login.erb +30 -0
  35. data/lib/casserver/views/proxy.builder +12 -0
  36. data/lib/casserver/views/proxy_validate.builder +25 -0
  37. data/lib/casserver/views/service_validate.builder +18 -0
  38. data/lib/casserver/views/validate.erb +2 -0
  39. data/po/de_DE/rubycas-server.po +127 -0
  40. data/po/es_ES/rubycas-server.po +123 -0
  41. data/po/fr_FR/rubycas-server.po +128 -0
  42. data/po/ja_JP/rubycas-server.po +126 -0
  43. data/po/pl_PL/rubycas-server.po +123 -0
  44. data/po/pt_BR/rubycas-server.po +123 -0
  45. data/po/ru_RU/rubycas-server.po +118 -0
  46. data/po/rubycas-server.pot +112 -0
  47. data/po/zh_CN/rubycas-server.po +113 -0
  48. data/po/zh_TW/rubycas-server.po +113 -0
  49. data/public/themes/cas.css +121 -0
  50. data/{lib → public}/themes/notice.png +0 -0
  51. data/{lib → public}/themes/ok.png +0 -0
  52. data/{lib → public}/themes/simple/bg.png +0 -0
  53. data/public/themes/simple/favicon.png +0 -0
  54. data/{lib → public}/themes/simple/login_box_bg.png +0 -0
  55. data/{lib → public}/themes/simple/logo.png +0 -0
  56. data/public/themes/simple/theme.css +28 -0
  57. data/{lib → public}/themes/urbacon/bg.png +0 -0
  58. data/{lib → public}/themes/urbacon/login_box_bg.png +0 -0
  59. data/{lib → public}/themes/urbacon/logo.png +0 -0
  60. data/public/themes/urbacon/theme.css +33 -0
  61. data/{lib → public}/themes/warning.png +0 -0
  62. data/resources/init.d.sh +1 -1
  63. data/rubycas-server.gemspec +57 -0
  64. data/setup.rb +4 -4
  65. data/spec/alt_config.yml +50 -0
  66. data/spec/authenticators/active_resource_spec.rb +109 -0
  67. data/spec/authenticators/ldap_spec.rb +53 -0
  68. data/spec/casserver_spec.rb +149 -0
  69. data/spec/default_config.yml +50 -0
  70. data/spec/model_spec.rb +42 -0
  71. data/spec/spec.opts +4 -0
  72. data/spec/spec_helper.rb +88 -0
  73. data/spec/utils_spec.rb +53 -0
  74. data/tasks/bundler.rake +4 -0
  75. data/tasks/db/migrate.rake +12 -0
  76. data/tasks/localization.rake +13 -0
  77. data/tasks/spec.rake +10 -0
  78. metadata +294 -91
  79. data/CHANGELOG.txt +0 -1
  80. data/History.txt +0 -252
  81. data/LICENSE.txt +0 -504
  82. data/Manifest.txt +0 -72
  83. data/PostInstall.txt +0 -3
  84. data/README.txt +0 -25
  85. data/bin/rubycas-server-ctl +0 -22
  86. data/config.example.yml +0 -442
  87. data/config/hoe.rb +0 -76
  88. data/config/requirements.rb +0 -15
  89. data/custom_views.example.rb +0 -11
  90. data/lib/casserver/conf.rb +0 -112
  91. data/lib/casserver/controllers.rb +0 -452
  92. data/lib/casserver/environment.rb +0 -30
  93. data/lib/casserver/models.rb +0 -218
  94. data/lib/casserver/postambles.rb +0 -174
  95. data/lib/casserver/version.rb +0 -9
  96. data/lib/casserver/views.rb +0 -243
  97. data/lib/rubycas-server.rb +0 -1
  98. data/lib/rubycas-server/version.rb +0 -1
  99. data/lib/themes/cas.css +0 -121
  100. data/lib/themes/simple/theme.css +0 -28
  101. data/lib/themes/urbacon/theme.css +0 -33
  102. data/misc/basic_cas_single_signon_mechanism_diagram.png +0 -0
  103. data/misc/basic_cas_single_signon_mechanism_diagram.svg +0 -652
  104. data/script/console +0 -10
  105. data/script/destroy +0 -14
  106. data/script/generate +0 -14
  107. data/script/txt2html +0 -82
  108. data/tasks/deployment.rake +0 -34
  109. data/tasks/environment.rake +0 -7
  110. data/tasks/website.rake +0 -17
  111. data/vendor/isaac_0.9.1/LICENSE +0 -26
  112. data/vendor/isaac_0.9.1/README +0 -78
  113. data/vendor/isaac_0.9.1/TODO +0 -3
  114. data/vendor/isaac_0.9.1/VERSIONS +0 -3
  115. data/vendor/isaac_0.9.1/crypt/ISAAC.rb +0 -171
  116. data/vendor/isaac_0.9.1/isaac.gemspec +0 -39
  117. data/vendor/isaac_0.9.1/setup.rb +0 -596
  118. data/vendor/isaac_0.9.1/test/TC_ISAAC.rb +0 -76
  119. data/website/index.html +0 -40
  120. data/website/index.txt +0 -3
  121. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  122. data/website/stylesheets/screen.css +0 -138
  123. data/website/template.html.erb +0 -40
@@ -0,0 +1,91 @@
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
@@ -0,0 +1,270 @@
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
@@ -0,0 +1,745 @@
1
+ require 'sinatra/base'
2
+ require 'casserver/localization'
3
+ require 'casserver/utils'
4
+ require 'casserver/cas'
5
+
6
+ require 'logger'
7
+ $LOG ||= Logger.new(STDOUT)
8
+
9
+ module CASServer
10
+ class Server < Sinatra::Base
11
+ CONFIG_FILE = ENV['CONFIG_FILE'] || "/etc/rubycas-server/config.yml"
12
+
13
+ include CASServer::CAS # CAS protocol helpers
14
+ include Localization
15
+
16
+ set :app_file, __FILE__
17
+ set :public, Proc.new { settings.config[:public_dir] || File.join(root, "..", "..", "public") }
18
+
19
+ config = HashWithIndifferentAccess.new(
20
+ :maximum_unused_login_ticket_lifetime => 5.minutes,
21
+ :maximum_unused_service_ticket_lifetime => 5.minutes, # CAS Protocol Spec, sec. 3.2.1 (recommended expiry time)
22
+ :maximum_session_lifetime => 2.days, # all tickets are deleted after this period of time
23
+ :log => {:file => 'casserver.log', :level => 'DEBUG'},
24
+ :uri_path => ""
25
+ )
26
+ set :config, config
27
+
28
+ def self.uri_path
29
+ config[:uri_path]
30
+ end
31
+
32
+ # Strip the config.uri_path from the request.path_info...
33
+ # FIXME: do we really need to override all of Sinatra's #static! to make this happen?
34
+ def static!
35
+ return if (public_dir = settings.public).nil?
36
+ public_dir = File.expand_path(public_dir)
37
+
38
+ path = File.expand_path(public_dir + unescape(request.path_info.gsub(/^#{settings.config[:uri_path]}/,'')))
39
+ return if path[0, public_dir.length] != public_dir
40
+ return unless File.file?(path)
41
+
42
+ env['sinatra.static_file'] = path
43
+ send_file path, :disposition => nil
44
+ end
45
+
46
+ def self.run!(options={})
47
+ set options
48
+
49
+ handler = detect_rack_handler
50
+ handler_name = handler.name.gsub(/.*::/, '')
51
+
52
+ puts "== RubyCAS-Server is starting up " +
53
+ "on port #{config[:port] || port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
54
+
55
+ begin
56
+ opts = handler_options
57
+ rescue Exception => e
58
+ print_cli_message e, :error
59
+ raise e
60
+ end
61
+
62
+ handler.run self, opts do |server|
63
+ [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
64
+ set :running, true
65
+ end
66
+ rescue Errno::EADDRINUSE => e
67
+ puts "== Something is already running on port #{port}!"
68
+ end
69
+
70
+ def self.quit!(server, handler_name)
71
+ ## Use thins' hard #stop! if available, otherwise just #stop
72
+ server.respond_to?(:stop!) ? server.stop! : server.stop
73
+ puts "\n== RubyCAS-Server is shutting down" unless handler_name =~/cgi/i
74
+ end
75
+
76
+ def self.print_cli_message(msg, type = :info)
77
+ if respond_to?(:config) && config && config[:quiet]
78
+ return
79
+ end
80
+
81
+ if type == :error
82
+ io = $stderr
83
+ prefix = "!!! "
84
+ else
85
+ io = $stdout
86
+ prefix = ">>> "
87
+ end
88
+
89
+ io.puts
90
+ io.puts "#{prefix}#{msg}"
91
+ io.puts
92
+ end
93
+
94
+ def self.load_config_file(config_file)
95
+ begin
96
+ config_file = File.open(config_file)
97
+ rescue Errno::ENOENT => e
98
+
99
+ print_cli_message "Config file #{config_file} does not exist!", :error
100
+ print_cli_message "Would you like the default config file copied to #{config_file.inspect}? [y/N]"
101
+ if gets.strip.downcase == 'y'
102
+ require 'fileutils'
103
+ default_config = File.dirname(__FILE__) + '/../../config/config.example.yml'
104
+
105
+ if !File.exists?(File.dirname(config_file))
106
+ print_cli_message "Creating config directory..."
107
+ FileUtils.mkdir_p(File.dirname(config_file), :verbose => true)
108
+ end
109
+
110
+ print_cli_message "Copying #{default_config.inspect} to #{config_file.inspect}..."
111
+ FileUtils.cp(default_config, config_file, :verbose => true)
112
+ print_cli_message "The default config has been copied. You should now edit it and try starting again."
113
+ exit
114
+ else
115
+ print_cli_message "Cannot start RubyCAS-Server without a valid config file.", :error
116
+ raise e
117
+ end
118
+ rescue Errno::EACCES => e
119
+ print_cli_message "Config file #{config_file.inspect} is not readable (permission denied)!", :error
120
+ raise e
121
+ rescue => e
122
+ print_cli_message "Config file #{config_file.inspect} could not be read!", :error
123
+ raise e
124
+ end
125
+
126
+ config.merge! HashWithIndifferentAccess.new(YAML.load(config_file))
127
+ set :server, config[:server] || 'webrick'
128
+ end
129
+
130
+ def self.reconfigure!(config)
131
+ config.each do |key, val|
132
+ self.config[key] = val
133
+ end
134
+ init_database!
135
+ init_logger!
136
+ init_authenticators!
137
+ end
138
+
139
+ def self.handler_options
140
+ handler_options = {
141
+ :Host => bind || config[:bind_address],
142
+ :Port => config[:port] || 443
143
+ }
144
+
145
+ handler_options.merge(handler_ssl_options).to_hash.symbolize_keys!
146
+ end
147
+
148
+ def self.handler_ssl_options
149
+ return {} unless config[:ssl_cert]
150
+
151
+ cert_path = config[:ssl_cert]
152
+ key_path = config[:ssl_key] || config[:ssl_cert]
153
+
154
+ unless cert_path.nil? && key_path.nil?
155
+ raise "The ssl_cert and ssl_key options cannot be used with mongrel. You will have to run your " +
156
+ " server behind a reverse proxy if you want SSL under mongrel." if
157
+ config[:server] == 'mongrel'
158
+
159
+ raise "The specified certificate file #{cert_path.inspect} does not exist or is not readable. " +
160
+ " Your 'ssl_cert' configuration setting must be a path to a valid " +
161
+ " ssl certificate." unless
162
+ File.exists? cert_path
163
+
164
+ raise "The specified key file #{key_path.inspect} does not exist or is not readable. " +
165
+ " Your 'ssl_key' configuration setting must be a path to a valid " +
166
+ " ssl private key." unless
167
+ File.exists? key_path
168
+
169
+ require 'openssl'
170
+ require 'webrick/https'
171
+
172
+ cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
173
+ key = OpenSSL::PKey::RSA.new(File.read(key_path))
174
+
175
+ {
176
+ :SSLEnable => true,
177
+ :SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
178
+ :SSLCertificate => cert,
179
+ :SSLPrivateKey => key
180
+ }
181
+ end
182
+ end
183
+
184
+ def self.init_authenticators!
185
+ auth = []
186
+
187
+ if config[:authenticator].nil?
188
+ print_cli_message "No authenticators have been configured. Please double-check your config file (#{CONFIG_FILE.inspect}).", :error
189
+ exit 1
190
+ end
191
+
192
+ begin
193
+ # attempt to instantiate the authenticator
194
+ config[:authenticator] = [config[:authenticator]] unless config[:authenticator].instance_of? Array
195
+ config[:authenticator].each { |authenticator| auth << authenticator[:class].constantize}
196
+ rescue NameError
197
+ if config[:authenticator].instance_of? Array
198
+ config[:authenticator].each do |authenticator|
199
+ if !authenticator[:source].nil?
200
+ # config.yml explicitly names source file
201
+ require authenticator[:source]
202
+ else
203
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
204
+ auth_rb = authenticator[:class].underscore.gsub('cas_server/', '')
205
+ require 'casserver/'+auth_rb
206
+ end
207
+ auth << authenticator[:class].constantize
208
+ end
209
+ else
210
+ if config[:authenticator][:source]
211
+ # config.yml explicitly names source file
212
+ require config[:authenticator][:source]
213
+ else
214
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
215
+ auth_rb = config[:authenticator][:class].underscore.gsub('cas_server/', '')
216
+ require 'casserver/'+auth_rb
217
+ end
218
+
219
+ auth << config[:authenticator][:class].constantize
220
+ config[:authenticator] = [config[:authenticator]]
221
+ end
222
+ end
223
+
224
+ auth.zip(config[:authenticator]).each_with_index{ |auth_conf, index|
225
+ authenticator, conf = auth_conf
226
+ $LOG.debug "About to setup #{authenticator} with #{conf.inspect}..."
227
+ authenticator.setup(conf.merge('auth_index' => index)) if authenticator.respond_to?(:setup)
228
+ $LOG.debug "Done setting up #{authenticator}."
229
+ }
230
+
231
+ set :auth, auth
232
+ end
233
+
234
+ def self.init_logger!
235
+ if config[:log]
236
+ if $LOG && config[:log][:file]
237
+ print_cli_message "Redirecting RubyCAS-Server log to #{config[:log][:file]}"
238
+ #$LOG.close
239
+ $LOG = Logger.new(config[:log][:file])
240
+ end
241
+ $LOG.level = Logger.const_get(config[:log][:level]) if config[:log][:level]
242
+ end
243
+
244
+ if config[:db_log]
245
+ if $LOG && config[:db_log][:file]
246
+ $LOG.debug "Redirecting ActiveRecord log to #{config[:log][:file]}"
247
+ #$LOG.close
248
+ ActiveRecord::Base.logger = Logger.new(config[:db_log][:file])
249
+ end
250
+ ActiveRecord::Base.logger.level = Logger.const_get(config[:db_log][:level]) if config[:db_log][:level]
251
+ end
252
+ end
253
+
254
+ def self.init_database!
255
+
256
+ unless config[:disable_auto_migrations]
257
+ ActiveRecord::Base.establish_connection(config[:database])
258
+ print_cli_message "Running migrations to make sure your database schema is up to date..."
259
+ prev_db_log = ActiveRecord::Base.logger
260
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
261
+ ActiveRecord::Migration.verbose = true
262
+ ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + "/../../db/migrate")
263
+ ActiveRecord::Base.logger = prev_db_log
264
+ print_cli_message "Your database is now up to date."
265
+ end
266
+
267
+ ActiveRecord::Base.establish_connection(config[:database])
268
+ end
269
+
270
+ configure do
271
+ load_config_file(CONFIG_FILE)
272
+ init_logger!
273
+ init_database!
274
+ init_authenticators!
275
+ end
276
+
277
+ before do
278
+ GetText.locale = determine_locale(request)
279
+ content_type :html, 'charset' => 'utf-8'
280
+ @theme = settings.config[:theme]
281
+ @organization = settings.config[:organization]
282
+ @uri_path = settings.config[:uri_path]
283
+ @infoline = settings.config[:infoline]
284
+ @custom_views = settings.config[:custom_views]
285
+ @template_engine = settings.config[:template_engine] || :erb
286
+ if @template_engine != :erb
287
+ require @template_engine
288
+ @template_engine = @template_engine.to_sym
289
+ end
290
+ end
291
+
292
+ # The #.#.# comments (e.g. "2.1.3") refer to section numbers in the CAS protocol spec
293
+ # under http://www.ja-sig.org/products/cas/overview/protocol/index.html
294
+
295
+ # 2.1 :: Login
296
+
297
+ # 2.1.1
298
+ get "#{uri_path}/login" do
299
+ CASServer::Utils::log_controller_action(self.class, params)
300
+
301
+ # make sure there's no caching
302
+ headers['Pragma'] = 'no-cache'
303
+ headers['Cache-Control'] = 'no-store'
304
+ headers['Expires'] = (Time.now - 1.year).rfc2822
305
+
306
+ # optional params
307
+ @service = clean_service_url(params['service'])
308
+ @renew = params['renew']
309
+ @gateway = params['gateway'] == 'true' || params['gateway'] == '1'
310
+
311
+ if tgc = request.cookies['tgt']
312
+ tgt, tgt_error = validate_ticket_granting_ticket(tgc)
313
+ end
314
+
315
+ if tgt and !tgt_error
316
+ @message = {:type => 'notice',
317
+ :message => _("You are currently logged in as '%s'. If this is not you, please log in below.") % tgt.username }
318
+ elsif tgt_error
319
+ $LOG.debug("Ticket granting cookie could not be validated: #{tgt_error}")
320
+ elsif !tgt
321
+ $LOG.debug("No ticket granting ticket detected.")
322
+ end
323
+
324
+ if params['redirection_loop_intercepted']
325
+ @message = {:type => 'mistake',
326
+ :message => _("The client and server are unable to negotiate authentication. Please try logging in again later.")}
327
+ end
328
+
329
+ begin
330
+ if @service
331
+ if @renew
332
+ $LOG.info("Authentication renew explicitly requested. Proceeding with CAS login for service #{@service.inspect}.")
333
+ elsif tgt && !tgt_error
334
+ $LOG.debug("Valid ticket granting ticket detected.")
335
+ st = generate_service_ticket(@service, tgt.username, tgt)
336
+ service_with_ticket = service_uri_with_ticket(@service, st)
337
+ $LOG.info("User '#{tgt.username}' authenticated based on ticket granting cookie. Redirecting to service '#{@service}'.")
338
+ redirect service_with_ticket, 303 # response code 303 means "See Other" (see Appendix B in CAS Protocol spec)
339
+ elsif @gateway
340
+ $LOG.info("Redirecting unauthenticated gateway request to service '#{@service}'.")
341
+ redirect @service, 303
342
+ else
343
+ $LOG.info("Proceeding with CAS login for service #{@service.inspect}.")
344
+ end
345
+ elsif @gateway
346
+ $LOG.error("This is a gateway request but no service parameter was given!")
347
+ @message = {:type => 'mistake',
348
+ :message => _("The server cannot fulfill this gateway request because no service parameter was given.")}
349
+ else
350
+ $LOG.info("Proceeding with CAS login without a target service.")
351
+ end
352
+ rescue URI::InvalidURIError
353
+ $LOG.error("The service '#{@service}' is not a valid URI!")
354
+ @message = {:type => 'mistake',
355
+ :message => _("The target service your browser supplied appears to be invalid. Please contact your system administrator for help.")}
356
+ end
357
+
358
+ lt = generate_login_ticket
359
+
360
+ $LOG.debug("Rendering login form with lt: #{lt}, service: #{@service}, renew: #{@renew}, gateway: #{@gateway}")
361
+
362
+ @lt = lt.ticket
363
+
364
+ #$LOG.debug(env)
365
+
366
+ # If the 'onlyLoginForm' parameter is specified, we will only return the
367
+ # login form part of the page. This is useful for when you want to
368
+ # embed the login form in some external page (as an IFRAME, or otherwise).
369
+ # The optional 'submitToURI' parameter can be given to explicitly set the
370
+ # action for the form, otherwise the server will try to guess this for you.
371
+ if params.has_key? 'onlyLoginForm'
372
+ if @env['HTTP_HOST']
373
+ guessed_login_uri = "http#{@env['HTTPS'] && @env['HTTPS'] == 'on' ? 's' : ''}://#{@env['REQUEST_URI']}#{self / '/login'}"
374
+ else
375
+ guessed_login_uri = nil
376
+ end
377
+
378
+ @form_action = params['submitToURI'] || guessed_login_uri
379
+
380
+ if @form_action
381
+ render :login_form
382
+ else
383
+ status 500
384
+ render _("Could not guess the CAS login URI. Please supply a submitToURI parameter with your request.")
385
+ end
386
+ else
387
+ render @template_engine, :login
388
+ end
389
+ end
390
+
391
+
392
+ # 2.2
393
+ post "#{uri_path}/login" do
394
+ Utils::log_controller_action(self.class, params)
395
+
396
+ # 2.2.1 (optional)
397
+ @service = clean_service_url(params['service'])
398
+
399
+ # 2.2.2 (required)
400
+ @username = params['username']
401
+ @password = params['password']
402
+ @lt = params['lt']
403
+
404
+ # Remove leading and trailing widespace from username.
405
+ @username.strip! if @username
406
+
407
+ if @username && settings.config[:downcase_username]
408
+ $LOG.debug("Converting username #{@username.inspect} to lowercase because 'downcase_username' option is enabled.")
409
+ @username.downcase!
410
+ end
411
+
412
+ if error = validate_login_ticket(@lt)
413
+ @message = {:type => 'mistake', :message => error}
414
+ # generate another login ticket to allow for re-submitting the form
415
+ @lt = generate_login_ticket.ticket
416
+ status 500
417
+ render @template_engine, :login
418
+ end
419
+
420
+ # generate another login ticket to allow for re-submitting the form after a post
421
+ @lt = generate_login_ticket.ticket
422
+
423
+ $LOG.debug("Logging in with username: #{@username}, lt: #{@lt}, service: #{@service}, auth: #{settings.auth.inspect}")
424
+
425
+ credentials_are_valid = false
426
+ extra_attributes = {}
427
+ successful_authenticator = nil
428
+ begin
429
+ auth_index = 0
430
+ settings.auth.each do |auth_class|
431
+ auth = auth_class.new
432
+
433
+ auth_config = settings.config[:authenticator][auth_index]
434
+ # pass the authenticator index to the configuration hash in case the authenticator needs to know
435
+ # it splace in the authenticator queue
436
+ auth.configure(auth_config.merge('auth_index' => auth_index))
437
+
438
+ credentials_are_valid = auth.validate(
439
+ :username => @username,
440
+ :password => @password,
441
+ :service => @service,
442
+ :request => @env
443
+ )
444
+ if credentials_are_valid
445
+ extra_attributes.merge!(auth.extra_attributes) unless auth.extra_attributes.blank?
446
+ successful_authenticator = auth
447
+ break
448
+ end
449
+
450
+ auth_index += 1
451
+ end
452
+
453
+ if credentials_are_valid
454
+ $LOG.info("Credentials for username '#{@username}' successfully validated using #{successful_authenticator.class.name}.")
455
+ $LOG.debug("Authenticator provided additional user attributes: #{extra_attributes.inspect}") unless extra_attributes.blank?
456
+
457
+ # 3.6 (ticket-granting cookie)
458
+ tgt = generate_ticket_granting_ticket(@username, extra_attributes)
459
+ response.set_cookie('tgt', tgt.to_s)
460
+
461
+ $LOG.debug("Ticket granting cookie '#{tgt.inspect}' granted to #{@username.inspect}")
462
+
463
+ if @service.blank?
464
+ $LOG.info("Successfully authenticated user '#{@username}' at '#{tgt.client_hostname}'. No service param was given, so we will not redirect.")
465
+ @message = {:type => 'confirmation', :message => _("You have successfully logged in.")}
466
+ else
467
+ @st = generate_service_ticket(@service, @username, tgt)
468
+
469
+ begin
470
+ service_with_ticket = service_uri_with_ticket(@service, @st)
471
+
472
+ $LOG.info("Redirecting authenticated user '#{@username}' at '#{@st.client_hostname}' to service '#{@service}'")
473
+ redirect service_with_ticket, 303 # response code 303 means "See Other" (see Appendix B in CAS Protocol spec)
474
+ rescue URI::InvalidURIError
475
+ $LOG.error("The service '#{@service}' is not a valid URI!")
476
+ @message = {
477
+ :type => 'mistake',
478
+ :message => _("The target service your browser supplied appears to be invalid. Please contact your system administrator for help.")
479
+ }
480
+ end
481
+ end
482
+ else
483
+ $LOG.warn("Invalid credentials given for user '#{@username}'")
484
+ @message = {:type => 'mistake', :message => _("Incorrect username or password.")}
485
+ status 401
486
+ end
487
+ rescue CASServer::AuthenticatorError => e
488
+ $LOG.error(e)
489
+ # generate another login ticket to allow for re-submitting the form
490
+ @lt = generate_login_ticket.ticket
491
+ @message = {:type => 'mistake', :message => _(e.to_s)}
492
+ status 401
493
+ end
494
+
495
+ render @template_engine, :login
496
+ end
497
+
498
+ get /^#{uri_path}\/?$/ do
499
+ redirect "#{config['uri_path']}/login", 303
500
+ end
501
+
502
+
503
+ # 2.3
504
+
505
+ # 2.3.1
506
+ get "#{uri_path}/logout" do
507
+ CASServer::Utils::log_controller_action(self.class, params)
508
+
509
+ # The behaviour here is somewhat non-standard. Rather than showing just a blank
510
+ # "logout" page, we take the user back to the login page with a "you have been logged out"
511
+ # message, allowing for an opportunity to immediately log back in. This makes it
512
+ # easier for the user to log out and log in as someone else.
513
+ @service = clean_service_url(params['service'] || params['destination'])
514
+ @continue_url = params['url']
515
+
516
+ @gateway = params['gateway'] == 'true' || params['gateway'] == '1'
517
+
518
+ tgt = CASServer::Model::TicketGrantingTicket.find_by_ticket(request.cookies['tgt'])
519
+
520
+ response.delete_cookie 'tgt'
521
+
522
+ if tgt
523
+ CASServer::Model::TicketGrantingTicket.transaction do
524
+ $LOG.debug("Deleting Service/Proxy Tickets for '#{tgt}' for user '#{tgt.username}'")
525
+ tgt.granted_service_tickets.each do |st|
526
+ send_logout_notification_for_service_ticket(st) if config[:enable_single_sign_out]
527
+ # TODO: Maybe we should do some special handling if send_logout_notification_for_service_ticket fails?
528
+ # (the above method returns false if the POST results in a non-200 HTTP response).
529
+ $LOG.debug "Deleting #{st.class.name.demodulize} #{st.ticket.inspect} for service #{st.service}."
530
+ st.destroy
531
+ end
532
+
533
+ pgts = CASServer::Model::ProxyGrantingTicket.find(:all,
534
+ :conditions => [CASServer::Model::Base.connection.quote_table_name(CASServer::Model::ServiceTicket.table_name)+".username = ?", tgt.username],
535
+ :include => :service_ticket)
536
+ pgts.each do |pgt|
537
+ $LOG.debug("Deleting Proxy-Granting Ticket '#{pgt}' for user '#{pgt.service_ticket.username}'")
538
+ pgt.destroy
539
+ end
540
+
541
+ $LOG.debug("Deleting #{tgt.class.name.demodulize} '#{tgt}' for user '#{tgt.username}'")
542
+ tgt.destroy
543
+ end
544
+
545
+ $LOG.info("User '#{tgt.username}' logged out.")
546
+ else
547
+ $LOG.warn("User tried to log out without a valid ticket-granting ticket.")
548
+ end
549
+
550
+ @message = {:type => 'confirmation', :message => _("You have successfully logged out.")}
551
+
552
+ @message[:message] +=_(" Please click on the following link to continue:") if @continue_url
553
+
554
+ @lt = generate_login_ticket
555
+
556
+ if @gateway && @service
557
+ redirect @service, 303
558
+ elsif @continue_url
559
+ render @template_engine, :logout
560
+ else
561
+ render @template_engine, :login
562
+ end
563
+ end
564
+
565
+
566
+ # Handler for obtaining login tickets.
567
+ # This is useful when you want to build a custom login form located on a
568
+ # remote server. Your form will have to include a valid login ticket
569
+ # value, and this can be fetched from the CAS server using the POST handler.
570
+
571
+ get "#{uri_path}/loginTicket" do
572
+ CASServer::Utils::log_controller_action(self.class, params)
573
+
574
+ $LOG.error("Tried to use login ticket dispenser with get method!")
575
+
576
+ status 422
577
+
578
+ "To generate a login ticket, you must make a POST request."
579
+ end
580
+
581
+
582
+ # Renders a page with a login ticket (and only the login ticket)
583
+ # in the response body.
584
+ post "#{uri_path}/loginTicket" do
585
+ CASServer::Utils::log_controller_action(self.class, params)
586
+
587
+ lt = generate_login_ticket
588
+
589
+ $LOG.debug("Dispensing login ticket #{lt} to host #{(@env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']).inspect}")
590
+
591
+ @lt = lt.ticket
592
+
593
+ @lt
594
+ end
595
+
596
+
597
+ # 2.4
598
+
599
+ # 2.4.1
600
+ get "#{uri_path}/validate" do
601
+ CASServer::Utils::log_controller_action(self.class, params)
602
+
603
+ # required
604
+ @service = clean_service_url(params['service'])
605
+ @ticket = params['ticket']
606
+ # optional
607
+ @renew = params['renew']
608
+
609
+ st, @error = validate_service_ticket(@service, @ticket)
610
+ @success = st && !@error
611
+
612
+ @username = st.username if @success
613
+
614
+ status response_status_from_error(@error) if @error
615
+
616
+ render @template_engine, :validate, :layout => false
617
+ end
618
+
619
+
620
+ # 2.5
621
+
622
+ # 2.5.1
623
+ get "#{uri_path}/serviceValidate" do
624
+ CASServer::Utils::log_controller_action(self.class, params)
625
+
626
+ # required
627
+ @service = clean_service_url(params['service'])
628
+ @ticket = params['ticket']
629
+ # optional
630
+ @renew = params['renew']
631
+
632
+ st, @error = validate_service_ticket(@service, @ticket)
633
+ @success = st && !@error
634
+
635
+ if @success
636
+ @username = st.username
637
+ if @pgt_url
638
+ pgt = generate_proxy_granting_ticket(@pgt_url, st)
639
+ @pgtiou = pgt.iou if pgt
640
+ end
641
+ @extra_attributes = st.granted_by_tgt.extra_attributes || {}
642
+ end
643
+
644
+ status response_status_from_error(@error) if @error
645
+
646
+ render :builder, :proxy_validate
647
+ end
648
+
649
+
650
+ # 2.6
651
+
652
+ # 2.6.1
653
+ get "#{uri_path}/proxyValidate" do
654
+ CASServer::Utils::log_controller_action(self.class, params)
655
+
656
+ # required
657
+ @service = clean_service_url(params['service'])
658
+ @ticket = params['ticket']
659
+ # optional
660
+ @pgt_url = params['pgtUrl']
661
+ @renew = params['renew']
662
+
663
+ @proxies = []
664
+
665
+ t, @error = validate_proxy_ticket(@service, @ticket)
666
+ @success = t && !@error
667
+
668
+ @extra_attributes = {}
669
+ if @success
670
+ @username = t.username
671
+
672
+ if t.kind_of? CASServer::Model::ProxyTicket
673
+ @proxies << t.granted_by_pgt.service_ticket.service
674
+ end
675
+
676
+ if @pgt_url
677
+ pgt = generate_proxy_granting_ticket(@pgt_url, t)
678
+ @pgtiou = pgt.iou if pgt
679
+ end
680
+
681
+ @extra_attributes = t.granted_by_tgt.extra_attributes || {}
682
+ end
683
+
684
+ status response_status_from_error(@error) if @error
685
+
686
+ render :builder, :proxy_validate
687
+ end
688
+
689
+
690
+ # 2.7
691
+ get "#{uri_path}/proxy" do
692
+ CASServer::Utils::log_controller_action(self.class, params)
693
+
694
+ # required
695
+ @ticket = params['pgt']
696
+ @target_service = params['targetService']
697
+
698
+ pgt, @error = validate_proxy_granting_ticket(@ticket)
699
+ @success = pgt && !@error
700
+
701
+ if @success
702
+ @pt = generate_proxy_ticket(@target_service, pgt)
703
+ end
704
+
705
+ status response_status_from_error(@error) if @error
706
+
707
+ render :builder, :proxy
708
+ end
709
+
710
+
711
+
712
+ # Helpers
713
+
714
+ def response_status_from_error(error)
715
+ case error.code.to_s
716
+ when /^INVALID_/, 'BAD_PGT'
717
+ 422
718
+ when 'INTERNAL_ERROR'
719
+ 500
720
+ else
721
+ 500
722
+ end
723
+ end
724
+
725
+ def serialize_extra_attribute(builder, key, value)
726
+ if value.kind_of?(String)
727
+ builder.tag! key, value
728
+ elsif value.kind_of?(Numeric)
729
+ builder.tag! key, value.to_s
730
+ else
731
+ builder.tag! key do
732
+ builder.cdata! value.to_yaml
733
+ end
734
+ end
735
+ end
736
+
737
+ def compile_template(engine, data, options, views)
738
+ super engine, data, options, @custom_views || views
739
+ rescue Errno::ENOENT
740
+ raise unless @custom_views
741
+ super engine, data, options, views
742
+ end
743
+ end
744
+ end
745
+