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

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

ndg.security.server/setup.py:

ndg.security.server/setup.cfg:

  • removed EasyInstall? and build sections
  • reinstated tag_build - set to '_dews' - and tag_svn_revision

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

  • removed socket import and added os
  • added M2Crypto SSL support - works with Python client unit tests (required setting SSL v2 and 3 support)

but problems with WebSphere? client

ndg.security.server/ndg/security/server/AttAuthority/init.py,
ndg.security.server/ndg/security/server/conf/attAuthorityProperties.xml,
ndg.security.test/ndg/security/test/AttAuthority/siteAAttAuthorityProperties.xml,
ndg.security.test/ndg/security/test/AttAuthority/siteBAttAuthorityProperties.xml:

  • added sslKeyPwd setting for properties

ndg.security.server/ndg/security/server/MyProxy.py:

  • ensure cnHostPfx is reinitialised to if equal to None

ndg.security.common/setup.py:

  • added M2Crypto, ZSI and 4Suite to dependencies
  • revised dependency links to use NDG site, http://ndg.nerc.ac.uk/dist and ZSI sourceforge link taken

from pyGridWare settings. Latter won't work for PyXML but does work from command line ??

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

  • IMPORTANT FIX * - removed strip() from signed info digest calc - NOT needed and caused some problems

