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

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

Unit tested MSI PDP with per attribute entry attribute authority addresses.

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