Skip to content
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

Eliminate token cache timeout setting in most cases #126

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

vvdb-architecture
Copy link
Contributor

@vvdb-architecture vvdb-architecture commented Oct 8, 2024

This pull request eliminates the reliance on a fixed timeout for the token cache.
In other words, the MaxTime settings in the following section:

    "TokenCache": {
      "CacheName": "Volatile",
      "MaxTime": "00:00:20"
    },

... isn't used anymore and can be eliminated.

The reasoning behind the modification is that the implicit validity of the cached information is never longer than the expiration date of the stored token. And in all use cases except one, the expiration date can be obtained from the token information being stored (i.e. the exp claim). This makes the explicit setting in the configuration unnecessary.

The only exception is the "extra claims" than can optionally be loaded. These extra claims are cached. It is expected that the IClaimsFiller implementation returns an exp claim. If it doesn't, the code now looks for an exp claim in the existing ClaimsPrincipal. And if for some reason that fails, we need a timeout. This timeout is now part of the ClaimsFillerOptions. It is called MaxTime as before, and defaults to 20 minutes if it isn't specified (since in existing code, it would not be).

The code AppPrincipalTransform.cs has been optimized to avoid needless list copying, and to systematically compare claim types case-insensitively.

What is still missing in this PR is a way to explicitly delete cached entries when the user logs off. It also doesn't change the way cache keys are computed.

According to https://www.rfc-editor.org/rfc/rfc7519 section 10.1, claim names (a.k.a. "types") are case-sensitive: this has been enacted throughout the code.
Claims manipulations have been factored out in a separate utility class.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced token caching mechanism to dynamically calculate expiration times.
    • Introduced utility methods for improved claims handling in OAuth2 security contexts.
  • Bug Fixes

    • Improved error handling in claims filling process.
  • Refactor

    • Streamlined claim handling and caching logic for better performance.
    • Simplified configuration validation for claims filling.
    • Updated method signatures to support new caching strategies.
  • Documentation

    • Added XML documentation to clarify method functionalities and parameters.

Copy link

coderabbitai bot commented Oct 8, 2024

Walkthrough

The changes in this pull request involve multiple modifications across various classes within the Arc4u.Standard.OAuth2 namespace. Key updates include enhancements to token caching mechanisms, adjustments in claim handling, and the introduction of a new ClaimsUtility class. The AfterAccessNotification method in the Cache class now dynamically calculates expiration times for tokens, while the AppPrincipalTransform class has streamlined claim management. Additionally, the ITokenCache interface has been updated to include a timeout parameter for caching methods.

Changes

File Change Summary
src/Arc4u.Standard.OAuth2.AspNetCore.Adal/Token/Cache.cs Enhanced AfterAccessNotification method to calculate maximum expiration time for tokens before caching.
src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs Updated claims handling, removed ClaimsToExclude, simplified claim addition logic, and modified SaveClaimsToCache method to include a TimeSpan timeout parameter.
src/Arc4u.Standard.OAuth2.AspNetCore/Security/AppServicePrincipalFactory.cs Removed static fields tokenExpirationClaimType and ClaimsToExclude, updated CreatePrincipal method signature, and simplified claims processing with MergeClaims method.
src/Arc4u.Standard.OAuth2.Client/Security/Principal/AppPrincipalFactory.cs Removed static fields tokenExpirationClaimType and ClaimsToExclude, updated claims management logic in BuildTheIdentity, and ensured claims are removed from cache in SignOutUserAsync.
src/Arc4u.Standard.OAuth2/Security/ClaimsUtility.cs Introduced ClaimsUtility class with methods for sanitizing and merging claims, along with utility methods for expiration handling.
src/Arc4u.Standard.OAuth2/Token/ITokenCache.cs Updated Put method signature to include a TimeSpan timeout parameter and added XML documentation.
src/Arc4u.Standard.OAuth2/Token/ApplicationCache.cs Updated Put method to include a TimeSpan timeout parameter for cache entries.
src/Arc4u.Standard.OAuth2/Token/ApplicationLocalDataCache.cs Modified Put method signature to include a TimeSpan timeout parameter and refined exception handling in Clear method.

Poem

In the land of tokens, where rabbits play,
New caching magic has come our way!
With claims now swift and options bright,
We hop through code, our hearts alight!
So let’s cheer for changes, both big and small,
In our burrow of code, we celebrate all! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (16)
src/Arc4u.Standard.OAuth2/Token/ITokenCache.cs (1)

12-20: LGTM! Improved method signature and added documentation.

The changes to the Put method signature and the addition of XML documentation are well-implemented and align with the PR objectives. The new timeout parameter allows for more flexible expiration times, which is a good improvement over the previous fixed timeout approach.

Consider a minor improvement to the XML documentation for consistency:

 /// <summary>
 /// Set or overwrite the item with key <paramref name="key"/> with <paramref name="data"/>.
-/// The item expires after <paramref name="timeout"/>
+/// The item expires after <paramref name="timeout"/>.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <param name="key"></param>
 /// <param name="timeout"></param>
 /// <param name="data"></param>

This change adds a period at the end of the sentence for consistency with other comments.

