Security changes included, please review!!!!

Author: mramm
Date: Sat Mar 14 18:29:34 2009
New Revision: 6503
URL: http://trac.turbogears.org/changeset/6503

Log:
Fixed controller auth to always use _check_security, and to flash proper
messages, and properly return 403's when the user is authenticated, but not
authorized.

Fixed some issues with the auth texts (extra_environ not being set).

Fixed one test which was checking for the wrong auth denial flash message.
 Made controller dispatch only look for notfound handlers on 404's not 403 (
or any other http error for that matter).

Modified:
  trunk/tg/controllers.py
  trunk/tg/test_stack/test_authz.py

Modified: trunk/tg/controllers.py
==============================================================================
--- trunk/tg/controllers.py     (original)
+++ trunk/tg/controllers.py     Sat Mar 14 18:29:34 2009
@@ -18,6 +18,8 @@
 import pylons
 from pylons import url as pylons_url, config
 from pylons.controllers import WSGIController
+from pylons.controllers.util import abort
+
 from repoze.what.predicates import NotAuthorizedError
 import tw

@@ -26,6 +28,7 @@
 from tg.render import render as tg_render
 from tg.decorators import expose, allow_only
 from tg.i18n import setup_i18n
+from tg.flash import flash

 from webob import Request
 from webob.exc import HTTPUnauthorized
@@ -457,7 +460,7 @@
        """
        pass

-def _check_security(obj):
+def _check_controller_auth(obj):
    """this function checks if a controller has a 'alow_only' attribute and
if
    it is the case, test that this require predicate can be evaled to True.
    It will raise a Forbidden exception if the predicate is not valid.
@@ -468,10 +471,7 @@
        klass_instance = obj

    if hasattr(klass_instance, "_check_security"):
-        if not klass_instance._check_security():
-            if hasattr(klass_instance, "_failed_authorization"):
-                return klass_instance._failed_authorization()
-            raise HTTPUnauthorized().exception
+        klass_instance._check_security()

 def _object_dispatch(obj, url_path):
    remainder = url_path
@@ -503,9 +503,12 @@
            log.debug("a 401 error occured for obj: %s" % obj)
            raise

-        except HTTPException:
+        except HTTPException, e:
+            if e.status_int != 404:
+                raise e.exception
+
            if not notfound_handlers:
-                raise HTTPNotFound().exception
+                raise e.exception

            name, obj, parent, remainder = notfound_handlers.pop()
            if name == 'default':
@@ -517,7 +520,7 @@
                continue

 def _find_restful_dispatch(obj, parent, remainder):
-    _check_security(obj)
+    _check_controller_auth(obj)
    if not inspect.isclass(obj) and not isinstance(obj, RestController):
        return obj, remainder
    if inspect.isclass(obj) and not issubclass(obj, RestController):
@@ -611,7 +614,7 @@
        if obj is None:
            raise HTTPNotFound().exception

-        _check_security(obj)
+        _check_controller_auth(obj)

        if _iscontroller(obj):
            return obj, parent, remainder
@@ -781,21 +784,32 @@
        return result

    def _check_security(self):
-        if not hasattr(self, "allow_only") or  self.allow_only is None:
+        if not hasattr(self, "allow_only") or self.allow_only is None:
            log.debug('No controller-wide authorization at %s',
                      pylons.request.path)
            return True
-
-        environ = pylons.request.environ
        try:
