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

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

* Change to AttCert? format and AA WS interface and AttAuthority? class for DEWS *

  • New userId element in Attribute Certificates + getAttCert call to an AA can specify a

user ID to be set in the returned AC.

python/ndg.security.server/ndg/security/server/AttAuthority/server-config.tac,
python/ndg.security.server/ndg/security/server/AttAuthority/AttAuthority_services_server.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services.py,
python/ndg.security.common/ndg/security/common/AttAuthority/AttAuthority_services_types.py,
python/www/html/attAuthority.wsdl:
added userId to WSDL interface.

python/ndg.security.server/ndg/security/server/AttAuthority/init.py:

  • added userId to getAttCert method.
  • changed refs to proxyCert to holderCert because cert meay not be a proxy
  • changed call to AttCert?.getRoles to AttCert?.roles
  • changed refs to userDN to userId

python/ndg.security.common/ndg/security/common/XMLSec.py: "ns1" is not needed for
reference C14N unsuppressed prefixes.

python/ndg.security.common/ndg/security/common/X509.py: made 'serialize' and 'deserialize'
aliases to serialise and deserialise methods respectively.

python/ndg.security.common/ndg/security/common/AttCert.py:

  • made AttCert? namespace a configurable class variable
  • changed all get/set attribute methods to private methods used by new-style class

properties.

  • updated setitem to use appropriate set* methods.
  • fix to setIssuerSerialNumber ref to 'issuerSerialNumber' instead of 'serialNumber'

python/ndg.security.common/ndg/security/common/AttAuthority/init.py: AA WS client -
added userId as keyword to getAttCert.

python/ndg.security.common/ndg/security/common/CredWallet.py: replace AttCert?.getRoles()
calls with AttCert?.roles property

python/ndg.security.test/ndg/security/test/AttAuthority/siteAUserRoles.py,
python/ndg.security.test/ndg/security/test/AttAuthority/siteBUserRoles.py:
swap refs to userDN with userId.

python/ndg.security.test/ndg/security/test/AttAuthority/AttAuthorityClientTest.py:
added new test for where an explicit userId is set.

python/ndg.security.test/ndg/security/test/AttAuthority/attAuthorityClientTest.cfg:
added userId parameter.

python/ndg.security.test/ndg/security/test/AttCert/AttCertTest.py: added tests for
property get calls.

python/ndg.security.test/ndg/security/test/MyProxy/Makefile: include call to MyProxy?
test to get proxy cert and private key.

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