src/Arc4u.Standard.OAuth2.AspNetCore/Options/ClaimsFillerOptions.cs (1)

11-14: LGTM! Consider enhancing the documentation slightly.

The addition of the MaxTime property is well-implemented and properly documented. It provides a clear purpose for caching extra claims when LoadClaimsFromClaimsFillerProvider is true.

Consider adding a brief explanation of why 20 minutes was chosen as the default value. This could help future maintainers understand the reasoning behind this specific duration. For example:

 /// <summary>
-/// If <see cref="LoadClaimsFromClaimsFillerProvider"/> is true, the extra claims are cached for <see cref="MaxTime"/>."/>
+/// If <see cref="LoadClaimsFromClaimsFillerProvider"/> is true, the extra claims are cached for <see cref="MaxTime"/>.
+/// The default value of 20 minutes provides a balance between performance and data freshness for most use cases.
 /// </summary>
 public TimeSpan MaxTime { get; set; } = TimeSpan.FromMinutes(20);
src/Arc4u.Standard.OAuth2/Token/ApplicationLocalDataCache.cs (1)

Line range hint 24-31: Approve simplification of Clear method with a suggestion

The removal of the empty try-catch block in the Clear method improves code clarity. This change allows exceptions to propagate, which is generally a good practice for better error handling and debugging.

Consider adding a log statement to capture any exceptions that may occur during the Cache.Remove operation:

 public void Clear(string key)
 {
-    Cache.Remove(key);
-    _token = null;
+    try
+    {
+        Cache.Remove(key);
+        _token = null;
+    }
+    catch (Exception ex)
+    {
+        Logger.Technical().From<ApplicationLocalDataCache>().Exception(ex).Message("Failed to clear cache").Log();
+    }
 }

This will provide visibility into any issues that may occur during cache clearing operations.

src/Arc4u.Standard.OAuth2.AspNetCore/Extensions/AddClaimsFillerExtension.cs (2)

14-28: Improved configuration validation, with minor suggestions.

The additional validation logic enhances the robustness of the ClaimsFillerOptions configuration. This is a positive change that will help catch configuration errors early. However, consider the following suggestions for further improvement:

  1. Use a List<string> or StringBuilder for accumulating error messages instead of string concatenation. This would be more efficient, especially if more validations are added in the future.

  2. The MaxTime check could be more explicit about the expected value. Consider changing the condition to validate.MaxTime <= TimeSpan.Zero to catch negative values as well.

Here's a suggested refactoring that addresses these points:

 if (validate.LoadClaimsFromClaimsFillerProvider)
 {
-    string? configErrors = null;
+    var configErrors = new List<string>();
     if (null == validate.SettingsKeys || !validate.SettingsKeys.Any())
     {
-        configErrors += "Settings key must be provided." + System.Environment.NewLine;
+        configErrors.Add("Settings key must be provided.");
     }
-    if (validate.LoadClaimsFromClaimsFillerProvider && validate.MaxTime == TimeSpan.Zero)
+    if (validate.MaxTime <= TimeSpan.Zero)
     {
-        configErrors += $"If {nameof(validate.LoadClaimsFromClaimsFillerProvider)} is true, then {nameof(validate.MaxTime)} must be provided." + System.Environment.NewLine;
+        configErrors.Add($"{nameof(validate.MaxTime)} must be a positive TimeSpan when {nameof(validate.LoadClaimsFromClaimsFillerProvider)} is true.");
     }
-    if (configErrors is not null)
+    if (configErrors.Any())
     {
-        throw new ConfigurationException(configErrors);
+        throw new ConfigurationException(string.Join(Environment.NewLine, configErrors));
     }
 }

This refactoring improves efficiency and clarity while maintaining the intended functionality.


52-56: Formatting improvement with suggestion for consistency.

The formatting changes improve the readability of the lambda expression, which is beneficial. However, to maintain consistency with the changes made in the first AddClaimsFiller method, consider adding the MaxTime property to this method as well.

Here's a suggested addition to include the MaxTime property:

 AddClaimsFiller(services, o =>
 {
     o.LoadClaimsFromClaimsFillerProvider = options.LoadClaimsFromClaimsFillerProvider;
     o.SettingsKeys = options.SettingsKeys;
+    o.MaxTime = options.MaxTime;
 });

This addition ensures that the MaxTime property is properly propagated when the AddClaimsFiller method is called with an IConfiguration object.

src/Arc4u.Standard.OAuth2/Token/ApplicationCache.cs (1)

50-50: LGTM! Consider adding timeout validation.

The update to use the timeout parameter in the _cache.Put call is correct and aligns with the PR objectives. This change allows for more dynamic and flexible timeout management.

Consider adding a validation check for the timeout parameter to ensure it's not negative or zero:

 public void Put<T>(string key, TimeSpan timeout, T data)
 {
+    if (timeout <= TimeSpan.Zero)
+    {
+        throw new ArgumentException("Timeout must be a positive value.", nameof(timeout));
+    }
     if (null == data)
     {
         _logger.Technical().From<ApplicationCache>().System("A null token data information was provided to the cache. We skip this data from the cache.");
         return;
     }

     _logger.Technical().From<ApplicationCache>().System($"Adding token data information to the cache: {key}.").Log();
     _cache.Put(GetKey(key), timeout, data);
     _logger.Technical().From<ApplicationCache>().System($"Added token data information to the cache: {key}.").Log();
 }
