synapse-rubycas-server 1.1.4.pre → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +2 -21
  3. data/Rakefile +0 -6
  4. data/bin/rubycas-server +26 -12
  5. data/config.ru +1 -2
  6. data/config/unicorn.rb +88 -0
  7. data/lib/casserver.rb +1 -2
  8. data/lib/casserver/cas.rb +4 -330
  9. data/lib/casserver/server.rb +8 -4
  10. data/lib/casserver/views/_login_form.erb +36 -15
  11. data/lib/casserver/views/layout.erb +4 -40
  12. data/lib/casserver/views/login.erb +27 -13
  13. data/locales/en.yml +3 -17
  14. data/public/themes/cas.css +0 -2
  15. data/public/themes/simple/theme.css +28 -0
  16. data/spec/casserver_spec.rb +1 -1
  17. data/spec/config/default_config.yml +1 -1
  18. metadata +12 -146
  19. data/bin/cap +0 -16
  20. data/bin/capify +0 -16
  21. data/bin/foreman +0 -16
  22. data/bin/lessc +0 -16
  23. data/bin/rackup +0 -16
  24. data/bin/rake2thor +0 -16
  25. data/bin/therubyracer +0 -16
  26. data/bin/thor +0 -16
  27. data/bin/tilt +0 -16
  28. data/bin/unicorn +0 -16
  29. data/bin/unicorn_rails +0 -16
  30. data/config/deploy.rb +0 -36
  31. data/config/deploy/production.rb +0 -4
  32. data/config/deploy/staging.rb +0 -4
  33. data/config/recipes/base.rb +0 -8
  34. data/config/recipes/git.rb +0 -10
  35. data/config/recipes/nginx.rb +0 -28
  36. data/config/recipes/puma.rb +0 -38
  37. data/config/recipes/rubycas.rb +0 -11
  38. data/config/recipes/templates/nginx.erb +0 -43
  39. data/config/recipes/templates/puma.erb +0 -13
  40. data/config/recipes/templates/rubycas.erb +0 -114
  41. data/config/unicorn/development.rb +0 -14
  42. data/config/unicorn/production.rb +0 -14
  43. data/config/unicorn/staging.rb +0 -14
  44. data/public/app.css +0 -9641
  45. data/public/assets/fontawesome-webfont.eot +0 -0
  46. data/public/assets/fontawesome-webfont.svg +0 -255
  47. data/public/assets/fontawesome-webfont.ttf +0 -0
  48. data/public/assets/fontawesome-webfont.woff +0 -0
  49. data/public/assets/gothamhtf-black-webfont.eot +0 -0
  50. data/public/assets/gothamhtf-black-webfont.svg +0 -241
  51. data/public/assets/gothamhtf-black-webfont.ttf +0 -0
  52. data/public/assets/gothamhtf-black-webfont.woff +0 -0
  53. data/public/assets/gothamhtf-blackitalic-webfont.eot +0 -0
  54. data/public/assets/gothamhtf-blackitalic-webfont.svg +0 -241
  55. data/public/assets/gothamhtf-blackitalic-webfont.ttf +0 -0
  56. data/public/assets/gothamhtf-blackitalic-webfont.woff +0 -0
  57. data/public/assets/gothamhtf-bold-webfont.eot +0 -0
  58. data/public/assets/gothamhtf-bold-webfont.svg +0 -241
  59. data/public/assets/gothamhtf-bold-webfont.ttf +0 -0
  60. data/public/assets/gothamhtf-bold-webfont.woff +0 -0
  61. data/public/assets/gothamhtf-bolditalic-webfont.eot +0 -0
  62. data/public/assets/gothamhtf-bolditalic-webfont.svg +0 -241
  63. data/public/assets/gothamhtf-bolditalic-webfont.ttf +0 -0
  64. data/public/assets/gothamhtf-bolditalic-webfont.woff +0 -0
  65. data/public/assets/gothamhtf-book-webfont.eot +0 -0
  66. data/public/assets/gothamhtf-book-webfont.svg +0 -241
  67. data/public/assets/gothamhtf-book-webfont.ttf +0 -0
  68. data/public/assets/gothamhtf-book-webfont.woff +0 -0
  69. data/public/assets/gothamhtf-bookitalic-webfont.eot +0 -0
  70. data/public/assets/gothamhtf-bookitalic-webfont.svg +0 -241
  71. data/public/assets/gothamhtf-bookitalic-webfont.ttf +0 -0
  72. data/public/assets/gothamhtf-bookitalic-webfont.woff +0 -0
  73. data/public/assets/gothamhtf-light-webfont.eot +0 -0
  74. data/public/assets/gothamhtf-light-webfont.svg +0 -241
  75. data/public/assets/gothamhtf-light-webfont.ttf +0 -0
  76. data/public/assets/gothamhtf-light-webfont.woff +0 -0
  77. data/public/assets/gothamhtf-lightitalic-webfont.eot +0 -0
  78. data/public/assets/gothamhtf-lightitalic-webfont.svg +0 -241
  79. data/public/assets/gothamhtf-lightitalic-webfont.ttf +0 -0
  80. data/public/assets/gothamhtf-lightitalic-webfont.woff +0 -0
  81. data/public/assets/gothamhtf-medium-webfont.eot +0 -0
  82. data/public/assets/gothamhtf-medium-webfont.svg +0 -241
  83. data/public/assets/gothamhtf-medium-webfont.ttf +0 -0
  84. data/public/assets/gothamhtf-medium-webfont.woff +0 -0
  85. data/public/assets/gothamhtf-thin-webfont.eot +0 -0
  86. data/public/assets/gothamhtf-thin-webfont.svg +0 -241
  87. data/public/assets/gothamhtf-thin-webfont.ttf +0 -0
  88. data/public/assets/gothamhtf-thin-webfont.woff +0 -0
  89. data/public/assets/gothamhtf-thinitalic-webfont.eot +0 -0
  90. data/public/assets/gothamhtf-thinitalic-webfont.svg +0 -241
  91. data/public/assets/gothamhtf-thinitalic-webfont.ttf +0 -0
  92. data/public/assets/gothamhtf-thinitalic-webfont.woff +0 -0
  93. data/public/assets/gothamhtf-ultra-webfont.eot +0 -0
  94. data/public/assets/gothamhtf-ultra-webfont.svg +0 -241
  95. data/public/assets/gothamhtf-ultra-webfont.ttf +0 -0
  96. data/public/assets/gothamhtf-ultra-webfont.woff +0 -0
  97. data/public/assets/gothamhtf-ultraitalic-webfont.eot +0 -0
  98. data/public/assets/gothamhtf-ultraitalic-webfont.svg +0 -241
  99. data/public/assets/gothamhtf-ultraitalic-webfont.ttf +0 -0
  100. data/public/assets/gothamhtf-ultraitalic-webfont.woff +0 -0
  101. data/public/assets/gothamhtf-xlight-webfont.eot +0 -0
  102. data/public/assets/gothamhtf-xlight-webfont.svg +0 -241
  103. data/public/assets/gothamhtf-xlight-webfont.ttf +0 -0
  104. data/public/assets/gothamhtf-xlight-webfont.woff +0 -0
  105. data/public/assets/gothamhtf-xlightitalic-webfont.eot +0 -0
  106. data/public/assets/gothamhtf-xlightitalic-webfont.svg +0 -241
  107. data/public/assets/gothamhtf-xlightitalic-webfont.ttf +0 -0
  108. data/public/assets/gothamhtf-xlightitalic-webfont.woff +0 -0
  109. data/public/css/app.css +0 -190
  110. data/public/css/bootstrap-responsive.min.css +0 -9
  111. data/public/css/bootstrap.min.css +0 -9
  112. data/public/img/glyphicons-halflings-white.png +0 -0
  113. data/public/img/glyphicons-halflings.png +0 -0
  114. data/public/js/app.js +0 -0
  115. data/public/js/bootstrap.min.js +0 -6
  116. data/public/js/jquery-1.8.0.js +0 -9227
  117. data/public/themes/app.css +0 -4652
  118. data/rubycas-server.gemspec +0 -62
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODQxZTBlYWM3YTJkNWIwMTYyNTc3OGM4OGNjMmJkYjE5YzA5MjVkZQ==
5
+ data.tar.gz: !binary |-
6
+ OGY5NmJjMGIyOGNjOGQ1NDMxODE0YTRjMTljNTRkMTM3NmU5Y2VkZg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZmQ3MjNmNGE0MjY1NGU4ZDk1ZTBkNDg0Nzk3YjNmODRhNTc3ODQxMWQyNTU1
10
+ NjgyNGQ2NDI1YzM3YTIzODAxZjZjY2Q5NmVkNDBhNjUyNTJiYWQ1NWY3NmM5
11
+ Yjg1N2FjNTMyNDIxMTM2ZDA3M2M4ZmE0ZWNkNTRlY2I4N2U5MDE=
12
+ data.tar.gz: !binary |-
13
+ NzQzMjMyN2YyMjI0MTEyNGRkM2U3YzRkNTRjOGFiNDI0Y2YxMTNjZTYxMmYy
14
+ NDlmMjAyYjUxODc5ZjVlZjhjYmNlMTQ0MjI0ZDJlNmI1OGI3NjgzYjUzMWJl
15
+ ZDZhMzEwNTlhOGI2ODAwOWEyZGI0ZGE0OWUzNTM3NmRmNzNiYzk=
data/Gemfile CHANGED
@@ -1,29 +1,10 @@
1
1
  source "http://rubygems.org"
