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

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