mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Fix email extraction from username (#2075)
This commit is contained in:
@@ -13,7 +13,7 @@ namespace Microsoft.SqlTools.Authentication
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides APIs to acquire access token using MSAL.NET v4 with provided <see cref="AuthenticationParams"/>.
|
/// Provides APIs to acquire access token using MSAL.NET v4 with provided <see cref="AuthenticationParams"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Authenticator: IAuthenticator
|
public class Authenticator : IAuthenticator
|
||||||
{
|
{
|
||||||
private AuthenticatorConfiguration configuration;
|
private AuthenticatorConfiguration configuration;
|
||||||
|
|
||||||
@@ -58,10 +58,27 @@ namespace Microsoft.SqlTools.Authentication
|
|||||||
IEnumerator<IAccount>? accounts = (await publicClientApplication.GetAccountsAsync().ConfigureAwait(false)).GetEnumerator();
|
IEnumerator<IAccount>? accounts = (await publicClientApplication.GetAccountsAsync().ConfigureAwait(false)).GetEnumerator();
|
||||||
IAccount? account = default;
|
IAccount? account = default;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(@params.UserName) && accounts.MoveNext())
|
if (!string.IsNullOrEmpty(@params.UserName))
|
||||||
{
|
{
|
||||||
// Handle username format to extract email: "John Doe - johndoe@constoso.com"
|
// Handle username format to extract email: "John Doe - johndoe@constoso.com" as received from ADS/VSCode-MSSQL
|
||||||
string username = @params.UserName.Contains(" - ") ? @params.UserName.Split(" - ")[1] : @params.UserName;
|
|
||||||
|
// Additional possible usernames to consider:
|
||||||
|
// John Doe (Role - Department) - johndoe@constoso.com
|
||||||
|
// John - Doe - johndoe@constoso.com
|
||||||
|
// John Doe - john-doe@constoso.com
|
||||||
|
// John Doe - john-doe@constoso.org-name.com
|
||||||
|
|
||||||
|
// A different way of implementing this is by sending user's email directly to STS in 'username' property but that would cause incompatibility
|
||||||
|
// with saved connection profiles and reading from settings.json, therefore this solution is used as of now.
|
||||||
|
|
||||||
|
string emailSeparator = " - ";
|
||||||
|
string username = @params.UserName;
|
||||||
|
if (username.Contains(emailSeparator))
|
||||||
|
{
|
||||||
|
int startIndex = username.LastIndexOf(emailSeparator) + emailSeparator.Length;
|
||||||
|
username = username.Substring(startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Utils.isValidEmail(username))
|
if (!Utils.isValidEmail(username))
|
||||||
{
|
{
|
||||||
SqlToolsLogger.Pii($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | Unexpected username format, email not retreived: {@params.UserName}. " +
|
SqlToolsLogger.Pii($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | Unexpected username format, email not retreived: {@params.UserName}. " +
|
||||||
@@ -69,33 +86,41 @@ namespace Microsoft.SqlTools.Authentication
|
|||||||
throw new Exception($"Invalid email address format for user: [{username}] received for Azure Active Directory authentication.");
|
throw new Exception($"Invalid email address format for user: [{username}] received for Azure Active Directory authentication.");
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
if (accounts.MoveNext())
|
||||||
{
|
{
|
||||||
IAccount? currentVal = accounts.Current;
|
do
|
||||||
if (string.Compare(username, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0)
|
|
||||||
{
|
{
|
||||||
account = currentVal;
|
IAccount? currentVal = accounts.Current;
|
||||||
SqlToolsLogger.Verbose($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | User account found in MSAL Cache: {account.HomeAccountId}");
|
if (string.Compare(username, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||||
break;
|
{
|
||||||
|
account = currentVal;
|
||||||
|
SqlToolsLogger.Verbose($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | User account found in MSAL Cache: {account.HomeAccountId}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
while (accounts.MoveNext());
|
||||||
while (accounts.MoveNext());
|
|
||||||
|
|
||||||
if (null != account)
|
if (null != account)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// Fetch token silently
|
try
|
||||||
var result = await publicClientApplication.AcquireTokenSilent(@params.Scopes, account)
|
{
|
||||||
.ExecuteAsync(cancellationToken: cancellationToken)
|
// Fetch token silently
|
||||||
.ConfigureAwait(false);
|
var result = await publicClientApplication.AcquireTokenSilent(@params.Scopes, account)
|
||||||
accessToken = new AccessToken(result!.AccessToken, result!.ExpiresOn);
|
.ExecuteAsync(cancellationToken: cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
accessToken = new AccessToken(result!.AccessToken, result!.ExpiresOn);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
SqlToolsLogger.Verbose($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | Silent authentication failed for resource {@params.Resource} for ConnectionId {@params.ConnectionId}.");
|
||||||
|
SqlToolsLogger.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
else
|
||||||
{
|
{
|
||||||
SqlToolsLogger.Verbose($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | Silent authentication failed for resource {@params.Resource} for ConnectionId {@params.ConnectionId}.");
|
SqlToolsLogger.Error($"{nameof(Authenticator)}.{nameof(GetTokenAsync)} | Account not found in MSAL cache for user.");
|
||||||
SqlToolsLogger.Error(e);
|
throw new Exception($"User account '{username}' not found in MSAL cache, please add linked account or refresh account credentials.");
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.Authentication;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Authentication
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class AuthenticatorTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
[TestCase("John Doe - johndoe@constoso.com", "johndoe@constoso.com")]
|
||||||
|
[TestCase("John Doe - john-doe@constoso.com", "john-doe@constoso.com")]
|
||||||
|
[TestCase("John Doe (Manager - Sales) - johndoe@constoso.com", "johndoe@constoso.com")]
|
||||||
|
[TestCase("John - Doe (Manager - Sales) - john-doe@constoso.com", "john-doe@constoso.com")]
|
||||||
|
[TestCase("John Doe - johndoe@constoso-sales.com", "johndoe@constoso-sales.com")]
|
||||||
|
[TestCase("johndoe@constoso.com", "johndoe@constoso.com")]
|
||||||
|
[TestCase("johndoe@constoso-sales.com", "johndoe@constoso-sales.com")]
|
||||||
|
public async Task GetTokenAsyncExtractsEmailSuccessfully(string username, string expectedEmail)
|
||||||
|
{
|
||||||
|
Authenticator authenticator = new Authenticator(new SqlTools.Authentication.Utility.AuthenticatorConfiguration(
|
||||||
|
Guid.NewGuid().ToString(), "AppName", ".", "dummyCacheFile"), () => ("key", "iv"));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await authenticator.GetTokenAsync(new AuthenticationParams(AuthenticationMethod.ActiveDirectoryInteractive,
|
||||||
|
"https://login.microsoftonline.com/",
|
||||||
|
"common",
|
||||||
|
"https://database.windows.net/",
|
||||||
|
new string[] {
|
||||||
|
"https://database.windows.net/.default"
|
||||||
|
},
|
||||||
|
username,
|
||||||
|
Guid.Empty),
|
||||||
|
CancellationToken.None);
|
||||||
|
Assert.Fail("Expected exception did not occur.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Assert.False(e.Message.StartsWith("Invalid email address format", StringComparison.OrdinalIgnoreCase), $"Email address format should be correct, message received: {e.Message}");
|
||||||
|
Assert.True(e.Message.Contains($"User account '{expectedEmail}' not found in MSAL cache, please add linked account or refresh account credentials."), $"Expected error did not occur, message received: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user