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

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

Refactoring Credential Wallet to enable caching of SAML assertions.

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
524from ndg.security.common.wssecurity import WSSecurityConfig
525
526
527class PIP(object):
528    """Policy Information Point - this implementation enables the PDP to
529    retrieve attributes about the Subject"""
530    wsseSectionName = 'wssecurity'
531   
532    def __init__(self, prefix='', **cfg):
533        '''Set-up WS-Security and SSL settings for connection to an
534        Attribute Authority
535       
536        @type **cfg: dict
537        @param **cfg: keywords including 'sslCACertFilePathList' used to set a
538        list of CA certificates for an SSL connection to the Attribute
539        Authority if used and also WS-Security settings as used by
540        ndg.security.common.wssecurity.WSSecurityConfig
541        '''
542        self.wssecurityCfg = WSSecurityConfig()
543        wssePrefix = prefix + PIP.wsseSectionName
544        self.wssecurityCfg.update(cfg, prefix=wssePrefix)
545                 
546        # List of CA certificates used to verify peer certificate with SSL
547        # connections to Attribute Authority
548        self.sslCACertFilePathList = cfg.get(prefix+'sslCACertFilePathList', [])
549       
550        # List of CA certificates used to verify the signatures of
551        # Attribute Certificates retrieved
552        self.caCertFilePathList = cfg.get(prefix + 'caCertFilePathList', [])
553
554
555    def attributeQuery(self, attributeQuery):
556        """Query the Attribute Authority specified in the request to retrieve
557        the attributes if any corresponding to the subject
558       
559        @type attributeResponse: PIPAttributeQuery
560        @param attributeResponse:
561        @rtype: PIPAttributeResponse
562        @return: response containing the attributes retrieved from the
563        Attribute Authority"""
564       
565        subject = attributeQuery[PIPAttributeQuery.SUBJECT_NS]
566        username = subject[Subject.USERID_NS]
567        sessionId = subject[Subject.SESSIONID_NS]
568        attributeAuthorityURI = attributeQuery[
569                                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS]
570       
571        sessionId = subject[Subject.SESSIONID_NS]
572       
573        log.debug("PIP: received attribute query: %r", attributeQuery)
574       
575        attributeCertificate = self._getAttributeCertificate(
576                    attributeAuthorityURI,
577                    username=username,
578                    sessionId=sessionId,
579                    sessionManagerURI=subject[Subject.SESSIONMANAGERURI_NS])
580
581        attributeResponse = PIPAttributeResponse()
582        attributeResponse[Subject.ROLES_NS] = attributeCertificate.roles
583       
584        log.debug("PIP.attributeQuery response: %r", attributeResponse)
585       
586        return attributeResponse
587   
588    def _getAttributeCertificate(self,
589                                 attributeAuthorityURI,
590                                 username=None,
591                                 sessionId=None,
592                                 sessionManagerURI=None):
593        '''Retrieve an Attribute Certificate
594
595        @type attributeAuthorityURI: basestring
596        @param attributeAuthorityURI: URI to Attribute Authority service
597        @type username: basestring
598        @param username: subject user identifier - could be an OpenID       
599        @type sessionId: basestring
600        @param sessionId: Session Manager session handle
601        @type sessionManagerURI: basestring
602        @param sessionManagerURI: URI to remote session manager service
603        @rtype: ndg.security.common.AttCert.AttCert
604        @return: Attribute Certificate containing user roles
605        '''
606
607        if sessionId and sessionManagerURI:
608            attrCert = self._getAttributeCertificateFromSessionManager(
609                                                     attributeAuthorityURI,
610                                                     sessionId,
611                                                     sessionManagerURI)
612        else:
613            attrCert = self._getAttributeCertificateFromAttributeAuthority(
614                                                     attributeAuthorityURI,
615                                                     username)
616       
617        try:
618            attrCert.certFilePathList = self.caCertFilePathList
619            attrCert.isValid(raiseExcep=True)
620       
621        except AttCertInvalidSignature, e:
622            log.exception(e)
623            raise AttributeCertificateInvalidSignature()
624       
625        except AttCertNotBeforeTimeError, e:   
626            log.exception(e)
627            raise AttributeCertificateNotBeforeTimeError()
628       
629        except AttCertExpired, e:   
630            log.exception(e)
631            raise AttributeCertificateExpired()
632
633        except AttCertError, e:
634            log.exception(e)
635            raise InvalidAttributeCertificate()
636       
637        return attrCert
638   
639           
640    def _getAttributeCertificateFromSessionManager(self,
641                                                   attributeAuthorityURI,
642                                                   sessionId,
643                                                   sessionManagerURI):
644        '''Retrieve an Attribute Certificate using the subject's Session
645        Manager
646       
647        @type sessionId: basestring
648        @param sessionId: Session Manager session handle
649        @type sessionManagerURI: basestring
650        @param sessionManagerURI: URI to remote session manager service
651        @type attributeAuthorityURI: basestring
652        @param attributeAuthorityURI: URI to Attribute Authority service
653        @rtype: ndg.security.common.AttCert.AttCert
654        @return: Attribute Certificate containing user roles
655        '''
656       
657        log.debug("PIP._getAttributeCertificateFromSessionManager ...")
658       
659        try:
660            # Create Session Manager client - if a file path was set, setting
661            # are read from a separate config file section otherwise, from the
662            # PDP config object
663            smClnt = SessionManagerClient(
664                            uri=sessionManagerURI,
665                            sslCACertFilePathList=self.sslCACertFilePathList,
666                            cfg=self.wssecurityCfg)
667        except Exception, e:
668            log.error("Creating Session Manager client: %s" % e)
669            raise InitSessionCtxError()
670       
671         
672        try:
673            # Make request for attribute certificate
674            return smClnt.getAttCert(
675                                attributeAuthorityURI=attributeAuthorityURI,
676                                sessID=sessionId)
677       
678        except AttributeRequestDenied, e:
679            log.error("Request for attribute certificate denied: %s" % e)
680            raise PDPUserAccessDenied()
681       
682        except SessionNotFound, e:
683            log.error("No session found: %s" % e)
684            raise SessionNotFoundMsg()
685
686        except SessionExpired, e:
687            log.error("Session expired: %s" % e)
688            raise SessionExpiredMsg()
689
690        except SessionCertTimeError, e:
691            log.error("Session cert. time error: %s" % e)
692            raise InvalidSessionMsg()
693           
694        except InvalidSession, e:
695            log.error("Invalid user session: %s" % e)
696            raise InvalidSessionMsg()
697
698        except Exception, e:
699            log.error("Request from Session Manager [%s] to Attribute "
700                      "Authority [%s] for attribute certificate: %s: %s" % 
701                      (sessionManagerURI,
702                       attributeAuthorityURI,
703                       e.__class__, e))
704            raise AttributeCertificateRequestError()
705
706           
707    def _getAttributeCertificateFromAttributeAuthority(self,
708                                                       attributeAuthorityURI,
709                                                       username):
710        '''Retrieve an Attribute Certificate direct from an Attribute
711        Authority.  This method is invoked if no session ID or Session
712        Manager endpoint where provided
713       
714        @type username: basestring
715        @param username: user identifier - may be an OpenID URI
716        @type attributeAuthorityURI: basestring
717        @param attributeAuthorityURI: URI to Attribute Authority service
718        @rtype: ndg.security.common.AttCert.AttCert
719        @return: Attribute Certificate containing user roles
720        '''
721       
722        log.debug("PIP._getAttributeCertificateFromAttributeAuthority ...")
723       
724        try:
725            # Create Attribute Authority client - if a file path was set,
726            # settingare read  from a separate config file section otherwise,
727            # from the PDP config object
728            aaClnt = AttributeAuthorityClient(
729                            uri=attributeAuthorityURI,
730                            sslCACertFilePathList=self.sslCACertFilePathList,
731                            cfg=self.wssecurityCfg)
732        except Exception, e:
733            log.error("Creating Attribute Authority client: %s" % e)
734            raise InitSessionCtxError()
735       
736         
737        try:
738            # Make request for attribute certificate
739            return aaClnt.getAttCert(userId=username)
740       
741       
742        except AA_AttributeRequestDenied, e:
743            log.error("Request for attribute certificate denied: %s" % e)
744            raise PDPUserAccessDenied()
745       
746        # TODO: handle other specific Exception types here for more fine
747        # grained response info
748
749        except Exception, e:
750            log.error("Request to Attribute Authority [%s] for attribute "
751                      "certificate: %s: %s", attributeAuthorityURI,
752                      e.__class__, e)
753            raise AttributeCertificateRequestError()
754 
755           
756class PDP(object):
757    """Policy Decision Point"""
758   
759    def __init__(self, policy, pip):
760        """Read in a file which determines access policy"""
761        self.policy = policy
762        self.pip = pip
763
764    def _getPolicy(self):
765        if self._policy is None:
766            raise TypeError("Policy object has not been initialised")
767        return self._policy
768   
769    def _setPolicy(self, policy):
770        if not isinstance(policy, (Policy, None.__class__)):
771            raise TypeError("Expecting %s or None type for PDP policy; got %r"%
772                            (Policy.__class__.__name__, policy))
773        self._policy = policy
774
775    policy = property(fget=_getPolicy,
776                      fset=_setPolicy,
777                      doc="Policy type object used by the PDP to determine "
778                          "access for resources")
779
780    def _getPIP(self):
781        if self._pip is None:
782            raise TypeError("PIP object has not been initialised")
783       
784        return self._pip
785   
786    def _setPIP(self, pip):
787        if not isinstance(pip, (PIP, None.__class__)):
788            raise TypeError("Expecting %s or None type for PDP PIP; got %r"%
789                            (PIP.__class__.__name__, pip))
790        self._pip = pip
791
792    pip = property(fget=_getPIP,
793                   fset=_setPIP,
794                   doc="Policy Information Point - PIP type object used by "
795                       "the PDP to retrieve user attributes")
796   
797    def evaluate(self, request):
798        '''Make access control decision'''
799       
800        # Look for matching targets to the given resource
801        resourceURI = request.resource[Resource.URI_NS]
802        matchingTargets = [target for target in self.policy.targets
803                           if target.regEx.match(resourceURI) is not None]
804        numMatchingTargets = len(matchingTargets)
805        if numMatchingTargets == 0:
806            log.debug("PDP.evaluate: granting access - no targets matched "
807                      "the resource URI path [%s]", 
808                      resourceURI)
809            return Response(status=Response.DECISION_PERMIT)
810       
811        # Iterate through matching targets checking for user access
812        request.subject[Subject.ROLES_NS] = []
813        permitForAllTargets = [Response.DECISION_PERMIT]*numMatchingTargets
814       
815        # Keep a look-up of the decisions for each target
816        status = []
817       
818        for matchingTarget in matchingTargets:
819           
820            # Make call to the Policy Information Point to pull user
821            # attributes applicable to this resource
822            attributeQuery = PIPAttributeQuery()
823            attributeQuery[PIPAttributeQuery.SUBJECT_NS] = request.subject
824           
825            attributeQuery[PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS] = \
826                                    matchingTarget.attributeAuthorityURI
827           
828            # Exit from function returning indeterminate status if a
829            # problem occurs here
830            try:
831                attributeResponse = self.pip.attributeQuery(attributeQuery)
832               
833            except SubjectRetrievalError, e:
834                # i.e. a defined exception within the scope of this
835                # module
836                log.exception(e)
837                return Response(Response.DECISION_INDETERMINATE,
838                                message=str(e))
839               
840            except Exception, e:
841                log.exception(e)
842                return Response(Response.DECISION_INDETERMINATE,
843                                message="An internal error occurred")
844                           
845            # Accumulate attributes retrieved from multiple attribute
846            # authorities
847            request.subject[Subject.ROLES_NS] += attributeResponse[
848                                                        Subject.ROLES_NS]
849               
850            # Match the subject's attributes against the target
851            # One of any rule - at least one of the subject's attributes
852            # must match one of the attributes restricting access to the
853            # resource.
854            log.debug("PDP.evaluate: Matching subject attributes %r against "
855                      "resource attributes %r ...", 
856                      request.subject[Subject.ROLES_NS],
857                      matchingTarget.attributes)
858           
859            status.append(PDP._match(matchingTarget.attributes, 
860                                     request.subject[Subject.ROLES_NS]))
861           
862        # All targets must yield permit status for access to be granted
863        if status == permitForAllTargets:
864            return Response(Response.DECISION_PERMIT)
865        else:   
866            return Response(Response.DECISION_DENY,
867                            message="Insufficient privileges to access the "
868                                    "resource")
869       
870    @staticmethod
871    def _match(resourceAttr, subjectRoleAttr):
872        """Helper method to iterate over user and resource attributes
873        If one at least one match is found, a permit response is returned
874        """
875        for attr in resourceAttr:
876            if attr in subjectRoleAttr:
877                return Response.DECISION_PERMIT
878           
879        return Response.DECISION_DENY
880
881       
Note: See TracBrowser for help on using the repository browser.