Server code error in Backendless SDK when saving new entity

Dear Backendless,
I am trying to implement a Post Create trigger on one of my tables to create a new record in another table from the result of the write.
I get this error in CodeRunner:

[ERROR] In Backendless SDK occurred error with message: "Backendless API return error: User has no permission to create entity. Error code:1011"

I checked the ACLs and they allow any authenticated user to write to the target table but prevent unauthenticated users from creating new records.
The PHP code is as follows:

  public function afterCreate( $runner_context, $post, $execution_result ) {
    $latest_post = new Latest_post();
    $latest_post->setPost($post);
    $latest_post->setOwner($post->getOwner());
    $latest_post->setOwnerId($post->getOwnerId());
    $latest_post->save();
  }

Shouldn’t the server code assume the identity of the current user?

Hi Emmanuel,

The server code does not assume any identity. An identity which was used on the client side is provided to you via the RunnerContext argument. If your business logic requires a specific identity (the original calling user or any other one), you would need to establish it by using the login API call.

Hope this helps.

Regards,
Mark

Hi Mark,

I’m not sure what you suggest I do with the user I get from the login API. Let’s say I’m running a trigger that needs special permissions. Are you suggesting I login using code such as:

$superuser = Backendless::$UserService->login('supername', 'superpass');

If yes, what do I do with that user now?
In any case, may I suggest that the server code should either assume the current user’s roles or get a superuser role automatically, because the way it is now is very limiting…
Thanks for your help.

Once you login, the SDK will know that an identity is established. Any subsequent calls after that one will use the identity of the logged in user and as a result, the ACL for the objects you “CRUD” will be in action.

I like the idea of assigning a special “superuser” identity to the business logic. We will look into it.

Hi Mark,

Thanks for the answer, it looks that I didn’t explain myself properly. Let me try again:

    I am writing a simple blogging iOS app using the Backendless SDK My users can log with Facebook (exclusively, no name/password login or registration) Once logged in, a user can then write a new post on the device. By touching a button, the app saves the new post to the native DB using the client SDK methods At that point I use a Post Create trigger server side to run some adhoc business logic as described earlier.
As you can see, the server code is invoked indirectly by the user from the client. The context is also aware of the current user as can be seen in the dump of the context object in the handler:
public function afterCreate( $runner_context, $post, $execution_result ) {
    print_r($runner_context);
}

In the console:

backendless\core\servercode\RunnerContext Object
(
    [appId:backendless\core\servercode\RunnerContext:private] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    [userToken:backendless\core\servercode\RunnerContext:private] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    [deviceType:backendless\core\servercode\RunnerContext:private] => IOS
    [userId:backendless\core\servercode\RunnerContext:private] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    [missingProperties:backendless\core\servercode\RunnerContext:private] => Array
        (
        ...
        )


    [prematureResult:backendless\core\servercode\RunnerContext:private] => 
    [userRole:backendless\core\servercode\RunnerContext:private] => Array
        (
            [0] => AuthenticatedUser
            [1] => SocialUser
            [2] => FacebookUser
        )
)

But the server code fails to write to the table, even though I gave Authenticated users permission to do so (but not NonAuthenticated users).
In other words, the server code behaves as an unauthenticated user. Ideally, it should either receive the permissions of the current user or some superuser permissions as discussed earlier.

I hope that clarifies my problem.

Thanks

Yes, I understand what’s going and the behavior you are observing is consistent with how the system is designed. By default the server-side code does not assume any identity. In order for an identity to take place, your server-side trigger must perform the login operation. It can be done either using the REST API:

https://backendless.com/documentation/users/rest/users_login.htm

or using the Java SDK:
https://backendless.com/documentation/users/android/users_login.htm

Once you login a user, the server-side code process will have access to user-token. The user-token can be provided for all subsequent API calls your server-side code will make. The SDK for Java handles it automatically, as long as you make the login call. With REST, the user-token header must be added manually to the subsequent requests (just like everything else with REST).

