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

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@7632
Revision 7632, 36.7 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 15: NDG Security 1.5.8 Branch Release for Questionnaire

  • Committing 1.5.8 release. ZSI based SOAP functionality is disabled for Python 2.6 use but this does not affect currently used interfaces in production deployment.
  • 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   
484_sessionmanagerImportOK = True
485try:
486    from ndg.security.common.sessionmanager import (SessionManagerClient, 
487    SessionNotFound, SessionCertTimeError, SessionExpired, InvalidSession, 
488    AttributeRequestDenied)
489except ImportError, e:
490    warnings.warn("sessionmanager module import failed NdgPIP class will be "
491                  "disabled: %s" % e)
492    _sessionmanagerImportOK = False
493
494from ndg.security.common.attributeauthority import (AttributeAuthorityClient, 
495    NoTrustedHosts, NoMatchingRoleInTrustedHosts, 
496    InvalidAttributeAuthorityClientCtx)
497from ndg.security.common.attributeauthority import AttributeRequestDenied as \
498    AA_AttributeRequestDenied
499                   
500from ndg.security.common.authz.pdp import (PDPUserNotLoggedIn, 
501    PDPUserAccessDenied)
502   
503   
504class SubjectRetrievalError(Exception):
505    """Generic exception class for errors related to information about the
506    subject"""
507   
508class InvalidAttributeCertificate(SubjectRetrievalError):
509    "The certificate containing authorisation roles is invalid"
510    def __init__(self, msg=None):
511        SubjectRetrievalError.__init__(self, msg or 
512                                       InvalidAttributeCertificate.__doc__)
513
514class AttributeCertificateInvalidSignature(SubjectRetrievalError):
515    ("There is a problem with the signature of the certificate containing "
516     "authorisation roles")
517    def __init__(self, msg=None):
518        SubjectRetrievalError.__init__(self, msg or 
519                                AttributeCertificateInvalidSignature.__doc__)
520             
521class AttributeCertificateNotBeforeTimeError(SubjectRetrievalError):
522    ("There is a time issuing error with certificate containing authorisation "
523    "roles")
524    def __init__(self, msg=None):
525        SubjectRetrievalError.__init__(self, msg or 
526                                AttributeCertificateNotBeforeTimeError.__doc__)
527       
528class AttributeCertificateExpired(SubjectRetrievalError):
529    "The certificate containing authorisation roles has expired"
530    def __init__(self, msg=None):
531        SubjectRetrievalError.__init__(self, msg or 
532                                       AttributeCertificateExpired.__doc__)
533           
534class SessionExpiredMsg(SubjectRetrievalError):
535    'Session has expired.  Please re-login at your home organisation'
536    def __init__(self, msg=None):
537        SubjectRetrievalError.__init__(self, msg or SessionExpiredMsg.__doc__)
538
539class SessionNotFoundMsg(SubjectRetrievalError):
540    'No session was found.  Please try re-login with your home organisation'
541    def __init__(self, msg=None):
542        SubjectRetrievalError.__init__(self, msg or 
543                                       SessionNotFoundMsg.__doc__)
544
545class InvalidSessionMsg(SubjectRetrievalError):
546    'Session is invalid.  Please try re-login with your home organisation'
547    def __init__(self, msg=None):
548        SubjectRetrievalError.__init__(self, msg or 
549                                       InvalidSessionMsg.__doc__)
550
551class InitSessionCtxError(SubjectRetrievalError):
552    'A problem occurred initialising a session connection'
553    def __init__(self, msg=None):
554        SubjectRetrievalError.__init__(self, msg or 
555                                       InitSessionCtxError.__doc__)
556
557class AttributeCertificateRequestError(SubjectRetrievalError):
558    'A problem occurred requesting a certificate containing authorisation roles'
559    def __init__(self, msg=None):
560        SubjectRetrievalError.__init__(self, msg or 
561                                    AttributeCertificateRequestError.__doc__)
562
563class PIPAttributeQuery(_AttrDict):
564    '''Policy Information Point Query class.'''
565    namespaces = (
566        "urn:ndg:security:authz:1.0:attr:subject",
567        "urn:ndg:security:authz:1.0:attr:attributeAuthorityURI",
568    ) 
569    (SUBJECT_NS, ATTRIBUTEAUTHORITY_NS) = namespaces   
570
571class PIPAttributeResponse(dict):
572    '''Policy Information Point Response class.'''
573    namespaces = (
574        Subject.ROLES_NS,
575    )
576
577
578class PIPBase(object):
579    """Policy Information Point base class.  PIP enables PDP to get user
580    attribute information in order to make access control decisions
581    """
582    def __init__(self, prefix='', **cfg):
583        '''Initialise settings for connection to an Attribute Authority'''
584        raise NotImplementedError(PIPBase.__init__.__doc__)
585   
586    def attributeQuery(self, attributeQuery):
587        """Query the Attribute Authority specified in the request to retrieve
588        the attributes if any corresponding to the subject
589       
590        @type attributeResponse: PIPAttributeQuery
591        @param attributeResponse:
592        @rtype: PIPAttributeResponse
593        @return: response containing the attributes retrieved from the
594        Attribute Authority"""
595        raise NotImplementedError(PIPBase.attributeQuery.__doc__)
596   
597
598from ndg.security.common.wssecurity import WSSecurityConfig
599
600class NdgPIP(PIPBase):
601    """Policy Information Point - this implementation enables the PDP to
602    retrieve attributes about the Subject"""
603    wsseSectionName = 'wssecurity'
604   
605    def __init__(self, prefix='', **cfg):
606        '''Set-up WS-Security and SSL settings for connection to an
607        Attribute Authority
608       
609        @type **cfg: dict
610        @param **cfg: keywords including 'sslCACertFilePathList' used to set a
611        list of CA certificates for an SSL connection to the Attribute
612        Authority if used and also WS-Security settings as used by
613        ndg.security.common.wssecurity.WSSecurityConfig
614        '''
615        if _sessionmanagerImportOK == False:
616            raise ImportError("Error with sessionmanager module import, this "
617                              "class is disabled")
618           
619        self.wssecurityCfg = WSSecurityConfig()
620        wssePrefix = prefix + NdgPIP.wsseSectionName
621        self.wssecurityCfg.update(cfg, prefix=wssePrefix)
622                 
623        # List of CA certificates used to verify peer certificate with SSL
624        # connections to Attribute Authority
625        self.sslCACertFilePathList = cfg.get(prefix+'sslCACertFilePathList', [])
626       
627        # List of CA certificates used to verify the signatures of
628        # Attribute Certificates retrieved
629        self.caCertFilePathList = cfg.get(prefix + 'caCertFilePathList', [])
630
631    def attributeQuery(self, attributeQuery):
632        """Query the Attribute Authority specified in the request to retrieve
633        the attributes if any corresponding to the subject
634       
635        @type attributeResponse: PIPAttributeQuery
636        @param attributeResponse:
637        @rtype: PIPAttributeResponse
638        @return: response containing the attributes retrieved from the
639        Attribute Authority"""
640       
641        subject = attributeQuery[PIPAttributeQuery.SUBJECT_NS]
642        username = subject[Subject.USERID_NS]
643        sessionId = subject[Subject.SESSIONID_NS]
644        attributeAuthorityURI = attributeQuery[
645                                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS]
646       
647        sessionId = subject[Subject.SESSIONID_NS]
648       
649        log.debug("PIP: received attribute query: %r", attributeQuery)
650       
651        attributeCertificate = self._getAttributeCertificate(
652                    attributeAuthorityURI,
653                    username=username,
654                    sessionId=sessionId,
655                    sessionManagerURI=subject[Subject.SESSIONMANAGERURI_NS])
656
657        attributeResponse = PIPAttributeResponse()
658        attributeResponse[Subject.ROLES_NS] = attributeCertificate.roles
659       
660        log.debug("PIP.attributeQuery response: %r", attributeResponse)
661       
662        return attributeResponse
663   
664    def _getAttributeCertificate(self,
665                                 attributeAuthorityURI,
666                                 username=None,
667                                 sessionId=None,
668                                 sessionManagerURI=None):
669        '''Retrieve an Attribute Certificate
670
671        @type attributeAuthorityURI: basestring
672        @param attributeAuthorityURI: URI to Attribute Authority service
673        @type username: basestring
674        @param username: subject user identifier - could be an OpenID       
675        @type sessionId: basestring
676        @param sessionId: Session Manager session handle
677        @type sessionManagerURI: basestring
678        @param sessionManagerURI: URI to remote session manager service
679        @rtype: ndg.security.common.AttCert.AttCert
680        @return: Attribute Certificate containing user roles
681        '''
682
683        if sessionId and sessionManagerURI:
684            attrCert = self._getAttributeCertificateFromSessionManager(
685                                                     attributeAuthorityURI,
686                                                     sessionId,
687                                                     sessionManagerURI)
688        else:
689            attrCert = self._getAttributeCertificateFromAttributeAuthority(
690                                                     attributeAuthorityURI,
691                                                     username)
692       
693        try:
694            attrCert.certFilePathList = self.caCertFilePathList
695            attrCert.isValid(raiseExcep=True)
696       
697        except AttCertInvalidSignature, e:
698            log.exception(e)
699            raise AttributeCertificateInvalidSignature()
700       
701        except AttCertNotBeforeTimeError, e:   
702            log.exception(e)
703            raise AttributeCertificateNotBeforeTimeError()
704       
705        except AttCertExpired, e:   
706            log.exception(e)
707            raise AttributeCertificateExpired()
708
709        except AttCertError, e:
710            log.exception(e)
711            raise InvalidAttributeCertificate()
712       
713        return attrCert
714           
715    def _getAttributeCertificateFromSessionManager(self,
716                                                   attributeAuthorityURI,
717                                                   sessionId,
718                                                   sessionManagerURI):
719        '''Retrieve an Attribute Certificate using the subject's Session
720        Manager
721       
722        @type sessionId: basestring
723        @param sessionId: Session Manager session handle
724        @type sessionManagerURI: basestring
725        @param sessionManagerURI: URI to remote session manager service
726        @type attributeAuthorityURI: basestring
727        @param attributeAuthorityURI: URI to Attribute Authority service
728        @rtype: ndg.security.common.AttCert.AttCert
729        @return: Attribute Certificate containing user roles
730        '''
731       
732        log.debug("PIP._getAttributeCertificateFromSessionManager ...")
733       
734        try:
735            # Create Session Manager client - if a file path was set, setting
736            # are read from a separate config file section otherwise, from the
737            # PDP config object
738            smClnt = SessionManagerClient(
739                            uri=sessionManagerURI,
740                            sslCACertFilePathList=self.sslCACertFilePathList,
741                            cfg=self.wssecurityCfg)
742        except Exception, e:
743            log.error("Creating Session Manager client: %s" % e)
744            raise InitSessionCtxError()
745             
746        try:
747            # Make request for attribute certificate
748            return smClnt.getAttCert(
749                                attributeAuthorityURI=attributeAuthorityURI,
750                                sessID=sessionId)
751       
752        except AttributeRequestDenied, e:
753            log.error("Request for attribute certificate denied: %s" % e)
754            raise PDPUserAccessDenied()
755       
756        except SessionNotFound, e:
757            log.error("No session found: %s" % e)
758            raise SessionNotFoundMsg()
759
760        except SessionExpired, e:
761            log.error("Session expired: %s" % e)
762            raise SessionExpiredMsg()
763
764        except SessionCertTimeError, e:
765            log.error("Session cert. time error: %s" % e)
766            raise InvalidSessionMsg()
767           
768        except InvalidSession, e:
769            log.error("Invalid user session: %s" % e)
770            raise InvalidSessionMsg()
771
772        except Exception, e:
773            log.error("Request from Session Manager [%s] to Attribute "
774                      "Authority [%s] for attribute certificate: %s: %s" % 
775                      (sessionManagerURI,
776                       attributeAuthorityURI,
777                       e.__class__, e))
778            raise AttributeCertificateRequestError()
779           
780    def _getAttributeCertificateFromAttributeAuthority(self,
781                                                       attributeAuthorityURI,
782                                                       username):
783        '''Retrieve an Attribute Certificate direct from an Attribute
784        Authority.  This method is invoked if no session ID or Session
785        Manager endpoint where provided
786       
787        @type username: basestring
788        @param username: user identifier - may be an OpenID URI
789        @type attributeAuthorityURI: basestring
790        @param attributeAuthorityURI: URI to Attribute Authority service
791        @rtype: ndg.security.common.AttCert.AttCert
792        @return: Attribute Certificate containing user roles
793        '''
794       
795        log.debug("PIP._getAttributeCertificateFromAttributeAuthority ...")
796       
797        try:
798            # Create Attribute Authority client - if a file path was set,
799            # settingare read  from a separate config file section otherwise,
800            # from the PDP config object
801            aaClnt = AttributeAuthorityClient(
802                            uri=attributeAuthorityURI,
803                            sslCACertFilePathList=self.sslCACertFilePathList,
804                            cfg=self.wssecurityCfg)
805        except Exception:
806            log.error("Creating Attribute Authority client: %s",
807                      traceback.format_exc())
808            raise InitSessionCtxError()
809       
810         
811        try:
812            # Make request for attribute certificate
813            return aaClnt.getAttCert(userId=username)
814       
815       
816        except AA_AttributeRequestDenied:
817            log.error("Request for attribute certificate denied: %s",
818                      traceback.format_exc())
819            raise PDPUserAccessDenied()
820       
821        # TODO: handle other specific Exception types here for more fine
822        # grained response info
823
824        except Exception, e:
825            log.error("Request to Attribute Authority [%s] for attribute "
826                      "certificate: %s: %s", attributeAuthorityURI,
827                      e.__class__, traceback.format_exc())
828            raise AttributeCertificateRequestError()
829       
830# Backwards compatibility
831PIP = NdgPIP
832
833         
834class PDP(object):
835    """Policy Decision Point"""
836   
837    def __init__(self, policy, pip):
838        """Read in a file which determines access policy"""
839        self.policy = policy
840        self.pip = pip
841
842    def _getPolicy(self):
843        if self.__policy is None:
844            raise TypeError("Policy object has not been initialised")
845        return self.__policy
846   
847    def _setPolicy(self, policy):
848        if not isinstance(policy, (Policy, None.__class__)):
849            raise TypeError("Expecting %s or None type for PDP policy; got %r"%
850                            (Policy.__class__.__name__, policy))
851        self.__policy = policy
852
853    policy = property(fget=_getPolicy,
854                      fset=_setPolicy,
855                      doc="Policy type object used by the PDP to determine "
856                          "access for resources")
857
858    def _getPIP(self):
859        if self.__pip is None:
860            raise TypeError("PIP object has not been initialised")
861       
862        return self.__pip
863   
864    def _setPIP(self, pip):
865        if not isinstance(pip, (PIPBase, None.__class__)):
866            raise TypeError("Expecting %s or None type for PDP PIP; got %r"%
867                            (PIPBase.__class__.__name__, pip))
868        self.__pip = pip
869
870    pip = property(fget=_getPIP,
871                   fset=_setPIP,
872                   doc="Policy Information Point - PIP type object used by "
873                       "the PDP to retrieve user attributes")
874   
875    def evaluate(self, request):
876        '''Make access control decision'''
877       
878        if not isinstance(request, Request):
879            raise TypeError("Expecting %s type for request; got %r" %
880                            (Request.__class__.__name__, request))
881       
882        # Look for matching targets to the given resource
883        resourceURI = request.resource[Resource.URI_NS]
884        matchingTargets = [target for target in self.policy.targets
885                           if target.regEx.match(resourceURI) is not None]
886        numMatchingTargets = len(matchingTargets)
887        if numMatchingTargets == 0:
888            log.debug("PDP.evaluate: granting access - no targets matched "
889                      "the resource URI path [%s]", 
890                      resourceURI)
891            return Response(status=Response.DECISION_PERMIT)
892       
893        # Iterate through matching targets checking for user access
894        request.subject[Subject.ROLES_NS] = []
895        permitForAllTargets = [Response.DECISION_PERMIT]*numMatchingTargets
896       
897        # Keep a look-up of the decisions for each target
898        status = []
899       
900        # Make a query object for querying the Policy Information Point
901        attributeQuery = PIPAttributeQuery()
902        attributeQuery[PIPAttributeQuery.SUBJECT_NS] = request.subject
903       
904        # Keep a cache of queried Attribute Authorities to avoid calling them
905        # multiple times
906        queriedAttributeAuthorityURIs = []
907       
908        # Iterate through the targets gathering user attributes from the
909        # relevant attribute authorities
910        for matchingTarget in matchingTargets:
911           
912            # Make call to the Policy Information Point to pull user
913            # attributes applicable to this resource
914            for attribute in matchingTarget.attributes:
915                if (attribute.attributeAuthorityURI in 
916                    queriedAttributeAuthorityURIs): 
917                    continue
918                         
919                attributeQuery[
920                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS
921                ] = attribute.attributeAuthorityURI
922           
923                # Exit from function returning indeterminate status if a
924                # problem occurs here
925                try:
926                    attributeResponse = self.pip.attributeQuery(attributeQuery)
927                   
928                except SubjectRetrievalError, e:
929                    # i.e. a defined exception within the scope of this
930                    # module
931                    log.error("SAML Attribute Query %s: %s", 
932                              type(e), traceback.format_exc())
933                    return Response(Response.DECISION_INDETERMINATE, 
934                                    message=traceback.format_exc())
935                               
936                except Exception, e:
937                    log.error("SAML Attribute Query %s: %s", 
938                              type(e), traceback.format_exc())
939                    return Response(Response.DECISION_INDETERMINATE,
940                                    message="An internal error occurred")
941                               
942                # Accumulate attributes retrieved from multiple attribute
943                # authorities
944                request.subject[Subject.ROLES_NS] += attributeResponse[
945                                                            Subject.ROLES_NS]
946               
947            # Match the subject's attributes against the target
948            # One of any rule - at least one of the subject's attributes
949            # must match one of the attributes restricting access to the
950            # resource.
951            log.debug("PDP.evaluate: Matching subject attributes %r against "
952                      "resource attributes %r ...", 
953                      request.subject[Subject.ROLES_NS],
954                      matchingTarget.attributes)
955           
956            status.append(PDP._match(matchingTarget.attributes, 
957                                     request.subject[Subject.ROLES_NS]))
958           
959        # All targets must yield permit status for access to be granted
960        if status == permitForAllTargets:
961            return Response(Response.DECISION_PERMIT)
962        else:   
963            return Response(Response.DECISION_DENY,
964                            message="Insufficient privileges to access the "
965                                    "resource")
966       
967    @staticmethod
968    def _match(resourceAttr, subjectAttr):
969        """Helper method to iterate over user and resource attributes
970        If one at least one match is found, a permit response is returned
971        """
972        for attr in resourceAttr:
973            if attr.name in subjectAttr:
974                return Response.DECISION_PERMIT
975           
976        return Response.DECISION_DENY
977
978       
Note: See TracBrowser for help on using the repository browser.