source: TI12-security/trunk/python/ndg_security_common/ndg/security/common/authz/msi.py @ 6062

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