source: TI12-security/trunk/MyProxyWebService/myproxy/server/wsgi/middleware.py @ 6943

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/MyProxyWebService/myproxy/server/wsgi/middleware.py@6943
Revision 6943, 19.2 KB checked in by pjkersha, 9 years ago (diff)

Incomplete - task 5: MyProxy? Logon HTTPS Interface

  • Working myproxy-ws-get-trustroots.sh http client shell script.
Line 
1"""MyProxy WSGI middleware - places a MyProxy client instance in environ for
2other downstream middleware or apps to access and use
3 
4NERC DataGrid 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: $"
12import logging
13log = logging.getLogger(__name__)
14import traceback
15import socket
16import httplib
17import base64
18
19from webob import Request
20from OpenSSL import crypto
21
22from myproxy.client import MyProxyClient, MyProxyClientError
23from myproxy.server.wsgi.httpbasicauth import HttpBasicAuthResponseException
24 
25
26class MyProxyClientMiddlewareError(Exception):
27    """Runtime error with MyProxyClientMiddleware"""
28
29       
30class MyProxyClientMiddlewareConfigError(MyProxyClientMiddlewareError):
31    """Configuration error with MyProxyClientMiddleware"""
32
33
34class MyProxyClientMiddlewareBase(object):
35    """Base class for common functionality"""
36    __slots__ = ('__app', '__clientEnvironKeyName',)
37   
38    CLIENT_ENV_KEYNAME_OPTNAME = 'clientEnvKeyName'
39    DEFAULT_CLIENT_ENV_KEYNAME = ('myproxy.server.wsgi.middleware.'
40                                  'MyProxyClientMiddleware.myProxyClient')
41       
42    def __init__(self, app):
43        self.__app = app
44        self.__clientEnvironKeyName = None
45   
46    def _getClientEnvironKeyName(self):
47        """Get MyProxyClient environ key name
48       
49        @rtype: basestring
50        @return: MyProxyClient environ key name
51        """
52        return self.__clientEnvironKeyName
53
54    def _setClientEnvironKeyName(self, value):
55        """Set MyProxyClient environ key name
56       
57        @type value: basestring
58        @param value: MyProxyClient environ key name
59        """
60        if not isinstance(value, basestring):
61            raise TypeError('Expecting string type for "clientEnvironKeyName"; '
62                            'got %r type' % type(value))
63        self.__clientEnvironKeyName = value
64
65    clientEnvironKeyName = property(fget=_getClientEnvironKeyName, 
66                                    fset=_setClientEnvironKeyName, 
67                                    doc="key name in environ for the "
68                                        "MyProxyClient instance") 
69   
70    @property
71    def app(self):
72        """Get Property method for reference to next WSGI application in call
73        stack
74        @rtype: function
75        @return: WSGI application
76        """
77        return self.__app
78   
79    @staticmethod
80    def getStatusMessage(statusCode):
81        '''Make a standard status message for use with start_response
82        @type statusCode: int
83        @param statusCode: HTTP status code
84        @rtype: str
85        @return: status code with standard message
86        @raise KeyError: for invalid status code
87        '''
88        return '%d %s' % (statusCode, httplib.responses[statusCode])
89       
90   
91class MyProxyClientMiddleware(MyProxyClientMiddlewareBase):
92    '''
93    Create a MyProxy client and make it available to other middleware in the
94    WSGI stack
95    '''
96    # Options for ini file
97    LOGON_FUNC_ENV_KEYNAME_OPTNAME = 'logonFuncEnvKeyName'     
98   
99    # Default environ key names
100    DEFAULT_LOGON_FUNC_ENV_KEYNAME = ('myproxy.server.wsgi.middleware.'
101                                      'MyProxyClientMiddleware.logon')
102   
103    CERT_REQ_POST_PARAM_KEYNAME = 'certificate_request'
104   
105    # Option prefixes
106    PARAM_PREFIX = 'myproxy.'
107    MYPROXY_CLIENT_PARAM_PREFIX = 'client.'
108   
109    __slots__ = (
110        '__app',
111        '__myProxyClient', 
112        '__logonFuncEnvironKeyName',
113    )
114   
115    def __init__(self, app):
116        '''Create attributes
117       
118        @type app: function
119        @param app: WSGI callable for next application in stack
120        '''
121        super(MyProxyClientMiddleware, self).__init__(app)
122        self.__myProxyClient = None
123        self.__logonFuncEnvironKeyName = None
124
125    @classmethod
126    def filter_app_factory(cls, app, global_conf, 
127                           prefix=PARAM_PREFIX, 
128                           myProxyClientPrefix=MYPROXY_CLIENT_PARAM_PREFIX, 
129                           **app_conf):
130        """Function following Paste filter app factory signature
131       
132        @type app: callable following WSGI interface
133        @param app: next middleware/application in the chain     
134        @type global_conf: dict       
135        @param global_conf: PasteDeploy global configuration dictionary
136        @type prefix: basestring
137        @param prefix: prefix for configuration items
138        @type app_conf: dict       
139        @param app_conf: PasteDeploy application specific configuration
140        dictionary
141        """
142        app = cls(app)
143        app.parseConfig(prefix=prefix, myProxyClientPrefix=myProxyClientPrefix,
144                        **app_conf)
145        return app
146   
147    def parseConfig(self, prefix=PARAM_PREFIX, myProxyClientPrefix='',
148                    **app_conf):
149        """Parse dictionary of configuration items updating the relevant
150        attributes of this instance
151       
152        @type prefix: basestring
153        @param prefix: prefix for configuration items
154        @type prefix: basestring
155        @param prefix: explicit prefix for MyProxyClient class specific
156        configuration items
157        @type app_conf: dict       
158        @param app_conf: PasteDeploy application specific configuration
159        dictionary
160        """
161       
162        # Get MyProxyClient initialisation parameters
163        myProxyClientFullPrefix = prefix + myProxyClientPrefix
164                           
165        myProxyClientKw = dict([(k.replace(myProxyClientFullPrefix, ''), v) 
166                                 for k,v in app_conf.items() 
167                                 if k.startswith(myProxyClientFullPrefix)])
168       
169        self.myProxyClient = MyProxyClient(**myProxyClientKw)
170        clientEnvKeyOptName = prefix + \
171                            MyProxyClientMiddleware.CLIENT_ENV_KEYNAME_OPTNAME
172                   
173        self.clientEnvironKeyName = app_conf.get(clientEnvKeyOptName,
174                            MyProxyClientMiddleware.DEFAULT_CLIENT_ENV_KEYNAME)
175                   
176        logonFuncEnvKeyOptName = prefix + \
177                        MyProxyClientMiddleware.LOGON_FUNC_ENV_KEYNAME_OPTNAME
178
179        self.logonFuncEnvironKeyName = app_conf.get(logonFuncEnvKeyOptName,
180                        MyProxyClientMiddleware.DEFAULT_LOGON_FUNC_ENV_KEYNAME)
181
182    def _getLogonFuncEnvironKeyName(self):
183        """Get MyProxyClient logon function environ key name
184       
185        @rtype: basestring
186        @return: MyProxyClient logon function environ key name
187        """
188        return self.__logonFuncEnvironKeyName
189
190    def _setLogonFuncEnvironKeyName(self, value):
191        """Set MyProxyClient environ key name
192       
193        @type value: basestring
194        @param value: MyProxyClient logon function environ key name
195        """
196        if not isinstance(value, basestring):
197            raise TypeError('Expecting string type for '
198                            '"logonFuncEnvironKeyName"; got %r type' % 
199                            type(value))
200        self.__logonFuncEnvironKeyName = value
201
202    logonFuncEnvironKeyName = property(fget=_getLogonFuncEnvironKeyName, 
203                                       fset=_setLogonFuncEnvironKeyName, 
204                                       doc="key name in environ for the "
205                                           "MyProxy logon function")
206   
207    def _getMyProxyClient(self):
208        """Get MyProxyClient instance
209       
210        @rtype: myproxy.client.MyProxyClient
211        @return: MyProxyClient instance
212        """
213        return self.__myProxyClient
214
215    def _setMyProxyClient(self, value):
216        """Set MyProxyClient instance
217       
218        @type value: myproxy.client.MyProxyClient
219        @param value: MyProxyClient instance
220        """
221        if not isinstance(value, MyProxyClient):
222            raise TypeError('Expecting %r type for "myProxyClient" attribute '
223                            'got %r' % (MyProxyClient, type(value)))
224        self.__myProxyClient = value
225       
226    myProxyClient = property(fget=_getMyProxyClient,
227                             fset=_setMyProxyClient, 
228                             doc="MyProxyClient instance used to convert HTTPS"
229                                 " call into a call to a MyProxy server")
230
231    def __call__(self, environ, start_response):
232        '''Set MyProxyClient instance and MyProxy logon method in environ
233       
234        @type environ: dict
235        @param environ: WSGI environment variables dictionary
236        @type start_response: function
237        @param start_response: standard WSGI start response function
238        '''
239        log.debug("MyProxyClientMiddleware.__call__ ...")
240        environ[self.clientEnvironKeyName] = self.myProxyClient
241        environ[self.logonFuncEnvironKeyName] = self.myProxyLogon
242       
243        return self.app(environ, start_response)
244   
245    @property
246    def myProxyLogon(self):
247        """Return the MyProxy logon method wrapped as a HTTP Basic Auth
248        authenticate interface function
249        """
250        def _myProxylogon(environ, start_response, username, password):
251            """Wrap MyProxy logon method as a WSGI app
252            """ 
253            requestMethod = environ.get('REQUEST_METHOD')             
254            if requestMethod != 'POST':
255                response = "HTTP Request method not recognised"
256                log.error("HTTP Request method %r not recognised", 
257                          requestMethod)
258                raise HttpBasicAuthResponseException(response, 
259                                                     httplib.METHOD_NOT_ALLOWED)
260           
261            request = Request(environ)
262            certReqKey = self.__class__.CERT_REQ_POST_PARAM_KEYNAME
263            pemCertReq = request.POST.get(certReqKey)
264            if pemCertReq is None:
265                response = "No %r form variable set" % certReqKey
266                log.error(response)
267                raise HttpBasicAuthResponseException(response, 
268                                                     httplib.BAD_REQUEST)
269            log.debug("cert req = %r", pemCertReq)
270           
271            # Expecting PEM encoded request
272            try:
273                certReq = crypto.load_certificate_request(crypto.FILETYPE_PEM,
274                                                          pemCertReq)
275            except crypto.Error, e:
276                log.error("Error loading input certificate request: %r", 
277                          pemCertReq)
278                raise HttpBasicAuthResponseException("Error loading input "
279                                                     "certificate request",
280                                                     httplib.BAD_REQUEST)
281               
282            # Convert to ASN1 format expect by logon client call
283            asn1CertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 
284                                                          certReq)
285           
286            try:
287                credentials = self.myProxyClient.logon(username, 
288                                                       password,
289                                                       certReq=asn1CertReq)
290                status = self.getStatusMessage(httplib.OK)
291                response = '\n'.join(credentials)
292               
293                start_response(status,
294                               [('Content-length', str(len(response))),
295                                ('Content-type', 'text/plain')])
296                return [response]
297                       
298            except MyProxyClientError, e:
299                raise HttpBasicAuthResponseException(str(e),
300                                                     httplib.UNAUTHORIZED)
301            except socket.error, e:
302                raise MyProxyClientMiddlewareError("Socket error "
303                                        "with MyProxy server %r: %s" % 
304                                        (self.myProxyClient.hostname, e))
305            except Exception, e:
306                log.error("MyProxyClient.logon raised an unknown exception "
307                          "calling %r: %s", 
308                          self.myProxyClient.hostname,
309                          traceback.format_exc())
310                raise # Trigger 500 Internal Server Error
311           
312        return _myProxylogon
313   
314   
315class MyProxyGetTrustRootsMiddlewareError(Exception):
316    """MyProxyGetTrustRootsMiddleware exception class"""
317   
318   
319class MyProxyGetTrustRootsMiddleware(MyProxyClientMiddlewareBase):
320    """HTTP client interface for MyProxy server Get Trust Roots method
321   
322    It relies on a myproxy.server.wsgi.MyProxyClientMiddleware instance called
323    upstream in the WSGI stack to set up a MyProxyClient instance and make it
324    available in the environ to call its getTrustRoots method.
325    """
326   
327    # Options for ini file
328    CLIENT_ENV_KEYNAME_OPTNAME = \
329        MyProxyClientMiddleware.CLIENT_ENV_KEYNAME_OPTNAME
330       
331    PATH_OPTNAME = 'path'     
332   
333    DEFAULT_CLIENT_ENV_KEYNAME = \
334        MyProxyClientMiddleware.DEFAULT_CLIENT_ENV_KEYNAME
335   
336    DEFAULT_PATH = '/myproxy/get-trustroots'
337   
338    # Option prefixes
339    PARAM_PREFIX = 'myproxy.getTrustRoots.'
340   
341    __slots__ = (
342        '__path',
343    )
344   
345    def __init__(self, app):
346        '''Create attributes
347       
348        @type app: function
349        @param app: WSGI callable for next application in stack
350        '''
351        super(MyProxyGetTrustRootsMiddleware, self).__init__(app)
352        self.__path = None
353       
354    @classmethod
355    def filter_app_factory(cls, app, global_conf, prefix=PARAM_PREFIX, 
356                           **app_conf):
357        """Function following Paste filter app factory signature
358       
359        @type app: callable following WSGI interface
360        @param app: next middleware/application in the chain     
361        @type global_conf: dict       
362        @param global_conf: PasteDeploy global configuration dictionary
363        @type prefix: basestring
364        @param prefix: prefix for configuration items
365        @type app_conf: dict       
366        @param app_conf: PasteDeploy application specific configuration
367        dictionary
368        """
369        app = cls(app)
370        app.parseConfig(prefix=prefix, **app_conf)
371        return app
372   
373    def parseConfig(self, prefix=PARAM_PREFIX, **app_conf):
374        """Parse dictionary of configuration items updating the relevant
375        attributes of this instance
376       
377        @type prefix: basestring
378        @param prefix: prefix for configuration items
379        @type app_conf: dict       
380        @param app_conf: PasteDeploy application specific configuration
381        dictionary
382        """
383        clientEnvKeyOptName = prefix + self.__class__.CLIENT_ENV_KEYNAME_OPTNAME
384                   
385        self.clientEnvironKeyName = app_conf.get(clientEnvKeyOptName,
386                                    self.__class__.DEFAULT_CLIENT_ENV_KEYNAME)
387       
388        pathOptName = prefix + self.__class__.PATH_OPTNAME
389        self.path = app_conf.get(pathOptName, self.__class__.DEFAULT_PATH)
390
391    def _getPath(self):
392        """Get URI path for get trust roots method
393        @rtype: basestring
394        @return: path for get trust roots method
395        """
396        return self.__path
397
398    def _setPath(self, value):
399        """Set URI path for get trust roots method
400        @type value: basestring
401        @param value: path for get trust roots method
402        """
403        if not isinstance(value, basestring):
404            raise TypeError('Expecting string type for "path"; got %r' % 
405                            type(value))
406       
407        self.__path = value
408
409    path = property(fget=_getPath, fset=_setPath, 
410                    doc="environ SCRIPT_NAME path which invokes the "
411                        "getTrustRoots method on this middleware")
412   
413    def __call__(self, environ, start_response):
414        '''Get MyProxyClient instance from environ and call MyProxy
415        getTrustRoots method returning the response.
416       
417        MyProxyClientMiddleware must be in place upstream in the WSGI stack
418       
419        @type environ: dict
420        @param environ: WSGI environment variables dictionary
421        @type start_response: function
422        @param start_response: standard WSGI start response function
423        '''
424        # Skip if path doesn't match
425        if environ['PATH_INFO'] != self.path:
426            return self.app(environ, start_response)
427       
428        log.debug("MyProxyGetTrustRootsMiddleware.__call__ ...")
429       
430        # Check method
431        requestMethod = environ.get('REQUEST_METHOD')             
432        if requestMethod != 'GET':
433            response = "HTTP Request method not recognised"
434            log.error("HTTP Request method %r not recognised", requestMethod)
435            status = self.__class__.getStatusMessage(httplib.BAD_REQUEST)
436            start_response(status,
437                           [('Content-type', 'text/plain'),
438                            ('Content-length', str(len(response)))])
439            return [response]
440       
441        myProxyClient = environ[self.clientEnvironKeyName]
442        if not isinstance(myProxyClient, MyProxyClient):
443            raise TypeError('Expecting %r type for "myProxyClient" environ[%r] '
444                            'attribute got %r' % (MyProxyClient, 
445                                                  self.clientEnvironKeyName,
446                                                  type(myProxyClient)))
447       
448        response = self._getTrustRoots(myProxyClient)
449        start_response(self.getStatusMessage(httplib.OK),
450                       [('Content-length', str(len(response))),
451                        ('Content-type', 'text/plain')])
452
453        return [response]
454   
455    @classmethod
456    def _getTrustRoots(cls, myProxyClient):
457        """Call getTrustRoots method on MyProxyClient instance retrieved from
458        environ and format and return a HTTP response
459       
460        @type myProxyClient: myproxy.client.MyProxyClient
461        @param myProxyClient: MyProxyClient instance on which to call
462        getTrustRoots method
463       
464        @rtype: basestring
465        @return: trust roots formatted as a HTTP response
466        """
467        try:
468            trustRoots = myProxyClient.getTrustRoots()
469           
470            # Serialise dict response
471            response = "\n".join(["%s=%s" % (k, base64.b64encode(v))
472                                  for k,v in trustRoots.items()])
473           
474            return response
475                   
476        except MyProxyClientError, e:
477            log.error("MyProxyClient.getTrustRoots raised an "
478                      "MyProxyClientError exception calling %r: %s", 
479                      myProxyClient.hostname,
480                      traceback.format_exc())
481           
482        except socket.error, e:
483            raise MyProxyGetTrustRootsMiddlewareError("Socket error with "
484                                                      "MyProxy server %r: %s" % 
485                                                      (myProxyClient.hostname, 
486                                                       e))
487        except Exception, e:
488            log.error("MyProxyClient.getTrustRoots raised an unknown exception "
489                      "calling %r: %s", 
490                      myProxyClient.hostname,
491                      traceback.format_exc())
492            raise # Trigger 500 Internal Server Error
493       
Note: See TracBrowser for help on using the repository browser.