138 строки
5.3 KiB
C#
138 строки
5.3 KiB
C#
/**** Git Credential Manager for Windows ****
|
|
*
|
|
* Copyright (c) GitHub Corporation
|
|
* All rights reserved.
|
|
*
|
|
* MIT License
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the """"Software""""), to deal
|
|
* in the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
|
**/
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using GitHub.Shared.Controls;
|
|
using GitHub.Shared.ViewModels;
|
|
using Microsoft.Alm.Authentication;
|
|
|
|
namespace Atlassian.Bitbucket.Authentication
|
|
{
|
|
public interface IGui : IRuntimeService
|
|
{
|
|
/// <summary>
|
|
/// Presents the user with `<paramref name="windowCreator"/>` with the `<paramref name="viewModel"/>`.
|
|
/// <para/>
|
|
/// Returns `<see langword="true"/>` if the user completed the dialog; otherwise `<see langword="false"/>` if the user canceled or abandoned the dialog.
|
|
/// </summary>
|
|
/// <param name="viewModel">The view model passed to the presented window.</param>
|
|
/// <param name="windowCreator">Creates the window `<paramref name="viewModel"/>` is passed to.</param>
|
|
bool ShowViewModel(DialogViewModel viewModel, Func<AuthenticationDialogWindow> windowCreator);
|
|
}
|
|
|
|
internal class Gui : Microsoft.Alm.Authentication.Base, IGui
|
|
{
|
|
public Gui(RuntimeContext context)
|
|
: base(context)
|
|
{ }
|
|
|
|
public Type ServiceType
|
|
=> typeof(IGui);
|
|
|
|
public bool ShowViewModel(DialogViewModel viewModel, Func<AuthenticationDialogWindow> windowCreator)
|
|
{
|
|
StartSTATask(() =>
|
|
{
|
|
EnsureApplicationResources();
|
|
|
|
var window = windowCreator();
|
|
|
|
window.DataContext = viewModel;
|
|
|
|
window.ShowDialog();
|
|
})
|
|
.Wait();
|
|
|
|
return viewModel.Result == AuthenticationDialogResult.Ok
|
|
&& viewModel.IsValid;
|
|
}
|
|
|
|
private static void EnsureApplicationResources()
|
|
{
|
|
if (!UriParser.IsKnownScheme("pack"))
|
|
{
|
|
UriParser.Register(new GenericUriParser(GenericUriParserOptions.GenericAuthority), "pack", -1);
|
|
}
|
|
|
|
var appResourcesUri = new Uri("pack://application:,,,/Bitbucket.Authentication;component/AppResources.xaml", UriKind.RelativeOrAbsolute);
|
|
|
|
// If we launch two dialogs in the same process (Credential followed by 2fa), calling new
|
|
// App() throws an exception stating the Application class can't be created twice.
|
|
// Creating an App instance happens to set Application.Current to that instance (it's
|
|
// weird). However, if you don't set the ShutdownMode to OnExplicitShutdown, the second
|
|
// time you launch a dialog, Application.Current is null even in the same process.
|
|
if (Application.Current == null)
|
|
{
|
|
var app = new Application();
|
|
|
|
Debug.Assert(Application.Current == app, "Current application not set");
|
|
|
|
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
|
|
|
app.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = appResourcesUri });
|
|
}
|
|
else
|
|
{
|
|
// Application.Current exists, but what if in the future, some other code created the
|
|
// singleton. Let's make sure our resources are still loaded.
|
|
var resourcesExist = Application.Current.Resources.MergedDictionaries.Any(r => r.Source == appResourcesUri);
|
|
|
|
if (!resourcesExist)
|
|
{
|
|
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = appResourcesUri });
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Task StartSTATask(Action action)
|
|
{
|
|
var completionSource = new TaskCompletionSource<object>();
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
completionSource.SetResult(null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
completionSource.SetException(e);
|
|
}
|
|
});
|
|
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
|
|
thread.Start();
|
|
|
|
return completionSource.Task;
|
|
}
|
|
}
|
|
}
|