Uploaded image for project: 'mod_python'
  1. mod_python
  2. MODPYTHON-43

mod_python.publisher auth functions access to globals

    Details

    • Type: Improvement
    • Status: Closed
    • Priority: Minor
    • Resolution: Fixed
    • Affects Version/s: 3.1.4
    • Fix Version/s: 3.3.1
    • Component/s: publisher
    • Labels:
      None

      Description

      In the mod_python.publisher code, the code for performing basic authentication
      has in a few spots code of the form:

      if "_auth_" in func_code.co_names:
      i = list(func_code.co_names).index("_auth_")
      _auth_ = func_code.co_consts[i+1]
      if hasattr(_auth_, "co_name"):
      _auth_ = new.function(_auth_, globals())
      found_auth = 1

      What this does is that if the target of the request is a function and that function
      contains a nested function, which in this case is called "_auth_", then that
      nested function is turned into a callable object and is subsequently called to
      determine if the user is able to perform the request.

      In making the nested function callable, it uses "globals()". By using this though
      it is using the globals from the mod_python.publisher module and not the
      module which the nested function is contained within. This means that the
      following code will actually fail.

      import xxx

      def function(req):

      def _auth_(req,username,password):
      return xxx.auth(req,username,password)

      This is because the module "xxx" imported at global scope within the module isn't
      available to the nested function when it is called as it is seeing the globals of
      mod_python.publisher instead. To get around the problem, the import has to be
      local to the nested function.

      def function(req):

      def _auth_(req,username,password):
      import xxx
      return xxx.auth(req,username,password)

      Since in this case the auth function being called is a nested function, we know that
      we can actually grab the globals for the correct module by getting "func_globals"
      from the enclosing function.

      if "_auth_" in func_code.co_names:
      i = list(func_code.co_names).index("_auth_")
      _auth_ = func_code.co_consts[i+1]
      if hasattr(_auth_, "co_name"):
      _auth_ = new.function(_auth_, object.func_globals)
      found_auth = 1

      Ie., instead of "globals()", use "object.func_globals" where "object is the enclosing
      function object.

        Activity

        Hide
        grahamd Graham Dumpleton added a comment -

        It is worth noting that the original code is fragile in other ways as well.
        Specifically, the code is probably only gauranteed to work if the nested
        auth functions are the first nested functions in the method. If they aren't
        then any nested functions appearing before them must not have default
        parameters.

        Take for example the following code:

        def func1():
        def func2(a,b="b"):
        pass
        def _auth_(req,user,password):
        pass

        func_code = func1.func_code

        print func_code.co_names
        print func_code.co_consts

        print list(func_code.co_names).index("_auth_")
        print func_code.co_consts[list(func_code.co_names).index("__auth__")+1]

        When run this produces:

        ('func2', '_auth_')
        (None, 'b', <code object func2 at 0x61a60, file "bug.py", line 2>, <code object _auth_ at 0x61aa0, file "bug.py", line 4>)
        1
        <code object func2 at 0x61a60, file "bug.py", line 2>

        Where the code is supposed to select _auth_(), it is errornously selecting
        func2() instead. This is because the constants that represent the default
        parameters of the first nested functions, are throwing out the calculation
        to work out which is the code object for _auth_().

        Even if func2() didn't exist, the selection process would also be thrown out
        if _auth_() itself had any default parameters.

        So far from what I can work out, there doesn't seem to be a way of knowing
        when extra constants are inserted which correspond to default parameters
        of nested functions.

        Thus, to be safe, one must ensure auth functions are the first things to be
        defined in the function and that the auth functions themselves should not
        have any default parameters.

        Show
        grahamd Graham Dumpleton added a comment - It is worth noting that the original code is fragile in other ways as well. Specifically, the code is probably only gauranteed to work if the nested auth functions are the first nested functions in the method. If they aren't then any nested functions appearing before them must not have default parameters. Take for example the following code: def func1(): def func2(a,b="b"): pass def _ auth _(req,user,password): pass func_code = func1.func_code print func_code.co_names print func_code.co_consts print list(func_code.co_names).index("_ auth _") print func_code.co_consts [list(func_code.co_names).index("__auth__")+1] When run this produces: ('func2', '_ auth _') (None, 'b', <code object func2 at 0x61a60, file "bug.py", line 2>, <code object _ auth _ at 0x61aa0, file "bug.py", line 4>) 1 <code object func2 at 0x61a60, file "bug.py", line 2> Where the code is supposed to select _ auth _(), it is errornously selecting func2() instead. This is because the constants that represent the default parameters of the first nested functions, are throwing out the calculation to work out which is the code object for _ auth _(). Even if func2() didn't exist, the selection process would also be thrown out if _ auth _() itself had any default parameters. So far from what I can work out, there doesn't seem to be a way of knowing when extra constants are inserted which correspond to default parameters of nested functions. Thus, to be safe, one must ensure auth functions are the first things to be defined in the function and that the auth functions themselves should not have any default parameters.
        Hide
        grahamd Graham Dumpleton added a comment -

        Patches attached as grahamd_20060224_MP43_1.diff.

        If no one can see problems with this, I will commit change into repository for 3.3.

        Show
        grahamd Graham Dumpleton added a comment - Patches attached as grahamd_20060224_MP43_1.diff. If no one can see problems with this, I will commit change into repository for 3.3.

          People

          • Assignee:
            grahamd Graham Dumpleton
            Reporter:
            grahamd Graham Dumpleton
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development