Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,39 @@ static Optional<BrowserService> create() {
* @throws java.net.URISyntaxException If it is not a valid URL string
* @since 4.0.25
*/
void launchWebAuthentication(String url, String callbackUrlScheme, Consumer<String> callback)
throws IOException, URISyntaxException;
default void launchWebAuthentication(String url, String callbackUrlScheme, Consumer<String> callback)
throws IOException, URISyntaxException {
launchWebAuthentication(url, callbackUrlScheme, false, callback);
}

/**
* Starts a web authentication session, like
* {@link #launchWebAuthentication(String, String, Consumer)}, optionally requesting an
* ephemeral browsing session.
*
* <p>When {@code prefersEphemeralSession} is {@code true}, the session is requested to run
* in a private/ephemeral browsing mode that does not share cookies or other browsing data
* with the browser, and discards them when the session ends. Each authentication then starts
* from a clean slate, at the cost of losing single sign-on.</p>
*
* <p>On Android this enables ephemeral browsing on the Auth Tab (or the Custom Tab
* fallback). It is best-effort: it requires the browser to support it (e.g. recent
* Chrome versions); browsers without support ignore the request and share browsing
* data as usual. On Desktop the flag is ignored.</p>
*
* @param url the authentication URL to load, including the {@code redirect_uri} expected by the
* web service.
* @param callbackUrlScheme either a custom URL scheme (without {@code ://}, e.g. {@code "myapp"})
* or a full HTTPS URL (e.g. {@code "https://example.com/callback"}) that
* the web service uses for its redirect.
* @param prefersEphemeralSession if {@code true}, requests a private browsing session that does
* not share or persist cookies and other browsing data.
* @param callback a consumer that receives the full callback URL on success, or {@code null} if
* the user canceled the flow or an error occurred.
* @throws java.io.IOException If the URL can't be opened
* @throws java.net.URISyntaxException If it is not a valid URL string
* @since 4.0.26
*/
void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession,
Consumer<String> callback) throws IOException, URISyntaxException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,25 @@ public void launchExternalBrowser(String url) throws IOException {
}

@Override
public void launchWebAuthentication(String url, String callbackUrlScheme, Consumer<String> callback)
throws IOException, URISyntaxException {
public void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession,
Consumer<String> callback) throws IOException, URISyntaxException {
if (url == null || url.isEmpty()) {
throw new IOException("Authentication url cannot be null or empty");
}
if (callbackUrlScheme == null || callbackUrlScheme.isEmpty()) {
throw new IOException("Callback url scheme cannot be null or empty");
}
if (Util.DEBUG) {
LOG.info("Launch web authentication URL: " + url + ", callback scheme: " + callbackUrlScheme);
LOG.info("Launch web authentication URL: " + url + ", callback scheme: " + callbackUrlScheme
+ ", ephemeral: " + prefersEphemeralSession);
}
authCallback = callback;
startWebAuthentication(url, callbackUrlScheme);
startWebAuthentication(url, callbackUrlScheme, prefersEphemeralSession);
}

// native
private native boolean launchURL(String url);
private native void startWebAuthentication(String url, String callbackUrlScheme);
private native void startWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession);

// callback
public static void setAuthResult(String callbackUrl) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ public void launchExternalBrowser(String url) throws IOException {
}

@Override
public void launchWebAuthentication(String url, String callbackUrlScheme, Consumer<String> callback)
throws IOException, URISyntaxException {
public void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession,
Consumer<String> callback) throws IOException, URISyntaxException {
launchExternalBrowser(url);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,22 @@ public void launchExternalBrowser(String url) throws IOException {
}

@Override
public void launchWebAuthentication(String url, String callbackUrlScheme, Consumer<String> callback) throws IOException {
public void launchWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession,
Consumer<String> callback) throws IOException {
if (url == null || url.isEmpty()) {
throw new IOException("Authentication url cannot be null or empty");
}
if (callbackUrlScheme == null || callbackUrlScheme.isEmpty()) {
throw new IOException("Callback url scheme cannot be null or empty");
}
authCallback = callback;
startWebAuthentication(url, callbackUrlScheme);
startWebAuthentication(url, callbackUrlScheme, prefersEphemeralSession);
}

// native
private native boolean launchURL(String url);
private static native void initBrowser();
private static native void startWebAuthentication(String url, String callbackUrlScheme);
private static native void startWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession);

