source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authzservice.py @ 6593

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

Moved XACML code into it's own NDG_XACML branch in the trunk. The code is still incomplete and not in use by the rest of ndg.security.

Line 
1"""Authorization service with SAML 2.0 authorisation decision query interface
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "17/02/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 ndg.security.common.utils.factory import importModuleObject
15from ndg.security.common.authz.msi import Policy, PDP, PIP
16
17
18class AuthzServiceMiddlewareError(Exception):
19    """Authorisation Service generic exception type"""
20   
21
22class AuthzServiceMiddlewareConfigError(AuthzServiceMiddlewareError):
23    """Authorisation Service configuration error"""
24   
25   
26class AuthzServiceMiddleware(object):
27    '''WSGI to add an NDG Security Authorization Service in the environ.  This
28    enables multiple WSGI filters to access the same underlying Attribute
29    Authority instance e.g. provide SAML SOAP and WSDL SOAP based interfaces
30    to the same Authorization Service
31    '''
32    DEFAULT_AUTHZ_DECISION_QUERY_IFACE_KEYNAME = \
33    'ndg.security.server.wsgi.authorizationservice.authzDecisionQueryInterface'
34   
35    ENVIRON_KEYNAME_AUTHZ_DECISION_QUERY_IFACE_OPTNAME = \
36        'authzDecisionQueryInterfaceEnvironKeyName'
37   
38    AUTHZ_DECISION_QUERY_IFACE_OPTNAME = 'authzDecisionQueryInterface'
39   
40    POLICY_FILEPATH_OPTNAME = 'filePath'
41    POLICY_CFG_PREFIX = 'policy.'
42   
43    __slots__ = ('__pdp', '__authzDecisionFunc', '__authzDecisionFuncKeyName')
44       
45    def __init__(self, app):
46        '''Set-up an Authorization Service instance
47        '''       
48        super(AuthzServiceMiddleware, self).__init__(app, {}) 
49         
50        self._app = app
51        self.__pdp = None
52        self.__authzDecisionFunc = None
53        self.__authzDecisionFuncKeyName = None
54   
55    def initialise(self, global_conf, prefix='authorizationservice.',
56                   **app_conf):
57        """Set-up Authorization Service middleware using a Paste app factory
58        pattern.  Overloaded base class method to enable custom settings from
59        app_conf
60       
61        @type app: callable following WSGI interface
62        @param app: next middleware application in the chain     
63        @type global_conf: dict       
64        @param global_conf: PasteDeploy global configuration dictionary
65        @type prefix: basestring
66        @param prefix: prefix for configuration items
67        @type app_conf: dict       
68        @param app_conf: PasteDeploy application specific configuration
69        dictionary
70        """
71        cls = AuthzServiceMiddleware
72       
73        # Initialise the Authorisation Policy
74        policyCfgPrefix = prefix + cls.POLICY_CFG_PREFIX
75        policyFilePathOptName = policyCfgPrefix + cls.POLICY_FILEPATH_OPTNAME
76       
77        policyFilePath = app_conf.get(policyFilePathOptName)
78        if policyFilePath is None:
79            raise AuthzServiceMiddlewareConfigError("Expecting policy file "
80                                                    "setting option %r" %
81                                                    policyFilePathOptName)
82       
83        policy = Policy.Parse(policyFilePath)
84       
85        # Initialise the Policy Information Point
86        pip = PIP()
87       
88        # Initialise the PDP reading in the policy       
89        self.pdp = PDP(policy, pip)
90       
91        authzDecisionQueryIfaceEnvironKeyOptName = prefix + \
92            cls.ENVIRON_KEYNAME_AUTHZ_DECISION_QUERY_IFACE_OPTNAME
93           
94        self.authzDecisionFuncKeyName = app_conf.get(
95                                authzDecisionQueryIfaceEnvironKeyOptName,
96                                cls.DEFAULT_AUTHZ_DECISION_QUERY_IFACE_KEYNAME)
97       
98        authzDecisionQueryIfaceOptName = prefix + \
99                                         cls.AUTHZ_DECISION_QUERY_IFACE_OPTNAME
100                                         
101        self.authzDecisionFunc = app_conf.get(
102                                authzDecisionQueryIfaceOptName,
103                                cls.DEFAULT_AUTHZ_DECISION_QUERY_IFACE_KEYNAME)
104       
105    @classmethod
106    def filter_app_factory(cls, app, global_conf, **app_conf):
107        '''Wrapper to enable instantiation compatible with Paste Deploy
108        filter application factory function signature
109       
110        @type app: callable following WSGI interface
111        @param app: next middleware application in the chain     
112        @type global_conf: dict       
113        @param global_conf: PasteDeploy global configuration dictionary
114        @type prefix: basestring
115        @param prefix: prefix for configuration items
116        @type app_conf: dict       
117        @param app_conf: PasteDeploy application specific configuration
118        dictionary
119        '''
120        app = cls(app)
121        app.initialise(global_conf, **app_conf)
122       
123        return app
124   
125    def __call__(self, environ, start_response):
126        '''Set the Authorization Decision function in environ
127       
128        @type environ: dict
129        @param environ: WSGI environment variables dictionary
130        @type start_response: function
131        @param start_response: standard WSGI start response function
132        @rtype: iterable
133        @return: next application in the WSGI stack
134        '''
135        environ[self.authzDecisionFuncKeyName] = self.authzDecisionFunc
136        return self._app(environ, start_response)
137
138    def _getPdp(self):
139        return self.__pdp
140
141    def _setPdp(self, value):
142        if not isinstance(value, PDP):
143            raise TypeError('Expecting %r type for "pdp" attribute; got %r '
144                            'instead' % value)
145           
146        self.__pdp = value
147
148    pdp = property(_getPdp, _setPdp, None, "Policy Decision Point")
149
150    def _get_authzDecisionFuncKeyName(self):
151        return self.__authzDecisionFuncKeyName
152
153    def _set_authzDecisionFuncKeyName(self, val):
154        if not isinstance(val, basestring):
155            raise TypeError('Expecting %r for "getAuthzDecisionKeyName" '
156                            'attribute; got %r' % (basestring, type(val)))
157        self.__authzDecisionFuncKeyName = val
158       
159    authzDecisionFuncKeyName = property(fget=_get_authzDecisionFuncKeyName, 
160                                        fset=_set_authzDecisionFuncKeyName, 
161                                        doc="Key name used to index "
162                                            "Authorization Service SAML authz "
163                                            "decision query function in "
164                                            "environ dictionary")
165   
166    def _getAuthzDecisionFunc(self):
167        return self.__authzDecisionFunc
168   
169    def _setAuthzDecisionFunc(self, value):
170        if isinstance(value, basestring):
171            self.__authzDecisionFunc = importModuleObject(value)
172           
173        elif iscallable(value):
174            self.__authzDecisionFunc = value
175        else:
176            raise TypeError('Expecting callable for "authzDecisionFunc" '
177                            'attribute; got %r instead.' % type(value))
178   
179    authzDecisionFunc = property(_getAuthzDecisionFunc,
180                                 _setAuthzDecisionFunc,
181                                 doc="authorisation decision function set in "
182                                     "environ for downstream SAML Query "
183                                     "middleware to invoke in response to "
184                                     "<authzDecisionQuery>s")
185         
186    def createAuthzDecisionFunc(self):
187        """Return the authorisation decision function so that __call__ can add
188        it to environ for the SAML Query middleware to pick up and invoke
189       
190        @return: SAML authorisation decision function
191        @rtype: callable
192        """
193        def getAuthzDecision(authzDecisionQuery):
194            """Authorisation decision function accepts a SAML AuthzDecisionQuery
195            and calls the Policy Decision Point returning a response
196           
197            @type authzDecisionQuery: saml.saml2.core.AuthzDecisionQuery
198            @param authzDecisionQuery: WSGI environment variables dictionary
199            @rtype: saml.saml2.core.Response
200            @return: SAML response containing Authorisation Decision Statement
201            """       
202            # Make a request object to pass to the PDP
203            request = Request()
204            request.subject[Subject.USERID_NS
205                            ] = authzDecisionQuery.subject.nameID.value
206           
207            # IdP Session Manager specific settings:
208            #
209            # The following won't be set if the IdP running the OpenID Provider
210            # hasn't also deployed a Session Manager.  In this case, the
211            # Attribute Authority will be queried directly from here without a
212            # remote Session Manager intermediary to cache credentials
213            request.resource[Resource.URI_NS] = authzDecisionQuery.resource
214   
215            # Call the PDP
216            pdpResponse = self.pdp.evaluate(request)       
217           
218            response = Response()
219                       
220            authzDecisionStatement = AuthzDecisionStatement()
221            authzDecisionStatement.resource = authzDecisionQuery.resource
222            authzDecisionStatement.actions.append(Action())
223            authzDecisionStatement.actions[-1].namespace = Action.GHPP_NS_URI
224            authzDecisionStatement.actions[-1].value = Action.HTTP_GET_ACTION
225            assertion.authzDecisionStatements.append(authzDecisionStatement)
226
227            if pdpResponse.status == Response.DECISION_PERMIT:
228                log.info("AuthzServiceMiddleware.__call__: PDP granted "
229                         "access for URI path [%s] using policy [%s]", 
230                         resourceURI, 
231                         self.policyFilePath)
232               
233                authzDecisionStatement.decision = DecisionType.PERMIT
234           
235            elif pdpResponse.status == Response.DECISION_INDETERMINATE:
236                log.info("AuthzServiceMiddleware.__call__: PDP returned a "
237                         "status of [%s] denying access for URI path [%s] using "
238                         "policy [%s]", 
239                         pdpResponse.decisionValue2String[response.status],
240                         resourceURI,
241                         self.policyFilePath) 
242               
243                authzDecisionStatement.decision = DecisionType.INDETERMINATE
244                 
245            else:
246                log.info("AuthzServiceMiddleware.__call__: PDP returned a "
247                         "status of [%s] denying access for URI path [%s] using "
248                         "policy [%s]", 
249                         pdpResponse.decisionValue2String[response.status],
250                         resourceURI,
251                         self.policyFilePath) 
252               
253                authzDecisionStatement.decision = DecisionType.DENY
254                                                               
255           
256            now = datetime.utcnow()
257            response.issueInstant = now
258           
259            # Make up a request ID that this response is responding to
260            response.inResponseTo = query.id
261            response.id = str(uuid4())
262            response.version = SAMLVersion(SAMLVersion.VERSION_20)
263               
264            response.issuer = Issuer()
265            response.issuer.format = self.issuerFormat
266            response.issuer.value = self.issuer
267
268            response.status = Status()
269            response.status.statusCode = StatusCode()
270            response.status.statusMessage = StatusMessage()       
271           
272            response.status.statusCode.value = StatusCode.SUCCESS_URI
273            response.status.statusMessage.value = "Response created successfully"
274               
275            assertion = Assertion()
276            assertion.version = SAMLVersion(SAMLVersion.VERSION_20)
277            assertion.id = str(uuid4())
278            assertion.issueInstant = now
279           
280            # Add a conditions statement for a validity of 8 hours
281            assertion.conditions = Conditions()
282            assertion.conditions.notBefore = now
283            assertion.conditions.notOnOrAfter = now + timedelta(seconds=60*60*8)
284                   
285            assertion.subject = Subject() 
286            assertion.subject.nameID = NameID()
287            assertion.subject.nameID.format = query.subject.nameID.format
288            assertion.subject.nameID.value = query.subject.nameID.value
289               
290            assertion.issuer = Issuer()
291            assertion.issuer.format = self.issuerFormat
292            assertion.issuer.value = self.issuer
293   
294            response.assertions.append(assertion)
295            return response
296       
297        return getAuthzDecision
298
Note: See TracBrowser for help on using the repository browser.