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

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

New Policy Information Point class ndg.security.common.authz.pip.esg.PIP for ESG Authorisation Service.

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