-
Notifications
You must be signed in to change notification settings - Fork 141
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
Fix Web Authentication issues #169
Conversation
@@ -93,7 +93,7 @@ public void bindService() { | |||
Log.v(TAG, "Trying to bind the service"); | |||
Context context = this.context.get(); | |||
boolean success = false; | |||
if (context != null) { | |||
if (context != null && preferredPackage != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to bind if there's no available service
@@ -133,7 +133,7 @@ public void launchUri(@NonNull final Uri uri) { | |||
public void run() { | |||
boolean available = false; | |||
try { | |||
available = sessionLatch.await(MAX_WAIT_TIME_SECONDS, TimeUnit.SECONDS); | |||
available = sessionLatch.await(preferredPackage == null ? 0 : MAX_WAIT_TIME_SECONDS, TimeUnit.SECONDS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to wait for the service connection if not available
context.startActivity(fallbackIntent); | ||
} catch (ActivityNotFoundException ex) { | ||
Log.e(TAG, "No Browser available to open the URI"); | ||
throw new IllegalStateException("Could not find any Browser application installed in this device to handle the intent.", ex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While this would 💥, there's a check performed earlier in the code for this not to happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lbalmaceda I would prefer that you Log.e() this rather than throw the IllegalStateException. We would prefer that over any possibility of a crash. This method, launchUri(), is called from your AuthenticationActivity. As such, it's impossible to catch the exception (without using an UncaughtExceptionHandler).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@billgockeler The only public facing class is WebAuthProvider
; then OAuthManager
and CustomTabsController
are package-private and used internally by this lib. This exception is thrown as part of the Controller logic if no Browser app was installed at the moment the Uri was meant to be launched. However, a previous check at WebAuthProvider.Builder#start
asserts a browser is available or calls the callback with the exception, this happens here. So there's no way this library's WebAuth could be used without that check being performed first. The AuthenticationActivity
is not meant to be called directly by you.
That said, I don't agree this exception should be caught silently since as I said originally this is an edge case that it doesn't make sense to support. e.g. if there's no browser, the authentication can't be done at all. However, this case is already handled for you and you'll receive the onFailure
call if that happens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lbalmaceda I fully understand that we're not calling AuthenticationActivity
directly. But I think perhaps you're not understanding my point so let me try this another way.
-
What options do we have to catch the
IllegalStateException
that might be thrown byCustomTabsController
? The answer is none (without installing an UncaughtExceptionHandler). If thrown, it will result in a crash in our app, just as we've been dealing with for months. This is not acceptable. -
I understand your code and with this fix the
IllegalStateException
should never be thrown. If so, then why throw it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like this to fail fast, as soon as we can check if a browser is present. We do that in the builder when the start method is called. We can fail safely since the callback is on scope. Not doing it early would mean a lot of instances are getting created (WebAuthProvider, OAuthManager, CustomTabsController, the activity, etc) when we know in advance it will fail, so it's a waste of resources IMO. e.g. we could capture this exception on the AuthenticationActivity and notify somehow the WebAuthProvider that it couldn't even be launched because no browser is installed... Waste of resources 🌮
I thought of keeping it as part of this "internal class" logic, since the method is "failing" and someone should be notified about that. I do agree that throwing an exception deliberately without catching it is not correct, and I'm not going to catch it there because of what I explained above. Since we both agree it's unreachable code, as I'm checking for browser apps ASAP and failing safely, the 2 options left are either keep or remove this uncaught exception. I've removed it already ✌️ Thanks for the comments
@@ -191,7 +191,7 @@ static String getBestBrowserPackage(@NonNull Context context) { | |||
} else if (!customTabsBrowsers.isEmpty()) { | |||
return customTabsBrowsers.get(0); | |||
} else { | |||
return defaultBrowser; | |||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can return null
meaning no "custom tab compatible browser" is installed
@@ -267,6 +269,11 @@ Builder withPKCE(PKCE pkce) { | |||
return this; | |||
} | |||
|
|||
static boolean hasBrowserAppInstalled(@NonNull PackageManager packageManager) { | |||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://auth0.com")); | |||
return intent.resolveActivity(packageManager) != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a valid "browser app" can resolve this basic intent
controller = new CustomTabsController(context, DEFAULT_BROWSER_PACKAGE); | ||
} | ||
|
||
@Test | ||
public void shouldNotHaveCustomizationOptionsSetByDefault() throws Exception { | ||
controller = new CustomTabsController(context, DEFAULT_BROWSER_PACKAGE); | ||
CustomTabsController controller = new CustomTabsController(context, DEFAULT_BROWSER_PACKAGE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't overwrite the class field
@@ -181,6 +183,7 @@ public void shouldBindAndLaunchUri() throws Exception { | |||
verify(context, timeout(MAX_TEST_WAIT_TIME_MS)).startActivity(launchIntentCaptor.capture()); | |||
Intent intent = launchIntentCaptor.getValue(); | |||
assertThat(intent.getAction(), is(Intent.ACTION_VIEW)); | |||
assertThat(intent.getPackage(), is(DEFAULT_BROWSER_PACKAGE)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a specified package would imply that this intent is targeting a specific browser app (the custom tab compatible found one)
Intent intent = launchIntentCaptor.getValue(); | ||
assertThat(intent.getAction(), is(Intent.ACTION_VIEW)); | ||
//A null package name would make the OS decide the best app to resolve the intent | ||
assertThat(intent.getPackage(), is(nullValue())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like the comment says, no package means the OS would pick the best app or prompt the user to pick one.
@@ -241,31 +259,26 @@ public void shouldLaunchUriWithFallbackIfCustomTabIntentFails() throws Exception | |||
.when(context).startActivity(any(Intent.class)); | |||
controller.launchUri(uri); | |||
|
|||
verify(context, new Timeout(MAX_TEST_WAIT_TIME_MS, VerificationModeFactory.times(2))).startActivity(launchIntentCaptor.capture()); | |||
verify(context, timeout(MAX_TEST_WAIT_TIME_MS)).startActivity(launchIntentCaptor.capture()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needed because of threads 🤷♂️
CustomTabsSession session = mock(CustomTabsSession.class); | ||
ComponentName componentName = new ComponentName(DEFAULT_BROWSER_PACKAGE, DEFAULT_BROWSER_PACKAGE + ".CustomTabsService"); | ||
//This depends on an implementation detail but is the only way to test it because of methods visibility | ||
PowerMockito.when(session, "getComponentName").thenReturn(componentName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
required to assert the package later in tests
context.startActivity(fallbackIntent); | ||
} catch (ActivityNotFoundException ex) { | ||
Log.e(TAG, "No Browser available to open the URI"); | ||
throw new IllegalStateException("Could not find any Browser application installed in this device to handle the intent.", ex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lbalmaceda I would prefer that you Log.e() this rather than throw the IllegalStateException. We would prefer that over any possibility of a crash. This method, launchUri(), is called from your AuthenticationActivity. As such, it's impossible to catch the exception (without using an UncaughtExceptionHandler).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange situation to have no browser on an Android device. Thanks for leaving the comments as it's not as easy for me to follow. However, it makes sense and reflected in tests.
@cocojoe can I get your blessing again, please? ✨ |
Changes are still approved, fix your CI |
Occasionally, no browser app was installed in the device triggering the following exception:
While this is a border case scenario, it can still happen on some modified ROMs where the user is allowed to remove even the default browser app.
Furthermore, when picking the right Custom Tab compatible browser there was a bug where the default browser might be picked even if it was not compatible. Attempting to bind to this app's Custom Tab service resulted in the following exception:
Both issues should be fixed on this PR.
Closes #138