okta / okta-oidc-android

OIDC SDK for Android
https://github.com/okta/okta-oidc-android
Other
60 stars 45 forks source link

Crash when app backgrounded during login/logout #315

Closed idomo1 closed 2 years ago

idomo1 commented 2 years ago

Describe the bug?

If the app is backgrounded between initiating WebAuthClient.signIn/signout and the web client opening, there is a crash.

Sign out

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1551)
        at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1591)
        at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:341)
        at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:306)
        at com.okta.oidc.OktaResultFragment.addLogoutFragment(OktaResultFragment.java:66)
        at com.okta.oidc.clients.web.SyncWebAuthClientImpl.lambda$startSignOut$4$com-okta-oidc-clients-web-SyncWebAuthClientImpl(SyncWebAuthClientImpl.java:321)
        at com.okta.oidc.clients.web.SyncWebAuthClientImpl$$ExternalSyntheticLambda5.run(D8$$SyntheticClass)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6682)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

Sign in

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1551)
        at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1591)
        at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:341)
        at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:306)
        at com.okta.oidc.OktaResultFragment.addLoginFragment(OktaResultFragment.java:52)
        at com.okta.oidc.clients.web.SyncWebAuthClientImpl.lambda$startSignIn$2$com-okta-oidc-clients-web-SyncWebAuthClientImpl(SyncWebAuthClientImpl.java:187)
        at com.okta.oidc.clients.web.SyncWebAuthClientImpl$$ExternalSyntheticLambda4.run(D8$$SyntheticClass)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6682)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

Note that calling WebAuthClient.cancel() in onStop isn't a solution, since onStop is called as well when the login/logout web client is launched.

What is expected to happen?

The SDK should catch the error and pass it to ResultCallback.onError

What is the actual behavior?

A fatal exception

Reproduction Steps?

Additional Information?

This is similar to this issue https://github.com/okta/okta-oidc-android/issues/263, but the fix applied makes no difference.

SDK Version

1.2.3

Build Information

No response

JayNewstrom commented 2 years ago

@idomo1 what Android version(s) are you seeing this on?

idomo1 commented 2 years ago

Reproduced it myself on an Android 7 real device and emulators for Android 7, 7.1, 8, 8.1, 9, 10, 11 and 12 on SDK v.1.2.3. For Android 10+ I could only seem to get it to crash on sign out for whatever reason. Crash logs from production show it happening on Android 10, 11 and 12, though that's with SDK v1.2.2.

JayNewstrom commented 2 years ago

Thanks for the report. I'm able to reproduce.

The SDK is not intended to be called when your application is in the background. Before calling signIn, ensure your activity is in the foreground by using something along the lines of lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED).

JayNewstrom commented 2 years ago

I've been working on a lot of fixes in this area for our new SDK. The new SDK will do the right thing for all of these cases to put less burden on you as an app developer.

idomo1 commented 2 years ago

@JayNewstrom I think you've misunderstood. The activity is started when the SDK is called, but because of the 2-3 second delay between calling WebAuthClient.signIn and the custom tab opening, there is time for the user to background the app and cause the crash. There isn't anyway that I can see of preventing this from the SDK user side.

JayNewstrom commented 2 years ago

I see. This is one of the scenarios I fixed in the new mobile SDK. I'll see if there's anything I can do here.

Thanks for the clarification.

JayNewstrom commented 2 years ago

It looks like the best we can do in this SDK (due to some internal limitations) is to return a cancelled result if the activity has been backgrounded/destroyed.

I'll see if I can make a PR for it.

idomo1 commented 2 years ago

That's a lot better than crashing, thanks!

JayNewstrom commented 2 years ago

@idomo1 opened the PR for the fix here: https://github.com/okta/okta-oidc-android/pull/316

Tyree-McGee commented 1 year ago

Hello, we are using version 1.3.2 and we are seeing the following crash. We noticed that your PR for the fix has been merged in version 1.2.5. Are any of you aware if this crash is still occurring?

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1610)
  at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1650)
  at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:341)
  at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:306)
  at com.okta.oidc.OktaResultFragment.addLogoutFragment(OktaResultFragment.java:65)
  at com.okta.oidc.clients.web.SyncWebAuthClientImpl.lambda$startSignOut$4(SyncWebAuthClientImpl.java:336)
  at com.okta.oidc.clients.web.SyncWebAuthClientImpl.$r8$lambda$9xGAYUir3zQW6WeNxzc89L77keM(SyncWebAuthClientImpl.java)
  at com.okta.oidc.clients.web.SyncWebAuthClientImpl$$InternalSyntheticLambda$1$4f21a8c7c9ad1cac09d3351f8ce878563a9e121d919d104677a8d21e37f4789b$0.run$bridge(SyncWebAuthClientImpl.java:30)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loopOnce(Looper.java:226)
  at android.os.Looper.loop(Looper.java:313)
  at android.app.ActivityThread.main(ActivityThread.java:8663)
  at java.lang.reflect.Method.invoke(Method.java:-2)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)

