castanet 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.md +4 -0
- data/LICENSE +20 -0
- data/README.md +63 -0
- data/lib/castanet.rb +8 -0
- data/lib/castanet/client.rb +207 -0
- data/lib/castanet/proxy_ticket.rb +133 -0
- data/lib/castanet/proxy_ticket_error.rb +6 -0
- data/lib/castanet/query_building.rb +60 -0
- data/lib/castanet/responses.rb +24 -0
- data/lib/castanet/responses/common.rl +44 -0
- data/lib/castanet/responses/proxy.rb +526 -0
- data/lib/castanet/responses/proxy.rl +120 -0
- data/lib/castanet/responses/ticket_validate.rb +720 -0
- data/lib/castanet/responses/ticket_validate.rl +172 -0
- data/lib/castanet/service_ticket.rb +180 -0
- data/lib/castanet/version.rb +3 -0
- metadata +184 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'castanet'
|
2
|
+
|
3
|
+
%%{
|
4
|
+
machine ticket_validate;
|
5
|
+
|
6
|
+
action save_username { r.username = buffer; buffer = '' }
|
7
|
+
action save_failure_code { r.failure_code = buffer; buffer = '' }
|
8
|
+
action save_failure_reason { r.failure_reason = buffer.strip; buffer = '' }
|
9
|
+
action save_pgt_iou { r.pgt_iou = buffer; buffer = '' }
|
10
|
+
action save_proxy { r.proxies << buffer; buffer = '' }
|
11
|
+
action set_authenticated { r.ok = true }
|
12
|
+
|
13
|
+
include common "common.rl";
|
14
|
+
|
15
|
+
# Leaf tags
|
16
|
+
# ---------
|
17
|
+
|
18
|
+
pgt_iou = "<cas:proxyGrantingTicket>"
|
19
|
+
ticket @buffer
|
20
|
+
"</cas:proxyGrantingTicket>" %save_pgt_iou;
|
21
|
+
proxy = "<cas:proxy>"
|
22
|
+
char_data @buffer
|
23
|
+
"</cas:proxy>" %save_proxy;
|
24
|
+
user = "<cas:user>"
|
25
|
+
char_data @buffer
|
26
|
+
"</cas:user>" %save_username;
|
27
|
+
|
28
|
+
# Non-leaf tags
|
29
|
+
# -------------
|
30
|
+
|
31
|
+
authentication_failure_start = "<cas:authenticationFailure code="
|
32
|
+
quote
|
33
|
+
failure_code %save_failure_code
|
34
|
+
quote
|
35
|
+
">";
|
36
|
+
authentication_failure_end = "</cas:authenticationFailure>";
|
37
|
+
|
38
|
+
authentication_success_start = "<cas:authenticationSuccess>";
|
39
|
+
authentication_success_end = "</cas:authenticationSuccess>";
|
40
|
+
proxies = "<cas:proxies>"
|
41
|
+
( space* proxy space* )*
|
42
|
+
"</cas:proxies>";
|
43
|
+
|
44
|
+
# Top-level elements
|
45
|
+
# ------------------
|
46
|
+
|
47
|
+
ok_cas_st = ( service_response_start
|
48
|
+
space*
|
49
|
+
authentication_success_start
|
50
|
+
space*
|
51
|
+
user
|
52
|
+
space*
|
53
|
+
pgt_iou?
|
54
|
+
space*
|
55
|
+
proxies?
|
56
|
+
space*
|
57
|
+
authentication_success_end
|
58
|
+
space*
|
59
|
+
service_response_end ) @set_authenticated;
|
60
|
+
|
61
|
+
failed_cas_st = ( service_response_start
|
62
|
+
space*
|
63
|
+
authentication_failure_start
|
64
|
+
failure_reason %save_failure_reason
|
65
|
+
authentication_failure_end
|
66
|
+
space*
|
67
|
+
service_response_end );
|
68
|
+
|
69
|
+
main := ok_cas_st | failed_cas_st;
|
70
|
+
}%%
|
71
|
+
|
72
|
+
module Castanet::Responses
|
73
|
+
##
|
74
|
+
# A parsed representation of responses from `/serviceValidate` or
|
75
|
+
# `/proxyValidate`.
|
76
|
+
#
|
77
|
+
# The responses for the above services are identical, so we implement their
|
78
|
+
# parser with the same state machine.
|
79
|
+
#
|
80
|
+
# The code in this class implements a state machine generated by Ragel. The
|
81
|
+
# state machine definition is in ticket_validate.rl.
|
82
|
+
#
|
83
|
+
# @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, sections 2.5, 2.6,
|
84
|
+
# and appendix A
|
85
|
+
class TicketValidate
|
86
|
+
##
|
87
|
+
# Whether or not this response passed CAS authentication.
|
88
|
+
#
|
89
|
+
# @return [Boolean]
|
90
|
+
attr_accessor :ok
|
91
|
+
|
92
|
+
alias_method :ok?, :ok
|
93
|
+
|
94
|
+
##
|
95
|
+
# The failure code returned on authentication failure.
|
96
|
+
#
|
97
|
+
# @return [String, nil]
|
98
|
+
attr_accessor :failure_code
|
99
|
+
|
100
|
+
##
|
101
|
+
# The reason given by the CAS server for authentication failure.
|
102
|
+
#
|
103
|
+
# @return [String, nil]
|
104
|
+
attr_accessor :failure_reason
|
105
|
+
|
106
|
+
##
|
107
|
+
# The PGT IOU returned by an authentication success message.
|
108
|
+
#
|
109
|
+
# @return [String, nil]
|
110
|
+
attr_accessor :pgt_iou
|
111
|
+
|
112
|
+
##
|
113
|
+
# A list of authentication proxies for this ticket.
|
114
|
+
#
|
115
|
+
# Each participant in an authentication chain adds one entry to this list.
|
116
|
+
# As an example, assume the existence of two services:
|
117
|
+
#
|
118
|
+
# 1. frontend
|
119
|
+
# 2. backend
|
120
|
+
#
|
121
|
+
# If `frontend` proxied access to `backend`, the proxy list would be
|
122
|
+
#
|
123
|
+
# 1. backend
|
124
|
+
# 2. frontend
|
125
|
+
#
|
126
|
+
# The proxy chain has an unbounded maximum length. The proxy order
|
127
|
+
# specified in the CAS response is preserved.
|
128
|
+
#
|
129
|
+
# For proxy tickets that fail validation, this will be an empty list. It
|
130
|
+
# should also be an empty list for service tickets too, although that's
|
131
|
+
# really up to the CAS server.
|
132
|
+
#
|
133
|
+
# Although this list is technically a valid component of an authentication
|
134
|
+
# response issued by `/serviceValidate`, it's really only applicable to
|
135
|
+
# proxy tickets.
|
136
|
+
#
|
137
|
+
# @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.6.2
|
138
|
+
# @return [Array]
|
139
|
+
attr_accessor :proxies
|
140
|
+
|
141
|
+
##
|
142
|
+
# The name of the owner of the validated service or proxy ticket.
|
143
|
+
#
|
144
|
+
# This information is only present on authentication success.
|
145
|
+
#
|
146
|
+
# @return [String, nil]
|
147
|
+
attr_accessor :username
|
148
|
+
|
149
|
+
##
|
150
|
+
# Generates a {TicketValidate} object from a CAS response.
|
151
|
+
#
|
152
|
+
# @param [String] response the CAS response
|
153
|
+
# @return [TicketValidate}
|
154
|
+
def self.from_cas(response)
|
155
|
+
data = response.strip.unpack('U*')
|
156
|
+
buffer = ''
|
157
|
+
|
158
|
+
%% write init;
|
159
|
+
|
160
|
+
new.tap do |r|
|
161
|
+
%% write exec;
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize
|
166
|
+
self.ok = false
|
167
|
+
self.proxies = []
|
168
|
+
end
|
169
|
+
|
170
|
+
%% write data;
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'castanet'
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Castanet
|
7
|
+
class ServiceTicket
|
8
|
+
extend Forwardable
|
9
|
+
include Responses
|
10
|
+
include QueryBuilding
|
11
|
+
|
12
|
+
##
|
13
|
+
# Set this to `true` to _not_ use HTTPS for CAS server communication.
|
14
|
+
#
|
15
|
+
# In almost all cases where CAS is used, there is no good reason to avoid
|
16
|
+
# HTTPS. However, if you
|
17
|
+
#
|
18
|
+
# 1. need to have access to CAS server messages and
|
19
|
+
# 2. are in an isolated development environment
|
20
|
+
#
|
21
|
+
# then it may make sense to disable HTTPS.
|
22
|
+
#
|
23
|
+
# This is usually set by {Castanet::Client}.
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
26
|
+
attr_accessor :https_disabled
|
27
|
+
|
28
|
+
##
|
29
|
+
# The proxy callback URL to use for service validation.
|
30
|
+
#
|
31
|
+
# @return [String, nil]
|
32
|
+
attr_accessor :proxy_callback_url
|
33
|
+
|
34
|
+
##
|
35
|
+
# The URL of the service to use for retrieving PGTs.
|
36
|
+
#
|
37
|
+
# @return [String, nil]
|
38
|
+
attr_accessor :proxy_retrieval_url
|
39
|
+
|
40
|
+
##
|
41
|
+
# The URL of the CAS server's serviceValidate service.
|
42
|
+
#
|
43
|
+
# @return [String, nil]
|
44
|
+
attr_accessor :service_validate_url
|
45
|
+
|
46
|
+
##
|
47
|
+
# The wrapped service ticket.
|
48
|
+
#
|
49
|
+
# @return [String, nil]
|
50
|
+
attr_reader :ticket
|
51
|
+
|
52
|
+
##
|
53
|
+
# The wrapped service URL.
|
54
|
+
#
|
55
|
+
# @return [String, nil]
|
56
|
+
attr_reader :service
|
57
|
+
|
58
|
+
##
|
59
|
+
# The response from the CAS server.
|
60
|
+
#
|
61
|
+
# {ServiceTicket} sets this attribute whilst executing {#present!}, but it
|
62
|
+
# can be manually set for e.g. testing purposes.
|
63
|
+
#
|
64
|
+
# @return [#ok?, #pgt_iou]
|
65
|
+
attr_accessor :response
|
66
|
+
|
67
|
+
def_delegators :response, :ok?, :pgt_iou, :username
|
68
|
+
|
69
|
+
##
|
70
|
+
# The PGT associated with this service ticket.
|
71
|
+
#
|
72
|
+
# This is set after a successful invocation of {#retrieve_pgt!}.
|
73
|
+
#
|
74
|
+
# @return [String, nil]
|
75
|
+
attr_accessor :pgt
|
76
|
+
|
77
|
+
def initialize(ticket, service)
|
78
|
+
@service = service
|
79
|
+
@ticket = ticket
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Validates `ticket` for the service URL given in `service`. If
|
84
|
+
# {#proxy_callback_url} is not nil, also attempts to retrieve the PGTIOU
|
85
|
+
# for this service ticket.
|
86
|
+
#
|
87
|
+
# CAS service tickets are one-time-use only
|
88
|
+
# =========================================
|
89
|
+
#
|
90
|
+
# This method checks `ticket` against `service` using the CAS server, so you
|
91
|
+
# must take care to only validate a given `ticket` _once_.
|
92
|
+
#
|
93
|
+
# Since ServiceTicket does not maintain any state with regard to whether a
|
94
|
+
# ServiceTicket instance has already been presented, multiple presentations
|
95
|
+
# of the same ticket will result in behavior like this:
|
96
|
+
#
|
97
|
+
# st = service_ticket(ticket, service)
|
98
|
+
# st.present!
|
99
|
+
#
|
100
|
+
# st.ok? # => true
|
101
|
+
#
|
102
|
+
# st.present!
|
103
|
+
#
|
104
|
+
# st.ok? # => false
|
105
|
+
#
|
106
|
+
# @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, sections 2.5 and
|
107
|
+
# 3.1.1
|
108
|
+
#
|
109
|
+
# @return void
|
110
|
+
def present!
|
111
|
+
uri = URI.parse(validation_url).tap do |u|
|
112
|
+
u.query = validation_parameters
|
113
|
+
end
|
114
|
+
|
115
|
+
http = Net::HTTP.new(uri.host, uri.port).tap do |h|
|
116
|
+
h.use_ssl = !https_disabled
|
117
|
+
end
|
118
|
+
|
119
|
+
http.start do |h|
|
120
|
+
cas_response = h.get(uri.to_s)
|
121
|
+
|
122
|
+
self.response = parsed_ticket_validate_response(cas_response.body)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Retrieves a PGT from {#proxy_retrieval_url} using the PGT IOU.
|
128
|
+
#
|
129
|
+
# CAS 2.0 does not specify whether PGTIOUs are one-time-use only.
|
130
|
+
# Therefore, Castanet does not prevent multiple invocations of
|
131
|
+
# `retrieve_pgt!`; however, it is safest to assume that PGTIOUs are
|
132
|
+
# one-time-use only.
|
133
|
+
#
|
134
|
+
# CAS 2.0 also does not specify the response format for proxy callbacks.
|
135
|
+
# `retrieve_pgt!` assumes that a `200` response from {#proxy_retrieval_url}
|
136
|
+
# will contain the PGT and only the PGT.
|
137
|
+
#
|
138
|
+
# The retrieved PGT will be written to {#pgt} if this method succeeds.
|
139
|
+
#
|
140
|
+
# @return void
|
141
|
+
def retrieve_pgt!
|
142
|
+
uri = URI.parse(proxy_retrieval_url).tap do |u|
|
143
|
+
u.query = query(['pgtIou', pgt_iou])
|
144
|
+
end
|
145
|
+
|
146
|
+
http = Net::HTTP.new(uri.host, uri.port).tap do |h|
|
147
|
+
h.use_ssl = !https_disabled
|
148
|
+
end
|
149
|
+
|
150
|
+
http.start do |h|
|
151
|
+
self.pgt = h.get(uri.to_s).body
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
protected
|
156
|
+
|
157
|
+
##
|
158
|
+
# The URL to use for ticket validation.
|
159
|
+
#
|
160
|
+
# @return [String]
|
161
|
+
def validation_url
|
162
|
+
service_validate_url
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
##
|
168
|
+
# Builds a query string for use with the `serviceValidate` service.
|
169
|
+
#
|
170
|
+
# @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.5.1
|
171
|
+
# @param [String] ticket a service ticket
|
172
|
+
# @param [String] service a service URL
|
173
|
+
# @return [String] a query component of a URI
|
174
|
+
def validation_parameters
|
175
|
+
query(['ticket', ticket],
|
176
|
+
['service', service],
|
177
|
+
['pgtUrl', proxy_callback_url])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
metadata
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: castanet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- David Yip
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-02-03 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: autotest
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
prerelease: false
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: ci_reporter
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: cucumber
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: mechanize
|
61
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
type: :development
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: rack
|
74
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: *id005
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rspec
|
87
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 2
|
94
|
+
- 0
|
95
|
+
version: "2.0"
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: *id006
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: webmock
|
101
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
type: :development
|
110
|
+
prerelease: false
|
111
|
+
version_requirements: *id007
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: yard
|
114
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
version: "0"
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: *id008
|
125
|
+
description: A small, snappy CAS 2.0 client library for Ruby applications
|
126
|
+
email:
|
127
|
+
- yipdw@northwestern.edu
|
128
|
+
executables: []
|
129
|
+
|
130
|
+
extensions: []
|
131
|
+
|
132
|
+
extra_rdoc_files: []
|
133
|
+
|
134
|
+
files:
|
135
|
+
- README.md
|
136
|
+
- History.md
|
137
|
+
- LICENSE
|
138
|
+
- lib/castanet/client.rb
|
139
|
+
- lib/castanet/proxy_ticket.rb
|
140
|
+
- lib/castanet/proxy_ticket_error.rb
|
141
|
+
- lib/castanet/query_building.rb
|
142
|
+
- lib/castanet/responses/common.rl
|
143
|
+
- lib/castanet/responses/proxy.rb
|
144
|
+
- lib/castanet/responses/proxy.rl
|
145
|
+
- lib/castanet/responses/ticket_validate.rb
|
146
|
+
- lib/castanet/responses/ticket_validate.rl
|
147
|
+
- lib/castanet/responses.rb
|
148
|
+
- lib/castanet/service_ticket.rb
|
149
|
+
- lib/castanet/version.rb
|
150
|
+
- lib/castanet.rb
|
151
|
+
has_rdoc: true
|
152
|
+
homepage: ""
|
153
|
+
licenses: []
|
154
|
+
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
|
158
|
+
require_paths:
|
159
|
+
- lib
|
160
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
segments:
|
166
|
+
- 0
|
167
|
+
version: "0"
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
segments:
|
174
|
+
- 0
|
175
|
+
version: "0"
|
176
|
+
requirements: []
|
177
|
+
|
178
|
+
rubyforge_project:
|
179
|
+
rubygems_version: 1.3.7
|
180
|
+
signing_key:
|
181
|
+
specification_version: 3
|
182
|
+
summary: A CAS client library
|
183
|
+
test_files: []
|
184
|
+
|