Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting error while trying to refresh boxSession (once the access token has expired) #344

Open
dshbq opened this issue Feb 8, 2018 · 14 comments
Labels
documentation improvement or addition needed to documentation enhancement

Comments

@dshbq
Copy link

dshbq commented Feb 8, 2018

Hi,

In my app, when I'm trying to refresh the boxEnterprise access token once the box token has expired, i get below error and once the token is refreshed (it takes sometime to refresh the token), everything works fine.

Below is the error logs:

Unable to repair user java.util.concurrent.ExecutionException: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference at java.util.concurrent.FutureTask.report(FutureTask.java:94) at java.util.concurrent.FutureTask.get(FutureTask.java:164) at com.box.androidsdk.content.models.BoxSession$BoxSessionRefreshRequest.onSend(BoxSession.java:708) at com.box.androidsdk.content.models.BoxSession$BoxSessionRefreshRequest.onSend(BoxSession.java:691) at com.box.androidsdk.content.requests.BoxRequest.send(BoxRequest.java:190) at com.box.androidsdk.content.BoxFutureTask$1.call(BoxFutureTask.java:38) at com.box.androidsdk.content.BoxFutureTask$1.call(BoxFutureTask.java:31) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at com.box.androidsdk.content.models.BoxSession$2.run(BoxSession.java:496) Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference at com.box.androidsdk.content.auth.BoxAuthentication$4.call(BoxAuthentication.java:504) at com.box.androidsdk.content.auth.BoxAuthentication$4.call(BoxAuthentication.java:446) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761) 02-08 16:28:58.239 27721-28460/com.allstate.view E/BoxSession: Unable to repair user com.box.androidsdk.content.BoxException: An error occurred while sending the request at com.box.androidsdk.content.requests.BoxRequest.onSend(BoxRequest.java:243) at com.box.androidsdk.content.requests.BoxRequest.send(BoxRequest.java:190) at com.box.androidsdk.content.models.BoxSession$BoxSessionAuthCreationRequest.onSend(BoxSession.java:764) at com.box.androidsdk.content.models.BoxSession$BoxSessionAuthCreationRequest.onSend(BoxSession.java:744) at com.box.androidsdk.content.requests.BoxRequest.send(BoxRequest.java:190) at com.box.androidsdk.content.BoxFutureTask$1.call(BoxFutureTask.java:38) at com.box.androidsdk.content.BoxFutureTask$1.call(BoxFutureTask.java:31) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761)

Could you please let me know if I'm doing something wrong.

Here is the code i tried for refresh the token:
if (boxSession == null) {
 boxSession = getBoxSession(context);
 return boxSession;
 } else if (currentTime > expiryTime) {
 boxSession.refresh();
 return boxSession; } else { return boxSession; }

I also tried to wrap the "boxSession.refresh()" call inside a thread and use it but still getting the same error.

Also, i have a custom refresh provider, which is being triggered automatically when i call "boxSession.refresh()", inside the refresh provider, i have logic to hit API to get my latest token from the API and override local one and also, update expiry time inside that.

Thanks!!

@dshbq dshbq changed the title Getting error when trying to refresh boxSession access token Getting error when trying to refresh boxSession (once the access token has expired) Feb 8, 2018
@dshbq dshbq changed the title Getting error when trying to refresh boxSession (once the access token has expired) Getting error while trying to refresh boxSession (once the access token has expired) Feb 8, 2018
@dshbq
Copy link
Author

dshbq commented Feb 9, 2018

@doncung could you please check and let me know.

@doncung
Copy link
Contributor

doncung commented Feb 9, 2018

What version of the SDK are you using. Also just to check are you using any proguarding right now?

@dshbq
Copy link
Author

dshbq commented Feb 9, 2018

I am using the latest box sdk version (4.0.11). Regarding proguarding , I am using the debug verison of the app , it does not have proguarding

@doncung
Copy link
Contributor

doncung commented Feb 10, 2018

How are you passing the custom refresh provider to the sessions, or are you using the global version inside of BoxAuthentication? Can you add a break point to BoxSessionRefreshRequest inside of BoxSession? The line numbers from the stack trace don't seem to match up to anything with a String for me.

Also it could be helpful if you could post what you are doing in your AuthenticationRefreshProvider implementation.

@dshbq
Copy link
Author

dshbq commented Feb 12, 2018

I have custom class for handling box Session, a singleton class. Inside the singleton class, this is what I m doing for initialization of boxSession:

