source: TI12-security/branches/ndg-security-1.5.x/ndg_security_common/ndg/security/common/authz/msi.py @ 7119

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/branches/ndg-security-1.5.x/ndg_security_common/ndg/security/common/authz/msi.py@7119
Revision 7119, 36.3 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 10: OpenID Provider HTML/Javascript response incompatible with OpenID4Java

  • Removed old Session Manager code from 1.5.x branch
  • started updating certificates for new test CA.
  • Property svn:keywords set to Id
Line 
1"""NDG Security MSI Resource Policy module
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "03/04/09"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__contact__ = "Philip.Kershaw@stfc.ac.uk"
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = "$Id$"
12
13import logging
14log = logging.getLogger(__name__)
15
16import traceback
17import warnings
18try:
19    from xml.etree import ElementTree
20
21except ImportError:
22    warnings.warn("xml.etree not available, importing from elementtree egg "
23                  "instead")
24    from elementtree import ElementTree
25
26from ndg.security.common.utils import TypedList
27from ndg.security.common.utils.etree import QName
28
29
30class PolicyParseError(Exception):
31    """Error reading policy attributes from file"""
32
33class InvalidPolicyXmlNsError(Exception):
34    """Invalid XML namespace for policy document"""
35
36class PolicyComponent(object):
37    """Base class for Policy and Policy subelements"""
38    VERSION_1_0_XMLNS = "urn:ndg:security:authz:1.0:policy"
39    VERSION_1_1_XMLNS = "urn:ndg:security:authz:1.1:policy"
40    XMLNS = (VERSION_1_0_XMLNS, VERSION_1_1_XMLNS)
41    __slots__ = ('__xmlns', )
42
43    def __init__(self):
44        self.__xmlns = None
45       
46    def _getXmlns(self):
47        return self.__xmlns
48
49    def _setXmlns(self, value):
50        if not isinstance(value, basestring):
51            raise TypeError('Expecting string type for "xmlns" '
52                            'attribute; got %r' % type(value))
53        self.__xmlns = value
54
55    xmlns = property(_getXmlns, _setXmlns, 
56                     doc="XML Namespace for policy the document")
57   
58    @property
59    def isValidXmlns(self):
60        return self.xmlns in PolicyComponent.XMLNS
61   
62   
63class Policy(PolicyComponent):
64    """NDG MSI Policy."""   
65    DESCRIPTION_LOCALNAME = "Description"
66    TARGET_LOCALNAME = "Target"
67   
68    __slots__ = (
69        '__policyFilePath',
70        '__description',
71        '__targets',
72    )
73   
74    def __init__(self, policyFilePath=None):
75        super(Policy, self).__init__()
76        self.__policyFilePath = policyFilePath
77        self.__description = None
78        self.__targets = TypedList(Target)
79
80    def _getPolicyFilePath(self):
81        return self.__policyFilePath
82
83    def _setPolicyFilePath(self, value):
84        if not isinstance(value, basestring):
85            raise TypeError('Expecting string type for "policyFilePath" '
86                            'attribute; got %r' % type(value))
87           
88        self.__policyFilePath = value
89
90    policyFilePath = property(_getPolicyFilePath, _setPolicyFilePath, 
91                              doc="Policy file path")
92
93    def _getTargets(self):
94        return self.__targets
95
96    def _setTargets(self, value):
97        if (not isinstance(value, TypedList) and 
98            not issubclass(value.elementType, Target.__class__)):
99            raise TypeError('Expecting TypedList(Target) for "targets" '
100                            'attribute; got %r' % type(value))
101        self.__targets = value
102
103    targets = property(_getTargets, _setTargets, 
104                       doc="list of Policy targets")
105
106    def _getDescription(self):
107        return self.__description
108
109    def _setDescription(self, value):
110        if not isinstance(value, basestring):
111            raise TypeError('Expecting string type for "description" '
112                            'attribute; got %r' % type(value))
113        self.__description = value
114
115    description = property(_getDescription, _setDescription, 
116                           doc="Policy Description text")
117   
118    def parse(self):
119        """Parse the policy file set in policyFilePath attribute
120        """
121        elem = ElementTree.parse(self.policyFilePath)
122        root = elem.getroot()
123       
124        self.xmlns = QName.getNs(root.tag)
125        if not self.isValidXmlns:
126            raise InvalidPolicyXmlNsError("Namespace %r is recognised; valid "
127                                          "namespaces are: %r" %
128                                          (self.xmlns, Policy.XMLNS))
129           
130        for elem in root:
131            localName = QName.getLocalPart(elem.tag)
132            if localName == Policy.DESCRIPTION_LOCALNAME:
133                self.description = elem.text.strip()
134               
135            elif localName == Policy.TARGET_LOCALNAME:
136                self.targets.append(Target.Parse(elem))
137               
138            else:
139                raise PolicyParseError("Invalid policy attribute: %s" % 
140                                        localName)
141               
142    @classmethod
143    def Parse(cls, policyFilePath):
144        policy = cls(policyFilePath=policyFilePath)
145        policy.parse()
146        return policy
147
148
149class TargetParseError(PolicyParseError):
150    """Error reading resource attributes from file"""
151
152import re
153   
154class Target(PolicyComponent):
155    """Define access behaviour for a resource match a given URI pattern"""
156    URI_PATTERN_LOCALNAME = "URIPattern"
157    ATTRIBUTES_LOCALNAME = "Attributes"
158    ATTRIBUTE_AUTHORITY_LOCALNAME = "AttributeAuthority"
159   
160    __slots__ = (
161        '__uriPattern',
162        '__attributes',
163        '__regEx'       
164    )
165
166    ATTRIBUTE_AUTHORITY_LOCALNAME_DEPRECATED_MSG = """\
167Use of a <%r/> child element within Target elements will be deprecated for future
168releases.  Put the Attribute Authority setting in an Attribute
169<AttributeAuthorityURI/> element e.g.
170
171<Target>
172    <uriPattern>^/.*</uriPattern>
173    <Attributes>
174        <Attribute>
175            <Name>myattribute</Name>
176            <AttributeAuthorityURI>https://myattributeauthority.ac.uk</AttributeAuthorityURI>
177        </Attribute>
178    </Attributes>
179</Target>
180"""  % ATTRIBUTE_AUTHORITY_LOCALNAME 
181   
182    def __init__(self):
183        super(Target, self).__init__()
184        self.__uriPattern = None
185        self.__attributes = []
186        self.__regEx = None
187       
188    def getUriPattern(self):
189        return self.__uriPattern
190
191    def setUriPattern(self, value):
192        if not isinstance(value, basestring):
193            raise TypeError('Expecting string type for "uriPattern" '
194                            'attribute; got %r' % type(value))
195        self.__uriPattern = value
196
197    uriPattern = property(getUriPattern, 
198                          setUriPattern, 
199                          doc="URI Pattern to match this target")
200
201    def getAttributes(self):
202        return self.__attributes
203
204    def setAttributes(self, value):
205        if (not isinstance(value, TypedList) and 
206            not issubclass(value.elementType, Attribute.__class__)):
207            raise TypeError('Expecting TypedList(Attribute) for "attributes" '
208                            'attribute; got %r' % type(value))
209        self.__attributes = value
210
211    attributes = property(getAttributes, 
212                          setAttributes, 
213                          doc="Attributes restricting access to this target")
214
215    def getRegEx(self):
216        return self.__regEx
217
218    def setRegEx(self, value):
219        self.__regEx = value
220
221    regEx = property(getRegEx, setRegEx, doc="RegEx's Docstring")
222       
223    def parse(self, root):
224       
225        self.xmlns = QName.getNs(root.tag)
226        version1_0attributeAuthorityURI = None
227       
228        for elem in root:
229            localName = QName.getLocalPart(elem.tag)
230            if localName == Target.URI_PATTERN_LOCALNAME:
231                self.uriPattern = elem.text.strip()
232                self.regEx = re.compile(self.uriPattern)
233               
234            elif localName == Target.ATTRIBUTES_LOCALNAME:
235                for attrElem in elem:
236                    if self.xmlns == Target.VERSION_1_1_XMLNS:
237                        self.attributes.append(Attribute.Parse(attrElem))
238                    else:
239                        attribute = Attribute()
240                        attribute.name = attrElem.text.strip()
241                        self.attributes.append(attribute)
242                   
243            elif localName == Target.ATTRIBUTE_AUTHORITY_LOCALNAME:
244                # Expecting first element to contain the URI
245                warnings.warn(
246                        Target.ATTRIBUTE_AUTHORITY_LOCALNAME_DEPRECATED_MSG,
247                        PendingDeprecationWarning)
248               
249                version1_0attributeAuthorityURI = elem[-1].text.strip()
250            else:
251                raise TargetParseError("Invalid Target attribute: %s" % 
252                                       localName)
253               
254        if self.xmlns == Target.VERSION_1_0_XMLNS:
255            msg = ("Setting all attributes with Attribute Authority "
256                   "URI set read using Version 1.0 schema.  This will "
257                   "be deprecated in future releases")
258           
259            warnings.warn(msg, PendingDeprecationWarning)
260            log.warning(msg)
261           
262            if version1_0attributeAuthorityURI is None:
263                raise TargetParseError("Assuming version 1.0 schema "
264                                       "for Attribute Authority URI setting "
265                                       "but no URI has been set")
266               
267            for attribute in self.attributes:
268                attribute.attributeAuthorityURI = \
269                    version1_0attributeAuthorityURI
270   
271    @classmethod
272    def Parse(cls, root):
273        resource = cls()
274        resource.parse(root)
275        return resource
276   
277    def __str__(self):
278        return str(self.uriPattern)
279
280
281class AttributeParseError(PolicyParseError):
282    """Error parsing a Policy Attribute element"""
283   
284
285class Attribute(PolicyComponent):
286    """encapsulate a target attribute including the name and an Attribute
287    Authority from which user attribute information may be queried
288    """
289    NAME_LOCALNAME = "Name"
290    ATTRIBUTE_AUTHORITY_URI_LOCALNAME = "AttributeAuthorityURI"
291   
292    __slots__ = ('__name', '__attributeAuthorityURI')
293   
294    def __init__(self):
295        super(Attribute, self).__init__()
296        self.__name = ''
297        self.__attributeAuthorityURI = None
298
299    def __str__(self):
300        return self.__name
301   
302    def _getName(self):
303        return self.__name
304
305    def _setName(self, value):
306        if not isinstance(value, basestring):
307            raise TypeError('Expecting string type for "name"; got %r' %
308                            type(value))
309        self.__name = value
310
311    name = property(fget=_getName, 
312                    fset=_setName, 
313                    doc="Attribute name")
314       
315    def _getAttributeAuthorityURI(self):
316        return self.__attributeAuthorityURI
317
318    def _setAttributeAuthorityURI(self, value):
319        self.__attributeAuthorityURI = value
320
321    attributeAuthorityURI = property(_getAttributeAuthorityURI, 
322                                     _setAttributeAuthorityURI, 
323                                     doc="Attribute Authority URI")
324       
325    def parse(self, root):
326        """Parse from an ElementTree Element"""
327        self.xmlns = QName.getNs(root.tag)
328       
329        for elem in root:
330            localName = QName.getLocalPart(elem.tag)
331            if localName == Attribute.ATTRIBUTE_AUTHORITY_URI_LOCALNAME:
332                self.attributeAuthorityURI = elem.text.strip()
333               
334            elif localName == Attribute.NAME_LOCALNAME:
335                self.name = elem.text.strip()
336            else:
337                raise AttributeParseError("Invalid Attribute element name: %s" % 
338                                          localName)
339   
340    @classmethod
341    def Parse(cls, root):
342        """Parse from an ElementTree Element and return a new instance"""
343        resource = cls()
344        resource.parse(root)
345        return resource
346 
347       
348class _AttrDict(dict):
349    """Utility class for holding a constrained list of attributes governed
350    by a namespace list"""
351    namespaces = ()
352    def __init__(self, **attributes):
353        invalidAttributes = [attr for attr in attributes
354                             if attr not in self.__class__.namespaces]
355        if len(invalidAttributes) > 0:
356            raise TypeError("The following attribute namespace(s) are not "
357                            "recognised: %s" % invalidAttributes)
358           
359        self.update(attributes)
360
361    def __setitem__(self, key, val):
362        if key not in self.__class__.namespaces:
363            raise KeyError('Namespace "%s" not recognised.  Valid namespaces '
364                           'are: %s' % self.__class__.namespaces)
365           
366        dict.__setitem__(self, key, val)
367
368
369    def update(self, d, **kw):       
370        for dictArg in (d, kw):
371            for k in dictArg:
372                if k not in self.__class__.namespaces:
373                    raise KeyError('Namespace "%s" not recognised.  Valid '
374                                   'namespaces are: %s' % 
375                                   self.__class__.namespaces)
376       
377        dict.update(self, d, **kw)
378
379
380class Subject(_AttrDict):
381    '''Subject designator'''
382    namespaces = (
383        "urn:ndg:security:authz:1.0:attr:subject:userId",
384        "urn:ndg:security:authz:1.0:attr:subject:sessionId",
385        "urn:ndg:security:authz:1.0:attr:subject:sessionManagerURI",
386        "urn:ndg:security:authz:1.0:attr:subject:roles"       
387    )
388    (USERID_NS, SESSIONID_NS, SESSIONMANAGERURI_NS, ROLES_NS) = namespaces
389
390
391class Resource(_AttrDict):
392    '''Resource designator'''
393    namespaces = (
394        "urn:ndg:security:authz:1.0:attr:resource:uri",
395    )
396    (URI_NS,) = namespaces
397
398           
399class Request(object):
400    '''Request to send to a PDP'''
401    def __init__(self, subject=Subject(), resource=Resource()):
402        self.subject = subject
403        self.resource = resource
404
405    def _getSubject(self):
406        return self.__subject
407   
408    def _setSubject(self, subject):
409        if not isinstance(subject, Subject,):
410            raise TypeError("Expecting %s type for Request subject; got %r" %
411                            (Subject.__class__.__name__, subject))
412        self.__subject = subject
413
414    subject = property(fget=_getSubject,
415                       fset=_setSubject,
416                       doc="Subject type object representing subject accessing "
417                           "a resource")
418
419    def _getResource(self):
420        return self.__resource
421   
422    def _setResource(self, resource):
423        if not isinstance(resource, Resource):
424            raise TypeError("Expecting %s for Request Resource; got %r" %
425                            (Resource.__class__.__name__, resource))
426        self.__resource = resource
427
428    resource = property(fget=_getResource,
429                        fset=_setResource,
430                        doc="Resource to be protected")
431
432
433class Response(object):
434    '''Response from a PDP'''
435    decisionValues = range(4)
436    (DECISION_PERMIT,
437     DECISION_DENY,
438     DECISION_INDETERMINATE,
439     DECISION_NOT_APPLICABLE) = decisionValues
440
441    # string versions of the 4 Decision types used for encoding
442    DECISIONS = ("Permit", "Deny", "Indeterminate", "NotApplicable")
443   
444    decisionValue2String = dict(zip(decisionValues, DECISIONS))
445   
446    def __init__(self, status, message=None):
447        self.__status = None
448        self.__message = None
449       
450        self.status = status
451        self.message = message
452
453    def _setStatus(self, status):
454        if status not in Response.decisionValues:
455            raise TypeError("Status %s not recognised" % status)
456       
457        self.__status = status
458       
459    def _getStatus(self):
460        return self.__status
461   
462    status = property(fget=_getStatus,
463                      fset=_setStatus,
464                      doc="Integer response code; one of %r" % decisionValues)
465
466    def _setMessage(self, message):
467        if not isinstance(message, (basestring, type(None))):
468            raise TypeError('Expecting string or None type for "message"; got '
469                            '%r' % type(message))
470       
471        self.__message = message
472       
473    def _getMessage(self):
474        return self.__message
475   
476    message = property(fget=_getMessage,
477                       fset=_setMessage,
478                       doc="Optional message associated with response")
479       
480       
481from ndg.security.common.AttCert import (AttCertInvalidSignature, 
482    AttCertNotBeforeTimeError, AttCertExpired, AttCertError)
483     
484from ndg.security.common.sessionmanager import (SessionManagerClient, 
485    SessionNotFound, SessionCertTimeError, SessionExpired, InvalidSession, 
486    AttributeRequestDenied)
487
488from ndg.security.common.attributeauthority import (AttributeAuthorityClient, 
489    NoTrustedHosts, NoMatchingRoleInTrustedHosts, 
490    InvalidAttributeAuthorityClientCtx)
491from ndg.security.common.attributeauthority import AttributeRequestDenied as \
492    AA_AttributeRequestDenied
493                   
494from ndg.security.common.authz.pdp import (PDPUserNotLoggedIn, 
495    PDPUserAccessDenied)
496   
497   
498class SubjectRetrievalError(Exception):
499    """Generic exception class for errors related to information about the
500    subject"""
501   
502class InvalidAttributeCertificate(SubjectRetrievalError):
503    "The certificate containing authorisation roles is invalid"
504    def __init__(self, msg=None):
505        SubjectRetrievalError.__init__(self, msg or 
506                                       InvalidAttributeCertificate.__doc__)
507
508class AttributeCertificateInvalidSignature(SubjectRetrievalError):
509    ("There is a problem with the signature of the certificate containing "
510     "authorisation roles")
511    def __init__(self, msg=None):
512        SubjectRetrievalError.__init__(self, msg or 
513                                AttributeCertificateInvalidSignature.__doc__)
514             
515class AttributeCertificateNotBeforeTimeError(SubjectRetrievalError):
516    ("There is a time issuing error with certificate containing authorisation "
517    "roles")
518    def __init__(self, msg=None):
519        SubjectRetrievalError.__init__(self, msg or 
520                                AttributeCertificateNotBeforeTimeError.__doc__)
521       
522class AttributeCertificateExpired(SubjectRetrievalError):
523    "The certificate containing authorisation roles has expired"
524    def __init__(self, msg=None):
525        SubjectRetrievalError.__init__(self, msg or 
526                                       AttributeCertificateExpired.__doc__)
527           
528class SessionExpiredMsg(SubjectRetrievalError):
529    'Session has expired.  Please re-login at your home organisation'
530    def __init__(self, msg=None):
531        SubjectRetrievalError.__init__(self, msg or SessionExpiredMsg.__doc__)
532
533class SessionNotFoundMsg(SubjectRetrievalError):
534    'No session was found.  Please try re-login with your home organisation'
535    def __init__(self, msg=None):
536        SubjectRetrievalError.__init__(self, msg or 
537                                       SessionNotFoundMsg.__doc__)
538
539class InvalidSessionMsg(SubjectRetrievalError):
540    'Session is invalid.  Please try re-login with your home organisation'
541    def __init__(self, msg=None):
542        SubjectRetrievalError.__init__(self, msg or 
543                                       InvalidSessionMsg.__doc__)
544
545class InitSessionCtxError(SubjectRetrievalError):
546    'A problem occurred initialising a session connection'
547    def __init__(self, msg=None):
548        SubjectRetrievalError.__init__(self, msg or 
549                                       InitSessionCtxError.__doc__)
550
551class AttributeCertificateRequestError(SubjectRetrievalError):
552    'A problem occurred requesting a certificate containing authorisation roles'
553    def __init__(self, msg=None):
554        SubjectRetrievalError.__init__(self, msg or 
555                                    AttributeCertificateRequestError.__doc__)
556
557class PIPAttributeQuery(_AttrDict):
558    '''Policy Information Point Query class.'''
559    namespaces = (
560        "urn:ndg:security:authz:1.0:attr:subject",
561        "urn:ndg:security:authz:1.0:attr:attributeAuthorityURI",
562    ) 
563    (SUBJECT_NS, ATTRIBUTEAUTHORITY_NS) = namespaces   
564
565class PIPAttributeResponse(dict):
566    '''Policy Information Point Response class.'''
567    namespaces = (
568        Subject.ROLES_NS,
569    )
570
571
572class PIPBase(object):
573    """Policy Information Point base class.  PIP enables PDP to get user
574    attribute information in order to make access control decisions
575    """
576    def __init__(self, prefix='', **cfg):
577        '''Initialise settings for connection to an Attribute Authority'''
578        raise NotImplementedError(PIPBase.__init__.__doc__)
579   
580    def attributeQuery(self, attributeQuery):
581        """Query the Attribute Authority specified in the request to retrieve
582        the attributes if any corresponding to the subject
583       
584        @type attributeResponse: PIPAttributeQuery
585        @param attributeResponse:
586        @rtype: PIPAttributeResponse
587        @return: response containing the attributes retrieved from the
588        Attribute Authority"""
589        raise NotImplementedError(PIPBase.attributeQuery.__doc__)
590   
591
592from ndg.security.common.wssecurity import WSSecurityConfig
593
594class NdgPIP(PIPBase):
595    """Policy Information Point - this implementation enables the PDP to
596    retrieve attributes about the Subject"""
597    wsseSectionName = 'wssecurity'
598   
599    def __init__(self, prefix='', **cfg):
600        '''Set-up WS-Security and SSL settings for connection to an
601        Attribute Authority
602       
603        @type **cfg: dict
604        @param **cfg: keywords including 'sslCACertFilePathList' used to set a
605        list of CA certificates for an SSL connection to the Attribute
606        Authority if used and also WS-Security settings as used by
607        ndg.security.common.wssecurity.WSSecurityConfig
608        '''
609        self.wssecurityCfg = WSSecurityConfig()
610        wssePrefix = prefix + NdgPIP.wsseSectionName
611        self.wssecurityCfg.update(cfg, prefix=wssePrefix)
612                 
613        # List of CA certificates used to verify peer certificate with SSL
614        # connections to Attribute Authority
615        self.sslCACertFilePathList = cfg.get(prefix+'sslCACertFilePathList', [])
616       
617        # List of CA certificates used to verify the signatures of
618        # Attribute Certificates retrieved
619        self.caCertFilePathList = cfg.get(prefix + 'caCertFilePathList', [])
620
621    def attributeQuery(self, attributeQuery):
622        """Query the Attribute Authority specified in the request to retrieve
623        the attributes if any corresponding to the subject
624       
625        @type attributeResponse: PIPAttributeQuery
626        @param attributeResponse:
627        @rtype: PIPAttributeResponse
628        @return: response containing the attributes retrieved from the
629        Attribute Authority"""
630       
631        subject = attributeQuery[PIPAttributeQuery.SUBJECT_NS]
632        username = subject[Subject.USERID_NS]
633        sessionId = subject[Subject.SESSIONID_NS]
634        attributeAuthorityURI = attributeQuery[
635                                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS]
636       
637        sessionId = subject[Subject.SESSIONID_NS]
638       
639        log.debug("PIP: received attribute query: %r", attributeQuery)
640       
641        attributeCertificate = self._getAttributeCertificate(
642                    attributeAuthorityURI,
643                    username=username,
644                    sessionId=sessionId,
645                    sessionManagerURI=subject[Subject.SESSIONMANAGERURI_NS])
646
647        attributeResponse = PIPAttributeResponse()
648        attributeResponse[Subject.ROLES_NS] = attributeCertificate.roles
649       
650        log.debug("PIP.attributeQuery response: %r", attributeResponse)
651       
652        return attributeResponse
653   
654    def _getAttributeCertificate(self,
655                                 attributeAuthorityURI,
656                                 username=None,
657                                 sessionId=None,
658                                 sessionManagerURI=None):
659        '''Retrieve an Attribute Certificate
660
661        @type attributeAuthorityURI: basestring
662        @param attributeAuthorityURI: URI to Attribute Authority service
663        @type username: basestring
664        @param username: subject user identifier - could be an OpenID       
665        @type sessionId: basestring
666        @param sessionId: Session Manager session handle
667        @type sessionManagerURI: basestring
668        @param sessionManagerURI: URI to remote session manager service
669        @rtype: ndg.security.common.AttCert.AttCert
670        @return: Attribute Certificate containing user roles
671        '''
672
673        if sessionId and sessionManagerURI:
674            attrCert = self._getAttributeCertificateFromSessionManager(
675                                                     attributeAuthorityURI,
676                                                     sessionId,
677                                                     sessionManagerURI)
678        else:
679            attrCert = self._getAttributeCertificateFromAttributeAuthority(
680                                                     attributeAuthorityURI,
681                                                     username)
682       
683        try:
684            attrCert.certFilePathList = self.caCertFilePathList
685            attrCert.isValid(raiseExcep=True)
686       
687        except AttCertInvalidSignature, e:
688            log.exception(e)
689            raise AttributeCertificateInvalidSignature()
690       
691        except AttCertNotBeforeTimeError, e:   
692            log.exception(e)
693            raise AttributeCertificateNotBeforeTimeError()
694       
695        except AttCertExpired, e:   
696            log.exception(e)
697            raise AttributeCertificateExpired()
698
699        except AttCertError, e:
700            log.exception(e)
701            raise InvalidAttributeCertificate()
702       
703        return attrCert
704           
705    def _getAttributeCertificateFromSessionManager(self,
706                                                   attributeAuthorityURI,
707                                                   sessionId,
708                                                   sessionManagerURI):
709        '''Retrieve an Attribute Certificate using the subject's Session
710        Manager
711       
712        @type sessionId: basestring
713        @param sessionId: Session Manager session handle
714        @type sessionManagerURI: basestring
715        @param sessionManagerURI: URI to remote session manager service
716        @type attributeAuthorityURI: basestring
717        @param attributeAuthorityURI: URI to Attribute Authority service
718        @rtype: ndg.security.common.AttCert.AttCert
719        @return: Attribute Certificate containing user roles
720        '''
721       
722        log.debug("PIP._getAttributeCertificateFromSessionManager ...")
723       
724        try:
725            # Create Session Manager client - if a file path was set, setting
726            # are read from a separate config file section otherwise, from the
727            # PDP config object
728            smClnt = SessionManagerClient(
729                            uri=sessionManagerURI,
730                            sslCACertFilePathList=self.sslCACertFilePathList,
731                            cfg=self.wssecurityCfg)
732        except Exception, e:
733            log.error("Creating Session Manager client: %s" % e)
734            raise InitSessionCtxError()
735             
736        try:
737            # Make request for attribute certificate
738            return smClnt.getAttCert(
739                                attributeAuthorityURI=attributeAuthorityURI,
740                                sessID=sessionId)
741       
742        except AttributeRequestDenied, e:
743            log.error("Request for attribute certificate denied: %s" % e)
744            raise PDPUserAccessDenied()
745       
746        except SessionNotFound, e:
747            log.error("No session found: %s" % e)
748            raise SessionNotFoundMsg()
749
750        except SessionExpired, e:
751            log.error("Session expired: %s" % e)
752            raise SessionExpiredMsg()
753
754        except SessionCertTimeError, e:
755            log.error("Session cert. time error: %s" % e)
756            raise InvalidSessionMsg()
757           
758        except InvalidSession, e:
759            log.error("Invalid user session: %s" % e)
760            raise InvalidSessionMsg()
761
762        except Exception, e:
763            log.error("Request from Session Manager [%s] to Attribute "
764                      "Authority [%s] for attribute certificate: %s: %s" % 
765                      (sessionManagerURI,
766                       attributeAuthorityURI,
767                       e.__class__, e))
768            raise AttributeCertificateRequestError()
769           
770    def _getAttributeCertificateFromAttributeAuthority(self,
771                                                       attributeAuthorityURI,
772                                                       username):
773        '''Retrieve an Attribute Certificate direct from an Attribute
774        Authority.  This method is invoked if no session ID or Session
775        Manager endpoint where provided
776       
777        @type username: basestring
778        @param username: user identifier - may be an OpenID URI
779        @type attributeAuthorityURI: basestring
780        @param attributeAuthorityURI: URI to Attribute Authority service
781        @rtype: ndg.security.common.AttCert.AttCert
782        @return: Attribute Certificate containing user roles
783        '''
784       
785        log.debug("PIP._getAttributeCertificateFromAttributeAuthority ...")
786       
787        try:
788            # Create Attribute Authority client - if a file path was set,
789            # settingare read  from a separate config file section otherwise,
790            # from the PDP config object
791            aaClnt = AttributeAuthorityClient(
792                            uri=attributeAuthorityURI,
793                            sslCACertFilePathList=self.sslCACertFilePathList,
794                            cfg=self.wssecurityCfg)
795        except Exception:
796            log.error("Creating Attribute Authority client: %s",
797                      traceback.format_exc())
798            raise InitSessionCtxError()
799       
800         
801        try:
802            # Make request for attribute certificate
803            return aaClnt.getAttCert(userId=username)
804       
805       
806        except AA_AttributeRequestDenied:
807            log.error("Request for attribute certificate denied: %s",
808                      traceback.format_exc())
809            raise PDPUserAccessDenied()
810       
811        # TODO: handle other specific Exception types here for more fine
812        # grained response info
813
814        except Exception, e:
815            log.error("Request to Attribute Authority [%s] for attribute "
816                      "certificate: %s: %s", attributeAuthorityURI,
817                      e.__class__, traceback.format_exc())
818            raise AttributeCertificateRequestError()
819       
820# Backwards compatibility
821PIP = NdgPIP
822
823         
824class PDP(object):
825    """Policy Decision Point"""
826   
827    def __init__(self, policy, pip):
828        """Read in a file which determines access policy"""
829        self.policy = policy
830        self.pip = pip
831
832    def _getPolicy(self):
833        if self.__policy is None:
834            raise TypeError("Policy object has not been initialised")
835        return self.__policy
836   
837    def _setPolicy(self, policy):
838        if not isinstance(policy, (Policy, None.__class__)):
839            raise TypeError("Expecting %s or None type for PDP policy; got %r"%
840                            (Policy.__class__.__name__, policy))
841        self.__policy = policy
842
843    policy = property(fget=_getPolicy,
844                      fset=_setPolicy,
845                      doc="Policy type object used by the PDP to determine "
846                          "access for resources")
847
848    def _getPIP(self):
849        if self.__pip is None:
850            raise TypeError("PIP object has not been initialised")
851       
852        return self.__pip
853   
854    def _setPIP(self, pip):
855        if not isinstance(pip, (PIPBase, None.__class__)):
856            raise TypeError("Expecting %s or None type for PDP PIP; got %r"%
857                            (PIPBase.__class__.__name__, pip))
858        self.__pip = pip
859
860    pip = property(fget=_getPIP,
861                   fset=_setPIP,
862                   doc="Policy Information Point - PIP type object used by "
863                       "the PDP to retrieve user attributes")
864   
865    def evaluate(self, request):
866        '''Make access control decision'''
867       
868        if not isinstance(request, Request):
869            raise TypeError("Expecting %s type for request; got %r" %
870                            (Request.__class__.__name__, request))
871       
872        # Look for matching targets to the given resource
873        resourceURI = request.resource[Resource.URI_NS]
874        matchingTargets = [target for target in self.policy.targets
875                           if target.regEx.match(resourceURI) is not None]
876        numMatchingTargets = len(matchingTargets)
877        if numMatchingTargets == 0:
878            log.debug("PDP.evaluate: granting access - no targets matched "
879                      "the resource URI path [%s]", 
880                      resourceURI)
881            return Response(status=Response.DECISION_PERMIT)
882       
883        # Iterate through matching targets checking for user access
884        request.subject[Subject.ROLES_NS] = []
885        permitForAllTargets = [Response.DECISION_PERMIT]*numMatchingTargets
886       
887        # Keep a look-up of the decisions for each target
888        status = []
889       
890        # Make a query object for querying the Policy Information Point
891        attributeQuery = PIPAttributeQuery()
892        attributeQuery[PIPAttributeQuery.SUBJECT_NS] = request.subject
893       
894        # Keep a cache of queried Attribute Authorities to avoid calling them
895        # multiple times
896        queriedAttributeAuthorityURIs = []
897       
898        # Iterate through the targets gathering user attributes from the
899        # relevant attribute authorities
900        for matchingTarget in matchingTargets:
901           
902            # Make call to the Policy Information Point to pull user
903            # attributes applicable to this resource
904            for attribute in matchingTarget.attributes:
905                if (attribute.attributeAuthorityURI in 
906                    queriedAttributeAuthorityURIs): 
907                    continue
908                         
909                attributeQuery[
910                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS
911                ] = attribute.attributeAuthorityURI
912           
913                # Exit from function returning indeterminate status if a
914                # problem occurs here
915                try:
916                    attributeResponse = self.pip.attributeQuery(attributeQuery)
917                   
918                except SubjectRetrievalError, e:
919                    # i.e. a defined exception within the scope of this
920                    # module
921                    log.error("SAML Attribute Query %s: %s", 
922                              type(e), traceback.format_exc())
923                    return Response(Response.DECISION_INDETERMINATE, 
924                                    message=traceback.format_exc())
925                               
926                except Exception, e:
927                    log.error("SAML Attribute Query %s: %s", 
928                              type(e), traceback.format_exc())
929                    return Response(Response.DECISION_INDETERMINATE,
930                                    message="An internal error occurred")
931                               
932                # Accumulate attributes retrieved from multiple attribute
933                # authorities
934                request.subject[Subject.ROLES_NS] += attributeResponse[
935                                                            Subject.ROLES_NS]
936               
937            # Match the subject's attributes against the target
938            # One of any rule - at least one of the subject's attributes
939            # must match one of the attributes restricting access to the
940            # resource.
941            log.debug("PDP.evaluate: Matching subject attributes %r against "
942                      "resource attributes %r ...", 
943                      request.subject[Subject.ROLES_NS],
944                      matchingTarget.attributes)
945           
946            status.append(PDP._match(matchingTarget.attributes, 
947                                     request.subject[Subject.ROLES_NS]))
948           
949        # All targets must yield permit status for access to be granted
950        if status == permitForAllTargets:
951            return Response(Response.DECISION_PERMIT)
952        else:   
953            return Response(Response.DECISION_DENY,
954                            message="Insufficient privileges to access the "
955                                    "resource")
956       
957    @staticmethod
958    def _match(resourceAttr, subjectAttr):
959        """Helper method to iterate over user and resource attributes
960        If one at least one match is found, a permit response is returned
961        """
962        for attr in resourceAttr:
963            if attr.name in subjectAttr:
964                return Response.DECISION_PERMIT
965           
966        return Response.DECISION_DENY
967
968       
Note: See TracBrowser for help on using the repository browser.