Package myproxy :: Package ws :: Package server :: Package wsgi :: Module middleware
[hide private]

Source Code for Module myproxy.ws.server.wsgi.middleware

  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 
27 28 29 -class MyProxyLogonWSMiddlewareError(Exception):
30 """Errors related to the MyProxy Web Service middleware"""
31
32 33 -class MyProxyLogonWSMiddleware(MyProxyClientMiddleware):
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 # Options for ini file 62 LOGON_FUNC_ENV_KEYNAME_OPTNAME = 'logonFuncEnvKeyName' 63 64 # Default environ key names 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
75 - def __init__(self, app):
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
84 - def parseConfig(self, prefix=PARAM_PREFIX, myProxyClientPrefix=None, 85 **app_conf):
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 # Call parent version 100 super(MyProxyLogonWSMiddleware, self).parseConfig(prefix=prefix, 101 myProxyClientPrefix=myProxyClientPrefix, **app_conf) 102 103 # Extract addtional parameters 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
111 - def logonFuncEnvironKeyName(self):
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
120 - def logonFuncEnvironKeyName(self, value):
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
147 - def myProxyLogon(self):
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 # Extract cert request and convert to standard string - SSL library 179 # will not accept unicode 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 # Expecting PEM encoded request 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 # Convert to ASN1 format expect by logon client call 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 # Trigger 500 Internal Server Error
231 232 return _myProxylogon
233
234 235 -class MyProxyGetTrustRootsMiddlewareError(Exception):
236 """MyProxyGetTrustRootsMiddleware exception class"""
237
238 239 -class MyProxyGetTrustRootsMiddleware(MyProxyClientMiddlewareBase):
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 # Option prefixes 263 PARAM_PREFIX = 'myproxy.getTrustRoots.' 264 265 __slots__ = ( 266 '__path', 267 ) 268
269 - def __init__(self, app):
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
279 - def filter_app_factory(cls, app, global_conf, prefix=PARAM_PREFIX, 280 **app_conf):
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
300 - def parseConfig(self, prefix=PARAM_PREFIX, **app_conf):
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
318 - def _getPath(self):
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
325 - def _setPath(self, value):
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 # Skip if path doesn't match 355 if environ['PATH_INFO'] != self.path: 356 return self.app(environ, start_response) 357 358 log.debug("MyProxyGetTrustRootsMiddleware.__call__ ...") 359 360 # Check method 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
386 - def _getTrustRoots(cls, myProxyClient):
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 # Serialise dict response 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 # Trigger 500 Internal Server Error
428