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

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@7257
Revision 7257, 13.1 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 2: XACML-Security Integration

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