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.
- 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
|