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

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

ndg.security.server/setup.py: added *.conf for conf/ openssl.conf file

ndg.security.server/ndg/security/server/AttAuthority/init.py:

  • Use RotatingFileHandler? from logging package to enable store of ACs issued to be limited. Properties file attCertFileLogCnt sets maximum number of files created before rotation.
  • newAttCertFilePath() is replaced by rotating file handler functionality
  • added logging with some debug messages - more needed to complete

ndg.security.server/ndg/security/server/conf/attCert/init.py

  • renamed to ndg.security.test/ndg/security/test/AttAuthority/attCertLog/init.py

ndg.security.server/ndg/security/server/conf/userRoles.py: userIsRegistered should return bool

ndg.security.server/ndg/security/server/conf/attAuthorityProperties.xml,
ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml:

  • filled in default values for most attributes to ease installation config tasks
  • attCertFilePfx and attCertFileSfx replaced with attCertFileName and attCertFileLog attributes for new AC logging.

ndg.security.client/ndg/security/client/ndgSessionClient.py: removed debug calls. This module may now be surplus because of Pylons framework and plans for Java and PHP clients.

ndg.security.test/ndg/security/test/AttAuthority/siteAUserRoles.py: added coapec for testing

ndg.security.test/ndg/security/test/AttAuthority/siteAMapConfig.xml: fix formatting

ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg: altered settings for tests

ndg.security.test/ndg/security/test/Log/LogTest.py: exptd with log config. Eventually change to be harness for SOAP log interface

Makefile: use default python + added force target.

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