Update auth.user_groups after add_membership() or del_membership() operation

149 views
Skip to first unread message

Richard

unread,
Jun 20, 2017, 1:52:18 PM6/20/17
to web2py-developers
Hello,

I would like to make use of this snippet (Thanks to Simone) :

def has_membership(id_role):
    return True if (auth.user_id and id_role in auth.user_groups.values()) or \
                   (auth.user_id and id_role in auth.user_groups) else False

To by pass auth.has_membership() and speed up my app... The problem is I don't want to ask my user to logoff and login when I modify their membership...


Now I would like to know if I can use cache.ram() in tools.py to maintain a list of user(s) that membership has been updated so auth.user_groups should be updated on next request...

Do you think it would work? If not why? And what would be a better design to make this possible??

Thanks

Richard

Richard Vézina

unread,
Jun 21, 2017, 10:21:22 AM6/21/17
to web2py-d...@googlegroups.com
I can't figure out how I can make it work without create a global variable for handling user_id that need user_groups to be forced to update on next request...

Any idea?? 

Richard

--
-- mail from:GoogleGroups "web2py-developers" mailing list
make speech: web2py-developers@googlegroups.com
unsubscribe: web2py-developers+unsubscribe@googlegroups.com
details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-developers+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Richard Vézina

unread,
Jun 21, 2017, 10:25:44 AM6/21/17
to web2py-d...@googlegroups.com
Anthony idea : 

"Another option would be to keep a record of active session identifiers for each user, and whenever a role change occurs, update all of that user's active sessions (note, this will not work with cookie-based sessions -- only sessions stored on the server)."

I like the idea, but where does we keep the information? If it not working for cookie-based session, which is a major drawback though... I mean it would be preferable that the feature works in any configuration...

Richard



Massimo DiPierro

unread,
Jun 21, 2017, 11:31:02 AM6/21/17
to web2py-d...@googlegroups.com
perhaps the list could be kept by the used code and passed as a parameter. I am reluctant to add features that nobody uses.


details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-develop...@googlegroups.com.

Richard Vézina

unread,
Jun 21, 2017, 12:21:27 PM6/21/17
to web2py-d...@googlegroups.com
Ok, so something like

auth.default_settings.user_id_need_user_groups_update = cache.ram('user_id_need_user_groups_update', lambda: []) in models.. That would make more sens then create form authapi.py as I couldn't reinit without losing the list at each request from my understanding...

It would requires either way that AuthAPI to be alter or subclassed from the user app to modify add/del_membership()

And I still have to figure out how to make the check each time to force the reinitialization of auth.user_groups, I am not sure if there is anything that is trigger at each, is authapi init perform at each request?? I had put this in the begining of __init__ (in blue) :

    def __init__(self, db=None, hmac_key=None, signature=True):
        self.db = db
        session = current.session
        auth = session.auth
        if user_id_need_user_groups_update:
            if auth.user_id in user_id_need_user_groups_update:
                self.user_groups = auth.update_groups()
        else:
            self.user_groups = auth and auth.user_groups or {}
        now = current.request.now        


And modify add/del_membership() in blue :

    def add_membership(self, group_id=None, user_id=None, role=None):
        """
        Gives user_id membership of group_id or role
        if user is None than user_id is that of current logged in user
        """

        group_id = group_id or self.id_group(role)
        try:
            group_id = int(group_id)
        except:
            group_id = self.id_group(group_id)  # interpret group_id as a role
        if not user_id and self.user:
            user_id = self.user.id
        if not group_id:
            raise ValueError('group_id not provided or invalid')
        if not user_id:
            raise ValueError('user_id not provided or invalid')
        membership = self.table_membership()
        db = membership._db
        record = db((membership.user_id == user_id) &
                    (membership.group_id == group_id),
                    ignore_common_filters=True).select().first()
        if record:
            if hasattr(record, 'is_active') and not record.is_active:
                record.update_record(is_active=True)
            return record.id
        else:
            id = membership.insert(group_id=group_id, user_id=user_id)
        if role and user_id == self.user_id:
            self.user_groups[group_id] = role
        else:
            self.update_groups()
            self.default_settings.user_id_need_user_groups_update.append(user_id)
        self.log_event(self.messages['add_membership_log'],
                       dict(user_id=user_id, group_id=group_id))
        return id

    def del_membership(self, group_id=None, user_id=None, role=None):
        """
        Revokes membership from group_id to user_id
        if user_id is None than user_id is that of current logged in user
        """

        group_id = group_id or self.id_group(role)
        try:
            group_id = int(group_id)
        except:
            group_id = self.id_group(group_id)  # interpret group_id as a role
        if not user_id and self.user:
            user_id = self.user.id
        membership = self.table_membership()
        self.log_event(self.messages['del_membership_log'],
                       dict(user_id=user_id, group_id=group_id))
        ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete()
        if group_id in self.user_groups:
            del self.user_groups[group_id]
        else:
            self.default_settings.user_id_need_user_groups_update.append(user_id)
        return ret

I have not test anything yet...

I understand your concern, but it improve app speed a lot avoiding to hit the db with auth.has_membership(), we could even and replace the actual has_membership by the one that don't hit database once the user as set user_id_need_user_groups_update, I guess it would allow user traction to the feature and we could add this proposal in the book as a mean (a step) to speed up app???

Richard


Richard Vézina