2
+ gemspec
2
3
 
3
- gem 'appraisal'
4
- gem 'synapse-rubycas-server', "1.1.4"
5
- gem "mysql2"
6
- gem 'activerecord-mysql2-adapter'
7
- gem "activerecord"
8
- gem "activesupport"
9
- gem "sinatra"
10
- gem "sinatra-r18n"
11
- gem 'puma', "~> 2.1.1"
12
- gem "newrelic_rpm"
13
- gem "rake", "~> 10.0.4"
14
-
15
- group :development do
16
- gem 'capistrano-ext'
17
- gem 'capistrano_colors'
18
- gem 'capistrano-rbenv'
19
- gem 'capistrano-unicorn', :require => false
20
- gem 'foreman'
21
- gem 'hipchat'
22
- end
23
4
 
24
5
  # Gems for authenticators
25
6
  group :ldap do
26
- gem "net-ldap", "~> 0.1.1"
7
+ gem "net-ldap", "~> 0.1.1"
27
8
  end
28
9
 
29
10
  group :active_resource do
data/Rakefile CHANGED
@@ -1,9 +1,3 @@
1
1
  require 'appraisal'
2
2
  Dir['tasks/**/*.rake'].each { |rake| load rake }
3
3
  task :default => :spec
4
-
5
-
6
- desc "Open an irb session preloaded with this library"
7
- task :console do
8
- sh "irb -rubygems -I lib -r casserver.rb"
9
- end
data/bin/rubycas-server CHANGED
@@ -1,16 +1,30 @@
1
- #!/usr/bin/env ruby-local-exec
2
- #
3
- # This file was generated by Bundler.
4
- #
5
- # The application 'rubycas-server' is installed as part of a gem, and
6
- # this file is here to facilitate running it.
7
- #
1
+ #!/usr/bin/env ruby
8
2
 
