t2-server 0.6.1 → 0.9.0

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.
Files changed (52) hide show
  1. data/.rvmrc +1 -0
  2. data/CHANGES.rdoc +48 -0
  3. data/LICENCE.rdoc +2 -2
  4. data/README.rdoc +245 -10
  5. data/Rakefile +108 -0
  6. data/bin/t2-delete-runs +21 -34
  7. data/bin/t2-get-output +134 -0
  8. data/bin/t2-run-workflow +121 -109
  9. data/bin/t2-server-admin +128 -0
  10. data/bin/t2-server-info +25 -38
  11. data/lib/t2-server-cli.rb +116 -0
  12. data/lib/t2-server.rb +16 -27
  13. data/lib/t2-server/admin.rb +147 -0
  14. data/lib/t2-server/connection-parameters.rb +144 -0
  15. data/lib/t2-server/connection.rb +352 -0
  16. data/lib/t2-server/credentials.rb +84 -0
  17. data/lib/t2-server/exceptions.rb +42 -21
  18. data/lib/t2-server/port.rb +472 -0
  19. data/lib/t2-server/run.rb +822 -227
  20. data/lib/t2-server/server.rb +313 -317
  21. data/lib/t2-server/util.rb +71 -0
  22. data/lib/t2-server/xml/libxml.rb +87 -0
  23. data/lib/t2-server/xml/nokogiri.rb +85 -0
  24. data/lib/t2-server/xml/rexml.rb +85 -0
  25. data/lib/t2-server/xml/xml.rb +111 -0
  26. data/lib/t2server.rb +4 -1
  27. data/t2-server.gemspec +112 -0
  28. data/test/tc_admin.rb +63 -0
  29. data/test/{tc_paths.rb → tc_params.rb} +11 -25
  30. data/test/tc_perms.rb +132 -0
  31. data/test/tc_run.rb +200 -67
  32. data/test/tc_secure.rb +191 -0
  33. data/test/tc_server.rb +25 -23
  34. data/test/tc_util.rb +74 -0
  35. data/test/ts_t2server.rb +57 -12
  36. data/test/workflows/always_fail.t2flow +69 -0
  37. data/test/workflows/list_and_value.t2flow +12 -0
  38. data/test/workflows/list_with_errors.t2flow +107 -0
  39. data/test/workflows/secure/basic-http.t2flow +74 -0
  40. data/test/workflows/secure/basic-https.t2flow +74 -0
  41. data/test/workflows/secure/client-https.t2flow +162 -0
  42. data/test/workflows/secure/digest-http.t2flow +129 -0
  43. data/test/workflows/secure/digest-https.t2flow +107 -0
  44. data/test/workflows/secure/heater-pk.pem +20 -0
  45. data/test/workflows/secure/user-cert.p12 +0 -0
  46. data/test/workflows/secure/ws-http.t2flow +180 -0
  47. data/test/workflows/secure/ws-https.t2flow +180 -0
  48. data/test/workflows/strings.txt +10 -0
  49. data/test/workflows/xml_xpath.t2flow +136 -136
  50. data/version.yml +4 -0
  51. metadata +132 -34
  52. data/lib/t2-server/xml.rb +0 -86
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2010-2012 The University of Manchester, UK.
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # * Neither the names of The University of Manchester nor the names of its
16
+ # contributors may be used to endorse or promote products derived from this
17
+ # software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # Author: Robert Haines
32
+
33
+ require 'forwardable'
34
+
35
+ module T2Server
36
+
37
+ # This is the base class for holding parameters for network connections. It
38
+ # delegates most work to the underlying Hash in which options are actually
39
+ # stored.
40
+ #
41
+ # The parameters that can be set are:
42
+ # * :ca_file
43
+ # * :ca_path
44
+ # * :verify_peer
45
+ # * :client_certificate
46
+ # * :client_password
47
+ # All others will be ignored. Any parameters not set will return +nil+ when
48
+ # queried.
49
+ class ConnectionParameters
50
+ # :stopdoc:
51
+ ALLOWED_PARAMS = [
52
+ :ca_file,
53
+ :ca_path,
54
+ :verify_peer,
55
+ :client_certificate,
56
+ :client_password
57
+ ]
58
+ # :startdoc:
59
+
60
+ extend Forwardable
61
+ def_delegators :@params, :[], :to_s, :inspect
62
+
63
+ # Create a new set of connection parameters with no defaults set.
64
+ def initialize
65
+ @params = {}
66
+ end
67
+
68
+ # :call-seq:
69
+ # [param] = value -> value
70
+ #
71
+ # Set a connection parameter. See the list of allowed parameters in the
72
+ # class description.
73
+ def []=(param, value)
74
+ @params[param] = value if ALLOWED_PARAMS.include?(param)
75
+ end
76
+ end
77
+
78
+ # Connection parameters with sensible defaults set for standard connections.
79
+ # If the connection is over SSL then the peer will be verified using the
80
+ # underlying OS's certificate store.
81
+ class DefaultConnectionParameters < ConnectionParameters
82
+ def initialize
83
+ super
84
+ self[:verify_peer] = true
85
+ self[:ca_path] = "/etc/ssl/certs" # need to get good defaults for Win/OSX
86
+ end
87
+ end
88
+
89
+ # Connection parameters that specifically turn off peer verification when
90
+ # using SSL.
91
+ class InsecureSSLConnectionParameters < ConnectionParameters
92
+ def initialize
93
+ super
94
+ self[:verify_peer] = false
95
+ end
96
+ end
97
+
98
+ # Connection parameters that simplify setting up verification of servers with
99
+ # "self-signed" or non-standard certificates.
100
+ class CustomCASSLConnectionParameters < DefaultConnectionParameters
101
+ # :call-seq:
102
+ # new(path) -> CustomCASSLConnectionParameters
103
+ #
104
+ # _path_ can either be a directory where the required certificate is stored
105
+ # or the path to the certificate file itself.
106
+ def initialize(path)
107
+ super
108
+
109
+ case path
110
+ when String
111
+ self[:ca_path] = path if File.directory? path
112
+ self[:ca_file] = path if File.file? path
113
+ when File
114
+ self[:ca_file] = path.path
115
+ when Dir
116
+ self[:ca_path] = path.path
117
+ end
118
+ end
119
+ end
120
+
121
+ # Connection parameters that simplify setting up client authentication to a
122
+ # server over SSL.
123
+ class ClientAuthSSLConnectionParameters < DefaultConnectionParameters
124
+ # :call-seq:
125
+ # new(certificate, password = nil) -> ClientAuthSSLConnectionParameters
126
+ #
127
+ # _certificate_ should point to a file with the client user's certificate
128
+ # and private key. The key will be unlocked with _password_ if it is
129
+ # encrypted. If _password_ is not specified, but needed, then the
130
+ # underlying SSL implementation may ask for it if it can.
131
+ def initialize(cert, password = nil)
132
+ super
133
+
134
+ case cert
135
+ when String
136
+ self[:client_certificate] = cert
137
+ when File
138
+ self[:client_certificate] = cert.path
139
+ end
140
+
141
+ self[:client_password] = password
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,352 @@
1
+ # Copyright (c) 2010-2012 The University of Manchester, UK.
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # * Neither the names of The University of Manchester nor the names of its
16
+ # contributors may be used to endorse or promote products derived from this
17
+ # software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # Author: Robert Haines
32
+
33
+ require 'uri'
34
+ require 'net/https'
35
+
36
+ module T2Server
37
+
38
+ # This is a factory for connections to a Taverna Server. It will return
39
+ # either a http or https connection depending on what sort of uri is passed
40
+ # into it. This class maintains a list of connections that it knows about
41
+ # and will return an already established connection if it can.
42
+ class ConnectionFactory
43
+
44
+ private_class_method :new
45
+
46
+ # list of connections we know about
47
+ @@connections = []
48
+
49
+ # :call-seq:
50
+ # ConnectionFactory.connect(uri) -> Connection
51
+ #
52
+ # Connect to a Taverna Server instance and return either a
53
+ # T2Server::HttpConnection or T2Server::HttpsConnection object to
54
+ # represent it.
55
+ def ConnectionFactory.connect(uri, params = nil)
56
+ # we want to use URIs here
57
+ if !uri.is_a? URI
58
+ raise URI::InvalidURIError.new
59
+ end
60
+
61
+ # if we're given params they must be of the right type
62
+ if !params.nil? and !params.is_a? ConnectionParameters
63
+ raise ArgumentError, "Parameters must be ConnectionParameters", caller
64
+ end
65
+
66
+ # see if we've already got this connection
67
+ conn = @@connections.find {|c| c.uri == uri}
68
+
69
+ if !conn
70
+ if uri.scheme == "http"
71
+ conn = HttpConnection.new(uri, params)
72
+ elsif uri.scheme == "https"
73
+ conn = HttpsConnection.new(uri, params)
74
+ else
75
+ raise URI::InvalidURIError.new
76
+ end
77
+
78
+ @@connections << conn
79
+ end
80
+
81
+ conn
82
+ end
83
+ end
84
+
85
+ # A class representing a http connection to a Taverna Server. This class
86
+ # should only ever be created via the T2Server::Connection factory class.
87
+ class HttpConnection
88
+ # The URI of this connection instance.
89
+ attr_reader :uri
90
+
91
+ # Open a http connection to the Taverna Server at the uri supplied.
92
+ def initialize(uri, params = nil)
93
+ @uri = uri
94
+ @params = params || DefaultConnectionParameters.new
95
+
96
+ # set up http connection
97
+ @http = Net::HTTP.new(@uri.host, @uri.port)
98
+ end
99
+
100
+ # :call-seq:
101
+ # POST_run(path, value, credentials) -> String
102
+ #
103
+ # Initialize a T2Server::Run on a server by uploading its workflow.
104
+ # The new run's identifier (in String form) is returned.
105
+ def POST_run(path, value, credentials)
106
+ response = _POST(path, value, "application/xml", credentials)
107
+
108
+ case response
109
+ when Net::HTTPCreated
110
+ # return the identifier of the newly created run
111
+ path_leaf_from_uri(response['location'])
112
+ when Net::HTTPForbidden
113
+ raise ServerAtCapacityError.new
114
+ when Net::HTTPUnauthorized
115
+ raise AuthorizationError.new(credentials)
116
+ else
117
+ raise UnexpectedServerResponse.new(response)
118
+ end
119
+ end
120
+
121
+ # :call-seq:
122
+ # POST_file(path, value, run, credentials) -> bool
123
+ #
124
+ # Upload a file to a run. If successful, true is returned.
125
+ def POST_file(path, value, run, credentials)
126
+ response = _POST(path, value, "application/xml", credentials)
127
+
128
+ case response
129
+ when Net::HTTPCreated
130
+ # OK, carry on...
131
+ true
132
+ when Net::HTTPNotFound
133
+ raise RunNotFoundError.new(run)
134
+ when Net::HTTPForbidden
135
+ raise AccessForbiddenError.new("run #{run}")
136
+ when Net::HTTPUnauthorized
137
+ raise AuthorizationError.new(credentials)
138
+ else
139
+ raise UnexpectedServerResponse.new(response)
140
+ end
141
+ end
142
+
143
+ # :call-seq:
144
+ # POST_dir(path, value, run, dir, credentials) -> bool
145
+ #
146
+ # Create a directory in the scratch space of a run. If successful, true
147
+ # is returned.
148
+ def POST_dir(path, value, run, dir, credentials)
149
+ response = _POST(path, value, "application/xml", credentials)
150
+
151
+ case response
152
+ when Net::HTTPCreated
153
+ # OK, carry on...
154
+ true
155
+ when Net::HTTPNotFound
156
+ raise RunNotFoundError.new(run)
157
+ when Net::HTTPForbidden
158
+ raise AccessForbiddenError.new("#{dir} on run #{run}")
159
+ when Net::HTTPUnauthorized
160
+ raise AuthorizationError.new(credentials)
161
+ else
162
+ raise UnexpectedServerResponse.new(response)
163
+ end
164
+ end
165
+
166
+ # :call-seq:
167
+ # GET(path, type, range, credentials) -> String
168
+ #
169
+ # HTTP GET a resource at _path_ of _type_ from the server. If successful
170
+ # the body of the response is returned. A portion of the data can be
171
+ # retrieved by specifying a byte range, start..end, with the _range_
172
+ # parameter.
173
+ def GET(path, type, range, credentials)
174
+ get = Net::HTTP::Get.new(path)
175
+ get["Accept"] = type
176
+ get["Range"] = "bytes=#{range.min}-#{range.max}" unless range.nil?
177
+ response = submit(get, nil, credentials)
178
+
179
+ case response
180
+ when Net::HTTPOK, Net::HTTPPartialContent
181
+ return response.body
182
+ when Net::HTTPNoContent
183
+ return nil
184
+ when Net::HTTPMovedTemporarily
185
+ new_conn = redirect(response["location"])
186
+ raise ConnectionRedirectError.new(new_conn)
187
+ when Net::HTTPNotFound
188
+ raise AttributeNotFoundError.new(path)
189
+ when Net::HTTPForbidden
190
+ raise AccessForbiddenError.new("attribute #{path}")
191
+ when Net::HTTPUnauthorized
192
+ raise AuthorizationError.new(credentials)
193
+ else
194
+ raise UnexpectedServerResponse.new(response)
195
+ end
196
+ end
197
+
198
+ # :call-seq:
199
+ # PUT(path, value, type, credentials) -> bool
200
+ #
201
+ # Perform a HTTP PUT of _value_ to a path on the server. If successful
202
+ # true is returned.
203
+ def PUT(path, value, type, credentials)
204
+ put = Net::HTTP::Put.new(path)
205
+ put.content_type = type
206
+ response = submit(put, value, credentials)
207
+
208
+ case response
209
+ when Net::HTTPOK
210
+ # OK, so carry on
211
+ true
212
+ when Net::HTTPNotFound
213
+ raise AttributeNotFoundError.new(path)
214
+ when Net::HTTPForbidden
215
+ raise AccessForbiddenError.new("attribute #{path}")
216
+ when Net::HTTPUnauthorized
217
+ raise AuthorizationError.new(credentials)
218
+ else
219
+ raise UnexpectedServerResponse.new(response)
220
+ end
221
+ end
222
+
223
+ # :call-seq:
224
+ # POST(path, value, type, credentials)
225
+ #
226
+ # Perform an HTTP POST of _value_ to a path on the server and return the
227
+ # identifier of the created attribute as a string.
228
+ def POST(path, value, type, credentials)
229
+ response = _POST(path, value, type, credentials)
230
+
231
+ case response
232
+ when Net::HTTPCreated
233
+ # return the identifier of the newly created item
234
+ path_leaf_from_uri(response['location'])
235
+ when Net::HTTPNotFound
236
+ raise AttributeNotFoundError.new(path)
237
+ when Net::HTTPForbidden
238
+ raise AccessForbiddenError.new("attribute #{path}")
239
+ when Net::HTTPUnauthorized
240
+ raise AuthorizationError.new(credentials)
241
+ else
242
+ raise UnexpectedServerResponse.new(response)
243
+ end
244
+ end
245
+
246
+ # :call-seq:
247
+ # DELETE(path, credentials) -> bool
248
+ #
249
+ # Perform an HTTP DELETE on a path on the server. If successful true
250
+ # is returned.
251
+ def DELETE(path, credentials)
252
+ run = path.split("/")[-1]
253
+ delete = Net::HTTP::Delete.new(path)
254
+ response = submit(delete, nil, credentials)
255
+
256
+ case response
257
+ when Net::HTTPNoContent
258
+ # Success, carry on...
259
+ true
260
+ when Net::HTTPNotFound
261
+ raise RunNotFoundError.new(run)
262
+ when Net::HTTPForbidden
263
+ raise AccessForbiddenError.new("run #{run}")
264
+ when Net::HTTPUnauthorized
265
+ raise AuthorizationError.new(credentials)
266
+ else
267
+ raise UnexpectedServerResponse.new(response)
268
+ end
269
+ end
270
+
271
+ # :call-seq:
272
+ # OPTIONS(path, credentials) -> Hash
273
+ #
274
+ # Perform the HTTP OPTIONS command on the given _path_ and return a hash
275
+ # of the headers returned.
276
+ def OPTIONS(path, credentials)
277
+ options = Net::HTTP::Options.new(path)
278
+ response = submit(options, nil, credentials)
279
+
280
+ case response
281
+ when Net::HTTPOK
282
+ response.to_hash
283
+ when Net::HTTPForbidden
284
+ raise AccessForbiddenError.new("resource #{path}")
285
+ when Net::HTTPUnauthorized
286
+ raise AuthorizationError.new(credentials)
287
+ else
288
+ raise UnexpectedServerResponse.new(response)
289
+ end
290
+ end
291
+
292
+ private
293
+ def _POST(path, value, type, credentials)
294
+ post = Net::HTTP::Post.new(path)
295
+ post.content_type = type
296
+ submit(post, value, credentials)
297
+ end
298
+
299
+ def path_leaf_from_uri(uri)
300
+ URI.parse(uri).path.split('/')[-1]
301
+ end
302
+
303
+ def submit(request, value, credentials)
304
+ credentials.authenticate(request) unless credentials.nil?
305
+
306
+ begin
307
+ @http.request(request, value)
308
+ rescue InternalHTTPError => e
309
+ raise ConnectionError.new(e)
310
+ end
311
+ end
312
+
313
+ def redirect(location)
314
+ uri = URI.parse(location)
315
+ new_uri = URI::HTTP.new(uri.scheme, nil, uri.host, uri.port, nil,
316
+ @uri.path, nil, nil, nil);
317
+ ConnectionFactory.connect(new_uri, @params)
318
+ end
319
+ end
320
+
321
+ # A class representing a https connection to a Taverna Server. This class
322
+ # should only ever be created via the T2Server::Connection factory class.
323
+ class HttpsConnection < HttpConnection
324
+
325
+ # Open a https connection to the Taverna Server at the uri supplied.
326
+ def initialize(uri, params = nil)
327
+ super(uri, params)
328
+
329
+ # Configure connection options using params
330
+ @http.use_ssl = true
331
+
332
+ # Peer verification
333
+ if @params[:verify_peer]
334
+ if @params[:ca_file]
335
+ @http.ca_file = @params[:ca_file]
336
+ else
337
+ @http.ca_path = @params[:ca_path]
338
+ end
339
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
340
+ else
341
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
342
+ end
343
+
344
+ # Client authentication
345
+ if @params[:client_certificate]
346
+ pem = File.read(@params[:client_certificate])
347
+ @http.cert = OpenSSL::X509::Certificate.new(pem)
348
+ @http.key = OpenSSL::PKey::RSA.new(pem, @params[:client_password])
349
+ end
350
+ end
351
+ end
352
+ end