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
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public T WithExtraQueryParameters(IDictionary<string, (string Value, bool Includ
return this as T;
}


/// <summary>
/// Validates the parameters of the AcquireToken operation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal class AcquireTokenCommonParameters
public X509Certificate2 MtlsCertificate { get; internal set; }
public List<string> AdditionalCacheParameters { get; set; }
public SortedList<string, Func<CancellationToken, Task<string>>> CacheKeyComponents { get; internal set; }
public bool? SendOfflineAccessScope { get; set; }
public string FmiPathSuffix { get; internal set; }
public string ClientAssertionFmiPath { get; internal set; }
public bool IsMtlsPopRequested { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Identity.Client.Advanced
Comment thread
iNinja marked this conversation as resolved.
{
Comment thread
iNinja marked this conversation as resolved.
Expand Down Expand Up @@ -44,9 +46,64 @@ public static T WithExtraHttpHeaders<T>(
this AbstractAcquireTokenParameterBuilder<T> builder,
IDictionary<string, string> extraHttpHeaders)
where T : AbstractAcquireTokenParameterBuilder<T>
{
{
builder.CommonParameters.ExtraHttpHeaders = extraHttpHeaders;
return (T)builder;
}

/// <summary>
/// Adds a key-value pair to the token cache key without sending it as a query parameter.
/// Use this to partition cached tokens (e.g., isolating short-lived sessions from regular
/// sessions for the same user). Both <c>AcquireTokenByAuthorizationCode</c> and
/// <c>AcquireTokenSilent</c> must use the same partition key to match cached entries.
/// </summary>
/// <param name="builder">The builder to chain .With methods.</param>
/// <param name="key">The partition key name.</param>
/// <param name="value">The partition key value.</param>
/// <returns>The builder to chain .With methods.</returns>
public static T WithCachePartitionKey<T>(
this BaseAbstractAcquireTokenParameterBuilder<T> builder,
string key,
string value)
where T : BaseAbstractAcquireTokenParameterBuilder<T>
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}

if (key.Length == 0)
{
throw new ArgumentException("Value cannot be empty.", nameof(key));
Comment thread
iNinja marked this conversation as resolved.
}

if (value is null)
{
throw new ArgumentNullException(nameof(value));
}

builder.CommonParameters.CacheKeyComponents ??= new SortedList<string, Func<CancellationToken, Task<string>>>();
string capturedValue = value;
builder.CommonParameters.CacheKeyComponents[key] = (CancellationToken _) => Task.FromResult(capturedValue);
return (T)builder;
}

/// <summary>
/// Controls whether MSAL sends the reserved <c>offline_access</c> scope while continuing to
/// send <c>openid</c> and <c>profile</c>. Only applicable to authorization code redemption flows.
/// </summary>
/// <param name="builder">The builder to chain .With methods.</param>
/// <param name="offlineAccessScope">
/// Set to <see langword="false"/> to omit <c>offline_access</c>. Set to <see langword="true"/>
/// to preserve the default MSAL behavior of sending all reserved scopes.
/// </param>
/// <returns>The builder to chain .With methods.</returns>
public static AcquireTokenByAuthorizationCodeParameterBuilder WithReservedScopes(
this AcquireTokenByAuthorizationCodeParameterBuilder builder,
bool offlineAccessScope)
{
builder.CommonParameters.SendOfflineAccessScope = offlineAccessScope;
return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public AuthenticationRequestParameters(

HomeAccountId = homeAccountId;
CacheKeyComponents = cacheKeyComponents;
SendOfflineAccessScope = commonParameters.SendOfflineAccessScope;
}

public ApplicationConfiguration AppConfig => _serviceBundle.Config;
Expand Down Expand Up @@ -152,6 +153,7 @@ public IAuthenticationOperation AuthenticationScheme
public IEnumerable<string> PersistedCacheParameters => _commonParameters.AdditionalCacheParameters;

public SortedList<string, string> CacheKeyComponents {get; private set; }
public bool? SendOfflineAccessScope { get; private set; }

#region TODO REMOVE FROM HERE AND USE FROM SPECIFIC REQUEST PARAMETERS
// TODO: ideally, these can come from the particular request instance and not be in RequestBase since it's not valid for all requests.
Expand Down
6 changes: 6 additions & 0 deletions src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public async Task<MsalTokenResponse> SendTokenRequestAsync(

string scopes = !string.IsNullOrEmpty(scopeOverride) ? scopeOverride : GetDefaultScopes(_requestParams.Scope);

if (_requestParams.SendOfflineAccessScope is false)
{
scopes = string.Join(" ", scopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => !string.Equals(s, OAuth2Value.ScopeOfflineAccess, StringComparison.OrdinalIgnoreCase)));
}

await AddBodyParamsAndHeadersAsync(additionalBodyParameters, scopes, tokenEndpoint, cancellationToken).ConfigureAwait(false);

AddThrottlingHeader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEv
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value) -> T
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEv
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value) -> T
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEv
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value) -> T
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEv
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value) -> T
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEv
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value) -> T
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation3.AfterCredentialEv
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void
Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey<T>(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder<T> builder, string key, string value) -> T
static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder
Comment thread
iNinja marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.Extensibility;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.Utils;
Comment thread
iNinja marked this conversation as resolved.
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.Identity.Test.Common.Core.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.Identity.Test.Unit.PublicApiTests
{
[TestClass]
public class WithCachePartitionKeyTests
{
[TestMethod]
public async Task WithCachePartitionKey_PopulatesCacheKeyComponents_Async()
{
// Arrange
const string cachePartitionKey = "partition_key";
const string cachePartitionValue = "partition_value";

var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
.WithClientSecret(TestConstants.ClientSecret)
.BuildConcrete();

var builder = app.AcquireTokenForClient(TestConstants.s_scope)
.WithCachePartitionKey(cachePartitionKey, cachePartitionValue);

// Act
var commonParameters = GetCommonParameters(builder);

// Assert
Assert.IsNotNull(commonParameters.CacheKeyComponents);
Assert.HasCount(1, commonParameters.CacheKeyComponents);
Assert.IsTrue(commonParameters.CacheKeyComponents.ContainsKey(cachePartitionKey));
var cachedValue = await commonParameters.CacheKeyComponents[cachePartitionKey](CancellationToken.None)
.ConfigureAwait(false);
Assert.AreEqual(cachePartitionValue, cachedValue);
Assert.IsNull(commonParameters.ExtraQueryParameters);
}

[TestMethod]
public async Task WithCachePartitionKey_DoesNotAddToExtraQueryParameters_Async()
{
using (var httpManager = new MockHttpManager())
{
// Arrange
const string cachePartitionKey = "partition_key";
const string cachePartitionValue = "partition_value";

var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
.WithClientSecret(TestConstants.ClientSecret)
.WithHttpManager(httpManager)
.WithInstanceDiscovery(false)
.BuildConcrete();

var handler = httpManager.AddSuccessTokenResponseMockHandlerForPost();
handler.UnExpectedPostData = new Dictionary<string, string>
{
{ cachePartitionKey, null }
};

// Act
var result = await app.AcquireTokenByAuthorizationCode(TestConstants.s_scope, TestConstants.DefaultAuthorizationCode)
.WithCachePartitionKey(cachePartitionKey, cachePartitionValue)
.ExecuteAsync()
.ConfigureAwait(false);

// Assert
Assert.IsNotNull(result);
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
}
}

[TestMethod]
public void WithCachePartitionKey_ThrowsOnNullOrEmptyKey()
{
// Arrange
var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
.WithClientSecret(TestConstants.ClientSecret)
.BuildConcrete();

var builder = app.AcquireTokenForClient(TestConstants.s_scope);

// Act
ArgumentNullException nullKeyException = AssertException.Throws<ArgumentNullException>(() => builder.WithCachePartitionKey(null, "value"));
ArgumentException emptyKeyException = AssertException.Throws<ArgumentException>(() => builder.WithCachePartitionKey(string.Empty, "value"));

// Assert
Assert.AreEqual("key", nullKeyException.ParamName);
Assert.AreEqual("key", emptyKeyException.ParamName);
}

[TestMethod]
public void WithCachePartitionKey_ThrowsOnNullValue()
{
// Arrange
var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
.WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true)
.WithClientSecret(TestConstants.ClientSecret)
.BuildConcrete();

var builder = app.AcquireTokenForClient(TestConstants.s_scope);

// Act
ArgumentNullException exception = AssertException.Throws<ArgumentNullException>(() => builder.WithCachePartitionKey("key", null));

// Assert
Assert.AreEqual("value", exception.ParamName);
}

private static AcquireTokenCommonParameters GetCommonParameters(object builder)
{
Type currentType = builder.GetType();

while (currentType != null)
{
var commonParametersProperty = currentType.GetProperty(
"CommonParameters",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

Comment thread
iNinja marked this conversation as resolved.
if (commonParametersProperty != null)
{
return (AcquireTokenCommonParameters)commonParametersProperty.GetValue(builder);
}

currentType = currentType.BaseType;
}

Assert.Fail("CommonParameters property was not found on the builder.");
return null;
}
}
}
Loading
Loading