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.
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
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:
Clone this app. It will poll the client, anvil server or uplink server for the environment name. Anvil | Login
Create 3 public environments, e.g. Environment A, B and C.
Enable Uplink for one of the public environments, and check ‘Send server calls from other environments to this Uplink’
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.
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.
Very quickly you will in each tab have received all 3 environment names, where you should have received only 1.
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.
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.