source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/sessionmanager.py @ 4480

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/sessionmanager.py@4480
Revision 4480, 21.1 KB checked in by pjkersha, 11 years ago (diff)

Combined Services tests:

  • added capability for Session Manager to call a local Attribute Authority in the WSGI stack of the same Paste instance
  • SOAP client can specify that the Session Manager call a local Attribute Authority by setting AttAuthorityURI to nill in the web service call.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Security client - client interface classes to Session Manager
2
3Make requests for authentication and authorisation
4
5NERC Data Grid Project
6
7This software may be distributed under the terms of the Q Public License,
8version 1.0 or later.
9"""
10__author__ = "P J Kershaw"
11__date__ = "24/04/06"
12__copyright__ = "(C) 2007 STFC & NERC"
13__contact__ = "Philip.Kershaw@stfc.ac.uk"
14__revision__ = "$Id:sessionmanager.py 4373 2008-10-29 09:54:39Z pjkersha $"
15
16import logging
17log = logging.getLogger(__name__)
18
19import sys
20import os
21
22# Determine https http transport
23import urlparse
24
25from ZSI.wstools.Utility import HTTPResponse
26
27from ndg.security.common.wssecurity.dom import SignatureHandler
28from ndg.security.common.X509 import *
29from ndg.security.common.AttCert import AttCert, AttCertParse
30from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
31    HostCheck
32from ndg.security.common.zsi.httpproxy import ProxyHTTPConnection
33from ndg.security.common.zsi.sessionmanager.SessionManager_services import \
34                                                SessionManagerServiceLocator
35
36
37
38class SessionManagerClientError(Exception):
39    """Exception handling for SessionManagerClient class"""
40
41class SessionNotFound(SessionManagerClientError):
42    """Raise when a session ID input doesn't match with an active session on
43    the Session Manager"""
44
45class SessionCertTimeError(SessionManagerClientError):
46    """Session's X.509 Cert. not before time is BEFORE the system time -
47    usually caused by server's clocks being out of sync.  Fix by all servers
48    running NTP"""
49
50class SessionExpired(SessionManagerClientError):
51    """Session's X.509 Cert. has expired"""
52
53class InvalidSession(SessionManagerClientError):
54    """Session is invalid"""
55
56class InvalidSessionManagerClientCtx(SessionManagerClientError):
57    """Session Manager ZSI Client is not initialised"""
58 
59class AttributeRequestDenied(SessionManagerClientError):
60    """Raise when a getAttCert call to the Attribute Authority is denied"""
61   
62    def __init__(self, *args, **kw):
63        """Raise exception for attribute request denied with option to give
64        caller hint to certificates that could used to try to obtain a
65        mapped certificate
66       
67        @type extAttCertList: list
68        @param extAttCertList: list of candidate Attribute Certificates that
69        could be used to try to get a mapped certificate from the target
70        Attribute Authority"""
71       
72        # Prevent None type setting
73        self.__extAttCertList = []
74        if 'extAttCertList' in kw and kw['extAttCertList'] is not None:
75            for ac in kw['extAttCertList']:
76                if isinstance(ac, basestring):
77                    ac = AttCertParse(ac)
78                elif not isinstance(ac, AttCert):
79                    raise SessionManagerClientError(
80                        "Input external Attribute Cert. must be AttCert type")
81                         
82                self.__extAttCertList += [ac]
83               
84            del kw['extAttCertList']
85           
86        Exception.__init__(self, *args, **kw)
87
88       
89    def __getExtAttCertList(self):
90        """Return list of candidate Attribute Certificates that could be used
91        to try to get a mapped certificate from the target Attribute Authority
92        """
93        return self.__extAttCertList
94
95    extAttCertList = property(fget=__getExtAttCertList,
96                              doc="list of candidate Attribute Certificates "
97                                  "that could be used to try to get a mapped "
98                                  "certificate from the target Attribute "
99                                  "Authority")
100
101
102class SessionManagerClient(object):
103    """Client interface to Session Manager Web Service
104   
105    @type excepMap: dict
106    @cvar excepMap: map exception strings returned from SOAP fault to client
107    Exception class to call"""
108
109    excepMap = {
110        'SessionNotFound':                         SessionNotFound,
111        'UserSessionX509CertNotBeforeTimeError':   SessionCertTimeError,
112        'UserSessionExpired':                      SessionExpired,
113        'InvalidUserSession':                      InvalidSession
114    }
115   
116    def __init__(self, 
117                 uri=None, 
118                 tracefile=None,
119                 httpProxyHost=None,
120                 noHttpProxyList=False,
121                 sslCACertList=[],
122                 sslCACertFilePathList=[],
123                 sslPeerCertCN=None, 
124                 setSignatureHandler=True,
125                 **signatureHandlerKw):
126        """
127        @type uri: string
128        @param uri: URI for Session Manager WS.  Setting it will set the
129        Service user
130               
131        @type tracefile: file stream type
132        @param tracefile: set to file object such as sys.stderr to give extra
133        WS debug information
134       
135        @type sslCACertList: list
136        @param sslCACertList: This keyword is for use with SSL connections
137        only.  Set a list of one or more CA certificates.  The peer cert.
138        must verify against at least one of these otherwise the connection
139        is dropped.
140       
141        @type sslCACertFilePathList: list
142        @param sslCACertFilePathList: the same as the above except CA certs
143        can be passed as a list of file paths to read from
144       
145        @type sslPeerCertCN: string
146        @param sslPeerCertCN: set an alternate CommonName to match with peer
147        cert.  This keyword is for use with SSL connections only.
148       
149        @type setSignatureHandler: bool
150        @param setSignatureHandler: flag to determine whether to apply
151        WS-Security Signature Handler or not
152
153        @type signatureHandlerKw: dict
154        @param signatureHandlerKw: keywords to configure signature handler"""
155
156        log.debug("SessionManagerClient.__init__ ...")
157       
158        self.__srv = None
159        self.__uri = None
160        self._transdict = {}
161        self._transport = ProxyHTTPConnection       
162       
163        if uri:
164            self.uri = uri
165
166        self.httpProxyHost = httpProxyHost
167        self.noHttpProxyList = noHttpProxyList
168
169        if sslPeerCertCN:
170            self.sslPeerCertCN = sslPeerCertCN
171       
172        if sslCACertList:
173            self.sslCACertList = sslCACertList
174        elif sslCACertFilePathList:
175            self.sslCACertFilePathList = sslCACertFilePathList
176
177        # WS-Security Signature handler - set only if any of the keywords were
178        # set
179        log.debug("signatureHandlerKw = %s" % signatureHandlerKw)
180        if setSignatureHandler:
181            self.__signatureHandler = SignatureHandler(**signatureHandlerKw)
182        else:
183            self.__signatureHandler = None
184       
185        self.__tracefile = tracefile
186
187         
188        # Instantiate Session Manager WS ZSI client
189        if self.__uri:
190            self.initService()
191       
192
193    def __setURI(self, uri):
194        """Set URI for service
195        @type uri: string
196        @param uri: URI for service to connect to"""
197       
198        if not isinstance(uri, basestring):
199            raise SessionManagerClientError(
200                             "Session Manager URI must be a valid string")
201       
202        self.__uri = uri
203        try:
204            scheme = urlparse.urlparse(self.__uri)[0]
205        except TypeError:
206            raise AttributeAuthorityClientError(
207                    "Error parsing transport type from URI: %s" % self.__uri)
208               
209        if scheme == "https":
210            self._transport = HTTPSConnection
211        else:
212            self._transport = ProxyHTTPConnection
213           
214            # Ensure SSL settings are cancelled
215            self.__setSSLPeerCertCN(None)
216
217    def __getURI(self):
218        """Get URI for service
219        @rtype: string
220        @return: uri for service to be invoked"""
221        return self.__uri
222       
223    uri = property(fset=__setURI, fget=__getURI, doc="Session Manager URI")
224
225
226    def __setHTTPProxyHost(self, val):
227        """Set a HTTP Proxy host overriding any http_proxy environment variable
228        setting"""
229        if self._transport != ProxyHTTPConnection:
230            log.debug("Ignoring httpProxyHost setting: transport class is "
231                      "not ProxyHTTPConnection type")
232            return
233       
234        self._transdict['httpProxyHost'] = val
235
236    httpProxyHost = property(fset=__setHTTPProxyHost, 
237                             doc="HTTP Proxy hostname - overrides any "
238                                 "http_proxy env var setting")
239
240
241    def __setNoHttpProxyList(self, val):
242        """Set to list of hosts for which to ignore the HTTP Proxy setting"""
243        if self._transport != ProxyHTTPConnection:
244            log.debug("Ignore noHttpProxyList setting: transport " + \
245                      "class is not ProxyHTTPConnection type")
246            return
247       
248        self._transdict['noHttpProxyList']= val
249
250    noHttpProxyList = property(fset=__setNoHttpProxyList, 
251                               doc="Set to list of hosts for which to ignore "
252                                   "the HTTP Proxy setting")
253   
254
255    def __setSSLPeerCertCN(self, cn):
256        """For use with HTTPS connections only.  Specify the Common
257        Name to match with Common Name of the peer certificate.  This is not
258        needed if the peer cert CN = peer hostname"""
259        if self._transport != HTTPSConnection:
260            return
261       
262        if self._transdict.get('postConnectionCheck'):
263            self._transdict['postConnectionCheck'].peerCertCN = cn
264        else:
265            self._transdict['postConnectionCheck'] = HostCheck(peerCertCN=cn)
266
267    sslPeerCertCN = property(fset=__setSSLPeerCertCN, 
268                             doc="for https connections, set CN of peer cert "
269                                 "if other than peer hostname")
270
271
272    def __setSSLCACertList(self, caCertList):
273        """For use with HTTPS connections only.  Specify CA certs to one of
274        which the peer cert must verify its signature against"""
275        if self._transport != HTTPSConnection:
276            return
277       
278        if self._transdict.get('postConnectionCheck'):
279            self._transdict['postConnectionCheck'].caCertList = caCertList
280        else:
281            self._transdict['postConnectionCheck'] = \
282                                            HostCheck(caCertList=caCertList)
283
284    sslCACertList = property(fset=__setSSLCACertList, 
285                             doc="for https connections, set list of CA certs "
286                                 "from which to verify peer cert")
287
288
289    def __setSSLCACertFilePathList(self, caCertFilePathList):
290        """For use with HTTPS connections only.  Specify CA certs to one of
291        which the peer cert must verify its signature against"""
292        if self._transport != HTTPSConnection:
293            return
294       
295        if self._transdict.get('postConnectionCheck'):
296            self._transdict['postConnectionCheck'].caCertFilePathList = \
297                                            caCertFilePathList
298        else:
299            self._transdict['postConnectionCheck'] = \
300                            HostCheck(caCertFilePathList=caCertFilePathList)
301
302    sslCACertFilePathList = property(fset=__setSSLCACertFilePathList, 
303                                     doc="for https connections, set list of "
304                                     "CA cert files from which to verify peer "
305                                     "cert")
306
307
308    def __setSignatureHandler(self, signatureHandler):
309        """Set SignatureHandler object property method - set to None to for no
310        digital signature and verification"""
311        if signatureHandler is not None and \
312           not isinstance(signatureHandler, SignatureHandler):
313            raise AttributeError("Signature Handler must be %s type or None "
314                                 "for no message security" %
315                        "ndg.security.common.wssecurity.dom.SignatureHandler")
316                           
317        self.__signatureHandler = signatureHandler
318
319    def __getSignatureHandler(self):
320        "Get SignatureHandler object property method"
321        return self.__signatureHandler
322   
323    signatureHandler = property(fget=__getSignatureHandler,
324                                fset=__setSignatureHandler,
325                                doc="SignatureHandler object")
326   
327    def initService(self, uri=None):
328        """Set the WS client for the Session Manager"""
329        if uri:
330            self.__setURI(uri)
331   
332        # WS-Security Signature handler object is passed to binding
333        try:
334            locator = SessionManagerServiceLocator()
335            self.__srv = locator.getSessionManager(self.__uri,
336                                         sig_handler=self.__signatureHandler,
337                                         tracefile=self.__tracefile,
338                                         transport=self._transport,
339                                         transdict=self._transdict)
340        except HTTPResponse, e:
341            raise SessionManagerClientError, \
342                "Initialising Service for \"%s\": %s %s" % \
343                (self.__uri, e.status, e.reason)
344   
345    def connect(self,
346                username,
347                passphrase=None,
348                passphraseFilePath=None,
349                createServerSess=True):
350        """Request a new user session from the Session Manager
351       
352        @type username: string
353        @param username: the username of the user to connect
354       
355        @type passphrase: string
356        @param passphrase: user's pass-phrase
357       
358        @type passphraseFilePath: string
359        @param passphraseFilePath: a file containing the user's pass-phrase. 
360        Use this as an alternative to passphrase keyword.
361                                 
362        @type createServerSess: bool
363        @param createServerSess: If set to True, the SessionManager will create
364        and manage a session for the user.  For non-browser client case, it's
365        possible to choose to have a client or server side session using this
366        keyword.  If set to False sessID returned will be None
367       
368        @rtype: tuple
369        @return user cert, user private key, issuing cert and sessID all as
370        strings but sessID will be None if the createServerSess keyword is
371        False"""
372   
373        if not self.__srv:
374            raise InvalidSessionManagerClientCtx("Client binding is not "
375                                                 "initialised")
376       
377        if passphrase is None:
378            try:
379                passphrase = open(passphraseFilePath).read().strip()
380           
381            except Exception, e:
382                raise SessionManagerClientError, "Pass-phrase not defined: " + \
383                                             str(e)
384
385        # Make connection
386        res = self.__srv.connect(username, passphrase, createServerSess)
387
388        # Convert from unicode because unicode causes problems with
389        # M2Crypto private key load
390        return tuple([isinstance(i,unicode) and str(i) or i for i in res])
391   
392    def disconnect(self, userCert=None, sessID=None):
393        """Delete an existing user session from the Session Manager
394       
395        disconnect([userCert=c]|[sessID=i])
396       
397        @type userCert: string                 
398        @param userCert: user's certificate used to identifier which session
399        to disconnect.  This arg is not needed if the message is signed with
400        the user cert or if sessID is set. 
401                               
402        @type sessID: string
403        @param sessID: session ID.  Input this as an alternative to userCert
404        This arg is not needed if the message is signed with the user cert or
405        if userCert keyword is."""
406   
407        if not self.__srv:
408            raise InvalidSessionManagerClientCtx("Client binding is not "
409                                                 "initialised")
410
411        # Make connection
412        self.__srv.disconnect(userCert, sessID)
413   
414    def getSessionStatus(self, userDN=None, sessID=None):
415        """Check for the existence of a session with a given
416        session ID / user certificate Distinguished Name
417       
418        disconnect([sessID=id]|[userDN=dn])
419       
420        @type userCert: string                 
421        @param userCert: user's certificate used to identifier which session
422        to disconnect.  This arg is not needed if the message is signed with
423        the user cert or if sessID is set. 
424                               
425        @type sessID: string
426        @param sessID: session ID.  Input this as an alternative to userCert
427        This arg is not needed if the message is signed with the user cert or
428        if userCert keyword is."""
429   
430        if not self.__srv:
431            raise InvalidSessionManagerClientCtx("Client binding is not "
432                                                 "initialised")
433       
434        if sessID and userDN:
435            raise SessionManagerClientError(
436                            'Only "SessID" or "userDN" keywords may be set')
437           
438        if not sessID and not userDN:
439            raise SessionManagerClientError(
440                            'A "SessID" or "userDN" keyword must be set')         
441           
442        # Make connection
443        return self.__srv.getSessionStatus(userDN, sessID)
444
445    def getAttCert(self,
446                   userCert=None,
447                   sessID=None,
448                   attAuthorityURI=None,
449                   reqRole=None,
450                   mapFromTrustedHosts=True,
451                   rtnExtAttCertList=False,
452                   extAttCertList=[],
453                   extTrustedHostList=[]):   
454        """Request NDG Session Manager Web Service to retrieve an Attribute
455        Certificate from the given Attribute Authority and cache it in the
456        user's credential wallet held by the session manager.
457       
458        ac = getAttCert([sessID=i]|[userCert=p][key=arg, ...])
459         
460        @raise AttributeRequestDenied: this is raised if the request is
461        denied because the user is not registered with the Attribute
462        Authority.  In this case, a list of candidate attribute certificates
463        may be returned which could be used to retry with a request for a
464        mapped AC.  These are assigned to the raised exception's
465        extAttCertList attribute
466             
467        @type userCert: string
468        @param userCert: user certificate - use as ID instead of session
469        ID.  This can be omitted if the message is signed with a user
470        certificate.  In this case the user certificate is passed in the
471        BinarySecurityToken of the WS-Security header
472       
473        @type sessID: string
474        @param sessID: session ID.  Input this as an alternative to
475        userCert in the case of a browser client.
476       
477        @type attAuthorityURI: string
478        @param attAuthorityURI: URI for Attribute Authority WS.
479       
480        @type reqRole: string
481        @param reqRole: The required role for access to a data set.  This
482        can be left out in which case the Attribute Authority just returns
483        whatever Attribute Certificate it has for the user
484       
485        @type mapFromTrustedHosts: bool
486        @param mapFromTrustedHosts: Allow a mapped Attribute Certificate to
487        be created from a user certificate from another trusted host.
488       
489        @type rtnExtAttCertList: bool
490        @param rtnExtAttCertList: Set this flag True so that if the
491        attribute request is denied, a list of potential attribute
492        certificates for mapping may be returned.
493       
494        @type extAttCertList: list
495        @param extAttCertList: A list of Attribute Certificates from other
496        trusted hosts from which the target Attribute Authority can make a
497        mapped certificate
498       
499        @type extTrustedHostList: list
500        @param extTrustedHostList: A list of trusted hosts that can be used
501        to get Attribute Certificates for making a mapped AC.
502       
503        @rtype: ndg.security.common.AttCert.AttCert
504        @return: if successful, an attribute certificate."""
505   
506        if not self.__srv:
507            raise InvalidSessionManagerClientCtx("Client binding is not "
508                                                 "initialised")
509       
510        # Make request
511        try:
512            attCert, msg, extAttCertList = self.__srv.getAttCert(userCert,
513                                                           sessID, 
514                                                           attAuthorityURI,
515                                                           reqRole,
516                                                           mapFromTrustedHosts,
517                                                           rtnExtAttCertList,
518                                                           extAttCertList,
519                                                           extTrustedHostList)
520        except Exception, e:
521            # Try to detect exception type from SOAP fault message
522            errMsg = str(e)
523            for excep in self.excepMap:
524                if excep in errMsg:
525                    raise self.excepMap[excep]
526       
527            # Catch all in case none of the known types matched
528            raise e
529       
530        if not attCert:
531            raise AttributeRequestDenied(msg, extAttCertList=extAttCertList)
532       
533        return AttCertParse(attCert)
534                           
Note: See TracBrowser for help on using the repository browser.