src/Arc4u.Standard.UnitTest/Security/TokenCacheOptionsTests.cs (3)

Line range hint 28-45: LGTM: MaxTime assertion removed as per PR objectives

The removal of the MaxTime assertion aligns with the PR's goal of eliminating the explicit timeout in most scenarios. The test still correctly verifies the CacheName setting.

Consider renaming the test method from TockeCacheOption_From_Action_Should to TokenCacheOption_From_Action_Should to fix the typo in "Token".


Line range hint 47-71: LGTM: MaxTime configuration and assertion removed

The removal of the MaxTime configuration entry and its corresponding assertion is in line with the PR objectives. The test continues to verify the correct setting of CacheName from the configuration.

As before, consider renaming the test method from TockeCacheOption_From_Config_Should to TokenCacheOption_From_Config_Should to correct the typo in "Token".


Line range hint 73-98: Approved, but test method name needs update

The removal of MaxTime-related configurations and assertions aligns with the PR objectives. However, the test method name no longer accurately reflects its purpose.

Rename the test method from TockeCacheOption_From_Config_With_Default_MaxTime_Should to TokenCacheOption_From_Config_Should_UseProvidedCacheName or a similar name that better describes its current functionality. This change will improve the clarity of the test suite and prevent confusion for future developers.

src/Arc4u.Standard.OAuth2.AspNetCore/Middleware/BasicAuthenticationMiddleware.cs (1)

89-89: Approve change with suggestions for improvement

This change aligns well with the PR objectives by removing the fixed timeout for the token cache. The new approach of using the token's expiration time is more dynamic and efficient. Good job!

A few suggestions to further improve this:

  1. Verify that tokenInfo.ExpiresOnUtc is always set and valid before using it in this calculation.
  2. Consider adding a comment explaining the rationale behind this change for future maintainers.

Here's a suggested improvement:

- tokenCache?.Put(cacheKey, tokenInfo.ExpiresOnUtc - DateTime.UtcNow, tokenInfo);
+ // Cache the token with its remaining validity time instead of a fixed timeout
+ if (tokenInfo.ExpiresOnUtc > DateTime.UtcNow)
+ {
+     tokenCache?.Put(cacheKey, tokenInfo.ExpiresOnUtc - DateTime.UtcNow, tokenInfo);
+ }
+ else
+ {
+     _logger.Technical().LogWarning("Attempted to cache an expired token");
+ }

This change adds a safety check and logging, which could help catch any unexpected scenarios where the token might already be expired.

src/Arc4u.Standard.UnitTest/Security/JwtHttpHandlerTests.cs (6)

274-276: Consider using a more realistic expiration time for cache entries

While the mock setup for the TokenCache's Put method is correct, using TimeSpan.MaxValue as the expiration time might not reflect real-world scenarios accurately. Consider using a more realistic expiration time that aligns with typical token lifetimes or the specific requirements of your application.

You could replace TimeSpan.MaxValue with a constant or configuration value that represents a typical token lifetime, for example:

-mockTokenCache.Setup(m => m.Put<TokenInfo>(It.IsAny<string>(), TimeSpan.MaxValue, It.IsAny<TokenInfo>()));
+mockTokenCache.Setup(m => m.Put<TokenInfo>(It.IsAny<string>(), TimeSpan.FromHours(1), It.IsAny<TokenInfo>()));

Line range hint 1-1000: Consider consistent use of var keyword

Throughout the file, there's a mix of explicit type declarations and the var keyword. For better readability and consistency, consider using var more consistently where the type is obvious from the right side of the assignment.

For example, you could change instances like:

-IConfiguration configuration = new ConfigurationRoot(new List<IConfigurationProvider>(config.Providers));
+var configuration = new ConfigurationRoot(new List<IConfigurationProvider>(config.Providers));

-IServiceCollection services = new ServiceCollection();
+var services = new ServiceCollection();

This change would make the code more consistent and potentially more readable, especially for complex types.


Line range hint 1-1000: Consider refining test method naming convention

The test method names in this file are descriptive but quite long. Additionally, there's some inconsistency in the naming convention, with some using underscores and others ending with "Should". Consider adopting a more consistent and concise naming convention for improved readability.

Here are a few suggestions:

  1. Use a consistent format, e.g., "Should_[ExpectedBehavior]When[Condition]"
  2. Keep names concise while still being descriptive
  3. Use underscores consistently for readability

For example:

-public async Task Jwt_With_OAuth2_And_Principal_With_OIDC_Token_Should()
+public async Task Should_InjectBearerToken_When_UsingOAuth2WithOIDCToken()

-public async Task Jwt_With_ClientSecet_Should()
+public async Task Should_InjectBearerToken_When_UsingClientSecret()

This approach maintains descriptiveness while improving consistency and readability.


Line range hint 1-1000: Consider reorganizing tests for improved structure