// callback
public static void setAuthResult(String callbackUrl) {
Expand Down
9 changes: 5 additions & 4 deletions modules/browser/src/main/native/android/c/browser.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ static void initializeDalvikHandles() {
ATTACH_DALVIK();
jmethodID jBrowserServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "<init>", "(Landroid/app/Activity;)V");
jBrowserServiceLaunchMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "launchURL", "(Ljava/lang/String;)Z");
jBrowserServiceStartWebAuthMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "startWebAuthentication", "(Ljava/lang/String;Ljava/lang/String;)V");
jBrowserServiceStartWebAuthMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jBrowserServiceClass, "startWebAuthentication", "(Ljava/lang/String;Ljava/lang/String;Z)V");

jobject jActivity = substrateGetActivity();
jobject jtmpobj = (*dalvikEnv)->NewObject(dalvikEnv, jBrowserServiceClass, jBrowserServiceInitMethod, jActivity);
Expand Down Expand Up @@ -93,17 +93,18 @@ JNIEXPORT jboolean JNICALL Java_com_gluonhq_attach_browser_impl_AndroidBrowserSe
}

JNIEXPORT void JNICALL Java_com_gluonhq_attach_browser_impl_AndroidBrowserService_startWebAuthentication
(JNIEnv *env, jclass jClass, jstring jurl, jstring jcallbackUrlScheme)
(JNIEnv *env, jclass jClass, jstring jurl, jstring jcallbackUrlScheme, jboolean jprefersEphemeralSession)
{
const char *urlChars = (*env)->GetStringUTFChars(env, jurl, NULL);
const char *schemeChars = (*env)->GetStringUTFChars(env, jcallbackUrlScheme, NULL);
if (isDebugAttach()) {
ATTACH_LOG_FINE("Browser start web authentication for url %s, callback scheme %s\n", urlChars, schemeChars);
ATTACH_LOG_FINE("Browser start web authentication for url %s, callback scheme %s, ephemeral %d\n",
urlChars, schemeChars, jprefersEphemeralSession);
}
ATTACH_DALVIK();
jstring durl = (*dalvikEnv)->NewStringUTF(dalvikEnv, urlChars);
jstring dscheme = (*dalvikEnv)->NewStringUTF(dalvikEnv, schemeChars);
(*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikBrowserService, jBrowserServiceStartWebAuthMethod, durl, dscheme);
(*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikBrowserService, jBrowserServiceStartWebAuthMethod, durl, dscheme, jprefersEphemeralSession);
DETACH_DALVIK();
// (*env)->ReleaseStringUTFChars(env, jurl, urlChars);
// (*env)->ReleaseStringUTFChars(env, jcallbackUrlScheme, schemeChars);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public class DalvikBrowserService {
private static final String EXTRA_REDIRECT_SCHEME = "androidx.browser.auth.extra.REDIRECT_SCHEME";
private static final String EXTRA_HTTPS_REDIRECT_HOST = "androidx.browser.auth.extra.HTTPS_REDIRECT_HOST";
private static final String EXTRA_HTTPS_REDIRECT_PATH = "androidx.browser.auth.extra.HTTPS_REDIRECT_PATH";
private static final String EXTRA_ENABLE_EPHEMERAL_BROWSING = "androidx.browser.customtabs.extra.ENABLE_EPHEMERAL_BROWSING";
// Custom Tabs service action and capability categories, from androidx.browser
private static final String ACTION_CUSTOM_TABS_SERVICE = "android.support.customtabs.action.CustomTabsService";
private static final String CATEGORY_EPHEMERAL_BROWSING = "androidx.browser.customtabs.category.EphemeralBrowsing";

private final Activity activity;
private final boolean debug;
Expand Down Expand Up @@ -84,7 +88,7 @@ private boolean launchURL(String url) {
return true;
}

private void startWebAuthentication(String url, String callbackUrlScheme) {
private void startWebAuthentication(String url, String callbackUrlScheme, boolean prefersEphemeralSession) {
if (url == null || url.isEmpty() || callbackUrlScheme == null || callbackUrlScheme.isEmpty()) {
Log.e(TAG, "Invalid web authentication parameters: url and callbackUrlScheme are required");
nativeWebAuthResult(null);
Expand All @@ -93,6 +97,10 @@ private void startWebAuthentication(String url, String callbackUrlScheme) {

Intent authIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
authIntent.putExtra(EXTRA_LAUNCH_AUTH_TAB, true);
if (prefersEphemeralSession) {
// best-effort: browsers without ephemeral browsing support ignore this extra
authIntent.putExtra(EXTRA_ENABLE_EPHEMERAL_BROWSING, true);
}
// null session so browsers without Auth Tab support treat this as a Custom Tab
Bundle sessionBundle = new Bundle();
sessionBundle.putBinder(EXTRA_SESSION, null);
Expand All @@ -119,6 +127,15 @@ private void startWebAuthentication(String url, String callbackUrlScheme) {
return;
}

if (prefersEphemeralSession) {
String browserPackage = authIntent.resolveActivity(activity.getPackageManager()).getPackageName();
if (!supportsEphemeralBrowsing(browserPackage)) {
Log.w(TAG, "The browser handling the web authentication (" + browserPackage
+ ") does not support ephemeral browsing: the session will share the "
+ "browser's cookies and browsing data");
}
}

Util.setOnActivityResultHandler(new IntentHandler() {
@Override
public void gotActivityResult(int requestCode, int resultCode, Intent intent) {
Expand Down Expand Up @@ -190,6 +207,17 @@ private static boolean matchesCallback(Uri uri, String callbackUrlScheme) {
return callbackUrlScheme.equals(uri.getScheme());
}

/**
* Checks whether the given browser package advertises support for ephemeral browsing in its
* Custom Tabs service (equivalent to CustomTabsClient.isEphemeralBrowsingSupported).
*/
private boolean supportsEphemeralBrowsing(String browserPackage) {
Intent serviceIntent = new Intent(ACTION_CUSTOM_TABS_SERVICE)
.setPackage(browserPackage)
.addCategory(CATEGORY_EPHEMERAL_BROWSING);
return activity.getPackageManager().resolveService(serviceIntent, 0) != null;
}

private static void clearPendingSession() {
pendingService = null;
pendingCallbackUrlScheme = null;
Expand Down
4 changes: 2 additions & 2 deletions modules/browser/src/main/native/ios/Browser.m
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ void sendAuthResult(NSString *callbackUrl) {
}

JNIEXPORT void JNICALL Java_com_gluonhq_attach_browser_impl_IOSBrowserService_startWebAuthentication
(JNIEnv *env, jclass jClass, jstring jUrl, jstring jScheme)
(JNIEnv *env, jclass jClass, jstring jUrl, jstring jScheme, jboolean jPrefersEphemeralSession)
{
const jchar *charsUrl = (*env)->GetStringChars(env, jUrl, NULL);
NSString *url = [NSString stringWithCharacters:(UniChar *)charsUrl length:(*env)->GetStringLength(env, jUrl)];
Expand Down Expand Up @@ -189,7 +189,7 @@ void sendAuthResult(NSString *callbackUrl) {
if (@available(iOS 13.0, *)) {
_authContextProvider = [[AttachAuthContextProvider alloc] init];
_authSession.presentationContextProvider = _authContextProvider;
_authSession.prefersEphemeralWebBrowserSession = NO;
_authSession.prefersEphemeralWebBrowserSession = (jPrefersEphemeralSession == JNI_TRUE);
}

dispatch_async(dispatch_get_main_queue(), ^{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="smsto" />
</intent>
<!-- required to query the browser's Custom Tabs service capabilities
(e.g. ephemeral browsing support) on Android 11+ -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
</manifest>

Loading