9
- require 'pathname'
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
- Pathname.new(__FILE__).realpath)
3
+ # Enables UTF-8 compatibility in ruby 1.8.
4
+ $KCODE = 'u' if RUBY_VERSION < '1.9'
12
5
 
13
6
  require 'rubygems'
14
- require 'bundler/setup'
15
7
 
16
- load Gem.bin_path('rubycas-server', 'rubycas-server')
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+
10
+ if ARGV.join.match('--debugger')
11
+ require 'ruby-debug'
12
+ puts
13
+ puts "=> Debugger Enabled"
14
+ end
15
+
16
+ if ARGV.join.match('-c')
17
+ c = ARGV.join.match(/-c\s*([^\s]+)/)
18
+ if (c && c[1])
19
+ ENV['CONFIG_FILE'] = c[1]
20
+ puts
21
+ puts "=> Using custom config file #{ENV['CONFIG_FILE'].inspect}"
22
+ else
23
+ $stderr.puts("To specify a custom config file use `rubycas-server -c path/to/config_file_name.yml`.")
24
+ exit
25
+ end
26
+ end
27
+
28
+ require 'casserver'
29
+
30
+ CASServer::Server.run!
data/config.ru CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
- Bundler.require
2
+ require 'bundler/setup'
4
3
 
5
4
  $:.unshift "#{File.dirname(__FILE__)}/lib"
