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

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

Initial Integration of Single Sign On Service with OpenID and Pylons AuthKit?:

  • WAYF now contains an OpenID textbox for sign in
  • No role integration carried out yet - OpenID has no better privileges than an anonymous user(!)
  • Integrated into Authkit - requires lots of config settings in pylons ini file
  • HTTP 401 error get redirected automatically to WAYF
  • Need to create an AuthKit? egg from SVN 151 checkout - will put on NDG dist
  • 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 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: %s" % self.__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            log.info("Ignoring httpProxyHost setting: transport class is " +\
237                     " not ProxyHTTPConnection type")
238            return
239       
240        self._transdict['httpProxyHost'] = val
241
242    httpProxyHost = property(fset=__setHTTPProxyHost, 
243        doc="HTTP Proxy hostname - overrides any http_proxy env var setting")
244
245
246    #_________________________________________________________________________
247    def __setIgnoreHttpProxyEnv(self, val):
248        """Set to True to ignore any http_proxy environment variable setting"""
249        if self._transport != ProxyHTTPConnection:
250            log.info("Ignore ignoreHttpProxyEnv setting: transport " + \
251                     "class is not ProxyHTTPConnection type")
252            return
253       
254        self._transdict['ignoreHttpProxyEnv']= val
255
256    ignoreHttpProxyEnv = property(fset=__setIgnoreHttpProxyEnv, 
257    doc="Set to True to ignore any http_proxy environment variable setting")
258   
259
260    #_________________________________________________________________________
261    def __setSSLPeerCertCN(self, cn):
262        """For use with HTTPS connections only.  Specify the Common
263        Name to match with Common Name of the peer certificate.  This is not
264        needed if the peer cert CN = peer hostname"""
265        if self._transport != HTTPSConnection:
266            return
267       
268        if self._transdict.get('postConnectionCheck'):
269            self._transdict['postConnectionCheck'].peerCertCN = cn
270        else:
271            self._transdict['postConnectionCheck'] = HostCheck(peerCertCN=cn)
272
273    sslPeerCertCN = property(fset=__setSSLPeerCertCN, 
274doc="for https connections, set CN of peer cert if other than peer hostname")
275
276
277    #_________________________________________________________________________
278    def __setSSLCACertList(self, caCertList):
279        """For use with HTTPS connections only.  Specify CA certs to one of
280        which the peer cert must verify its signature against"""
281        if self._transport != HTTPSConnection:
282            return
283       
284        if self._transdict.get('postConnectionCheck'):
285            self._transdict['postConnectionCheck'].caCertList = caCertList
286        else:
287            self._transdict['postConnectionCheck'] = \
288                                            HostCheck(caCertList=caCertList)
289
290    sslCACertList = property(fset=__setSSLCACertList, 
291doc="for https connections, set list of CA certs from which to verify peer cert")
292
293
294    #_________________________________________________________________________
295    def __setSSLCACertFilePathList(self, caCertFilePathList):
296        """For use with HTTPS connections only.  Specify CA certs to one of
297        which the peer cert must verify its signature against"""
298        if self._transport != HTTPSConnection:
299            return
300       
301        if self._transdict.get('postConnectionCheck'):
302            self._transdict['postConnectionCheck'].caCertFilePathList = \
303                                            caCertFilePathList
304        else:
305            self._transdict['postConnectionCheck'] = \
306                            HostCheck(caCertFilePathList=caCertFilePathList)
307
308    sslCACertFilePathList = property(fset=__setSSLCACertFilePathList, 
309                                     doc=\
310"for https connections, set list of CA cert files from which to verify peer cert")
311
312
313    #_________________________________________________________________________
314    def __setSignatureHandler(self, signatureHandler):
315        """Set SignatureHandler object property method - set to None to for no
316        digital signature and verification"""
317        if signatureHandler is not None and \
318           not isinstance(signatureHandler, SignatureHandler):
319            raise AttributeError, \
320    "Signature Handler must be %s type or None for no message security" % \
321        "ndg.security.common.wsSecurity.SignatureHandler"
322                           
323        self.__signatureHandler = signatureHandler
324
325
326    #_________________________________________________________________________
327    def __getSignatureHandler(self):
328        "Get SignatureHandler object property method"
329        return self.__signatureHandler
330   
331    signatureHandler = property(fget=__getSignatureHandler,
332                                fset=__setSignatureHandler,
333                                doc="SignatureHandler object")
334   
335       
336    #_________________________________________________________________________
337    def initService(self, uri=None):
338        """Set the WS client for the Session Manager"""
339        if uri:
340            self.__setURI(uri)
341   
342        # WS-Security Signature handler object is passed to binding
343        try:
344            locator = SessionMgrServiceLocator()
345            self.__srv = locator.getSessionMgr(self.__uri,
346                                         sig_handler=self.__signatureHandler,
347                                         tracefile=self.__tracefile,
348                                         transport=self._transport,
349                                         transdict=self._transdict)
350        except HTTPResponse, e:
351            raise SessionMgrClientError, \
352                "Initialising Service for \"%s\": %s %s" % \
353                (self.__uri, e.status, e.reason)
354   
355       
356    #_________________________________________________________________________   
357    def connect(self,
358                username,
359                passphrase=None,
360                passphraseFilePath=None,
361                createServerSess=True):
362        """Request a new user session from the Session Manager
363       
364        @type username: string
365        @param username: the username of the user to connect
366       
367        @type passphrase: string
368        @param passphrase: user's pass-phrase
369       
370        @type passphraseFilePath: string
371        @param passphraseFilePath: a file containing the user's pass-phrase. 
372        Use this as an alternative to passphrase keyword.
373                                 
374        @type createServerSess: bool
375        @param createServerSess: If set to True, the SessionMgr will create
376        and manage a session for the user.  For non-browser client case, it's
377        possible to choose to have a client or server side session using this
378        keyword.  If set to False sessID returned will be None
379       
380        @rtype: tuple
381        @return user cert, user private key, issuing cert and sessID all as
382        strings but sessID will be None if the createServerSess keyword is
383        False"""
384   
385        if passphrase is None:
386            try:
387                passphrase = open(passphraseFilePath).read().strip()
388           
389            except Exception, e:
390                raise SessionMgrClientError, "Pass-phrase not defined: " + \
391                                             str(e)
392
393        # Make connection
394        res = self.__srv.connect(username, passphrase, createServerSess)
395
396        # Convert from unicode because unicode causes problems with
397        # M2Crypto private key load
398        return tuple([isinstance(i,unicode) and str(i) or i for i in res])
399   
400       
401    #_________________________________________________________________________   
402    def disconnect(self, userCert=None, sessID=None):
403        """Delete an existing user session from the Session Manager
404       
405        disconnect([userCert=c]|[sessID=i])
406       
407        @type userCert: string                 
408        @param userCert: user's certificate used to identifier which session
409        to disconnect.  This arg is not needed if the message is signed with
410        the user cert or if sessID is set. 
411                               
412        @type sessID: string
413        @param sessID: session ID.  Input this as an alternative to userCert
414        This arg is not needed if the message is signed with the user cert or
415        if userCert keyword is."""
416
417        # Make connection
418        self.__srv.disconnect(userCert, sessID)
419   
420       
421    #_________________________________________________________________________   
422    def getSessionStatus(self, userDN=None, sessID=None):
423        """Check for the existence of a session with a given
424        session ID / user certificate Distinguished Name
425       
426        disconnect([sessID=id]|[userDN=dn])
427       
428        @type userCert: string                 
429        @param userCert: user's certificate used to identifier which session
430        to disconnect.  This arg is not needed if the message is signed with
431        the user cert or if sessID is set. 
432                               
433        @type sessID: string
434        @param sessID: session ID.  Input this as an alternative to userCert
435        This arg is not needed if the message is signed with the user cert or
436        if userCert keyword is."""
437       
438        if sessID and userDN:
439            raise SessionMgrClientError(
440                            'Only "SessID" or "userDN" keywords may be set')
441           
442        if not sessID and not userDN:
443            raise SessionMgrClientError(
444                            'A "SessID" or "userDN" keyword must be set')         
445           
446        # Make connection
447        return self.__srv.getSessionStatus(userDN, sessID)
448
449   
450    #_________________________________________________________________________
451    def getAttCert(self,
452                   userCert=None,
453                   sessID=None,
454                   attAuthorityURI=None,
455                   attAuthorityCert=None,
456                   reqRole=None,
457                   mapFromTrustedHosts=True,
458                   rtnExtAttCertList=False,
459                   extAttCertList=[],
460                   extTrustedHostList=[]):   
461        """Request NDG Session Manager Web Service to retrieve an Attribute
462        Certificate from the given Attribute Authority and cache it in the
463        user's credential wallet held by the session manager.
464       
465        ac = getAttCert([sessID=i]|[userCert=p][key=arg, ...])
466         
467        @raise AttributeRequestDenied: this is raised if the request is
468        denied because the user is not registered with the Attribute
469        Authority.  In this case, a list of candidate attribute certificates
470        may be returned which could be used to retry with a request for a
471        mapped AC.  These are assigned to the raised exception's
472        extAttCertList attribute
473             
474        @type userCert: string
475        @param userCert: user certificate - use as ID instead of session
476        ID.  This can be omitted if the message is signed with a user
477        certificate.  In this case the user certificate is passed in the
478        BinarySecurityToken of the WS-Security header
479       
480        @type sessID: string
481        @param sessID: session ID.  Input this as an alternative to
482        userCert in the case of a browser client.
483       
484        @type attAuthorityURI: string
485        @param attAuthorityURI: URI for Attribute Authority WS.
486       
487        @type attAuthorityCert: string
488        @param attAuthorityCert: The Session Manager uses the Public key of
489        the Attribute Authority to encrypt requests to it.
490       
491        @type reqRole: string
492        @param reqRole: The required role for access to a data set.  This
493        can be left out in which case the Attribute Authority just returns
494        whatever Attribute Certificate it has for the user
495       
496        @type mapFromTrustedHosts: bool
497        @param mapFromTrustedHosts: Allow a mapped Attribute Certificate to
498        be created from a user certificate from another trusted host.
499       
500        @type rtnExtAttCertList: bool
501        @param rtnExtAttCertList: Set this flag True so that if the
502        attribute request is denied, a list of potential attribute
503        certificates for mapping may be returned.
504       
505        @type extAttCertList: list
506        @param extAttCertList: A list of Attribute Certificates from other
507        trusted hosts from which the target Attribute Authority can make a
508        mapped certificate
509       
510        @type extTrustedHostList: list
511        @param extTrustedHostList: A list of trusted hosts that can be used
512        to get Attribute Certificates for making a mapped AC.
513       
514        @rtype: ndg.security.common.AttCert.AttCert
515        @return: if successful, an attribute certificate."""
516       
517        # Make request
518        try:
519            attCert, msg, extAttCertList = self.__srv.getAttCert(userCert,
520                                                       sessID, 
521                                                       attAuthorityURI,
522                                                       attAuthorityCert,
523                                                       reqRole,
524                                                       mapFromTrustedHosts,
525                                                       rtnExtAttCertList,
526                                                       extAttCertList,
527                                                       extTrustedHostList)
528        except Exception, e:
529            # Try to detect exception type from SOAP fault message
530            errMsg = str(e)
531            for excep in self.excepMap:
532                if excep in errMsg:
533                    raise self.excepMap[excep]
534       
535            # Catch all in case none of the known types matched
536            raise e
537       
538        if not attCert:
539            raise AttributeRequestDenied(msg, extAttCertList=extAttCertList)
540       
541        return AttCertParse(attCert)
542   
543                                   
544    #_________________________________________________________________________
545    def getX509Cert(self):
546        """Retrieve the public key of the Session Manager"""
547        return self.__srv.getX509Cert()
548                           
Note: See TracBrowser for help on using the repository browser.