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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/authz/msi.py@6586
Revision 6586, 36.1 KB checked in by pjkersha, 11 years ago (diff)

Started ESG Authorisation Service implementation ndg.security.server.wsgi.authorizationservice - SAML SOAP based interface to a Policy Decision Point enabling centralised policy for a range of services.

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