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

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

SessionMgr? SessionMgrClient? unit tests complete with tests for getSessionStatus included

ndg.security.server/ndg/security/server/conf/sessionMgr.tac:

  • code for getSessionStatus incorporated

ndg.security.server/ndg/security/server/SessionMgr/SessionMgr_services_server.py,
ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services.py,
ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services_types.py,
www/html/sessionMgr.wsdl: fixed getSessionStatusResponse - isAlive element needs to be nested within a sequence elem.

ndg.security.test/ndg/security/test/sessionMgrClient/SessionMgrClientTest.py,

ndg.security.test/ndg/security/test/sessionMgrClient/sessionMgrProperties.xml: default to https for tests

ndg.security.test/ndg/security/test/sessionMgrClient/sessionMgrClientTest.cfg: get rid of test1AddUser and added test2GetSessionStatus

ndg.security.test/ndg/security/test/sessionMgr/test.py: SessionMgr? unit tests all working

ndg.security.common/ndg/security/common/SessionMgr/init.py: added getSessionStatus method

ndg.security.common/ndg/security/common/AttAuthority/init.py: fix to getHostInfo - return dict indexed by hostname

ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services.py: re-ran code generation from WSDL

Makefile: added targets for building ZSI code stubs from AA and SM WSDLs.

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