The file contains multiple test methods covering various scenarios, but there's no clear separation or grouping of related tests. To improve readability and maintainability, consider reorganizing the tests into nested classes based on the scenarios they're testing.

Here's an example of how you could restructure the tests:

public class JwtHttpHandlerTests
{
    public class OAuth2Tests
    {
        [Fact]
        public async Task Should_InjectBearerToken_When_UsingOAuth2WithOIDCToken() { ... }

        [Fact]
        public async Task Should_InjectBearerToken_When_UsingOAuth2WithBearerToken() { ... }
    }

    public class ClientSecretTests
    {
        [Fact]
        public async Task Should_InjectBearerToken_When_UsingClientSecret() { ... }
    }

    public class RemoteSecretTests
    {
        [Fact]
        public async Task Should_InjectBasicAuth_When_UsingRemoteSecretWithBasicAuthorization() { ... }

        [Fact]
        public async Task Should_InjectCustomHeader_When_UsingRemoteSecret() { ... }
    }

    // ... other nested classes for different scenarios
}

This structure would group related tests together, making it easier to navigate and maintain the test suite. It also allows for shared setup methods within each nested class if needed.


Line range hint 1-1000: Consider adding tests for error handling and edge cases

The current test suite covers various successful scenarios, but it may be beneficial to add tests that explicitly check error conditions and edge cases. This would improve the robustness of the test suite and ensure that the JwtHttpHandler behaves correctly in exceptional situations.

Consider adding tests for scenarios such as:

  1. Expired tokens
  2. Invalid tokens
  3. Missing required configuration
  4. Network failures when refreshing tokens
  5. Concurrent token refresh requests

For example, you could add a test like this:

[Fact]
public async Task Should_ThrowException_When_TokenIsExpired()
{
    // Arrange
    var expiredJwt = new JwtSecurityToken("issuer", "audience", 
        new List<Claim> { new Claim("key", "value") }, 
        expires: DateTime.UtcNow.AddHours(-1));
    var expiredAccessToken = new JwtSecurityTokenHandler().WriteToken(expiredJwt);

    // ... set up the test with the expired token ...

    // Act & Assert
    await Assert.ThrowsAsync<SecurityTokenExpiredException>(async () =>
    {
        await invoker.SendAsync(httpRequestMessage, CancellationToken.None);
    });
}

Adding such tests would increase confidence in the error handling capabilities of the JwtHttpHandler.


Line range hint 1-1000: Consider centralizing and improving test data setup

Currently, test data such as tokens and configuration is set up individually in each test method, leading to some repetition across the test suite. To improve maintainability and readability, consider centralizing the test data setup and potentially implementing a test data builder pattern.

Here are some suggestions for improvement:

  1. Create helper methods for common setup tasks:
private IConfiguration CreateTestConfiguration(Dictionary<string, string> additionalSettings = null)
{
    var baseSettings = new Dictionary<string, string>
    {
        ["Authentication:DefaultAuthority:Url"] = "https://login.microsoft.com"
        // ... other common settings ...
    };

    if (additionalSettings != null)
    {
        foreach (var setting in additionalSettings)
        {
            baseSettings[setting.Key] = setting.Value;
        }
    }

    return new ConfigurationBuilder().AddInMemoryCollection(baseSettings).Build();
}

private string CreateTestToken(DateTime? expirationTime = null)
{
    var jwt = new JwtSecurityToken("issuer", "audience", 
        new List<Claim> { new Claim("key", "value") }, 
        notBefore: DateTime.UtcNow.AddHours(-1), 
        expires: expirationTime ?? DateTime.UtcNow.AddHours(1));
    return new JwtSecurityTokenHandler().WriteToken(jwt);
}
  1. Implement a test data builder for complex objects:
public class TestPrincipalBuilder
{
    private string _authenticationType = Constants.BearerAuthenticationType;
    private string _accessToken = null;

    public TestPrincipalBuilder WithAuthenticationType(string type)
    {
        _authenticationType = type;
        return this;
    }

    public TestPrincipalBuilder WithAccessToken(string token)
    {
        _accessToken = token;
        return this;
    }

    public AppPrincipal Build()
    {
        var identity = new ClaimsIdentity(_authenticationType);
        if (_accessToken != null)
        {
            identity.BootstrapContext = _accessToken;
        }
        return new AppPrincipal(new Arc4u.Security.Principal.Authorization(), identity, "S-1-0-0");
    }
}
  1. Use these in your tests:
[Fact]
public async Task Should_InjectBearerToken_When_UsingOAuth2WithBearerToken()
{
    var config = CreateTestConfiguration(new Dictionary<string, string>
    {
        ["Authentication:OAuth2.Settings:Audiences:0"] = "urn://audience.com",
        ["Authentication:OAuth2.Settings:Scopes"] = "user.read user.write"
    });

    var accessToken = CreateTestToken();

    var principal = new TestPrincipalBuilder()
        .WithAuthenticationType(Constants.BearerAuthenticationType)
        .WithAccessToken(accessToken)
        .Build();

    // ... rest of the test setup and execution ...
}

These changes would make the tests more readable, reduce repetition, and make it easier to maintain and extend the test suite in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 13928e8 and c747c36.