private BoxSession getBoxSession(Context context) { info = new BoxAuthentication.BoxAuthenticationInfo(); info.setAccessToken(UserPreferenceManager.getInstance().getAppUserToken(context)); **MyAuthenticationRefreshProvider refreshProvider = new MyAuthenticationRefreshProvider(); BoxAuthentication.getInstance().setRefreshProvider(refreshProvider);** boxSession = new BoxSession(context, info, null); boxSession.authenticate(); boxSession.setSessionAuthListener(this); enableBoxLogs(); return boxSession; }
and below is my custom refresh provider where I am run a background thread, fetching new token from enterprise APIs and update the access token:

public class MyAuthenticationRefreshProvider implements Serializable, BoxAuthentication.AuthenticationRefreshProvider { @Override public BoxAuthentication.BoxAuthenticationInfo refreshAuthenticationInfo(final BoxAuthentication.BoxAuthenticationInfo info) throws BoxException { BoxAuthentication.BoxAuthenticationInfo newBoxInfo = new BoxAuthentication.BoxAuthenticationInfo(); EnterpriseGetTokenResponse enterpriseGetTokenResponse = getRefreshToken(); if (enterpriseGetTokenResponse != null) { newBoxInfo.setAccessToken(enterpriseGetTokenResponse.getAccessToken()); Logging.log("v", Constant.DIGITAL_LOCKER_TAG, "New app token : " + enterpriseGetTokenResponse.getAccessToken() + "; refresh: "); UserPreferenceManager.getInstance().setTokenExpiryTime(context, (Long.parseLong(enterpriseGetTokenResponse.getAcquiredAtMS()) + Long.parseLong(enterpriseGetTokenResponse.getAccessTokenTTLMS()))); UserPreferenceManager.getInstance().setAppUserToken(context, enterpriseGetTokenResponse.getAccessToken()); } return newBoxInfo; } @Override public boolean launchAuthUi(String userId, BoxSession session) { return true; } }

And, finally in my singleton class i check this condition to get the token (this is the trigger point for other classes):

public BoxSession initBoxSession(Context context) { this.context = context; long expiryTime = UserPreferenceManager.getInstance().getTokenExpiryTime(context); long currentTime = new Date().getTime(); if (boxSession == null) { boxSession = getBoxSession(context); return boxSession; } else if (currentTime > expiryTime) { boxSession.refresh(); return boxSession; } else { return boxSession; } }
Everything works fine usually but once the token expires (when currentTime > expiryTime inside initBoxSession method) i get the above mentioned error and i can't pull the box folders and files which i am trying to pull and but as soon as i refresh the screen, since the box token has refreshed now, everything works smoothly.

My assumption was even though token refresh, it takes sometime and during that time may be im trying to pull box files/folders and which is causing this. (some concurrent exception probably).

Hope it helps u to understand the scenario.

@doncung
Copy link
Contributor

doncung commented Feb 13, 2018

For the refresh provider I'd recommend copying the user information from the previous authInfo. The authInfo is passed in to the method call you are getting so that you know which user needs refreshing (in case your application has multiple users performing operations), but you will also want to keep all that info in the authInfo object otherwise the SDK may make a user call.

As for the expiration logic, refresh is done asynchronously so your theory is likely correct. In other words the session you are using from init is still in the process of refreshing, and any api calls used to make it will fail. Generally the SDK is designed so that you don't have to do that logic on your own as your refresh provider logic should be triggered whenever an api call you make fails due to an expired access token and then the api call is automatically retried.

@dshbq
Copy link
Author

dshbq commented Feb 13, 2018

Thanks For Response. so, as u said he session you are using from init is still in the process of refreshing, and any api calls used to make it will fail, i also suspect on this but how to prevent this ?
I mean if i am trying to refresh the access, how i should wait for it before triggering any other BoxAPI call. Could you please explain this part.

Also, regarding copying the user information from the previous authInfo, it has to be done inside the refresh provider, if i understand it correct, right?

Thanks!!

@dshbq
Copy link
Author

dshbq commented Feb 15, 2018

Hi @doncung ,
I tried copying user information from the previous authInfo inside refresh provider but now i started getting that error everytime. Could you please give a simple example of how to refresh the access token on the flow (when the application is running & at any point i have to call box api, so i should wait for the refresh to happen and then call the box api).
Hope u got the case scenario.

I have custom provider in which i get the new token from enterprise API and update my shared preferences. Also, to check if i need to refresh or not, i compare the expiry time with current time as i have mentioned in the above comments.

Thanks

@doncung
Copy link
Contributor

doncung commented Feb 16, 2018

Sure. I'll look into updating our documentation on this. In the mean time if you want to block for the initial session (assuming you are not on the UI thread) you can simply call refresh().get() (It returns a future task).
Alternatively you can use listeners to wait for the session to be authenticated before starting your logic (The listener can be passed in with the authenticate method. It will be triggered after the session is authenticated either successfully or has failed).

