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

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

ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:
fix to caCertFilePathList input to SignatureHandler?. Correctly initialise
if not set.

ndg.security.server/ndg/security/server/AttAuthority/init.py:
Corrected error message text for where a user is not registered or no
mapping is available: ref. userId rather than AC holder DN to allow for the
case in DEWS where a userId distinct from a Proxy cert. DN is used.

ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
added test8GetMappedAttCertStressTest test for WebSphere? integration tests.
It makes multiple calls with different ACs input to check for errors in
signature or verification.

ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg:
added additional config params for the above.

ndg.security.test/ndg/security/test/MyProxy/myProxyProperties.xml and
ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
switched cert ID of test machine.

ndg.security.common/ndg/security/common/X509.py:

  • new X509Cert methods asDER and asPEM to convert to these formats.

toString now calls to asPEM

  • new class X509Stack to wrap M2Crypto.X509.X509_Stack. This includes an

extra method, verifyCertChain, to verify a chain of trust in the certs
contained in the stack.

  • standalone function, X509StackParseFromDER, wraps

M2Crypto.X509.new_stack_from_der

  • fix to X500DN class to enable correct parsing of proxy certificate DNs.

These have multiple CN entries. These are represented by changing the CN
dict entry to a tuple when initialised.

ndg.security.common/ndg/security/common/wsSecurity.py: changes to enable
handling of certificate chains in WSSE BinarySecurityToken? elements. This
will enable use of proxy certificates with signatures as their chain of
trust is proxy cert -> user cert -> CA cert rather than just cert -> CA cert.

types.

BinarySecurityToken? ValueType? to use

  • SignatureHandler?.init includes new signingCertChain keyword.
  • signingCertChain attribute of class enables setting of an X509Stack object

to assign to BinarySecurityToken?.

then Base 64 encode rather than converting into PEM and then having to
strip BEGIN CERT / END CERT delimiters.

to enable check of Canonicalization - REMOVE in future check in.

BinarySecurityToken? ValueTypes? - 'X509PKIPathv1', 'X509' and 'X509v3'

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