source: TI12-security/trunk/MyProxyLogonWebService/myproxy/server/wsgi/middleware.py @ 6937

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

Incomplete - task 5: MyProxy? Logon HTTPS Interface

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
17from cStringIO import StringIO
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 MyProxyClientMiddleware(object):
35    '''
36    Create a MyProxy client and make it available to other middleware in the
37    WSGI stack
38    '''
39    # Options for ini file
40    CLIENT_ENV_KEYNAME_OPTNAME = 'clientEnvKeyName'
41    LOGON_FUNC_ENV_KEYNAME_OPTNAME = 'logonFuncEnvKeyName'     
42   
43    # Default environ key names
44    CLIENT_ENV_KEYNAME = ('myproxy.server.wsgi.middleware.'
45                          'MyProxyClientMiddleware.myProxyClient')
46    LOGON_FUNC_ENV_KEYNAME = ('myproxy.server.wsgi.middleware.'
47                              'MyProxyClientMiddleware.logon')
48   
49    CERTIFICATE_REQUST_POST_PARAM_KEYNAME = 'certificate_request'
50    GET_TRUSTROOTS_PARAM_KEYNAME = 'get_trustroots'
51    GET_TRUSTROOTS_TRUE_STR = '1'
52   
53    # Option prefixes
54    PARAM_PREFIX = 'myproxy.'
55    MYPROXY_CLIENT_PARAM_PREFIX = 'client.'
56   
57    __slots__ = (
58        '__app',
59        '__myProxyClient', 
60        '__logonFuncEnvironKeyName',
61        '__clientEnvironKeyName'
62    )
63   
64    def __init__(self, app):
65        ''''''
66        self.__app = app
67        self.__myProxyClient = None
68        self.__logonFuncEnvironKeyName = None
69        self.__clientEnvironKeyName = None
70
71    @classmethod
72    def filter_app_factory(cls, app, global_conf, 
73                           prefix=PARAM_PREFIX, 
74                           myProxyClientPrefix=MYPROXY_CLIENT_PARAM_PREFIX, 
75                           **app_conf):
76        """Function following Paste filter app factory signature
77       
78        @type app: callable following WSGI interface
79        @param app: next middleware/application in the chain     
80        @type global_conf: dict       
81        @param global_conf: PasteDeploy global configuration dictionary
82        @type prefix: basestring
83        @param prefix: prefix for configuration items
84        @type app_conf: dict       
85        @param app_conf: PasteDeploy application specific configuration
86        dictionary
87        """
88        app = cls(app)
89        app.parseConfig(prefix=prefix, myProxyClientPrefix=myProxyClientPrefix,
90                        **app_conf)
91        return app
92   
93    def parseConfig(self, prefix=PARAM_PREFIX, myProxyClientPrefix='',
94                    **app_conf):
95        """Parse dictionary of configuration items updating the relevant
96        attributes of this instance
97       
98        @type prefix: basestring
99        @param prefix: prefix for configuration items
100        @type prefix: basestring
101        @param prefix: explicit prefix for MyProxyClient class specific
102        configuration items
103        @type app_conf: dict       
104        @param app_conf: PasteDeploy application specific configuration
105        dictionary
106        """
107       
108        # Get MyProxyClient initialisation parameters
109        myProxyClientFullPrefix = prefix + myProxyClientPrefix
110                           
111        myProxyClientKw = dict([(k.replace(myProxyClientFullPrefix, ''), v) 
112                                 for k,v in app_conf.items() 
113                                 if k.startswith(myProxyClientFullPrefix)])
114       
115        self.myProxyClient = MyProxyClient(**myProxyClientKw)
116        clientEnvKeyOptName = prefix + \
117                            MyProxyClientMiddleware.CLIENT_ENV_KEYNAME_OPTNAME
118                   
119        self.clientEnvironKeyName = app_conf.get(clientEnvKeyOptName,
120                                MyProxyClientMiddleware.CLIENT_ENV_KEYNAME)
121                   
122        logonFuncEnvKeyOptName = prefix + \
123                    MyProxyClientMiddleware.LOGON_FUNC_ENV_KEYNAME_OPTNAME
124
125        self.logonFuncEnvironKeyName = app_conf.get(logonFuncEnvKeyOptName,
126                                MyProxyClientMiddleware.LOGON_FUNC_ENV_KEYNAME) 
127               
128    def _getClientEnvironKeyName(self):
129        return self.__clientEnvironKeyName
130
131    def _setClientEnvironKeyName(self, value):
132        if not isinstance(value, basestring):
133            raise TypeError('Expecting string type for "clientEnvironKeyName"; '
134                            'got %r type' % type(value))
135        self.__clientEnvironKeyName = value
136
137    clientEnvironKeyName = property(fget=_getClientEnvironKeyName, 
138                                    fset=_setClientEnvironKeyName, 
139                                    doc="key name in environ for the "
140                                        "MyProxyClient instance")   
141
142    def _getLogonFuncEnvironKeyName(self):
143        return self.__logonFuncEnvironKeyName
144
145    def _setLogonFuncEnvironKeyName(self, value):
146        if not isinstance(value, basestring):
147            raise TypeError('Expecting string type for '
148                            '"logonFuncEnvironKeyName"; got %r type' % 
149                            type(value))
150        self.__logonFuncEnvironKeyName = value
151
152    logonFuncEnvironKeyName = property(fget=_getLogonFuncEnvironKeyName, 
153                                       fset=_setLogonFuncEnvironKeyName, 
154                                       doc="key name in environ for the "
155                                           "MyProxy logon function")
156   
157    def _getMyProxyClient(self):
158        return self.__myProxyClient
159
160    def _setMyProxyClient(self, value):
161        if not isinstance(value, MyProxyClient):
162            raise TypeError('Expecting %r type for "myProxyClient" attribute '
163                            'got %r' % (MyProxyClient, type(value)))
164        self.__myProxyClient = value
165       
166    myProxyClient = property(fget=_getMyProxyClient,
167                             fset=_setMyProxyClient, 
168                             doc="MyProxyClient instance used to convert HTTPS"
169                                 " call into a call to a MyProxy server")
170
171    def __call__(self, environ, start_response):
172        '''Set MyProxyClient instance and MyProxy logon method in environ
173       
174        @type environ: dict
175        @param environ: WSGI environment variables dictionary
176        @type start_response: function
177        @param start_response: standard WSGI start response function
178        '''
179        log.debug("MyProxyClientMiddleware.__call__ ...")
180        environ[self.clientEnvironKeyName] = self.myProxyClient
181        environ[self.logonFuncEnvironKeyName] = self.myProxyLogon
182       
183        return self.__app(environ, start_response)
184   
185    @property
186    def myProxyLogon(self):
187        """Return the MyProxy logon method wrapped as a HTTP Basic Auth
188        authenticate interface function
189        """
190        def _myProxylogon(environ, start_response, username, password):
191            """Wrap MyProxy logon method as a WSGI app
192            """ 
193            requestMethod = environ.get('REQUEST_METHOD')             
194            if requestMethod != 'POST':
195                response = "HTTP Request method not recognised"
196                log.error("HTTP Request method %r not recognised", 
197                          requestMethod)
198                raise HttpBasicAuthResponseException(response, 
199                                                     httplib.METHOD_NOT_ALLOWED)
200           
201            request = Request(environ)
202            certReqKey = self.__class__.CERTIFICATE_REQUST_POST_PARAM_KEYNAME
203            pemCertReq = request.POST.get(certReqKey)
204            if pemCertReq is None:
205                response = "No %r form variable set" % certReqKey
206                log.error(response)
207                raise HttpBasicAuthResponseException(response, 
208                                                     httplib.BAD_REQUEST)
209            log.debug("cert req = %r", pemCertReq)
210            getTrustRootsKey = self.__class__.GET_TRUSTROOTS_PARAM_KEYNAME
211            getTrustRoots = (request.postvars.get(getTrustRootsKey) == 
212                             self.__class__.GET_TRUSTROOTS_TRUE_STR)
213           
214            # Expecting PEM encoded request
215            try:
216                certReq = crypto.load_certificate_request(crypto.FILETYPE_PEM,
217                                                          pemCertReq)
218            except crypto.Error, e:
219                log.error("Error loading input certificate request: %r", 
220                          pemCertReq)
221                raise HttpBasicAuthResponseException("Error loading input "
222                                                     "certificate request",
223                                                     httplib.BAD_REQUEST)
224               
225            # Convert to ASN1 format expect by logon client call
226            asn1CertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 
227                                                          certReq)
228           
229            try:
230                if getTrustRoots:
231                    trustRootsDict = self.myProxyClient.getTrustRoots()
232                   
233                    trustRoots = '\n'.join([
234                        "FILEDATA_%s=%s" % (fileName, fileContents) 
235                        for fileName, fileContents in trustRootsDict.items()
236                    ])
237                else:
238                    trustRoots = ''
239               
240                credentials = self.myProxyClient.logon(username, 
241                                                       password,
242                                                       certReq=asn1CertReq)
243                status = self.getStatusMessage(httplib.OK)
244                response = '\n'.join(credentials)
245                response += '\n'+trustRoots
246               
247                start_response(status,
248                               [('Content-length', str(len(response))),
249                                ('Content-type', 'text/plain')])
250                return [response]
251                       
252            except MyProxyClientError, e:
253                raise HttpBasicAuthResponseException(str(e),
254                                                     httplib.UNAUTHORIZED)
255            except socket.error, e:
256                raise MyProxyClientMiddlewareError("Socket error "
257                                        "with MyProxy server %r: %s" % 
258                                        (self.myProxyClient.hostname, e))
259            except Exception, e:
260                log.error("MyProxyClient.logon raised an unknown exception "
261                          "calling %r: %s", 
262                          self.myProxyClient.hostname,
263                          traceback.format_exc())
264                raise # Trigger 500 Internal Server Error
265           
266
267       
268        return _myProxylogon
269
270    @staticmethod
271    def getStatusMessage(statusCode):
272        '''Make a standard status message for use with start_response
273        @type statusCode: int
274        @param statusCode: HTTP status code
275        @rtype: str
276        @return: status code with standard message
277        @raise KeyError: for invalid status code
278        '''
279        return '%d %s' % (statusCode, httplib.responses[statusCode])
280   
Note: See TracBrowser for help on using the repository browser.