Thread 2:
0   dalvik.system.VMStack.getThreadStackTrace(VMStack.java:-2)
1   java.lang.Thread.getStackTrace(Thread.java:1841)
2   java.lang.Thread.getAllStackTraces(Thread.java:1909)
3   com.microsoft.appcenter.crashes.Crashes.y
4   t8.d.uncaughtException
5   java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1073)
6   java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)
7   java.lang.Thread.dispatchUncaughtException(Thread.java:2306)

Thread 31:
0   java.lang.Object.wait(Object.java:-2)
1   java.lang.Object.wait(Object.java:442)
2   java.lang.Object.wait(Object.java:568)
3   java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:232)
4   java.lang.Daemons$Daemon.run(Daemons.java:140)
5   java.lang.Thread.run(Thread.java:1012)

Thread 32:
0   java.lang.Object.wait(Object.java:-2)
1   java.lang.Object.wait(Object.java:442)
2   java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:203)
3   java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:224)
4   java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:300)
5   java.lang.Daemons$Daemon.run(Daemons.java:140)
6   java.lang.Thread.run(Thread.java:1012)

Thread 33:
0   java.lang.Object.wait(Object.java:-2)
1   java.lang.Object.wait(Object.java:442)
2   java.lang.Object.wait(Object.java:568)
3   java.lang.Daemons$FinalizerWatchdogDaemon.sleepUntilNeeded(Daemons.java:385)
4   java.lang.Daemons$FinalizerWatchdogDaemon.runInternal(Daemons.java:365)
5   java.lang.Daemons$Daemon.run(Daemons.java:140)
6   java.lang.Thread.run(Thread.java:1012)

Thread 39:
0   java.lang.Object.wait(Object.java:-2)
1   java.lang.Object.wait(Object.java:442)
2   java.lang.Object.wait(Object.java:568)
3   java.util.TimerThread.mainLoop(Timer.java:534)
4   java.util.TimerThread.run(Timer.java:513)

Thread 41:
0   jdk.internal.misc.Unsafe.park(Unsafe.java:-2)
1   java.util.concurrent.locks.LockSupport.park(LockSupport.java:194)
2   java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2081)
3   java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:433)
4   java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1063)
5   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1123)
6   java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
7   java.lang.Thread.run(Thread.java:1012)

Thread 42:
0   android.os.MessageQueue.nativePollOnce(MessageQueue.java:-2)
1   android.os.MessageQueue.next(MessageQueue.java:335)
2   android.os.Looper.loopOnce(Looper.java:186)
3   android.os.Looper.loop(Looper.java:313)
4   android.os.HandlerThread.run(HandlerThread.java:67)

Thread 43:
0   android.os.MessageQueue.nativePollOnce(MessageQueue.java:-2)
1   android.os.MessageQueue.next(MessageQueue.java:335)
2   android.os.Looper.loopOnce(Looper.java:186)
3   android.os.Looper.loop(Looper.java:313)
4   android.os.HandlerThread.run(HandlerThread.java:67)

Thread 44:
0   android.os.MessageQueue.nativePollOnce(MessageQueue.java:-2)
1   android.os.MessageQueue.next(MessageQueue.java:335)
2   android.os.Looper.loopOnce(Looper.java:186)
3   android.os.Looper.loop(Looper.java:313)
4   android.os.HandlerThread.run(HandlerThread.java:67)

Thread 46:
0   android.os.MessageQueue.nativePollOnce(MessageQueue.java:-2)
1   android.os.MessageQueue.next(MessageQueue.java:335)
2   android.os.Looper.loopOnce(Looper.java:186)
3   android.os.Looper.loop(Looper.java:313)
4   android.os.HandlerThread.run(HandlerThread.java:67)

Thread 48:
0   android.os.MessageQueue.nativePollOnce(MessageQueue.java:-2)
1   android.os.MessageQueue.next(MessageQueue.java:335)
2   android.os.Looper.loopOnce(Looper.java:186)
3   android.os.Looper.loop(Looper.java:313)
4   android.os.HandlerThread.run(HandlerThread.java:67)

