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

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

Fixes following update to NOCS deployment.

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