From 8233c5c1cd7094af861d552257618a8abd157896 Mon Sep 17 00:00:00 2001 From: Matthew Asplund Date: Wed, 19 Jul 2023 12:03:17 -0500 Subject: [PATCH] Switch to using new identity server auth flow (#212) --- Docs/Samples/C/Build-Extension.md | 2 +- Docs/Samples/C/Console-Application.md | 2 +- Docs/Samples/C/Dynamic-Library.md | 2 +- Docs/Samples/C/Static-Library.md | 2 +- Docs/Samples/C/Windows-Application.md | 2 +- Samples/Cpp/DirectX/PackageLock.sml | 2 +- Scripts/Windows/build-packagemanager.cmd | 4 +- Scripts/Windows/soup.cmd | 2 +- Scripts/Windows/soupd.cmd | 2 +- .../PackageManager/AuthenticationManager.cs | 69 +++--- .../Properties/launchSettings.json | 4 +- .../Soup.Build.PackageManager.csproj | 8 +- .../PackageManager/SystemBrowser.cs | 204 ++++++++++++++++++ .../PackageManager/TokenCache.cs | 65 ------ 14 files changed, 244 insertions(+), 126 deletions(-) create mode 100644 Source/GenerateSharp/PackageManager/SystemBrowser.cs delete mode 100644 Source/GenerateSharp/PackageManager/TokenCache.cs diff --git a/Docs/Samples/C/Build-Extension.md b/Docs/Samples/C/Build-Extension.md index a1332b57c..bc61729c0 100644 --- a/Docs/Samples/C/Build-Extension.md +++ b/Docs/Samples/C/Build-Extension.md @@ -1,4 +1,4 @@ -# C++ Build Extension +# C Build Extension This is a console application that has a custom build extension that alters the build state. The custom build Tasks will run before and after the core Build Task and will simply print a nice hello message. [Source](https://github.com/SoupBuild/Soup/tree/main/Samples/C/BuildExtension) diff --git a/Docs/Samples/C/Console-Application.md b/Docs/Samples/C/Console-Application.md index c654a80fb..1f502ee2d 100644 --- a/Docs/Samples/C/Console-Application.md +++ b/Docs/Samples/C/Console-Application.md @@ -1,4 +1,4 @@ -# C++ Console Application +# C Console Application This is the smallest amount of code to get a console application building using Soup. [Source](https://github.com/SoupBuild/Soup/tree/main/Samples/C/ConsoleApplication) diff --git a/Docs/Samples/C/Dynamic-Library.md b/Docs/Samples/C/Dynamic-Library.md index eee9df9f1..e47c84446 100644 --- a/Docs/Samples/C/Dynamic-Library.md +++ b/Docs/Samples/C/Dynamic-Library.md @@ -1,4 +1,4 @@ -# C++ Dynamic Library +# C Dynamic Library This is a console application that has a single dynamic library dependency. [Source](https://github.com/SoupBuild/Soup/tree/main/Samples/C/DynamicLibrary) diff --git a/Docs/Samples/C/Static-Library.md b/Docs/Samples/C/Static-Library.md index 343cd2fa1..6557ec96d 100644 --- a/Docs/Samples/C/Static-Library.md +++ b/Docs/Samples/C/Static-Library.md @@ -1,4 +1,4 @@ -# Static Library Reference +# C Static Library Reference This is a console application that has a single static library dependency. [Source](https://github.com/SoupBuild/Soup/tree/main/Samples/C/StaticLibrary) diff --git a/Docs/Samples/C/Windows-Application.md b/Docs/Samples/C/Windows-Application.md index b7dbcb5b4..fd29240bd 100644 --- a/Docs/Samples/C/Windows-Application.md +++ b/Docs/Samples/C/Windows-Application.md @@ -1,4 +1,4 @@ -# C++ Windows Application +# C Windows Application This is a windows application that demonstrates creating a GUI windows application. [Source](https://github.com/SoupBuild/Soup/tree/main/Samples/C/WindowsApplication) diff --git a/Samples/Cpp/DirectX/PackageLock.sml b/Samples/Cpp/DirectX/PackageLock.sml index a9ce487af..527998f28 100644 --- a/Samples/Cpp/DirectX/PackageLock.sml +++ b/Samples/Cpp/DirectX/PackageLock.sml @@ -2,7 +2,7 @@ Version: 4 Closures: { Root: { "C++": [ - { Name: "Samples.Cpp.DirectX", Version: "./", Build: "Build0", Tool: "Tool0" } + { Name: "Samples.Cpp.DirectX", Version: "../DirectX", Build: "Build0", Tool: "Tool0" } ] } Build0: { diff --git a/Scripts/Windows/build-packagemanager.cmd b/Scripts/Windows/build-packagemanager.cmd index 5e0868da7..c2fe7c7f5 100644 --- a/Scripts/Windows/build-packagemanager.cmd +++ b/Scripts/Windows/build-packagemanager.cmd @@ -7,6 +7,6 @@ SET SourceDir=%RootDir%\Source SET PackageManagerDir=%SourceDir%\GenerateSharp\PackageManager REM - Build PackageManager -echo dotnet publish %PackageManagerDir% -c %Flavor% -f net7.0-windows10.0.17763.0 -r win-x64 --self-contained -call dotnet publish %PackageManagerDir% -c %Flavor% -f net7.0-windows10.0.17763.0 -r win-x64 --self-contained +echo dotnet publish %PackageManagerDir% -c %Flavor% -f net7.0 -r win-x64 --self-contained +call dotnet publish %PackageManagerDir% -c %Flavor% -f net7.0 -r win-x64 --self-contained if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% diff --git a/Scripts/Windows/soup.cmd b/Scripts/Windows/soup.cmd index 9328084a3..b93a30463 100644 --- a/Scripts/Windows/soup.cmd +++ b/Scripts/Windows/soup.cmd @@ -36,7 +36,7 @@ robocopy %GlobalOutDir%\Wren\Soup.CSharp\%SOUP_CSHARP_VERSION%\%ConfigHash%\ %Ru robocopy %GlobalPackagesDir%\Wren\Soup.Wren\%SOUP_WREN_VERSION%\ %RunDir%\Soup\BuiltIn\Soup.Wren\%SOUP_WREN_VERSION%\ Recipe.sml /NJH /NJS /NDL > NUL robocopy %GlobalOutDir%\Wren\Soup.Wren\%SOUP_WREN_VERSION%\%ConfigHash%\ %RunDir%\Soup\BuiltIn\Soup.Wren\%SOUP_WREN_VERSION%\out\ /MIR /NJH /NJS /NDL > NUL -robocopy %OutDir%\msbuild\bin\Soup.Build.PackageManager\Release\net7.0-windows10.0.17763.0\win-x64\publish\ %RunDir%\Soup\PackageManager\ /MIR /NJH /NJS /NDL > NUL +robocopy %OutDir%\msbuild\bin\Soup.Build.PackageManager\Release\net7.0\win-x64\publish\ %RunDir%\Soup\PackageManager\ /MIR /NJH /NJS /NDL > NUL robocopy %OutDir%\msbuild\bin\Swhere\Release\net7.0\win-x64\publish\ %RunDir%\ swhere.exe /NJH /NJS /NDL > NUL diff --git a/Scripts/Windows/soupd.cmd b/Scripts/Windows/soupd.cmd index 57c1f5a16..289d47d30 100644 --- a/Scripts/Windows/soupd.cmd +++ b/Scripts/Windows/soupd.cmd @@ -36,7 +36,7 @@ robocopy %GlobalOutDir%\Wren\Soup.CSharp\%SOUP_CSHARP_VERSION%\%ConfigHash%\ %Ru robocopy %GlobalPackagesDir%\Wren\Soup.Wren\%SOUP_WREN_VERSION%\ %RunDir%\Soup\BuiltIn\Soup.Wren\%SOUP_WREN_VERSION%\ Recipe.sml /NJH /NJS /NDL > NUL robocopy %GlobalOutDir%\Wren\Soup.Wren\%SOUP_WREN_VERSION%\%ConfigHash%\ %RunDir%\Soup\BuiltIn\Soup.Wren\%SOUP_WREN_VERSION%\out\ /MIR /NJH /NJS /NDL > NUL -robocopy %OutDir%\msbuild\bin\Soup.Build.PackageManager\Debug\net7.0-windows10.0.17763.0\win-x64\publish\ %RunDir%\Soup\PackageManager\ /MIR /NJH /NJS /NDL > NUL +robocopy %OutDir%\msbuild\bin\Soup.Build.PackageManager\Debug\net7.0\win-x64\publish\ %RunDir%\Soup\PackageManager\ /MIR /NJH /NJS /NDL > NUL robocopy %OutDir%\msbuild\bin\Swhere\Debug\net7.0\win-x64\publish\ %RunDir%\ swhere.exe /NJH /NJS /NDL > NUL %RunDir%\Soup.cmd %* \ No newline at end of file diff --git a/Source/GenerateSharp/PackageManager/AuthenticationManager.cs b/Source/GenerateSharp/PackageManager/AuthenticationManager.cs index 948354392..e0713b9ba 100644 --- a/Source/GenerateSharp/PackageManager/AuthenticationManager.cs +++ b/Source/GenerateSharp/PackageManager/AuthenticationManager.cs @@ -4,26 +4,13 @@ namespace Soup.Build.PackageManager { - using System.Linq; using System.Threading.Tasks; - using Microsoft.Identity.Client; - using Opal; + using IdentityModel.OidcClient; internal class AuthenticationManager : IAuthenticationManager { - private static readonly string Tenant = $"soupbuild.com"; - private static readonly string AzureAdB2CHostname = $"soupbuild.b2clogin.com"; - private static readonly string ClientId = "29b9e45c-332b-4f93-a41f-af525dee4730"; - private static readonly string SignUpSignInPolicyId = "B2C_1_SignUp_SignIn"; - private static readonly string SoupApiScope = "/ba178231-c318-435d-881a-25f9e00df20a/soup_build_api"; - - private static readonly string Scope = $"https://{Tenant}{SoupApiScope}"; - - private static string RedirectUri => $"https://{AzureAdB2CHostname}/oauth2/nativeclient"; - - private static string AuthorityBase => $"https://{AzureAdB2CHostname}/tfp/{Tenant}/"; - - public static string SignUpSignInAuthority => $"{AuthorityBase}{SignUpSignInPolicyId}"; + static string _authority = "https://auth.soupbuild.com/"; + // static string _authority = "https://localhost:5001/"; /// /// Ensure the user is logged in @@ -31,38 +18,30 @@ internal class AuthenticationManager : IAuthenticationManager /// The access token public async Task EnsureSignInAsync() { - IPublicClientApplication publicClientApp = PublicClientApplicationBuilder.Create(ClientId) - .WithB2CAuthority(SignUpSignInAuthority) - .WithRedirectUri(RedirectUri) - .WithLogging(PublicClientLog, LogLevel.Info, false) - .Build(); - - TokenCache.Bind(publicClientApp.UserTokenCache); - - AuthenticationResult authResult; - var scopes = new string[] { Scope }; - - try - { - // Attempt to silently acquire the user token - var accounts = await publicClientApp.GetAccountsAsync(SignUpSignInPolicyId); - authResult = await publicClientApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) - .ExecuteAsync(); - } - catch (MsalUiRequiredException) - { - // Ignore, user will need to sign in interactively. - authResult = await publicClientApp.AcquireTokenInteractive(scopes) - .ExecuteAsync(); - } - - return authResult.AccessToken; + var token = await Login(); + return token; } - private static void PublicClientLog(LogLevel level, string message, bool containsPii) + private async Task Login() { - string logs = $"{level} {message}"; - Log.Diag(logs); + // Create a redirect URI using an available port on the loopback address + var browser = new SystemBrowser(); + string redirectUri = string.Format($"http://127.0.0.1:{browser.Port}"); + + var options = new OidcClientOptions() + { + Authority = _authority, + ClientId = "Soup.Native", + RedirectUri = redirectUri, + Scope = "openid profile soup_api", + FilterClaims = false, + Browser = browser, + }; + + var oidcClient = new OidcClient(options); + var result = await oidcClient.LoginAsync(new LoginRequest()); + + return result.AccessToken; } } } diff --git a/Source/GenerateSharp/PackageManager/Properties/launchSettings.json b/Source/GenerateSharp/PackageManager/Properties/launchSettings.json index cecc28989..44be50f03 100644 --- a/Source/GenerateSharp/PackageManager/Properties/launchSettings.json +++ b/Source/GenerateSharp/PackageManager/Properties/launchSettings.json @@ -2,8 +2,8 @@ "profiles": { "Soup.Build.PackageManager": { "commandName": "Project", - "commandLineArgs": "restore-packages [TEST_DIR]", - "commandLineArgs2": "publish-package [TEST_DIR]" + "commandLineArgs2": "restore-packages [TEST_DIR]", + "commandLineArgs": "publish-package C:/Users/mwasp/Dev/Repos/Soup/Source/GenerateSharp/Opal" } } } \ No newline at end of file diff --git a/Source/GenerateSharp/PackageManager/Soup.Build.PackageManager.csproj b/Source/GenerateSharp/PackageManager/Soup.Build.PackageManager.csproj index f6bd4836d..fffcfc039 100644 --- a/Source/GenerateSharp/PackageManager/Soup.Build.PackageManager.csproj +++ b/Source/GenerateSharp/PackageManager/Soup.Build.PackageManager.csproj @@ -1,7 +1,7 @@  Exe - net7.0;net7.0-windows10.0.17763.0 + net7.0 enable true true @@ -9,11 +9,11 @@ false - - + + + - diff --git a/Source/GenerateSharp/PackageManager/SystemBrowser.cs b/Source/GenerateSharp/PackageManager/SystemBrowser.cs new file mode 100644 index 000000000..fcab1abd3 --- /dev/null +++ b/Source/GenerateSharp/PackageManager/SystemBrowser.cs @@ -0,0 +1,204 @@ +// +// Copyright (c) Soup. All rights reserved. +// InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var listener = new LoopbackHttpListener(Port, _path)) + { + OpenBrowser(options.StartUrl); + + try + { + var result = await listener.WaitForCallbackAsync(); + if (String.IsNullOrWhiteSpace(result)) + { + return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = "Empty response." }; + } + + return new BrowserResult { Response = result, ResultType = BrowserResultType.Success }; + } + catch (TaskCanceledException ex) + { + return new BrowserResult { ResultType = BrowserResultType.Timeout, Error = ex.Message }; + } + catch (Exception ex) + { + return new BrowserResult { ResultType = BrowserResultType.UnknownError, Error = ex.Message }; + } + } + } + + public static void OpenBrowser(string url) + { + try + { + Process.Start(url); + } + catch + { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + url = url.Replace("&", "^&"); + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + else + { + throw; + } + } + } + } + + public class LoopbackHttpListener : IDisposable + { + const int DefaultTimeout = 60 * 5; // 5 mins (in seconds) + + IWebHost _host; + TaskCompletionSource _source = new TaskCompletionSource(); + string _url; + + public string Url => _url; + + public LoopbackHttpListener(int port, string? path = null) + { + path = path ?? String.Empty; + if (path.StartsWith("/")) path = path.Substring(1); + + _url = $"http://127.0.0.1:{port}/{path}"; + + _host = new WebHostBuilder() + .UseKestrel() + .UseUrls(_url) + .Configure(Configure) + .Build(); + _host.Start(); + } + + public void Dispose() + { + Task.Run(async () => + { + await Task.Delay(500); + _host.Dispose(); + }); + } + + void Configure(IApplicationBuilder app) + { + app.Run(async ctx => + { + if (ctx.Request.Method == "GET") + { + await SetResultAsync(ctx.Request.QueryString.Value ?? string.Empty, ctx); + } + else if (ctx.Request.Method == "POST") + { + if (ctx.Request.ContentType is not null && + !ctx.Request.ContentType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) + { + ctx.Response.StatusCode = 415; + } + else + { + using (var sr = new StreamReader(ctx.Request.Body, Encoding.UTF8)) + { + var body = await sr.ReadToEndAsync(); + await SetResultAsync(body, ctx); + } + } + } + else + { + ctx.Response.StatusCode = 405; + } + }); + } + + private async Task SetResultAsync(string value, HttpContext ctx) + { + try + { + ctx.Response.StatusCode = 200; + ctx.Response.ContentType = "text/html"; + await ctx.Response.WriteAsync("

You can now return to the application.

"); + await ctx.Response.Body.FlushAsync(); + + _source.TrySetResult(value); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + + ctx.Response.StatusCode = 400; + ctx.Response.ContentType = "text/html"; + await ctx.Response.WriteAsync("

Invalid request.

"); + await ctx.Response.Body.FlushAsync(); + } + } + + public Task WaitForCallbackAsync(int timeoutInSeconds = DefaultTimeout) + { + Task.Run(async () => + { + await Task.Delay(timeoutInSeconds * 1000); + _source.TrySetCanceled(); + }); + + return _source.Task; + } + } +} \ No newline at end of file diff --git a/Source/GenerateSharp/PackageManager/TokenCache.cs b/Source/GenerateSharp/PackageManager/TokenCache.cs deleted file mode 100644 index 1087e7e7e..000000000 --- a/Source/GenerateSharp/PackageManager/TokenCache.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) Soup. All rights reserved. -// - -namespace Soup.Build.PackageManager -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Reflection; - using System.Security.Cryptography; - using Microsoft.Identity.Client; - - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Will investigate ASP.Net Data Protection when supporting more platforms.")] - internal static class TokenCache - { - /// - /// Path to the token cache - /// - public static readonly string CacheFolderPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "SoupBuild"); - public static readonly string CacheFilePath = Path.Combine( - CacheFolderPath, - "msal.cache"); - - private static readonly object FileLock = new object(); - - public static void BeforeAccessNotification(TokenCacheNotificationArgs args) - { - lock (FileLock) - { - args.TokenCache.DeserializeMsalV3( - File.Exists(CacheFilePath) - ? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath), null, DataProtectionScope.CurrentUser) - : null); - } - } - - public static void AfterAccessNotification(TokenCacheNotificationArgs args) - { - // if the access operation resulted in a cache update - if (args.HasStateChanged) - { - lock (FileLock) - { - // Ensure the directory exists - if (!Directory.Exists(CacheFolderPath)) - Directory.CreateDirectory(CacheFolderPath); - - // reflect changes in the persistent store - File.WriteAllBytes( - CacheFilePath, - ProtectedData.Protect(args.TokenCache.SerializeMsalV3(), null, DataProtectionScope.CurrentUser)); - } - } - } - - public static void Bind(ITokenCache tokenCache) - { - tokenCache.SetBeforeAccess(BeforeAccessNotification); - tokenCache.SetAfterAccess(AfterAccessNotification); - } - } -}