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

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

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

python/www/html/attAuthority.wsdl,
python/ndg.security.server/ndg/security/server/AttAuthority/AttAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services_types.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services.py:
Include request denied message in getAttCertResponse.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py:
fix to AttAuthorityAccessDenied? doc message.

python/ndg.security.server/ndg/security/server/SessionMgr/server-config.tac:
Exlpicitly convert AttCert? in response to string type.

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

  • make explicit imports from ndg.security.common.CredWallet?
  • make X509CertParse import
  • updated exception handling for getAttCert call to CredWallet?.

python/www/html/sessionMgr.wsdl,
python/ndg.security.server/ndg/security/server/SessionMgr/SessionMgr_services_server.py,
python/ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services.py,
python/ndg.security.common/ndg/security/common/SessionMgr/SessionMgr_services_types.py:
Remove statusCode from getAttCertResponse - not needed.

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
minor updates to getAttCert tests.

python/ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
fix to test1Store settings

python/ndg.security.test/ndg/security/test/MyProxy/Makefile:
makefile copies proxy obtained from MyProxy? ready for use in AttAuthority? client tests.

python/ndg.security.test/ndg/security/test/SessionMgr/SessionMgrClientTest.py:

  • add AttributeRequestDenied? import from SessionMgr?.
  • fix test4CookieDisconnect signing PKI settings
  • revised output tuple for getAttCert calls.
  • Added test6aCookieGetAttCertRefused to demonstrate attribute request denied exception
  • test3ProxyCertConnect signature verification failing at server!

python/ndg.security.test/ndg/security/test/SessionMgr/sessionMgrClientTest.cfg:
added more getAttCert test params.

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

python/ndg.security.common/ndg/security/common/wsSecurity.py:
comment out all print statements - only 'print decryptedData' affected in decrypt method
of EncryptionHandler?. This is not in use.

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

  • Added AttributeRequestDenied? exception for handling getAttCert calls.
  • msg now included in output tuple for getAttCert call.

python/ndg.security.common/ndg/security/common/AttCert.py:
Override XMLSecDoc parent class toString and str calls so that output is returned even
if the signature DOM object has not been initialised.

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

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