p8-casablanca 0.0.1
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.
- data/History.txt +6 -0
- data/Manifest.txt +17 -0
- data/README.textile +79 -0
- data/Rakefile +15 -0
- data/bin/casablanca +10 -0
- data/init.rb +3 -0
- data/lib/casablanca.rb +3 -0
- data/lib/casablanca/cli.rb +19 -0
- data/lib/casablanca/client.rb +162 -0
- data/lib/casablanca/filters/rails.rb +48 -0
- data/lib/casablanca/response_parsers.rb +50 -0
- data/lib/casablanca/version.rb +3 -0
- data/test/test_client.rb +96 -0
- data/test/test_helper.rb +54 -0
- data/test/test_parser.rb +22 -0
- data/test/test_rails_filter.rb +92 -0
- data/test/test_ticket.rb +71 -0
- metadata +85 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.textile
|
4
|
+
Rakefile
|
5
|
+
init.rb
|
6
|
+
bin/casablanca
|
7
|
+
lib/casablanca.rb
|
8
|
+
lib/casablanca/cli.rb
|
9
|
+
lib/casablanca/client.rb
|
10
|
+
lib/casablanca/filters/rails.rb
|
11
|
+
lib/casablanca/response_parsers.rb
|
12
|
+
lib/casablanca/version.rb
|
13
|
+
test/test_client.rb
|
14
|
+
test/test_helper.rb
|
15
|
+
test/test_parser.rb
|
16
|
+
test/test_rails_filter.rb
|
17
|
+
test/test_ticket.rb
|
data/README.textile
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
h1. Casablanca
|
2
|
+
|
3
|
+
h2. Description
|
4
|
+
|
5
|
+
A single sign-on client based on the CAS 2.0 protocol.
|
6
|
+
Has a filter for Rails so it can be used as a Rails plugin.
|
7
|
+
|
8
|
+
sudo gem install p8-casablanca
|
9
|
+
|
10
|
+
h2. TODO
|
11
|
+
|
12
|
+
* Add flag to turn on/off mock request
|
13
|
+
* Add extra attributes
|
14
|
+
* Implement gateway and proxy
|
15
|
+
* Check for single signout
|
16
|
+
* Check for endless redirects
|
17
|
+
|
18
|
+
h2. Usage
|
19
|
+
|
20
|
+
In IRB:
|
21
|
+
|
22
|
+
require 'casablanca'
|
23
|
+
|
24
|
+
C = Casablanca::CommandLineClient.new({ :cas_server_url => "http://localhost:4567",
|
25
|
+
:service_url => "http://localhost:3000" })
|
26
|
+
|
27
|
+
ticket = C.get_service_ticket('admin', 'admin')
|
28
|
+
C.authenticate_ticket(ticket)
|
29
|
+
|
30
|
+
|
31
|
+
In a Rails project:
|
32
|
+
- environment.rb:
|
33
|
+
|
34
|
+
Casablanca::RailsFilter.client = Casablanca::Client.new(:cas_server_url => "http://localhost:4567", :service_url => "http://localhost:3000")
|
35
|
+
|
36
|
+
- Add the following to application.rb:
|
37
|
+
|
38
|
+
before_filter Casablanca::RailsFilter
|
39
|
+
|
40
|
+
def current_person
|
41
|
+
@current_person ||= login_from_cas unless @current_person == false
|
42
|
+
end
|
43
|
+
|
44
|
+
def login_from_cas
|
45
|
+
if session[:cas_user]
|
46
|
+
person = Person.find_by_name(session[:cas_user])
|
47
|
+
logout_killing_session! unless person
|
48
|
+
person
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
- Add the following to you logout action
|
53
|
+
|
54
|
+
Casablanca::RailsFilter.logout(self)
|
55
|
+
|
56
|
+
h2. LICENSE:
|
57
|
+
|
58
|
+
(The MIT License)
|
59
|
+
|
60
|
+
Copyright (c) 2009 Petrik de Heus
|
61
|
+
|
62
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
63
|
+
a copy of this software and associated documentation files (the
|
64
|
+
'Software'), to deal in the Software without restriction, including
|
65
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
66
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
67
|
+
permit persons to whom the Software is furnished to do so, subject to
|
68
|
+
the following conditions:
|
69
|
+
|
70
|
+
The above copyright notice and this permission notice shall be
|
71
|
+
included in all copies or substantial portions of the Software.
|
72
|
+
|
73
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
74
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
75
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
76
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
77
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
78
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
79
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require 'lib/casablanca/version'
|
6
|
+
|
7
|
+
Hoe.new('casablanca', Casablanca::VERSION) do |p|
|
8
|
+
p.developer('FIX', 'FIX@example.com')
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'metric_fu'
|
12
|
+
|
13
|
+
MetricFu::Configuration.run do |config|
|
14
|
+
config.coverage = { :test_files => ['test/**/test_*.rb'] }
|
15
|
+
end
|
data/bin/casablanca
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
|
3
|
+
options = { :sandbox => false, :irb => irb }
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
libs << " -r #{File.dirname(__FILE__)}/../lib/casablanca/client.rb"
|
7
|
+
libs << " -r #{File.dirname(__FILE__)}/../lib/casablanca/cli.rb"
|
8
|
+
libs << " -r #{File.dirname(__FILE__)}/../lib/casablanca/response_parsers.rb"
|
9
|
+
|
10
|
+
exec "#{options[:irb]} #{libs} --simple-prompt"
|
data/init.rb
ADDED
data/lib/casablanca.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
config = { :cas_server_url => "http://localhost:4567", :service_url => "http://localhost:3000" }
|
2
|
+
INFO = %(
|
3
|
+
=====================================================
|
4
|
+
CASABLANCA CLIENT CONSOLE
|
5
|
+
|
6
|
+
Use C for a configured client (#{config.inspect})
|
7
|
+
Example:
|
8
|
+
|
9
|
+
t = C.get_service_ticket('admin', 'admin')
|
10
|
+
C.authenticate_ticket(t)
|
11
|
+
|
12
|
+
C.cas_server_url = "http://example.com/cas_server"
|
13
|
+
C.service_url = "http://example.com/application"
|
14
|
+
|
15
|
+
)
|
16
|
+
|
17
|
+
C = @client = Casablanca::CommandLineClient.new(config)
|
18
|
+
|
19
|
+
puts INFO
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/https'
|
4
|
+
require 'rexml/document'
|
5
|
+
|
6
|
+
module Casablanca
|
7
|
+
|
8
|
+
class Client
|
9
|
+
attr_reader :service_url
|
10
|
+
def initialize(config)
|
11
|
+
raise ":cas_server_url is required" unless config[:cas_server_url]
|
12
|
+
@cas_server_url = config[:cas_server_url]
|
13
|
+
@service_url = config[:service_url]
|
14
|
+
end
|
15
|
+
|
16
|
+
def authenticate_ticket(ticket)
|
17
|
+
response = request_validation(ticket)
|
18
|
+
ticket.authenticate(response)
|
19
|
+
end
|
20
|
+
|
21
|
+
def login_url
|
22
|
+
url = "#{@cas_server_url}/login?service=#{@service_url}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def logout_url
|
26
|
+
"#{@cas_server_url}/logout"
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_url
|
30
|
+
"#{@cas_server_url}/proxyValidate"
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def request_validation(ticket)
|
36
|
+
raise "ticket.service_url cannot be empty" if ticket.service_url.nil? || ticket.service_url.strip == ""
|
37
|
+
uri = URI.parse(validate_url)
|
38
|
+
uri.merge_query(ticket.to_request_params)
|
39
|
+
response = get(uri)
|
40
|
+
puts "#{@cas_server_url} #{response.inspect}:\n#{response.body}"
|
41
|
+
unless response.kind_of?(Net::HTTPSuccess)
|
42
|
+
raise ResponseError, "#{response.code}, #{response.body}"
|
43
|
+
end
|
44
|
+
response.body
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(uri)
|
48
|
+
https(uri) do |h|
|
49
|
+
h.get("#{uri.path}?#{uri.query}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def https(uri)
|
54
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
55
|
+
https.use_ssl = (uri.scheme == 'https')
|
56
|
+
begin
|
57
|
+
https.start do |h|
|
58
|
+
yield(h)
|
59
|
+
end
|
60
|
+
rescue Errno::ECONNREFUSED => error
|
61
|
+
raise CasServerException
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
class CommandLineClient < Client
|
68
|
+
attr_accessor :cas_server_url, :service_url
|
69
|
+
def login(username, password)
|
70
|
+
post(URI.parse(login_url), {:username => username, :password => password, :service => service_url})
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_service_ticket(username, password)
|
74
|
+
location = login(username, password)['location']
|
75
|
+
query = {}
|
76
|
+
URI.parse(location).query.collect{|q| k,v = q.split('='); query[k] = v }
|
77
|
+
Ticket.new(query['ticket'], @service_url)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def post(uri, form_data)
|
83
|
+
req = Net::HTTP::Post.new(uri.path)
|
84
|
+
req.set_form_data(form_data, ';')
|
85
|
+
https(uri) do |h|
|
86
|
+
h.request(req)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Ticket
|
92
|
+
attr_accessor :user, :failure_code, :failure_message
|
93
|
+
attr_reader :service_url
|
94
|
+
|
95
|
+
def initialize(ticket, service_url, renew = false)
|
96
|
+
@service_url = service_url
|
97
|
+
@ticket = ticket
|
98
|
+
@renew = renew
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.from_hash(hash)
|
102
|
+
ticket = Ticket.new(hash[:ticket], hash[:service_url], hash[:renew])
|
103
|
+
ticket.user = hash[:user]
|
104
|
+
ticket
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_request_params
|
108
|
+
params = {:service => @service_url,
|
109
|
+
:ticket => @ticket }
|
110
|
+
params[:renew] = 1 if @renew
|
111
|
+
params
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_hash
|
115
|
+
props = {}
|
116
|
+
props[:user] = @user if authenticated?
|
117
|
+
props[:renew] = @renew if @renew
|
118
|
+
props[:service_url] = @service_url
|
119
|
+
props[:ticket] = @ticket
|
120
|
+
props
|
121
|
+
end
|
122
|
+
|
123
|
+
def authenticated?
|
124
|
+
!!@user
|
125
|
+
end
|
126
|
+
|
127
|
+
def authenticate(body)
|
128
|
+
response = CasResponseParser.parse(self, body)
|
129
|
+
authenticated?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ResponseError < Exception
|
134
|
+
end
|
135
|
+
|
136
|
+
class CasServerException < Exception
|
137
|
+
end
|
138
|
+
|
139
|
+
class UnknownTicketType < Exception
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class URI::HTTP
|
144
|
+
def merge_query(hash)
|
145
|
+
q = query ? query + '&' : ''
|
146
|
+
self.query = "#{q}#{hash_to_uri_array(hash)}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def hash_to_uri_array(hash)
|
150
|
+
hash.collect do |name, value|
|
151
|
+
if value.kind_of? Array
|
152
|
+
value.map {|v| stringify_param(name, v) }
|
153
|
+
else
|
154
|
+
stringify_param(name, value)
|
155
|
+
end
|
156
|
+
end.join('&')
|
157
|
+
end
|
158
|
+
|
159
|
+
def stringify_param(name, value)
|
160
|
+
"#{CGI::escape(name.to_s)}=#{CGI::escape(value.to_s)}"
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Casablanca
|
2
|
+
class RailsFilter
|
3
|
+
@@client = nil
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def client=client
|
8
|
+
@@client = client
|
9
|
+
end
|
10
|
+
|
11
|
+
def filter(controller)
|
12
|
+
return true if previous_ticket(controller) && !controller.params[:renew]
|
13
|
+
ticket = Ticket.new(controller.params[:ticket], @@client.service_url, controller.params[:renew])
|
14
|
+
if @@client.authenticate_ticket(ticket)
|
15
|
+
puts "Ticket authenticated #{ticket.failure_message}"
|
16
|
+
controller.session[:cas_user] = ticket.user
|
17
|
+
controller.session[:cas_ticket] = ticket.to_hash
|
18
|
+
return true
|
19
|
+
else
|
20
|
+
puts "Ticket authentication failed: #{ticket.failure_message}"
|
21
|
+
controller.session[:cas_user] = nil
|
22
|
+
controller.session[:cas_ticket] = nil
|
23
|
+
controller.send(:redirect_to, login_url)
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def login_url
|
29
|
+
@@client.login_url
|
30
|
+
end
|
31
|
+
|
32
|
+
def logout(controller)
|
33
|
+
controller.send(:reset_session)
|
34
|
+
controller.send(:redirect_to, @@client.logout_url)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def previous_ticket(controller)
|
40
|
+
hash = controller.session[:cas_ticket]
|
41
|
+
return nil unless hash
|
42
|
+
Ticket.from_hash(hash)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Casablanca
|
2
|
+
|
3
|
+
class CasResponseParser
|
4
|
+
def protocol
|
5
|
+
self.class.to_s.gsub(/(Casablanca::Cas_|_ResponseParser)/, '').gsub('_', '.').to_f
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.parse(ticket, body)
|
9
|
+
raise ResponseError, "Response body is empty" if body.nil? || body.strip == ""
|
10
|
+
#return Cas_1_0_Parser.new(body) if ?
|
11
|
+
response = Cas_2_0_ResponseParser.new(body)
|
12
|
+
ticket.user = response.user
|
13
|
+
unless response.authenticated?
|
14
|
+
ticket.failure_code = response.failure_code
|
15
|
+
ticket.failure_message = response.failure_message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Cas_2_0_ResponseParser < CasResponseParser
|
21
|
+
def initialize(xml)
|
22
|
+
doc = REXML::Document.new(xml)
|
23
|
+
@xml = doc.elements['cas:serviceResponse'].elements[1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def user
|
27
|
+
strip_text(@xml.elements['cas:user'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def authenticated?
|
31
|
+
@xml.name == 'authenticationSuccess'
|
32
|
+
end
|
33
|
+
|
34
|
+
def failure_code
|
35
|
+
@xml.elements['//cas:authenticationFailure'].attributes['code']
|
36
|
+
end
|
37
|
+
|
38
|
+
def failure_message
|
39
|
+
strip_text(@xml.elements['//cas:authenticationFailure'])
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def strip_text(tag)
|
45
|
+
tag.text.strip if tag
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
class TestClient < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@client = Client.new(:cas_server_url => "http://localhost:4567", :service_url => "http://localhost:3000")
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_config
|
9
|
+
assert_equal @client.service_url, "http://localhost:3000"
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_config_requires_cas_server_url
|
13
|
+
assert_raises(RuntimeError) do
|
14
|
+
@client = Client.new({})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_authenticate_ticket
|
19
|
+
cli = CommandLineClient.new(:cas_server_url => "http://localhost:4567", :service_url => "http://localhost:3000")
|
20
|
+
ticket = cli.login_ticket('admin', 'admin') #'ST-1231341579r871C5757B79767C21E'
|
21
|
+
service_ticket = Ticket.new(ticket, 'http://localhost:3000', true)
|
22
|
+
@client.authenticate_ticket(service_ticket)
|
23
|
+
assert_equal 'admin', service_ticket.user
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_validate_expired_ticket
|
27
|
+
ticket = 'ST-1231341579r871C5757B79767C21E'
|
28
|
+
service_ticket = Ticket.new(ticket, 'http://localhost:3000', true)
|
29
|
+
@client.authenticate_ticket(service_ticket)
|
30
|
+
assert_equal 'INVALID_TICKET', service_ticket.failure_code
|
31
|
+
#assert_equal "Ticket 'ST-1231341579r871C5757B79767C21E' has already been used up.", ticket.failure_message
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_validate_invalid_ticket
|
35
|
+
ticket = '1231341579r871C5757B79767C21E'
|
36
|
+
service_ticket = Ticket.new(ticket, 'http://localhost:3000', true)
|
37
|
+
@client.authenticate_ticket(service_ticket)
|
38
|
+
assert_equal 'INVALID_TICKET', service_ticket.failure_code
|
39
|
+
assert_equal "Ticket 1231341579r871C5757B79767C21E not recognized.", service_ticket.failure_message
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_authenticate_ticket_with_empty_service_url
|
43
|
+
service_ticket = Ticket.new('ticket', nil)
|
44
|
+
assert_raises(RuntimeError) do
|
45
|
+
@client.authenticate_ticket(service_ticket)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_login_url
|
50
|
+
assert_equal 'http://localhost:4567/login?service=http://localhost:3000', @client.login_url
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_logout_url
|
54
|
+
assert_equal 'http://localhost:4567/logout', @client.logout_url
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_validate_url
|
58
|
+
assert_equal 'http://localhost:4567/proxyValidate', @client.validate_url
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
class TestCommandLineClient < Test::Unit::TestCase
|
64
|
+
def setup
|
65
|
+
@client = CommandLineClient.new(:cas_server_url => "http://localhost:4567", :service_url => "http://localhost:3000")
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_login
|
69
|
+
@client.expects(:post).returns(MockResponse.new('', '303', :location => 'http://localhost:3000?ticket=ST-1231341579r871C5757B79767C21E'))
|
70
|
+
res = @client.login('admin', 'admin')
|
71
|
+
assert_equal '', res.body
|
72
|
+
assert_equal '303', res.code
|
73
|
+
assert_equal 'http://localhost:3000?ticket=ST-1231341579r871C5757B79767C21E', res['location']
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_get_service_ticket
|
77
|
+
@client.expects(:post).returns(MockResponse.new('', '303', :location => 'http://localhost:3000?ticket=ST-1231341579r871C5757B79767C21E'))
|
78
|
+
ticket = @client.get_login_ticket('admin', 'admin')
|
79
|
+
assert_equal 'ST-1231341579r871C5757B79767C21E', ticket
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class TestURIHTTP < Test::Unit::TestCase
|
85
|
+
def test_merge_query
|
86
|
+
uri = URI.parse('http://localhost:4567/login')
|
87
|
+
uri.merge_query({:order_by => ['1', '2']})
|
88
|
+
assert_equal 'order_by=1&order_by=2', uri.query
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_merge_query_with_existing_query
|
92
|
+
uri = URI.parse('http://localhost:4567/login?search=ah')
|
93
|
+
uri.merge_query({:order_by => ['1', '2']})
|
94
|
+
assert_equal 'search=ah&order_by=1&order_by=2', uri.query
|
95
|
+
end
|
96
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'casablanca.rb')))
|
2
|
+
require(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'casablanca', 'filters', 'rails.rb')))
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
include Casablanca
|
9
|
+
end
|
10
|
+
|
11
|
+
class MockResponse < Net::HTTPResponse
|
12
|
+
attr_accessor :body, :code
|
13
|
+
def initialize(body, code=200, header={})
|
14
|
+
@body, @code, @header = body, code, header
|
15
|
+
end
|
16
|
+
|
17
|
+
def []= key, value
|
18
|
+
@header[key.to_sym] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def [] key
|
22
|
+
@header[key.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
def kind_of?(klass)
|
26
|
+
if klass == Net::HTTPSuccess
|
27
|
+
code.to_i == 200
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
VALID_REQUEST = %(
|
33
|
+
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
|
34
|
+
<cas:authenticationSuccess>
|
35
|
+
<cas:user>admin</cas:user>
|
36
|
+
</cas:authenticationSuccess>
|
37
|
+
</cas:serviceResponse>
|
38
|
+
)
|
39
|
+
|
40
|
+
INVALID_REQUEST = %(
|
41
|
+
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
|
42
|
+
<cas:authenticationFailure code="INVALID_REQUEST">
|
43
|
+
Ticket or service parameter was missing in the request.
|
44
|
+
</cas:authenticationFailure>
|
45
|
+
</cas:serviceResponse>
|
46
|
+
)
|
47
|
+
|
48
|
+
INVALID_TICKET = %(
|
49
|
+
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
|
50
|
+
<cas:authenticationFailure code="INVALID_TICKET">
|
51
|
+
Ticket ST-1231242314r72465638160B31E8D1 not recognized.
|
52
|
+
</cas:authenticationFailure>
|
53
|
+
</cas:serviceResponse>
|
54
|
+
)
|
data/test/test_parser.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
class TestCas_2_0_ResponseParser < Test::Unit::TestCase
|
4
|
+
def test_parse_valid_ticket
|
5
|
+
response = Cas_2_0_ResponseParser.new(VALID_REQUEST)
|
6
|
+
assert_equal 'admin', response.user
|
7
|
+
assert_equal 2.0, response.protocol
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_parse_invalid_request
|
11
|
+
response = Cas_2_0_ResponseParser.new(INVALID_REQUEST)
|
12
|
+
assert_equal 'INVALID_REQUEST', response.failure_code
|
13
|
+
assert_equal 'Ticket or service parameter was missing in the request.', response.failure_message
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_parse_invalid_ticket
|
17
|
+
response = Cas_2_0_ResponseParser.new(INVALID_TICKET)
|
18
|
+
assert_equal 'INVALID_TICKET', response.failure_code
|
19
|
+
assert_equal 'Ticket ST-1231242314r72465638160B31E8D1 not recognized.', response.failure_message
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
require 'action_pack'
|
3
|
+
class TestRailsFilter < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@client = Client.new(:cas_server_url => "http://localhost:4567", :service_url => "http://localhost:3000")
|
6
|
+
RailsFilter.client = @client
|
7
|
+
@controller = Controller.new
|
8
|
+
@controller.params = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_login_url
|
12
|
+
assert_equal 'http://localhost:4567/login?service=http://localhost:3000', RailsFilter.login_url
|
13
|
+
end
|
14
|
+
|
15
|
+
# def test_filter_requires_config
|
16
|
+
# RailsFilter.config = nil
|
17
|
+
# assert_raises(RuntimeError) do
|
18
|
+
# RailsFilter.filter(Controller.new)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
|
22
|
+
def test_logout
|
23
|
+
RailsFilter.logout(@controller)
|
24
|
+
assert_equal({}, @controller.session)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_filter_invalid_attempt
|
28
|
+
@controller.session = {}
|
29
|
+
assert_equal false, RailsFilter.filter(@controller)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_filter_authenticated
|
33
|
+
params = {:ticket => 'a'}
|
34
|
+
@client.expects(:authenticate_ticket).returns(true)
|
35
|
+
@controller.params = params
|
36
|
+
assert_equal true, RailsFilter.filter(@controller)
|
37
|
+
assert_session(nil, { :ticket => 'a', :service_url => 'http://localhost:3000' })
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_filter_same_ticket
|
41
|
+
params = {:ticket => 'a'}
|
42
|
+
@controller.session = { :cas_ticket => params, :cas_user => 'admin' }
|
43
|
+
@controller.params = params
|
44
|
+
assert_equal true, RailsFilter.filter(@controller)
|
45
|
+
assert_session('admin', params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_filter_resets_sessions_for_renew
|
49
|
+
@controller.session[:cas_ticket] = { :ticket => 'a', :service_url => 'b' }
|
50
|
+
@controller.params = {:renew => true }
|
51
|
+
assert_equal false, RailsFilter.filter(@controller)
|
52
|
+
assert_session(nil, nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_session(user, ticket)
|
56
|
+
assert_equal ticket, @controller.session[:cas_ticket]
|
57
|
+
assert_equal user, @controller.session[:cas_user]
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class Controller # < ActionController::Base
|
63
|
+
attr_accessor :params, :session
|
64
|
+
def initialize
|
65
|
+
@session = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
def request
|
69
|
+
Request.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def url_for(url)
|
73
|
+
url
|
74
|
+
end
|
75
|
+
|
76
|
+
def redirect_to(url)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def reset_session
|
82
|
+
@session = {}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Request
|
87
|
+
def headers
|
88
|
+
{}
|
89
|
+
end
|
90
|
+
def post?
|
91
|
+
end
|
92
|
+
end
|
data/test/test_ticket.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
class TestTicket < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@ticket = Ticket.new('ST-1231242314r72465638160B31E8D1', 'http://localhost:3000')
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_create_service_ticket
|
9
|
+
ticket = Ticket.new('ST-1231242314r72465638160B31E8D1', 'http://localhost:3000')
|
10
|
+
assert_equal 'ST-1231242314r72465638160B31E8D1', ticket.to_hash[:ticket]
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_create_proxy_ticket
|
14
|
+
ticket = Ticket.new('PT-1231242314r72465638160B31E8D1', 'http://localhost:3000')
|
15
|
+
assert_equal 'PT-1231242314r72465638160B31E8D1', ticket.to_hash[:ticket]
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_to_hash
|
19
|
+
ticket = Ticket.new('ST-1231242314r72465638160B31E8D1', 'http://localhost:3000')
|
20
|
+
assert_equal 'ST-1231242314r72465638160B31E8D1', ticket.to_hash[:ticket]
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_from_hash
|
24
|
+
props = {:ticket => 'ticket',
|
25
|
+
:service_url => "http://localhost:3000",
|
26
|
+
:renew => 1,
|
27
|
+
:user => 'admin' }
|
28
|
+
ticket = Ticket.from_hash(props)
|
29
|
+
assert_equal props, ticket.to_hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_to_request_params
|
33
|
+
ticket = Ticket.new('ticket', 'http://localhost:3000')
|
34
|
+
expected = {:ticket => 'ticket',
|
35
|
+
:service => "http://localhost:3000" }
|
36
|
+
assert_equal(expected, ticket.to_request_params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_to_request_params_with_renew
|
40
|
+
ticket = Ticket.new('ticket', 'http://localhost:3000', true)
|
41
|
+
expected = {:ticket => 'ticket',
|
42
|
+
:service => "http://localhost:3000",
|
43
|
+
:renew => 1 }
|
44
|
+
assert_equal(expected, ticket.to_request_params)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_authenticate_valid_ticket
|
48
|
+
@ticket.authenticate(VALID_REQUEST)
|
49
|
+
assert_equal 'admin', @ticket.user
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_authenticate_invalid_request_resets_ticket_to_unauthenticated
|
53
|
+
@ticket.authenticate(VALID_REQUEST)
|
54
|
+
assert_equal true, @ticket.authenticated?
|
55
|
+
@ticket.authenticate(INVALID_REQUEST)
|
56
|
+
assert_equal false, @ticket.authenticated?
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_authenticate_invalid_request
|
60
|
+
@ticket.authenticate(INVALID_REQUEST)
|
61
|
+
assert_equal 'INVALID_REQUEST', @ticket.failure_code
|
62
|
+
assert_equal 'Ticket or service parameter was missing in the request.', @ticket.failure_message
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_authenticate_invalid_ticket
|
66
|
+
@ticket.authenticate(INVALID_TICKET)
|
67
|
+
assert_equal 'INVALID_TICKET', @ticket.failure_code
|
68
|
+
assert_equal 'Ticket ST-1231242314r72465638160B31E8D1 not recognized.', @ticket.failure_message
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: p8-casablanca
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Petrik de Heus
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-17 00:00:00 -08:00
|
13
|
+
default_executable: casablanca
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.8.2
|
23
|
+
version:
|
24
|
+
description:
|
25
|
+
email:
|
26
|
+
- FIX@example.com
|
27
|
+
executables:
|
28
|
+
- casablanca
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.textile
|
35
|
+
files:
|
36
|
+
- History.txt
|
37
|
+
- Manifest.txt
|
38
|
+
- Rakefile
|
39
|
+
- init.rb
|
40
|
+
- bin/casablanca
|
41
|
+
- lib/casablanca.rb
|
42
|
+
- lib/casablanca/cli.rb
|
43
|
+
- lib/casablanca/client.rb
|
44
|
+
- lib/casablanca/filters/rails.rb
|
45
|
+
- lib/casablanca/response_parsers.rb
|
46
|
+
- lib/casablanca/version.rb
|
47
|
+
- test/test_client.rb
|
48
|
+
- test/test_helper.rb
|
49
|
+
- test/test_parser.rb
|
50
|
+
- test/test_rails_filter.rb
|
51
|
+
- test/test_ticket.rb
|
52
|
+
- README.textile
|
53
|
+
has_rdoc: true
|
54
|
+
homepage:
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --main
|
58
|
+
- README.txt
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: casablanca
|
76
|
+
rubygems_version: 1.2.0
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: A single sign-on client based on the CAS 2.0 protocol.
|
80
|
+
test_files:
|
81
|
+
- test/test_client.rb
|
82
|
+
- test/test_helper.rb
|
83
|
+
- test/test_parser.rb
|
84
|
+
- test/test_rails_filter.rb
|
85
|
+
- test/test_ticket.rb
|