Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
bgavrilMS committed Sep 12, 2024
2 parents e514268 + 7c663df commit 4cb3b57
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 123 deletions.
12 changes: 11 additions & 1 deletion src/client/Microsoft.Identity.Client.Broker/WamAdapters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ public static NativeInterop.AuthParameters GetCommonAuthParameters(
{
authParams.Properties["instance_aware"] = "true";
}

//SSH Cert
if(authenticationRequestParameters.AuthenticationScheme.AccessTokenType == "ssh-cert")
{
authParams.Properties["key_id"]= authenticationRequestParameters.AuthenticationScheme.KeyId;
foreach (KeyValuePair<string, string> kvp in authenticationRequestParameters.AuthenticationScheme.GetTokenRequestParams())
{
authParams.Properties[kvp.Key] = kvp.Value;
}
}

//pass extra query parameters if there are any
if (authenticationRequestParameters.ExtraQueryParameters != null)
Expand Down Expand Up @@ -352,7 +362,7 @@ private static MsalTokenResponse ParseRuntimeResponse(
Scope = authResult.GrantedScopes,
ExpiresIn = (long)(DateTime.SpecifyKind(authResult.ExpiresOn, DateTimeKind.Utc) - DateTimeOffset.UtcNow).TotalSeconds,
ClientInfo = authResult.Account.ClientInfo,
TokenType = authResult.IsPopAuthorization ? Constants.PoPAuthHeaderPrefix : BrokerResponseConst.Bearer,
TokenType = authResult.IsPopAuthorization ? Constants.PoPAuthHeaderPrefix: authenticationRequestParameters.RequestContext.ApiEvent.TokenType.ToString(),
WamAccountId = authResult.Account.AccountId,
TokenSource = TokenSource.Broker
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
namespace Microsoft.Identity.Client
{
/// <summary>
/// Parameter builder for the <see cref="IConfidentialClientApplication.AcquireTokenByUsernamePassword(IEnumerable{string}, string, string)"/>
/// Parameter builder for the <see cref="IByUsernameAndPassword.AcquireTokenByUsernamePassword(IEnumerable{string}, string, string)"/>
/// operation. See https://aka.ms/msal-net-up
/// </summary>
public sealed class AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder :
Expand Down Expand Up @@ -67,5 +67,16 @@ internal override ApiEvent.ApiIds CalculateApiEventId()
{
return ApiEvent.ApiIds.AcquireTokenByUsernamePassword;
}

/// <inheritdoc/>
protected override void Validate()
{
base.Validate();

if (Parameters.SendX5C == null)
{
Parameters.SendX5C = ServiceBundle.Config?.SendX5C ?? false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ public async Task<AuthenticationResult> ExecuteAsync(
commonParameters,
requestContext,
_confidentialClientApplication.UserTokenCacheInternal).ConfigureAwait(false);

requestParams.SendX5C = usernamePasswordParameters.SendX5C ?? false;

var handler = new UsernamePasswordRequest(
ServiceBundle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,5 @@ internal abstract class AbstractAcquireTokenConfidentialClientParameters
/// This overrides application config settings.
/// </summary>
public bool? SendX5C { get; set; }

/// <summary>
/// if <c>true</c> then Spa code param will be sent via AcquireTokenByAuthorizeCode
/// </summary>
public bool SpaCode { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ internal class AcquireTokenByAuthorizationCodeParameters : AbstractAcquireTokenC

public string PkceCodeVerifier { get; set; }

/// <summary>
/// if <c>true</c> then Spa code param will be sent via AcquireTokenByAuthorizeCode
/// </summary>
public bool SpaCode { get; set; }

public void LogParameters(ILoggerAdapter logger)
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
// Licensed under the MIT License.

using System.Security;
using System.Text;
using Microsoft.Identity.Client.Core;

namespace Microsoft.Identity.Client.ApiConfig.Parameters
{

internal class AcquireTokenByUsernamePasswordParameters : AbstractAcquireTokenByUsernameParameters, IAcquireTokenParameters
{
public string Password { get; set; }

public bool? SendX5C { get; set; } // CCA only

/// <inheritdoc/>
public void LogParameters(ILoggerAdapter logger)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.ApiConfig;
using Microsoft.Identity.Client.Broker;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.SSHCertificates;
using Microsoft.Identity.Client.UI;
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Test.Common;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.Identity.Test.Common.Core.Mocks;
Expand All @@ -35,6 +38,23 @@ public class RuntimeBrokerTests
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

//This client id is for Azure CLI which is one of the only 2 clients that have PreAuth to use ssh cert feature
string _SSH_ClientId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46";
//SSH User impersonation scope required for this test
private string[] _SSH_scopes = new[] { "https://pas.windows.net/CheckMyAccess/Linux/user_impersonation" };

private string CreateJwk()
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
RSAParameters rsaKeyInfo = rsa.ExportParameters(false);

string modulus = Base64UrlHelpers.Encode(rsaKeyInfo.Modulus);
string exp = Base64UrlHelpers.Encode(rsaKeyInfo.Exponent);
string jwk = $"{{\"kty\":\"RSA\", \"n\":\"{modulus}\", \"e\":\"{exp}\"}}";

return jwk;
}

// This test should fail locally but succeed in a CI build.
[IgnoreOnOneBranch]
[TestMethod]
Expand Down Expand Up @@ -223,6 +243,45 @@ await AssertException.TaskThrowsAsync<MsalUiRequiredException>(
.ConfigureAwait(false);
}

[IgnoreOnOneBranch]
[TestMethod]
public async Task WamWithSSHCertificateAuthenticationSchemeAsync()
{
IntPtr intPtr = GetForegroundWindow();
Func<IntPtr> windowHandleProvider = () => intPtr;
var labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false);

IPublicClientApplication pca = PublicClientApplicationBuilder
.Create(_SSH_ClientId)
.WithTestLogging()
.WithAuthority(labResponse.Lab.Authority, "organizations")
.WithParentActivityOrWindow(windowHandleProvider)
.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows))
.Build();

string jwk = CreateJwk();
//Do a login with username password
AuthenticationResult result = await pca
.AcquireTokenByUsernamePassword(_SSH_scopes, labResponse.User.Upn, labResponse.User.GetOrFetchPassword())
.ExecuteAsync()
.ConfigureAwait(false);

//Assert successful login
var accounts = await pca.GetAccountsAsync().ConfigureAwait(false);
Assert.IsNotNull(accounts);
var account = accounts.FirstOrDefault();
Assert.IsNotNull(account);

//Acquire token with SSH cert
result = await pca
.AcquireTokenSilent(_SSH_scopes, account)
.WithSSHCertificateAuthenticationScheme(jwk, "key1")
.ExecuteAsync()
.ConfigureAwait(false);

Assert.AreEqual("SshCert", result.TokenType);
}

[IgnoreOnOneBranch]
[TestMethod]
public async Task WamUsernamePasswordWithForceRefreshAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,22 @@ private async Task RunHappyPathTestAsync(LabResponse labResponse, string federat
else
{
IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(cloud);
clientApp = ConfidentialClientApplicationBuilder
var clientAppBuilder = ConfidentialClientApplicationBuilder
.Create(settings.ClientId)
.WithTestLogging()
.WithHttpClientFactory(factory)
.WithAuthority(labResponse.Lab.Authority, "organizations")
.WithClientSecret(settings.GetSecret())
.Build();
.WithAuthority(labResponse.Lab.Authority, "organizations");

if (cloud == Cloud.Arlington)
{
clientAppBuilder.WithClientSecret(settings.GetSecret());
}
else
{
clientAppBuilder.WithCertificate(settings.GetCertificate(), true);
}

clientApp = clientAppBuilder.Build();
}

AuthenticationResult authResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,41 @@ await app.AcquireTokenByAuthorizationCode(TestConstants.s_scope, TestConstants.D
}
}


// regression test for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4913
[DataTestMethod]
[DataRow(true)]
[DataRow(false)]
public async Task RopcCcaSendsX5CAsync(bool sendX5C)
{
using (var harness = CreateTestHarness())
{
var certificate = CertHelper.GetOrCreateTestCert();
var exportedCertificate = Convert.ToBase64String(certificate.Export(X509ContentType.Cert));

var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithHttpManager(harness.HttpManager)
.WithCertificate(certificate, sendX5C)
.Build();

harness.HttpManager.AddInstanceDiscoveryMockHandler();

harness.HttpManager.AddMockHandler(
CreateTokenResponseHttpHandlerWithX5CValidation(
clientCredentialFlow: false,
expectedX5C: sendX5C ? exportedCertificate: null));

var result = await (app as IByUsernameAndPassword)
.AcquireTokenByUsernamePassword(
TestConstants.s_scope,
TestConstants.Username,
TestConstants.DefaultPassword)
.ExecuteAsync()
.ConfigureAwait(false);
}
}

private static string ComputeCertThumbprint(X509Certificate2 certificate, bool useSha2)
{
string thumbprint = null;
Expand Down
Loading

0 comments on commit 4cb3b57

Please sign in to comment.