[FIXED] Returned environment name is inconsistent

What I’m trying to do:

My app has one public environment for each client.

I want Client A to only be able to use the url attached to the Client A environment. So when Client A makes a server call (uplink), I perform this check:

if anvil.app.environment.name != 'Client A':
   raise PermissionError('You are not allowed access to this URL.')

The problem is that this PermissionError is often raised, even though the correct URL is being used.

On retry it usually succeeds, but it is not fun to display false PermissionErrors to the client.

This has been an issue since I implemented this check in mid december, and has happened for all environments and different users.

This seems like a bug, or could I be doing something wrong?

What I’ve tried and what’s not working:

When the logged in user and the anvil.app.environment.name do not match, I retry 3 times with a 1 second delay between each attempt. Still it occationally fails.

I didn’t believe, but I did a quick test and you are right: if I define 3 environments, each with its uplink key, I make sure I don’t check the “Send server calls from other environments to this uplink” on any of them, run 3 uplink scripts that define a function with the same name, and the result is that Anvil picks one of them randomly.

I think this is a bug. What is the point of defining 3 different uplink keys when they step on each other toes?

Luckily there is a workaround: you can add the function name to the decorator on the uplink script.

Something like this (assuming that each of your environments ends with a unique letter):

# on the uplink script
@anvil.server.callable('uplink_function_A')
def uplink_function():
    print('Uplink Client A')
    return 'Uplink Client A'

# on the app
server.call('uplink_function_' + app.environment.name[-1])

If you want to use the exact same source code for different uplink scripts running on different machines, you can get the suffix on the decorator from an environment variable. I append the machine name to the function names, so the server can decide which function on which machine to call.

The name of the debug environment may change, but you can figure out a way around that.

1 Like

The string value does not have to be a constant. It can be computed at load-time.

1 Like

For one app I used to generate a random function name for the decorator, then when the uplink script booted it would register that function in the data tables, along with its capabilities.

That way, I could keep my uplink scripts pretty much identical but have a way for the main server to interact with a specific external server (selected based on capability) without having to know anything about it in advance.

edit - just realised this has nothing to do with the original question, sorry for the noise :slight_smile:

2 Likes

On the contrary, it might be a very useful part of an interim workaround.

2 Likes

Like generating something such as a secret API key your end user could drop into a credentials file that was also stored in a column of your clients Users table.

When the uplink script would run it would use the decorator to register all of the function names appended with the correct user ‘key’, and when the client called a server function it could just get that ‘key’ from anvil.users.get_user() and append it to the string parameter as passed to anvil.server.call()

@stefano.menci : interesting that you found another issue when you have different uplink connections for each environment.

I use a single uplink connection for all environments, and I managed to reproduce my problem. Here is how:

  1. Clone this app. It will poll the client, anvil server or uplink server for the environment name.
    Anvil | Login

  2. Create 3 public environments, e.g. Environment A, B and C.

  3. Enable Uplink for one of the public environments, and check ‘Send server calls from other environments to this Uplink’

  4. Start an uplink server against your uplink key on your local machine. Find the uplink server code in the module ‘uplink_code’ in your app clone.

  5. Open 3 tabs in your web browser, each against the public url of each of your environments. Start polling against the uplink server (select in the drop down) for all 3 of them.

  6. Very quickly you will in each tab have received all 3 environment names, where you should have received only 1.
    image

Observations:

  • It is only for Uplink you get this behavior. Polling against the Anvil server works fine.
  • If you remove the time delay in the uplink function, you will not get this. So it seems like new server calls arriving during the wait is impacting the result.
  • If you only poll from one environment, you only get the correct environment, which again indicate that simultaneous calls from other environments confuse the result.

It seems to me there is a bug in anvil.app.environment.name when run on an uplink server.

Do others get the same result?

I get the same result. It looks like the environment name is not thread safe and when two concurrent calls happen, the environment name changes.

I modified the uplink script (not thread safe, but good enough for this quick test):

counter = 0

@anvil.server.callable
def uplink_get_env_name() -> str:
    global counter
    counter += 1
    my_counter = counter
    print(' '  * (my_counter-1) + str(my_counter), anvil.app.environment.name)
    time.sleep(2)
    print(' '  * (my_counter-1) + str(my_counter), anvil.app.environment.name)
    return anvil.app.environment.name

And I got this result:

Connected to "A" as SERVER
1 A
1 A
 2 A
  3 B
 2 B
   4 A
  3 A
    5 B
   4 B
     6 A
    5 A
      7 B
     6 B
      7 B

You can see that the environment name for session 2 changes from A to B after session 3 starts while the 2 is still running.

You can mitigate the problem by getting the environment name on the first line of the function, but I wouldn’t rely on it.

Or you could pass the environment name as an argument, but hackers could change that name on the client.


So it looks like we found two different errors:

  • The environment name is not thread-safe
  • One environment can call a function registered on the uplink scripts connected to another environment
1 Like

Great summary, @stefano.menci .

I hope anvil staff picks this up and move this topic to the bug report category.

Yep, this is definitely a bug! Moving to Bug Reports.

This is now fixed, in anvil-uplink version 0.3.42.

Thanks for the thorough report and repro cases!

3 Likes

Wow, that was fast!

I upgraded to the new anvil-uplink version and there is no threading issue.

Thanks for solving this so quickly.

2 Likes

I upgraded and tested the other bug, the one with each environment with its own uplink key, and it works now.

If an environment has an uplink key defined, it will only call functions from its own uplink.

If an environment has no uplink key defined, it will call functions from environments with uplink key with Send server calls from other environments to this Uplink enabled. If there are none it will fail, if there are more than one it will randomly pick one of them.

1 Like