-
Notifications
You must be signed in to change notification settings - Fork 45
Allow overriding the maximum length when parsing refresh tokens #350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| // Copyright (c) Duende Software. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
|
||
| namespace Duende.AccessTokenManagement.Types; | ||
|
|
||
| /// <summary> | ||
| /// Tests for <see cref="RefreshToken.SetMaxLength"/>. These are isolated into a separate | ||
| /// non-parallel collection because <see cref="RefreshToken.SetMaxLength"/> mutates static state | ||
| /// that would affect other tests running concurrently. | ||
| /// </summary> | ||
| [Collection(nameof(RefreshTokenTests))] | ||
| public class RefreshTokenSetMaxLengthTests : IDisposable | ||
|
Comment on lines
+6
to
+12
|
||
| { | ||
| public void Dispose() => | ||
| // Reset to the default max length after each test so static state doesn't leak. | ||
| RefreshToken.SetMaxLength(RefreshToken.MaxLength); | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_AllowsLargerTokens() | ||
| { | ||
| // Arrange | ||
| var largeMaxLength = 16 * 1024; // 16 KB, e.g. for ADFS tokens | ||
| var largeValue = new string('a', RefreshToken.MaxLength + 1); // Exceeds default, within new limit | ||
| RefreshToken.SetMaxLength(largeMaxLength); | ||
|
|
||
| // Act | ||
| var result = RefreshToken.Parse(largeValue); | ||
|
|
||
| // Assert | ||
| result.ToString().ShouldBe(largeValue); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_StillRejectsTokensExceedingNewLimit() | ||
| { | ||
| // Arrange | ||
| var newMaxLength = 8 * 1024; | ||
| RefreshToken.SetMaxLength(newMaxLength); | ||
| var tooLargeValue = new string('a', newMaxLength + 1); | ||
|
|
||
| // Act & Assert | ||
| var exception = Should.Throw<InvalidOperationException>(() => RefreshToken.Parse(tooLargeValue)); | ||
| exception.Message.ShouldContain("exceeds maximum length"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_TryParse_AllowsLargerTokens() | ||
| { | ||
| // Arrange | ||
| var largeMaxLength = 16 * 1024; | ||
| var largeValue = new string('a', RefreshToken.MaxLength + 1); | ||
| RefreshToken.SetMaxLength(largeMaxLength); | ||
|
|
||
| // Act | ||
| var success = RefreshToken.TryParse(largeValue, out var result, out var errors); | ||
|
|
||
| // Assert | ||
| success.ShouldBeTrue(); | ||
| result.ShouldNotBeNull(); | ||
| result.ToString().ShouldBe(largeValue); | ||
| errors.ShouldBeEmpty(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_TryParse_StillRejectsTokensExceedingNewLimit() | ||
| { | ||
| // Arrange | ||
| var newMaxLength = 8 * 1024; | ||
| RefreshToken.SetMaxLength(newMaxLength); | ||
| var tooLargeValue = new string('a', newMaxLength + 1); | ||
|
|
||
| // Act | ||
| var success = RefreshToken.TryParse(tooLargeValue, out var result, out var errors); | ||
|
|
||
| // Assert | ||
| success.ShouldBeFalse(); | ||
| result.ShouldBeNull(); | ||
| errors.ShouldNotBeEmpty(); | ||
| errors[0].ShouldContain("exceeds maximum length"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_CanReduceLimit() | ||
| { | ||
| // Arrange | ||
| var smallerMaxLength = 100; | ||
| RefreshToken.SetMaxLength(smallerMaxLength); | ||
| var value = new string('a', smallerMaxLength + 1); | ||
|
|
||
| // Act & Assert | ||
| var exception = Should.Throw<InvalidOperationException>(() => RefreshToken.Parse(value)); | ||
| exception.Message.ShouldContain("exceeds maximum length"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_AtExactNewLimit_Succeeds() | ||
| { | ||
| // Arrange | ||
| var newMaxLength = 8 * 1024; | ||
| RefreshToken.SetMaxLength(newMaxLength); | ||
| var value = new string('a', newMaxLength); | ||
|
|
||
| // Act | ||
| var result = RefreshToken.Parse(value); | ||
|
|
||
| // Assert | ||
| result.ToString().ShouldBe(value); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_Zero_ThrowsArgumentOutOfRangeException() => | ||
| // Act & Assert | ||
| Should.Throw<ArgumentOutOfRangeException>(() => RefreshToken.SetMaxLength(0)); | ||
|
|
||
| [Fact] | ||
| public void SetMaxLength_Negative_ThrowsArgumentOutOfRangeException() => | ||
| // Act & Assert | ||
| Should.Throw<ArgumentOutOfRangeException>(() => RefreshToken.SetMaxLength(-1)); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // Copyright (c) Duende Software. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
|
||
| namespace Duende.AccessTokenManagement.Types; | ||
|
|
||
| /// <summary> | ||
| /// Both the <see cref="RefreshTokenTests"/> and <see cref="RefreshTokenSetMaxLengthTests"/> classes share a collection to prevent parallel execution, | ||
| /// since <see cref="RefreshToken.SetMaxLength"/> mutates static state. | ||
| /// </summary> | ||
| [Collection(nameof(RefreshTokenTests))] | ||
| public class RefreshTokenTests | ||
|
Comment on lines
+6
to
+11
|
||
| { | ||
| [Fact] | ||
| public void Parse_ValidValue_ReturnsRefreshToken() | ||
| { | ||
| // Arrange | ||
| var validValue = "valid_refresh_token"; | ||
|
|
||
| // Act | ||
| var result = RefreshToken.Parse(validValue); | ||
|
|
||
| // Assert | ||
| result.ToString().ShouldBe(validValue); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Parse_InvalidValue_ThrowsException() | ||
| { | ||
| // Arrange | ||
| var invalidValue = new string('a', RefreshToken.MaxLength + 1); // Exceeds max length | ||
|
|
||
| // Act & Assert | ||
| var exception = Should.Throw<InvalidOperationException>(() => RefreshToken.Parse(invalidValue)); | ||
| exception.Message.ShouldContain("exceeds maximum length"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryParse_ValidValue_ReturnsTrueAndRefreshToken() | ||
| { | ||
| // Arrange | ||
| var validValue = "valid_refresh_token"; | ||
|
|
||
| // Act | ||
| var success = RefreshToken.TryParse(validValue, out var result, out var errors); | ||
|
|
||
| // Assert | ||
| success.ShouldBeTrue(); | ||
| result.ShouldNotBeNull(); | ||
| result.ToString().ShouldBe(validValue); | ||
| errors.ShouldBeEmpty(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TryParse_InvalidValue_ReturnsFalseAndErrors() | ||
| { | ||
| // Arrange | ||
| var invalidValue = new string('a', RefreshToken.MaxLength + 1); // Exceeds max length | ||
|
|
||
| // Act | ||
| var success = RefreshToken.TryParse(invalidValue, out var result, out var errors); | ||
|
|
||
| // Assert | ||
| success.ShouldBeFalse(); | ||
| result.ShouldBeNull(); | ||
| errors.ShouldNotBeEmpty(); | ||
| errors[0].ShouldContain("exceeds maximum length"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Parse_AtExactMaxLength_ReturnsRefreshToken() | ||
| { | ||
| // Arrange | ||
| var value = new string('a', RefreshToken.MaxLength); | ||
|
|
||
| // Act | ||
| var result = RefreshToken.Parse(value); | ||
|
|
||
| // Assert | ||
| result.ToString().ShouldBe(value); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Parse_NullValue_ThrowsException() => | ||
| // Act & Assert | ||
| Should.Throw<InvalidOperationException>(() => RefreshToken.Parse(null!)); | ||
|
|
||
| [Fact] | ||
| public void Parse_EmptyValue_ThrowsException() => | ||
| // Act & Assert | ||
| Should.Throw<InvalidOperationException>(() => RefreshToken.Parse(string.Empty)); | ||
|
|
||
| [Fact] | ||
| public void Parse_WhitespaceValue_ThrowsException() => | ||
| // Act & Assert | ||
| Should.Throw<InvalidOperationException>(() => RefreshToken.Parse(" ")); | ||
|
|
||
| [Fact] | ||
| public void ParameterlessConstructor_ThrowsException() | ||
| { | ||
| // Act & Assert | ||
| var exception = Should.Throw<InvalidOperationException>(() => new RefreshToken()); | ||
| exception.Message.ShouldContain("Can't create null value"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ImplicitStringConversion_ReturnsValue() | ||
| { | ||
| // Arrange | ||
| var value = "my_refresh_token"; | ||
| var token = RefreshToken.Parse(value); | ||
|
|
||
| // Act | ||
| string converted = token; | ||
|
|
||
| // Assert | ||
| converted.ShouldBe(value); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SetMaxLengthupdatesValidators[0], which couples the API to the current ordering/shape of theValidatorsarray. If additional validation rules are added later (or the order changes), this method could silently stop updating the max-length rule. Consider refactoring so the max-length check is implemented via a dedicated validator method that reads a staticint(e.g.,Volatile.Read/Write) or by rebuilding the validators array in a way that doesn't rely on a hard-coded index.