source: TI12-security/trunk/python/ndg.security.server/ndg/security/server/attributeauthority.py @ 5648

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.server/ndg/security/server/attributeauthority.py@5648
Revision 5648, 63.7 KB checked in by pjkersha, 10 years ago (diff)

ndg.security.server.attributeauthority.AttributeAuthority?: added samlAttributeQuery method and new AttributeInterface?.getAttributes plugin class method to enable SAML support as need for ESG.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Attribute Authority server side code
2
3handles security user attribute (role) allocation
4
5NERC Data Grid Project
6"""
7__author__ = "P J Kershaw"
8__date__ = "15/04/05"
9__copyright__ = "(C) 2009 Science and Technology Facilities Council"
10__license__ = "BSD - see LICENSE file in top-level directory"
11__contact__ = "Philip.Kershaw@stfc.ac.uk"
12__revision__ = '$Id:attributeauthority.py 4367 2008-10-29 09:27:59Z pjkersha $'
13import logging
14log = logging.getLogger(__name__)
15
16import os
17import re
18
19# For parsing of properties file
20try: # python 2.5
21    from xml.etree import cElementTree as ElementTree
22except ImportError:
23    # if you've installed it yourself it comes this way
24    import cElementTree as ElementTree
25
26# SAML 2.0 Attribute Query Support - added 20/08/2009
27from uuid import uuid4
28from datetime import datetime, timedelta
29from saml.saml2.core import Response, Assertion, Attribute, AttributeValue, \
30    AttributeStatement, SAMLVersion, Subject, NameID, Issuer, AttributeQuery, \
31    XSStringAttributeValue, XSGroupRoleAttributeValue, Conditions, Status, \
32    StatusCode
33   
34from saml.common.xml import SAMLConstants
35from saml.xml.etree import AssertionElementTree, AttributeQueryElementTree, \
36    ResponseElementTree, XSGroupRoleAttributeValueElementTree
37
38from ndg.security.common.utils import TypedList
39from ndg.security.common.utils.classfactory import instantiateClass
40from ndg.security.common.utils.configfileparsers import \
41    CaseSensitiveConfigParser
42   
43# X.509 Certificate handling
44from ndg.security.common.X509 import X509Cert
45
46# NDG Attribute Certificate
47from ndg.security.common.AttCert import AttCert
48
49
50class AttributeAuthorityError(Exception):
51    """Exception handling for NDG Attribute Authority class."""
52    def __init__(self, msg):
53        log.error(msg)
54        Exception.__init__(self, msg)
55
56
57class AttributeAuthorityConfigError(Exception):
58    """NDG Attribute Authority error with configuration. e.g. properties file
59    directory permissions or role mapping file"""
60    def __init__(self, msg):
61        log.error(msg)
62        Exception.__init__(self, msg)
63       
64       
65class AttributeAuthorityAccessDenied(AttributeAuthorityError):
66    """NDG Attribute Authority - access denied exception.
67
68    Raise from getAttCert method where no roles are available for the user
69    but that the request is otherwise valid.  In all other error cases raise
70    AttributeAuthorityError"""   
71   
72   
73class AttributeAuthorityNoTrustedHosts(AttributeAuthorityError):
74    """Raise from getTrustedHosts if there are no trusted hosts defined in
75    the map configuration"""
76
77
78class AttributeAuthorityNoMatchingRoleInTrustedHosts(AttributeAuthorityError):
79    """Raise from getTrustedHosts if there is no mapping to any of the
80    trusted hosts for the given input role name"""
81
82
83class AttributeAuthority(object):
84    """NDG Attribute Authority - service for allocation of user authorization
85    tokens - attribute certificates.
86   
87    @type propertyDefaults: dict
88    @cvar propertyDefaults: valid configuration property keywords
89   
90    @type attributeInterfacePropertyDefaults: dict
91    @cvar attributeInterfacePropertyDefaults: valid configuration property
92    keywords for the Attribute Interface plugin
93   
94    @type mapConfigHostDefaults: dict
95    @cvar mapConfigHostDefaults: valid configuration property
96    keywords for the Map Configuration XML Host element
97   
98    @type DEFAULT_CONFIG_DIRNAME: string
99    @cvar DEFAULT_CONFIG_DIRNAME: configuration directory under $NDGSEC_DIR -
100    default location for properties file
101   
102    @type DEFAULT_PROPERTY_FILENAME: string
103    @cvar DEFAULT_PROPERTY_FILENAME: default file name for properties file
104    under DEFAULT_CONFIG_DIRNAME
105   
106    @type ATTRIBUTE_INTERFACE_KEYNAME: basestring
107    @param ATTRIBUTE_INTERFACE_KEYNAME: attribute interface parameters key
108    name - see initAttributeInterface for details
109    """
110
111    # Code designed from NERC Data Grid Enterprise and Information Viewpoint
112    # documents.
113    #
114    # Also, draws from Neil Bennett's ACServer class used in the Java
115    # implementation of NDG Security
116
117    DEFAULT_CONFIG_DIRNAME = "conf"
118    DEFAULT_PROPERTY_FILENAME = "attributeAuthority.cfg"
119    ATTRIBUTE_INTERFACE_KEYNAME = 'attributeInterface'
120    CONFIG_LIST_SEP_PAT = re.compile(',\W*')
121   
122    attributeInterfacePropertyDefaults = {
123        'modFilePath':  '',
124        'modName':      '',
125        'className':    ''
126    }
127   
128    # valid configuration property keywords with accepted default values. 
129    # Values set to not NotImplemented here denote keys which must be specified
130    # in the config
131    propertyDefaults = { 
132        'name':                 '',
133        'signingCertFilePath':  '',
134        'signingPriKeyFilePath':'',
135        'signingPriKeyPwd':     None,
136        'caCertFilePathList':   [],
137        'attCertLifetime':      -1,
138        'attCertNotBeforeOff':  0.,
139        'attCertFileName':      '',
140        'attCertFileLogCnt':    0,
141        'mapConfigFilePath':    '',
142        'attCertDir':           '',
143        'dnSeparator':          '/',
144        ATTRIBUTE_INTERFACE_KEYNAME: attributeInterfacePropertyDefaults
145    }
146   
147    mapConfigHostDefaults = {
148        'siteName':                 None,
149        'aaURI':                    NotImplemented,
150        'aaDN':                     NotImplemented,
151        'loginURI':                 NotImplemented,
152        'loginServerDN':            NotImplemented,
153        'loginRequestServerDN':     NotImplemented
154    }
155
156    def __init__(self):
157        """Create new Attribute Authority instance"""
158        log.info("Initialising service ...")
159       
160        # Initial config file property based attributes
161        for name, val in AttributeAuthority.propertyDefaults.items():
162            setattr(self, '_AttributeAuthority__%s' % name, val)
163       
164        self.__caCertFilePathList = TypedList(basestring)
165       
166        self.__propFilePath = None       
167        self.__propFileSection = 'DEFAULT'
168        self.__propPrefix = ''
169       
170        # Initialise role mapping look-ups - These are set in readMapConfig()
171        self.__mapConfig = None
172        self.__localRole2RemoteRole = None
173        self.__remoteRole2LocalRole = None
174       
175        self.__cert = None
176       
177        # Issuer details - serialise using the separator string set in the
178        # properties file
179        self.__issuer = None
180        self.__issuerSerialNumber = None
181        self.__attCertLog = None
182        self.__name = None
183       
184        self.__attributeInterfaceCfg = {}
185
186    def _getMapConfig(self):
187        return self.__mapConfig
188
189    def _getCert(self):
190        return self.__cert
191
192    def _getIssuer(self):
193        return self.__issuer
194
195    def _getIssuerSerialNumber(self):
196        return self.__issuerSerialNumber
197
198    def _getAttCertLog(self):
199        return self.__attCertLog
200
201    def _getName(self):
202        return self.__name
203
204    def _getAttCertLifetime(self):
205        return self.__attCertLifetime
206
207    def _getAttCertNotBeforeOff(self):
208        return self.__attCertNotBeforeOff
209
210    def _getAttCertDir(self):
211        return self.__attCertDir
212
213    def _getAttributeInterface(self):
214        return self.__attributeInterface
215
216    def _getMapConfigFile(self):
217        return self.__mapConfigFile
218
219    def _getName(self):
220        return self.__name
221
222    def _getTrustedHostInfo(self):
223        return self.__trustedHostInfo
224
225    def _setCert(self, value):
226        if not isinstance(value, X509Cert):
227            raise TypeError('Expecting %r type for "cert"; got %r' %
228                            (X509Cert, type(value)))
229           
230        self.__cert = value
231
232    def _setIssuer(self, value):
233        self.__issuer = value
234
235    def _setIssuerSerialNumber(self, value):
236        if not isinstance(value, (long, int)):
237            raise TypeError('Expecting long or int type for "name"; got %r' %
238                            type(value))
239        self.__issuerSerialNumber = value
240
241    def _setAttCertLog(self, value):
242        if not isinstance(value, AttCertLog):
243            raise TypeError('Expecting %r type for "attCertLog"; got %r' %
244                            (AttCertLog, type(value)))
245        self.__attCertLog = value
246
247    def _setName(self, value):
248        if not isinstance(value, basestring):
249            raise TypeError('Expecting string type for "name"; got %r' %
250                            type(value))
251        self.__name = value
252
253    def _setAttCertLifetime(self, value):
254        if isinstance(value, float):
255            self.__attCertLifetime = value
256           
257        elif isinstance(value, (basestring, int, long)):
258            self.__attCertLifetime = float(value)
259        else:
260            raise TypeError('Expecting float, int, long or string type for '
261                            '"attCertLifetime"; got %r' % type(value))
262
263    def _setAttCertNotBeforeOff(self, value):
264        if isinstance(value, float):
265            self.__attCertNotBeforeOff = value
266           
267        elif isinstance(value, (basestring, int, long)):
268            self.__attCertNotBeforeOff = float(value)
269        else:
270            raise TypeError('Expecting float, int, long or string type for '
271                            '"attCertNotBeforeOff"; got %r' % type(value))
272
273    def _setAttCertDir(self, value):
274        if not isinstance(value, basestring):
275            raise TypeError('Expecting string type for "attCertDir"; got %r' % 
276                            type(value))
277
278        # Check directory path
279        try:
280            dirList = os.listdir(value)
281
282        except OSError, osError:
283            raise AttributeAuthorityConfigError('Invalid directory path for '
284                                                'Attribute Certificates store '
285                                                '"%s": %s' % 
286                                                (value, osError.strerror))
287        self.__attCertDir = value
288
289    def _setMapConfigFilePath(self, value):
290        self.__mapConfigFilePath = value
291
292    def _setTrustedHostInfo(self, value):
293        self.__trustedHostInfo = value
294
295    def _get_caCertFilePathList(self):
296        return self.__caCertFilePathList
297
298    def _set_caCertFilePathList(self, val):
299        if not isinstance(val, (list, tuple)):
300            raise TypeError('Expecting list or tuple type for '
301                            '"caCertFilePathList"; got %r' % type(val))
302           
303        # Overwrite any original settings
304        self.__caCertFilePathList = TypedList(basestring)
305       
306        # Update with new items
307        self.__caCertFilePathList += val
308   
309    caCertFilePathList = property(fget=_get_caCertFilePathList,
310                                  fset=_set_caCertFilePathList,
311                                  doc="list of file paths for CA certificates "
312                                      "used to validate an Attribute "
313                                      "Certificate")
314   
315    def _get_signingCertFilePath(self):
316        return self.__signingCertFilePath
317   
318    def _set_signingCertFilePath(self, value):
319        if not isinstance(value, basestring):
320            raise TypeError('Expecting string type for "signingCertFilePath"; '
321                            'got %r' % type(value))
322        self.__signingCertFilePath = value
323         
324    signingCertFilePath = property(fget=_get_signingCertFilePath, 
325                                   fset=_set_signingCertFilePath,
326                                   doc="X.509 certificate used for Attribute "
327                                       "certificate signature")
328   
329    def _get_signingPriKeyFilePath(self):
330        return self.__signingPriKeyFilePath
331   
332    def _set_signingPriKeyFilePath(self, value):
333        if not isinstance(value, basestring):
334            raise TypeError('Expecting string type for '
335                            '"signingPriKeyFilePath"; got %r' % type(value))
336        self.__signingPriKeyFilePath = value
337         
338    signingPriKeyFilePath = property(fget=_get_signingPriKeyFilePath, 
339                                     fset=_set_signingPriKeyFilePath,
340                                     doc="File Path for private key used to "
341                                         "sign Attribute certificate")
342   
343    def _get_signingPriKeyPwd(self):
344        return self.__signingPriKeyPwd
345   
346    def _set_signingPriKeyPwd(self, value):
347        if not isinstance(value, (type(None), basestring)):
348            raise TypeError('Expecting string or None type for '
349                            '"signingPriKeyPwd"; got %r' % type(value))
350        self.__signingPriKeyPwd = value
351         
352    signingPriKeyPwd = property(fget=_get_signingPriKeyPwd, 
353                                fset=_set_signingPriKeyPwd,
354                                doc="Password for private key file used to "
355                                    "for Attribute certificate signature")
356
357    def _get_attributeInterfaceCfg(self):
358        return self.__attributeInterfaceCfg
359   
360    attributeInterfaceCfg = property(fget=_get_attributeInterfaceCfg,
361                                     doc="Settings for Attribute Interface "
362                                         "initialisation")
363   
364    def _get_attCertFileName(self):
365        return self.__attCertFileName
366   
367    def _set_attCertFileName(self, value):
368        if not isinstance(value, basestring):
369            raise TypeError('Expecting string type for "attCertFileName"; got '
370                            '%r' % type(value))
371           
372        self.__attCertFileName = value
373         
374    attCertFileName = property(fget=_get_attCertFileName, 
375                                fset=_set_attCertFileName,
376                                doc="Attribute certificate file name for log "
377                                    "initialisation")
378   
379    def _get_attCertFileLogCnt(self):
380        return self.__attCertFileLogCnt
381   
382    def _set_attCertFileLogCnt(self, value):
383        if isinstance(value, int):
384            self.__attCertFileLogCnt = value
385        elif isinstance(value, basestring):
386            self.__attCertFileLogCnt = int(value)
387        else:
388            raise TypeError('Expecting int or string type for '
389                            '"attCertFileLogCnt"; got %r' % type(value))
390         
391    attCertFileLogCnt = property(fget=_get_attCertFileLogCnt, 
392                                 fset=_set_attCertFileLogCnt,
393                                 doc="Counter for Attribute Certificate log "
394                                     "rotating file handler")
395   
396    def _get_dnSeparator(self):
397        return self.__dnSeparator
398   
399    def _set_dnSeparator(self, value):
400        if not isinstance(value, basestring):
401            raise TypeError('Expecting string type for "dnSeparator"; got '
402                            '%r' % type(value))
403        self.__dnSeparator = value
404         
405    dnSeparator = property(fget=_get_dnSeparator, 
406                           fset=_set_dnSeparator,
407                           doc="Distinguished Name separator character used "
408                               "with X.509 Certificate issuer certificate")
409           
410    def _getMapConfigFilePath(self):
411        return self.__mapConfigFilePath
412   
413    def _setMapConfigFilePath(self, val):
414        if not isinstance(val, basestring):
415            raise AttributeAuthorityConfigError("Input Map Configuration "
416                                                "file path must be a "
417                                                "valid string.")
418        self.__mapConfigFilePath = val
419         
420    mapConfigFilePath = property(fget=_getMapConfigFilePath,
421                                 fset=_setMapConfigFilePath,
422                                 doc="File path for Role Mapping configuration") 
423
424    def setPropFilePath(self, val=None):
425        """Set properties file from input or based on environment variable
426        settings
427       
428        @type val: basestring
429        @param val: properties file path"""
430        log.debug("Setting property file path")
431        if not val:
432            if 'NDGSEC_AA_PROPFILEPATH' in os.environ:
433                val = os.environ['NDGSEC_AA_PROPFILEPATH']
434               
435            elif 'NDGSEC_DIR' in os.environ:
436                val = os.path.join(os.environ['NDGSEC_DIR'], 
437                                   AttributeAuthority.DEFAULT_CONFIG_DIRNAME,
438                                   AttributeAuthority.DEFAULT_PROPERTY_FILENAME)
439            else:
440                raise AttributeError('Unable to set default Attribute '
441                                     'Authority properties file path: neither '
442                                     '"NDGSEC_AA_PROPFILEPATH" or "NDGSEC_DIR"'
443                                     ' environment variables are set')
444               
445        if not isinstance(val, basestring):
446            raise AttributeError("Input Properties file path "
447                                 "must be a valid string.")
448     
449        self.__propFilePath = os.path.expandvars(val)
450        log.debug("Path set to: %s" % val)
451       
452    def getPropFilePath(self):
453        '''Get the properties file path
454       
455        @rtype: basestring
456        @return: properties file path'''
457        return self.__propFilePath
458       
459    # Also set up as a property
460    propFilePath = property(fset=setPropFilePath,
461                            fget=getPropFilePath,
462                            doc="path to file containing Attribute Authority "
463                                "configuration parameters.  It defaults to "
464                                "$NDGSEC_AA_PROPFILEPATH or if not set, "
465                                "$NDGSEC_DIR/conf/attributeAuthority.cfg")   
466   
467    def setPropFileSection(self, val=None):
468        """Set section name to read properties from ini file.  This is set from
469        input or based on environment variable setting
470        NDGSEC_AA_PROPFILESECTION
471       
472        @type val: basestring
473        @param val: section name"""
474        log.debug("Setting property file section name")
475        if not val:
476            val = os.environ.get('NDGSEC_AA_PROPFILESECTION', 'DEFAULT')
477               
478        if not isinstance(val, basestring):
479            raise AttributeError("Input Properties file section name "
480                                 "must be a valid string.")
481     
482        self.__propFileSection = val
483        log.debug("Properties file section set to: %s" % val)
484       
485    def getPropFileSection(self):
486        '''Get the section name to extract properties from an ini file -
487        DOES NOT apply to XML file properties
488       
489        @rtype: basestring
490        @return: section name'''
491        return self.__propFileSection
492       
493    # Also set up as a property
494    propFileSection = property(fset=setPropFileSection,
495                               fget=getPropFileSection,
496                               doc="Set the file section name for ini file "
497                                   "properties")   
498   
499    def setPropPrefix(self, val=None):
500        """Set prefix for properties read from ini file.  This is set from
501        input or based on environment variable setting
502        NDGSEC_AA_PROPFILEPREFIX
503       
504        DOES NOT apply to XML file properties
505       
506        @type val: basestring
507        @param val: section name"""
508        log.debug("Setting property file section name")
509        if val is None:
510            val = os.environ.get('NDGSEC_AA_PROPFILEPREFIX', 'DEFAULT')
511               
512        if not isinstance(val, basestring):
513            raise AttributeError("Input Properties file section name "
514                                 "must be a valid string.")
515     
516        self.__propPrefix = val
517        log.debug("Properties file section set to: %s" % val)
518       
519    def getPropPrefix(self):
520        '''Get the prefix name used for properties in an ini file -
521        DOES NOT apply to XML file properties
522       
523        @rtype: basestring
524        @return: section name'''
525        log.debug("Getting property file prefix")
526        return self.__propPrefix
527   
528       
529    # Also set up as a property
530    propPrefix = property(fset=setPropPrefix,
531                          fget=getPropPrefix,
532                          doc="Set a prefix for ini file properties")   
533   
534    mapConfig = property(fget=_getMapConfig, 
535                         doc="MapConfig object")
536
537    cert = property(fget=_getCert, 
538                    fset=_setCert, 
539                    doc="X.509 Issuer Certificate")
540
541    issuer = property(fget=_getIssuer, 
542                      fset=_setIssuer, 
543                      doc="Issuer name")
544
545    issuerSerialNumber = property(fget=_getIssuerSerialNumber, 
546                                  fset=_setIssuerSerialNumber, 
547                                  doc="Issuer Serial Number")
548
549    attCertLog = property(fget=_getAttCertLog,
550                          fset=_setAttCertLog, 
551                          doc="Attribute certificate logging object")
552
553    name = property(fget=_getName, 
554                    fset=_setName, 
555                    doc="Issuer organisation name")
556
557    attCertLifetime = property(fget=_getAttCertLifetime, 
558                               fset=_setAttCertLifetime, 
559                               doc="Attribute certificate lifetime")
560
561    attCertNotBeforeOff = property(fget=_getAttCertNotBeforeOff, 
562                                   fset=_setAttCertNotBeforeOff, 
563                                   doc="Attribute certificate clock skew in "
564                                       "seconds")
565
566    attCertDir = property(fget=_getAttCertDir, 
567                          fset=_setAttCertDir, 
568                          doc="Attribute certificate log directory")
569
570    attributeInterface = property(fget=_getAttributeInterface, 
571                                  doc="Attribute Interface object")
572
573    name = property(fget=_getName, fset=_setName, doc="Organisation Name")
574
575    trustedHostInfo = property(fget=_getTrustedHostInfo, 
576                               fset=_setTrustedHostInfo, 
577                               doc="Dictionary of trusted organisations")
578       
579    @classmethod
580    def fromPropertyFile(cls, propFilePath=None, propFileSection='DEFAULT',
581                         propPrefix='attributeauthority.', 
582                         bReadMapConfig=True):
583        """Create new NDG Attribute Authority instance from the property file
584        settings
585
586        @type propFilePath: string
587        @param propFilePath: path to file containing Attribute Authority
588        configuration parameters.  It defaults to $NDGSEC_AA_PROPFILEPATH or
589        if not set, $NDGSEC_DIR/conf/attributeAuthority.cfg
590        @type propFileSection: basestring
591        @param propFileSection: section of properties file to read from.
592        properties files
593        @type propPrefix: basestring
594        @param propPrefix: set a prefix for filtering attribute authority
595        property names - useful where properties are being parsed from a file
596        section containing parameter names for more than one application
597        @type bReadMapConfig: boolean
598        @param bReadMapConfig: by default the Map Configuration file is
599        read.  Set this flag to False to override.
600        """
601           
602        attributeAuthority = AttributeAuthority()
603        if propFileSection:
604            attributeAuthority.propFileSection = propFileSection
605           
606        if propPrefix:
607            attributeAuthority.propPrefix = propPrefix
608
609        attributeAuthority.propFilePath = propFilePath           
610        attributeAuthority.readProperties()
611        attributeAuthority.initialise(bReadMapConfig=bReadMapConfig)
612   
613        return attributeAuthority
614
615       
616    @classmethod
617    def fromProperties(cls, propPrefix='attributeauthority.', 
618                       bReadMapConfig=True, **prop):
619        """Create new NDG Attribute Authority instance from input property
620        keywords
621
622        @type propPrefix: basestring
623        @param propPrefix: set a prefix for filtering attribute authority
624        property names - useful where properties are being parsed from a file
625        section containing parameter names for more than one application
626        @type bReadMapConfig: boolean
627        @param bReadMapConfig: by default the Map Configuration file is
628        read.  Set this flag to False to override.
629        """
630        attributeAuthority = AttributeAuthority()
631        if propPrefix:
632            attributeAuthority.propPrefix = propPrefix
633               
634        attributeAuthority.setProperties(**prop)
635        attributeAuthority.initialise(bReadMapConfig=bReadMapConfig)
636       
637        return attributeAuthority
638   
639    def initialise(self, bReadMapConfig=True):
640        """Convenience method for set up of Attribute Interface, map
641        configuration and PKI"""
642       
643        # Read the Map Configuration file
644        if bReadMapConfig:
645            self.readMapConfig()
646
647        # Instantiate Certificate object
648        log.debug("Reading and checking Attribute Authority X.509 cert. ...")
649        self.cert = X509Cert.Read(self.signingCertFilePath)
650
651        # Check it's valid
652        try:
653            self.cert.isValidTime(raiseExcep=True)
654           
655        except Exception, e:
656            raise AttributeAuthorityError("Attribute Authority's certificate "
657                                          "is invalid: %s" % e)
658       
659        # Check CA certificate
660        log.debug("Reading and checking X.509 CA certificate ...")
661        for caCertFile in self.caCertFilePathList:
662            caCert = X509Cert(caCertFile)
663            caCert.read()
664           
665            try:
666                caCert.isValidTime(raiseExcep=True)
667               
668            except Exception, e:
669                raise AttributeAuthorityError('CA certificate "%s" is '
670                                              'invalid: %s'% (caCert.dn, e))
671       
672        # Issuer details - serialise using the separator string set in the
673        # properties file
674        self.issuer = self.cert.dn.serialise(separator=self.dnSeparator)
675
676        self.issuerSerialNumber = self.cert.serialNumber
677       
678        # Load user - user attribute look-up plugin
679        self.initAttributeInterface()
680       
681        attCertFilePath = os.path.join(self.attCertDir, self.attCertFileName)
682               
683        # Rotating file handler used for logging attribute certificates
684        # issued.
685        self.attCertLog = AttCertLog(attCertFilePath,
686                                     backUpCnt=self.attCertFileLogCnt)
687
688    def setProperties(self, **prop):
689        """Set configuration from an input property dictionary
690        @type prop: dict
691        @param prop: properties dictionary containing configuration items
692        to be set
693        """
694        lenPropPrefix = len(self.propPrefix)
695       
696        # '+ 1' allows for the dot separator
697        lenAttributeInterfacePrefix = len(
698                            AttributeAuthority.ATTRIBUTE_INTERFACE_KEYNAME) + 1
699       
700        for name, val in prop.items():
701            if name.startswith(self.propPrefix):
702                name = name[lenPropPrefix:]
703           
704            if name.startswith(AttributeAuthority.ATTRIBUTE_INTERFACE_KEYNAME):
705                name = name[lenAttributeInterfacePrefix:]
706                self.attributeInterfaceCfg[name] = val
707                continue
708           
709            if name not in AttributeAuthority.propertyDefaults:
710                raise AttributeError('Invalid attribute name "%s"' % name)
711           
712            if isinstance(val, basestring):
713                val = os.path.expandvars(val)
714           
715            if isinstance(AttributeAuthority.propertyDefaults[name], list):
716                val = AttributeAuthority.CONFIG_LIST_SEP_PAT.split(val)
717               
718            # This makes an implicit call to the appropriate property method
719            try:
720                setattr(self, name, val)
721            except AttributeError:
722                raise AttributeError("Can't set attribute \"%s\"" % name)         
723           
724    def readProperties(self):
725        '''Read the properties files and do some checking/converting of input
726        values
727        '''
728        if not os.path.isfile(self.propFilePath):
729            raise IOError('Error parsing properties file "%s": No such file' % 
730                          self.propFilePath)
731           
732        defaultItems = {'here': os.path.dirname(self.propFilePath)}
733       
734        cfg = CaseSensitiveConfigParser(defaults=defaultItems)
735        cfg.read(self.propFilePath)
736       
737        cfgItems = dict([(name, val) 
738                         for name, val in cfg.items(self.propFileSection)
739                         if name != 'here'])
740        self.setProperties(**cfgItems)
741
742    def initAttributeInterface(self):
743        '''Load host sites custom user roles interface to enable the AA to
744        # assign roles in an attribute certificate on a getAttCert request'''
745        classProperties = {}
746        classProperties.update(self.attributeInterfaceCfg)
747       
748        modName = classProperties.pop('modName')
749        className = classProperties.pop('className') 
750       
751        # file path may be omitted   
752        modFilePath = classProperties.pop('modFilePath', None) 
753                     
754        self.__attributeInterface = instantiateClass(modName,
755                                             className,
756                                             moduleFilePath=modFilePath,
757                                             objectType=AttributeInterface,
758                                             classProperties=classProperties)
759
760    def getAttCert(self,
761                   userId=None,
762                   holderX509Cert=None,
763                   holderX509CertFilePath=None,
764                   userAttCert=None,
765                   userAttCertFilePath=None):
766
767        """Request a new Attribute Certificate for use in authorisation
768
769        getAttCert([userId=uid][holderX509Cert=x509Cert|
770                    holderX509CertFilePath=x509CertFile, ]
771                   [userAttCert=cert|userAttCertFilePath=certFile])
772         
773        @type userId: string
774        @param userId: identifier for the user who is entitled to the roles
775        in the certificate that is issued.  If this keyword is omitted, then
776        the userId will be set to the DN of the holder.
777       
778        holder = the holder of the certificate - an inidividual user or an
779        organisation to which the user belongs who vouches for that user's ID
780       
781        userId = the identifier for the user who is entitled to the roles
782        specified in the Attribute Certificate that is issued.
783                 
784        @type holderX509Cert: string / ndg.security.common.X509.X509Cert type
785        @param holderX509Cert: base64 encoded string containing proxy cert./
786        X.509 cert object corresponding to the ID who will be the HOLDER of
787        the Attribute Certificate that will be issued.  - Normally, using
788        proxy certificates, the holder and user ID are the same but there
789        may be cases where the holder will be an organisation ID.  This is the
790        case for NDG security with the DEWS project
791       
792        @param holderX509CertFilePath: string
793        @param holderX509CertFilePath: file path to proxy/X.509 certificate of
794        candidate holder
795     
796        @type userAttCert: string or AttCert type
797        @param userAttCert: externally provided attribute certificate from
798        another data centre.  This is only necessary if the user is not
799        registered with this attribute authority.
800                       
801        @type userAttCertFilePath: string
802        @param userAttCertFilePath: alternative to userAttCert except pass
803        in as a file path to an attribute certificate instead.
804       
805        @rtype: AttCert
806        @return: new attribute certificate"""
807
808        log.debug("Calling getAttCert ...")
809       
810        # Read candidate Attribute Certificate holder's X.509 certificate
811        try:
812            if holderX509CertFilePath is not None:
813                                   
814                # Certificate input as a file
815                holderX509Cert = X509Cert()
816                holderX509Cert.read(holderX509CertFilePath)
817               
818            elif isinstance(holderX509Cert, basestring):
819
820                # Certificate input as string text
821                holderX509Cert = X509Cert.Parse(holderX509Cert)
822               
823            elif not isinstance(holderX509Cert, (X509Cert, None.__class__)):
824                raise AttributeAuthorityError("Holder X.509 Certificate must "
825                                              "be set to valid type: a file "
826                                              "path, string, X509 object or "
827                                              "None")           
828        except Exception, e:
829            log.error("Holder X.509 certificate: %s" % e)
830            raise
831
832
833        # Check certificate hasn't expired
834        if holderX509Cert:
835            log.debug("Checking candidate holder X.509 certificate ...")
836            try:
837                holderX509Cert.isValidTime(raiseExcep=True)
838               
839            except Exception, e:
840                log.error("User X.509 certificate is invalid: " + e)
841                raise
842
843           
844        # If no user ID is input, set id from holder X.509 certificate DN
845        # instead
846        if not userId:
847            if not holderX509Cert:
848                raise AttributeAuthorityError("If no user ID is set a holder "
849                                              "X.509 certificate must be "
850                                              "present")
851            try:
852                userId = holderX509Cert.dn.serialise(\
853                                         separator=self.dnSeparator) 
854            except Exception, e:
855                log.error("Setting user Id from holder certificate DN: %s" % e)
856                raise
857       
858        # Make a new Attribute Certificate instance passing in certificate
859        # details for later signing
860        attCert = AttCert()
861
862        # First certificate in list contains the public key corresponding to
863        # the private key
864        attCert.certFilePathList = [self.signingCertFilePath] + \
865                                                                self.caCertFilePathList
866             
867        # Check for expiry of each certificate                   
868        for x509Cert in attCert.certFilePathList:
869            X509Cert.Read(x509Cert).isValidTime(raiseExcep=True)
870                                                               
871        attCert.signingKeyFilePath = self.signingPriKeyFilePath
872        attCert.signingKeyPwd = self.signingPriKeyPwd
873       
874       
875        # Set holder's Distinguished Name if a holder X.509 certificate was
876        # input
877        if holderX509Cert:
878            try:
879                attCert['holder'] = holderX509Cert.dn.serialise(
880                                        separator=self.dnSeparator)           
881            except Exception, e:
882                 log.error("Holder X.509 Certificate DN: %s" % e)
883                 raise
884           
885        # Set Issuer details from Attribute Authority
886        issuerDN = self.cert.dn
887        try:
888            attCert['issuer'] = \
889                    issuerDN.serialise(separator=self.dnSeparator)           
890        except Exception, e:
891            log.error("Issuer X.509 Certificate DN: %s" % e)
892            raise 
893           
894        attCert['issuerName'] = self.name
895        attCert['issuerSerialNumber'] = self.issuerSerialNumber
896
897        attCert['userId'] = userId
898       
899        # Set validity time
900        try:
901            attCert.setValidityTime(
902                        lifetime=self.attCertLifetime,
903                        notBeforeOffset=self.attCertNotBeforeOff)
904
905            # Check against the holder X.509 certificate's expiry if set
906            if holderX509Cert:
907                dtHolderCertNotAfter = holderX509Cert.notAfter
908               
909                if attCert.getValidityNotAfter(asDatetime=True) > \
910                   dtHolderCertNotAfter:
911   
912                    # Adjust the attribute certificate's expiry date time
913                    # so that it agrees with that of the certificate
914                    # ... but also make ensure that the not before skew is
915                    # still applied
916                    attCert.setValidityTime(dtNotAfter=dtHolderCertNotAfter,
917                            notBeforeOffset=self.attCertNotBeforeOff)
918           
919        except Exception, e:
920            log.error("Error setting attribute certificate validity time: %s" %
921                      e)
922            raise 
923
924        # Check name is registered with this Attribute Authority - if no
925        # user roles are found, the user is not registered
926        userRoles = self.getRoles(userId)
927        if userRoles:
928            # Set as an Original Certificate
929            #
930            # User roles found - user is registered with this data centre
931            # Add roles for this user for this data centre
932            attCert.addRoles(userRoles)
933
934            # Mark new Attribute Certificate as an original
935            attCert['provenance'] = AttCert.origProvenance
936
937        else:           
938            # Set as a Mapped Certificate
939            #
940            # No roles found - user is not registered with this data centre
941            # Check for an externally provided certificate from another
942            # trusted data centre
943            if userAttCertFilePath:
944               
945                # Read externally provided certificate
946                try:
947                    userAttCert = AttCert.Read(userAttCertFilePath)
948                   
949                except Exception, e:
950                    raise AttributeAuthorityError("Reading external Attribute "
951                                                  "Certificate: %s" % e)                           
952            elif userAttCert:
953                # Allow input as a string but convert to
954                if isinstance(userAttCert, basestring):
955                    userAttCert = AttCert.Parse(userAttCert)
956                   
957                elif not isinstance(userAttCert, AttCert):
958                    raise AttributeAuthorityError(
959                        "Expecting userAttCert as a string or AttCert type")       
960            else:
961                raise AttributeAuthorityAccessDenied('User "%s" is not '
962                    'registered and no external attribute certificate is '
963                    'available to make a mapping.' % userId)
964
965
966            # Check it's an original certificate - mapped certificates can't
967            # be used to make further mappings
968            if userAttCert.isMapped():
969                raise AttributeAuthorityError("External Attribute Certificate "
970                                              "must have an original "
971                                              "provenance in order "
972                                              "to make further mappings.")
973
974
975            # Check it's valid and signed
976            try:
977                # Give path to CA cert to allow check
978                userAttCert.certFilePathList = self.caCertFilePathList
979                userAttCert.isValid(raiseExcep=True)
980               
981            except Exception, e:
982                raise AttributeAuthorityError("Invalid Remote Attribute "
983                                        "Certificate: " + str(e))       
984
985
986            # Check that's it's holder matches the candidate holder
987            # certificate DN
988            if holderX509Cert and userAttCert.holderDN != holderX509Cert.dn:
989                raise AttributeAuthorityError("User certificate and Attribute "
990                                        'Certificate DNs don\'t match: "%s"'
991                                        ' and "%s"' % (holderX509Cert.dn, 
992                                                       userAttCert.holderDN))
993           
994 
995            # Get roles from external Attribute Certificate
996            trustedHostRoles = userAttCert.roles
997
998
999            # Map external roles to local ones
1000            localRoles = self.mapRemoteRoles2LocalRoles(
1001                                                    userAttCert['issuerName'],
1002                                                    trustedHostRoles)
1003            if not localRoles:
1004                raise AttributeAuthorityAccessDenied("No local roles mapped "
1005                                               "to the %s roles: %s" % 
1006                                               (userAttCert['issuerName'], 
1007                                                ', '.join(trustedHostRoles)))
1008
1009            attCert.addRoles(localRoles)
1010           
1011           
1012            # Mark new Attribute Certificate as mapped
1013            attCert.provenance = AttCert.mappedProvenance
1014
1015            # Copy the user Id from the external AC
1016            attCert.userId = userAttCert.userId
1017           
1018            # End set mapped certificate block
1019
1020        try:
1021            # Digitally sign certificate using Attribute Authority's
1022            # certificate and private key
1023            attCert.applyEnvelopedSignature()
1024           
1025            # Check the certificate is valid
1026            attCert.isValid(raiseExcep=True)
1027           
1028            # Write out certificate to keep a record of it for auditing
1029            #attCert.write()
1030            self.__attCertLog.info(attCert)
1031           
1032            log.info('Issued an Attribute Certificate to "%s" with roles: '
1033                     '"%s"' % (userId, '", "'.join(attCert.roles)))
1034
1035            # Return the cert to caller
1036            return attCert
1037       
1038        except Exception, e:
1039            raise AttributeAuthorityError('New Attribute Certificate "%s": %s'%
1040                                          (attCert.filePath, e))
1041
1042    def samlAttributeQuery(self, attributeQuery):
1043        """Respond to SAML 2.0 Attribute Query
1044        """
1045        if not isinstance(attributeQuery, AttributeQuery):
1046            raise TypeError('Expecting %r for attribute query; got %r' %
1047                            (AttributeQuery, type(attributeQuery)))
1048           
1049        samlResponse = Response()
1050       
1051        samlResponse.issueInstant = datetime.utcnow()
1052        if self.attCertNotBeforeOff != 0:
1053            samlResponse.issueInstant += timedelta(
1054                                            seconds=self.attCertNotBeforeOff)
1055           
1056        samlResponse.id = str(uuid4())
1057        samlResponse.issuer = Issuer()
1058       
1059        samlResponse.status = Status()
1060        samlResponse.status.statusCode = StatusCode()
1061        samlResponse.status.statusCode.value = StatusCode.SUCCESS_URI       
1062       
1063        # Attribute Query validation ...
1064        utcNow = datetime.utcnow()
1065        if attributeQuery.issueInstant >= utcNow:
1066            msg = ('SAML Attribute Query issueInstant [%s] is at or after '
1067                   'the current clock time [%s]') % \
1068                   (attributeQuery.issueInstant, SAMLDateTime.toString(utcNow))
1069            log.error(msg)
1070                     
1071            samlResponse.status.statusCode.value = StatusCode.REQUESTER_URI
1072            samlResponse.status.statusMessage = msg
1073            return samlResponse
1074           
1075        elif attributeQuery.version < SAMLVersion.VERSION_20:
1076            samlResponse.status.statusCode.value = \
1077                                        StatusCode.REQUEST_VERSION_TOO_LOW_URI
1078            return samlResponse
1079       
1080        elif attributeQuery.version > SAMLVersion.VERSION_20:
1081            samlResponse.status.statusCode.value = \
1082                                        StatusCode.REQUEST_VERSION_TOO_HIGH_URI
1083            return samlResponse
1084       
1085        elif attributeQuery.subject.nameID.format != "urn:esg:openid":
1086            log.error('SAML Attribute Query subject format is "%r"; expecting '
1087                      '"%s"' % (attributeQuery.subject.nameID.format,
1088                                "urn:esg:openid"))
1089            samlResponse.status.statusCode.value = StatusCode.REQUESTER_URI
1090            samlResponse.status.statusMessage = \
1091                                "Subject Name ID format is not recognised"
1092            return samlResponse
1093       
1094        elif attributeQuery.issuer.format != "urn:esg:issuer":
1095            log.error('SAML Attribute Query subject format is "%r"; expecting '
1096                      '"%s"' % (attributeQuery.subject.nameID.format,
1097                                "urn:esg:issuer"))
1098            samlResponse.status.statusCode.value = StatusCode.REQUESTER_URI
1099            samlResponse.status.statusMessage = \
1100                                "Issuer Name ID format is not recognised"
1101            return samlResponse
1102       
1103        # Gather requested attributes and pass to attribute interface for
1104        # retrieval of values
1105        requestedAttributeNames = [attribute.name
1106                                   for attribute in attributeQuery.attributes]
1107       
1108        try:
1109            # Return a dictionary of name, value pairs
1110            attributes = self.getAttributes(attributeQuery.subject.nameID.value,
1111                                            requestedAttributeNames,
1112                                            attributeQuery.subject.nameID.value)
1113        except Exception, e:
1114            # TODO: exception handling for requested attributes not allowed
1115            # etc.
1116            pass
1117       
1118        # Nb. SAML 2.0 spec says issuer format must be omitted
1119        samlResponse.issuer.value = self.issuer
1120       
1121        samlResponse.inResponseTo = attributeQuery.id
1122       
1123        # Create a new assertion to hold the attributes to be returned
1124        assertion = Assertion()
1125       
1126        assertion.version = SAMLVersion(SAMLVersion.VERSION_20)
1127        assertion.id = str(uuid4())
1128        assertion.issueInstant = samlResponse.issueInstant
1129       
1130        assertion.conditions = Conditions()
1131        assertion.conditions.notBefore = assertion.issueInstant
1132        assertion.conditions.notOnOrAfter = assertion.conditions.notBefore + \
1133            timedelta(seconds=self.attCertLifetime)
1134       
1135        assertion.subject = Subject() 
1136        assertion.subject.nameID = NameID()
1137        assertion.subject.nameID.format = attributeQuery.subject.nameID.format
1138        assertion.subject.nameID.value = attributeQuery.subject.nameID.value
1139
1140        attributeStatement = AttributeStatement()
1141       
1142        for attrName, val in attributes.items():
1143            samlAttribute = Attribute()
1144            samlAttribute.name = attrName
1145            samlAttribute.nameFormat = samlAttribute.nameFormat
1146            samlAttribute.friendlyName = samlAttribute.friendlyName
1147           
1148            samlAttributeValue = XSStringAttributeValue()
1149            samlAttributeValue.value = val
1150            samlAttribute.attributeValues.append(samlAttributeValue)
1151            attributeStatement.attributes.append(samlAttribute)
1152 
1153        assertion.attributeStatements.append(attributeStatement)       
1154        samlResponse.assertions.append(assertion)
1155       
1156        # Add mapping for ESG Group/Role Attribute Value to enable ElementTree
1157        # Attribute Value factory to render the XML output
1158        toXMLTypeMap = {
1159            XSGroupRoleAttributeValue: XSGroupRoleAttributeValueElementTree
1160        }
1161
1162        return samlResponse
1163   
1164    def readMapConfig(self):
1165        """Parse Map Configuration file.
1166        """
1167       
1168        log.debug("Reading map configuration file ...")
1169       
1170        try:
1171            tree = ElementTree.parse(self.mapConfigFilePath)
1172            rootElem = tree.getroot()
1173           
1174        except IOError, e:
1175            raise AttributeAuthorityConfigError('Error parsing Map '
1176                                                'Configuration file "%s": %s' % 
1177                                                (e.filename, e.strerror))         
1178        except Exception, e:
1179            raise AttributeAuthorityConfigError('Error parsing Map '
1180                                                'Configuration file: "%s": %s'% 
1181                                                (self.mapConfigFilePath, e))
1182       
1183        trustedElem = rootElem.findall('trusted')
1184        if not trustedElem: 
1185            # Make an empty list so that for loop block below is skipped
1186            # without an error 
1187            trustedElem = ()
1188
1189        # Dictionaries:
1190        # 1) to hold all the data
1191        self.__mapConfig = {'thisHost': {}, 'trustedHosts': {}}
1192
1193        # ... look-up
1194        # 2) hosts corresponding to a given role and
1195        # 3) roles of external data centre to this data centre
1196        self.__localRole2TrustedHost = {}
1197        self.__localRole2RemoteRole = {}
1198        self.__remoteRole2LocalRole = {}
1199
1200        # Information about this host
1201        try:
1202            thisHostElem = rootElem.findall('thisHost')[0]
1203           
1204        except Exception, e:
1205            raise AttributeAuthorityConfigError('"thisHost" tag not found in '
1206                                                'Map Configuration file "%s"' % 
1207                                                self.mapConfigFilePath)
1208
1209        try:
1210            hostName = thisHostElem.attrib.values()[0]
1211           
1212        except Exception, e:
1213            raise AttributeAuthorityConfigError('"name" attribute of '
1214                                                '"thisHost" element not found '
1215                                                'in Map Configuration file '
1216                                                '"%s"' % 
1217                                                self.mapConfigFilePath)
1218
1219        # hostname is also stored in the AA's config file in the 'name' tag. 
1220        # Check the two match as the latter is copied into Attribute
1221        # Certificates issued by this AA
1222        #
1223        # TODO: would be better to rationalise this so that the hostname is
1224        # stored in one place only.
1225        #
1226        # P J Kershaw 14/06/06
1227        if hostName != self.name:
1228            raise AttributeAuthorityError('"name" attribute of "thisHost" '
1229                                          'element in Map Configuration file '
1230                                          'doesn\'t match "name" element in '
1231                                          'properties file.')
1232       
1233        # Information for THIS Attribute Authority
1234        self.__mapConfig['thisHost'][hostName] = {}
1235
1236        for k, v in AttributeAuthority.mapConfigHostDefaults.items():
1237            val = thisHostElem.findtext(k)
1238            if val is None and v == NotImplemented:
1239                raise AttributeAuthorityConfigError('<thisHost> option <%s> '
1240                                                    'must be set.' % k)
1241            self.__mapConfig['thisHost'][hostName][k] = val     
1242       
1243        # Information about trusted hosts
1244        for elem in trustedElem:
1245            try:
1246                trustedHost = elem.attrib.values()[0]
1247               
1248            except Exception, e:
1249                raise AttributeAuthorityConfigError('Error reading trusted '
1250                                                    'host name: %s' % e)
1251 
1252            # Add signatureFile and list of roles
1253            #
1254            # (Currently Optional) additional tag allows query of the URI
1255            # where a user would normally login at the trusted host.  Added
1256            # this feature to allow users to be forwarded to their home site
1257            # if they are accessing a secure resource and are not
1258            # authenticated
1259            #
1260            # P J Kershaw 25/05/06
1261            self.__mapConfig['trustedHosts'][trustedHost] = {}
1262            for k, v in AttributeAuthority.mapConfigHostDefaults.items():
1263                val = thisHostElem.findtext(k)
1264                if val is None and v == NotImplemented:
1265                    raise AttributeAuthorityConfigError('<trustedHost> option '
1266                                                        '<%s> must be set.'%k)
1267                   
1268                self.__mapConfig['trustedHosts'][trustedHost][k] = \
1269                                                        elem.findtext(k)   
1270
1271            roleElem = elem.findall('role')
1272            if roleElem:
1273                # Role keyword value requires special parsing before
1274                # assignment
1275                self.__mapConfig['trustedHosts'][trustedHost]['role'] = \
1276                                        [dict(i.items()) for i in roleElem]
1277            else:
1278                # It's possible for trust relationships to not contain any
1279                # role mapping.  e.g. a site's login service trusting other
1280                # sites login requests
1281                self.__mapConfig['trustedHosts'][trustedHost]['role'] = []
1282                       
1283            self.__localRole2RemoteRole[trustedHost] = {}
1284            self.__remoteRole2LocalRole[trustedHost] = {}
1285           
1286            for role in self.__mapConfig['trustedHosts'][trustedHost]['role']:
1287                try:
1288                    localRole = role['local']
1289                    remoteRole = role['remote']
1290                except KeyError, e:
1291                    raise AttributeAuthorityError('Reading map configuration '
1292                                                  ' file "%s": no element '
1293                                                  '"%s" for host "%s"' % 
1294                                                (self.mapConfigFilePath, 
1295                                                 e, 
1296                                                 trustedHost))
1297                   
1298                # Role to host look-up
1299                if localRole in self.__localRole2TrustedHost:
1300                   
1301                    if trustedHost not in \
1302                       self.__localRole2TrustedHost[localRole]:
1303                        self.__localRole2TrustedHost[localRole].\
1304                                                        append(trustedHost)                       
1305                else:
1306                    self.__localRole2TrustedHost[localRole] = [trustedHost]
1307
1308
1309                # Trusted Host to local role and trusted host to trusted role
1310                # map look-ups
1311                try:
1312                    self.__remoteRole2LocalRole[trustedHost][remoteRole].\
1313                                                            append(localRole)                 
1314                except KeyError:
1315                    self.__remoteRole2LocalRole[trustedHost][remoteRole] = \
1316                                                                [localRole]
1317                   
1318                try:
1319                    self.__localRole2RemoteRole[trustedHost][localRole].\
1320                                                            append(remoteRole)                 
1321                except KeyError:
1322                    self.__localRole2RemoteRole[trustedHost][localRole] = \
1323                                                                [remoteRole]
1324       
1325        # Store trusted host info look-up for retrieval by getTrustedHostInfo
1326        # method                                                                         
1327        #
1328        # Nb. {}.fromkeys([...]).keys() is a fudge to get unique elements
1329        # from a list i.e. convert the list elements to a dict eliminating
1330        # duplicated elements and convert the keys back into a list.
1331        self._trustedHostInfo = dict(
1332        [
1333            (
1334                k, 
1335                {
1336                    'siteName':             v['siteName'],
1337                    'aaURI':                v['aaURI'], 
1338                    'aaDN':                 v['aaDN'], 
1339                    'loginURI':             v['loginURI'], 
1340                    'loginServerDN':        v['loginServerDN'], 
1341                    'loginRequestServerDN': v['loginRequestServerDN'], 
1342                    'role':                 {}.fromkeys([role['remote'] 
1343                                                         for role in v['role']]
1344                                                       ).keys()
1345                }
1346            ) for k, v in self.__mapConfig['trustedHosts'].items()
1347        ])
1348
1349        log.info('Loaded map configuration file "%s"' % self.mapConfigFilePath)
1350       
1351       
1352    def getRoles(self, userId):
1353        """Get the roles available to the registered user identified userId.
1354
1355        @type dn: string
1356        @param dn: user identifier - could be a X500 Distinguished Name
1357        @return: list of roles for the given user ID"""
1358
1359        log.debug('Calling getRoles for user "%s" ...' % userId)
1360       
1361        # Call to AttributeInterface derived class.  Each Attribute Authority
1362        # should define it's own roles class derived from AttributeInterface to
1363        # define how roles are accessed
1364        try:
1365            return self.__attributeInterface.getRoles(userId)
1366
1367        except Exception, e:
1368            raise AttributeAuthorityError("Getting user roles: %s" % e)
1369       
1370       
1371    def _getHostInfo(self):
1372        """Return the host that this Attribute Authority represents: its ID,
1373        the user login URI and WSDL address.  Call this method via the
1374        'hostInfo' property
1375       
1376        @rtype: dict
1377        @return: dictionary of host information derived from the map
1378        configuration"""
1379       
1380        return self.__mapConfig['thisHost']
1381       
1382    hostInfo = property(fget=_getHostInfo, 
1383                        doc="Return information about this host")
1384       
1385       
1386    def getTrustedHostInfo(self, role=None):
1387        """Return a dictionary of the hosts that have trust relationships
1388        with this AA.  The dictionary is indexed by the trusted host name
1389        and contains AA service, login URIs and the roles that map to the
1390        given input local role.
1391
1392        @type role: string
1393        @param role: if set, return trusted hosts that having a mapping set
1394        for this role.  If no role is input, return all the AA's trusted hosts
1395        with all their possible roles
1396
1397        @rtype: dict
1398        @return: dictionary of the hosts that have trust relationships
1399        with this AA.  It returns an empty dictionary if role isn't
1400        recognised"""
1401               
1402        log.debug('Calling getTrustedHostInfo with role = "%s" ...' % role) 
1403                                 
1404        if not self.__mapConfig or not self.__localRole2RemoteRole:
1405            # This Attribute Authority has no trusted hosts
1406            raise AttributeAuthorityNoTrustedHosts("The %s Attribute "
1407                                                   "Authority has no trusted "
1408                                                   "hosts" % 
1409                                                   self.name)
1410
1411
1412        if role is None:
1413            # No role input - return all trusted hosts with their service URIs
1414            # and the remote roles they map to
1415            return self._trustedHostInfo
1416
1417        else:           
1418            # Get trusted hosts for given input local role       
1419            try:
1420                trustedHosts = self.__localRole2TrustedHost[role]
1421            except:
1422                raise AttributeAuthorityNoMatchingRoleInTrustedHosts(
1423                    'None of the trusted hosts have a mapping to the '
1424                    'input role "%s"' % role)
1425   
1426   
1427            # Get associated Web service URI and roles for the trusted hosts
1428            # identified and return as a dictionary indexed by host name
1429            trustedHostInfo = dict(
1430       [(
1431            host, 
1432            {
1433                'siteName': self.__mapConfig['trustedHosts'][host]['siteName'],
1434                'aaURI':    self.__mapConfig['trustedHosts'][host]['aaURI'],
1435                'aaDN':     self.__mapConfig['trustedHosts'][host]['aaDN'],
1436                'loginURI': self.__mapConfig['trustedHosts'][host]['loginURI'],
1437                'loginServerDN': 
1438                        self.__mapConfig['trustedHosts'][host]['loginServerDN'],
1439                'loginRequestServerDN': 
1440                self.__mapConfig['trustedHosts'][host]['loginRequestServerDN'],
1441                'role':     self.__localRole2RemoteRole[host][role]
1442            }
1443        ) for host in trustedHosts])
1444                         
1445            return trustedHostInfo
1446       
1447       
1448    def mapRemoteRoles2LocalRoles(self, trustedHost, trustedHostRoles):
1449        """Map roles of trusted hosts to roles for this data centre
1450
1451        @type trustedHost: string
1452        @param trustedHost: name of external trusted data centre
1453        @type trustedHostRoles: list
1454        @param trustedHostRoles:   list of external roles to map
1455        @return: list of mapped roles"""
1456
1457        if not self.__remoteRole2LocalRole:
1458            raise AttributeAuthorityError("Roles map is not set - ensure " 
1459                                    "readMapConfig() has been called.")
1460
1461
1462        # Check the host name is a trusted one recorded in the map
1463        # configuration
1464        if not self.__remoteRole2LocalRole.has_key(trustedHost):
1465            return []
1466
1467        # Add local roles, skipping if no mapping is found
1468        localRoles = []
1469        for trustedRole in trustedHostRoles:
1470            if trustedRole in self.__remoteRole2LocalRole[trustedHost]:
1471                localRoles.extend(
1472                        self.__remoteRole2LocalRole[trustedHost][trustedRole])
1473               
1474        return localRoles
1475
1476
1477from logging.handlers import RotatingFileHandler
1478
1479# Inherit directly from Logger
1480_loggerClass = logging.getLoggerClass()
1481class AttCertLog(_loggerClass, object):
1482    """Log each Attribute Certificate issued using a rotating file handler
1483    so that the number of files held can be managed"""
1484   
1485    def __init__(self, attCertFilePath, backUpCnt=1024):
1486        """Set up a rotating file handler to log ACs issued.
1487        @type attCertFilePath: string
1488        @param attCertFilePath: set where to store ACs.  Set from
1489        AttributeAuthority properties file.
1490       
1491        @type backUpCnt: int
1492        @param backUpCnt: set the number of files to store before rotating
1493        and overwriting old files."""
1494       
1495        if not isinstance(backUpCnt, int):
1496            raise TypeError('Expecting int type for "backUpCnt" keyword')
1497       
1498        # Inherit from Logger class
1499        super(AttCertLog, self).__init__(name='', level=logging.INFO)
1500                           
1501        # Set a format for messages so that only the content of the AC is
1502        # logged, nothing else.
1503        formatter = logging.Formatter(fmt="", datefmt="")
1504
1505        # maxBytes is set to one so that only one AC will be written before
1506        # rotation to the next file
1507        fileLog = RotatingFileHandler(attCertFilePath, 
1508                                      maxBytes=1, 
1509                                      backupCount=backUpCnt)
1510        fileLog.setFormatter(formatter)           
1511        self.addHandler(fileLog)
1512                       
1513class AttributeInterfaceError(Exception):
1514    """Exception handling for NDG Attribute Authority User Roles interface
1515    class."""
1516
1517
1518class AttributeInterface(object):
1519    """An abstract base class to define the user roles interface to an
1520    Attribute Authority.
1521
1522    Each NDG data centre should implement a derived class which implements
1523    the way user roles are provided to its representative Attribute Authority.
1524   
1525    Roles are expected to indexed by user Distinguished Name (DN).  They
1526    could be stored in a database or file."""
1527
1528    # User defined class may wish to specify a URI for a database interface or
1529    # path for a user roles configuration file
1530    def __init__(self, **prop):
1531        """User Roles base class - derive from this class to define
1532        roles interface to Attribute Authority
1533       
1534        @type prop: dict
1535        @param prop: custom properties to pass to this class
1536        """
1537
1538    def getRoles(self, userId):
1539        """Virtual method - Derived method should return the roles for the
1540        given user's Id or else raise an exception
1541       
1542        @type userId: string
1543        @param userId: user identity e.g. user Distinguished Name
1544        @rtype: list
1545        @return: list of roles for the given user ID"""
1546        raise NotImplementedError(self.getRoles.__doc__)
1547 
1548    def getAttributes(self, userId, requestedAttributeNames, requestorId):
1549        """Virtual method should be implemented in a derived class to enable
1550        AttributeAuthority.samlAttributeQuery - The derived method should
1551        return the attributes requested for the given user's Id or else raise
1552        an exception
1553       
1554        @type userId: string
1555        @param userId: user identity e.g. user Distinguished Name
1556        @type requestedAttributeNames: list
1557        @param requestedAttributeNames: list of attribute names set in the
1558        attribute query
1559        @type requestorId: basestring
1560        @param requestorId: identity of the agent making the attribute query
1561        @rtype: dict
1562        @return: list of attribute name, value pairs for the given user ID"""
1563        raise NotImplementedError(self.getAttributes.__doc__)
Note: See TracBrowser for help on using the repository browser.