Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Commit

Permalink
Add unit tests for token storage in auth properties
Browse files Browse the repository at this point in the history
  • Loading branch information
josephdecock committed May 2, 2024
1 parent 9c3abb7 commit 66307f2
Show file tree
Hide file tree
Showing 6 changed files with 449 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class AuthenticationSessionUserAccessTokenStore : IUserTokenStore
{
private readonly IHttpContextAccessor _contextAccessor;
private readonly IStoreTokensInAuthenticationProperties _tokensInProps;
private readonly IAuthenticationSchemeProvider _schemeProvider;
private readonly ILogger<AuthenticationSessionUserAccessTokenStore> _logger;

// per-request cache so that if SignInAsync is used, we won't re-read the old/cached AuthenticateResult from the handler
Expand All @@ -35,13 +34,11 @@ public class AuthenticationSessionUserAccessTokenStore : IUserTokenStore
public AuthenticationSessionUserAccessTokenStore(
IHttpContextAccessor contextAccessor,
IStoreTokensInAuthenticationProperties tokensInProps,
IAuthenticationSchemeProvider schemeProvider,
ILogger<AuthenticationSessionUserAccessTokenStore> logger)
{
_contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
_logger = logger;
_tokensInProps = tokensInProps;
_schemeProvider = schemeProvider;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ namespace Duende.AccessTokenManagement.OpenIdConnect;
/// <inheritdoc/>
public class StoreTokensInAuthenticationProperties(
IOptionsMonitor<UserTokenManagementOptions> tokenManagementOptionsMonitor,
IOptionsMonitor<CookieAuthenticationOptions> cookieOptionsMonitor,
IAuthenticationSchemeProvider schemeProvider,
ILogger<StoreTokensInAuthenticationProperties> logger,
IOptionsMonitor<CookieAuthenticationOptions> cookieOptionsMonitor) : IStoreTokensInAuthenticationProperties
ILogger<StoreTokensInAuthenticationProperties> logger
) : IStoreTokensInAuthenticationProperties
{
private const string TokenPrefix = ".Token.";
private const string TokenNamesKey = ".TokenNames";
Expand Down Expand Up @@ -166,9 +167,34 @@ public void RemoveUserToken(AuthenticationProperties authenticationProperties, U
var names = GetTokenNamesWithScheme(parameters);
authenticationProperties.Items.Remove(names.Token);
authenticationProperties.Items.Remove(names.TokenType);
authenticationProperties.Items.Remove(names.DPoPKey);
authenticationProperties.Items.Remove(names.Expires);
authenticationProperties.Items.Remove(names.RefreshToken);

// The DPoP key and refresh token are shared with all resources, so we
// can only delete them if no other tokens with a different resource
// exist. The key and refresh token are shared for all resources within
// a challenge scheme if we are using a challenge scheme.

var keys = authenticationProperties.Items.Keys.Where(k =>
k.StartsWith(NamePrefix(OpenIdConnectParameterNames.AccessToken)));

var usingChallengeSuffix = AppendChallengeSchemeToTokenNames(parameters);
if (usingChallengeSuffix)
{
var challengeScheme = parameters?.ChallengeScheme ?? throw new InvalidOperationException("Attempt to use challenge scheme in token names, but no challenge scheme specified in UserTokenRequestParameters");
var challengeSuffix = $"||{challengeScheme}";
keys = keys.Where(k => k.EndsWith(challengeSuffix));
}

// If we see a resource separator now, we know there are other resources
// using the refresh token and/or dpop key and so we shouldn't delete
// them
var otherResourcesExist = keys.Any(k => k.Contains("::"));

if(!otherResourcesExist)
{
authenticationProperties.Items.Remove(names.DPoPKey);
authenticationProperties.Items.Remove(names.RefreshToken);
}
}

private TokenNames GetTokenNamesWithoutScheme(UserTokenRequestParameters? parameters = null)
Expand Down
2 changes: 0 additions & 2 deletions test/Tests/ClientTokenManagementApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@

using Duende.IdentityServer.Configuration;
using IdentityModel;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;

Expand Down
22 changes: 22 additions & 0 deletions test/Tests/Framework/TestOptionsMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Microsoft.Extensions.Options;

namespace Duende.AccessTokenManagement.Tests;

public class TestOptionsMonitor<TOptions>(TOptions? currentValue = null) : IOptionsMonitor<TOptions>
where TOptions : class, new()
{
public TOptions CurrentValue { get; set; } = currentValue ?? new();

public TOptions Get(string? name)
{
return CurrentValue;
}

public IDisposable? OnChange(Action<TOptions, string?> listener)
{
throw new NotImplementedException();
}
}
72 changes: 72 additions & 0 deletions test/Tests/Framework/TestSchemeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;

namespace Duende.AccessTokenManagement.Tests;

public class TestSchemeProvider : IAuthenticationSchemeProvider
{
public TestSchemeProvider(string signInSchemeName = "testScheme")
{
DefaultSignInScheme = new AuthenticationScheme(signInSchemeName, signInSchemeName, typeof(CookieAuthenticationHandler));
}

public AuthenticationScheme? DefaultSignInScheme { get; set; }

public Task<AuthenticationScheme?> GetDefaultSignInSchemeAsync()
{
return Task.FromResult(DefaultSignInScheme);
}

#region Not Implemented (No tests have needed these yet)

public void AddScheme(AuthenticationScheme scheme)
{
throw new NotImplementedException();
}

public Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
{
throw new NotImplementedException();
}

public Task<AuthenticationScheme?> GetDefaultAuthenticateSchemeAsync()
{
throw new NotImplementedException();
}

public Task<AuthenticationScheme?> GetDefaultChallengeSchemeAsync()
{
throw new NotImplementedException();
}

public Task<AuthenticationScheme?> GetDefaultForbidSchemeAsync()
{
throw new NotImplementedException();
}


public Task<AuthenticationScheme?> GetDefaultSignOutSchemeAsync()
{
throw new NotImplementedException();
}

public Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
{
throw new NotImplementedException();
}

public Task<AuthenticationScheme?> GetSchemeAsync(string name)
{
throw new NotImplementedException();
}

public void RemoveScheme(string name)
{
throw new NotImplementedException();
}

#endregion
}
Loading

0 comments on commit 66307f2

Please sign in to comment.