rubycas-server 0.7.1.1 → 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+