source: TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/sessionmanager.py @ 6440

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