-            self.allow_only.check_authorization(environ)
-            log.debug('Succeeded controller-wide authorization at %s',
-                      pylons.request.path)
-            return True
-        except NotAuthorizedError, error:
-            log.debug('Failed controller authorization at %s',
-                      pylons.request.path)
-            return False
+            predicate = self.allow_only
+            predicate.check_authorization(pylons.request.environ)
+        except NotAuthorizedError, e:
+            reason = unicode(e)
+            if hasattr(self, '_failed_authorization'):
+                # Should shortcircut the rest, but if not we will still
+                # deny authorization
+                self._failed_authorization(reason)
+            if pylons.request.environ.get('REMOTE_USER'):
+                # The user is authenticated but not allowed.
+                code = 403
+                status = 'error'
+            else:
+                # The user has not been not authenticated.
+                code = 401
+                status = 'warning'
+                reason = "The current user must have been authenticated"
+            pylons.response.status = code
+            flash(reason, status=status)
+            abort(code, comment=reason)
+

 class WSGIAppController(TGController):
    """

Modified: trunk/tg/test_stack/test_authz.py
==============================================================================
--- trunk/tg/test_stack/test_authz.py   (original)
+++ trunk/tg/test_stack/test_authz.py   Sat Mar 14 18:29:34 2009
@@ -155,7 +155,7 @@
 class ControllerWithAllowOnlyDecoratorAndAuthzDenialHandler(TGController):
    """
    Mock TG2 protected controller using the @allow_only decorator, but also
-    using .failed_authorization()
+    using ._failed_authorization()

    """

@@ -311,7 +311,7 @@
        self._check_flash(resp, 'The current user must have been
authenticated')
        # As an authenticated user:
        environ = {'REMOTE_USER': 'someone'}
-        resp = self.app.get('/hr/', status=403)
+        resp = self.app.get('/hr/', extra_environ = environ, status=403)
        assert "you can manage Human Resources" not in resp.body
        self._check_flash(resp, r'The current user must be
\"hiring-manager\"')

@@ -328,7 +328,7 @@
        self._check_flash(resp, 'The current user must have been
authenticated')
        # As an authenticated user:
        environ = {'REMOTE_USER': 'someone'}
-        resp = self.app.get('/hr/hire/gustavo', status=403)
+        resp = self.app.get('/hr/hire/gustavo', extra_environ = environ,
status=403)
        assert "was just hired" not in resp.body
        self._check_flash(resp, r'The current user must be
\"hiring-manager\"')

@@ -387,6 +387,7 @@
    def test_authz_granted_without_require(self):
        environ = {'REMOTE_USER': 'hiring-manager'}
        resp = self.app.get('/', extra_environ=environ, status=200)
+        print resp
        self.assertEqual("you can manage Human Resources", resp.body)

    def test_authz_denied_without_require(self):
@@ -396,7 +397,7 @@
        self._check_flash(resp, 'The current user must have been
authenticated')
        # As an authenticated user:
        environ = {'REMOTE_USER': 'someone'}
-        resp = self.app.get('/', status=403)
+        resp = self.app.get('/', extra_environ = environ, status=403)
        assert "you can manage Human Resources" not in resp.body
        self._check_flash(resp, r'The current user must be
\"hiring-manager\"')

@@ -410,10 +411,11 @@
        # As an anonymous user:
        resp = self.app.get('/hire/gustavo', status=401)
        assert "was just hired" not in resp.body
+        print resp.body
        self._check_flash(resp, 'The current user must have been
authenticated')
        # As an authenticated user:
        environ = {'REMOTE_USER': 'someone'}
-        resp = self.app.get('/hire/gustavo', status=403)
+        resp = self.app.get('/hire/gustavo', extra_environ = environ,
status=403)
        assert "was just hired" not in resp.body
        self._check_flash(resp, r'The current user must be
\"hiring-manager\"')

@@ -452,7 +454,7 @@
        # As an anonymous user:
        resp = self.app.get('/mounted_app/da-path', status=401)
        assert "Hello from /mounted_app/" not in resp.body
-        self._check_flash(resp, r'The current user must be \"gustavo\"')
+        self._check_flash(resp, r'The current user must have been
authenticated')
        # As an authenticated user:
        environ = {'REMOTE_USER': 'non-gustavo'}
        resp = self.app.get('/mounted_app/da-path', extra_environ=environ,



-- 
Mark Ramm-Christensen
email: mark at compoundthinking dot com
blog: www.compoundthinking.com/blog

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"TurboGears Trunk" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/turbogears-trunk?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to