Skip to content

Commit

Permalink
Ticket #771 : Can resolve trust chain
Browse files Browse the repository at this point in the history
Can validate trust chain
Add some UTS to check the exceptions
Start to support automatic registration
  • Loading branch information
thabart committed Jul 15, 2024
1 parent 9d9faaa commit 30522a9
Show file tree
Hide file tree
Showing 66 changed files with 3,562 additions and 2,894 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) SimpleIdServer. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using Microsoft.Extensions.Logging;
using SimpleIdServer.IdServer.Domains;
using SimpleIdServer.IdServer.Exceptions;



// Copyright (c) SimpleIdServer. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using SimpleIdServer.IdServer.Helpers;
using SimpleIdServer.IdServer.Stores;
using SimpleIdServer.OpenidFederation.Clients;
using SimpleIdServer.OpenidFederation.Stores;
using System.Text.Json;

namespace SimpleIdServer.IdServer.Federation.Helpers;

public class FederationClientHelper : StandardClientHelper
{
private readonly ILogger<FederationClientHelper> _logger;
private readonly IFederationEntityStore _federationEntityStore;

public FederationClientHelper(
IdServer.Helpers.IHttpClientFactory httpClientFactory,
IClientRepository clientRepository,
ILogger<FederationClientHelper> logger,
IFederationEntityStore federationEntityStore) : base(httpClientFactory, clientRepository)
{
_logger = logger;
_federationEntityStore = federationEntityStore;
}

/// <summary>
/// Resolve the client.
/// Two scenarios are supported :
/// 1). Returns the client from the database.
/// 2). Resolve the trust chain and returns the client.
/// </summary>
/// <param name="realm"></param>
/// <param name="clientId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="OAuthException"></exception>
public override async Task<Client> ResolveClient(string realm, string clientId, CancellationToken cancellationToken)
{
var result = await base.ResolveClient(realm, clientId, cancellationToken);
if (result == null)
result = await ResolveClientByOpenidFederation(realm, clientId, cancellationToken);

return result;
}

private async Task<Client> ResolveClientByOpenidFederation(string realm, string clientId, CancellationToken cancellationToken)
{
const string metadataName = "openid_relying_party";
var resolver = TrustChainResolver.New();
List<OpenidTrustChain> trustChains = null;
try
{
trustChains = await resolver.ResolveTrustChains(clientId, cancellationToken);
}
catch (Exception ex)
{
var message = ex.ToString();
_logger.LogError(message);
throw new OAuthException(ErrorCodes.INVALID_REQUEST, message);
}

var allAuthorities = await _federationEntityStore.GetAllAuthorities(realm, cancellationToken);
var allTrustAnchors = trustChains.Select(c => c.TrustAnchor);
var filteredTrustChain = trustChains.FirstOrDefault(tc => allAuthorities.Any(at => at.Sub == tc.TrustAnchor.FederationResult.Sub));
if (filteredTrustChain == null)
throw new OAuthException(ErrorCodes.MISSING_TRUST_ANCHOR, Resources.Global.NoTrustAnchorCanBeResolved);


var federationResult = filteredTrustChain.EntityStatements.First().FederationResult;
if (federationResult.Metadata == null ||
federationResult.Metadata.OtherParameters == null ||
!federationResult.Metadata.OtherParameters.ContainsKey(metadataName))
throw new OAuthException(ErrorCodes.INVALID_REQUEST, Resources.Global.MissingOpenidRpInEntityStatement);
var client = JsonSerializer.Deserialize<Client>(federationResult.Metadata.OtherParameters[metadataName]);
return client;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// Copyright (c) SimpleIdServer. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using Microsoft.Extensions.DependencyInjection.Extensions;
using SimpleIdServer.IdServer.Federation;
using SimpleIdServer.IdServer.Federation.Builders;
using SimpleIdServer.IdServer.Federation.Helpers;
using SimpleIdServer.IdServer.Helpers;

namespace Microsoft.Extensions.DependencyInjection;

Expand All @@ -13,6 +16,8 @@ public static IdServerBuilder AddOpenidFederation(this IdServerBuilder builder,
if (cb != null) builder.Services.Configure(cb);
else builder.Services.Configure(cb);
builder.Services.AddTransient<IOpenidProviderFederationEntityBuilder, OpenidProviderFederationEntityBuilder>();
builder.Services.RemoveAll<IClientHelper>();
builder.Services.AddTransient<IClientHelper, FederationClientHelper>();
return builder;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,10 @@
<data name="InvalidIssuer" xml:space="preserve">
<value>the issuer is not valid</value>
</data>
<data name="MissingOpenidRpInEntityStatement" xml:space="preserve">
<value>the openid_relying_party doesn't exist in the entity statement</value>
</data>
<data name="NoTrustAnchorCanBeResolved" xml:space="preserve">
<value>no trust anchor can be resolved</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,17 @@
<ProjectReference Include="..\SimpleIdServer.IdServer\SimpleIdServer.IdServer.csproj" />
<ProjectReference Include="..\SimpleIdServer.OpenidFederation\SimpleIdServer.OpenidFederation.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Global.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Global.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Global.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Global.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
87 changes: 44 additions & 43 deletions src/IdServer/SimpleIdServer.IdServer/ErrorCodes.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,48 @@
// Copyright (c) SimpleIdServer. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
namespace SimpleIdServer.IdServer
namespace SimpleIdServer.IdServer;

public static class ErrorCodes
{
public static class ErrorCodes
{
public const string INVALID_REQUEST = "invalid_request";
public const string INVALID_CLIENT = "invalid_client";
public const string INVALID_REDIRECT_URI = "invalid_redirect_uri";
public const string INVALID_CLIENT_METADATA = "invalid_client_metadata";
public const string INVALID_CLIENT_METADATA_URI = "invalid_client_metadata_uri";
public const string INVALID_SOFTWARE_STATEMENT = "invalid_software_statement";
public const string INVALID_NETWORK = "invalid_network";
public const string INVALID_GRANT = "invalid_grant";
public const string INVALID_TOKEN = "invalid_token";
public const string INVALID_SCOPE = "invalid_scope";
public const string INVALID_DEVICE_CODE = "invalid_device_code";
public const string INVALID_DPOP_PROOF = "invalid_dpop_proof";
public const string INVALID_REQUEST_OBJECT = "invalid_request_object";
public const string UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
public const string UNSUPPORTED_TOKEN_TYPE = "unsupported_token_type";
public const string UNSUPPORTED_RESPONSE_MODE = "unsupported_response_mode";
public const string UNSUPPORTED_RESPONSE_TYPE = "unsupported_response_type";
public const string LOGIN_REQUIRED = "login_required";
public const string ACCESS_DENIED = "access_denied";
public const string INVALID_BINDING_MESSAGE = "invalid_binding_message";
public const string EXPIRED_LOGIN_HINT_TOKEN = "expired_login_hint_token";
public const string UNKNOWN_USER_ID = "unknown_user_id";
public const string AUTHORIZATION_PENDING = "authorization_pending";
public const string EXPIRED_TOKEN = "expired_token";
public const string SLOW_DOWN = "slow_down";
public const string UNKNOWN_USER = "unknown_user";
public const string UNKNOWN_ACR = "unknown_acr";
public const string UNKNOWN_AMR = "unknown_amr";
public const string INVALID_CREDENTIALS = "invalid_credentials";
public const string INVALID_TARGET = "invalid_target";
public const string REQUEST_DENIED = "request_denied";
public const string INVALID_GRANT_ID = "invalid_grant_id";
public const string INVALID_RESOURCE_ID = "invalid_resource_id";
public const string NOT_FOUND = "not_found";
public const string INVALID_AUTHORIZATION_DETAILS = "invalid_authorization_details";
public const string UNEXPECTED_ERROR = "unexpected_error";
public const string USE_DPOP_NONCE = "use_dpop_nonce";
public const string INACTIVE_SESSION = "inactive_session";
public const string NO_ACTIVE_OTP = "no_active_otp";
}
public const string INVALID_REQUEST = "invalid_request";
public const string INVALID_CLIENT = "invalid_client";
public const string INVALID_REDIRECT_URI = "invalid_redirect_uri";
public const string INVALID_CLIENT_METADATA = "invalid_client_metadata";
public const string INVALID_CLIENT_METADATA_URI = "invalid_client_metadata_uri";
public const string INVALID_SOFTWARE_STATEMENT = "invalid_software_statement";
public const string INVALID_NETWORK = "invalid_network";
public const string INVALID_GRANT = "invalid_grant";
public const string INVALID_TOKEN = "invalid_token";
public const string INVALID_SCOPE = "invalid_scope";
public const string INVALID_DEVICE_CODE = "invalid_device_code";
public const string INVALID_DPOP_PROOF = "invalid_dpop_proof";
public const string INVALID_REQUEST_OBJECT = "invalid_request_object";
public const string UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
public const string UNSUPPORTED_TOKEN_TYPE = "unsupported_token_type";
public const string UNSUPPORTED_RESPONSE_MODE = "unsupported_response_mode";
public const string UNSUPPORTED_RESPONSE_TYPE = "unsupported_response_type";
public const string LOGIN_REQUIRED = "login_required";
public const string ACCESS_DENIED = "access_denied";
public const string INVALID_BINDING_MESSAGE = "invalid_binding_message";
public const string EXPIRED_LOGIN_HINT_TOKEN = "expired_login_hint_token";
public const string UNKNOWN_USER_ID = "unknown_user_id";
public const string AUTHORIZATION_PENDING = "authorization_pending";
public const string EXPIRED_TOKEN = "expired_token";
public const string SLOW_DOWN = "slow_down";
public const string UNKNOWN_USER = "unknown_user";
public const string UNKNOWN_ACR = "unknown_acr";
public const string UNKNOWN_AMR = "unknown_amr";
public const string INVALID_CREDENTIALS = "invalid_credentials";
public const string INVALID_TARGET = "invalid_target";
public const string REQUEST_DENIED = "request_denied";
public const string INVALID_GRANT_ID = "invalid_grant_id";
public const string INVALID_RESOURCE_ID = "invalid_resource_id";
public const string NOT_FOUND = "not_found";
public const string INVALID_AUTHORIZATION_DETAILS = "invalid_authorization_details";
public const string UNEXPECTED_ERROR = "unexpected_error";
public const string USE_DPOP_NONCE = "use_dpop_nonce";
public const string INACTIVE_SESSION = "inactive_session";
public const string NO_ACTIVE_OTP = "no_active_otp";
public const string VALIDATION_FAILED = "validation_failed";
public const string MISSING_TRUST_ANCHOR = "missing_trust_anchor";
}
13 changes: 11 additions & 2 deletions src/IdServer/SimpleIdServer.IdServer/Helpers/IClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using SimpleIdServer.IdServer.Domains;
using SimpleIdServer.IdServer.Exceptions;
using SimpleIdServer.IdServer.Resources;
using SimpleIdServer.IdServer.Stores;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -22,16 +23,21 @@ public interface IClientHelper
Task<IEnumerable<JsonWebKey>> ResolveJsonWebKeys(Client client, CancellationToken cancellationToken);
Task<IEnumerable<JsonWebKey>> ResolveJsonWebKeys(string jwksUri, CancellationToken cancellationToken);
bool IsNonPreRegisteredRelyingParty(string clientId);
Task<Client> ResolveClient(string realm, string clientId, CancellationToken cancellationToken);
Task<Client> ResolveSelfDeclaredClient(JsonObject request, CancellationToken cancellationToken);
}

public class OAuthClientHelper : IClientHelper
public class StandardClientHelper : IClientHelper
{
private readonly Helpers.IHttpClientFactory _httpClientFactory;
private readonly IClientRepository _clientRepository;

public OAuthClientHelper(Helpers.IHttpClientFactory httpClientFactory)
public StandardClientHelper(
Helpers.IHttpClientFactory httpClientFactory,
IClientRepository clientRepository)
{
_httpClientFactory = httpClientFactory;
_clientRepository = clientRepository;
}

public virtual async Task<IEnumerable<string>> GetRedirectionUrls(Client client, CancellationToken cancellationToken)
Expand Down Expand Up @@ -102,6 +108,9 @@ public bool IsNonPreRegisteredRelyingParty(string clientId)
return false;
}

public virtual Task<Client> ResolveClient(string realm, string clientId, CancellationToken cancellationToken)
=> _clientRepository.GetByClientId(realm, clientId, cancellationToken);

public async Task<Client> ResolveSelfDeclaredClient(JsonObject request, CancellationToken cancellationToken)
{
var clientMetadataUri = request.GetClientMetadataUri();
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/IdServer/SimpleIdServer.IdServer/Resources/Global.resx
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,9 @@
<data name="MissingIdTokenHint" xml:space="preserve">
<value>id_token_hint parameter is missing</value>
</data>
<data name="MissingOpenidRpInEntityStatement" xml:space="preserve">
<value>the openid_relying_party doesn't exist in the entity statement</value>
</data>
<data name="MissingParameter" xml:space="preserve">
<value>missing parameter {0}</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ private static IServiceCollection AddOAuthTokenApi(this IServiceCollection servi
services.AddTransient<IClaimsJwsPayloadEnricher, ClaimsJwsPayloadEnricher>();
services.AddTransient<ICodeChallengeMethodHandler, PlainCodeChallengeMethodHandler>();
services.AddTransient<ICodeChallengeMethodHandler, S256CodeChallengeMethodHandler>();
services.AddTransient<IClientHelper, OAuthClientHelper>();
services.AddTransient<IClientHelper, StandardClientHelper>();
services.AddTransient<IAmrHelper, AmrHelper>();
services.AddTransient<IUmaPermissionTicketHelper, UMAPermissionTicketHelper>();
services.AddTransient<IExtractRequestHelper, ExtractRequestHelper>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ protected async Task<IActionResult> Get(FederationFetchRequest request, string r
issuer,
cancellationToken);
if (validationResult.ErrorCode != null) return Error(validationResult.HttpStatusCode, validationResult.ErrorCode, validationResult.ErrorMessage);
// UPDATE ISS and JWKS !!!
var handler = new JsonWebTokenHandler();
var jws = handler.CreateToken(JsonSerializer.Serialize(validationResult.OpenidFederationResult), new SigningCredentials(signingCredential.Key, signingCredential.Algorithm));
return new ContentResult
Expand Down Expand Up @@ -71,7 +70,7 @@ private async Task<FederationFetchValidationResult> Validate(
{
var cacheKey = GetCacheKey(request.Sub);
var entityStatement = await _federationEntityStore.GetSubordinate(request.Sub, realm, cancellationToken);
if (entityStatement == null) return FederationFetchValidationResult.Error(ErrorCodes.NOT_FOUND, Resources.Global.UnknownEntityStatement);
if (entityStatement == null) return FederationFetchValidationResult.Error(ErrorCodes.NOT_FOUND, string.Format(Resources.Global.UnknownEntityStatement, request.Sub));
var cacheOpenidFederation = await _distributedCache.GetAsync(cacheKey, cancellationToken);
if (cacheOpenidFederation == null)
{
Expand All @@ -81,9 +80,10 @@ private async Task<FederationFetchValidationResult> Validate(
if(openidFederation == null) return FederationFetchValidationResult.Error(ErrorCodes.INVALID_REQUEST, Resources.Global.ImpossibleToExtractOpenidFederation);
await _distributedCache.SetStringAsync(cacheKey, JsonSerializer.Serialize(openidFederation), new DistributedCacheEntryOptions
{
AbsoluteExpiration = openidFederation.ValidTo
AbsoluteExpiration = openidFederation.FederationResult.ValidTo
}, cancellationToken);
return FederationFetchValidationResult.Ok(openidFederation);
openidFederation.FederationResult.Iss = issuer;
return FederationFetchValidationResult.Ok(openidFederation.FederationResult);
}
}

Expand Down
Loading

0 comments on commit 30522a9

Please sign in to comment.