1 """MyProxy Web Service WSGI middleware - exposes MyProxy logon and get trust
2 roots as web service methods
3
4 NERC MashMyData Project
5 """
6 __author__ = "P J Kershaw"
7 __date__ = "24/05/10"
8 __copyright__ = "(C) 2010 Science and Technology Facilities Council"
9 __license__ = "BSD - see LICENSE file in top-level directory"
10 __contact__ = "Philip.Kershaw@stfc.ac.uk"
11 __revision__ = "$Id: $"
12 import logging
13 log = logging.getLogger(__name__)
14
15 import httplib
16 import socket
17 import base64
18 import traceback
19
20 from webob import Request
21 from OpenSSL import crypto
22
23 from myproxy.client import MyProxyClient, MyProxyClientError
24 from myproxy.server.wsgi.middleware import (MyProxyClientMiddlewareBase,
25 MyProxyClientMiddleware)
26 from myproxy.ws.server.wsgi.httpbasicauth import HttpBasicAuthResponseException
30 """Errors related to the MyProxy Web Service middleware"""
31
34 """Build on MyClientMiddleware to expose a special logon Web Service method
35
36 TODO: possible refactor to NOT inherit from MyProxyClientMiddleware but
37 instead receive a MyProxyClient instance via environ set from an upstream
38 MyProxyClientMiddleware object
39
40 @cvar LOGON_FUNC_ENV_KEYNAME_OPTNAME: ini file option name to set the key
41 name in WSGI environ dict to assign to the Logon function created by this
42 middleware
43 @type LOGON_FUNC_ENV_KEYNAME_OPTNAME: string
44
45 @cvar DEFAULT_LOGON_FUNC_ENV_KEYNAME: default value for the key name in
46 WSGI environ dict to assign to the Logon function created by this
47 middleware
48 @type DEFAULT_LOGON_FUNC_ENV_KEYNAME: string
49
50 @cvar CERT_REQ_POST_PARAM_KEYNAME: HTTP POST field name for the
51 certificate request posted in logon calls
52 @type CERT_REQ_POST_PARAM_KEYNAME: string
53
54 @ivar __logonFuncEnvironKeyName:
55 @type __logonFuncEnvironKeyName: string
56
57 @cvar PARAM_PREFIX: prefix for ini file option names
58 @type PARAM_PREFIX: string
59 """
60
61
62 LOGON_FUNC_ENV_KEYNAME_OPTNAME = 'logonFuncEnvKeyName'
63
64
65 DEFAULT_LOGON_FUNC_ENV_KEYNAME = ('myproxy.server.wsgi.middleware.'
66 'MyProxyClientMiddleware.logon')
67
68 CERT_REQ_POST_PARAM_KEYNAME = 'certificate_request'
69
70 __slots__ = (
71 '__logonFuncEnvironKeyName',
72 )
73 PARAM_PREFIX = 'myproxy.ws.server.logon.'
74
76 '''Create attributes
77
78 @type app: function
79 @param app: WSGI callable for next application in stack
80 '''
81 super(MyProxyLogonWSMiddleware, self).__init__(app)
82 self.__logonFuncEnvironKeyName = None
83
86 """Parse dictionary of configuration items updating the relevant
87 attributes of this instance
88
89 @type prefix: basestring
90 @param prefix: prefix for configuration items
91 @type myProxyClientPrefix: basestring
92 @param myProxyClientPrefix: explicit prefix for MyProxyClient class
93 specific configuration items - ignored in this derived method
94 @type app_conf: dict
95 @param app_conf: PasteDeploy application specific configuration
96 dictionary
97 """
98
99
100 super(MyProxyLogonWSMiddleware, self).parseConfig(prefix=prefix,
101 myProxyClientPrefix=myProxyClientPrefix, **app_conf)
102
103
104 logonFuncEnvKeyOptName = prefix + \
105 self.__class__.LOGON_FUNC_ENV_KEYNAME_OPTNAME
106
107 self.logonFuncEnvironKeyName = app_conf.get(logonFuncEnvKeyOptName,
108 self.__class__.DEFAULT_LOGON_FUNC_ENV_KEYNAME)
109
110 @property
112 """Get MyProxyClient logon function environ key name
113
114 @rtype: basestring
115 @return: MyProxyClient logon function environ key name
116 """
117 return self.__logonFuncEnvironKeyName
118
119 @logonFuncEnvironKeyName.setter
121 """Set MyProxyClient environ key name
122
123 @type value: basestring
124 @param value: MyProxyClient logon function environ key name
125 """
126 if not isinstance(value, basestring):
127 raise TypeError('Expecting string type for '
128 '"logonFuncEnvironKeyName"; got %r type' %
129 type(value))
130 self.__logonFuncEnvironKeyName = value
131
132 - def __call__(self, environ, start_response):
133 '''Set MyProxy logon method in environ
134
135 @type environ: dict
136 @param environ: WSGI environment variables dictionary
137 @type start_response: function
138 @param start_response: standard WSGI start response function
139 '''
140 log.debug("MyProxyClientMiddleware.__call__ ...")
141 environ[self.logonFuncEnvironKeyName] = self.myProxyLogon
142
143 return super(MyProxyLogonWSMiddleware, self).__call__(environ,
144 start_response)
145
146 @property
148 """Return the MyProxy logon method wrapped as a HTTP Basic Auth
149 authenticate interface function
150
151 @rtype: function
152 @return: MyProxy logon HTTP Basic Auth Callback
153 """
154 def _myProxylogon(environ, start_response, username, password):
155 """Wrap MyProxy logon method as a WSGI app
156 @type environ: dict
157 @param environ: WSGI environment variables dictionary
158 @type start_response: function
159 @param start_response: standard WSGI start response function
160 @type username: basestring
161 @param username: username credential to MyProxy logon
162 @type password: basestring
163 @param password: pass-phrase for MyProxy logon call
164 @raise HttpBasicAuthResponseException: invalid client request
165 @raise MyProxyClientMiddlewareError: socket error for backend
166 MyProxy server
167 """
168 request = Request(environ)
169
170 requestMethod = environ.get('REQUEST_METHOD')
171 if requestMethod != 'POST':
172 response = "HTTP Request method not recognised"
173 log.error("HTTP Request method %r not recognised",
174 requestMethod)
175 raise HttpBasicAuthResponseException(response,
176 httplib.METHOD_NOT_ALLOWED)
177
178
179
180 cert_req_key = self.__class__.CERT_REQ_POST_PARAM_KEYNAME
181 pem_cert_req = str(request.POST.get(cert_req_key))
182 if pem_cert_req is None:
183 response = ("No %r form variable set in POST message" %
184 cert_req_key)
185 log.error(response)
186 raise HttpBasicAuthResponseException(response,
187 httplib.BAD_REQUEST)
188
189 log.debug("cert req = %r", pem_cert_req)
190
191
192 try:
193 cert_req = crypto.load_certificate_request(crypto.FILETYPE_PEM,
194 pem_cert_req)
195 except crypto.Error, e:
196 log.error("Error loading input certificate request: %r",
197 pem_cert_req)
198 raise HttpBasicAuthResponseException("Error loading input "
199 "certificate request",
200 httplib.BAD_REQUEST)
201
202
203 asn1CertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1,
204 cert_req)
205
206 try:
207 credentials = self.myProxyClient.logon(username,
208 password,
209 certReq=asn1CertReq)
210 status = self.getStatusMessage(httplib.OK)
211 response = '\n'.join(credentials)
212
213 start_response(status,
214 [('Content-length', str(len(response))),
215 ('Content-type', 'text/plain')])
216 return [response]
217
218 except MyProxyClientError, e:
219 raise HttpBasicAuthResponseException(str(e),
220 httplib.UNAUTHORIZED)
221 except socket.error, e:
222 raise MyProxyLogonWSMiddlewareError("Socket error "
223 "with MyProxy server %r: %s" %
224 (self.myProxyClient.hostname, e))
225 except Exception, e:
226 log.error("MyProxyClient.logon raised an unknown exception "
227 "calling %r: %s",
228 self.myProxyClient.hostname,
229 traceback.format_exc())
230 raise
231
232 return _myProxylogon
233
236 """MyProxyGetTrustRootsMiddleware exception class"""
237
240 """HTTP client interface for MyProxy server Get Trust Roots method
241
242 It relies on a myproxy.server.wsgi.MyProxyClientMiddleware instance called
243 upstream in the WSGI stack to set up a MyProxyClient instance and make it
244 available in the environ to call its getTrustRoots method.
245
246 @cvar PATH_OPTNAME: ini file option to set the URI path for this service
247 @type PATH_OPTNAME: string
248
249 @cvar DEFAULT_PATH: default URI path setting
250 @type DEFAULT_PATH: string
251
252 @cvar PARAM_PREFIX: prefix for ini file option names
253 @type PARAM_PREFIX: string
254
255 @ivar __path: URI path setting for this service
256 @type __path: basestring
257 """
258
259 PATH_OPTNAME = 'path'
260 DEFAULT_PATH = '/myproxy/get-trustroots'
261
262
263 PARAM_PREFIX = 'myproxy.getTrustRoots.'
264
265 __slots__ = (
266 '__path',
267 )
268
270 '''Create attributes
271
272 @type app: function
273 @param app: WSGI callable for next application in stack
274 '''
275 super(MyProxyGetTrustRootsMiddleware, self).__init__(app)
276 self.__path = None
277
278 @classmethod
281 """Function following Paste filter app factory signature
282
283 @type app: callable following WSGI interface
284 @param app: next middleware/application in the chain
285 @type global_conf: dict
286 @param global_conf: PasteDeploy global configuration dictionary
287 @type prefix: basestring
288 @param prefix: prefix for configuration items
289 @type app_conf: dict
290 @param app_conf: PasteDeploy application specific configuration
291 dictionary
292
293 @rtype: myproxy.server.wsgi.middleware.MyProxyGetTrustRootsMiddleware
294 @return: an instance of this middleware
295 """
296 app = cls(app)
297 app.parseConfig(prefix=prefix, **app_conf)
298 return app
299
301 """Parse dictionary of configuration items updating the relevant
302 attributes of this instance
303
304 @type prefix: basestring
305 @param prefix: prefix for configuration items
306 @type app_conf: dict
307 @param app_conf: PasteDeploy application specific configuration
308 dictionary
309 """
310 clientEnvKeyOptName = prefix + self.__class__.CLIENT_ENV_KEYNAME_OPTNAME
311
312 self.clientEnvironKeyName = app_conf.get(clientEnvKeyOptName,
313 self.__class__.DEFAULT_CLIENT_ENV_KEYNAME)
314
315 pathOptName = prefix + self.__class__.PATH_OPTNAME
316 self.path = app_conf.get(pathOptName, self.__class__.DEFAULT_PATH)
317
319 """Get URI path for get trust roots method
320 @rtype: basestring
321 @return: path for get trust roots method
322 """
323 return self.__path
324
326 """Set URI path for get trust roots method
327 @type value: basestring
328 @param value: path for get trust roots method
329 """
330 if not isinstance(value, basestring):
331 raise TypeError('Expecting string type for "path"; got %r' %
332 type(value))
333
334 self.__path = value
335
336 path = property(fget=_getPath, fset=_setPath,
337 doc="environ SCRIPT_NAME path which invokes the "
338 "getTrustRoots method on this middleware")
339
340 - def __call__(self, environ, start_response):
341 '''Get MyProxyClient instance from environ and call MyProxy
342 getTrustRoots method returning the response.
343
344 MyProxyClientMiddleware must be in place upstream in the WSGI stack
345
346 @type environ: dict
347 @param environ: WSGI environment variables dictionary
348 @type start_response: function
349 @param start_response: standard WSGI start response function
350
351 @rtype: list
352 @return: get trust roots response
353 '''
354
355 if environ['PATH_INFO'] != self.path:
356 return self.app(environ, start_response)
357
358 log.debug("MyProxyGetTrustRootsMiddleware.__call__ ...")
359
360
361 requestMethod = environ.get('REQUEST_METHOD')
362 if requestMethod != 'GET':
363 response = "HTTP Request method not recognised"
364 log.error("HTTP Request method %r not recognised", requestMethod)
365 status = self.__class__.getStatusMessage(httplib.BAD_REQUEST)
366 start_response(status,
367 [('Content-type', 'text/plain'),
368 ('Content-length', str(len(response)))])
369 return [response]
370
371 myProxyClient = environ[self.clientEnvironKeyName]
372 if not isinstance(myProxyClient, MyProxyClient):
373 raise TypeError('Expecting %r type for "myProxyClient" environ[%r] '
374 'attribute got %r' % (MyProxyClient,
375 self.clientEnvironKeyName,
376 type(myProxyClient)))
377
378 response = self._getTrustRoots(myProxyClient)
379 start_response(self.getStatusMessage(httplib.OK),
380 [('Content-length', str(len(response))),
381 ('Content-type', 'text/plain')])
382
383 return [response]
384
385 @classmethod
387 """Call getTrustRoots method on MyProxyClient instance retrieved from
388 environ and format and return a HTTP response
389
390 @type myProxyClient: myproxy.client.MyProxyClient
391 @param myProxyClient: MyProxyClient instance on which to call
392 getTrustRoots method
393
394 @rtype: basestring
395 @return: trust roots base64 encoded and concatenated together
396 @raise MyProxyGetTrustRootsMiddlewareError: socket error with backend
397 MyProxy server
398 @raise MyProxyClientError: error response received by MyProxyClient
399 instance
400 """
401 try:
402 trustRoots = myProxyClient.getTrustRoots()
403
404
405 response = "\n".join(["%s=%s" % (k, base64.b64encode(v))
406 for k,v in trustRoots.items()])
407
408 return response
409
410 except MyProxyClientError, e:
411 log.error("MyProxyClient.getTrustRoots raised an "
412 "MyProxyClientError exception calling %r: %s",
413 myProxyClient.hostname,
414 traceback.format_exc())
415 raise
416
417 except socket.error, e:
418 raise MyProxyGetTrustRootsMiddlewareError("Socket error with "
419 "MyProxy server %r: %s" %
420 (myProxyClient.hostname,
421 e))
422 except Exception, e:
423 log.error("MyProxyClient.getTrustRoots raised an unknown exception "
424 "calling %r: %s",
425 myProxyClient.hostname,
426 traceback.format_exc())
427 raise
428