What I’m trying to do:
I want to figure out if it is safe to call a server function that accepts a user object, because I want to call a server function from another server function and avoid duplicate calls to get_user().
I’m concerned about whether its possible to impersonate a user just by knowing their email and last logged in date, especially if they’re signed up with Google and don’t have a hashed password stored in the Users table.
What I’ve tried and what’s not working:
Currently, I check which user is logged in within each server function. However, sometimes I want to call one server function from another and just pass the user object rather than running get_user again, like this:
Code Sample:
@anvil.server.callable
def function_1(input, user=None):
if not user:
user = anvil.users.get_user(allow_remembered=True)
# do something
@anvil.server.callable
def function_2(input):
user = anvil.users.get_user(allow_remembered=True)
# do something
function_1(input, user)
This opens up the possibility of being able to impersonate a user from the client side, assuming you can recreate a user object. I never thought this was an issue because you’d have to know the user’s hashed password, but in the case of Google logins, that info is not in the User’s table. Is there another safety barrier?
If this is not a good idea, is there another way to accomplish what I’m after?
The call to .get_user() is for the most part, just another row object in the users table, if you are passing it “back” to the server it barely makes a difference, what gets serialized is most likely just the row_id.
You are not really saving anything by doing that*, so you might as well not, and just use the users service from within the server module if you want to be secure.
just use:
@anvil.server.callable(require_user=True)
As the decorator instead and then call anvil.users.get_user() from within the function.
*by not really saving anything, my meaning is you will still be going to the data table to retrieve information from the row, just inside the server, which is what accessing any data from .get_user() does anyway.
If you want even more user security, you can pass any arbitrary self made entire security function (not a call, the whole function) into the decorator, and it will pass the user as its first argument to that function, so you can grant permission or refuse to continue past the callable decorator, by raising a permission error.
As @ianb says, a user object is a database table row, in table Users. Like all table rows, it’s passed back to Server code by reference, i.e., by row id.
So, the only way that Client-side code can “fake” a user is by creating an actual row in the Users table. By default, Users is not accessible to Client-side code, so (if you left that setting at the default) it can’t do that.
This comes up with background tasks as well - sometimes I want to call a “secured” server function from a background task and thus need to pass the user object as the background task is launching a separate process where no one is logged in.
I think I can be confident now that passing a user object to a server function is safe, and it helps to know that those calls to get_user aren’t causing a performance issue.