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

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

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:

  • soap_getAttCert - no need to convert X509 cert object verifyingCert from

WSSecurityHandler.signatureHandler, it now X509Cert type.

  • instantiation of WSSecurityHandler.signatureHandler - updated cert and private key

file path keyword names.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
Fixed bug in AttAuthority?.init - readProperties doesn't take filePath keyword.

python/ndg.security.server/ndg/security/server/SessionMgr/init.py:

  • added epydoc keywords to header

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
updated keywords for call to AttAuthorityClient?.init to use new
WSSecurityHandler.signatureHandler ones.

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg and
python/ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg: modified
settings.

python/ndg.security.common/ndg/security/common/AttAuthority/init.py:
AttAuthorityClient? -

  • get rid of PKI keyword settings and set using

WSSecurityHandler.signatureHandler ones directly instead.

  • added signatureHandler property and associated getSignatureHandler method.

python/ndg.security.common/ndg/security/common/XMLSec.py: remove old commented out code
that used dom element getAttributeNodeNS method.

python/ndg.security.common/ndg/security/common/wsSecurity.py: modified SignatureHandler?
so that X.509 cert and private key settings are properties. X.509 certs are handled as
ndg.security.common.X509.X509Cert types.

python/ndg.security.common/ndg/security/common/SessionMgr/init.py:

WSSecurityHandler.signatureHandler ones.

  • disconnect input proxyCert renamed userCert. proxyCert will be included in WSSE

header. userCert is the user certificate corresponding to the issuer of the proxy.

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

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