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

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

python/ndg.security.server/setup.py:

  • comment out Twisted from install - won't do egg install
  • updated long description

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

  • added verifyingCertFilePath keyword to SignatureHandler? initialisation
  • added SSL capability

python/conf/attAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml,
python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
added element names for reading SSL settings from properties file.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
added verifyingCertFilePath keyword to SignatureHandler? initialisation

python/conf/sessionMgrProperties.xml,
python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrProperties.xml,
python/ndg.security.server/ndg/security/server/SessionMgr/init.py:
added clntCertFile properties file element name for setting certificate for
verifying incoming SOAP messages.

python/ndg.security.server/ndg/security/server/SessionMgr/Makefile:
corrected typo.

python/ndg.security.server/ndg/security/server/MyProxy.py:
Put OpenSSLConfig and OpenSSLConfigError classes into their own package
'openssl' so that they can also be used by the Certificate Authority client.

python/www/html/certificateAuthority.wsdl,
python/ndg.security.server/ndg/security/server/ca/CertificateAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/ca/CertificateAuthority_services_types.py,
python/ndg.security.common/ndg/security/common/ca/CertificateAuthority_services.py: updated operations to issueCert, revokeCert and getCRL.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg: changed address of service to connect to.

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
alternative username connection settings

python/ndg.security.common/ndg/security/common/AttAuthority/init.py:
fixed typos in error message and comments.

ython/ndg.security.common/ndg/security/common/XMLSec.py: changed call to
getAttributeNodeNS to getAttributeNode for retrieving reference element URI
attribute.

python/ndg.security.common/ndg/security/common/ca/init.py: code for
Certificate Authority client

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

  • tidied up imports
  • added properties for setting keywords to reference and SignedInfo? C14N
  • changed sign method so that it is truely configurable allow use of inclusive or exclusive C14N based on the keywords set for reference and SignedInfo? C14N calls.
  • swapped calls to getAttributeNodeNS with getAttributeNode where appropriate.

java/DEWS/AttAuthority/appClientModule/META-INF/ibm-webservicesclient-bnd.xmi,
java/DEWS/AttAuthority/build/classes/META-INF/ibm-webservicesclient-bnd.xmi:
updated to that request generator correctly places X.509 cert in
BinarySecurityToken? element.

java/DEWS/AttAuthority/appClientModule/Main.java,
java/DEWS/AttAuthority/appClientjava/DEWS/AttAuthority/appClientModule/META-INF/ibm-webservicesclient-bnd.xmiModule/Main.java:
include calls to getX509Cert and getAttCert methods.

java/DEWS/SessionMgr/build/classes/META-INF/ibm-webservicesclient-bnd.xmi,
java/DEWS/SessionMgr/appClientModule/META-INF/ibm-webservicesclient-bnd.xmi:
updates for testing Session MAnager client

java/DEWS/SessionMgr/appClientModule/Main.java: switched username setting.

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