Skip to content

Commit acbaaca

Browse files
authored
fix(#401): update OIDC login handling to use custom tabs (#405)
1 parent 58557bd commit acbaaca

File tree

7 files changed

+93
-53
lines changed

7 files changed

+93
-53
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ android {
475475
dependencies {
476476
implementation fileTree(dir: 'libs', include: ['*.jar'])
477477
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.9.24')
478+
implementation 'androidx.browser:browser:1.8.0'
478479
implementation 'androidx.core:core:1.13.1'
479480
implementation 'androidx.activity:activity:1.9.0'
480481
implementation 'androidx.fragment:fragment:1.7.1'

src/main/AndroidManifest.xml

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,18 @@
4040
</intent-filter>
4141
</activity>
4242
<activity android:name="EmbeddedBrowserActivity"
43-
android:screenOrientation="portrait"
44-
android:configChanges="orientation|screenSize"
45-
tools:ignore="DiscouragedApi"/>
43+
android:screenOrientation="portrait"
44+
android:configChanges="orientation|screenSize"
45+
android:launchMode="singleTask"
46+
android:exported="true"
47+
tools:ignore="DiscouragedApi">
48+
<intent-filter android:autoVerify="true">
49+
<action android:name="android.intent.action.VIEW" />
50+
<category android:name="android.intent.category.DEFAULT" />
51+
<category android:name="android.intent.category.BROWSABLE" />
52+
<data android:scheme="@string/scheme" android:host="@string/app_host" android:pathPattern=".*"/>
53+
</intent-filter>
54+
</activity>
4655
<activity android:name="ConnectionErrorActivity"
4756
android:screenOrientation="portrait"
4857
android:configChanges="orientation|screenSize"
@@ -66,16 +75,6 @@
6675
<activity android:name="DomainVerificationActivity"
6776
android:screenOrientation="portrait"
6877
tools:ignore="DiscouragedApi" />
69-
<activity android:name="AppUrlIntentActivity"
70-
android:launchMode="singleInstance"
71-
android:exported="true">
72-
<intent-filter android:autoVerify="true">
73-
<action android:name="android.intent.action.VIEW" />
74-
<category android:name="android.intent.category.DEFAULT" />
75-
<category android:name="android.intent.category.BROWSABLE" />
76-
<data android:scheme="@string/scheme" android:host="@string/app_host" android:pathPattern=".*"/>
77-
</intent-filter>
78-
</activity>
7978
<activity android:name="UpgradingActivity"
8079
android:screenOrientation="portrait"
8180
android:configChanges="orientation|screenSize"

src/main/java/org/medicmobile/webapp/mobile/AppUrlIntentActivity.java

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/main/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivity.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,33 @@ public void onReceiveValue(String result) {
119119

120120
enableUrlHandlers(container);
121121

122-
Intent appLinkIntent = getIntent();
123-
Uri appLinkData = appLinkIntent.getData();
124-
browseTo(appLinkData);
125-
126122
if (settings.allowsConfiguration()) {
127123
toast(redactUrl(appUrl));
128124
}
129125

130126
registerRetryConnectionBroadcastReceiver();
131127

132128
String recentNavigation = settings.getLastUrl();
133-
if (isValidNavigationUrl(appUrl, recentNavigation)) {
129+
Intent appLinkIntent = getIntent();
130+
Uri appLinkData = appLinkIntent.getData();
131+
if (appLinkData != null) {
132+
// The app has been opened via an app link.
133+
browseTo(appLinkData);
134+
} else if (isValidNavigationUrl(appUrl, recentNavigation)) {
135+
// The app has been opened normally, and the user can start where they left off.
134136
container.loadUrl(recentNavigation);
137+
} else {
138+
// The app has been opened normally, but no previous URL is available. (Maybe it is the first time.)
139+
browseTo(null);
135140
}
136141
}
137142

143+
@Override
144+
protected void onNewIntent(Intent intent) {
145+
Uri appLinkData = intent.getData();
146+
browseTo(appLinkData);
147+
}
148+
138149
@SuppressWarnings("PMD.CallSuperFirst")
139150
@Override
140151
protected void onStart() {

src/main/java/org/medicmobile/webapp/mobile/UrlHandler.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import android.webkit.WebView;
1818
import android.webkit.WebViewClient;
1919

20+
import androidx.browser.customtabs.CustomTabsIntent;
21+
2022
public class UrlHandler extends WebViewClient {
2123
EmbeddedBrowserActivity parentActivity;
2224
SettingsStore settings;
@@ -31,10 +33,14 @@ public UrlHandler(EmbeddedBrowserActivity parentActivity, SettingsStore settings
3133
@Override
3234
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
3335
Uri uri = request.getUrl();
34-
if (isUrlRelated(this.settings.getAppUrl(), uri) || isOidcProviderUrl(uri)) {
36+
if (isUrlRelated(this.settings.getAppUrl(), uri)) {
3537
// Load all related URLs in the WebView
3638
return false;
3739
}
40+
if (isOidcProviderUrl(uri)) {
41+
launchCustomTab(uri);
42+
return true;
43+
}
3844

3945
// Let Android decide what to do with unrelated URLs
4046
// unrelated URLs include `tel:` and `sms:` uri schemes
@@ -43,6 +49,11 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request
4349
return true;
4450
}
4551

52+
private void launchCustomTab(Uri uri) {
53+
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
54+
customTabsIntent.launchUrl(this.parentActivity, uri);
55+
}
56+
4657
/**
4758
* Support OIDC login flow by allowing any URL that contains the current app_url as the redirect_uri.
4859
*/

src/test/java/org/medicmobile/webapp/mobile/EmbeddedBrowserActivityTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
import static org.mockito.Mockito.mock;
1515
import static org.mockito.Mockito.mockStatic;
1616
import static org.mockito.Mockito.never;
17+
import static org.mockito.Mockito.times;
18+
import static org.mockito.Mockito.verify;
1719
import static org.mockito.Mockito.when;
1820
import static org.robolectric.Shadows.shadowOf;
1921

2022
import android.content.Intent;
23+
import android.net.Uri;
2124

2225
import androidx.core.content.ContextCompat;
2326
import androidx.test.espresso.intent.Intents;
@@ -273,4 +276,25 @@ public void onActivityResult_unknownRequestCode_logRequestCode(){
273276
});
274277
}
275278
}
279+
280+
@Test
281+
public void onNewIntent() {
282+
Intent intent = mock(Intent.class);
283+
String url = "https://example.com";
284+
when(intent.getData()).thenReturn(Uri.parse(url));
285+
try(MockedStatic<MedicLog> medicLogMock = mockStatic(MedicLog.class)) {
286+
scenarioRule
287+
.getScenario()
288+
.onActivity(embeddedBrowserActivity -> {
289+
embeddedBrowserActivity.onNewIntent(intent);
290+
291+
verify(intent, times(1)).getData();
292+
medicLogMock.verify(() -> MedicLog.trace(
293+
eq(embeddedBrowserActivity),
294+
eq("Pointing browser to: %s"),
295+
eq(url)
296+
));
297+
});
298+
}
299+
}
276300
}

src/test/java/org/medicmobile/webapp/mobile/UrlHandlerTest.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.medicmobile.webapp.mobile;
22

3+
import static org.junit.Assert.assertEquals;
34
import static org.junit.Assert.assertFalse;
45
import static org.junit.Assert.assertTrue;
56
import static org.mockito.ArgumentMatchers.any;
67
import static org.mockito.Mockito.doNothing;
78
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.mockConstruction;
810
import static org.mockito.Mockito.times;
911
import static org.mockito.Mockito.verify;
1012
import static org.mockito.Mockito.when;
@@ -14,14 +16,18 @@
1416
import android.webkit.WebResourceRequest;
1517
import android.webkit.WebView;
1618

19+
import androidx.browser.customtabs.CustomTabsIntent;
20+
1721
import org.junit.Before;
1822
import org.junit.Test;
1923
import org.junit.runner.RunWith;
24+
import org.mockito.MockedConstruction;
2025
import org.robolectric.RobolectricTestRunner;
2126

2227
@RunWith(RobolectricTestRunner.class)
2328
public class UrlHandlerTest {
2429
private static final String APP_URL = "https://project-abc.medic.org";
30+
private EmbeddedBrowserActivity parentActivity;
2531
private SettingsStore settingsStore;
2632
private WebView webView;
2733
private WebResourceRequest webResourceRequest;
@@ -30,6 +36,7 @@ public class UrlHandlerTest {
3036

3137
@Before
3238
public void setup() {
39+
parentActivity = mock(EmbeddedBrowserActivity.class);
3340
settingsStore = mock(SettingsStore.class);
3441
when(settingsStore.getAppUrl()).thenReturn(APP_URL);
3542
webView = mock(WebView.class);
@@ -38,7 +45,7 @@ public void setup() {
3845
doNothing().when(context).startActivity(any());
3946
webResourceRequest = mock(WebResourceRequest.class);
4047

41-
handler = new UrlHandler(null, settingsStore);
48+
handler = new UrlHandler(parentActivity, settingsStore);
4249
}
4350

4451
@Test
@@ -69,17 +76,26 @@ public void shouldOverrideUrlLoading_withExternalUrl() {
6976

7077
@Test
7178
public void shouldOverrideUrlLoading_withExternalOidcProviderUrl() {
72-
when(webResourceRequest.getUrl()).thenReturn(
73-
Uri.parse("some-external-url.com?redirect_uri=" + Uri.encode(APP_URL + "/medic/login/oidc"))
74-
);
75-
76-
boolean result = handler.shouldOverrideUrlLoading(webView, webResourceRequest);
77-
78-
assertFalse(result);
79-
verify(settingsStore, times(2)).getAppUrl();
80-
verify(webResourceRequest).getUrl();
81-
verify(webView, times(0)).getContext();
82-
verify(context, times(0)).startActivity(any());
79+
CustomTabsIntent intent = mock(CustomTabsIntent.class);
80+
Uri expectedUri = Uri.parse("some-external-url.com?redirect_uri=" + Uri.encode(APP_URL + "/medic/login/oidc"));
81+
try (MockedConstruction<CustomTabsIntent.Builder> mocked = mockConstruction(
82+
CustomTabsIntent.Builder.class,
83+
(mock, context) -> when(mock.build()).thenReturn(intent)
84+
)) {
85+
doNothing().when(intent).launchUrl(any(), any());
86+
when(webResourceRequest.getUrl()).thenReturn(expectedUri);
87+
88+
boolean result = handler.shouldOverrideUrlLoading(webView, webResourceRequest);
89+
90+
assertTrue(result);
91+
verify(settingsStore, times(2)).getAppUrl();
92+
verify(webResourceRequest).getUrl();
93+
verify(webView, times(0)).getContext();
94+
verify(context, times(0)).startActivity(any());
95+
assertEquals(1, mocked.constructed().size());
96+
verify(mocked.constructed().get(0), times(1)).build();
97+
verify(intent, times(1)).launchUrl(parentActivity, expectedUri);
98+
}
8399
}
84100

85101
@Test

0 commit comments

Comments
 (0)