6
5
  require "casserver"
data/config/unicorn.rb ADDED
@@ -0,0 +1,88 @@
1
+ # Sample configuration file for Unicorn (not Rack)
2
+ #
3
+ # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
4
+ # documentation.
5
+ SINATRA_ROOT = `pwd`.strip
6
+
7
+ # Use at least one worker per core if you're on a dedicated server,
8
+ # more will usually help for _short_ waits on databases/caches.
9
+ worker_processes 3
10
+
11
+ # Help ensure your application will always spawn in the symlinked
12
+ # "current" directory that Capistrano sets up.
13
+ working_directory SINATRA_ROOT # available in 0.94.0+
14
+
15
+ # listen on both a Unix domain socket and a TCP port,
16
+ # we use a shorter backlog for quicker failover when busy
17
+ # listen "/tmp/.sock", :backlog => 64
18
+ listen 18889, :tcp_nopush => true
19
+
20
+ # nuke workers after 30 seconds instead of 60 seconds (the default)
21
+ timeout 30
22
+
23
+ # feel free to point this anywhere accessible on the filesystem
24
+
25
+ pid "#{SINATRA_ROOT}/tmp/pids/unicorn.pid"
26
+
27
+ # relative_path "/test_platform"
28
+ # some applications/frameworks log to stderr or stdout, so prevent
29
+ # them from going to /dev/null when daemonized here:
30
+ stderr_path "#{SINATRA_ROOT}/log/unicorn.stderr.log"
31
+ stdout_path "#{SINATRA_ROOT}/log/unicorn.stdout.log"
32
+
33
+ # combine REE with "preload_app true" for memory savings
34
+ # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
35
+ preload_app false
36
+ GC.respond_to?(:copy_on_write_friendly=) and
37
+ GC.copy_on_write_friendly = true
38
+
39
+ before_fork do |server, worker|
40
+ # the following is highly recomended for Rails + "preload_app true"
41
+ # as there's no need for the master process to hold a connection
42
+ # defined?(ActiveRecord::Base) and
43
+ # ActiveRecord::Base.connection.disconnect!
44
+
45
+ # The following is only recommended for memory/DB-constrained
46
+ # installations. It is not needed if your system can house
47
+ # twice as many worker_processes as you have configured.
48
+ #
49
+ # # This allows a new master process to incrementally
50
+ # # phase out the old master process with SIGTTOU to avoid a
51
+ # # thundering herd (especially in the "preload_app false" case)
52
+ # # when doing a transparent upgrade. The last worker spawned
53
+ # # will then kill off the old master process with a SIGQUIT.
54
+ old_pid = "#{server.config[:pid]}.oldbin"
55
+
56
+ puts 'pid:'
57
+ puts '-------------------'
58
+ puts server.pid
59
+ puts old_pid
60
+ puts '---------------------'
61
+
62
+ if old_pid != server.pid
63
+ begin
64
+ sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
65
+ Process.kill(sig, File.read(old_pid).to_i)
66
+ rescue Errno::ENOENT, Errno::ESRCH
67
+ end
68
+ end
69
+ #
70
+ # # *optionally* throttle the master from forking too quickly by sleeping
71
+ sleep 1
72
+ end
73
+
74
+ after_fork do |server, worker|
75
+ # per-process listener ports for debugging/admin/migrations
76
+ # addr = "127.0.0.1:#{9293 + worker.nr}"
77
+ # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
78
+
79
+ # the following is *required* for Rails + "preload_app true",
80
+ # defined?(ActiveRecord::Base) and
81
+ # ActiveRecord::Base.establish_connection
82
+
83
+ # if preload_app is true, then you may also want to check and
84
+ # restart any other shared sockets/descriptors such as Memcached,
85
+ # and Redis. TokyoCabinet file handles are safe to reuse
86
+ # between any number of forked children (assuming your kernel
87
+ # correctly implements pread()/pwrite() system calls)
88
+ end
data/lib/casserver.rb CHANGED
@@ -3,10 +3,9 @@ module CASServer; end
3
3
  require 'active_record'
