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

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

SessionMgr? -> SessionManager?

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