unread,
Jun 21, 2017, 12:26:20 PM6/21/17
to web2py-d...@googlegroups.com
Something like that :

    def has_membership(self, group_id=None, user_id=None, role=None):
        """
        Checks if user is member of group_id or role
        """
        if user_id_need_user_groups_update:
            return True if (auth.user_id and id_role in auth.user_groups.values()) or \
                   (auth.user_id and id_role in auth.user_groups) else False
        else:
            group_id = group_id or self.id_group(role)
            try:
                group_id = int(group_id)
            except:
                group_id = self.id_group(group_id)  # interpret group_id as a role
            if not user_id and self.user:
                user_id = self.user.id
            membership = self.table_membership()
            if group_id and user_id and self.db((membership.user_id == user_id) &
                                                (membership.group_id == group_id)).select():
                r = True
            else:
                r = False
            self.log_event(self.messages['has_membership_log'],
                           dict(user_id=user_id, group_id=group_id, check=r))
            return r

Anthony

unread,
Jun 21, 2017, 3:54:23 PM6/21/17
to web2py-developers
cache.ram is not a good option, as it is unique to a single process on a single machine. You need a list that is accessible from multiple processes, potentially across multiple machines (though that could slow things down, which is not great, as you would potentially need a check on every request).

Anthony

Niphlod

unread,
Jun 22, 2017, 4:07:14 AM6/22/17
to web2py-developers
the general problem popping here and there is that the design of the sessions in web2py doesn't "link" sessions to users when you try to read a session.

details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-develop...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
-- mail from:GoogleGroups "web2py-developers" mailing list

details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-develop...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
-- mail from:GoogleGroups "web2py-developers" mailing list

details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-develop...@googlegroups.com.

Richard Vézina

unread,
Jun 22, 2017, 9:28:22 AM6/22/17
to web2py-d...@googlegroups.com
:(

So, it would requires Redis as a dependency which it not acceptable as it use case wouldn't be enough broad to make it useful or a large audience...

Richard


details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-developers+unsubscribe@googlegroups.com.

Richard Vézina

unread,
Jun 22, 2017, 2:46:40 PM6/22/17
to web2py-d...@googlegroups.com
@Niphold, I am not sure I follow... The issue I see here is that we can't (or more don't whant) put the list of user_id that need to update user_groups var in session because session is to store user centric information and not information about other users... I think it would have been the best place, but while I am writting this I have a doubt about it as I think that the only session that would contain the uptodate list would still be the actual user session, so if I am write, it still not working that way...

But I mean, if session is not link to user, I am wrong above, so I am confuse, if you can clarify thing for me and details you statement...

As far as I understand, web2py doesn't have a memory of the state he was in the previous request (or at the end of the request execution), if session is unique to all user, that not true and session represent the actual web2py memory of it previous state... If this is the case, what you tell is that we should somehow split informations present in the session in 2 containers, user_session and web2py_memory_session, obviously better name would be welcome like web2py_state or something...

I guess it involve a major refactoring which to me would worth to be done if it brings more flexibility and speed...

We could also, if I am not to off the track with my whole understanding, just start having a web2py_state kind of session, where we can put the actual persistence needed information that we need to bring this feature on, and migrate progressively all the other information that are web2py internal related and that shouldn't go into session...

Thanks

Richard


On Thu, Jun 22, 2017 at 4:07 AM, Niphlod <nip...@gmail.com> wrote:

details : http://groups.google.com/group/web2py-developers
the project: http://code.google.com/p/web2py/
official : http://www.web2py.com/
---
You received this message because you are subscribed to the Google Groups "web2py-developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py-developers+unsubscribe@googlegroups.com.

Anthony

unread,
Jun 22, 2017, 11:16:02 PM6/22/17
to web2py-developers
On Thursday, June 22, 2017 at 2:46:40 PM UTC-4, Richard wrote:
@Niphold, I am not sure I follow... The issue I see here is that we can't (or more don't whant) put the list of user_id that need to update user_groups var in session because session is to store user centric information and not information about other users... I think it would have been the best place, but while I am writting this I have a doubt about it as I think that the only session that would contain the uptodate list would still be the actual user session, so if I am write, it still not working that way...

No, it would make no sense to put such a list in a session, as any given session is unique to a given user, and the list you envision must by its nature be globally available to all users.
 

But I mean, if session is not link to user, I am wrong above, so I am confuse, if you can clarify thing for me and details you statement...

What niphlod means is that although we can make a link from a given session to a logged-in user (because the user record is stored in the session when the user is logged in), we cannot go in the reverse direction and make a link from a user to the user's current sessions (without simply iterating through all the sessions and inspecting the contents to find a given user). So, when we change the Auth roles of a given user, it is not a simple operation to find the user's active sessions and alter them (with file-based sessions, we would have to read every session file and check for the user's ID). This is because Session knows nothing about Auth.

Anthony

Richard Vézina

unread,
Jul 7, 2017, 3:34:42 PM7/7/17
to web2py-d...@googlegroups.com
Seems I can just empty auth.user_groups = {} to force web2py to regenerate user_groups var... That way I can make sure that user don't have to logoff while changing user group...

Do you see any drawback??

Richard

--

Richard Vézina

unread,
Jul 7, 2017, 3:56:06 PM7/7/17
to web2py-d...@googlegroups.com
I still can't only "reset" auth.user_groups of a user that change his own role, I can't manage someone else role without ask him to logout... But it solve my biggest issue... 

When I manage membership, I would need to for someone to logout, but I think it comes back to the previous issues that prevent it...

Richard
Reply all
Reply to author
Forward
0 new messages