[Android] [WebAuth] Rework authentication flow (#1511)
- The 2FA process breaks the flow but opening a separate activity from a separate app. This results in the flow being interpreted as a cancellation. - The new flow uses an intermediate activity to correctly process the results. - Also converted the BindService into an async method. - Fixes #1292
This commit is contained in:
Родитель
a8b189887c
Коммит
4d5941a10f
|
@ -106,8 +106,6 @@ namespace Xamarin.Essentials
|
|||
{
|
||||
if (activity != null)
|
||||
CheckAppActions(activity.Intent);
|
||||
|
||||
WebAuthenticator.OnResume(null);
|
||||
}
|
||||
|
||||
static void CheckAppActions(AndroidIntent intent)
|
||||
|
|
|
@ -12,15 +12,7 @@ namespace Xamarin.Essentials
|
|||
public partial class WebAuthenticator
|
||||
{
|
||||
static TaskCompletionSource<WebAuthenticatorResult> tcsResponse = null;
|
||||
|
||||
static Uri uri = null;
|
||||
|
||||
static CustomTabsActivityManager CustomTabsActivityManager { get; set; }
|
||||
|
||||
static Uri RedirectUri { get; set; }
|
||||
|
||||
internal static Task<WebAuthenticatorResult> ResponseTask
|
||||
=> tcsResponse?.Task;
|
||||
static Uri currentRedirectUri = null;
|
||||
|
||||
internal static bool OnResume(Intent intent)
|
||||
{
|
||||
|
@ -28,7 +20,7 @@ namespace Xamarin.Essentials
|
|||
if (tcsResponse?.Task?.IsCompleted ?? true)
|
||||
return false;
|
||||
|
||||
if (intent == null)
|
||||
if (intent?.Data == null)
|
||||
{
|
||||
tcsResponse.TrySetCanceled();
|
||||
return false;
|
||||
|
@ -39,9 +31,9 @@ namespace Xamarin.Essentials
|
|||
var intentUri = new Uri(intent.Data.ToString());
|
||||
|
||||
// Only handle schemes we expect
|
||||
if (!WebUtils.CanHandleCallback(RedirectUri, intentUri))
|
||||
if (!WebUtils.CanHandleCallback(currentRedirectUri, intentUri))
|
||||
{
|
||||
tcsResponse.TrySetException(new InvalidOperationException($"Invalid Redirect URI, detected `{intentUri}` but expected a URI in the format of `{RedirectUri}`"));
|
||||
tcsResponse.TrySetException(new InvalidOperationException($"Invalid Redirect URI, detected `{intentUri}` but expected a URI in the format of `{currentRedirectUri}`"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -55,7 +47,7 @@ namespace Xamarin.Essentials
|
|||
}
|
||||
}
|
||||
|
||||
static Task<WebAuthenticatorResult> PlatformAuthenticateAsync(Uri url, Uri callbackUrl)
|
||||
static async Task<WebAuthenticatorResult> PlatformAuthenticateAsync(Uri url, Uri callbackUrl)
|
||||
{
|
||||
var packageName = Platform.AppContext.PackageName;
|
||||
|
||||
|
@ -77,58 +69,63 @@ namespace Xamarin.Essentials
|
|||
tcsResponse.TrySetCanceled();
|
||||
|
||||
tcsResponse = new TaskCompletionSource<WebAuthenticatorResult>();
|
||||
tcsResponse.Task.ContinueWith(t =>
|
||||
currentRedirectUri = callbackUrl;
|
||||
|
||||
var parentActivity = Platform.GetCurrentActivity(true);
|
||||
|
||||
var customTabsActivityManager = CustomTabsActivityManager.From(parentActivity);
|
||||
try
|
||||
{
|
||||
// Cleanup when done
|
||||
if (CustomTabsActivityManager != null)
|
||||
if (await BindServiceAsync(customTabsActivityManager))
|
||||
{
|
||||
CustomTabsActivityManager.NavigationEvent -= CustomTabsActivityManager_NavigationEvent;
|
||||
CustomTabsActivityManager.CustomTabsServiceConnected -= CustomTabsActivityManager_CustomTabsServiceConnected;
|
||||
var customTabsIntent = new CustomTabsIntent.Builder(customTabsActivityManager.Session)
|
||||
.SetShowTitle(true)
|
||||
.Build();
|
||||
|
||||
try
|
||||
{
|
||||
CustomTabsActivityManager?.Client?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CustomTabsActivityManager = null;
|
||||
}
|
||||
customTabsIntent.Intent.SetData(global::Android.Net.Uri.Parse(url.OriginalString));
|
||||
|
||||
WebAuthenticatorIntermediateActivity.StartActivity(parentActivity, customTabsIntent.Intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to opening the system browser if necessary
|
||||
var browserIntent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse(url.OriginalString));
|
||||
Platform.CurrentActivity.StartActivity(browserIntent);
|
||||
}
|
||||
});
|
||||
|
||||
uri = url;
|
||||
RedirectUri = callbackUrl;
|
||||
|
||||
CustomTabsActivityManager = CustomTabsActivityManager.From(Platform.GetCurrentActivity(true));
|
||||
CustomTabsActivityManager.NavigationEvent += CustomTabsActivityManager_NavigationEvent;
|
||||
CustomTabsActivityManager.CustomTabsServiceConnected += CustomTabsActivityManager_CustomTabsServiceConnected;
|
||||
|
||||
if (!CustomTabsActivityManager.BindService())
|
||||
return await tcsResponse.Task;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Fall back to opening the system browser if necessary
|
||||
var browserIntent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse(url.OriginalString));
|
||||
Platform.CurrentActivity.StartActivity(browserIntent);
|
||||
try
|
||||
{
|
||||
customTabsActivityManager.Client?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Task<bool> BindServiceAsync(CustomTabsActivityManager manager)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
manager.CustomTabsServiceConnected += OnCustomTabsServiceConnected;
|
||||
|
||||
if (!manager.BindService())
|
||||
{
|
||||
manager.CustomTabsServiceConnected -= OnCustomTabsServiceConnected;
|
||||
tcs.TrySetResult(false);
|
||||
}
|
||||
|
||||
return WebAuthenticator.ResponseTask;
|
||||
return tcs.Task;
|
||||
|
||||
void OnCustomTabsServiceConnected(ComponentName name, CustomTabsClient client)
|
||||
{
|
||||
manager.CustomTabsServiceConnected -= OnCustomTabsServiceConnected;
|
||||
tcs.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void CustomTabsActivityManager_CustomTabsServiceConnected(ComponentName name, CustomTabsClient client)
|
||||
{
|
||||
var builder = new CustomTabsIntent.Builder(CustomTabsActivityManager.Session)
|
||||
.SetShowTitle(true);
|
||||
|
||||
var customTabsIntent = builder.Build();
|
||||
customTabsIntent.Intent.AddFlags(ActivityFlags.SingleTop | ActivityFlags.NoHistory | ActivityFlags.NewTask);
|
||||
|
||||
var ctx = Platform.CurrentActivity;
|
||||
|
||||
CustomTabsHelper.AddKeepAliveExtra(ctx, customTabsIntent.Intent);
|
||||
|
||||
customTabsIntent.LaunchUrl(ctx, global::Android.Net.Uri.Parse(uri.OriginalString));
|
||||
}
|
||||
|
||||
static void CustomTabsActivityManager_NavigationEvent(int navigationEvent, global::Android.OS.Bundle extras) =>
|
||||
System.Diagnostics.Debug.WriteLine($"CustomTabs.NavigationEvent: {navigationEvent}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Android.App;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
|
@ -12,8 +10,13 @@ namespace Xamarin.Essentials
|
|||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
WebAuthenticator.OnResume(Intent);
|
||||
// start the intermediate activity again with flags to close the custom tabs
|
||||
var intent = new Intent(this, typeof(WebAuthenticatorIntermediateActivity));
|
||||
intent.SetData(Intent.Data);
|
||||
intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
|
||||
StartActivity(intent);
|
||||
|
||||
// finish this activity
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
[Activity(ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
|
||||
class WebAuthenticatorIntermediateActivity : Activity
|
||||
{
|
||||
const string launchedExtra = "launched";
|
||||
const string actualIntentExtra = "actual_intent";
|
||||
|
||||
bool launched;
|
||||
Intent actualIntent;
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
var extras = savedInstanceState ?? Intent.Extras;
|
||||
|
||||
// read the values
|
||||
launched = extras.GetBoolean(launchedExtra, false);
|
||||
actualIntent = extras.GetParcelable(actualIntentExtra) as Intent;
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
|
||||
if (!launched)
|
||||
{
|
||||
// if this is the first time, start the authentication flow
|
||||
StartActivity(actualIntent);
|
||||
|
||||
launched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, resume the auth flow and finish this activity
|
||||
WebAuthenticator.OnResume(Intent);
|
||||
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
|
||||
Intent = intent;
|
||||
}
|
||||
|
||||
protected override void OnSaveInstanceState(Bundle outState)
|
||||
{
|
||||
// save the values
|
||||
outState.PutBoolean(launchedExtra, launched);
|
||||
outState.PutParcelable(actualIntentExtra, actualIntent);
|
||||
|
||||
base.OnSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
public static void StartActivity(Activity activity, Intent intent)
|
||||
{
|
||||
var intermediateIntent = new Intent(activity, typeof(WebAuthenticatorIntermediateActivity));
|
||||
intermediateIntent.PutExtra(actualIntentExtra, intent);
|
||||
|
||||
activity.StartActivity(intermediateIntent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ Task("docs")
|
|||
.Does(() =>
|
||||
{
|
||||
MSBuild("./Xamarin.Essentials/Xamarin.Essentials.csproj", new MSBuildSettings()
|
||||
.EnableBinaryLogger("./output/binlogs/nugets.binlog")
|
||||
.EnableBinaryLogger("./output/binlogs/docs.binlog")
|
||||
.SetConfiguration("Release")
|
||||
.WithRestore()
|
||||
.WithTarget("mdocupdatedocs"));
|
||||
|
|
Загрузка…
Ссылка в новой задаче