synapse-rubycas-server 1.1.4.pre → 1.1.4
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.
- checksums.yaml +15 -0
- data/Gemfile +2 -21
- data/Rakefile +0 -6
- data/bin/rubycas-server +26 -12
- data/config.ru +1 -2
- data/config/unicorn.rb +88 -0
- data/lib/casserver.rb +1 -2
- data/lib/casserver/cas.rb +4 -330
- data/lib/casserver/server.rb +8 -4
- data/lib/casserver/views/_login_form.erb +36 -15
- data/lib/casserver/views/layout.erb +4 -40
- data/lib/casserver/views/login.erb +27 -13
- data/locales/en.yml +3 -17
- data/public/themes/cas.css +0 -2
- data/public/themes/simple/theme.css +28 -0
- data/spec/casserver_spec.rb +1 -1
- data/spec/config/default_config.yml +1 -1
- metadata +12 -146
- data/bin/cap +0 -16
- data/bin/capify +0 -16
- data/bin/foreman +0 -16
- data/bin/lessc +0 -16
- data/bin/rackup +0 -16
- data/bin/rake2thor +0 -16
- data/bin/therubyracer +0 -16
- data/bin/thor +0 -16
- data/bin/tilt +0 -16
- data/bin/unicorn +0 -16
- data/bin/unicorn_rails +0 -16
- data/config/deploy.rb +0 -36
- data/config/deploy/production.rb +0 -4
- data/config/deploy/staging.rb +0 -4
- data/config/recipes/base.rb +0 -8
- data/config/recipes/git.rb +0 -10
- data/config/recipes/nginx.rb +0 -28
- data/config/recipes/puma.rb +0 -38
- data/config/recipes/rubycas.rb +0 -11
- data/config/recipes/templates/nginx.erb +0 -43
- data/config/recipes/templates/puma.erb +0 -13
- data/config/recipes/templates/rubycas.erb +0 -114
- data/config/unicorn/development.rb +0 -14
- data/config/unicorn/production.rb +0 -14
- data/config/unicorn/staging.rb +0 -14
- data/public/app.css +0 -9641
- data/public/assets/fontawesome-webfont.eot +0 -0
- data/public/assets/fontawesome-webfont.svg +0 -255
- data/public/assets/fontawesome-webfont.ttf +0 -0
- data/public/assets/fontawesome-webfont.woff +0 -0
- data/public/assets/gothamhtf-black-webfont.eot +0 -0
- data/public/assets/gothamhtf-black-webfont.svg +0 -241
- data/public/assets/gothamhtf-black-webfont.ttf +0 -0
- data/public/assets/gothamhtf-black-webfont.woff +0 -0
- data/public/assets/gothamhtf-blackitalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-blackitalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-blackitalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-blackitalic-webfont.woff +0 -0
- data/public/assets/gothamhtf-bold-webfont.eot +0 -0
- data/public/assets/gothamhtf-bold-webfont.svg +0 -241
- data/public/assets/gothamhtf-bold-webfont.ttf +0 -0
- data/public/assets/gothamhtf-bold-webfont.woff +0 -0
- data/public/assets/gothamhtf-bolditalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-bolditalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-bolditalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-bolditalic-webfont.woff +0 -0
- data/public/assets/gothamhtf-book-webfont.eot +0 -0
- data/public/assets/gothamhtf-book-webfont.svg +0 -241
- data/public/assets/gothamhtf-book-webfont.ttf +0 -0
- data/public/assets/gothamhtf-book-webfont.woff +0 -0
- data/public/assets/gothamhtf-bookitalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-bookitalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-bookitalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-bookitalic-webfont.woff +0 -0
- data/public/assets/gothamhtf-light-webfont.eot +0 -0
- data/public/assets/gothamhtf-light-webfont.svg +0 -241
- data/public/assets/gothamhtf-light-webfont.ttf +0 -0
- data/public/assets/gothamhtf-light-webfont.woff +0 -0
- data/public/assets/gothamhtf-lightitalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-lightitalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-lightitalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-lightitalic-webfont.woff +0 -0
- data/public/assets/gothamhtf-medium-webfont.eot +0 -0
- data/public/assets/gothamhtf-medium-webfont.svg +0 -241
- data/public/assets/gothamhtf-medium-webfont.ttf +0 -0
- data/public/assets/gothamhtf-medium-webfont.woff +0 -0
- data/public/assets/gothamhtf-thin-webfont.eot +0 -0
- data/public/assets/gothamhtf-thin-webfont.svg +0 -241
- data/public/assets/gothamhtf-thin-webfont.ttf +0 -0
- data/public/assets/gothamhtf-thin-webfont.woff +0 -0
- data/public/assets/gothamhtf-thinitalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-thinitalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-thinitalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-thinitalic-webfont.woff +0 -0
- data/public/assets/gothamhtf-ultra-webfont.eot +0 -0
- data/public/assets/gothamhtf-ultra-webfont.svg +0 -241
- data/public/assets/gothamhtf-ultra-webfont.ttf +0 -0
- data/public/assets/gothamhtf-ultra-webfont.woff +0 -0
- data/public/assets/gothamhtf-ultraitalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-ultraitalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-ultraitalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-ultraitalic-webfont.woff +0 -0
- data/public/assets/gothamhtf-xlight-webfont.eot +0 -0
- data/public/assets/gothamhtf-xlight-webfont.svg +0 -241
- data/public/assets/gothamhtf-xlight-webfont.ttf +0 -0
- data/public/assets/gothamhtf-xlight-webfont.woff +0 -0
- data/public/assets/gothamhtf-xlightitalic-webfont.eot +0 -0
- data/public/assets/gothamhtf-xlightitalic-webfont.svg +0 -241
- data/public/assets/gothamhtf-xlightitalic-webfont.ttf +0 -0
- data/public/assets/gothamhtf-xlightitalic-webfont.woff +0 -0
- data/public/css/app.css +0 -190
- data/public/css/bootstrap-responsive.min.css +0 -9
- data/public/css/bootstrap.min.css +0 -9
- data/public/img/glyphicons-halflings-white.png +0 -0
- data/public/img/glyphicons-halflings.png +0 -0
- data/public/js/app.js +0 -0
- data/public/js/bootstrap.min.js +0 -6
- data/public/js/jquery-1.8.0.js +0 -9227
- data/public/themes/app.css +0 -4652
- 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
|
-
|
|
7
|
+
gem "net-ldap", "~> 0.1.1"
|
|
27
8
|
end
|
|
28
9
|
|
|
29
10
|
group :active_resource do
|
data/Rakefile
CHANGED
data/bin/rubycas-server
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
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
|