-
-
Save myroot/d998b80dbf52ad7632dfe519ca992fdc to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="utf-8" ?> | |
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
x:Class="GoogleOAuthTest.EmbeddedBrowser"> | |
<ContentPage.Content> | |
<WebView x:Name="Broswer" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"/> | |
</ContentPage.Content> | |
<ContentPage.ToolbarItems> | |
<ToolbarItem Text="Cancel" Order="Secondary" Clicked="OnCancel"/> | |
</ContentPage.ToolbarItems> | |
</ContentPage> |
using System; | |
using Xamarin.Forms; | |
using Xamarin.Forms.Xaml; | |
namespace GoogleOAuthTest | |
{ | |
[XamlCompilation(XamlCompilationOptions.Compile)] | |
public partial class EmbeddedBrowser : ContentPage, IEmbeddedBrowser | |
{ | |
public EmbeddedBrowser(string title) | |
{ | |
Title = title; | |
InitializeComponent(); | |
} | |
public event EventHandler Cancelled; | |
public void Start(string url) | |
{ | |
Broswer.Source = url; | |
} | |
protected override bool OnBackButtonPressed() | |
{ | |
return true; | |
} | |
private void OnCancel(object sender, EventArgs e) | |
{ | |
Cancelled?.Invoke(this, EventArgs.Empty); | |
} | |
} | |
} |
var clientSecrets = new ClientSecrets | |
{ | |
ClientId = "client id", | |
ClientSecret = "client secret" | |
}; | |
var browser = new EmbeddedBrowser("Login to google"); | |
browser.Cancelled += (s, evt) => { Navigation.PopAsync(); }; | |
var unused = Navigation.PushAsync(browser); | |
var userCredential = await GoogleWebAuthorizationBroker.AuthorizeAsync(clientSecrets, new[] { DriveService.Scope.Drive }, "user", CancellationToken.None, | |
codeReceiver : new TizenLocalServerCodeReceiver { EmbeddedBrowser = browser }); | |
unused = Navigation.PopAsync(); |
using Google.Apis.Auth.OAuth2; | |
using Google.Apis.Auth.OAuth2.Requests; | |
using Google.Apis.Auth.OAuth2.Responses; | |
using Google.Apis.Logging; | |
using System; | |
using System.Collections.Specialized; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Sockets; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace GoogleOAuthTest | |
{ | |
public interface IEmbeddedBrowser | |
{ | |
event EventHandler Cancelled; | |
void Start(string url); | |
} | |
class ConsoleLogger : ILogger | |
{ | |
public bool IsDebugEnabled => false; | |
public void Debug(string message, params object[] formatArgs) | |
{ | |
Console.WriteLine(message, formatArgs); | |
} | |
public void Error(Exception exception, string message, params object[] formatArgs) | |
{ | |
Debug(message, formatArgs); | |
} | |
public void Error(string message, params object[] formatArgs) | |
{ | |
Debug(message, formatArgs); | |
} | |
public ILogger ForType(Type type) | |
{ | |
return this; | |
} | |
public ILogger ForType<T>() | |
{ | |
return this; | |
} | |
public void Info(string message, params object[] formatArgs) | |
{ | |
Debug(message, formatArgs); | |
} | |
public void Warning(string message, params object[] formatArgs) | |
{ | |
Debug(message, formatArgs); | |
} | |
} | |
public class TizenLocalServerCodeReceiver : ICodeReceiver | |
{ | |
private static readonly ILogger Logger = new ConsoleLogger(); | |
/// <summary>The call back request path.</summary> | |
internal const string LoopbackCallbackPath = "/authorize/"; | |
/// <summary>Localhost callback URI, expects a port parameter.</summary> | |
internal static readonly string CallbackUriTemplateLocalhost = $"http://localhost:{{0}}{LoopbackCallbackPath}"; | |
/// <summary>127.0.0.1 callback URI, expects a port parameter.</summary> | |
internal static readonly string CallbackUriTemplate127001 = $"http://127.0.0.1:{{0}}{LoopbackCallbackPath}"; | |
/// <summary>Close HTML tag to return the browser so it will close itself.</summary> | |
internal const string DefaultClosePageResponse = | |
@"<html> | |
<head><title>OAuth 2.0 Authentication Token Received</title></head> | |
<body> | |
Received verification code. You may now close this window. | |
<script type='text/javascript'> | |
// This doesn't work on every browser. | |
window.setTimeout(function() { | |
this.focus(); | |
window.opener = this; | |
window.open('', '_self', ''); | |
window.close(); | |
}, 1000); | |
//if (window.opener) { window.opener.checkToken(); } | |
</script> | |
</body> | |
</html>"; | |
private static object s_lock = new object(); | |
// Current best-guess as to most suitable callback URI. | |
private static string s_callbackUriTemplate; | |
// Has a successful callback been received? | |
private static bool s_receivedCallback; | |
/// <summary> | |
/// Create an instance of <see cref="TizenLocalServerCodeReceiver"/>. | |
/// </summary> | |
public TizenLocalServerCodeReceiver() : this(DefaultClosePageResponse) { } | |
/// <summary> | |
/// Create an instance of <see cref="TizenLocalServerCodeReceiver"/>. | |
/// </summary> | |
/// <param name="closePageResponse">Custom close page response for this instance</param> | |
public TizenLocalServerCodeReceiver(string closePageResponse) | |
{ | |
_closePageResponse = closePageResponse; | |
lock (s_lock) | |
{ | |
// Listening on 127.0.0.1 is recommended, but can't be done in non-admin Windows 7 & 8. | |
// So use some tests/heuristics so maybe listen on localhost instead. | |
if (s_callbackUriTemplate != null && !s_receivedCallback) | |
{ | |
// On non-first runs, if a successful callback has not been received | |
// then force callback to use localhost rather than 127.0.0.1 | |
// This is to heuristically avoid errors on browsers that can't connect to 127.0.0.1 | |
// E.g. IE11 in enhanced protection mode. | |
s_callbackUriTemplate = CallbackUriTemplateLocalhost; | |
} | |
if (s_callbackUriTemplate == null) | |
{ | |
s_callbackUriTemplate = CallbackUriTemplate127001; | |
// No check required on NETStandard, it uses TcpListener which can only use IP adddresses, not DNS names. | |
} | |
// Set the instance field of which callback URI to use. | |
// An instance field is used to ensure any one instance of this class | |
// uses a consistent callback URI. | |
_callbackUriTemplate = s_callbackUriTemplate; | |
} | |
} | |
// Callback URI used for this instance. | |
private string _callbackUriTemplate; | |
// Close page response for this instance. | |
private readonly string _closePageResponse; | |
public IEmbeddedBrowser EmbeddedBrowser { get; set; } | |
// There is a race condition on the port used for the loopback callback. | |
// This is not good, but is now difficult to change due to RedirectUri and ReceiveCodeAsync | |
// being public methods. | |
private string redirectUri; | |
/// <inheritdoc /> | |
public string RedirectUri | |
{ | |
get | |
{ | |
if (string.IsNullOrEmpty(redirectUri)) | |
{ | |
redirectUri = string.Format(_callbackUriTemplate, GetRandomUnusedPort()); | |
} | |
return redirectUri; | |
} | |
} | |
/// <inheritdoc /> | |
public async Task<AuthorizationCodeResponseUrl> ReceiveCodeAsync(AuthorizationCodeRequestUrl url, | |
CancellationToken taskCancellationToken) | |
{ | |
var authorizationUrl = url.Build().AbsoluteUri; | |
// The listener type depends on platform: | |
// * .NET desktop: System.Net.HttpListener | |
// * .NET Core: LimitedLocalhostHttpServer (above, HttpListener is not available in any version of netstandard) | |
using (var listener = StartListener()) | |
{ | |
Logger.Debug("Open a browser with \"{0}\" URL", authorizationUrl); | |
bool browserOpenedOk; | |
try | |
{ | |
browserOpenedOk = OpenBrowser(authorizationUrl); | |
} | |
catch (Exception e) | |
{ | |
Logger.Error(e, "Failed to launch browser with \"{0}\" for authorization", authorizationUrl); | |
throw new NotSupportedException( | |
$"Failed to launch browser with \"{authorizationUrl}\" for authorization. See inner exception for details.", e); | |
} | |
if (!browserOpenedOk) | |
{ | |
Logger.Error("Failed to launch browser with \"{0}\" for authorization; platform not supported.", authorizationUrl); | |
throw new NotSupportedException( | |
$"Failed to launch browser with \"{authorizationUrl}\" for authorization; platform not supported."); | |
} | |
var ret = await GetResponseFromListener(listener, taskCancellationToken).ConfigureAwait(false); | |
// Note that a successful callback has been received. | |
s_receivedCallback = true; | |
return ret; | |
} | |
} | |
/// <summary>Returns a random, unused port.</summary> | |
private static int GetRandomUnusedPort() | |
{ | |
var listener = new TcpListener(IPAddress.Loopback, 0); | |
try | |
{ | |
listener.Start(); | |
return ((IPEndPoint)listener.LocalEndpoint).Port; | |
} | |
finally | |
{ | |
listener.Stop(); | |
} | |
} | |
private HttpListener StartListener() | |
{ | |
var listener = new HttpListener(); | |
listener.Prefixes.Add(RedirectUri); | |
listener.Start(); | |
return listener; | |
} | |
private async Task<AuthorizationCodeResponseUrl> GetResponseFromListener(HttpListener listener, CancellationToken ct) | |
{ | |
HttpListenerContext context; | |
// Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token, | |
// the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call. | |
using (ct.Register(listener.Stop)) | |
{ | |
// Wait to get the authorization code response. | |
try | |
{ | |
context = await listener.GetContextAsync().ConfigureAwait(false); | |
} | |
catch (Exception) when (ct.IsCancellationRequested) | |
{ | |
ct.ThrowIfCancellationRequested(); | |
// Next line will never be reached because cancellation will always have been requested in this catch block. | |
// But it's required to satisfy compiler. | |
throw new InvalidOperationException(); | |
} | |
} | |
NameValueCollection coll = context.Request.QueryString; | |
// Write a "close" response. | |
var bytes = Encoding.UTF8.GetBytes(_closePageResponse); | |
context.Response.ContentLength64 = bytes.Length; | |
context.Response.SendChunked = false; | |
context.Response.KeepAlive = false; | |
var output = context.Response.OutputStream; | |
await output.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); | |
await output.FlushAsync().ConfigureAwait(false); | |
output.Close(); | |
context.Response.Close(); | |
// Create a new response URL with a dictionary that contains all the response query parameters. | |
return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k])); | |
} | |
private bool OpenBrowser(string url) | |
{ | |
EmbeddedBrowser.Start(url); | |
return true; | |
} | |
} | |
} |
This works great on emulator but not on my TV - i get
Loaded '/opt/dotnet/runtime/Tizen.WebView.dll'. Cannot find or open the symbol file.
Exception thrown: 'System.DllNotFoundException' in
Exception thrown: 'System.DllNotFoundException' in System.Private.CoreLib.dll
Exception thrown: 'System.DllNotFoundException' in System.Private.CoreLib.dll
Cos as u see TV try to use Tizen WebView library... not Xamarin forms library... at HowToUse.cs: 8
Tizen web wiew i can only use in Elm- Sharp beta as described here https://docs.tizen.org/application/dotnet/guides/connectivity/webview
But i casnt initialaze chromium - its not found too... what should i use to create webview?
Tizen TV is not support webview
Cool ) Have tried find where it has been described...
Next -> Oh! I will use Oauth2 natively! .... Hahahah - Deprecated!
"PushAsync is not supported globally on Tizen, please use a NavigationPage."
use PushModalAsync istead...
Anyway, Great solution - Thanx!