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 ADDED
@@ -0,0 +1,4 @@
1
+ 0.0.1 (unreleased)
2
+ ==================
3
+
4
+ - Initial release.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 David Yip
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ Castanet: a small, snappy CAS client library
2
+ ============================================
3
+
4
+ Castanet is a [Central Authentication Service](http://www.jasig.org/cas) (CAS)
5
+ client library. It implements version 2.0 of the CAS protocol.
6
+
7
+ Castanet was built at the [Northwestern University Biomedical Informatics
8
+ Center](http://www.nucats.northwestern.edu/clinical-research-resources/data-collection-biomedical-informatics-and-nubic/bioinformatics-overview.html)
9
+ as a replacement for [RubyCAS-Client](https://github.com/gunark/rubycas-client)
10
+ in internal software.
11
+
12
+ Castanet is tested on Ruby 1.8.7, Ruby 1.9.2, JRuby 1.5.6 in Ruby 1.8 mode, and Rubinius 1.2.0.
13
+ Continuous integration reports are available at [NUBIC's CI
14
+ server](https://ctms-ci.nubic.northwestern.edu/hudson/job/castanet/).
15
+
16
+ Getting started
17
+ ===============
18
+
19
+ Mix `Castanet::Client` into the objects that need CAS client behavior.
20
+
21
+ Objects that include `Castanet::Client` must implement `cas_url`,
22
+ `proxy_callback_url`, and `proxy_retrieval_url`.
23
+
24
+ See the documentation for `Castanet::Client` for more information and usage
25
+ examples.
26
+
27
+ Acknowledgments
28
+ ===============
29
+
30
+ Castanet's test harness was based off of code originally written by [Rhett
31
+ Sutphin](mailto:rhett@detailedbalance.net).
32
+
33
+ Query string building code was taken from [Rack](http://rack.rubyforge.org/).
34
+
35
+ Development
36
+ ===========
37
+
38
+ Castanet uses [Bundler](http://gembundler.com/) version `~> 1.0` for dependency
39
+ management.
40
+
41
+ Some of Castanet's development dependencies work best in certain versions of
42
+ Ruby. Additionally, some implementations of Ruby do not support constructs
43
+ (i.e. `fork`) used by Castanet's tests. For this reason, Castanet's Cucumber
44
+ scenarios use [RVM](http://rvm.beginrescueend.com/) to run servers in
45
+ appropriate Ruby implementations.
46
+
47
+ Castanet's CAS response parsers are implemented using
48
+ [Ragel](http://www.complang.org/ragel/).
49
+
50
+ Once you've got Bundler, RVM, and Ragel installed and set up:
51
+
52
+ $ bundle install
53
+ $ rake udaeta:install_dependencies --trace # because it helps to see what's going on
54
+ $ rake ci --trace # ditto
55
+
56
+ Assuming you cloned Castanet at a point where its CI build succeeded, all steps
57
+ should pass. If they don't, feel free to ping me.
58
+
59
+ License
60
+ =======
61
+
62
+ Copyright (c) 2011 David Yip. Released under the X11 (MIT) License; see LICENSE
63
+ for details.
data/lib/castanet.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Castanet
2
+ autoload :Client, 'castanet/client'
3
+ autoload :ProxyTicket, 'castanet/proxy_ticket'
4
+ autoload :ProxyTicketError, 'castanet/proxy_ticket_error'
5
+ autoload :Responses, 'castanet/responses'
6
+ autoload :QueryBuilding, 'castanet/query_building'
7
+ autoload :ServiceTicket, 'castanet/service_ticket'
8
+ end
@@ -0,0 +1,207 @@
1
+ require 'castanet'
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module Castanet
7
+ ##
8
+ # A CAS client.
9
+ #
10
+ # Expected interface
11
+ # ==================
12
+ #
13
+ # Classes that mix in this module must define the method
14
+ #
15
+ # cas_url => String
16
+ #
17
+ # `cas_url` defines the base URL of the CAS server and must have a terminating /.
18
+ #
19
+ # If CAS proxying is desired, classes must further define
20
+ #
21
+ # proxy_callback_url => String
22
+ # proxy_retrieval_url => String
23
+ #
24
+ # `proxy_callback_url` is a URL of a service that will be used by the CAS
25
+ # server for depositing PGTs. (In the CAS protocol, it's the URL passed to
26
+ # `/serviceValidate` in the `pgtIou` parameter.)
27
+ #
28
+ # `proxy_retrieval_url` is a URL of a service that will be used to retrieve
29
+ # deposited PGTs.
30
+ #
31
+ #
32
+ # Security requirements
33
+ # =====================
34
+ #
35
+ # Section 2.5.4 of the CAS 2.0 protocol mandates that the proxy callback
36
+ # service pointed to by `proxy_callback_url` must
37
+ #
38
+ # 1. be accessible over HTTPS and
39
+ # 2. present an SSL certificate that
40
+ # 1. is valid and
41
+ # 2. has a canonical name that matches that of the proxy callback service.
42
+ #
43
+ # Secure channels are not required for any other part of the CAS protocol,
44
+ # but we still recommend using HTTPS for all communication involving any
45
+ # permutation of interactions between the CAS server, the user, and the
46
+ # application.
47
+ #
48
+ # Because of this ambiguity in the CAS protocol -- and because unencrypted
49
+ # transmission can be useful in isolated development environments -- Castanet
50
+ # will permit non-HTTPS communication with CAS servers. However, you must
51
+ # explicitly declare your intent in the class using this client by defining
52
+ # {#https_disabled} equal to `true`:
53
+ #
54
+ # class InsecureClient
55
+ # include Castanet::Client
56
+ #
57
+ # def https_disabled
58
+ # true
59
+ # end
60
+ # end
61
+ #
62
+ # Also keep in mind that future revisions of Castanet may remove this option.
63
+ #
64
+ # @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.5.4
65
+ # @see http://www.daemonology.net/blog/2009-09-04-complexity-is-insecurity.html
66
+ # "Complexity is insecurity" by Colin Percival
67
+ #
68
+ # Examples
69
+ # ========
70
+ #
71
+ # Presenting a service ticket
72
+ # ---------------------------
73
+ #
74
+ # ticket = service_ticket('ST-1foo', 'https://service.example.edu')
75
+ # ticket.present!
76
+ #
77
+ # ticket.ok? # => true or false
78
+ #
79
+ #
80
+ # Retrieving a proxy-granting ticket
81
+ # ----------------------------------
82
+ #
83
+ # ticket = service_ticket(...)
84
+ # ticket.present!
85
+ # ticket.retrieve_pgt! # PGT can be retrieved from ticket.pgt
86
+ #
87
+ #
88
+ # Requesting a proxy ticket
89
+ # -------------------------
90
+ #
91
+ # ticket = proxy_ticket(pgt, service) # returns a ProxyTicket
92
+ #
93
+ # {ProxyTicket}s can be coerced into Strings.
94
+ #
95
+ #
96
+ # Validating a proxy ticket
97
+ # -------------------------
98
+ #
99
+ # ticket = proxy_ticket(pgt, service)
100
+ # ticket.present!
101
+ #
102
+ # ticket.ok? # => true or false
103
+ #
104
+ #
105
+ # @see http://www.jasig.org/cas/protocol CAS 2.0 protocol
106
+ module Client
107
+ ##
108
+ # Whether or not to disable HTTPS for CAS server communication. Defaults
109
+ # to false.
110
+ #
111
+ # @return [false]
112
+ def https_disabled
113
+ false
114
+ end
115
+
116
+ ##
117
+ # Returns the service ticket validation endpoint for the configured CAS URL.
118
+ #
119
+ # The service ticket validation endpoint is defined as `cas_url` +
120
+ # `"/serviceValidate"`.
121
+ #
122
+ # @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.5
123
+ # @see #cas_url
124
+ # @return [String]
125
+ def service_validate_url
126
+ URI.join(cas_url, 'serviceValidate').to_s
127
+ end
128
+
129
+ ##
130
+ # Returns the proxy ticket grantor endpoint for the configured CAS URL.
131
+ #
132
+ # @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.7
133
+ # @see #cas_url
134
+ # @return [String]
135
+ def proxy_url
136
+ URI.join(cas_url, 'proxy').to_s
137
+ end
138
+
139
+ ##
140
+ # Returns the proxy ticket validation endpoint for the configured CAS URL.
141
+ #
142
+ # @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.6
143
+ # @see #cas_url
144
+ # @return [String]
145
+ def proxy_validate_url
146
+ URI.join(cas_url, 'proxyValidate').to_s
147
+ end
148
+
149
+ ##
150
+ # Prepares a {ServiceTicket} for the ticket `ticket` and the service URL
151
+ # `service`.
152
+ #
153
+ # The prepared {ServiceTicket} can be presented for validation at a later
154
+ # time.
155
+ #
156
+ # @param [String] ticket text of a service ticket
157
+ # @param [String] service a service URL
158
+ # @return [ServiceTicket]
159
+ def service_ticket(ticket, service)
160
+ ServiceTicket.new(ticket, service).tap do |st|
161
+ st.https_disabled = https_disabled
162
+ st.proxy_callback_url = proxy_callback_url
163
+ st.proxy_retrieval_url = proxy_retrieval_url
164
+ st.service_validate_url = service_validate_url
165
+ end
166
+ end
167
+
168
+ ##
169
+ # Given the PGT `pgt`, retrieves a proxy ticket for the service URL
170
+ # `service`.
171
+ #
172
+ # If a proxy ticket cannot be issued for any reason, this method raises a
173
+ # {ProxyTicketError} containing the failure code and reason returned by the
174
+ # CAS server.
175
+ #
176
+ # @see http://www.jasig.org/cas/protocol CAS 2.0 protocol, section 2.7
177
+ # @see {ProxyTicket#reify!}
178
+ # @raise [ProxyTicketError]
179
+ # @return [ProxyTicket] the issued proxy ticket
180
+ def issue_proxy_ticket(pgt, service)
181
+ ProxyTicket.new(nil, pgt, service).tap do |pt|
182
+ pt.https_disabled = https_disabled
183
+ pt.proxy_url = proxy_url
184
+ pt.proxy_validate_url = proxy_validate_url
185
+ end.reify!
186
+ end
187
+
188
+ ##
189
+ # Builds a {ProxyTicket} for the proxy ticket `pt` and service URL `service`.
190
+ #
191
+ # The returned {ProxyTicket} instance can be used to validate `pt` for
192
+ # `service` using `#present!`.
193
+ #
194
+ # @param [String, ProxyTicket] ticket the proxy ticket
195
+ # @param [String] service the service URL
196
+ # @return [ProxyTicket]
197
+ def proxy_ticket(ticket, service)
198
+ ProxyTicket.new(ticket.to_s, nil, service).tap do |pt|
199
+ pt.https_disabled = https_disabled
200
+ pt.proxy_callback_url = proxy_callback_url
201
+ pt.proxy_retrieval_url = proxy_retrieval_url
202
+ pt.proxy_url = proxy_url
203
+ pt.proxy_validate_url = proxy_validate_url
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,133 @@
1
+ require 'castanet'
2
+
3
+ require 'uri'
4
+
5
+ module Castanet
6
+ class ProxyTicket < ServiceTicket
7
+ ##
8
+ # The URL of the CAS server's proxy ticket granting service.
9
+ #
10
+ # @return [String]
11
+ attr_accessor :proxy_url
12
+
13
+ ##
14
+ # The URL of the CAS server's proxy ticket validation service.
15
+ #
16
+ # @return [String]
17
+ attr_accessor :proxy_validate_url
18
+
19
+ ##
20
+ # The `/proxy` response from the CAS server.
21
+ #
22
+ # This is set by {#reify!}, but can be set manually for testing purposes.
23
+ #
24
+ # @return [#ticket]
25
+ attr_accessor :proxy_response
26
+
27
+ def_delegator :proxy_response, :ok?, :issued?
28
+
29
+ def_delegators :proxy_response, :failure_code, :failure_reason
30
+
31
+ ##
32
+ # Initializes an instance of ProxyTicket.
33
+ #
34
+ # Instantiation guide
35
+ # ===================
36
+ #
37
+ # 1. If requesting a proxy ticket, set `pt` to nil, `service` to the
38
+ # service URL, and `pgt` to the proxy granting ticket.
39
+ # 2. If checking a proxy ticket, set `pt` to the proxy ticket, `service` to
40
+ # the service URL, and `pgt` to nil.
41
+ #
42
+ # @param [String, nil] pt the proxy ticket
43
+ # @param [String, nil] pgt the proxy granting ticket
44
+ # @param [String] service the service URL
45
+ def initialize(pt, pgt, service)
46
+ super(pt, service)
47
+
48
+ self.pgt = pgt
49
+ end
50
+
51
+ ##
52
+ # The proxy ticket wrapped by this object. This can come either from a
53
+ # proxy ticket issuance via {#reify!} or be set at instantiation. Tickets
54
+ # issued via {#reify!} have higher precedence.
55
+ #
56
+ # If a proxy ticket was neither supplied at instantiation nor requested via
57
+ # {#reify!}, then ticket will return nil.
58
+ #
59
+ # @return [String, nil] the proxy ticket
60
+ def ticket
61
+ proxy_response ? proxy_response.ticket : super
62
+ end
63
+
64
+ ##
65
+ # Returns the string representation of {#ticket}.
66
+ #
67
+ # If {#ticket} is not nil, then the return value of this method is
68
+ # {#ticket}; otherwise, it is `""`.
69
+ #
70
+ # @return [String] the ticket or empty string
71
+ def to_s
72
+ ticket.to_s
73
+ end
74
+
75
+ ##
76
+ # Requests a proxy ticket from {#proxy_url} and stores it in {#ticket}.
77
+ #
78
+ # If a proxy ticket cannot be issued for any reason, this method raises a
79
+ # {ProxyTicketError} containing the failure code and reason returned by the
80
+ # CAS server.
81
+ #
82
+ # This method should only be run once per `ProxyTicket` instance. It can be
83
+ # run multiple times, but each invocation will overwrite {#ticket} with a
84
+ # new ticket.
85
+ #
86
+ # This method is automatically called by {Client#proxy_ticket}, and as such
87
+ # should never need to be called by users of Castanet; however, in the
88
+ # interest of program organization, the method is public and located here.
89
+ # Also, if you're managing `ProxyTicket` instances manually for some reason,
90
+ # you may find this method useful.
91
+ #
92
+ # @raise [ProxyTicketError] if a proxy ticket cannot be issued
93
+ # @return void
94
+ def reify!
95
+ uri = URI.parse(proxy_url).tap do |u|
96
+ u.query = grant_parameters
97
+ end
98
+
99
+ http = Net::HTTP.new(uri.host, uri.port).tap do |h|
100
+ h.use_ssl = !https_disabled
101
+ end
102
+
103
+ http.start do |h|
104
+ cas_response = h.get(uri.to_s)
105
+
106
+ self.proxy_response = parsed_proxy_response(cas_response.body)
107
+
108
+ unless issued?
109
+ raise ProxyTicketError, "A proxy ticket could not be issued. Code: <#{failure_code}>, reason: <#{failure_reason}>."
110
+ end
111
+
112
+ self
113
+ end
114
+ end
115
+
116
+ protected
117
+
118
+ ##
119
+ # The URL to use for ticket validation.
120
+ #
121
+ # @return [String]
122
+ def validation_url
123
+ proxy_validate_url
124
+ end
125
+
126
+ private
127
+
128
+ def grant_parameters
129
+ query(['pgt', pgt],
130
+ ['targetService', service])
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,6 @@
1
+ require 'castanet'
2
+
3
+ module Castanet
4
+ class ProxyTicketError < StandardError
5
+ end
6
+ end