with verify.

  • 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#_____________________________________________________________________________
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                    'certFile',
91                    'keyFile',
92                    'keyPwd',
93                    'caCertFile',
94                    'clntCertFile',
95                    'attCertLifetime',
96                    'attCertNotBeforeOff',
97                    'attCertFilePfx',
98                    'attCertFileSfx',
99                    'mapConfigFile',
100                    'attCertDir',
101                    'dnSeparator',
102                    'userRolesModFilePath',
103                    'userRolesModName',
104                    'userRolesClassName',
105                    'userRolesPropFile')
106   
107    def __init__(self, propFilePath=None, bReadMapConfig=True):
108        """Create new NDG Attribute Authority instance
109
110        @type propFilePath: string
111        @keyword propFilePath: path to file containing Attribute Authority
112        configuration parameters.  It defaults to $NDGSEC_AA_PROPFILEPATH or
113        if not set, $NDG_DIR/conf/attAuthorityProperties.xml
114        @type bReadMapConfig: boolean
115        @keyword bReadMapConfig: by default the Map Configuration file is
116        read.  Set this flag to False to override.
117        """
118
119        # Base class initialisation
120        dict.__init__(self)
121
122        # Set from input or use defaults based or environment variables
123        self.setPropFilePath(propFilePath)
124
125        # Initialise role mapping look-ups - These are set in readMapConfig()
126        self.__mapConfig = None
127        self.__localRole2RemoteRole = None
128        self.__remoteRole2LocalRole = None
129
130
131        # Configuration file properties are held together in a dictionary
132        self.__prop = {}
133
134        # Read Attribute Authority Properties file
135        self.readProperties()
136
137        # Read the Map Configuration file
138        if bReadMapConfig:
139            self.readMapConfig()
140
141        # Instantiate Certificate object
142        self.__cert = X509Cert(self.__prop['certFile'])
143        self.__cert.read()
144
145        # Check it's valid
146        try:
147            self.__cert.isValidTime(raiseExcep=True)
148           
149        except Exception, e:
150            raise AttAuthorityError, \
151                    "Attribute Authority's certificate is invalid: " + str(e)
152       
153        # Check CA certificate
154        caCert = X509Cert(self.__prop['caCertFile'])
155        caCert.read()
156       
157        try:
158            caCert.isValidTime(raiseExcep=True)
159           
160        except Exception, e:
161            raise AttAuthorityError, "CA certificate is invalid: " + str(e)
162       
163        # Issuer details - serialise using the separator string set in the
164        # properties file
165        self.__issuer = \
166            self.__cert.dn.serialise(separator=self.__prop['dnSeparator'])
167
168        self.__issuerSerialNumber = self.__cert.serialNumber
169       
170       
171        # Load host sites custom user roles interface to enable the AA to
172        # assign roles in an attribute certificate on a getAttCert request
173        self.loadUserRolesInterface()
174       
175
176    #_________________________________________________________________________
177    def loadUserRolesInterface(self):
178        """Set-up user roles interface - load host sites custom AAUserRoles
179        derived class.  This class interfaces with the sites mechanism for
180        mapping user ID to the roles to which they are entitled.  This
181        could be via a user database"""
182
183        try:
184            try:
185                # Module file path may be None if the new module to be loaded
186                # can be found in the existing system path           
187                if self.__prop['userRolesModFilePath'] is not None:
188                    if not os.path.exists(\
189                              self.__prop['userRolesModFilePath']):
190                        raise Exception, "File path '%s' doesn't exist" % \
191                              self.__prop['userRolesModFilePath']
192                             
193                    # Temporarily extend system path ready for import
194                    sysPathBak = sys.path[:]
195                             
196                    sys.path.append(self.__prop['userRolesModFilePath'])
197               
198                # Import module name specified in properties file
199                userRolesMod = __import__(self.__prop['userRolesModName'],
200                                          globals(),
201                                          locals(),
202                                          [self.__prop['userRolesClassName']])
203   
204                userRolesClass = eval('userRolesMod.' + \
205                                     self.__prop['userRolesClassName'])
206            finally:
207                try:
208                    sys.path[:] = sysPathBak
209                except NameError:
210                    # sysPathBak may not have been defined
211                    pass
212                               
213        except Exception, e:
214            raise AttAuthorityError,'Importing User Roles module: %s' % str(e)
215
216        # Check class inherits from AAUserRoles abstract base class
217        if not issubclass(userRolesClass, AAUserRoles):
218            raise AttAuthorityError, \
219                "User Roles class %s must be derived from AAUserRoles" % \
220                self.__prop['userRolesClassName']
221
222
223        # Instantiate custom class
224        try:
225            self.__userRoles = userRolesClass(self.__prop['userRolesPropFile'])
226           
227        except Exception, e:
228            raise AttAuthorityError, \
229                "Error instantiating User Roles interface: " + str(e)
230     
231       
232    #_________________________________________________________________________
233    # Methods for Attribute Authority dictionary like behaviour       
234    def __delitem__(self, key):
235        self.__class__.__name__ + " keys cannot be removed"       
236        raise KeyError, 'Keys cannot be deleted from '+self.__class__.__name__
237
238
239    def __getitem__(self, key):
240        self.__class__.__name__ + """ behaves as data dictionary of Attribute
241        Authority properties
242        """
243        if key not in self.__prop:
244            raise KeyError, "Invalid key '%s'" % key
245       
246        return self.__prop[key]
247       
248
249    def clear(self):
250        raise KeyError, "Data cannot be cleared from "+self.__class__.__name__
251   
252    def keys(self):
253        return self.__prop.keys()
254
255    def items(self):
256        return self.__prop.items()
257
258    def values(self):
259        return self.__prop.values()
260
261    def has_key(self, key):
262        return self.__prop.has_key(key)
263
264    # 'in' operator
265    def __contains__(self, key):
266        return key in self.__prop
267
268
269    def setPropFilePath(self, val=None):
270        """Set properties file from input or based on environment variable
271        settings"""
272        if not val:
273            if 'NDGSEC_AA_PROPFILEPATH' in os.environ:
274                val = os.environ['NDGSEC_AA_PROPFILEPATH']
275               
276            elif 'NDG_DIR' in os.environ:
277                val = os.path.join(os.environ['NDG_DIR'], 
278                                   self.__class__.__confDir,
279                                   self.__class__.__propFileName)
280            else:
281                raise AttributeError, 'Unable to set default Attribute ' + \
282                    'Authority properties file path: neither ' + \
283                    '"NDGSEC_AA_PROPFILEPATH" or "NDG_DIR" environment ' + \
284                    'variables are set'
285               
286        if not isinstance(val, basestring):
287            raise AttributeError, "Input Properties file path " + \
288                                  "must be a valid string."
289     
290        self.__propFilePath = val
291       
292    # Also set up as a property
293    propFilePath = property(fset=setPropFilePath,
294                            doc="Set the path to the properties file")   
295   
296   
297    #_________________________________________________________________________
298    def getAttCert(self,
299                   userId=None,
300                   holderCert=None,
301                   holderCertFilePath=None,
302                   userAttCert=None,
303                   userAttCertFilePath=None):
304
305        """Request a new Attribute Certificate for use in authorisation
306
307        getAttCert([userId=uid][holderCert=px|holderCertFilePath=pxFile, ]
308                   [userAttCert=cert|userAttCertFilePath=certFile])
309         
310        @type userId: string
311        @keyword userId: identifier for the user who is entitled to the roles
312        in the certificate that is issued.  If this keyword is omitted, then
313        the userId will be set to the DN of the holder.
314       
315        holder = the holder of the certificate - an inidividual user or an
316        organisation to which the user belongs who vouches for that user's ID
317       
318        userId = the identifier for the user who is entitled to the roles
319        specified in the Attribute Certificate that is issued.
320                 
321        @type holderCert: string / ndg.security.common.X509.X509Cert type
322        @keyword holderCert: base64 encoded string containing proxy cert./
323        X.509 cert object corresponding to the ID who will be the HOLDER of
324        the Attribute Certificate that will be issued.  - Normally, using
325        proxy certificates, the holder and user ID are the same but there
326        may be cases where the holder will be an organisation ID.  This is the
327        case for NDG security with the DEWS project
328       
329        @keyword holderCertFilePath: string
330        @param holderCertFilePath: file path to proxy/X.509 certificate of
331        candidate holder
332     
333        @type userAttCert: string or AttCert type
334        @keyword userAttCert: externally provided attribute certificate from
335        another data centre.  This is only necessary if the user is not
336        registered with this attribute authority.
337                       
338        @type userAttCertFilePath: string
339        @keyword userAttCertFilePath: alternative to userAttCert except pass
340        in as a file path to an attribute certificate instead.
341       
342        @rtype: AttCert
343        @return: new attribute certificate"""
344
345
346        # Read X.509 certificate
347        try:           
348            if holderCertFilePath is not None:
349                                   
350                # Proxy Certificate input as a file
351                holderCert = X509Cert()
352                holderCert.read(holderCertFilePath)
353               
354            elif isinstance(holderCert, basestring):
355
356                # Proxy Certificate input as string text
357                holderCert = X509CertParse(holderCert)
358               
359            elif not isinstance(holderCert, X509Cert):
360                raise AttAuthorityError, \
361                "No input X.509 certificate file path or cert text/object set"
362           
363        except Exception, e:
364            raise AttAuthorityError, "User certificate: %s" % e
365
366
367        # Check proxy certificate hasn't expired
368        try:
369            holderCert.isValidTime(raiseExcep=True)
370           
371        except Exception, e:
372            raise AttAuthorityError, "User Proxy Certificate is invalid: " + \
373                                    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 proxy 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 proxy 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
568        try:
569            tree = ElementTree.parse(self.__propFilePath)
570           
571        except IOError, ioErr:
572            raise AttAuthorityError, \
573                                "Error parsing properties file \"%s\": %s" % \
574                                (ioErr.filename, ioErr.strerror)
575
576       
577        aaProp = tree.getroot()
578        if aaProp is None:
579            raise AttAuthorityError, \
580            "Parsing properties file \"%s\": root element is not defined" % \
581            self.__propFilePath
582
583
584        # Copy properties from file into a dictionary
585        self.__prop = {}
586        invalidKeys = []
587        try:
588            for elem in aaProp:
589                if elem.tag in self.__class__.__validKeys:
590               
591                    # Make sure to leave password element contents unchanged
592                    if 'eyPwd' not in elem.tag and elem.text: 
593                        self.__prop[elem.tag] = \
594                                        os.path.expandvars(elem.text.strip())
595                    else:
596                        self.__prop[elem.tag] = elem.text
597                else:
598                    invalidKeys.append(elem.tag)
599               
600        except Exception, e:
601            raise AttAuthorityError, \
602                "Error parsing tag \"%s\" in properties file \"%s\": %s" % \
603                (elem.tag, self.__propFilePath, e)
604 
605        if invalidKeys != []:
606            raise AttAuthorityError, "The following properties file " + \
607                                     "elements are invalid: " + \
608                                     ', '.join(invalidKeys)
609 
610        # Ensure Certificate time parameters are converted to numeric type
611        self.__prop['attCertLifetime'] = float(self.__prop['attCertLifetime'])
612        self.__prop['attCertNotBeforeOff'] = \
613                                    float(self.__prop['attCertNotBeforeOff'])
614
615        # Likewise port number
616        self.__prop['portNum'] = int(self.__prop['portNum'])
617       
618       
619        # Check directory path
620        try:
621            dirList = os.listdir(self.__prop['attCertDir'])
622
623        except OSError, osError:
624            raise AttAuthorityError, \
625            'Invalid directory path Attribute Certificates store "%s": %s' % \
626                (self.__prop['attCertDir'], osError.strerror)
627       
628       
629    #_________________________________________________________________________     
630    def readMapConfig(self, mapConfigFilePath=None):
631        """Parse Map Configuration file.
632
633        @type mapConfigFilePath: string
634        @keyword mapConfigFilePath: file path for map configuration file.  If
635        omitted, it uses member variable __prop['mapConfigFile'].
636        """
637       
638        if mapConfigFilePath is not None:
639            if not isinstance(mapConfigFilePath, basestring):
640                raise AttAuthorityError, \
641                "Input Map Configuration file path must be a valid string."
642           
643            self.__prop['mapConfigFile'] = mapConfigFilePath
644
645
646        try:
647            tree = ElementTree.parse(self.__prop['mapConfigFile'])
648            rootElem = tree.getroot()
649           
650        except IOError, e:
651            raise AttAuthorityError, \
652                            "Error parsing properties file \"%s\": %s" % \
653                            (e.filename, e.strerror)
654           
655        except Exception, e:
656            raise AttAuthorityError, \
657                "Error parsing Map Configuration file: \"%s\": %s" % \
658                (self.__prop['mapConfigFile'], e)
659
660           
661        trustedElem = rootElem.findall('trusted')
662        if not trustedElem: 
663            # Make an empty list so that for loop block below is skipped
664            # without an error 
665            trustedElem = ()
666
667        # Dictionaries:
668        # 1) to hold all the data
669        self.__mapConfig = {'thisHost': {}, 'trustedHosts': {}}
670
671        # ... look-up
672        # 2) hosts corresponding to a given role and
673        # 3) roles of external data centre to this data centre
674        self.__localRole2TrustedHost = {}
675        self.__localRole2RemoteRole = {}
676        self.__remoteRole2LocalRole = {}
677
678
679        # Information about this host
680        try:
681            thisHostElem = rootElem.findall('thisHost')[0]
682           
683        except Exception, e:
684            raise AttAuthorityError, \
685            "\"thisHost\" tag not found in Map Configuration file \"%s\"" % \
686            self.__prop['mapConfigFile']
687
688        try:
689            hostName = thisHostElem.attrib.values()[0]
690           
691        except Exception, e:
692            raise AttAuthorityError, "\"name\" attribute of \"thisHost\" " + \
693                        "tag not found in Map Configuration file \"%s\"" % \
694                        self.__prop['mapConfigFile']
695
696
697        # hostname is also stored in the AA's config file in the 'name' tag. 
698        # Check the two match as the latter is copied into Attribute
699        # Certificates issued by this AA
700        #
701        # TODO: would be better to rationalise this so that the hostname is
702        # stored in one place only.
703        #
704        # P J Kershaw 14/06/06
705        if hostName != self.__prop['name']:
706            raise AttAuthorityError, "\"name\" attribute of \"thisHost\" " + \
707                "element in Map Configuration file doesn't match " + \
708                "\"name\" element in properties file."
709       
710        self.__mapConfig['thisHost'][hostName] = \
711        {
712            'loginURI':     thisHostElem.findtext('loginURI'),
713            'aaURI':         thisHostElem.findtext('aaURI')
714        }       
715       
716       
717        # Information about trusted hosts
718        for elem in trustedElem:
719
720            roleElem = elem.findall('role')
721            if not roleElem:
722                raise AttAuthorityError("\"role\" tag not found in \"%s\"" % \
723                                        self.__prop['mapConfigFile'])
724
725            try:
726                trustedHost = elem.attrib.values()[0]
727               
728            except Exception, e:
729                raise AttAuthorityError, \
730                                    "Error reading trusted host name: %s" % e
731
732           
733            # Add signatureFile and list of roles
734            #
735            # (Currently Optional) additional tag allows query of the URI
736            # where a user would normally login at the trusted host.  Added
737            # this feature to allow users to be forwarded to their home site
738            # if they are accessing a secure resource and are not
739            # authenticated
740            #
741            # P J Kershaw 25/05/06
742            self.__mapConfig['trustedHosts'][trustedHost] = \
743            {
744                'loginURI':     elem.findtext('loginURI'),
745                'aaURI':         elem.findtext('aaURI'),
746                'role':         [dict(i.items()) for i in roleElem]
747            }
748
749                   
750            self.__localRole2RemoteRole[trustedHost] = {}
751            self.__remoteRole2LocalRole[trustedHost] = {}
752           
753            for role in self.__mapConfig['trustedHosts'][trustedHost]['role']:
754
755                localRole = role['local']
756                remoteRole = role['remote']
757               
758                # Role to host look-up
759                if localRole in self.__localRole2TrustedHost:
760                   
761                    if trustedHost not in \
762                       self.__localRole2TrustedHost[localRole]:
763                        self.__localRole2TrustedHost[localRole].\
764                                                        append(trustedHost)                       
765                else:
766                    self.__localRole2TrustedHost[localRole] = [trustedHost]
767
768
769                # Trusted Host to local role and trusted host to trusted role
770                # map look-ups
771                try:
772                    self.__remoteRole2LocalRole[trustedHost][remoteRole].\
773                                                            append(localRole)                 
774                except KeyError:
775                    self.__remoteRole2LocalRole[trustedHost][remoteRole] = \
776                                                                [localRole]
777                   
778                try:
779                    self.__localRole2RemoteRole[trustedHost][localRole].\
780                                                            append(remoteRole)                 
781                except KeyError:
782                    self.__localRole2RemoteRole[trustedHost][localRole] = \
783                                                                [remoteRole]                 
784       
785       
786    #_________________________________________________________________________     
787    def userIsRegistered(self, userId):
788        """Check a particular user is registered with the Data Centre that the
789        Attribute Authority represents
790       
791        Nb. this method is not used internally by AttAuthority class
792       
793        @type userId: string
794        @param userId: user identity - could be a X500 Distinguished Name
795        @rtype: bool
796        @return: True if user is registered, False otherwise"""
797        return self.__userRoles.userIsRegistered(userId)
798       
799       
800    #_________________________________________________________________________     
801    def getRoles(self, userId):
802        """Get the roles available to the registered user identified userId.
803
804        @type dn: string
805        @param dn: user identifier - could be a X500 Distinguished Name
806        @return: list of roles for the given user ID"""
807
808        # Call to AAUserRoles derived class.  Each Attribute Authority
809        # should define it's own roles class derived from AAUserRoles to
810        # define how roles are accessed
811        try:
812            return self.__userRoles.getRoles(userId)
813
814        except Exception, e:
815            raise AttAuthorityError, "Getting user roles: %s" % e
816       
817       
818    #_________________________________________________________________________     
819    def __getHostInfo(self):
820        """Return the host that this Attribute Authority represents: its ID,
821        the user login URI and WSDL address.  Call this method via the
822        'hostInfo' property
823       
824        @rtype: dict
825        @return: dictionary of host information derived from the map
826        configuration"""
827       
828        return self.__mapConfig['thisHost']
829       
830    hostInfo = property(fget=__getHostInfo, 
831                        doc="Return information about this host")
832       
833       
834    #_________________________________________________________________________     
835    def getTrustedHostInfo(self, role=None):
836        """Return a dictionary of the hosts that have trust relationships
837        with this AA.  The dictionary is indexed by the trusted host name
838        and contains AA service, login URIs and the roles that map to the
839        given input local role.
840
841        @type role: string
842        @keyword role: if set, return trusted hosts that having a mapping set
843        for this role.  If no role is input, return all the AA's trusted hosts
844        with all their possible roles
845
846        @rtype: dict
847        @return: dictionary of the hosts that have trust relationships
848        with this AA.  It returns an empty dictionary if role isn't
849        recognised"""
850                                         
851        if not self.__mapConfig or not self.__localRole2RemoteRole:
852            # This Attribute Authority has no trusted hosts
853            raise AttAuthorityNoTrustedHosts, \
854                "The %s Attribute Authority has no trusted hosts" % \
855                self.__prop['name']
856
857
858        if role is None:
859            # No role input - return all trusted hosts with their WSDL URIs
860            # and the remote roles they map to
861            #
862            # Nb. {}.fromkeys([...]).keys() is a fudge to get unique elements
863            # from a list i.e. convert the list elements to a dict eliminating
864            # duplicated elements and convert the keys back into a list.
865            trustedHostInfo = dict(\
866            [\
867                (\
868                    k, \
869                    {
870                        'aaURI':       v['aaURI'], \
871                        'loginURI':    v['loginURI'], \
872                        'role':        {}.fromkeys(\
873                            [role['remote'] for role in v['role']]\
874                        ).keys()
875                    }
876                ) for k, v in self.__mapConfig['trustedHosts'].items()
877            ])
878
879        else:           
880            # Get trusted hosts for given input local role       
881            try:
882                trustedHosts = self.__localRole2TrustedHost[role]
883            except:
884                raise AttAuthorityNoMatchingRoleInTrustedHosts, \
885                    'None of the trusted hosts have a mapping to the ' + \
886                    'input role "%s"' % role
887   
888   
889            # Get associated WSDL URI and roles for the trusted hosts
890            # identified and return as a dictionary indexed by host name
891            trustedHostInfo = dict(\
892   [(\
893        host, \
894        {
895            'aaURI':     self.__mapConfig['trustedHosts'][host]['aaURI'],
896            'loginURI': self.__mapConfig['trustedHosts'][host]['loginURI'],
897            'role':     self.__localRole2RemoteRole[host][role]
898        }\
899    ) for host in trustedHosts])
900                         
901        return trustedHostInfo
902       
903       
904    #_________________________________________________________________________     
905    def mapRemoteRoles2LocalRoles(self, trustedHost, trustedHostRoles):
906        """Map roles of trusted hosts to roles for this data centre
907
908        @type trustedHost: string
909        @param trustedHost: name of external trusted data centre
910        @type trustedHostRoles: list
911        @param trustedHostRoles:   list of external roles to map
912        @return: list of mapped roles"""
913
914        if not self.__remoteRole2LocalRole:
915            raise AttAuthorityError, "Roles map is not set - ensure " + \
916                                     "readMapConfig() has been called."
917
918
919        # Check the host name is a trusted one recorded in the map
920        # configuration
921        if not self.__remoteRole2LocalRole.has_key(trustedHost):
922            return []
923
924        # Add local roles, skipping if no mapping is found
925        localRoles = []
926        for trustedRole in trustedHostRoles:
927            if trustedRole in self.__remoteRole2LocalRole[trustedHost]:
928                localRoles.extend(\
929                        self.__remoteRole2LocalRole[trustedHost][trustedRole])
930               
931        return localRoles
932       
933       
934    #_________________________________________________________________________     
935    def __newAttCertFilePath(self):
936        """Create a new unique attribute certificate file path
937       
938        @return: string file path"""
939       
940        attCertFd, attCertFilePath = \
941                   tempfile.mkstemp(suffix=self.__prop['attCertFileSfx'],
942                                    prefix=self.__prop['attCertFilePfx'],
943                                    dir=self.__prop['attCertDir'],
944                                    text=True)
945
946        # The file is opened - close using the file descriptor returned in the
947        # first element of the tuple
948        os.close(attCertFd)
949
950        # The file path is the 2nd element
951        return attCertFilePath
952
953
954#_____________________________________________________________________________
955class AAUserRolesError(Exception):
956    """Exception handling for NDG Attribute Authority User Roles interface
957    class."""
958
959
960#_____________________________________________________________________________
961class AAUserRoles:
962    """An abstract base class to define the user roles interface to an
963    Attribute Authority.
964
965    Each NDG data centre should implement a derived class which implements
966    the way user roles are provided to its representative Attribute Authority.
967   
968    Roles are expected to indexed by user Distinguished Name (DN).  They
969    could be stored in a database or file."""
970
971    # User defined class may wish to specify a URI for a database interface or
972    # path for a user roles configuration file
973    def __init__(self, dbURI=None, filePath=None):
974        """User Roles abstract base class - derive from this class to define
975        roles interface to Attribute Authority
976       
977        @type dbURI: string
978        @keyword dbURI: database connection URI
979        @type filePath: string
980        @keyword filePath: file path for properties file containing settings
981        """
982        raise NotImplementedError, \
983            self.__init__.__doc__.replace('\n       ','')
984
985
986    def userIsRegistered(self, userId):
987        """Derived method should return True if user is known otherwise
988        False
989       
990        Nb. this method is not used by AttAuthority class and so does NOT need
991        to be implemented in a derived class.
992       
993        @type userId: string
994        @param userId: user Distinguished Name to look up.
995        @rtype: bool
996        @return: True if user is registered, False otherwise"""
997        raise NotImplementedError, \
998            self.userIsRegistered.__doc__.replace('\n       ','')
999
1000
1001    def getRoles(self, userId):
1002        """Derived method should return the roles for the given user's
1003        Id or else raise an exception
1004       
1005        @type userId: string
1006        @param userId: user identity e.g. user Distinguished Name
1007        @rtype: list
1008        @return: list of roles for the given user ID"""
1009        raise NotImplementedError, \
1010            self.getRoles.__doc__.replace('\n       ','')
1011                         
Note: See TracBrowser for help on using the repository browser.