Skip to content

Commit

Permalink
Add extensibility to update parameters for ROPC flow (#3130)
Browse files Browse the repository at this point in the history
* Add extension to update parameters for ROPC flow

* Update test to validate params added in extension

* Missed to commit the line

* Update the name to BeforeTokenAcquisitionForTestUser

* Try to resolve the warning
  • Loading branch information
neha-bhargava authored Nov 11, 2024
1 parent 29761d2 commit 7c7fab6
Show file tree
Hide file tree
Showing 20 changed files with 143 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForTestUser(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Web.TokenAcquisitionExtensionOptions!>?
static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string!
const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string!
Microsoft.Identity.Web.BeforeTokenAcquisitionForApp
Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForTestUser -> Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.TokenAcquisitionExtensionOptions() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForTestUser(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Web.TokenAcquisitionExtensionOptions!>?
static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string!
const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string!
Microsoft.Identity.Web.BeforeTokenAcquisitionForApp
Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForTestUser -> Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.TokenAcquisitionExtensionOptions() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForTestUser(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Web.TokenAcquisitionExtensionOptions!>?
static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string!
const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string!
Microsoft.Identity.Web.BeforeTokenAcquisitionForApp
Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForTestUser -> Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.TokenAcquisitionExtensionOptions() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForTestUser(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Web.TokenAcquisitionExtensionOptions!>?
static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string!
const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string!
Microsoft.Identity.Web.BeforeTokenAcquisitionForApp
Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForTestUser -> Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.TokenAcquisitionExtensionOptions() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForTestUser(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Web.TokenAcquisitionExtensionOptions!>?
static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string!
const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string!
Microsoft.Identity.Web.BeforeTokenAcquisitionForApp
Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForTestUser -> Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.TokenAcquisitionExtensionOptions() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.get -> string?
Microsoft.Identity.Web.MergedOptions.AppHomeTenantId.set -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForApp(Microsoft.Identity.Client.AcquireTokenForClientParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForTestUser(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions) -> void
readonly Microsoft.Identity.Web.TokenAcquisition.tokenAcquisitionExtensionOptionsMonitor -> Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Identity.Web.TokenAcquisitionExtensionOptions!>?
static Microsoft.Identity.Web.TokenAcquisition.ResolveTenant(string? tenant, Microsoft.Identity.Web.MergedOptions! mergedOptions) -> string?
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string!
const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string!
Microsoft.Identity.Web.BeforeTokenAcquisitionForApp
Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForTestUser -> Microsoft.Identity.Web.BeforeTokenAcquisitionForTestUser?
Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.TokenAcquisitionExtensionOptions() -> void
22 changes: 16 additions & 6 deletions src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,23 @@ public async Task<AuthenticationResult> GetAuthenticationResultForUserAsync(

}

// Check for extension options for the ROPC flow
TokenAcquisitionExtensionOptions? addInOptions = tokenAcquisitionExtensionOptionsMonitor?.CurrentValue;

// ROPC flow
var authenticationResult = await ((IByUsernameAndPassword)application).AcquireTokenByUsernamePassword(
scopes.Except(_scopesRequestedByMsal),
username,
password)
.ExecuteAsync()
.ConfigureAwait(false);
AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder builder = ((IByUsernameAndPassword)application)
.AcquireTokenByUsernamePassword(
scopes.Except(_scopesRequestedByMsal),
username,
password);

if (addInOptions != null)
{
addInOptions.InvokeOnBeforeTokenAcquisitionForTestUser(builder, tokenAcquisitionOptions);
}

var authenticationResult = await builder.ExecuteAsync()
.ConfigureAwait(false);

if (user.GetMsalAccountId() == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,22 @@ internal void InvokeOnBeforeTokenAcquisitionForApp(AcquireTokenForClientParamete
OnBeforeTokenAcquisitionForApp(builder, acquireTokenOptions);
}
}

/// <summary>
/// Event fired when a ROPC flow request is being built.
/// </summary>
public event BeforeTokenAcquisitionForTestUser? OnBeforeTokenAcquisitionForTestUser;

/// <summary>
/// Invoke the BeforeTokenAcquisitionForTestUser event.
/// </summary>
internal void InvokeOnBeforeTokenAcquisitionForTestUser(AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder builder,
AcquireTokenOptions? acquireTokenOptions)
{
if (OnBeforeTokenAcquisitionForTestUser != null)
{
OnBeforeTokenAcquisitionForTestUser(builder, acquireTokenOptions);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ namespace Microsoft.Identity.Web
/// <param name="builder">Builder</param>
/// <param name="acquireTokenOptions">Token acquisition options for the request. Can be null.</param>
public delegate void BeforeTokenAcquisitionForApp(AcquireTokenForClientParameterBuilder builder, AcquireTokenOptions? acquireTokenOptions);

/// <summary>
/// Signature for token acquisition extensions that act on the request builder, for ROPC flow.
/// </summary>
/// <param name="builder">Builder</param>
/// <param name="acquireTokenOptions">Token acquisition options for the request. Can be null.</param>
public delegate void BeforeTokenAcquisitionForTestUser(AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder builder, AcquireTokenOptions? acquireTokenOptions);
}
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#nullable enable
static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal!
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#nullable enable
static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal!
14 changes: 14 additions & 0 deletions tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpCreator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using Microsoft.Identity.Web.Util;
Expand Down Expand Up @@ -55,5 +57,17 @@ public static MockHttpMessageHandler CreateClientCredentialTokenHandler(

return handler;
}

public static MockHttpMessageHandler CreateHandlerToValidatePostData(
HttpMethod expectedMethod,
IDictionary<string, string> expectedPostData)
{
return new MockHttpMessageHandler()
{
ExpectedMethod = expectedMethod,
ExpectedPostData = expectedPostData,
ResponseMessage = CreateSuccessfulClientCredentialTokenResponseMessage(),
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Xunit;

namespace Microsoft.Identity.Web.Test.Common.Mocks
Expand All @@ -27,6 +28,8 @@ public MockHttpMessageHandler()

public HttpMethod ExpectedMethod { get; set; }

public IDictionary<string, string> ExpectedPostData { get; set; }

public Exception ExceptionToThrow { get; set; }

/// <summary>
Expand Down Expand Up @@ -78,14 +81,28 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
}

Assert.Equal(ExpectedMethod, request.Method);
await ValidatePostDataAsync(request);


return ResponseMessage;
}

private async Task ValidatePostDataAsync(HttpRequestMessage request)
{
if (request.Method != HttpMethod.Get && request.Content != null)
{
string postData = await request.Content.ReadAsStringAsync();
ActualRequestPostData = QueryStringParser.ParseKeyValueList(postData, '&', true, false);
}

return ResponseMessage;
if (ExpectedPostData != null)
{
foreach (string key in ExpectedPostData.Keys)
{
Assert.True(ActualRequestPostData.ContainsKey(key));
Assert.Equal(ExpectedPostData[key], ActualRequestPostData[key]);
}
}
}
}
}
53 changes: 53 additions & 0 deletions tests/Microsoft.Identity.Web.Test/TokenAcquisitionAddInTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
using Microsoft.Identity.Web.Test.Common;
using Xunit;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Graph;
using System.Collections.Generic;

namespace Microsoft.Identity.Web.Tests
{
Expand Down Expand Up @@ -60,5 +63,55 @@ public async Task InvokeOnBeforeTokenAcquisitionForApp_InvokesEvent()
Assert.NotNull(result);
Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
}

[Fact]
public async Task InvokeOnBeforeTokenAcquisitionForUsernamePassword_InvokesEvent()
{
// Arrange
var options = new TokenAcquisitionExtensionOptions();
var acquireTokenOptions = new AcquireTokenOptions();
acquireTokenOptions.ForceRefresh = true;

//Configure mocks
using MockHttpClientFactory mockHttpClient = new();
mockHttpClient.AddMockHandler(MockHttpCreator.CreateHandlerToValidatePostData(
System.Net.Http.HttpMethod.Post,
new Dictionary<string, string>() { { "x-ms-test", "test" } }));

var confidentialApp = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithAuthority(TestConstants.AuthorityCommonTenant)
.WithHttpClientFactory(mockHttpClient)
.WithInstanceDiscovery(false)
.WithClientSecret(TestConstants.ClientSecret)
.Build();

AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder builder = ((IByUsernameAndPassword)confidentialApp)
.AcquireTokenByUsernamePassword(new string[] { "scope" }, "username", "something");

bool eventInvoked = false;
options.OnBeforeTokenAcquisitionForTestUser += (builder, options) =>
{
MsalAuthenticationExtension extension = new MsalAuthenticationExtension();
extension.OnBeforeTokenRequestHandler = (request) =>
{
eventInvoked = true;
request.BodyParameters.Add("x-ms-test", "test");
return Task.CompletedTask;
};

builder.WithAuthenticationExtension(extension);
};

// Act
options.InvokeOnBeforeTokenAcquisitionForTestUser(builder, acquireTokenOptions);

var result = await builder.ExecuteAsync();

// Assert
Assert.True(eventInvoked);
Assert.NotNull(result);
Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
}
}
}

0 comments on commit 7c7fab6

Please sign in to comment.