📒 Files selected for processing (14)
  • src/Arc4u.Standard.OAuth2.AspNetCore.Adal/Token/Cache.cs (2 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (5 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/Extensions/AddClaimsFillerExtension.cs (2 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/Middleware/BasicAuthenticationMiddleware.cs (1 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/Options/ClaimsFillerOptions.cs (1 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/TokenProvider/CredentialTokenCacheTokenProvider.cs (1 hunks)
  • src/Arc4u.Standard.OAuth2/Extensions/TokenCacheExtension.cs (0 hunks)
  • src/Arc4u.Standard.OAuth2/Options/TokenCacheOptions.cs (0 hunks)
  • src/Arc4u.Standard.OAuth2/Token/ApplicationCache.cs (2 hunks)
  • src/Arc4u.Standard.OAuth2/Token/ApplicationLocalDataCache.cs (1 hunks)
  • src/Arc4u.Standard.OAuth2/Token/ITokenCache.cs (2 hunks)
  • src/Arc4u.Standard.UnitTest/Security/JwtHttpHandlerTests.cs (4 hunks)
  • src/Arc4u.Standard.UnitTest/Security/TokenCacheOptionsTests.cs (3 hunks)
  • src/Arc4u.Standard.UnitTest/Security/gRpcInterceptorTests.cs (5 hunks)
💤 Files with no reviewable changes (2)
  • src/Arc4u.Standard.OAuth2/Extensions/TokenCacheExtension.cs
  • src/Arc4u.Standard.OAuth2/Options/TokenCacheOptions.cs
🧰 Additional context used
🔇 Additional comments (16)
src/Arc4u.Standard.OAuth2/Token/ApplicationLocalDataCache.cs (2)

Line range hint 1-85: Overall assessment of ApplicationLocalDataCache changes

The changes to this class partially implement the PR objectives by introducing a timeout parameter in the Put method. However, there are a few points that need attention:

  1. The timeout parameter in the Put method is not currently used.
  2. The Clear method has been simplified, which improves code clarity but may affect error handling.

Please address the comments provided for each method to ensure the changes fully meet the PR objectives.

To ensure consistency across the codebase, let's verify the usage of the Put method:

#!/bin/bash
# Description: Check for calls to ApplicationLocalDataCache.Put method
rg --type csharp -A 3 'ApplicationLocalDataCache.*Put\('

This will help identify any places where the method is called and ensure they are updated to provide the new timeout parameter.


Line range hint 39-52: Implement the timeout parameter in the Put method

The Put method signature has been updated to include a TimeSpan timeout parameter, which aligns with the PR objectives. However, this parameter is currently not utilized within the method implementation.

To address this, consider updating the Cache.Put call to include the timeout parameter:

-Cache.Put(key, data);
+Cache.Put(key, data, timeout);

Also, ensure that the ICache interface and its implementation support this new signature.

Let's verify the ICache interface definition:

src/Arc4u.Standard.OAuth2/Token/ApplicationCache.cs (2)

Line range hint 1-85: Overall, the changes improve cache flexibility and align with PR objectives.

The modifications to the ApplicationCache class, particularly in the Put method, enhance the flexibility of the caching mechanism by allowing dynamic timeout settings. This aligns well with the PR objective of eliminating the fixed token cache timeout.

The removal of unused namespaces (System and System.Collections.Generic) is a good practice for code cleanliness.

These changes contribute to a more efficient and maintainable caching system for OAuth2 tokens.


41-41: LGTM! Verify usage across the codebase.

The addition of the TimeSpan timeout parameter to the Put method signature is a good improvement, allowing for more flexible cache management.

To ensure all calls to this method have been updated accordingly, run the following script:

src/Arc4u.Standard.UnitTest/Security/TokenCacheOptionsTests.cs (2)

1-10: LGTM: Using statements updated to reflect new dependencies

The changes in the using statements align well with the PR objectives. The addition of OAuth2-related namespaces (Arc4u.OAuth2.Extensions and Arc4u.OAuth2.Options) is consistent with the modifications to token caching mechanisms mentioned in the PR summary. The removal of unused namespaces helps keep the code clean and focused.


Line range hint 100-138: Review the relevance of this test case

The removal of the MaxTime configuration entry is consistent with the PR objectives. However, the test's name and expected behavior (throwing an exception for MaxTime set to zero) may no longer be relevant.

Please clarify the following:

  1. Is this test still necessary given the removal of the MaxTime setting from TokenCacheOptions?
  2. If it is still needed, should it be updated to test the new timeout mechanism in ClaimsFillerOptions mentioned in the PR objectives?
  3. If it's no longer relevant, should it be removed entirely?

Consider running the following script to check for any remaining references to MaxTime in the TokenCacheOptions class:

Based on the results, we can determine if this test needs to be updated, replaced, or removed.

src/Arc4u.Standard.OAuth2.AspNetCore/TokenProvider/CredentialTokenCacheTokenProvider.cs (1)

84-84: Improved token caching with dynamic expiration

This change enhances the token caching mechanism by using the token's actual expiration time instead of a fixed timeout. This approach is more accurate and efficient, aligning well with the PR objectives.

Benefits:

  1. More precise cache management based on each token's specific lifetime.
  2. Potential reduction in unnecessary token refreshes.
  3. Improved security by ensuring tokens are not kept in cache beyond their actual expiration time.

The implementation looks correct and doesn't introduce any apparent issues.

src/Arc4u.Standard.UnitTest/Security/gRpcInterceptorTests.cs (5)

11-13: LGTM: New dependencies added to support updated test scenarios.

The additional using statements for OAuth2, gRPC, and ASP.NET Core components are appropriate for the context of this test file. These imports align with the purpose of testing gRPC interceptors in an OAuth2 environment.

Also applies to: 22-22, 24-24


Line range hint 233-237: LGTM: Improved flexibility in test configuration setup.

The addition of the foreach loop to dynamically populate the configuration dictionary with scopes enhances the test's flexibility. This change allows for easier modification of the number of scopes in the test setup, which is a good practice for maintaining and extending tests.


266-266: LGTM: Token cache timeout removal implemented correctly.

The modification to use TimeSpan.MaxValue in the TokenCache mock setup aligns perfectly with the PR objective of removing the fixed timeout for the token cache. This change effectively relies on the token's inherent expiration, streamlining the caching process as intended.


539-539: LGTM: Improved comment placement for better readability.

The relocation of the comment "Register the different TokenProvider and CredentialTokenProviders." provides better context for the following code block. This minor change enhances the readability of the test setup.


Line range hint 1-585: Overall changes align well with PR objectives and maintain test integrity.

The modifications to this test file successfully implement the changes required by the PR objectives, particularly regarding the removal of fixed token cache timeouts. The new dependencies and updated test scenarios reflect these changes while maintaining the overall structure and coverage of the original tests. The implementation appears sound and consistent throughout the file.

src/Arc4u.Standard.UnitTest/Security/JwtHttpHandlerTests.cs (3)

112-118: LGTM: Improved test setup with token information

The addition of TokenRefreshInfo setup enhances the test scenario by providing necessary refresh and access token information. This change improves the completeness of the test case.


198-199: LGTM: Enhanced AppPrincipal setup with bearer token

The modification improves the test scenario by setting up an AppPrincipal with a BootstrapContext containing an access token. This change provides a more realistic representation of an authenticated principal with a bearer token.


Line range hint 244-248: LGTM: Dynamic scope configuration

The addition of this foreach loop enhances the test setup by dynamically adding scopes to the configuration dictionary. This approach allows for flexible and maintainable scope configuration in the test scenario.

src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (1)

102-102: Improved performance with HashSet for claim exclusion

Converting ClaimsToExclude to a HashSet<string> with StringComparer.OrdinalIgnoreCase enhances performance during claim exclusion checks due to faster lookup times compared to arrays or lists.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (3)

127-133: Improved caching logic: Simplified and potentially more efficient.

The updated code for handling cached claims is a good improvement. It simplifies the logic by removing the explicit expiration check and directly adds the cached claims to the identity. This change improves readability and potentially performance.

However, there's a minor optimization opportunity:

Consider using !identity.HasClaim(c => StringComparer.OrdinalIgnoreCase.Equals(c.Type, cachedClaim.ClaimType)) instead of !identity.Claims.Any(...) to potentially improve performance. Here's the suggested change:

 identity.AddClaims(cachedClaims.Where(c => !identity.Claims.Any(c1 => StringComparer.OrdinalIgnoreCase.Equals(c1.Type, c.ClaimType)))
-                               .Select(c => new Claim(c.ClaimType, c.Value)));
+                               .Where(c => !identity.HasClaim(c1 => StringComparer.OrdinalIgnoreCase.Equals(c1.Type, c.ClaimType)))
+                               .Select(c => new Claim(c.ClaimType, c.Value)));

This change uses the HasClaim method, which might be more efficient than querying the entire Claims collection.


152-162: Improved expiration date handling: More robust and comprehensive.

The new logic for parsing and handling expiration dates is a significant improvement. It checks for expiration claims from multiple sources and provides a fallback mechanism. This approach ensures that an expiration date is always available for caching purposes.

To further improve readability and maintainability, consider the following refactoring:

private DateTimeOffset GetExpirationDate(IEnumerable<ClaimDto> claims, ClaimsIdentity identity)
{
    var expClaim = claims.FirstOrDefault(c => StringComparer.OrdinalIgnoreCase.Equals(c.ClaimType, tokenExpirationClaimType))
                   ?? identity.Claims.FirstOrDefault(c => StringComparer.OrdinalIgnoreCase.Equals(c.Type, tokenExpirationClaimType));

    if (expClaim != null && long.TryParse(expClaim.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var ticks))
    {
        return DateTimeOffset.FromUnixTimeSeconds(ticks);
    }

    return DateTimeOffset.UtcNow.Add(_options.MaxTime);
}

Then, you can simplify the main method:

var expDate = GetExpirationDate(claims, identity);
SaveClaimsToCache(claims, cacheKey, expDate - DateTimeOffset.UtcNow);

This refactoring encapsulates the expiration date logic in a separate method, improving readability and making the code easier to maintain.


Line range hint 194-218: Improved cache saving logic with better error handling.

The updates to the SaveClaimsToCache method are good improvements:

  1. The method now accepts a TimeSpan parameter for more flexible cache timeout management.
  2. Proper null checks and argument validation have been added.
  3. The method checks if the timeout is valid before saving to cache, preventing potential issues.
  4. Error handling has been improved with try-catch blocks and appropriate logging.

These changes enhance the robustness and reliability of the caching mechanism.

For consistency and to follow best practices, consider the following minor improvement:

Move the ArgumentNullException.ThrowIfNull(claims); check to the beginning of the method, before the cacheKey check. This ensures that all argument validations are performed upfront:

private void SaveClaimsToCache([DisallowNull] IEnumerable<ClaimDto> claims, string? cacheKey, TimeSpan timeout)
{
    ArgumentNullException.ThrowIfNull(claims);

    if (string.IsNullOrWhiteSpace(cacheKey))
    {
        return;
    }

    // Rest of the method...
}

This change improves the method's consistency and adheres to the general practice of validating arguments at the beginning of a method.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c747c36 and 8332c5c.

📒 Files selected for processing (4)
  • src/Arc4u.Standard.OAuth2.AspNetCore.Adal/Token/Cache.cs (2 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (5 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/Options/ClaimsFillerOptions.cs (1 hunks)
  • src/Arc4u.Standard.OAuth2/Token/ITokenCache.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Arc4u.Standard.OAuth2.AspNetCore.Adal/Token/Cache.cs
  • src/Arc4u.Standard.OAuth2.AspNetCore/Options/ClaimsFillerOptions.cs
  • src/Arc4u.Standard.OAuth2/Token/ITokenCache.cs
🧰 Additional context used
🔇 Additional comments (4)
src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (4)

13-13: LGTM: Added necessary namespace for dependency injection.

The addition of using Microsoft.Extensions.DependencyInjection; is appropriate, likely to support the use of dependency injection features in the class.


102-102: Great optimization: Improved claim filtering efficiency.

Changing ClaimsToExclude from an array to a HashSet<string> with StringComparer.OrdinalIgnoreCase is an excellent optimization. This change offers two main benefits:

  1. Faster membership checks due to HashSet's O(1) average time complexity.
  2. Case-insensitive comparisons, ensuring consistent filtering regardless of claim type casing.

These improvements will enhance the overall performance of claim filtering operations.


145-150: Improved claim filtering and addition logic.

The updates to claim filtering and addition are good improvements:

  1. Using ClaimsToExclude.Contains() for filtering is more efficient due to the HashSet implementation.
  2. The logic for adding claims to the identity has been simplified and optimized.

However, there's still a redundant filtering operation that was mentioned in a previous review.

As suggested in a previous review, the second Where clause on line 149 is redundant since the claims are already filtered using ClaimsToExclude on line 145. Consider removing this redundant filtering to further optimize the code:

 identity.AddClaims(claims.Where(c => !StringComparer.OrdinalIgnoreCase.Equals(c.ClaimType, tokenExpirationClaimType))
-                        .Where(c => !identity.Claims.Any(c1 => StringComparer.OrdinalIgnoreCase.Equals(c1.Type, c.ClaimType)))
                         .Select(c => new Claim(c.ClaimType, c.Value)));

166-176: Well-implemented expiration date parsing method.

The TryParseExpirationDate method is a good addition to the class. It provides a clean and reusable way to parse expiration dates from claim values. The use of the out parameter pattern is appropriate for this scenario, allowing the method to return both a boolean indicating success and the parsed value.

Some positive aspects of this implementation:

  1. It's private and static, which is correct for its intended use within the class.
  2. It handles null values and parsing errors gracefully.
  3. It uses CultureInfo.InvariantCulture for parsing, ensuring consistent behavior across different locales.

…im names (a.k.a. "types") are case-sensitive.

Claims manipulations have been factored out in a separate utility class.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8332c5c and f0c3fc6.

📒 Files selected for processing (4)
  • src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (3 hunks)
  • src/Arc4u.Standard.OAuth2.AspNetCore/Security/AppServicePrincipalFactory.cs (2 hunks)
  • src/Arc4u.Standard.OAuth2.Client/Security/Principal/AppPrincipalFactory.cs (2 hunks)
  • src/Arc4u.Standard.OAuth2/Security/ClaimsUtility.cs (1 hunks)
🧰 Additional context used
🔇 Additional comments (10)
src/Arc4u.Standard.OAuth2.AspNetCore/Security/AppServicePrincipalFactory.cs (4)

11-11: LGTM: New namespace import aligns with PR objectives.

The addition of using Arc4u.Standard.OAuth2.Security; is consistent with the changes described in the PR objectives, particularly regarding token caching and claim handling modifications.


102-102: LGTM: Improved method signature and variable scoping.

The addition of the settingsResolveName parameter enhances flexibility in configuration resolution, aligning with the PR's goal of streamlining the caching process. Additionally, declaring the token variable inline within the try block is a good practice that limits the variable's scope.


Line range hint 1-146: Overall assessment: Changes align with PR objectives and improve code quality.

The modifications to AppServicePrincipalFactory.cs align well with the PR objectives. Key improvements include:

  1. Enhanced flexibility through the addition of the settingsResolveName parameter.
  2. Streamlined claim handling with the new MergeClaims and Sanitize methods.
  3. Optimized variable scoping for the token variable.

These changes contribute to a more efficient and maintainable codebase. However, ensure thorough testing of the new claim handling methods to maintain security and correctness.


105-105: Verify implementation of new claim handling methods.

The new approach using identity.MergeClaims(jwtToken.Claims.Sanitize()) appears to streamline claim handling, which aligns with the PR objectives. However, please ensure that the MergeClaims and Sanitize methods are properly implemented and thoroughly tested to maintain security and correctness.

To verify the implementation of these methods, please run the following script:

src/Arc4u.Standard.OAuth2/Security/ClaimsUtility.cs (2)

27-37: Verify the handling of the "exp" claim in Sanitize methods

In the Sanitize method for IEnumerable<ClaimDto> (lines 27-37), the "exp" claim is included in the output claims. Conversely, in the Sanitize method for IEnumerable<Claim> (lines 46-56), the "exp" claim is explicitly excluded. Please verify if this difference is intentional and aligns with the desired behavior.

Also applies to: 46-56


19-19: ⚠️ Potential issue

Fix syntax error in ClaimsToExclude initialization

There is a syntax error in the initialization of the ClaimsToExclude HashSet. In C#, you should initialize a HashSet<string> using new HashSet<string> { ... }.

Apply this diff to fix the syntax error:

-private static readonly HashSet<string> ClaimsToExclude = ["aud", "iss", "iat", "nbf", "acr", "aio", "appidacr", "ipaddr", "scp", "sub", "tid", "uti", "unique_name", "apptype", "appid", "ver", "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant", "http://schemas.microsoft.com/identity/claims/scope"];
+private static readonly HashSet<string> ClaimsToExclude = new HashSet<string> { "aud", "iss", "iat", "nbf", "acr", "aio", "appidacr", "ipaddr", "scp", "sub", "tid", "uti", "unique_name", "apptype", "appid", "ver", "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant", "http://schemas.microsoft.com/identity/claims/scope" };

Likely invalid or redundant comment.

src/Arc4u.Standard.OAuth2.AspNetCore/AppPrincipalTransform.cs (1)

151-160: Ensure cache timeout is non-negative to prevent errors

Calculating the timeout as DateTimeOffset.FromUnixTimeSeconds(ticks) - DateTimeOffset.UtcNow may result in a negative TimeSpan if the expiration time is in the past. Ensure the timeout is non-negative before proceeding.

src/Arc4u.Standard.OAuth2.Client/Security/Principal/AppPrincipalFactory.cs (3)

13-13: Importing Arc4u.Standard.OAuth2.Security is necessary

The added using directive using Arc4u.Standard.OAuth2.Security; is essential for accessing methods like TryGetExpiration, Sanitize(), and MergeClaims used later in the code.


126-129: Efficiently determine if claims can be copied from cache

The updated logic accurately checks if the token's expiration matches the cached claims' expiration. This ensures that cached claims are reused only when valid, reducing unnecessary backend calls.


149-154: Properly retrieve and merge additional claims

The code correctly retrieves additional claims using claimFiller.GetAsync, sanitizes them, and merges them into the identity. Logging the number of claims added enhances traceability and debugging capabilities.

Comment on lines +172 to +177
return _cacheHelper.GetCache().Get<List<ClaimDto>>(cacheKey);
}
catch (Exception ex)
{
_logger.Technical().Exception(ex).Log();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid catching general exceptions

Catching the general Exception type may mask specific issues and make debugging more difficult. Consider catching more specific exceptions that are expected from the cache retrieval operation.

Comment on lines +196 to +200
_cacheHelper.GetCache().Put(cacheKey, timeout, claims);
}
catch (Exception ex)
{
_logger.Technical().Exception(ex).Log();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid catching general exceptions

In the cache save operation, catching the general Exception type can hide specific errors and complicate error handling. It's advisable to catch specific exceptions that you anticipate from the cache put operation.

Comment on lines +137 to +142
identity.AddClaims(jwtToken.Claims.Sanitize().Select(c => new Claim(c.Type, c.Value)));
// there is no guarantee that expTokenClaim is not null, so we need to check it.
if (expTokenClaim is not null)
{
identity.AddClaim(expTokenClaim);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Verify potential duplication of the exp claim

Since Sanitize() might not remove the exp claim from jwtToken.Claims, adding expTokenClaim separately could result in duplicate exp claims in the identity. Please verify whether Sanitize() removes the exp claim to prevent duplication.

To confirm, check the implementation of Sanitize():

#!/bin/bash
# Description: Verify if the `exp` claim is removed by the Sanitize() method.

# Search for the Sanitize() method implementation
fd --type f --extension cs --exec rg --type cs 'ClaimExtensions\.Sanitize\(\)'

If Sanitize() preserves the exp claim, consider avoiding adding expTokenClaim again or modify Sanitize() accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant