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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<MicrosoftAspNetCoreIdentityUIPackageVersion>7.0.1</MicrosoftAspNetCoreIdentityUIPackageVersion>
</PropertyGroup>
<PropertyGroup>
<VersionPrefix>7.0.1</VersionPrefix>
<VersionPrefix>7.0.2</VersionPrefix>
<PreReleaseVersionLabel>rtm</PreReleaseVersionLabel>
<IncludeSourceRevisionInInformationalVersion>False</IncludeSourceRevisionInInformationalVersion>
<IsServicingBuild Condition="'$(PreReleaseVersionLabel)' == 'servicing'">true</IsServicingBuild>
Expand Down
2 changes: 1 addition & 1 deletion scripts/install-aspnet-codegenerator.cmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set VERSION=7.0.0-dev
set VERSION=8.0.0-dev
set DEFAULT_NUPKG_PATH=%userprofile%\.nuget\packages
set SRC_DIR=%cd%
set NUPKG=artifacts/packages/Debug/Shipping/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,65 +84,106 @@ private async Task<IPublicClientApplication> GetOrCreateApp()
public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
{
var app = await GetOrCreateApp();
AuthenticationResult? result = null;
var accounts = await app.GetAccountsAsync()!;
IAccount? account;
IAccount? account = string.IsNullOrEmpty(Username)
? accounts.FirstOrDefault()
: accounts.FirstOrDefault(account => string.Equals(account.Username, Username, StringComparison.OrdinalIgnoreCase));

if (!string.IsNullOrEmpty(Username))
{
account = accounts.FirstOrDefault(account => account.Username == Username);
}
else
AuthenticationResult? result = account is null
? await GetAuthenticationWithoutAccount(requestContext.Scopes, app, cancellationToken)
: await GetAuthenticationWithAccount(requestContext.Scopes, app, account, cancellationToken);

if (result is null || result.AccessToken is null)
{
account = accounts.FirstOrDefault();
_consoleLogger.LogFailureAndExit(Resources.FailedToAcquireToken);
}

// Note: In the future, the token type *could* be POP instead of Bearer
return new AccessToken(result!.AccessToken!, result.ExpiresOn);
}

private async Task<AuthenticationResult?> GetAuthenticationWithAccount(string[] scopes, IPublicClientApplication app, IAccount? account, CancellationToken cancellationToken)
{
AuthenticationResult? result = null;
try
{
result = await app.AcquireTokenSilent(requestContext.Scopes, account)
result = await app.AcquireTokenSilent(scopes, account)
.WithAuthority(Instance, TenantId)
.ExecuteAsync(cancellationToken);
}
catch (MsalUiRequiredException ex)
{
if (account == null && !string.IsNullOrEmpty(Username))
try
{
_consoleLogger.LogFailureAndExit(
$"No valid tokens found in the cache.\n" +
$"Please sign-in to Visual Studio with this account: {Username}.\n\n" +
$"After signing-in, re-run the tool.");
result = await app.AcquireTokenInteractive(scopes)
.WithAccount(account)
.WithClaims(ex.Claims)
.WithAuthority(Instance, TenantId)
.WithUseEmbeddedWebView(false)
.ExecuteAsync(cancellationToken);
}
catch (Exception e)
{
_consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, e.Message));
}
result = await app.AcquireTokenInteractive(requestContext.Scopes)
.WithAccount(account)
.WithClaims(ex.Claims)
.WithAuthority(Instance, TenantId)
.ExecuteAsync(cancellationToken);
}
catch (MsalServiceException ex)
{
// AAD error codes: https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes
if (ex.Message.Contains("AADSTS70002")) // "The client does not exist or is not enabled for consumers"
{
// We want to exit here because this is probably an MSA without an AAD tenant.
_consoleLogger.LogFailureAndExit(
"An Azure AD tenant, and a user in that tenant, " +
"needs to be created for this account before an application can be created. " +
"See https://aka.ms/ms-identity-app/create-a-tenant. ");
}
var errorMessage = ex.Message.Contains("AADSTS70002") // "The client does not exist or is not enabled for consumers"
? Resources.ClientDoesNotExist
: string.Join(Environment.NewLine, Resources.SignInError, ex.Message);

// we want to exit here. Re-sign in will not resolve the issue.
_consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, ex.Message));
_consoleLogger.LogFailureAndExit(errorMessage);
}
catch (Exception ex)
{
_consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, ex.Message));
}

if (result is null)
return result;
}

/// <summary>
/// If there are no matching accounts in the msal cache, we need to make a call to AcquireTokenInteractive in order to populate the cache.
/// </summary>
/// <param name="requestContext"></param>
/// <param name="app"></param>
/// <param name="result"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<AuthenticationResult?> GetAuthenticationWithoutAccount(string[] scopes, IPublicClientApplication app, CancellationToken cancellationToken)
{
AuthenticationResult? result = null;
try
{
_consoleLogger.LogFailureAndExit(Resources.FailedToAcquireToken);
result = await app.AcquireTokenInteractive(scopes)
.WithAuthority(Instance, TenantId)
.WithUseEmbeddedWebView(false)
.ExecuteAsync(cancellationToken);
}
catch (MsalUiRequiredException ex) // Need to get Claims, hence the nested try/catch
{
try
{
result = await app.AcquireTokenInteractive(scopes)
.WithClaims(ex.Claims)
.WithAuthority(Instance, TenantId)
.WithUseEmbeddedWebView(false)
.ExecuteAsync(cancellationToken);
}
catch (Exception e)
{
_consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, e.Message));
}
}
catch (Exception e)
{
_consoleLogger.LogFailureAndExit(string.Join(Environment.NewLine, Resources.SignInError, e.Message));
}

return new AccessToken(result!.AccessToken, result.ExpiresOn);
return result;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
<data name="AuthNotEnabled" xml:space="preserve">
<value>Authentication is not enabled yet in this project. An app registration will be created, but the tool does not add the code yet (work in progress).</value>
</data>
<data name="ClientDoesNotExist" xml:space="preserve">
<value>An Azure AD tenant, and a user in that tenant, needs to be created for this account before an application can be created. See https://aka.ms/ms-identity-app/create-a-tenant.</value>
</data>
<data name="ClientSecret" xml:space="preserve">
<value>Client secret - {0}.</value>
</data>
Expand Down
28 changes: 18 additions & 10 deletions src/Scaffolding/VS.Web.CG.EFCore/ConnectionStringsWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

using System;
using System.IO;
using Microsoft.DotNet.Scaffolding.Shared;
using Microsoft.VisualStudio.Web.CodeGeneration.DotNet;
using Newtonsoft.Json.Linq;

namespace Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore
{
public class ConnectionStringsWriter : IConnectionStringsWriter
{
private const string SQLConnectionStringFormat = "Server=(localdb)\\mssqllocaldb;Database={0};Trusted_Connection=True;MultipleActiveResultSets=true";
private const string SQLiteConnectionStringFormat = "Data Source={0}.db";

private IApplicationInfo _applicationInfo;
private IFileSystem _fileSystem;

Expand All @@ -30,6 +28,11 @@ internal ConnectionStringsWriter(
}

public void AddConnectionString(string connectionStringName, string dataBaseName, bool useSqlite)
{
AddConnectionString(connectionStringName, dataBaseName, useSqlite ? DbProvider.SQLite : DbProvider.SqlServer);
}

public void AddConnectionString(string connectionStringName, string databaseName, DbProvider databaseProvider)
{
var appSettingsFile = Path.Combine(_applicationInfo.ApplicationBasePath, "appsettings.json");
JObject content;
Expand All @@ -55,13 +58,18 @@ public void AddConnectionString(string connectionStringName, string dataBaseName

if (content[connectionStringNodeName][connectionStringName] == null)
{
var connectionString = string.Format(
useSqlite ? SQLiteConnectionStringFormat : SQLConnectionStringFormat,
dataBaseName);
writeContent = true;
content[connectionStringNodeName][connectionStringName] = connectionString;
if (EfConstants.ConnectionStringsDict.TryGetValue(databaseProvider, out var connectionString))
{
if (!databaseProvider.Equals(DbProvider.CosmosDb))
{
connectionString = string.Format(connectionString, databaseName);
}

writeContent = true;
content[connectionStringNodeName][connectionStringName] = connectionString;
}
}

// Json.Net loses comments so the above code if requires any changes loses
// comments in the file. The writeContent bool is for saving
// a specific case without losing comments - when no changes are needed.
Expand All @@ -71,4 +79,4 @@ public void AddConnectionString(string connectionStringName, string dataBaseName
}
}
}
}
}
Loading