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

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

Create new utility module, ClassFactory? - to allow generic instantiation
of classes dynamically.

Implement use of this in the AttAuth? and SessionMgr? services + adjust
the config files for these accordingly + abstract use of MyProxy? in
SessionMgr? to generic authNService - and create packages with real
and test authN services. Adjust the SessionMgr? tests to use the
test authN service.

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