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

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

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac:
modified soap_getAttCert to allow for unsigned client messages. If the
useSignatureHandler flag is not set, then the certificate passed in to
AttAuthority?.getAttCert is the userCert element of the SOAP message.

This is a useful capability if both client and service are behind a firewall
and message security is not required.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py,
python/ndg.security.server/ndg/security/server/conf/attAuthorityProperties.
xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml:
added useSignatureHandler element to list of elements in the properties file.
If this is not set, then the service will not apply signature or signature
verification to messages.

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py: use dictionary get() rather then [key] for signature keywords. This enables
them to be omitted in the config file so as to switch off the signature handler.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg: experimented with omitting signature PKI settings.

python/ndg.security.test/ndg/security/test/MyProxy/myProxyProperties.xml:
set serverCNprefix element to host/ for this MyProxy? installations server cert.

python/ndg.security.test/ndg/security/test/MyProxy/myProxyClientTest.cfg:
altered for account on this machine.

python/ndg.security.common/setup.py: slight change to Python 2.5 check for
ElementTree inclusion

python/ndg.security.common/ndg/security/common/AttAuthority/init.py:
SignatureHandler? is now optional. It's left as None if none of the signature
keywords are set via init. It can be set later as the signatureHandler
property now has set capability enabled.

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