Skip to content

Commit d6dfa3d

Browse files
authored
Fix TokenCredential.CreateTokenOptions to only accept ReadOnlyMemory<string> and throw exception for invalid types (#52021)
1 parent b1f072f commit d6dfa3d

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

sdk/core/Azure.Core/src/TokenCredential.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System;
45
using System.ClientModel;
56
using System.ClientModel.Primitives;
67
using System.Collections.Generic;
@@ -54,6 +55,23 @@ public override AuthenticationToken GetToken(GetTokenOptions properties, Cancell
5455
GetToken(TokenRequestContext.FromGetTokenOptions(properties), cancellationToken).ToAuthenticationToken();
5556

5657
/// <inheritdoc />
57-
public override GetTokenOptions? CreateTokenOptions(IReadOnlyDictionary<string, object> properties) => null;
58+
public override GetTokenOptions? CreateTokenOptions(IReadOnlyDictionary<string, object> properties)
59+
{
60+
// Check if scopes are present and in a valid format
61+
if (properties.TryGetValue(GetTokenOptions.ScopesPropertyName, out var scopesValue))
62+
{
63+
if (scopesValue is ReadOnlyMemory<string> scopes)
64+
{
65+
return new GetTokenOptions(properties);
66+
}
67+
else
68+
{
69+
throw new ArgumentException($"If a \"{GetTokenOptions.ScopesPropertyName}\" property is included in the properties, it should be typed as ReadOnlyMemory<string>.", nameof(properties));
70+
}
71+
}
72+
73+
// No scopes provided - insufficient information to create TokenRequestContext
74+
return null;
75+
}
5876
}
5977
}

sdk/core/Azure.Core/tests/DelegatedTokenCredentialTests.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.ClientModel.Primitives;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using NUnit.Framework;
@@ -56,5 +57,97 @@ public void CreateGetTokenCallsDelegate(TokenCredential credential)
5657
Assert.AreEqual(expectedToken, actualToken.Token);
5758
Assert.AreEqual(expires, actualToken.ExpiresOn);
5859
}
60+
61+
[Test]
62+
public void CreateTokenOptionsWithValidScopes()
63+
{
64+
// Test with ReadOnlyMemory<string> scopes
65+
var scopesMemory = new ReadOnlyMemory<string>(new string[] { "scope1", "scope2" });
66+
var properties = new Dictionary<string, object>
67+
{
68+
[GetTokenOptions.ScopesPropertyName] = scopesMemory,
69+
["additionalProperty"] = "value"
70+
};
71+
72+
var credential = DelegatedTokenCredential.Create(getToken);
73+
var result = credential.CreateTokenOptions(properties);
74+
75+
Assert.IsNotNull(result);
76+
Assert.AreEqual(scopesMemory, result.Properties[GetTokenOptions.ScopesPropertyName]);
77+
Assert.AreEqual("value", result.Properties["additionalProperty"]);
78+
}
79+
80+
[Test]
81+
public void CreateTokenOptionsWithStringArrayScopesThrowsException()
82+
{
83+
// Test with string[] scopes - should now throw an exception
84+
var scopesArray = new string[] { "scope1", "scope2" };
85+
var properties = new Dictionary<string, object>
86+
{
87+
[GetTokenOptions.ScopesPropertyName] = scopesArray,
88+
["additionalProperty"] = "value"
89+
};
90+
91+
var credential = DelegatedTokenCredential.Create(getToken);
92+
93+
var exception = Assert.Throws<ArgumentException>(() => credential.CreateTokenOptions(properties));
94+
Assert.That(exception.Message, Does.Contain("scopes"));
95+
Assert.That(exception.Message, Does.Contain("ReadOnlyMemory<string>"));
96+
}
97+
98+
[Test]
99+
public void CreateTokenOptionsWithoutScopes()
100+
{
101+
// Test without scopes property
102+
var properties = new Dictionary<string, object>
103+
{
104+
["additionalProperty"] = "value"
105+
};
106+
107+
var credential = DelegatedTokenCredential.Create(getToken);
108+
var result = credential.CreateTokenOptions(properties);
109+
110+
Assert.IsNull(result);
111+
}
112+
113+
[Test]
114+
public void CreateTokenOptionsWithInvalidScopesThrowsException()
115+
{
116+
// Test with invalid scopes type - should throw an exception
117+
var properties = new Dictionary<string, object>
118+
{
119+
[GetTokenOptions.ScopesPropertyName] = "invalid_scopes_type",
120+
["additionalProperty"] = "value"
121+
};
122+
123+
var credential = DelegatedTokenCredential.Create(getToken);
124+
125+
var exception = Assert.Throws<ArgumentException>(() => credential.CreateTokenOptions(properties));
126+
Assert.That(exception.Message, Does.Contain("scopes"));
127+
Assert.That(exception.Message, Does.Contain("ReadOnlyMemory<string>"));
128+
}
129+
130+
[Test]
131+
public void CreateTokenOptionsCanCreateValidTokenRequestContext()
132+
{
133+
// Test that the created GetTokenOptions can be used to create TokenRequestContext
134+
var scopesMemory = new ReadOnlyMemory<string>(new string[] { "scope1", "scope2" });
135+
var properties = new Dictionary<string, object>
136+
{
137+
[GetTokenOptions.ScopesPropertyName] = scopesMemory
138+
};
139+
140+
var credential = DelegatedTokenCredential.Create(getToken);
141+
var tokenOptions = credential.CreateTokenOptions(properties);
142+
143+
Assert.IsNotNull(tokenOptions);
144+
145+
// This should not throw since we have valid scopes
146+
Assert.DoesNotThrow(() =>
147+
{
148+
var context = TokenRequestContext.FromGetTokenOptions(tokenOptions);
149+
Assert.AreEqual(scopesMemory.ToArray(), context.Scopes);
150+
});
151+
}
59152
}
60153
}

0 commit comments

Comments
 (0)