Unable to edit grid record if I add @auth.requires_signature() to action. Is this normal?

87 views
Skip to first unread message

João Matos

unread,
Apr 20, 2019, 4:03:17 PM4/20/19
to web2py-users
If I replace @auth_requires_login() with @auth.requires_signature() to my index function (controller's main function) where a grid is created, the grid shows up without any issue, but if I try to edit a row, I get a Not Authorized message.

Anyone has any idea why this is happening?

If I replace @auth.requires_signature() with @auth.requires_login() everything works.

Massimo Di Pierro

unread,
Apr 20, 2019, 7:53:07 PM4/20/19
to web2py-users
It is intentional. The grid requires a valid user to make field editable. You can override this with:

grid(..., user_signature=False)

In any case if the user is not logged if you tables require a user signature, those fields will not be automatically filled.

João Matos

unread,
Apr 21, 2019, 3:56:25 AM4/21/19
to web2py-users
You didn't understand. In both cases I'm logged in.
If I have @requires_login() it all works. If I replace it with @requires_signature() the grid opens OK, but I'm unable to edit a record.

The menu option I created to open the grid has the user_signature=True and the grid has the default of user_signature=True.

Anthony

unread,
Apr 21, 2019, 9:07:42 AM4/21/19
to web2py-users
Can you show your code? I cannot reproduce this exact behavior.

Note, because the default behavior of @auth.requires_signature() is to include the query string when creating the signature, any functionality of the grid that uses the query string should not work, as the grid does not generate signatures for those links.

In any case, the grid already has built-in support for signed URLs for any write operations -- do you need more than that?

Anthony

João Matos

unread,
Apr 21, 2019, 10:53:08 AM4/21/19
to web2py-users
I wanted to have signed URL everywhere.
For that, I added user_signature=True to all my URL(). The grid has that as a default.
At this point everything worked with @requires_login() except one special case (I believe this special case may be related to the same issue I'm facing with this I describe here).

Then I added a var called sid (for session id) to every URL() which I use to identify the session (this way I'm able to distinguish ebetween 2 browser tabs).
At this point everything worked with @requires_login() except the special case I mentioned above.

Then I replaced @auth.requires_login() with @auth.requires_signature() and I'm able to access the grid but not the edit form. I receive a Not Authorized message.

In all these tests I'm logged in.

If I remove the sid var and keep the @auth.requires_signature() everything works.

If I replace t...@auth.requires_signature() with @auth.requires_login() and keep the sid var everything works.

Only the combination of both doesn't work.

Anthony

unread,
Apr 21, 2019, 12:22:44 PM4/21/19
to web2py-users
On Sunday, April 21, 2019 at 10:53:08 AM UTC-4, João Matos wrote:
I wanted to have signed URL everywhere.
For that, I added user_signature=True to all my URL(). The grid has that as a default.
At this point everything worked with @requires_login() except one special case (I believe this special case may be related to the same issue I'm facing with this I describe here).

Then I added a var called sid (for session id) to every URL() which I use to identify the session (this way I'm able to distinguish ebetween 2 browser tabs).
At this point everything worked with @requires_login() except the special case I mentioned above.

Then I replaced @auth.requires_login() with @auth.requires_signature() and I'm able to access the grid but not the edit form. I receive a Not Authorized message.

In all these tests I'm logged in.

If I remove the sid var and keep the @auth.requires_signature() everything works.

If I replace t...@auth.requires_signature() with @auth.requires_login() and keep the sid var everything works.

Only the combination of both doesn't work.

Need to see the code.
Message has been deleted

João Matos

unread,
Apr 21, 2019, 12:30:29 PM4/21/19
to web2py-users
Here is the correct version (my previous post, which I deleted, was another version):

#@auth.requires_signature()
@auth.requires_login()
def index():
   
# type: () -> Dict[str, gluon.DIV]
   
"""Index page.

    :return: Dict with grid.
    """

   
if session.return_to:
       
del session.return_to

    session
.table = 'opt_cat'

   
# Hidden fields in grid and edit/view form.
    db
.opt_cat.id.readable = False

    db
.opt_cat.one_opt_only.show_if = db.opt_cat.mandatory == False

   
if SUPERVISOR_ROLE_ID in auth.user_groups:
       
# Uses covering index opt_cat_is_active_name_en.
       
# Uses auto index sqlite_autoindex_opt_cat_1.
        grid
= SQLFORM.grid(
            db
.opt_cat,
            csv
=False,
            details
=False,
           
# Disable delete checkbox in edit form.
            editargs
=dict(deletable=False),
            maxtextlength
=GRID_COL_LEN_FOR_TEXT,
            ondelete
=on_delete,  # Grid only.
            onvalidation
=on_validation,  # And onupdate are form only.
           
orderby=db.opt_cat.name,
            paginate
=session.auth.user.pagination,
           
# represent_none='',  # Grid and view form only.
       
)  # type: gluon.DIV
    else:
       
# Hidden fields in grid and edit/view form.
        db
.opt_cat.canceled_on.readable = False
        db
.opt_cat.canceled_by.readable = False
        db
.opt_cat.cancel_approved_by.readable = False

       
# Uses covering index opt_cat_is_active_name_en (is_active=?).
       
# Uses index opt_cat_is_active_name (is_active=?).
        grid
= SQLFORM.grid(
            db
.opt_cat.is_active == True,
            create
=False,
            csv
=False,
            deletable
=False,
            details
=False,
            editable
=False,
            maxtextlength
=GRID_COL_LEN_FOR_TEXT,
           
orderby=db.opt_cat.name,
            paginate
=session.auth.user.pagination,
           
# represent_none='',  # Grid and view form only.
       
)

   
# Remove icons from default buttons.
    grid
.elements('span.icon', replace=None)

   
if request.args:
       
# Remove delete button.
        grid
.element('#delete_with_approval', replace=None)

   
if not request.args:
       
# Sort grid's search fields list.
        grid
.element('#w2p_query_fields').components = sort_grid_search_fields_list(grid)

       
if session.opt_cat_modified_on:
           
del session.opt_cat_modified_on
   
elif 'edit' in request.args:
       
# Edit uses opt_cat Pk.

        form
= grid.update_form  # type: gluon.sqlhtml.SQLFORM
       
# form['hidden'].update(mon=form.record.modified_on)
       
# Solves the record changed while editing, but doesn't solve it
       
# if the user 1st tries something that returns form.errors (eg.
       
# changing a unique field to something that already exists) and
       
# only after that he tries to save the record (which was changed
       
# by another user). For this the only solution I've found was
       
# using a session var.

       
if not session.opt_cat_modified_on:
            session
.opt_cat_modified_on = form.record.modified_on

       
if not form.record.is_active and not SUPERVISOR_ROLE_ID in auth.user_groups:
            session
.flash = T('Record was deleted while you were viewing the grid.')
            redirect
(URL(user_signature=True))

   
return dict(grid=grid)

Anthony

unread,
Apr 22, 2019, 7:59:34 AM4/22/19
to web2py-users
When using signed URLs, how do users get to that function (given that the URL requires a signature)? How do you construct the URL?

When using @auth.requires_signature(), does sorting and searching the grid work? Only edit fails?

Anthony

João Matos

unread,
Apr 22, 2019, 8:14:10 AM4/22/19
to web2py-users
It is called from the menu (models\menu.py). This is the code:

...
if auth.is_logged_in():
    response
.menu = [
       
(T('Home'), False, URL('default', 'index', user_signature=True)),
       
(T('Open work orders'), False, URL('open_wo', 'index', user_signature=True)),
       
(T('Tables'),
         
False,
         
None,
         
[
           
(db.opt_cat._plural, False, URL('opt_cat', 'index', vars={'sid': request.vars.sid}, user_signature=True)),
...

Didn't test the sorting or search then, but I tested now and they also don't work, returning the same Not Authorized message.

Anthony

unread,
Apr 22, 2019, 4:41:32 PM4/22/19
to web2py-users
On Monday, April 22, 2019 at 8:14:10 AM UTC-4, João Matos wrote:
It is called from the menu (models\menu.py). This is the code:

...
if auth.is_logged_in():
    response
.menu = [
       
(T('Home'), False, URL('default', 'index', user_signature=True)),
       
(T('Open work orders'), False, URL('open_wo', 'index', user_signature=True)),
       
(T('Tables'),
         
False,
         
None,
         
[
           
(db.opt_cat._plural, False, URL('opt_cat', 'index', vars={'sid': request.vars.sid}, user_signature=True)),
...

Didn't test the sorting or search then, but I tested now and they also don't work, returning the same Not Authorized message.

The problem is that you need the grid to generate signed URLs, which it does, but it does so without including the query string variables in constructing the signature. However, your menu URL does include the query string when creating the signature, and your @auth.requires_signature decorator also expects the signature to be based on the query string. You can try excluding the query string as follows:

@auth.requires_signature(hash_vars=False)

and to create the menu link:

URL('opt_cat', 'index', vars={'sid': request.vars.sid}, user_signature=True, hash_vars=False)

Note, that is a little less secure, as the signature will work with any URL with a full matching path, even if the query string is different.

Anyway, why do you need signed URLs in this case? Why is @auth.requires_login not sufficient?

Anthony

João Matos

unread,
Apr 23, 2019, 3:49:37 AM4/23/19
to web...@googlegroups.com
Thanks for the workaround Anthony.

I believe it should be the other way around. Grid should have the option hash_vars=True. I added the feature request in GH.

I'm using HTTPS and these security measures (only one of them per action, depending on the need):
 - @auth.requires_login()
 - @auth.requires(ADMINISTRATOR_ROLE_ID in auth.user_groups)
 - @auth.requires(request.env.http_referer and ('/single_equip_opt' in request.env.http_referer or '/single_equip_opt/get_approval' in request.env.http_referer))

Do you have any recommendations?

My though in replacing the @auth.requires_login() with @auth.requires_signature() (I believe @auth.requires_signature() also requires login, correct?) was adding another security layer, but with the limitation you explained, I think I will not do it.
What is your opinion on this?

With this severe limitation to @auth.requires_signature(), in what situation do you recommend using it?

Thanks,

JM

Massimo Di Pierro

unread,
Apr 23, 2019, 10:52:20 AM4/23/19
to web2py-users
No. requires_signature does not require login and serves a very different purpose. It is designed to delegate authentication. If a user has permission to access page A and the user is redirected to page B, A can sign B to tell B the user can be trusted.

Anthony

unread,
Apr 24, 2019, 10:52:25 AM4/24/19
to web2py-users
My though in replacing the @auth.requires_login() with @auth.requires_signature() (I believe @auth.requires_signature() also requires login, correct?) was adding another security layer, but with the limitation you explained, I think I will not do it.
What is your opinion on this?

With this severe limitation to @auth.requires_signature(), in what situation do you recommend using it?

It's not so much a limitation of @auth.requires_signature as it is with using it with the grid if there is something in the query string that must be protected from tampering.

In any case, it's not clear @auth.requires_signature adds any value over @auth.requires_login in this case.

Anthony
Reply all
Reply to author
Forward
0 new messages