So in the step 5 you listed above, before you make use of any of the Backendless APIs, simply do the login call for the superuser in your app. That should remove the error you are seeing.

Hope this helps.

Regards,
Mark

Aaaah! I think I get it now. Ok, I’ll try that, thanks Mark!

Hi Mark,

I’ve implemented your solution in Java and it’s working. Should I be worried about the performance impact of potentially having to do that for most if not all by business logic code? What is the impact on performance to have to login every time the code runs? How about all these opened sessions, won’t that be a problem?
I still think that at this point it would be worth considering this superuser thing we talked about earlier, don’t you think? Also, it would save from having to put the superuser’s ID and password in the code…
Thanks

Hi Emmanuel,

You could optimize it somewhat for now by storing the user-token in Backendless.Cache.

The algorithm is rather simple:

    Check if user token is present in cache. If not, proceed to step 5; Get user token from cache Set it in the system with the following call: UserTokenStorageFactory.instance().getStorage().set( userToken ); Go to 7; Perform login Obtain user token with the following code: String userToken = UserTokenStorageFactory.instance().getStorage().get(); Store it in cache. End of algorithm;
We will look into the superuser approach, however, it would still require us to perform the login call. The containers where custom business logic runs are not the same boxes/processes where the core backendless logic is, hence a session must be established.

Mark

Great, I’ll try that right now because I got the dreaded login limit message:

Exception Unable to login. Multiple login limit for the same user account has been reached code: 3044 message: Unable to login. Multiple login limit for the same user account has been reached

Thanks for the help, great tip. Do I need to worry about token expiry?

Yes, tokens do expire (you can configure the timeout in Users > Login > Enable Session Timeout section of Backendless Console).

You can validate your token with an API call documented here (see the “Validating User Login” section):

https://backendless.com/documentation/users/rest/users_login.htm

Here is my code, in case anyone else needs that functionality:

public class SuperuserSessionManager {
  public static boolean assumeSuperuserIdentity() {
    if (Backendless.Cache.contains("superuserToken")) {
      String token = Backendless.Cache.get("superuserToken", String.class);
      UserTokenStorageFactory.instance().getStorage().set(token);
      if (Backendless.UserService.isValidLogin()) {
        // Backendless.Cache.expireIn("superuserToken", 7200000);
        return true;
      }
    }
    try {
      Backendless.UserService.login("superuserid", "superuserpass"); 
    } catch (Exception e) {
      // TODO: handle exception
      return false;
    }
    String token = UserTokenStorageFactory.instance().getStorage().get();
    Backendless.Cache.put("superuserToken", token);
    return true;
  }
}

By the way the line Backendless.Cache.expireIn(“superuserToken”, 7200000); always fails in error with the message:

SEVERE: Cannot extend object's life in cache - object does not exist

Any idea why that would be?

We will investigate why it fails and report back

Hi Emmanuel,

Are you sure that you’re getting this cache exception in the pointed line? Because if you ran this exact line, you would get a BackendlessException with message “Expiration time should be from 1 to 7200 integer value”, since the time should be in seconds, not milliseconds.

Also I tried the following code and it worked fine:

Backendless.Cache.put( "superuserToken", "abcd" );


if( Backendless.Cache.contains( "superuserToken" ) )
  Backendless.Cache.expireIn( "superuserToken", 7200 );

Could you please provide some minimal example, on which you receive this error? It would help us to reproduce and investigate the problem.

Hi Mark,

I see that I didn’t read the doc properly, I mixed the “timestamp” parameter from the ExpireAt method and the “second” parameter from the function ExpireIn.
I will change my code and check again.
Also, I’m sure that the error I reported is what I got back then.

Thanks!

Hi Mark,

I can see that the provided RunnerContext has a userId and a userId and a userToken, but not the userPassword.

How can I perform the Java SDK login call with that information??

I’d need the objects to be saved with server code, to be saved as the user.

Thanks.

jdev, please submit a separate topic. Handling multiple questions within one topic is very hard to manage.

Mark