source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttCert.py @ 2910

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttCert.py@2910
Revision 2910, 40.7 KB checked in by pjkersha, 13 years ago (diff)

ndg.security.common.AttCert?: fixed epydoc for cvar and added #: attribute mark up.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1"""NDG Attribute Certificate (Authorisation -or Access- Token)
2
3NERC Data Grid Project
4
5"""
6__author__ = "P J Kershaw"
7__date__ = "05/04/05"
8__copyright__ = "(C) 2007 STFC & NERC"
9__license__ = \
10"""This software may be distributed under the terms of the Q Public
11License, version 1.0 or later."""
12__contact__ = "P.J.Kershaw@rl.ac.uk"
13__revision__ = '$Id$'
14
15import types
16import os
17import re
18import copy
19
20# XML Parsing
21
22# For parsing of properties file
23try: # python 2.5
24    from xml.etree import cElementTree as ElementTree
25except ImportError:
26    # if you've installed it yourself it comes this way
27    import cElementTree as ElementTree
28
29# Time module for use with validity times
30from time import strftime, strptime
31from datetime import datetime, timedelta
32
33# XML signature module based on M2Crypto, ZSI Canonicalization and DOM
34from XMLSec import XMLSecDoc, InvalidSignature, getParentNode
35
36from X509 import X500DN
37from X509 import X500DNError
38
39
40#_____________________________________________________________________________
41class AttCertError(Exception): 
42    """Exception handling for NDG Attribute Certificate class."""
43
44#_____________________________________________________________________________
45class AttCertReadOnlyDict(dict):
46    def __init__(self, inputDict):
47        super(AttCertReadOnlyDict, self).__init__(inputDict)
48       
49    def __setitem__(self, key, item):
50        raise KeyError, "Items are read-only in this dictionary"
51       
52#_____________________________________________________________________________
53class _MetaAttCert(type):
54    """Enable AttCert to have read only class variables e.g.
55   
56    print AttCert.mappedProvenance is allowed but,
57   
58    AttCert.mappedProvenance = None
59   
60    ... raises - AttributeError: can't set attribute"""
61   
62    #_________________________________________________________________________   
63    def __getVersion(cls):
64        '''Version of THIS format for the certificate'''
65        return '1.0'
66
67    version = property(fget=__getVersion, 
68                       doc="Version of the certificate format")
69   
70    #_________________________________________________________________________   
71    def __getMappedProvenance(cls):
72        '''Text for mapped provenance setting of certificate'''
73        return 'mapped'
74
75    mappedProvenance = property(fget=__getMappedProvenance,
76        doc="Text constant indicating cert has mapped roles from another")
77
78    #_________________________________________________________________________   
79    def __getOriginalProvenance(cls):
80        '''Text for original provenance setting of certificate'''
81        return 'original'
82   
83    origProvenance = property(fget=__getOriginalProvenance,
84        doc="Text constant indicating cert has original and not mapped roles")
85   
86   
87#_____________________________________________________________________________
88class AttCert(dict, XMLSecDoc):
89    """NDG Attribute Certificate (Authorisation or Access Token).
90   
91    @type __validProvenanceSettings: tuple
92    @cvar __validProvenanceSettings: string constants for allowable certificate provenance settings
93   
94    @type namespace: string
95    @cvar namespace: namespace for Attribute Certificate"""
96   
97    __metaclass__ = _MetaAttCert
98
99    # Provenance of certificate may be original or mapped from another
100    # certificate
101    __validProvenanceSettings = ('original', 'mapped')
102    namespace = "urn:ndg:security:attributeCertificate"
103
104    #_________________________________________________________________________   
105    def __init__(self, provenance='original', lifetime=28800, **xmlSecDocKw):
106        """Initialisation - Attribute Certificate file path may be specified.
107        Also, holder and issuer details and signing authority key and
108        certificate.
109       
110        @type lifetime: int
111        @param lifetime: set the lifetime for the certificate in seconds.
112        Defaults to 8 hours.
113       
114        @type **xmlSecDocKw: dict
115        @param **xmlSecDocKw: see XMLSec.XMLSec class for an explanation.
116        Keywords include, filePath for the cert. for reading/writing and
117        cert./private key settings for digital signature and verification."""
118
119        # Base class initialisation
120        dict.__init__(self)
121        XMLSecDoc.__init__(self, **xmlSecDocKw)
122
123        #: Data dictionary version of xml
124        #:
125        #: Nb. RoleSet is an empty list - it will be filled role dictionary
126        #: items [{'role': {'name': '<Name>'}}, ... ]
127        self.__dat = {
128           
129            "version":            AttCert.version,
130            "holder":             '',
131            "issuer":             '',
132            "issuerName":         '',
133            "issuerSerialNumber": 0,
134            "userId":             '',
135            "validity":           {"notBefore": '', "notAfter": ''},
136            "attributes":         {"roleSet": []},
137            "provenance":         ''
138        }
139
140        #: Holder X500DN object - instantiated in read method
141        self.__issuerDN = None
142        #: issuer X500DN object - instantiated in read method
143        self.__holderDN = None
144
145        self.__setProvenance(provenance)
146       
147        #: Certificate life time interval in seconds
148        self.__lifetime = lifetime
149       
150        #: Certificate not before time as datetime type
151        self.__dtNotBefore = None
152       
153        #: Certificate not after time as a datetime type
154        self.__dtNotAfter = None
155
156
157    #_________________________________________________________________________   
158    def __repr__(self):
159        """Override default behaviour to return internal dictionary content"""
160        return str(self.__dat)
161
162
163    #_________________________________________________________________________   
164    def __str__(self):
165        """Override XMLSec.XMLSecDoc equivalent"""
166        return self.toString()
167   
168   
169    #_________________________________________________________________________
170    def toString(self, **kw):
171        """Return certificate file content as a string
172       
173        @param **kw: keywords to XMLSec.XMLSecDoc.toString()
174        @rtype string
175        @return content of document"""
176
177        # If doc hasn't been parsed by parent (ie. not signed) return elements
178        # set so far using createXML method
179        return super(AttCert, self).toString(**kw) or self.createXML()
180
181               
182    #_________________________________________________________________________   
183    def __delitem__(self, key):
184        "Attribute Certificate keys cannot be removed"
185       
186        raise AttCertError, 'Keys cannot be deleted from ' + \
187                           self.__class__.__name__
188
189
190    #_________________________________________________________________________   
191    def __getitem__(self, key):
192        """Get an item from the __dat, __dat['validity'] or
193        __dat['attributes'] dictionaries.  This class behaves as data
194        dictionary of Attribute Certificate properties
195
196        @param key: name of key - key can be specified belonging to validity
197        or the attributes sub dictionaries
198        @param item: value to set dictionary item to
199        """
200       
201        # Check input key
202        if key in self.__dat:
203
204            # key recognised
205            item = self.__dat[key]               
206
207        elif key in self.__dat['validity']:
208
209            # Allow indexing via validity keys - a shorthand way of
210            # referencing for convenience
211            item = self.__dat['validity'][key]
212
213        elif key in self.__dat['attributes']:
214
215            # Allow indexing via attributes keys - a shorthand way of
216            # referencing for convenience
217            item = self.__dat['attributes'][key]
218
219        else:
220            # key not recognised as a short or long name version
221            raise KeyError, 'Key "%s" not recognised for %s' % \
222                               (key, self.__class__.__name__)
223
224        if isinstance(item, dict):
225            return AttCertReadOnlyDict(item)
226        else:
227            return item
228
229
230    #_________________________________________________________________________   
231    def __setitem__(self, key, item):       
232        """Set an item from the __dat dictionary.  This class behaves as data
233        dictionary of Attribute Certificate properties
234
235        @type key: string
236        @param key: name of key - key can be specified belonging to validity
237        or the attributes sub dictionaries
238       
239        @type item: string / int
240        @param item: value to set dictionary item to
241        """
242
243        # Check input key
244        if key in self.__dat:
245
246            # key recognised - check if setting provenance
247            if key == "provenance":
248                self.__setProvenance(item)
249               
250            elif key == "version":
251                self.__setVersion(item)
252                 
253            elif key == "holder":
254                self.__setHolder(item)
255               
256            elif key == "issuer":
257                self.__setIssuer(item)
258           
259            elif key == "issuerName":
260                self.__setIssuerName(item)
261           
262            elif key == "issuerSerialNumber":
263                self.__setIssuerSerialNumber(item)
264         
265            elif key == "userId":
266                self.__setUserId(item)
267                   
268            elif key == "validity":
269                raise KeyError, "'%s': use setValidityTime method " % \
270                    key + "to set notBefore/notAfter times"
271                           
272            elif key == "attributes":
273                raise KeyError, "'%s': use addRoles method to " % \
274                    key + "set list of role attributes"           
275            else:   
276                raise KeyError, "Key '%s' not recognised for %s'" % \
277                               (key, self.__class__.__name__)
278
279        elif key in self.__dat['attributes'] or \
280             key in self.__dat['attributes']['roleSet']:
281
282            # To complex to allow direct setting here
283            raise KeyError, "'%s': use addRoles method to " % key + \
284                            "set list of roles"           
285
286        elif key in self.__dat['validity']:
287            # Prevent setting of notBefore/notAfter - restrict to method
288            # setValidityTime
289            raise KeyError, "'%s': use setValidityTime method " % key + \
290                            "to set notBefore/notAfter times"           
291        else:
292            # key not recognised as a short or long name version
293            raise KeyError, "Key '%s' not recognised for %s'" % \
294                               (key, self.__class__.__name__)
295       
296
297    #_________________________________________________________________________   
298    def __eq__(self, attCert):
299        """Return true if all elements are the same"""       
300        try:
301            return min([self.__dat[key] == attCert[key] \
302                       for key in self.__dat.keys()])
303        except:
304            return False
305       
306
307    #_________________________________________________________________________   
308    def __nonzero__(self):
309        """Ensure if <attCertInstance> test yields True"""
310        return True
311   
312   
313    #_________________________________________________________________________   
314    def clear(self):
315        raise AttCertError, "Data cannot be cleared from " + \
316                           self.__class__.__name__
317
318   
319    #_________________________________________________________________________   
320    def copy(self):
321        return copy.copy(self)
322
323   
324    #_________________________________________________________________________   
325    def keys(self):
326        return self.__dat.keys()
327
328    #_________________________________________________________________________   
329    def items(self):
330        return self.__dat.items()
331
332    #_________________________________________________________________________   
333    def values(self):
334        return self.__dat.values()
335
336    #_________________________________________________________________________   
337    def has_key(self, key):
338        return self.__dat.has_key(key)
339
340    # 'in' operator
341    #_________________________________________________________________________   
342    def __contains__(self, key):
343        return key in self.__dat
344
345
346    #
347    # Get/Set methods
348    #
349    # Nb. it's also possible to access the data dictionary parameters via
350    # __setitem__ and __getitem__ standard dictionary methods
351    #
352    #_________________________________________________________________________   
353    def __setVersion(self, version):
354        """Set the version number to be written to file."""       
355        self.__dat['version'] = version
356   
357    #_________________________________________________________________________   
358    def __getVersion(self):
359        """Get version number as set in file."""
360        return self.__dat['version']
361
362    version = property(fget=__getVersion,
363                       fset=__setVersion, 
364                       doc="Attribute Certificate version")
365   
366    #_________________________________________________________________________   
367    def __setHolder(self, holder):
368        """Set holder's Distinguished Name string."""
369        if not isinstance(holder, basestring):
370            raise AttributeError, "holder DN must be a string"
371
372        self.__dat['holder'] = holder
373   
374    #_________________________________________________________________________   
375    def __getHolder(self):
376        """Get holder's Distinguished Name string."""
377        return self.__dat['holder']
378
379    holder = property(fget=__getHolder,
380                      fset=__setHolder, 
381                      doc="Attribute Certificate holder DN")
382
383    #_________________________________________________________________________   
384    def __getHolderDN(self):
385         """Get the holder's Distinguished Name as an X500DN instance"""
386         return self.__holderDN
387     
388    holderDN = property(fget=__getHolderDN,
389                        doc="Attribute Certificate holder DN as X500DN type")
390   
391    #_________________________________________________________________________   
392    def __setIssuer(self, issuer):
393        """Set issuer's Distinguished Name."""
394        if not isinstance(issuer, basestring):
395            raise AttributeError, "issuer DN must be a string"
396       
397        self.__dat['issuer'] = issuer
398   
399    #_________________________________________________________________________   
400    def __getIssuer(self):
401        """Get the issuer's Distinguished Name string"""
402        return self.__dat['issuer']
403
404    issuer = property(fget=__getIssuer, 
405                      fset=__setIssuer,
406                      doc="Certificate Issuer DN")
407
408    #_________________________________________________________________________   
409    def __getIssuerDN(self):
410         """Get the issuer's Distinguished Name as an X500DN instance"""
411         return self.__issuerDN
412     
413    issuerDN = property(fget=__getIssuerDN,
414                        doc="Attribute Certificate issuer DN as X500DN type")
415       
416    #_________________________________________________________________________   
417    def __setIssuerName(self, issuerName):
418        """Set the name of the issuer"""
419        if not isinstance(issuerName, basestring):
420            raise AttributeError, "issuerName must be a string"
421       
422        self.__dat['issuerName'] = issuerName
423   
424    #_________________________________________________________________________   
425    def __getIssuerName(self):
426        """@return the name of the issuer"""
427        return self.__dat['issuerName']
428
429    issuerName = property(fget=__getIssuerName, 
430                          fset=__setIssuerName,
431                          doc="Certificate Issuer name")
432   
433    #_________________________________________________________________________   
434    def __setIssuerSerialNumber(self, serialNumber):
435        """@param serialNumber: the issuer serial number"""
436        if not isinstance(serialNumber, int):
437            raise AttributeError, "issuerSerialNumber must be an integer"
438
439        self.__dat['issuerSerialNumber'] = serialNumber
440   
441    #_________________________________________________________________________   
442    def __getIssuerSerialNumber(self):
443        """@return the issuer serial number"""
444        return self.__dat['issuerSerialNumber']
445   
446    issuerSerialNumber = property(fget=__getIssuerSerialNumber, 
447                                  fset=__setIssuerSerialNumber,
448                                  doc="Certificate Issuer Serial Number")
449 
450       
451    #_________________________________________________________________________   
452    def __setUserId(self, userId):
453        """Set the name of the userId
454        @type userId: string
455        @param userId: user identifier"""
456        if not isinstance(userId, basestring):
457            raise AttributeError, "userId must be a string"
458       
459        self.__dat['userId'] = userId
460   
461    #_________________________________________________________________________   
462    def __getUserId(self):
463        """@rtype: string
464        @return: the user idenitifier"""
465        return self.__dat['userId']
466
467    userId = property(fget=__getUserId, 
468                      fset=__setUserId,
469                      doc="Certificate user identifier")
470   
471
472    # Nb. no setValidityNotBefore/setValidityNotAfter methods - use
473    # setValidityTime instead.
474   
475    #_________________________________________________________________________   
476    def getValidityNotBefore(self, asDatetime=False):
477        """Get the validity Not Before date/time string
478
479        @param asDatetime: boolean to True to return as a datetime type
480        Nb. time may not have been set - if so it will be set to None
481       
482        @return the not before time as string or datetime type - see above"""
483        if asDatetime is True:
484            return self.__dtNotBefore
485        else:
486            return self.__dat['validity']['notBefore']
487
488    validityNotBefore = property(fget=getValidityNotBefore, 
489                                  doc="Validity not before time as a string")
490
491
492    #_________________________________________________________________________   
493    def getValidityNotAfter(self, asDatetime=False):
494        """Get the validity Not After date/time string
495
496        @param asDatetime: boolean set to True to return as a datetime type
497        Nb. time may not have been set - if so it will be set to None
498       
499        @return the not after time as string or datetime type - see above"""
500        if asDatetime is True:
501            return self.__dtNotAfter
502        else:
503            return self.__dat['validity']['notAfter']
504
505    validityNotAfter = property(fget=getValidityNotAfter, 
506                                doc="Validity not after time as a string")
507
508   
509    #_________________________________________________________________________   
510    def __getRoleSet(self):
511        """@rtype: list of dict type
512        @return the roleSet as a list of role dictionaries."""
513        return self.__dat['attributes']['roleSet']
514
515    roleSet = property(fget=__getRoleSet, 
516                       doc="Role set dictionary")
517
518    #_________________________________________________________________________   
519    def __getRoles(self):
520        """Return roles as a list
521       
522        @return list of roles contained in the certificate"""
523       
524        try:
525            return [i.values()[0].values()[0] \
526                    for i in self.__dat['attributes']['roleSet']]
527        except:
528            return []
529       
530    roles = property(fget=__getRoles, 
531                     doc="List of roles in Attribute Certificate")
532
533       
534    #_________________________________________________________________________   
535    def __setProvenance(self, provenance):
536        """Set the provenance for the certificate: 'original' or 'mapped'.
537       
538        @param provenance: string provenance setting"""
539
540        if not self.isValidProvenance(provenance):
541            raise AttCertError, "Provenance must be set to \"" + \
542                   "\" or \"".join(AttCert.__validProvenanceSettings) + "\""
543       
544        self.__dat['provenance'] = provenance
545
546   
547    #_________________________________________________________________________   
548    def __getProvenance(self):
549        """Get the provenance for the certificate.
550       
551        @return provenance of certificate mapped or original"""
552        return self.__dat['provenance']
553
554    provenance = property(fget=__getProvenance,
555                          fset=__setProvenance, 
556                          doc="Provenance of the cert. - original or mapped")
557   
558
559    #_________________________________________________________________________   
560    def isValidProvenance(self, provenance=None):
561        """Check provenance is set correctly - to 'original'/'mapped'.
562
563        If no provenance argument is provided, test against the setting in
564        the current instance.
565       
566        @param provenance: by default the current provenance setting is
567        checked i.e. self.__dat['provenance'].  Set this keyword to override
568        and check against an alternate provenance setting.
569        @return True if certificate has a valid provenance setting
570        """
571       
572        if not provenance:
573            provenance = self.__dat['provenance']
574
575        return provenance in AttCert.__validProvenanceSettings
576       
577
578    #_________________________________________________________________________   
579    def isOriginal(self):
580        """Check for original provenance.
581        @return True if certificate has original roles"""
582        return self.__dat['provenance'] == self.__class__.origProvenance
583
584
585    #_________________________________________________________________________   
586    def isMapped(self):
587        """Check for mapped provenance.
588        @return True if certificate contain roles mapped from another"""
589        return self.__dat['provenance'] == self.__class__.mappedProvenance
590
591
592    #_________________________________________________________________________   
593    def addRoles(self, roleName):
594        """Add new roles to the roleSet in attributes.
595       
596        @param roleName: role name or list of role names to add to certificate
597        """
598
599        if isinstance(roleName, basestring):
600            roleName = [roleName]
601           
602        self.__dat['attributes']['roleSet'].extend(\
603                                [{'role': {'name': i}} for i in roleName])
604
605
606    #_________________________________________________________________________   
607    def parse(self, xmlTxt, rtnRootElem=False):
608        """Parse an Attribute Certificate content contained in string input
609
610        @param xmlTxt:     Attribute Certificate XML content as string
611        @param rtnRootElem: boolean set to True to return the ElementTree
612        root element
613        @return root element if rtnRootElem keyword is set to True"""
614       
615        rootElem = ElementTree.XML(xmlTxt)
616
617        # Call generic ElementTree parser
618        self.__parse(rootElem)
619
620
621        # Call base class parser method to initialise DOM objects for
622        # signature validation
623        try:
624            XMLSecDoc.parse(self, xmlTxt)
625
626        except Exception, e:
627            raise AttCertError, "Attribute Certificate: %s" % e
628
629        if rtnRootElem:
630            return rootElem
631
632       
633    #_________________________________________________________________________   
634    def read(self, filePath=None, **xmlSecDocKw):
635        """Read an Attribute Certificate from file
636
637        @param filePath:   file to be read, if omitted XMLSecDoc.__filePath
638        member variable is used instead"""
639
640        if filePath:
641            self.filePath = filePath
642
643        try:   
644            tree = ElementTree.parse(self.filePath)
645            rootElem = tree.getroot()
646        except Exception, e:
647            raise AttCertError, "Attribute Certificate: %s" % e
648       
649        # Call generic ElementTree parser
650        self.__parse(rootElem)
651
652        # Call base class read method to initialise libxml2 objects for
653        # signature validation
654        try:
655            XMLSecDoc.read(self, **xmlSecDocKw)
656
657        except Exception, e:
658            raise AttCertError, "Attribute Certificate: %s" % e
659
660       
661    #_________________________________________________________________________   
662    def __parse(self, rootElem):
663        """Private XML parsing method accepts a ElementTree.Element type
664        as input
665
666        @param rootElem: root element of doc - ElementTree.Element type
667        """
668       
669        # Extract from acInfo tag
670        acInfoElem = rootElem.find("acInfo")
671       
672        if not acInfoElem:
673            raise AttCertError, "<acInfo> tag not found in \"%s\"" % \
674                               self.filePath
675
676
677        # Copy all acInfo tags into dictionary
678        for elem in acInfoElem:
679            if elem.tag not in self.__dat:
680                raise AttCertError, '%s: "<%s>" not recognised.' % \
681                                    (self.filePath, elem.tag)
682
683            # Make sure not to copy validity and attributes tags - handle
684            # these separately below
685            if not elem.getchildren():
686                self.__dat[elem.tag] = elem.text
687
688        # Convert issuer and holder into X500DN instances
689        try:
690            self.__issuerDN = X500DN(dn=self.__dat['issuer'])
691
692        except X500DNError, x500dnErr:
693            raise AttCertError, "Issuer DN: %s" % x500dnErr
694
695
696        try:
697            self.__holderDN = X500DN(dn=self.__dat['holder'])
698
699        except X500DNError, x500dnErr:
700            raise AttCertError, "Holder DN: %s" % x500dnErr
701       
702                                 
703        # Extract validity and attributes subsets
704        self.__dat['validity']['notBefore'] = \
705                                rootElem.findtext("acInfo/validity/notBefore")
706       
707        if self.__dat['validity']['notBefore'] is None:
708            raise AttCertError, "<notBefore> tag not found in \"%s\"" % \
709                                                               self.filePath
710        elif self.__dat['validity']['notBefore'] == '':
711           
712            # Allow empty string setting but re-initialise corresponding
713            # datetime value
714            self.__dtNotBefore = None
715        else:
716            # Update datetime object equivalent
717            self.__dtNotBefore = self.timeStr2datetime(\
718                                        self.__dat['validity']['notBefore'])
719
720       
721        self.__dat['validity']['notAfter'] = \
722                                rootElem.findtext("acInfo/validity/notAfter")
723       
724        if self.__dat['validity']['notAfter'] is None:
725            raise AttCertError, '<notAfter> tag not found in "%s"' % \
726                               self.filePath
727        elif self.__dat['validity']['notBefore'] == '':
728           
729            # Allow empty string setting but re-initialise corresponding
730            # datetime value
731            self.__dtNotAfter = None
732        else:
733            # Update datetime object equivalent
734            self.__dtNotAfter = self.timeStr2datetime(\
735                                        self.__dat['validity']['notAfter'])
736
737        # set up role list
738        roleElem = acInfoElem.findall("attributes/roleSet/role/name")
739        if roleElem is None:
740            raise AttCertError, "<role> tag not found in \"%s\"" % \
741                               self.filePath
742       
743        self.__dat['attributes']['roleSet'] = \
744                                [{'role': {'name': i.text}} for i in roleElem]
745                   
746       
747        if not self.isValidVersion():           
748            raise AttCertError, 'Attribute Certificate version is ' + \
749                               self.__dat['version'] + ' but version ' + \
750                               AttCert.version + ' expected'
751
752
753    #_________________________________________________________________________   
754    def createXML(self):
755        """Create XML for Attribute Token from current data settings and
756        return as a string.  The XML created is MINUS the digital signature.
757        To obtain the signed version, run the applyEnvelopedSignature method
758        (inherited from XMLSecDoc) and pass the attCert object reference into
759        str()
760
761        @return xmlTxt: formatted XML for certificate as a string"""
762
763        # Nb.
764        # * this method is used by AttCert.read()
765        # * Signing by Attribute Authority is separate - see AttCert.sign()
766       
767
768        # Check for valid provenance
769        if not self.isValidProvenance():
770            raise AttCertError, "Provenance must be set to \"" + \
771                   "\" or \"".join(AttCert.__validProvenanceSettings) + "\""
772
773       
774        # Create string of all XML content       
775        xmlTxt = '<attributeCertificate targetNamespace="%s">' % \
776                                                self.__class__.namespace + \
777"""
778    <acInfo>
779        <version>""" + self.__dat['version'] + """</version>
780        <holder>""" + self.__dat['holder'] + """</holder>
781        <issuer>""" + self.__dat['issuer'] + """</issuer>
782        <issuerName>""" + self.__dat['issuerName'] + """</issuerName>
783        <issuerSerialNumber>""" + str(self.__dat['issuerSerialNumber']) +\
784            """</issuerSerialNumber>
785        <userId>""" + self.__dat['userId'] + """</userId>
786        <validity>
787            <notBefore>""" + self.__dat['validity']['notBefore'] + \
788            """</notBefore>
789            <notAfter>""" + self.__dat['validity']['notAfter'] + \
790            """</notAfter>
791        </validity>
792        <attributes>
793            <roleSet>
794            """ + "".join([\
795"""    <role>
796                    <name>""" + i['role']['name'] + """</name>
797                </role>
798            """ for i in self.__dat['attributes']['roleSet']]) + \
799            """</roleSet>
800        </attributes>
801        <provenance>""" + self.__dat['provenance'] + """</provenance>
802    </acInfo>
803</attributeCertificate>"""
804
805        # Return XML file content as a string
806        return xmlTxt
807
808
809    def applyEnvelopedSignature(self, **xmlSecDocKw):
810        '''Override super class version to ensure settings have been parsed
811        into a DOM object ready for signature
812       
813        @param **xmlSecDocKw: keywords applying to
814        XMLSecDoc.applyEnvelopedSignature()
815        '''       
816        self.parse(self.createXML())
817        super(AttCert, self).applyEnvelopedSignature(**xmlSecDocKw)
818
819       
820    #_________________________________________________________________________   
821    def setValidityTime(self,
822                        dtNotBefore=None, 
823                        dtNotAfter=None, 
824                        lifetime=None,
825                        notBeforeOffset=None):
826        """Set the notBefore and notAfter times which determine the window for
827        which the Attribute Certificate is valid.  These times are set as
828        datetime types and also the correct string format settings are made
829        ready for output.
830
831        Nb. use UTC time.  lifetime and notBeforeOffset are in seconds
832       
833        @param dtNotBefore: not before time as datetime type.  If omitted,
834        it defaults to the current time
835       
836        @param dtNotAfter: not after time as datetime type.  Defaults to
837        self.__dtNotBefore + self.__lifetime.  If dtNotAfter is set it will
838        reset self.__lifetime to self.__dtNotAfter - self.dtNotBefore
839       
840        @param lifetime: lifetime for certificate in seconds i.e. not after
841        time - not before time.  If dtNotAfter is set then this keyword will
842        be ignored.
843       
844        @param notBeforeOffset: skew the not before time by some offset.  This
845        is useful in cases where system clocks are not correctly synchronized
846        between different hosts.  Set a negative value to skew the offset
847        backward in time.
848        """
849
850        if dtNotBefore is not None:
851            if not isinstance(dtNotBefore, datetime):
852                raise AttCertError, \
853                                "Input not before time must be datetime type"
854           
855            self.__dtNotBefore = dtNotBefore
856           
857        else:
858            # Use current UTC +/- offset
859            self.__dtNotBefore = datetime.utcnow()
860           
861        if notBeforeOffset is not None:
862            self.__dtNotBefore += timedelta(seconds=notBeforeOffset)
863           
864
865        if dtNotAfter is not None:
866            if not isinstance(dtNotAfter, datetime):
867                raise AttCertError, \
868                                "Input not after time must be datetime type"
869
870            # Use input Not After time to calculate a new lifetime setting
871            dtDeltaLifeTime = dtNotAfter - self.__dtNotBefore
872            if dtDeltaLifeTime < timedelta(0):
873                raise AttCertError, "Input Not After time is invalid %s" % \
874                                   str(dtNotAfter)
875
876            self.__lifetime = dtDeltaLifeTime.days*86400 + \
877                              dtDeltaLifeTime.seconds
878
879            self.__dtNotAfter = dtNotAfter
880           
881        else:
882            # Check for input certificate life time interval
883            if lifetime is not None:
884                self.__lifetime = lifetime
885               
886            try:
887                # Make a time delta object from the lifetime expressed in
888                # seconds
889                dtDeltaLifeTime = timedelta(seconds=self.__lifetime)
890            except Exception, e:
891                raise AttCertError, "Invalid Certificate lifetime set %.3f" %\
892                                   self.__lifetime
893           
894            # Add certificate lifetime to calculate not after time
895            self.__dtNotAfter = self.__dtNotBefore + dtDeltaLifeTime
896
897       
898        self.__dat['validity']['notBefore'] = \
899                                    self.datetime2timeStr(self.__dtNotBefore)
900       
901        self.__dat['validity']['notAfter'] = \
902                                    self.datetime2timeStr(self.__dtNotAfter)
903
904
905    #_________________________________________________________________________   
906    def datetime2timeStr(self, dtVal):
907        """Convert a datetime object to a notBefore/notAfter time string
908       
909        @param dtVal: input datetime
910        @return datetime converted into correct string format for AttCert"""
911
912        if not isinstance(dtVal, datetime):
913            raise AttCertError, \
914                        "Invalid datetime object for conversion to string"
915       
916        # Convert from 1-12 to 0-11 month format used in XML file
917        #lDateTime = list(dtVal.utctimetuple()[0:6])
918
919        #lDateTime[1] -= 1
920
921        # Format as a single string with no commas or brackets
922        #return ''.join(re.findall('[0-9 ]', str(lDateTime)))
923
924        # Use 1-12 format
925        # P J Kershaw 09/06/05
926        return dtVal.strftime("%Y %m %d %H %M %S")
927
928
929    #_________________________________________________________________________   
930    def timeStr2datetime(self, sTime):
931        """Convert a notBefore/notAfter time string to a datetime object
932       
933        @param sTime: time in string format as used by AttCert
934        @return datetime type equivalent of string input"""
935
936        try:
937            lTime = strptime(sTime, "%Y %m %d %H %M %S")
938            return datetime(*lTime[0:6])
939       
940        except Exception, e:
941            raise AttCertError, \
942                "Error converting time string into datetime object: %s" % e
943       
944
945    #_________________________________________________________________________   
946    def isValidTime(self, dtNow=None, raiseExcep=False):
947        """Check Attribute Certificate for expiry.  Set raiseExcep to True
948        to raise an exception with a message indicating the nature of the
949        time error
950       
951        @param dtNow: the time to test against in datetime format.  This time
952        must be within the range of the not before and not after times in
953        order for the certificate to be valid.  Defaults to the current
954        system time
955       
956        @param raiseExcep: boolean set to True to raise an exception if the
957        time is invalid.  Defaults to False in which case no exception is
958        raised if the time is invalid, instead False is returned
959       
960        @return boolean True if time is valid, False if invalid.  Also see
961        raiseExcep keyword above."""
962
963        if not isinstance(self.__dtNotBefore, datetime):
964            raise AttCertError, "Not Before datetime is not set"
965
966        if not isinstance(self.__dtNotAfter, datetime):
967            raise AttCertError, "Not After datetime is not set"
968       
969        if dtNow is None:
970            dtNow = datetime.utcnow()
971       
972        # Testing only
973        #
974        # P J Kershaw 02/03/06
975        #notBefore = self.__dtNotBefore
976        #notAfter = self.__dtNotAfter
977        #print "Valid Time? = %d" % (dtNow > notBefore and dtNow < notAfter)
978        if raiseExcep:
979            if dtNow < self.__dtNotBefore:
980                raise AttCertError, "Current time %s " % \
981                           dtNow.strftime("%d/%m/%Y %H:%M:%S") + \
982                           "is before Attribute Certificate's " + \
983                           "not before time of %s" % \
984                           self.__dtNotBefore.strftime("%d/%m/%Y %H:%M:%S")
985           
986            if dtNow > self.__dtNotAfter:
987                raise AttCertError, "Current time %s " % \
988                           dtNow.strftime("%d/%m/%Y %H:%M:%S") + \
989                           "is after Attribute Certificate's " + \
990                           "expiry time of %s" % \
991                           self.__dtNotBefore.strftime("%d/%m/%Y %H:%M:%S")               
992           
993            return True       
994        else:
995            return dtNow > self.__dtNotBefore and dtNow < self.__dtNotAfter
996       
997       
998    #_________________________________________________________________________   
999    def isValidVersion(self):
1000        """Check Attribute Certificate XML file version
1001       
1002        @return boolean True if certificate version matches the expected one,
1003        False otherwise.
1004        """
1005        return self.__dat['version'] == AttCert.version
1006
1007
1008    #_________________________________________________________________________   
1009    def isValid(self,
1010                raiseExcep=False,
1011                chkTime=True,
1012                chkVersion=True,
1013                chkProvenance=True,
1014                chkSig=True,
1015                **xmlSecDocKw):
1016        """Check Attribute Certificate is valid:
1017
1018        - Time validity is OK
1019        - XML file version is OK
1020        - valid provenance setting
1021        - Signature is valid.
1022
1023        @param chkTime: set to True to do time validity check (default is
1024        True)
1025
1026        @param chkVersion: set to True to Attribute Certificate file
1027        version (default is True)
1028
1029        @param chkProvenanceset to True to check provenance value is valid
1030        (default is True)
1031
1032        @param chkSig: set to True to check digital signature - for
1033        this certFilePathList must contain the root certificate of the X.509
1034        certificate used to sign the AttCert.  Alternatively, certFilePathList
1035        can be set via __init__ (default chkSig value is True)
1036                               
1037        @param raiseExcep: set to true to raise an exception if invalid
1038        instead of returning False.  Default is to set this flag to False.
1039
1040        @param **xmlSecDocKw: Also accepts keyword arguments corresponding to
1041        XMLSecDoc.verifyEnvelopedSignature().
1042       
1043        @return boolean True if certificate is valid, False otherwise.  Also
1044        see explanation for raiseExcep keyword.                         
1045        """
1046
1047        # Carry out checks in turn - Specific exception error messages are
1048        # raised if flag is set
1049        if chkTime and not self.isValidTime(raiseExcep=raiseExcep):
1050            return False
1051
1052        if chkVersion and not self.isValidVersion():
1053            if raiseExcep:
1054                raise AttCertError, 'Attribute Certificate version is ' + \
1055                                   self.__dat['version'] + ' but version ' + \
1056                                   AttCert.version + ' expected'
1057            return False
1058
1059        if chkProvenance and not self.isValidProvenance():
1060            if raiseExcep:
1061                raise AttCertError, \
1062                    "Attribute Certificate Provenance must be set to \"" + \
1063                    "\" or \"".join(AttCert.__validProvenanceSettings) + "\""
1064            return False
1065
1066        # Handle exception from XMLSecDocc.isValidSig() regardless of
1067        # raiseExcep flag setting
1068        if chkSig:
1069            try:
1070                self.verifyEnvelopedSignature(**xmlSecDocKw)
1071       
1072            except InvalidSignature, e:
1073                 if raiseExcep:
1074                     raise AttCertError, e
1075                 else:
1076                     return False
1077               
1078        # All tests passed
1079        return True
1080
1081
1082#_____________________________________________________________________________
1083# Alternative AttCert constructors
1084def AttCertRead(filePath):
1085    """Create a new attribute certificate read in from a file"""
1086   
1087    attCert = AttCert(filePath=filePath)
1088    attCert.read()
1089   
1090    return attCert
1091
1092#_________________________________________________________________________   
1093def AttCertParse(attCertTxt):
1094    """Create a new attribute certificate from string of file content"""
1095   
1096    attCert = AttCert()
1097    attCert.parse(attCertTxt)
1098   
1099    return attCert
Note: See TracBrowser for help on using the repository browser.