source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/ctx_handler/saml_ctx_handler.py @ 7298

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/ctx_handler/saml_ctx_handler.py@7298
Revision 7298, 13.2 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 2: XACML-Security Integration

  • Working on PIP to make Attribute Service query
  • Property svn:keywords set to Id
Line 
1"""XACML Context handler translates to and from SAML Authorisation Decision
2Query / Response
3
4"""
5__author__ = "P J Kershaw"
6__date__ = "14/05/10"
7__copyright__ = "(C) 2010 Science and Technology Facilities Council"
8__license__ = "BSD - see LICENSE file in top-level directory"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id$'
11import logging
12log = logging.getLogger(__name__)
13
14from datetime import datetime, timedelta
15from uuid import uuid4
16
17from ndg.saml.saml2 import core as _saml
18from ndg.saml.common import SAMLVersion
19
20from ndg.xacml.core import context as _xacmlContext
21from ndg.xacml.core.attribute import Attribute as XacmlAttribute
22from ndg.xacml.core.attributevalue import AttributeValueClassFactory as \
23    XacmlAttributeValueClassFactory
24from ndg.xacml.parsers.etree.factory import ReaderFactory
25
26from ndg.security.server.xacml.pip.saml_pip import PIP
27
28
29class SamlPEPRequest(object):
30    """Helper class for SamlCtxHandler.handlePEPRequest"""
31    __slots__ = ('__authzDecisionQuery', '__response', '__policyFilePath')
32   
33    def __init__(self):
34        self.__authzDecisionQuery = None
35        self.__response = None
36   
37    def _getAuthzDecisionQuery(self):
38        return self.__authzDecisionQuery
39
40    def _setAuthzDecisionQuery(self, value):
41        if not isinstance(value, _saml.AuthzDecisionQuery):
42            raise TypeError('Expecting %r type for "response" attribute, got %r'
43                            % (_saml.Response, type(value)))
44        self.__authzDecisionQuery = value
45       
46    authzDecisionQuery = property(_getAuthzDecisionQuery, 
47                                  _setAuthzDecisionQuery, 
48                                  doc="SAML Authorisation Decision Query")
49
50    def _getResponse(self):
51        return self.__response
52
53    def _setResponse(self, value):
54        if not isinstance(value, _saml.Response):
55            raise TypeError('Expecting %r type for "response" attribute, got %r'
56                            % (_saml.Response, type(value)))
57        self.__response = value
58
59    response = property(_getResponse, _setResponse, doc="SAML Response")
60   
61       
62class SamlCtxHandler(_xacmlContext.handler.CtxHandlerBase):
63    """XACML Context handler for accepting SAML 2.0 based authorisation
64    decision queries and interfacing to a PEP with SAML based Attribute Query
65    Interface
66    """
67    __slots__ = (
68        '__policyFilePath',
69        '__issuerProxy', 
70        '__assertionLifetime',
71    )
72   
73    def __init__(self):
74        super(SamlCtxHandler, self).__init__()
75       
76        # Proxy object for SAML Response Issuer attributes.  By generating a
77        # proxy the Response objects inherent attribute validation can be
78        # applied to Issuer related config parameters before they're assigned to
79        # the response issuer object generated in the authorisation decision
80        # query response
81        self.__issuerProxy = _saml.Issuer()
82        self.__assertionLifetime = 0.
83       
84        # Policy Information Point
85        self.pip = PIP()
86
87    def _getIssuerFormat(self):
88        if self.__issuerProxy is None:
89            return None
90        else:
91            return self.__issuerProxy.value
92
93    def _setIssuerFormat(self, value):
94        if self.__issuerProxy is None:
95            self.__issuerProxy = _saml.Issuer()
96           
97        self.__issuerProxy.format = value
98
99    issuerFormat = property(_getIssuerFormat, _setIssuerFormat, 
100                            doc="Issuer format")
101
102    def _getIssuerName(self):
103        if self.__issuerProxy is None:
104            return None
105        else:
106            return self.__issuerProxy.value
107
108    def _setIssuerName(self, value):
109        if self.__issuerProxy is None:
110            self.__issuerProxy = _saml.Issuer()
111           
112        self.__issuerProxy.value = value
113
114    issuerName = property(_getIssuerName, _setIssuerName, 
115                          doc="Name of issuer of SAML Authorisation Query "
116                              "Response")
117   
118    _getAssertionLifetime = lambda self: self.__assertionLifetime
119   
120    def _setAssertionLifetime(self, value):
121        if isinstance(value, (int, float, long, basestring)):
122            self.__assertionLifetime = float(value)
123        else:
124            raise TypeError('Expecting int, long, float or string type for '
125                            '"assertionLifetime" attribute; got %s instead' % 
126                            type(value))
127
128    assertionLifetime = property(fget=_getAssertionLifetime,
129                                 fset=_setAssertionLifetime,
130                                 doc="lifetime of assertion in seconds used to "
131                                     "set assertion conditions notOnOrAfter "
132                                     "time")
133       
134    def handlePEPRequest(self, pepRequest):
135        """Handle request from Policy Enforcement Point
136       
137        @param pepRequest: request containing a SAML authorisation decision
138        query and optionally an initialised SAML response object
139        @type pepRequest: ndg.security.server.xacml.saml_ctx_handler.SamlPEPRequest
140        @return: SAML authorisation decision response
141        @rtype: ndg.saml.saml2.core.Response
142        """
143        samlAuthzDecisionQuery = pepRequest.authzDecisionQuery
144       
145        xacmlRequest = self._createXacmlRequestCtx(samlAuthzDecisionQuery)
146       
147        # Call the PDP
148        xacmlResponse = self.pdp.evaluate(xacmlRequest)
149       
150        # Create the SAML Response
151        samlResponse = self._createSAMLResponseAssertion(samlAuthzDecisionQuery,
152                                                         pepRequest.response)
153       
154        samlAuthzDecisionStatement = samlResponse.assertions[0
155                                                ].authzDecisionStatements[0]
156       
157        # Convert the decision status
158        if (xacmlResponse.results[0].decision == 
159            _xacmlContext.result.Decision.PERMIT):
160            log.info("PDP granted access for URI path [%s]", 
161                     samlAuthzDecisionQuery.resource)
162           
163            samlAuthzDecisionStatement.decision = _saml.DecisionType.PERMIT
164       
165        elif (xacmlResponse.results[0].decision == 
166              _xacmlContext.result.Decision.INDETERMINATE):
167            log.info("PDP returned a status of [%s] denying access for URI "
168                     "path [%s]", _xacmlContext.result.Decision.INDETERMINATE,
169                     samlAuthzDecisionQuery.resource) 
170           
171            samlAuthzDecisionStatement.decision = \
172                                                _saml.DecisionType.INDETERMINATE
173        else:
174            log.info("PDP returned a status of [%s] denying access for URI "
175                     "path [%s]", _xacmlContext.result.Decision.DENY,
176                     samlAuthzDecisionQuery.resource) 
177           
178            samlAuthzDecisionStatement.decision = _saml.DecisionType.DENY
179
180        return samlResponse
181       
182    def pipQuery(self, request, designator):
183        """Query a Policy Information Point to retrieve the attribute values
184        corresponding to the specified input designator.  Optionally, update the
185        request context.  This could be a subject, environment or resource. 
186        Matching attributes values are returned
187        """
188        return self.pip.query(request, designator)
189   
190    def _createXacmlRequestCtx(self, samlAuthzDecisionQuery):
191        """Translate SAML authorisation decision query into a XACML request
192        context
193        """
194        xacmlRequest = _xacmlContext.request.Request()
195        xacmlSubject = _xacmlContext.subject.Subject()
196       
197        xacmlAttributeValueFactory = XacmlAttributeValueClassFactory()
198       
199        openidSubjectAttribute = XacmlAttribute()
200        roleAttribute = XacmlAttribute()
201       
202        openidSubjectAttribute.attributeId = \
203                                samlAuthzDecisionQuery.subject.nameID.format
204                                       
205        XacmlAnyUriAttributeValue = xacmlAttributeValueFactory(
206                                    'http://www.w3.org/2001/XMLSchema#anyURI')
207       
208        openidSubjectAttribute.dataType = XacmlAnyUriAttributeValue.IDENTIFIER
209       
210        openidSubjectAttribute.attributeValues.append(
211                                                    XacmlAnyUriAttributeValue())
212        openidSubjectAttribute.attributeValues[-1].value = \
213                                samlAuthzDecisionQuery.subject.nameID.value
214       
215        xacmlSubject.attributes.append(openidSubjectAttribute)
216
217        XacmlStringAttributeValue = xacmlAttributeValueFactory(
218                                    'http://www.w3.org/2001/XMLSchema#string')
219
220        # TODO: get attributes - replace hard coded values
221        roleAttribute.attributeId = "urn:ndg:security:authz:1.0:attr"
222        roleAttribute.dataType = XacmlStringAttributeValue.IDENTIFIER
223       
224        roleAttribute.attributeValues.append(XacmlStringAttributeValue())
225        roleAttribute.attributeValues[-1].value = 'staff' 
226       
227        xacmlSubject.attributes.append(roleAttribute)
228                                 
229        xacmlRequest.subjects.append(xacmlSubject)
230       
231        resource = _xacmlContext.resource.Resource()
232        resourceAttribute = XacmlAttribute()
233        resource.attributes.append(resourceAttribute)
234       
235        resourceAttribute.attributeId = \
236                            "urn:oasis:names:tc:xacml:1.0:resource:resource-id"
237                           
238        resourceAttribute.dataType = XacmlAnyUriAttributeValue.IDENTIFIER
239        resourceAttribute.attributeValues.append(XacmlAnyUriAttributeValue())
240        resourceAttribute.attributeValues[-1].value = \
241                                                samlAuthzDecisionQuery.resource
242
243        xacmlRequest.resources.append(resource)
244       
245        xacmlRequest.action = _xacmlContext.action.Action()
246       
247        for action in samlAuthzDecisionQuery.actions:
248            xacmlActionAttribute = XacmlAttribute()
249            xacmlRequest.action.attributes.append(xacmlActionAttribute)
250           
251            xacmlActionAttribute.attributeId = \
252                                "urn:oasis:names:tc:xacml:1.0:action:action-id"
253            xacmlActionAttribute.dataType = XacmlStringAttributeValue.IDENTIFIER
254            xacmlActionAttribute.attributeValues.append(
255                                                    XacmlStringAttributeValue())
256            xacmlActionAttribute.attributeValues[-1].value = action.value
257       
258        return xacmlRequest
259   
260    def _createSAMLResponseAssertion(self, authzDecisionQuery, response):
261        """Helper method to add an assertion containing an Authorisation
262        Decision Statement to the SAML response
263       
264        @param authzDecisionQuery: SAML Authorisation Decision Query
265        @type authzDecisionQuery: ndg.saml.saml2.core.AuthzDecisionQuery
266        @param response: SAML response
267        @type response: ndg.saml.saml2.core.Response
268        """
269       
270        # Check for a response set, if none present create one.
271        if response is None:
272            response = _saml.Response()
273           
274            now = datetime.utcnow()
275            response.issueInstant = now
276           
277            # Make up a request ID that this response is responding to
278            response.inResponseTo = authzDecisionQuery.id
279            response.id = str(uuid4())
280            response.version = SAMLVersion(SAMLVersion.VERSION_20)
281               
282            response.issuer = _saml.Issuer()
283            response.issuer.format = self.issuerFormat
284            response.issuer.value = self.issuerName
285   
286            response.status = _saml.Status()
287            response.status.statusCode = _saml.StatusCode()
288            response.status.statusMessage = _saml.StatusMessage()       
289           
290            response.status.statusCode.value = _saml.StatusCode.SUCCESS_URI
291            response.status.statusMessage.value = ("Response created "
292                                                   "successfully")
293       
294        assertion = _saml.Assertion()
295        response.assertions.append(assertion)
296           
297        assertion.version = SAMLVersion(SAMLVersion.VERSION_20)
298        assertion.id = str(uuid4())
299       
300        now = datetime.utcnow()
301        assertion.issueInstant = now
302       
303        # Add a conditions statement for a validity of 8 hours
304        assertion.conditions = _saml.Conditions()
305        assertion.conditions.notBefore = now
306        assertion.conditions.notOnOrAfter = now + timedelta(
307                                                seconds=self.assertionLifetime)
308               
309        assertion.subject = _saml.Subject()
310        assertion.subject.nameID = _saml.NameID()
311        assertion.subject.nameID.format = \
312            authzDecisionQuery.subject.nameID.format
313        assertion.subject.nameID.value = \
314            authzDecisionQuery.subject.nameID.value
315       
316        authzDecisionStatement = _saml.AuthzDecisionStatement()
317        assertion.authzDecisionStatements.append(authzDecisionStatement)
318                   
319        authzDecisionStatement.resource = authzDecisionQuery.resource
320       
321        for action in authzDecisionQuery.actions:
322            authzDecisionStatement.actions.append(_saml.Action())
323            authzDecisionStatement.actions[-1].namespace = action.namespace
324            authzDecisionStatement.actions[-1].value = action.value
325
326        return response
Note: See TracBrowser for help on using the repository browser.