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

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