4
4
  require 'active_support'
5
5
  require 'sinatra/base'
6
+ require 'casserver/core_ext/directory_user'
6
7
  require 'builder' # for XML views
7
8
  require 'logger'
8
- require 'net/ldap'
9
- require 'casserver/core_ext/directory_user'
10
9
  $LOG = Logger.new(STDOUT)
11
10
 
12
11
  require 'casserver/authenticators/base'
data/lib/casserver/cas.rb CHANGED
@@ -34,332 +34,6 @@ module CASServer::CAS
34
34
  tgt.ticket = "TGC-" + String.random
35
35
  tgt.username = username
36
36
  tgt.extra_attributes = extra_attributes
37
- tgt.expires = (Time.now + 2.weeks).strftime("%a, %d-%b-%Y %H:%M:%S GMT")
38
- tgt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
39
- tgt.save!
40
- $LOG.debug("Generated ticket granting ticket '#{tgt.ticket}' for user" +
41
- " '#{tgt.username}' at '#{tgt.client_hostname}' with expiration of '#{tgt.expires}'" +
42
- (extra_attributes.blank? ? "" : " with extra attributes #{extra_attributes.inspect}"))
43
- tgt
44
- end
45
-
46
- def generate_service_ticket(service, username, tgt)
47
- # 3.1 (service ticket)
48
- st = ServiceTicket.new
49
- st.ticket = "ST-" + String.random
50
- st.service = service
51
- st.username = username
52
- st.granted_by_tgt_id = tgt.id
53
- st.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
54
- st.save!
55
- $LOG.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" +
56
- " for user '#{st.username}' at '#{st.client_hostname}'")
57
- st
58
- end
59
-
60
- def generate_proxy_ticket(target_service, pgt)
61
- # 3.2 (proxy ticket)
62
- pt = ProxyTicket.new
63
- pt.ticket = "PT-" + String.random
64
- pt.service = target_service
65
- pt.username = pgt.service_ticket.username
66
- pt.granted_by_pgt_id = pgt.id
67
- pt.granted_by_tgt_id = pgt.service_ticket.granted_by_tgt_id
68
- pt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
69
- pt.save!
70
- $LOG.debug("Generated proxy ticket '#{pt.ticket}' for target service '#{pt.service}'" +
71
- " for user '#{pt.username}' at '#{pt.client_hostname}' using proxy-granting" +
72
- " ticket '#{pgt.ticket}'")
73
- pt
74
- end
75
-
76
- def generate_proxy_granting_ticket(pgt_url, st)
77
- uri = URI.parse(pgt_url)
78
- https = Net::HTTP.new(uri.host,uri.port)
79
- https.use_ssl = true
80
-
81
- # Here's what's going on here:
82
- #
83
- # 1. We generate a ProxyGrantingTicket (but don't store it in the database just yet)
84
- # 2. Deposit the PGT and it's associated IOU at the proxy callback URL.
85
- # 3. If the proxy callback URL responds with HTTP code 200, store the PGT and return it;
86
- # otherwise don't save it and return nothing.
87
- #
88
- https.start do |conn|
89
- path = uri.path.empty? ? '/' : uri.path
90
- path += '?' + uri.query unless (uri.query.nil? || uri.query.empty?)
91
-
92
- pgt = ProxyGrantingTicket.new
93
- pgt.ticket = "PGT-" + String.random(60)
94
- pgt.iou = "PGTIOU-" + String.random(57)
95
- pgt.service_ticket_id = st.id
96
- pgt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
97
-
98
- # FIXME: The CAS protocol spec says to use 'pgt' as the parameter, but in practice
99
- # the JA-SIG and Yale server implementations use pgtId. We'll go with the
100
- # in-practice standard.
101
- path += (uri.query.nil? || uri.query.empty? ? '?' : '&') + "pgtId=#{pgt.ticket}&pgtIou=#{pgt.iou}"
102
-
103
- response = conn.request_get(path)
104
- # TODO: follow redirects... 2.5.4 says that redirects MAY be followed
105
- # NOTE: The following response codes are valid according to the JA-SIG implementation even without following redirects
106
-
107
- if %w(200 202 301 302 304).include?(response.code)
108
- # 3.4 (proxy-granting ticket IOU)
109
- pgt.save!
110
- $LOG.debug "PGT generated for pgt_url '#{pgt_url}': #{pgt.inspect}"
111
- pgt
112
- else
113
- $LOG.warn "PGT callback server responded with a bad result code '#{response.code}'. PGT will not be stored."
114
- nil
115
- end
116
- end
117
- end
118
-
119
- def validate_login_ticket(ticket)
120
- $LOG.debug("Validating login ticket '#{ticket}'")
121
-
122
- success = false
123
- if ticket.nil?
124
- error = t.error.no_login_ticket
125
- $LOG.warn "Missing login ticket."
126
- elsif lt = LoginTicket.find_by_ticket(ticket)
127
- if lt.consumed?
128
- error = t.error.login_ticket_already_used
129
- $LOG.warn "Login ticket '#{ticket}' previously used up"
130
- elsif Time.now - lt.created_on < settings.config[:maximum_unused_login_ticket_lifetime]
131
- $LOG.info "Login ticket '#{ticket}' successfully validated"
132
- else
133
- error = t.error.login_timeout
134
- $LOG.warn "Expired login ticket '#{ticket}'"
135
- end
136
- else
137
- error = t.error.invalid_login_ticket
138
- $LOG.warn "Invalid login ticket '#{ticket}'"
139
- end
140
-
141
- lt.consume! if lt
142
-
143
- error
144
- end
145
-
146
- def validate_ticket_granting_ticket(ticket)
147
- $LOG.debug("Validating ticket granting ticket '#{ticket}'")
148
-
149
- if ticket.nil?
150
- error = "No ticket granting ticket given."
151
- $LOG.debug error
152
- elsif tgt = TicketGrantingTicket.find_by_ticket(ticket)
153
- if settings.config[:maximum_session_lifetime] && Time.now - tgt.created_on > settings.config[:maximum_session_lifetime]
154
- tgt.destroy
155
- error = "Your session has expired. Please log in again."
156
- $LOG.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' expired."
157
- else
158
- $LOG.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' successfully validated."
159
- end
160
- else
161
- error = "Invalid ticket granting ticket '#{ticket}' (no matching ticket found in the database)."
162
- $LOG.warn(error)
163
- end
164
-
165
- [tgt, error]
166
- end
167
-
168
- def validate_service_ticket(service, ticket, allow_proxy_tickets = false)
169
- $LOG.debug "Validating service/proxy ticket '#{ticket}' for service '#{service}'"
170
-
171
- if service.nil? or ticket.nil?
172
- error = Error.new(:INVALID_REQUEST, "Ticket or service parameter was missing in the request.")
173
- $LOG.warn "#{error.code} - #{error.message}"
174
- elsif st = ServiceTicket.find_by_ticket(ticket)
175
- if st.consumed?
176
- error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has already been used up.")
177
- $LOG.warn "#{error.code} - #{error.message}"
178
- elsif st.kind_of?(CASServer::Model::ProxyTicket) && !allow_proxy_tickets
179
- error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' is a proxy ticket, but only service tickets are allowed here.")
180
- $LOG.warn "#{error.code} - #{error.message}"
181
- elsif Time.now - st.created_on > settings.config[:maximum_unused_service_ticket_lifetime]
182
- error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has expired.")
183
- $LOG.warn "Ticket '#{ticket}' has expired."
184
- elsif !st.matches_service? service
185
- error = Error.new(:INVALID_SERVICE, "The ticket '#{ticket}' belonging to user '#{st.username}' is valid,"+
186
- " but the requested service '#{service}' does not match the service '#{st.service}' associated with this ticket.")
187
- $LOG.warn "#{error.code} - #{error.message}"
188
- else
189
- $LOG.info("Ticket '#{ticket}' for service '#{service}' for user '#{st.username}' successfully validated.")
190
- end
191
- else
192
- error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' not recognized.")
193
- $LOG.warn("#{error.code} - #{error.message}")
194
- end
195
-
196
- if st
197
- st.consume!
198
- end
199
-
200
-
201
- [st, error]
202
- end
203
-
204
- def validate_proxy_ticket(service, ticket)
205
- pt, error = validate_service_ticket(service, ticket, true)
206
-
207
- if pt.kind_of?(CASServer::Model::ProxyTicket) && !error
208
- if not pt.granted_by_pgt
209
- error = Error.new(:INTERNAL_ERROR, "Proxy ticket '#{pt}' belonging to user '#{pt.username}' is not associated with a proxy granting ticket.")
210
- elsif not pt.granted_by_pgt.service_ticket
211
- error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{pt.granted_by_pgt}'"+
212
- " (associated with proxy ticket '#{pt}' and belonging to user '#{pt.username}' is not associated with a service ticket.")
213
- end
214
- end
215
-
216
- [pt, error]
217
- end
218
-
219
- def validate_proxy_granting_ticket(ticket)
220
- if ticket.nil?
221
- error = Error.new(:INVALID_REQUEST, "pgt parameter was missing in the request.")
222
- $LOG.warn("#{error.code} - #{error.message}")
223
- elsif pgt = ProxyGrantingTicket.find_by_ticket(ticket)
224
- if pgt.service_ticket
225
- $LOG.info("Proxy granting ticket '#{ticket}' belonging to user '#{pgt.service_ticket.username}' successfully validated.")
226
- else
227
- error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{ticket}' is not associated with a service ticket.")
228
- $LOG.error("#{error.code} - #{error.message}")
229
- end
230
- else
231
- error = Error.new(:BAD_PGT, "Invalid proxy granting ticket '#{ticket}' (no matching ticket found in the database).")
232
- $LOG.warn("#{error.code} - #{error.message}")
233
- end
234
-
235
- [pgt, error]
236
- end
237
-
238
- # Takes an existing ServiceTicket object (presumably pulled from the database)
239
- # and sends a POST with logout information to the service that the ticket
240
- # was generated for.
241
- #
242
- # This makes possible the "single sign-out" functionality added in CAS 3.1.
243
- # See http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out
244
- def send_logout_notification_for_service_ticket(st)
245
- uri = URI.parse(st.service)
246
- uri.path = '/' if uri.path.empty?
247
- time = Time.now
248
- rand = String.random
249
- path = uri.path
250
- req = Net::HTTP::Post.new(path)
251
- req.set_form_data('logoutRequest' => %{<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="#{rand}" Version="2.0" IssueInstant="#{time.rfc2822}">
252
- <saml:NameID></saml:NameID>
253
- <samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
254
- </samlp:LogoutRequest>})
255
-
256
- begin
257
- http = Net::HTTP.new(uri.host, uri.port)
258
- http.use_ssl = true if uri.scheme =='https'
259
-
260
- http.start do |conn|
261
- response = conn.request(req)
262
- if response.kind_of? Net::HTTPSuccess
263
- $LOG.info "Logout notification successfully posted to #{st.service.inspect}."
264
- return true
265
- else
266
- $LOG.error "Service #{st.service.inspect} responed to logout notification with code '#{response.code}'!"
267
- return false
268
- end
269
- end
270
- rescue Exception => e
271
- $LOG.error "Failed to send logout notification to service #{st.service.inspect} due to #{e}"
272
- return false
273
- end
274
- end
275
-
276
- def service_uri_with_ticket(service, st)
277
- raise ArgumentError, "Second argument must be a ServiceTicket!" unless st.kind_of? CASServer::Model::ServiceTicket
278
-
279
- # This will choke with a URI::InvalidURIError if service URI is not properly URI-escaped...
280
- # This exception is handled further upstream (i.e. in the controller).
281
- service_uri = URI.parse(service)
282
-
283
- if service.include? "?"
284
- if service_uri.query.empty?
285
- query_separator = ""
286
- else
287
- query_separator = "&"
288
- end
289
- else
290
- query_separator = "?"
291
- end
292
-
293
- service_with_ticket = service + query_separator + "ticket=" + st.ticket
294
- service_with_ticket
295
- end
296
-
297
- # Strips CAS-related parameters from a service URL and normalizes it,
298
- # removing trailing / and ?. Also converts any spaces to +.
299
- #
300
- # For example, "http://google.com?ticket=12345" will be returned as
301
- # "http://google.com". Also, "http://google.com/" would be returned as
302
- # "http://google.com".
303
- #
304
- # Note that only the first occurance of each CAS-related parameter is
305
- # removed, so that "http://google.com?ticket=12345&ticket=abcd" would be
306
- # returned as "http://google.com?ticket=abcd".
307
- def clean_service_url(dirty_service)
308
- return dirty_service if dirty_service.blank?
309
- clean_service = dirty_service.dup
310
- ['service', 'ticket', 'gateway', 'renew'].each do |p|
311
- clean_service.sub!(Regexp.new("&?#{p}=[^&]*"), '')
312
- end
313
-
314
- clean_service.gsub!(/[\/\?&]$/, '') # remove trailing ?, /, or &
315
- clean_service.gsub!('?&', '?')
316
- clean_service.gsub!(' ', '+')
317
-
318
- $LOG.debug("Cleaned dirty service URL #{dirty_service.inspect} to #{clean_service.inspect}") if
319
- dirty_service != clean_service
320
-
321
- return clean_service
322
- end
323
- module_function :clean_service_url
324
-
325
- end
326
- require 'uri'
327
- require 'net/https'
328
-
329
- require 'casserver/model'
330
- require 'casserver/core_ext'
331
-
332
- # Encapsulates CAS functionality. This module is meant to be included in
333
- # the CASServer::Controllers module.
334
- module CASServer::CAS
335
-
336
- include CASServer::Model
337
-
338
- def generate_login_ticket
339
- # 3.5 (login ticket)
340
- lt = LoginTicket.new
341
- lt.ticket = "LT-" + String.random
342
-
343
- lt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
344
- lt.save!
345
- $LOG.debug("Generated login ticket '#{lt.ticket}' for client" +
346
- " at '#{lt.client_hostname}'")
347
- lt
348
- end
349
-
350
- # Creates a TicketGrantingTicket for the given username. This is done when the user logs in
351
- # for the first time to establish their SSO session (after their credentials have been validated).
352
- #
353
- # The optional 'extra_attributes' parameter takes a hash of additional attributes
354
- # that will be sent along with the username in the CAS response to subsequent
355
- # validation requests from clients.
356
- def generate_ticket_granting_ticket(username, extra_attributes = {})
357
- # 3.6 (ticket granting cookie/ticket)
358
- tgt = TicketGrantingTicket.new
359
- tgt.ticket = "TGC-" + String.random
360
- tgt.username = username
361
- tgt.extra_attributes = extra_attributes
362
- # tgt.expires = (Time.now + 2.weeks).strftime("%a, %d-%b-%Y %H:%M:%S GMT")
363
37
  tgt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
