source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/SessionMgr/__init__.py @ 3790

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

Fixes to PEP and PDP for BADC deployment

  • 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$"
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 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 SessionMgr_services import SessionMgrServiceLocator
31
32import logging
33log = logging.getLogger(__name__)
34
35
36#_____________________________________________________________________________
37class SessionMgrClientError(Exception):
38    """Exception handling for SessionMgrClient class"""
39
40#_____________________________________________________________________________
41class SessionNotFound(SessionMgrClientError):
42    """Raise when a session ID input doesn't match with an active session on
43    the Session Manager"""
44
45#_____________________________________________________________________________
46class SessionCertTimeError(SessionMgrClientError):
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
51#_____________________________________________________________________________
52class SessionExpired(SessionMgrClientError):
53    """Session's X.509 Cert. has expired"""
54
55#_____________________________________________________________________________
56class InvalidSession(SessionMgrClientError):
57    """Session is invalid"""
58 
59#_____________________________________________________________________________
60class AttributeRequestDenied(SessionMgrClientError):
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 SessionMgrClientError, \
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 SessionMgrClient(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                 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("SessionMgrClient.__init__ ...")
157       
158        self.__srv = None
159        self.__uri = None
160        self._transdict = {}
161        self._transport = None       
162       
163        if uri:
164            self.__setURI(uri)
165
166        if sslPeerCertCN:
167            self.__setSSLPeerCertCN(sslPeerCertCN)
168       
169        if sslCACertList:
170            self.__setSSLCACertList(sslCACertList)
171        elif sslCACertFilePathList:
172            self.__setSSLCACertFilePathList(sslCACertFilePathList)
173
174        # WS-Security Signature handler - set only if any of the keywords were
175        # set
176        log.debug("signatureHandlerKw = %s" % signatureHandlerKw)
177        if setSignatureHandler:
178            self.__signatureHandler = SignatureHandler(**signatureHandlerKw)
179        else:
180            self.__signatureHandler = None
181       
182        self.__tracefile = tracefile
183
184         
185        # Instantiate Session Manager WS ZSI client
186        if self.__uri:
187            self.initService()
188       
189
190    #_________________________________________________________________________
191    def __setURI(self, uri):
192        """Set URI for service
193        @type uri: string
194        @param uri: URI for service to connect to"""
195       
196        if not isinstance(uri, basestring):
197            raise SessionMgrClientError, \
198                             "Session Manager WSDL URI must be a valid string"
199       
200        self.__uri = uri
201        try:
202            scheme = urlparse.urlparse(self.__uri)[0]
203        except TypeError:
204            raise AttributeAuthorityClientError, \
205                "Error parsing transport type from URI"
206               
207        if scheme == "https":
208            self._transport = HTTPSConnection
209        else:
210            self._transport = None
211           
212            # Ensure SSL settings are cancelled
213            self.__setSSLPeerCertCN(None)
214
215    #_________________________________________________________________________
216    def __getURI(self):
217        """Get URI for service
218        @rtype: string
219        @return: uri for service to be invoked"""
220        return self.__uri
221       
222    uri = property(fset=__setURI, fget=__getURI, doc="Session Manager URI")
223
224
225    #_________________________________________________________________________
226    def __setSSLPeerCertCN(self, cn):
227        """For use with HTTPS connections only.  Specify the Common
228        Name to match with Common Name of the peer certificate.  This is not
229        needed if the peer cert CN = peer hostname"""
230        if self._transport != HTTPSConnection:
231            return
232       
233        if self._transdict.get('postConnectionCheck'):
234            self._transdict['postConnectionCheck'].peerCertCN = cn
235        else:
236            self._transdict['postConnectionCheck'] = HostCheck(peerCertCN=cn)
237
238    sslPeerCertCN = property(fset=__setSSLPeerCertCN, 
239doc="for https connections, set CN of peer cert if other than peer hostname")
240
241
242    #_________________________________________________________________________
243    def __setSSLCACertList(self, caCertList):
244        """For use with HTTPS connections only.  Specify CA certs to one of
245        which the peer cert must verify its signature against"""
246        if self._transport != HTTPSConnection:
247            return
248       
249        if self._transdict.get('postConnectionCheck'):
250            self._transdict['postConnectionCheck'].caCertList = caCertList
251        else:
252            self._transdict['postConnectionCheck'] = \
253                                            HostCheck(caCertList=caCertList)
254
255    sslCACertList = property(fset=__setSSLCACertList, 
256doc="for https connections, set list of CA certs from which to verify peer cert")
257
258
259    #_________________________________________________________________________
260    def __setSSLCACertFilePathList(self, caCertFilePathList):
261        """For use with HTTPS connections only.  Specify CA certs to one of
262        which the peer cert must verify its signature against"""
263        if self._transport != HTTPSConnection:
264            return
265       
266        if self._transdict.get('postConnectionCheck'):
267            self._transdict['postConnectionCheck'].caCertFilePathList = \
268                                            caCertFilePathList
269        else:
270            self._transdict['postConnectionCheck'] = \
271                            HostCheck(caCertFilePathList=caCertFilePathList)
272
273    sslCACertFilePathList = property(fset=__setSSLCACertFilePathList, 
274                                     doc=\
275"for https connections, set list of CA cert files from which to verify peer cert")
276
277
278    #_________________________________________________________________________
279    def __setSignatureHandler(self, signatureHandler):
280        """Set SignatureHandler object property method - set to None to for no
281        digital signature and verification"""
282        if signatureHandler is not None and \
283           not isinstance(signatureHandler, SignatureHandler):
284            raise AttributeError, \
285    "Signature Handler must be %s type or None for no message security" % \
286        "ndg.security.common.wsSecurity.SignatureHandler"
287                           
288        self.__signatureHandler = signatureHandler
289
290
291    #_________________________________________________________________________
292    def __getSignatureHandler(self):
293        "Get SignatureHandler object property method"
294        return self.__signatureHandler
295   
296    signatureHandler = property(fget=__getSignatureHandler,
297                                fset=__setSignatureHandler,
298                                doc="SignatureHandler object")
299   
300       
301    #_________________________________________________________________________
302    def initService(self, uri=None):
303        """Set the WS client for the Session Manager"""
304        if uri:
305            self.__setURI(uri)
306   
307        # WS-Security Signature handler object is passed to binding
308        try:
309            locator = SessionMgrServiceLocator()
310            self.__srv = locator.getSessionMgr(self.__uri,
311                                         sig_handler=self.__signatureHandler,
312                                         tracefile=self.__tracefile,
313                                         transport=self._transport,
314                                         transdict=self._transdict)
315        except HTTPResponse, e:
316            raise SessionMgrClientError, \
317                "Initialising Service for \"%s\": %s %s" % \
318                (self.__uri, e.status, e.reason)
319   
320       
321    #_________________________________________________________________________   
322    def connect(self,
323                username,
324                passphrase=None,
325                passphraseFilePath=None,
326                createServerSess=True):
327        """Request a new user session from the Session Manager
328       
329        @type username: string
330        @param username: the username of the user to connect
331       
332        @type passphrase: string
333        @param passphrase: user's pass-phrase
334       
335        @type passphraseFilePath: string
336        @param passphraseFilePath: a file containing the user's pass-phrase. 
337        Use this as an alternative to passphrase keyword.
338                                 
339        @type createServerSess: bool
340        @param createServerSess: If set to True, the SessionMgr will create
341        and manage a session for the user.  For non-browser client case, it's
342        possible to choose to have a client or server side session using this
343        keyword.  If set to False sessID returned will be None
344       
345        @rtype: tuple
346        @return user cert, user private key, issuing cert and sessID all as
347        strings but sessID will be None if the createServerSess keyword is
348        False"""
349   
350        if passphrase is None:
351            try:
352                passphrase = open(passphraseFilePath).read().strip()
353           
354            except Exception, e:
355                raise SessionMgrClientError, "Pass-phrase not defined: " + \
356                                             str(e)
357
358        # Make connection
359        res = self.__srv.connect(username, passphrase, createServerSess)
360
361        # Convert from unicode because unicode causes problems with
362        # M2Crypto private key load
363        return tuple([isinstance(i,unicode) and str(i) or i for i in res])
364   
365       
366    #_________________________________________________________________________   
367    def disconnect(self, userCert=None, sessID=None):
368        """Delete an existing user session from the Session Manager
369       
370        disconnect([userCert=c]|[sessID=i])
371       
372        @type userCert: string                 
373        @param userCert: user's certificate used to identifier which session
374        to disconnect.  This arg is not needed if the message is signed with
375        the user cert or if sessID is set. 
376                               
377        @type sessID: string
378        @param sessID: session ID.  Input this as an alternative to userCert
379        This arg is not needed if the message is signed with the user cert or
380        if userCert keyword is."""
381
382        # Make connection
383        self.__srv.disconnect(userCert, sessID)
384   
385       
386    #_________________________________________________________________________   
387    def getSessionStatus(self, userDN=None, sessID=None):
388        """Check for the existence of a session with a given
389        session ID / user certificate Distinguished Name
390       
391        disconnect([sessID=id]|[userDN=dn])
392       
393        @type userCert: string                 
394        @param userCert: user's certificate used to identifier which session
395        to disconnect.  This arg is not needed if the message is signed with
396        the user cert or if sessID is set. 
397                               
398        @type sessID: string
399        @param sessID: session ID.  Input this as an alternative to userCert
400        This arg is not needed if the message is signed with the user cert or
401        if userCert keyword is."""
402       
403        if sessID and userDN:
404            raise SessionMgrClientError, \
405                            'Only "SessID" or "userDN" keywords may be set'
406
407        # Make connection
408        return self.__srv.getSessionStatus(userDN, sessID)
409
410   
411    #_________________________________________________________________________
412    def getAttCert(self,
413                   userCert=None,
414                   sessID=None,
415                   attAuthorityURI=None,
416                   attAuthorityCert=None,
417                   reqRole=None,
418                   mapFromTrustedHosts=True,
419                   rtnExtAttCertList=False,
420                   extAttCertList=[],
421                   extTrustedHostList=[]):   
422        """Request NDG Session Manager Web Service to retrieve an Attribute
423        Certificate from the given Attribute Authority and cache it in the
424        user's credential wallet held by the session manager.
425       
426        ac = getAttCert([sessID=i]|[userCert=p][key=arg, ...])
427         
428        @raise AttributeRequestDenied: this is raised if the request is
429        denied because the user is not registered with the Attribute
430        Authority.  In this case, a list of candidate attribute certificates
431        may be returned which could be used to retry with a request for a
432        mapped AC.  These are assigned to the raised exception's
433        extAttCertList attribute
434             
435        @type userCert: string
436        @param userCert: user certificate - use as ID instead of session
437        ID.  This can be omitted if the message is signed with a user
438        certificate.  In this case the user certificate is passed in the
439        BinarySecurityToken of the WS-Security header
440       
441        @type sessID: string
442        @param sessID: session ID.  Input this as an alternative to
443        userCert in the case of a browser client.
444       
445        @type attAuthorityURI: string
446        @param attAuthorityURI: URI for Attribute Authority WS.
447       
448        @type attAuthorityCert: string
449        @param attAuthorityCert: The Session Manager uses the Public key of
450        the Attribute Authority to encrypt requests to it.
451       
452        @type reqRole: string
453        @param reqRole: The required role for access to a data set.  This
454        can be left out in which case the Attribute Authority just returns
455        whatever Attribute Certificate it has for the user
456       
457        @type mapFromTrustedHosts: bool
458        @param mapFromTrustedHosts: Allow a mapped Attribute Certificate to
459        be created from a user certificate from another trusted host.
460       
461        @type rtnExtAttCertList: bool
462        @param rtnExtAttCertList: Set this flag True so that if the
463        attribute request is denied, a list of potential attribute
464        certificates for mapping may be returned.
465       
466        @type extAttCertList: list
467        @param extAttCertList: A list of Attribute Certificates from other
468        trusted hosts from which the target Attribute Authority can make a
469        mapped certificate
470       
471        @type extTrustedHostList: list
472        @param extTrustedHostList: A list of trusted hosts that can be used
473        to get Attribute Certificates for making a mapped AC.
474       
475        @rtype: ndg.security.common.AttCert.AttCert
476        @return: if successful, an attribute certificate."""
477       
478        # Make request
479        try:
480            attCert, msg, extAttCertList = self.__srv.getAttCert(userCert,
481                                                       sessID, 
482                                                       attAuthorityURI,
483                                                       attAuthorityCert,
484                                                       reqRole,
485                                                       mapFromTrustedHosts,
486                                                       rtnExtAttCertList,
487                                                       extAttCertList,
488                                                       extTrustedHostList)
489        except Exception, e:
490            # Try to detect exception type from SOAP fault message
491            errMsg = str(e)
492            for excep in self.excepMap:
493                if excep in errMsg:
494                    raise self.excepMap[excep]
495       
496            # Catch all in case none of the known types matched
497            raise e
498       
499        if not attCert:
500            raise AttributeRequestDenied(msg, extAttCertList=extAttCertList)
501       
502        return AttCertParse(attCert)
503   
504                                   
505    #_________________________________________________________________________
506    def getX509Cert(self):
507        """Retrieve the public key of the Session Manager"""
508        return self.__srv.getX509Cert()
509                           
Note: See TracBrowser for help on using the repository browser.