t2-server 0.6.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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