Thread 49:
0   jdk.internal.misc.Unsafe.park(Unsafe.java:-2)
1   java.util.concurrent.locks.LockSupport.park(LockSupport.java:194)
2   java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2081)
3   java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:433)
4   java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1063)
5   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1123)
6   java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
7   java.lang.Thread.run(Thread.java:1012)

Thread 52:
0   java.net.SocketInputStream.socketRead0(SocketInputStream.java:-2)
1   java.net.SocketInputStream.socketRead(SocketInputStream.java:118)
2   java.net.SocketInputStream.read(SocketInputStream.java:173)
3   java.net.SocketInputStream.read(SocketInputStream.java:143)
4   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:945)
5   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:909)
6   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
7   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
8   com.android.okhttp.okio.Okio$2.read(Okio.java:138)
9   com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
10  com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
11  com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
12  com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
13  com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
14  com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
15  com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
16  com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
17  com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
18  com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
19  com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
20  com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
21  com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
22  y8.c.a
23  y8.c.doInBackground
24  android.os.AsyncTask$3.call(AsyncTask.java:394)
25  java.util.concurrent.FutureTask.run(FutureTask.java:264)
26  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
27  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
28  java.lang.Thread.run(Thread.java:1012)

Thread 53:
0   java.lang.Object.wait(Object.java:-2)
1   com.android.okhttp.ConnectionPool$1.run(ConnectionPool.java:106)
2   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
3   java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
4   java.lang.Thread.run(Thread.java:1012)

Thread 54:
0   java.net.SocketInputStream.socketRead0(SocketInputStream.java:-2)
1   java.net.SocketInputStream.socketRead(SocketInputStream.java:118)
2   java.net.SocketInputStream.read(SocketInputStream.java:173)
3   java.net.SocketInputStream.read(SocketInputStream.java:143)
4   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:945)
5   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:909)
6   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
7   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
8   com.android.okhttp.okio.Okio$2.read(Okio.java:138)
9   com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
10  com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
11  com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
12  com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
13  com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
14  com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
15  com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
16  com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
17  com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
18  com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
19  com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
20  com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
21  com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
22  y8.c.a
23  y8.c.doInBackground
24  android.os.AsyncTask$3.call(AsyncTask.java:394)
25  java.util.concurrent.FutureTask.run(FutureTask.java:264)
26  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
27  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
28  java.lang.Thread.run(Thread.java:1012)

Thread 55:
0   java.net.SocketInputStream.socketRead0(SocketInputStream.java:-2)
1   java.net.SocketInputStream.socketRead(SocketInputStream.java:118)
2   java.net.SocketInputStream.read(SocketInputStream.java:173)
3   java.net.SocketInputStream.read(SocketInputStream.java:143)
4   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:945)
5   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:909)
6   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:824)
7   com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:797)
8   com.android.okhttp.okio.Okio$2.read(Okio.java:138)
9   com.android.okhttp.okio.AsyncTimeout$2.read(AsyncTimeout.java:213)
10  com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:307)
11  com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:301)
12  com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:197)
13  com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
14  com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
15  com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
16  com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
17  com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
18  com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
19  com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:542)
20  com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
21  com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
22  y8.c.a
23  y8.c.doInBackground
24  android.os.AsyncTask$3.call(AsyncTask.java:394)
25  java.util.concurrent.FutureTask.run(FutureTask.java:264)
26  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
27  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
28  java.lang.Thread.run(Thread.java:1012)

Thread 56:
0   java.lang.Object.wait(Object.java:-2)
1   com.android.okhttp.okio.AsyncTimeout.awaitTimeout(AsyncTimeout.java:325)
2   com.android.okhttp.okio.AsyncTimeout.access$000(AsyncTimeout.java:42)
3   com.android.okhttp.okio.AsyncTimeout$Watchdog.run(AsyncTimeout.java:288)

Thread 57:
0   jdk.internal.misc.Unsafe.park(Unsafe.java:-2)
1   java.util.concurrent.locks.LockSupport.park(LockSupport.java:194)
2   java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:885)
3   java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1039)
4   java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1345)
5   java.util.concurrent.CountDownLatch.await(CountDownLatch.java:232)
6   com.okta.oidc.clients.web.SyncWebAuthClientImpl.startSignOut
7   com.okta.oidc.clients.web.SyncWebAuthClientImpl.signOutOfOkta
8   com.okta.oidc.clients.web.WebAuthClientImpl.lambda$signOutOfOkta$8
9   com.okta.oidc.clients.web.WebAuthClientImpl.i
10  com.okta.oidc.clients.web.g.run
11  java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
12  java.util.concurrent.FutureTask.run(FutureTask.java:264)
13  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
14  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
15  java.lang.Thread.run(Thread.java:1012)