364
38
  tgt.save!
365
39
  $LOG.debug("Generated ticket granting ticket '#{tgt.ticket}' for user" +
@@ -413,7 +87,7 @@ module CASServer::CAS
413
87
  https.start do |conn|
414
88
  path = uri.path.empty? ? '/' : uri.path
415
89
  path += '?' + uri.query unless (uri.query.nil? || uri.query.empty?)
416
-
90
+
417
91
  pgt = ProxyGrantingTicket.new
418
92
  pgt.ticket = "PGT-" + String.random(60)
419
93
  pgt.iou = "PGTIOU-" + String.random(57)
@@ -428,7 +102,7 @@ module CASServer::CAS
428
102
  response = conn.request_get(path)
429
103
  # TODO: follow redirects... 2.5.4 says that redirects MAY be followed
430
104
  # NOTE: The following response codes are valid according to the JA-SIG implementation even without following redirects
431
-
105
+
432
106
  if %w(200 202 301 302 304).include?(response.code)
433
107
  # 3.4 (proxy-granting ticket IOU)
434
108
  pgt.save!
@@ -577,11 +251,11 @@ module CASServer::CAS
577
251
  <saml:NameID></saml:NameID>
578
252
  <samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
579
253
  </samlp:LogoutRequest>})
580
-
254
+
581
255
  begin
582
256
  http = Net::HTTP.new(uri.host, uri.port)
583
257
  http.use_ssl = true if uri.scheme =='https'
584
-
258
+
585
259
  http.start do |conn|
586
260
  response = conn.request(req)
587
261
  if response.kind_of? Net::HTTPSuccess