source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/AttAuthority/__init__.py @ 4110

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/AttAuthority/__init__.py@4110
Revision 4110, 44.0 KB checked in by cbyrom, 11 years ago (diff)

Allow BinarySecurityToken? ValueType? and whether SignatureConfirmation?
is required to be set from server xml properties file.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Attribute Authority server side code
2
3handles security user attribute (role) allocation
4
5NERC Data Grid Project
6"""
7__author__ = "P J Kershaw"
8__date__ = "15/04/05"
9__copyright__ = "(C) 2007 STFC & NERC"
10__license__ = \
11"""This software may be distributed under the terms of the Q Public
12License, version 1.0 or later."""
13__contact__ = "P.J.Kershaw@rl.ac.uk"
14__revision__ = '$Id$'
15
16import types
17
18
19# Create unique names for attribute certificates
20import tempfile
21import os
22
23# Alter system path for dynamic import of user roles class
24import sys
25
26# For parsing of properties file
27try: # python 2.5
28    from xml.etree import cElementTree as ElementTree
29except ImportError:
30    # if you've installed it yourself it comes this way
31    import cElementTree as ElementTree
32
33import logging
34log = logging.getLogger(__name__)
35
36# X509 Certificate handling
37from ndg.security.common.X509 import *
38
39# NDG Attribute Certificate
40from ndg.security.common.AttCert import *
41
42
43#_____________________________________________________________________________
44class AttAuthorityError(Exception):
45    """Exception handling for NDG Attribute Authority class."""
46    def __init__(self, msg):
47        log.error(msg)
48        Exception.__init__(self, msg)
49       
50
51#_____________________________________________________________________________
52class AttAuthorityAccessDenied(AttAuthorityError):
53    """NDG Attribute Authority - access denied exception.
54
55    Raise from getAttCert method where no roles are available for the user
56    but that the request is otherwise valid.  In all other error cases raise
57    AttAuthorityError"""   
58
59class AttAuthorityNoTrustedHosts(AttAuthorityError):
60    """Raise from getTrustedHosts if there are no trusted hosts defined in
61    the map configuration"""
62
63class AttAuthorityNoMatchingRoleInTrustedHosts(AttAuthorityError):
64    """Raise from getTrustedHosts if there is no mapping to any of the
65    trusted hosts for the given input role name"""
66
67
68#_____________________________________________________________________________
69class AttAuthority(dict):
70    """NDG Attribute Authority - server for allocation of user authorization
71    tokens - attribute certificates.
72   
73    @type __validKeys: dict
74    @cvar __validKeys: valid configuration property keywords - properties file
75    must contain these
76   
77    @type __confDir: string
78    @cvar __confDir: configuration directory under $NDGSEC_DIR - default location
79    for properties file
80   
81    @type __propFileName: string
82    @cvar __propFileName: default file name for properties file under
83    __confDir
84    """
85
86    # Code designed from NERC Data Grid Enterprise and Information Viewpoint
87    # documents.
88    #
89    # Also, draws from Neil Bennett's ACServer class used in the Java
90    # implementation of NDG Security
91
92    __confDir = "conf"
93    __propFileName = "attAuthorityProperties.xml"
94   
95    # valid configuration property keywords
96    __validKeys = { 'name':                '',
97                    'portNum':             -1,
98                    'useSSL':              False,
99                    'sslCertFile':         '',
100                    'sslKeyFile':          '',
101                    'sslKeyPwd':           '',
102                    'sslCACertDir':        '',
103                    'useSignatureHandler': True,
104                    'certFile':            '',
105                    'keyFile':             '',
106                    'keyPwd':              '',
107                    'wssRefInclNS':        [],
108                    'wssSignedInfoInclNS': [],
109                    'caCertFileList':      [],
110                    'clntCertFile':        '',
111                    'attCertLifetime':     -1,
112                    'attCertNotBeforeOff': 0,
113                    'attCertFileName':     '',
114                    'attCertFileLogCnt':   0,
115                    'mapConfigFile':       '',
116                    'attCertDir':          '',
117                    'dnSeparator':         '',
118                    'userRolesModFilePath':'',
119                    'userRolesModName':    '',
120                    'userRolesClassName':  '',
121                    'userRolesPropFile':   '',
122                    'reqBinSecTokValType': '',
123                    'applySignatureConfirmation': False}
124   
125    def __init__(self, propFilePath=None, bReadMapConfig=True):
126        """Create new NDG Attribute Authority instance
127
128        @type propFilePath: string
129        @param propFilePath: path to file containing Attribute Authority
130        configuration parameters.  It defaults to $NDGSEC_AA_PROPFILEPATH or
131        if not set, $NDGSEC_DIR/conf/attAuthorityProperties.xml
132        @type bReadMapConfig: boolean
133        @param bReadMapConfig: by default the Map Configuration file is
134        read.  Set this flag to False to override.
135        """
136        log.info("Initialising service ... ")
137       
138        # Base class initialisation
139        dict.__init__(self)
140
141        # Set from input or use defaults based or environment variables
142        self.setPropFilePath(propFilePath)
143
144        # Initialise role mapping look-ups - These are set in readMapConfig()
145        self.__mapConfig = None
146        self.__localRole2RemoteRole = None
147        self.__remoteRole2LocalRole = None
148
149
150        # Configuration file properties are held together in a dictionary
151        self.__prop = {}
152
153        # Read Attribute Authority Properties file
154        self.readProperties()
155
156        # Read the Map Configuration file
157        if bReadMapConfig:
158            self.readMapConfig()
159
160        # Instantiate Certificate object
161        log.debug("Reading and checking Attribute Authority X.509 cert. ...")
162        self.__cert = X509Cert(self.__prop['certFile'])
163        self.__cert.read()
164
165        # Check it's valid
166        try:
167            self.__cert.isValidTime(raiseExcep=True)
168           
169        except Exception, e:
170            raise AttAuthorityError, \
171                    "Attribute Authority's certificate is invalid: " + str(e)
172       
173        # Check CA certificate
174        log.debug("Reading and checking X.509 CA certificate ...")
175        for caCertFile in self.__prop['caCertFileList']:
176            caCert = X509Cert(caCertFile)
177            caCert.read()
178           
179            try:
180                caCert.isValidTime(raiseExcep=True)
181               
182            except Exception, e:
183                raise AttAuthorityError,'CA certificate "%s" is invalid: %s'%\
184                                        (caCert.dn, e)
185       
186        # Issuer details - serialise using the separator string set in the
187        # properties file
188        self.__issuer = \
189            self.__cert.dn.serialise(separator=self.__prop['dnSeparator'])
190
191        self.__issuerSerialNumber = self.__cert.serialNumber
192       
193       
194        # Load host sites custom user roles interface to enable the AA to
195        # assign roles in an attribute certificate on a getAttCert request
196        self.loadUserRolesInterface()
197
198
199        attCertFilePath = os.path.join(self.__prop['attCertDir'],
200                                       self.__prop['attCertFileName'])
201               
202        # Rotating file handler used for logging attribute certificates
203        # issued.
204        self.__attCertLog = AttCertLog(attCertFilePath)
205       
206
207    #_________________________________________________________________________
208    def loadUserRolesInterface(self):
209        """Set-up user roles interface - load host sites custom AAUserRoles
210        derived class.  This class interfaces with the sites mechanism for
211        mapping user ID to the roles to which they are entitled.  This
212        could be via a user database"""
213
214        log.debug("Loading User roles interface ...")
215        try:
216            try:
217                # Module file path may be None if the new module to be loaded
218                # can be found in the existing system path           
219                if self.__prop['userRolesModFilePath'] is not None:
220                    if not os.path.exists(\
221                              self.__prop['userRolesModFilePath']):
222                        raise Exception, "File path '%s' doesn't exist" % \
223                              self.__prop['userRolesModFilePath']
224                             
225                    # Temporarily extend system path ready for import
226                    sysPathBak = sys.path[:]
227                             
228                    sys.path.append(self.__prop['userRolesModFilePath'])
229               
230                # Import module name specified in properties file
231                userRolesMod = __import__(self.__prop['userRolesModName'],
232                                          globals(),
233                                          locals(),
234                                          [self.__prop['userRolesClassName']])
235   
236                userRolesClass = eval('userRolesMod.' + \
237                                     self.__prop['userRolesClassName'])
238            finally:
239                try:
240                    sys.path[:] = sysPathBak
241                except NameError:
242                    # sysPathBak may not have been defined
243                    pass
244                               
245        except Exception, e:
246            raise AttAuthorityError,'Importing User Roles module: %s' % str(e)
247
248        # Check class inherits from AAUserRoles abstract base class
249        if not issubclass(userRolesClass, AAUserRoles):
250            raise AttAuthorityError, \
251                "User Roles class %s must be derived from AAUserRoles" % \
252                self.__prop['userRolesClassName']
253
254
255        # Instantiate custom class
256        try:
257            self.__userRoles=userRolesClass(self.__prop['userRolesPropFile'])
258           
259        except Exception, e:
260            raise AttAuthorityError, \
261                "Error instantiating User Roles interface: " + str(e)
262               
263        log.info(\
264             'Instantiated "%s" class from user roles module: "%s" in "%s"' %\
265                 (self.__prop['userRolesClassName'],
266                  self.__prop['userRolesModName'],
267                  self.__prop['userRolesModFilePath']))
268
269       
270    #_________________________________________________________________________
271    # Methods for Attribute Authority dictionary like behaviour       
272    def __repr__(self):
273        """Return file properties dictionary as representation"""
274        return repr(self.__prop)
275   
276    def __delitem__(self, key):
277        self.__class__.__name__ + " keys cannot be removed"       
278        raise KeyError, 'Keys cannot be deleted from '+self.__class__.__name__
279
280
281    def __getitem__(self, key):
282        self.__class__.__name__ + """ behaves as data dictionary of Attribute
283        Authority properties
284        """
285        if key not in self.__prop:
286            raise KeyError, "Invalid key '%s'" % key
287       
288        return self.__prop[key]
289       
290    def get(self, kw):
291        return self.__prop.get(kw)
292   
293    def clear(self):
294        raise KeyError, "Data cannot be cleared from "+self.__class__.__name__
295   
296    def keys(self):
297        return self.__prop.keys()
298
299    def items(self):
300        return self.__prop.items()
301
302    def values(self):
303        return self.__prop.values()
304
305    def has_key(self, key):
306        return self.__prop.has_key(key)
307
308    # 'in' operator
309    def __contains__(self, key):
310        return key in self.__prop
311
312
313    def setPropFilePath(self, val=None):
314        """Set properties file from input or based on environment variable
315        settings"""
316        if not val:
317            if 'NDGSEC_AA_PROPFILEPATH' in os.environ:
318                val = os.environ['NDGSEC_AA_PROPFILEPATH']
319               
320            elif 'NDGSEC_DIR' in os.environ:
321                val = os.path.join(os.environ['NDGSEC_DIR'], 
322                                   self.__class__.__confDir,
323                                   self.__class__.__propFileName)
324            else:
325                raise AttributeError, 'Unable to set default Attribute ' + \
326                    'Authority properties file path: neither ' + \
327                    '"NDGSEC_AA_PROPFILEPATH" or "NDGSEC_DIR" environment ' + \
328                    'variables are set'
329               
330        if not isinstance(val, basestring):
331            raise AttributeError, "Input Properties file path " + \
332                                  "must be a valid string."
333     
334        self.__propFilePath = val
335       
336    # Also set up as a property
337    propFilePath = property(fset=setPropFilePath,
338                            doc="Set the path to the properties file")   
339   
340   
341    #_________________________________________________________________________
342    def getAttCert(self,
343                   userId=None,
344                   holderCert=None,
345                   holderCertFilePath=None,
346                   userAttCert=None,
347                   userAttCertFilePath=None):
348
349        """Request a new Attribute Certificate for use in authorisation
350
351        getAttCert([userId=uid][holderCert=px|holderCertFilePath=pxFile, ]
352                   [userAttCert=cert|userAttCertFilePath=certFile])
353         
354        @type userId: string
355        @param userId: identifier for the user who is entitled to the roles
356        in the certificate that is issued.  If this keyword is omitted, then
357        the userId will be set to the DN of the holder.
358       
359        holder = the holder of the certificate - an inidividual user or an
360        organisation to which the user belongs who vouches for that user's ID
361       
362        userId = the identifier for the user who is entitled to the roles
363        specified in the Attribute Certificate that is issued.
364                 
365        @type holderCert: string / ndg.security.common.X509.X509Cert type
366        @param holderCert: base64 encoded string containing proxy cert./
367        X.509 cert object corresponding to the ID who will be the HOLDER of
368        the Attribute Certificate that will be issued.  - Normally, using
369        proxy certificates, the holder and user ID are the same but there
370        may be cases where the holder will be an organisation ID.  This is the
371        case for NDG security with the DEWS project
372       
373        @param holderCertFilePath: string
374        @param holderCertFilePath: file path to proxy/X.509 certificate of
375        candidate holder
376     
377        @type userAttCert: string or AttCert type
378        @param userAttCert: externally provided attribute certificate from
379        another data centre.  This is only necessary if the user is not
380        registered with this attribute authority.
381                       
382        @type userAttCertFilePath: string
383        @param userAttCertFilePath: alternative to userAttCert except pass
384        in as a file path to an attribute certificate instead.
385       
386        @rtype: AttCert
387        @return: new attribute certificate"""
388
389        log.debug("Calling getAttCert ...")
390       
391        # Read X.509 certificate
392        try:           
393            if holderCertFilePath is not None:
394                                   
395                # Certificate input as a file
396                holderCert = X509Cert()
397                holderCert.read(holderCertFilePath)
398               
399            elif isinstance(holderCert, basestring):
400
401                # Certificate input as string text
402                holderCert = X509CertParse(holderCert)
403               
404            elif not isinstance(holderCert, X509Cert):
405                raise AttAuthorityError, \
406                                "No input file path or cert text/object set"
407           
408        except Exception, e:
409            raise AttAuthorityError, "User X.509 certificate: %s" % e
410
411
412        # Check certificate hasn't expired
413        log.debug("Checking client request X.509 certificate ...")
414        try:
415            holderCert.isValidTime(raiseExcep=True)
416           
417        except Exception, e:
418            raise AttAuthorityError, "User X.509 certificate is invalid: " + \
419                                    str(e)
420
421           
422        # Get Distinguished name from certificate as an X500DN type
423        if not userId:
424            try:
425                userId = holderCert.dn.serialise(\
426                                         separator=self.__prop['dnSeparator']) 
427            except Exception, e:
428                raise AttAuthorityError, \
429                    "Setting user Id from holder certificate DN: %s" % e
430       
431        # Make a new Attribute Certificate instance passing in certificate
432        # details for later signing
433        attCert = AttCert()
434
435        # First cert in list corresponds to the private key
436        attCert.certFilePathList = [self.__prop['certFile']] + \
437                                    self.__prop['caCertFileList']
438       
439        attCert.signingKeyFilePath = self.__prop['keyFile']
440        attCert.signingKeyPwd = self.__prop['keyPwd']
441       
442       
443        # Set holder's (user's) Distinguished Name
444        try:
445            attCert['holder'] = \
446                holderCert.dn.serialise(separator=self.__prop['dnSeparator'])           
447        except Exception, e:
448            raise AttAuthorityError, "Holder DN: %s" % e
449
450       
451        # Set Issuer details from Attribute Authority
452        issuerDN = self.__cert.dn
453        try:
454            attCert['issuer'] = \
455                    issuerDN.serialise(separator=self.__prop['dnSeparator'])           
456        except Exception, e:
457            raise AttAuthorityError, "Issuer DN: %s" % e
458       
459        attCert['issuerName'] = self.__prop['name']
460        attCert['issuerSerialNumber'] = self.__issuerSerialNumber
461
462        attCert['userId'] = userId
463       
464        # Set validity time
465        try:
466            attCert.setValidityTime(\
467                        lifetime=self.__prop['attCertLifetime'],
468                        notBeforeOffset=self.__prop['attCertNotBeforeOff'])
469
470            # Check against the certificate's expiry
471            dtHolderCertNotAfter = holderCert.notAfter
472           
473            if attCert.getValidityNotAfter(asDatetime=True) > \
474               dtHolderCertNotAfter:
475
476                # Adjust the attribute certificate's expiry date time
477                # so that it agrees with that of the certificate
478                # ... but also make ensure that the not before skew is still
479                # applied
480                attCert.setValidityTime(dtNotAfter=dtHolderCertNotAfter,
481                        notBeforeOffset=self.__prop['attCertNotBeforeOff'])
482           
483        except Exception, e:
484            raise AttAuthorityError, "Error setting validity time: %s" % e
485       
486
487        # Check name is registered with this Attribute Authority - if no
488        # user roles are found, the user is not registered
489        userRoles = self.getRoles(userId)
490        if userRoles:           
491            # Set as an Original Certificate
492            #
493            # User roles found - user is registered with this data centre
494            # Add roles for this user for this data centre
495            attCert.addRoles(userRoles)
496
497            # Mark new Attribute Certificate as an original
498            attCert['provenance'] = AttCert.origProvenance
499
500        else:           
501            # Set as a Mapped Certificate
502            #
503            # No roles found - user is not registered with this data centre
504            # Check for an externally provided certificate from another
505            # trusted data centre
506            if userAttCertFilePath:
507               
508                # Read externally provided certificate
509                try:
510                    userAttCert = AttCertRead(userAttCertFilePath)
511                   
512                except Exception, e:
513                    raise AttAuthorityError, \
514                            "Reading external Attribute Certificate: %s" % e                           
515            elif userAttCert:
516                # Allow input as a string but convert to
517                if isinstance(userAttCert, basestring):
518                    userAttCert = AttCertParse(userAttCert)
519                   
520                elif not isinstance(userAttCert, AttCert):
521                    raise AttAuthorityError, \
522                        "Expecting userAttCert as a string or AttCert type"         
523            else:
524                raise AttAuthorityAccessDenied, \
525                    "User \"%s\" is not registered and no " % userId + \
526                    "external attribute certificate is available to make " + \
527                    "a mapping."
528
529
530            # Check it's an original certificate - mapped certificates can't
531            # be used to make further mappings
532            if userAttCert.isMapped():
533                raise AttAuthorityError, \
534                    "External Attribute Certificate must have an " + \
535                    "original provenance in order to make further mappings."
536
537
538            # Check it's valid and signed
539            try:
540                # Give path to CA cert to allow check
541                userAttCert.certFilePathList = self.__prop['caCertFileList']
542                userAttCert.isValid(raiseExcep=True)
543               
544            except Exception, e:
545                raise AttAuthorityError, \
546                            "Invalid Remote Attribute Certificate: " + str(e)       
547
548
549            # Check that's it's holder matches the candidate holder
550            # certificate DN
551            if userAttCert.holderDN != holderCert.dn:
552                raise AttAuthorityError, \
553                    "User certificate and Attribute Certificate DNs " + \
554                    'don\'t match: "%s" and "%s"' % (holderCert.dn, 
555                                                     userAttCert.holderDN)
556           
557 
558            # Get roles from external Attribute Certificate
559            trustedHostRoles = userAttCert.roles
560
561
562            # Map external roles to local ones
563            localRoles = self.mapRemoteRoles2LocalRoles(\
564                                                    userAttCert['issuerName'],
565                                                    trustedHostRoles)
566            if not localRoles:
567                raise AttAuthorityAccessDenied, \
568                    "No local roles mapped to the %s roles: %s" % \
569                    (userAttCert['issuerName'], ', '.join(trustedHostRoles))
570
571            attCert.addRoles(localRoles)
572           
573           
574            # Mark new Attribute Certificate as mapped
575            attCert.provenance = AttCert.mappedProvenance
576
577            # Copy the user Id from the external AC
578            attCert.userId = userAttCert.userId
579           
580            # End set mapped certificate block
581
582        try:
583            # Digitally sign certificate using Attribute Authority's
584            # certificate and private key
585            attCert.applyEnvelopedSignature()
586           
587            # Check the certificate is valid
588            attCert.isValid(raiseExcep=True)
589           
590            # Write out certificate to keep a record of it for auditing
591            #attCert.write()
592            self.__attCertLog.info(attCert)
593           
594            log.info(\
595                 'Issued an Attribute Certificate to "%s" with roles: "%s"' %\
596                 (userId, '", "'.join(attCert.roles)))
597
598            # Return the cert to caller
599            return attCert
600       
601        except Exception, e:
602            raise AttAuthorityError, "New Attribute Certificate \"%s\": %s" %\
603                                    (attCert.filePath, e)
604       
605       
606    #_________________________________________________________________________     
607    def readProperties(self):
608
609        """Read the configuration properties for the Attribute Authority.
610        Nb. if parameters for the user roles interface change
611        loadUserRolesInterface() must be called explicitly in order for the
612        changes to take effect
613
614        @type propFilePath: string
615        @param propFilePath: file path to properties file
616        """
617
618        log.debug("Calling readProperties ...")
619        try:
620            tree = ElementTree.parse(self.__propFilePath)
621           
622        except IOError, ioErr:
623            raise AttAuthorityError, \
624                                "Error parsing properties file \"%s\": %s" % \
625                                (ioErr.filename, ioErr.strerror)
626
627       
628        aaProp = tree.getroot()
629        if aaProp is None:
630            raise AttAuthorityError, \
631            "Parsing properties file \"%s\": root element is not defined" % \
632            self.__propFilePath
633
634
635        # Copy properties from file into a dictionary
636        self.__prop = {}
637        invalidKeys = []
638        try:
639            for elem in aaProp:
640                if elem.tag in AttAuthority.__validKeys:
641               
642                    # Make sure to leave password element contents unchanged
643                    if isinstance(AttAuthority.__validKeys[elem.tag], list):
644                        if len(elem) == 0 and elem.text is not None:
645                            # Treat as a list of space separated elements
646                            self.__prop[elem.tag] = elem.text.split()
647                        else:
648                            # Parse from a list of sub-elements
649                            self.__prop[elem.tag] = \
650                                [os.path.expandvars(subElem.text.strip()) \
651                                 for subElem in elem]
652                           
653                    elif 'eyPwd' not in elem.tag and elem.text: 
654                        self.__prop[elem.tag] = \
655                                        os.path.expandvars(elem.text.strip())
656                    else:
657                        self.__prop[elem.tag] = elem.text
658                else:
659                    invalidKeys.append(elem.tag)
660               
661        except Exception, e:
662            raise AttAuthorityError, \
663                "Error parsing tag \"%s\" in properties file \"%s\": %s" % \
664                (elem.tag, self.__propFilePath, e)
665
666        if invalidKeys != []:
667            raise AttAuthorityError, "The following properties file " + \
668                                     "elements are invalid: " + \
669                                     ', '.join(invalidKeys)
670 
671        # Ensure Certificate time parameters are converted to numeric type
672        self.__prop['attCertLifetime'] = float(self.__prop['attCertLifetime'])
673        self.__prop['attCertNotBeforeOff'] = \
674                                    float(self.__prop['attCertNotBeforeOff'])
675
676        # Likewise ...
677        self.__prop['portNum'] = int(self.__prop['portNum'])
678        self.__prop['attCertFileLogCnt']=int(self.__prop['attCertFileLogCnt'])
679
680        # Check directory path
681        try:
682            dirList = os.listdir(self.__prop['attCertDir'])
683
684        except OSError, osError:
685            raise AttAuthorityError, \
686            'Invalid directory path Attribute Certificates store "%s": %s' % \
687                (self.__prop['attCertDir'], osError.strerror)
688
689        log.info('Loaded properties from "%s"' % self.__propFilePath)
690       
691       
692    #_________________________________________________________________________     
693    def readMapConfig(self, mapConfigFilePath=None):
694        """Parse Map Configuration file.
695
696        @type mapConfigFilePath: string
697        @param mapConfigFilePath: file path for map configuration file.  If
698        omitted, it uses member variable __prop['mapConfigFile'].
699        """
700       
701        log.debug("Reading map configuration file ...")
702       
703        if mapConfigFilePath is not None:
704            if not isinstance(mapConfigFilePath, basestring):
705                raise AttAuthorityError, \
706                "Input Map Configuration file path must be a valid string."
707           
708            self.__prop['mapConfigFile'] = mapConfigFilePath
709
710
711        try:
712            tree = ElementTree.parse(self.__prop['mapConfigFile'])
713            rootElem = tree.getroot()
714           
715        except IOError, e:
716            raise AttAuthorityError, \
717                            "Error parsing properties file \"%s\": %s" % \
718                            (e.filename, e.strerror)           
719        except Exception, e:
720            raise AttAuthorityError, \
721                "Error parsing Map Configuration file: \"%s\": %s" % \
722                (self.__prop['mapConfigFile'], e)
723
724           
725        trustedElem = rootElem.findall('trusted')
726        if not trustedElem: 
727            # Make an empty list so that for loop block below is skipped
728            # without an error 
729            trustedElem = ()
730
731        # Dictionaries:
732        # 1) to hold all the data
733        self.__mapConfig = {'thisHost': {}, 'trustedHosts': {}}
734
735        # ... look-up
736        # 2) hosts corresponding to a given role and
737        # 3) roles of external data centre to this data centre
738        self.__localRole2TrustedHost = {}
739        self.__localRole2RemoteRole = {}
740        self.__remoteRole2LocalRole = {}
741
742
743        # Information about this host
744        try:
745            thisHostElem = rootElem.findall('thisHost')[0]
746           
747        except Exception, e:
748            raise AttAuthorityError, \
749            "\"thisHost\" tag not found in Map Configuration file \"%s\"" % \
750            self.__prop['mapConfigFile']
751
752        try:
753            hostName = thisHostElem.attrib.values()[0]
754           
755        except Exception, e:
756            raise AttAuthorityError, "\"name\" attribute of \"thisHost\" " + \
757                    "element not found in Map Configuration file \"%s\"" % \
758                    self.__prop['mapConfigFile']
759
760
761        # hostname is also stored in the AA's config file in the 'name' tag. 
762        # Check the two match as the latter is copied into Attribute
763        # Certificates issued by this AA
764        #
765        # TODO: would be better to rationalise this so that the hostname is
766        # stored in one place only.
767        #
768        # P J Kershaw 14/06/06
769        if hostName != self.__prop['name']:
770            raise AttAuthorityError, "\"name\" attribute of \"thisHost\" " + \
771                "element in Map Configuration file doesn't match " + \
772                "\"name\" element in properties file."
773       
774        # Information for THIS Attribute Authority
775        hostDict = {}.fromkeys(('aaURI',
776                                'aaDN',
777                                'loginURI',
778                                'loginServerDN',
779                                'loginRequestServerDN'))
780        self.__mapConfig['thisHost'][hostName] = hostDict.copy()
781        for k in self.__mapConfig['thisHost'][hostName]:
782            self.__mapConfig['thisHost'][hostName][k]=thisHostElem.findtext(k)
783       
784        # Information about trusted hosts
785        for elem in trustedElem:
786            try:
787                trustedHost = elem.attrib.values()[0]
788               
789            except Exception, e:
790                raise AttAuthorityError, \
791                                    "Error reading trusted host name: %s" % e
792
793           
794            # Add signatureFile and list of roles
795            #
796            # (Currently Optional) additional tag allows query of the URI
797            # where a user would normally login at the trusted host.  Added
798            # this feature to allow users to be forwarded to their home site
799            # if they are accessing a secure resource and are not
800            # authenticated
801            #
802            # P J Kershaw 25/05/06
803            self.__mapConfig['trustedHosts'][trustedHost] = hostDict.copy()
804            for k in self.__mapConfig['trustedHosts'][trustedHost]:
805                self.__mapConfig['trustedHosts'][trustedHost][k] = \
806                                                        elem.findtext(k)   
807
808            roleElem = elem.findall('role')
809            if roleElem:
810                # Role keyword value requires special parsing before
811                # assignment
812                self.__mapConfig['trustedHosts'][trustedHost]['role'] = \
813                                        [dict(i.items()) for i in roleElem]
814            else:
815                # It's possible for trust relationships to not contain any
816                # role mapping.  e.g. a site's login service trusting other
817                # sites login requests
818                self.__mapConfig['trustedHosts'][trustedHost]['role'] = []
819                       
820            self.__localRole2RemoteRole[trustedHost] = {}
821            self.__remoteRole2LocalRole[trustedHost] = {}
822           
823            for role in self.__mapConfig['trustedHosts'][trustedHost]['role']:
824                try:
825                    localRole = role['local']
826                    remoteRole = role['remote']
827                except KeyError, e:
828                    raise AttAuthorityError, \
829            'Reading map config file "%s": no element "%s" for host "%s"' % \
830                        (self.__prop['mapConfigFile'], e, trustedHost)
831                   
832                # Role to host look-up
833                if localRole in self.__localRole2TrustedHost:
834                   
835                    if trustedHost not in \
836                       self.__localRole2TrustedHost[localRole]:
837                        self.__localRole2TrustedHost[localRole].\
838                                                        append(trustedHost)                       
839                else:
840                    self.__localRole2TrustedHost[localRole] = [trustedHost]
841
842
843                # Trusted Host to local role and trusted host to trusted role
844                # map look-ups
845                try:
846                    self.__remoteRole2LocalRole[trustedHost][remoteRole].\
847                                                            append(localRole)                 
848                except KeyError:
849                    self.__remoteRole2LocalRole[trustedHost][remoteRole] = \
850                                                                [localRole]
851                   
852                try:
853                    self.__localRole2RemoteRole[trustedHost][localRole].\
854                                                            append(remoteRole)                 
855                except KeyError:
856                    self.__localRole2RemoteRole[trustedHost][localRole] = \
857                                                                [remoteRole]                 
858        log.info('Loaded map configuration file "%s"' % \
859                 self.__prop['mapConfigFile'])
860
861       
862    #_________________________________________________________________________     
863    def userIsRegistered(self, userId):
864        """Check a particular user is registered with the Data Centre that the
865        Attribute Authority represents
866       
867        Nb. this method is not used internally by AttAuthority class and is
868        not a required part of the AAUserRoles API
869       
870        @type userId: string
871        @param userId: user identity - could be a X500 Distinguished Name
872        @rtype: bool
873        @return: True if user is registered, False otherwise"""
874        log.debug("Calling userIsRegistered ...")
875        return self.__userRoles.userIsRegistered(userId)
876       
877       
878    #_________________________________________________________________________     
879    def getRoles(self, userId):
880        """Get the roles available to the registered user identified userId.
881
882        @type dn: string
883        @param dn: user identifier - could be a X500 Distinguished Name
884        @return: list of roles for the given user ID"""
885
886        log.debug('Calling getRoles for user "%s" ...' % userId)
887       
888        # Call to AAUserRoles derived class.  Each Attribute Authority
889        # should define it's own roles class derived from AAUserRoles to
890        # define how roles are accessed
891        try:
892            return self.__userRoles.getRoles(userId)
893
894        except Exception, e:
895            raise AttAuthorityError, "Getting user roles: %s" % e
896       
897       
898    #_________________________________________________________________________     
899    def __getHostInfo(self):
900        """Return the host that this Attribute Authority represents: its ID,
901        the user login URI and WSDL address.  Call this method via the
902        'hostInfo' property
903       
904        @rtype: dict
905        @return: dictionary of host information derived from the map
906        configuration"""
907       
908        return self.__mapConfig['thisHost']
909       
910    hostInfo = property(fget=__getHostInfo, 
911                        doc="Return information about this host")
912       
913       
914    #_________________________________________________________________________     
915    def getTrustedHostInfo(self, role=None):
916        """Return a dictionary of the hosts that have trust relationships
917        with this AA.  The dictionary is indexed by the trusted host name
918        and contains AA service, login URIs and the roles that map to the
919        given input local role.
920
921        @type role: string
922        @param role: if set, return trusted hosts that having a mapping set
923        for this role.  If no role is input, return all the AA's trusted hosts
924        with all their possible roles
925
926        @rtype: dict
927        @return: dictionary of the hosts that have trust relationships
928        with this AA.  It returns an empty dictionary if role isn't
929        recognised"""
930               
931        log.debug('Calling getTrustedHostInfo with role = "%s" ...' % role) 
932                                 
933        if not self.__mapConfig or not self.__localRole2RemoteRole:
934            # This Attribute Authority has no trusted hosts
935            raise AttAuthorityNoTrustedHosts, \
936                "The %s Attribute Authority has no trusted hosts" % \
937                self.__prop['name']
938
939
940        if role is None:
941            # No role input - return all trusted hosts with their WSDL URIs
942            # and the remote roles they map to
943            #
944            # Nb. {}.fromkeys([...]).keys() is a fudge to get unique elements
945            # from a list i.e. convert the list elements to a dict eliminating
946            # duplicated elements and convert the keys back into a list.
947            trustedHostInfo = dict(\
948            [\
949                (\
950                    k, \
951                    {
952                        'aaURI':                v['aaURI'], \
953                        'aaDN':                 v['aaDN'], \
954                        'loginURI':             v['loginURI'], \
955                        'loginServerDN':        v['loginServerDN'], \
956                        'loginRequestServerDN': v['loginRequestServerDN'], \
957                        'role':        {}.fromkeys(\
958                            [role['remote'] for role in v['role']]\
959                        ).keys()
960                    }
961                ) for k, v in self.__mapConfig['trustedHosts'].items()
962            ])
963
964        else:           
965            # Get trusted hosts for given input local role       
966            try:
967                trustedHosts = self.__localRole2TrustedHost[role]
968            except:
969                raise AttAuthorityNoMatchingRoleInTrustedHosts, \
970                    'None of the trusted hosts have a mapping to the ' + \
971                    'input role "%s"' % role
972   
973   
974            # Get associated WSDL URI and roles for the trusted hosts
975            # identified and return as a dictionary indexed by host name
976            trustedHostInfo = dict(\
977   [(\
978        host, \
979        {
980            'aaURI': self.__mapConfig['trustedHosts'][host]['aaURI'],
981            'aaDN': self.__mapConfig['trustedHosts'][host]['aaDN'],
982            'loginURI': self.__mapConfig['trustedHosts'][host]['loginURI'],
983            'loginServerDN': \
984            self.__mapConfig['trustedHosts'][host]['loginServerDN'],
985            'loginRequestServerDN': \
986            self.__mapConfig['trustedHosts'][host]['loginRequestServerDN'],
987            'role': self.__localRole2RemoteRole[host][role]
988        }\
989    ) for host in trustedHosts])
990                         
991        return trustedHostInfo
992       
993       
994    #_________________________________________________________________________     
995    def mapRemoteRoles2LocalRoles(self, trustedHost, trustedHostRoles):
996        """Map roles of trusted hosts to roles for this data centre
997
998        @type trustedHost: string
999        @param trustedHost: name of external trusted data centre
1000        @type trustedHostRoles: list
1001        @param trustedHostRoles:   list of external roles to map
1002        @return: list of mapped roles"""
1003
1004        if not self.__remoteRole2LocalRole:
1005            raise AttAuthorityError, "Roles map is not set - ensure " + \
1006                                     "readMapConfig() has been called."
1007
1008
1009        # Check the host name is a trusted one recorded in the map
1010        # configuration
1011        if not self.__remoteRole2LocalRole.has_key(trustedHost):
1012            return []
1013
1014        # Add local roles, skipping if no mapping is found
1015        localRoles = []
1016        for trustedRole in trustedHostRoles:
1017            if trustedRole in self.__remoteRole2LocalRole[trustedHost]:
1018                localRoles.extend(\
1019                        self.__remoteRole2LocalRole[trustedHost][trustedRole])
1020               
1021        return localRoles
1022
1023
1024#_____________________________________________________________________________
1025from logging.handlers import RotatingFileHandler
1026
1027#_________________________________________________________________________
1028# Inherit directly from Logger
1029_loggerClass = logging.getLoggerClass()
1030class AttCertLog(_loggerClass, object):
1031    """Log each Attribute Certificate issued using a rotating file handler
1032    so that the number of files held can be managed"""
1033   
1034    def __init__(self, attCertFilePath, backUpCnt=1024):
1035        """Set up a rotating file handler to log ACs issued.
1036        @type attCertFilePath: string
1037        @param attCertFilePath: set where to store ACs.  Set from AttAuthority
1038        properties file.
1039       
1040        @type backUpCnt: int
1041        @param backUpCnt: set the number of files to store before rotating
1042        and overwriting old files."""
1043       
1044        # Inherit from Logger class
1045        super(AttCertLog, self).__init__(name='', level=logging.INFO)
1046                           
1047        # Set a format for messages so that only the content of the AC is
1048        # logged, nothing else.
1049        formatter = logging.Formatter(fmt="", datefmt="")
1050
1051        # maxBytes is set to one so that only one AC will be written before
1052        # rotation to the next file
1053        fileLog = RotatingFileHandler(attCertFilePath, 
1054                                      maxBytes=1, 
1055                                      backupCount=backUpCnt)
1056        fileLog.setFormatter(formatter)           
1057        self.addHandler(fileLog)
1058                       
1059#_____________________________________________________________________________
1060class AAUserRolesError(Exception):
1061    """Exception handling for NDG Attribute Authority User Roles interface
1062    class."""
1063
1064
1065#_____________________________________________________________________________
1066class AAUserRoles:
1067    """An abstract base class to define the user roles interface to an
1068    Attribute Authority.
1069
1070    Each NDG data centre should implement a derived class which implements
1071    the way user roles are provided to its representative Attribute Authority.
1072   
1073    Roles are expected to indexed by user Distinguished Name (DN).  They
1074    could be stored in a database or file."""
1075
1076    # User defined class may wish to specify a URI for a database interface or
1077    # path for a user roles configuration file
1078    def __init__(self, dbURI=None, filePath=None):
1079        """User Roles base class - derive from this class to define
1080        roles interface to Attribute Authority
1081       
1082        @type dbURI: string
1083        @param dbURI: database connection URI
1084        @type filePath: string
1085        @param filePath: file path for properties file containing settings
1086        """
1087        pass
1088
1089
1090    def userIsRegistered(self, userId):
1091        """Virtual method - Derived method should return True if user is known
1092        otherwise False
1093       
1094        Nb. this method is not used by AttAuthority class and so does NOT need
1095        to be implemented in a derived class.
1096       
1097        @type userId: string
1098        @param userId: user Distinguished Name to look up.
1099        @rtype: bool
1100        @return: True if user is registered, False otherwise"""
1101        raise NotImplementedError, \
1102            self.userIsRegistered.__doc__.replace('\n       ','')
1103
1104
1105    def getRoles(self, userId):
1106        """Virtual method - Derived method should return the roles for the
1107        given user's Id or else raise an exception
1108       
1109        @type userId: string
1110        @param userId: user identity e.g. user Distinguished Name
1111        @rtype: list
1112        @return: list of roles for the given user ID"""
1113        raise NotImplementedError, \
1114            self.getRoles.__doc__.replace('\n       ','')
1115                         
Note: See TracBrowser for help on using the repository browser.