@dshbq
Copy link
Author

dshbq commented Feb 16, 2018

@doncung As per your suggestion, i tried putting
boxSession.refresh().get(); instead of simply calling boxSession.refresh() while refreshing the token but i am still getting the same error as mentioned first time here.
Am i doing anything wrong here?

Also, i want my main thread to wait till session refresh, by calling boxSession.refresh().get();, So that, in any class at any point of time, token expires, its will refresh first and then other box call will happen.

@dshbq
Copy link
Author

dshbq commented Feb 20, 2018

Hi @doncung ,
could you please check my above comment.
Thanks!!

@doncung
Copy link
Contributor

doncung commented Feb 21, 2018

By main thread you are referring to UI thread? If so that is not recommended since the refresh is making an api call and can potentially take a while causing an ANR.
Here is an example of the logic I'm testing with that seems to work:

   private static final BoxAuthentication.AuthenticationRefreshProvider refreshProvider = new BoxAuthentication.AuthenticationRefreshProvider() {
        @Override
        public BoxAuthentication.BoxAuthenticationInfo refreshAuthenticationInfo(BoxAuthentication.BoxAuthenticationInfo info) throws BoxException {
            BoxAuthentication.BoxAuthenticationInfo authenticationInfo = new BoxAuthentication.BoxAuthenticationInfo(info.toJsonObject());
            authenticationInfo.setAccessToken("latest access token from server");
            return authenticationInfo;
        }

        @Override
        public boolean launchAuthUi(String userId, BoxSession session) {
            return false;
        }
    };

private void initSession(){

    private void initSession() {
        BoxAuthentication.getInstance().setRefreshProvider(refreshProvider);
        mSession = new BoxSession(this);
        mSession.setSessionAuthListener(this);
        mSession.authenticate(this);
        try {
            mSession.refresh().get();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(){
            @Override
            public void run() {
                initSession();
            }
        }.start();
    }


    @Override
    public void onAuthCreated(BoxAuthentication.BoxAuthenticationInfo info) {
        //Init file, and folder apis; and use them to fetch the root folder
        mFolderApi = new BoxApiFolder(mSession);
        mFileApi = new BoxApiFile(mSession);
        loadRootFolder();
    }

    @Override
    public void onAuthFailure(BoxAuthentication.BoxAuthenticationInfo info, Exception ex) {
        if (ex != null) {
            clearAdapter();
        } else if (info == null && mOldSession != null) {
            mSession = mOldSession;
            mSession.setSessionAuthListener(this);
            mOldSession = null;
            onAuthCreated(mSession.getAuthInfo());
        }
    }

    @Override
    public void onLoggedOut(BoxAuthentication.BoxAuthenticationInfo info, Exception ex) {
        clearAdapter();
        initSession();
    }


Basically I'm relying on the onAuthCreated logic to know when I have a valid info object which means I should have a good access token. At which point I should be able to make the call. The initSession logic is thrown inside a thread just to make sure it is not being run on the UI thread.

@dshbq
Copy link
Author

dshbq commented Feb 21, 2018

Thanks @doncung !! I will check this.

@technicallyerik
Copy link

I looked into this issue.

I validated that the crash happens in Box’s SDK here in “BoxAuthentication.java”, where session.getUserId() is null on the refresh of a token:

if (!session.getUserId().equals(info.getUser().getId())) {
    session.onAuthFailure(info, new BoxException("Session User Id has changed!"));
}

The problem was we were not setting the user in the initial session for the app user in the event we had to re-create a session:

private BoxSession getBoxSession(Context context) {
    info = new BoxAuthentication.BoxAuthenticationInfo();
    info.setAccessToken(UserPreferenceManager.getInstance().getAppUserToken(context));
    // *** This line was not there
    info.setUser(BoxUser.createFromId(UserPreferenceManager.getInstance().getUserId(context))); 
    // ***
    MyAuthenticationRefreshProvider refreshProvider = new MyAuthenticationRefreshProvider();
    BoxAuthentication.getInstance().setRefreshProvider(refreshProvider);
    boxSession = new BoxSession(context, info, null);
    boxSession.authenticate();
    boxSession.setSessionAuthListener(this);
    enableBoxLogs();
    return boxSession;
}

I'm not sure if we're misunderstanding something on our end, but I think https://github.com/box/box-android-sdk/blob/77dcea4ca0ea4871747ce2c0cbbcc8f1edc8d05f/doc/AppUsers.md could be clarified a bit, and I would argue the null pointer should be better handled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation improvement or addition needed to documentation enhancement
Projects
None yet
Development

No branches or pull requests

4 participants