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

Fnymmm/refactor identity service #74

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using IdentityModel.Client;
using IdOps.Models;

namespace IdOps.Abstractions
{
public interface IIdentityService
{
Task<RequestTokenResult> RequestTokenAsync(
TokenRequestData request,
TokenRequest request,
CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public interface IResultFactory<TResult, in TInput>
{
Task<TResult> Create(TInput input, CancellationToken cancellationToken);
Task<TResult> CreateRequestAsync(TInput input, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ Task<TokenResponse> RequestTokenAsync(
Task<TokenResponse> RequestClientCredentialsTokenAsync(
ClientCredentialsTokenRequest request,
CancellationToken cancellationToken);

Task<TokenResponse> RequestAuthorizationCodeTokenAsync(
AuthorizationCodeTokenRequest request,
CancellationToken cancellationToken);
}
64 changes: 9 additions & 55 deletions src/Server/src/AuthTokenGenerator/IdentityService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using IdentityModel.Client;
using IdOps.Abstractions;
using IdOps.Models;
Expand All @@ -11,26 +9,23 @@ public class IdentityService : IIdentityService
private readonly ITokenClient _tokenClient;
private readonly ITokenAnalyzer _tokenAnalyzer;

public IdentityService(
ITokenClient tokenClient,
ITokenAnalyzer tokenAnalyzer)
public IdentityService(ITokenClient tokenClient, ITokenAnalyzer tokenAnalyzer)
{
_tokenClient = tokenClient;
_tokenAnalyzer = tokenAnalyzer;
}

public async Task<RequestTokenResult> RequestTokenAsync(
TokenRequestData request,
TokenRequest request,
CancellationToken cancellationToken)
{
DiscoveryDocumentResponse disco = await _tokenClient.GetDiscoveryDocumentAsync(
request.Authority,
cancellationToken);

TokenResponse response = request.GrantType switch
{
"client_credentials" => await RequestClientCredentialTokenAsync(request, disco,cancellationToken),
_ => await RequestOtherGrantTypeTokenAsync(request, disco, cancellationToken)
"client_credentials" => await _tokenClient.RequestClientCredentialsTokenAsync(
(ClientCredentialsTokenRequest)request, cancellationToken),
"authorization_code" => await _tokenClient.RequestAuthorizationCodeTokenAsync(
(AuthorizationCodeTokenRequest)request, cancellationToken),
_ => await _tokenClient.RequestTokenAsync(request, cancellationToken)
};

if (response.IsError)
Expand All @@ -46,7 +41,7 @@ public async Task<RequestTokenResult> RequestTokenAsync(

return result;
}

TokenModel? accessToken = _tokenAnalyzer.Analyze(response.AccessToken);
if (accessToken == null)
{
Expand All @@ -55,49 +50,8 @@ public async Task<RequestTokenResult> RequestTokenAsync(
ErrorMessage = "Access Token could not be analyzed"
};
}
return new RequestTokenResult(true)
{
AccessToken = accessToken
};
}

private async Task<TokenResponse> RequestClientCredentialTokenAsync(
TokenRequestData request,
DiscoveryDocumentResponse disco,
CancellationToken cancellationToken)
{
return await _tokenClient.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = request.ClientId,
ClientSecret = request.Secret,
GrantType = request.GrantType,
Scope = request.Scopes.Any() ? string.Join(" ", request.Scopes) : null
}, cancellationToken);
}

private async Task<TokenResponse> RequestOtherGrantTypeTokenAsync(
TokenRequestData request,
DiscoveryDocumentResponse disco,
CancellationToken cancellationToken)
{
var pars = new Dictionary<string, string>();

if (request.Scopes is { } s && s.Any())
{
pars.Add("scope", string.Join(" ", request.Scopes));
}

return await _tokenClient.RequestTokenAsync(
new TokenRequest
{
Address = disco.TokenEndpoint,
ClientId = request.ClientId,
ClientSecret = request.Secret,
GrantType = request.GrantType,
Parameters = new Parameters(pars)
}, cancellationToken);
return new RequestTokenResult(true) { AccessToken = accessToken };
}
}
}
13 changes: 12 additions & 1 deletion src/Server/src/AuthTokenGenerator/Models/TokenClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,18 @@ public async Task<TokenResponse> RequestClientCredentialsTokenAsync(
CancellationToken cancellationToken)
{
using HttpClient httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.RequestClientCredentialsTokenAsync(request, cancellationToken);
var response =
await httpClient.RequestClientCredentialsTokenAsync(request, cancellationToken);
return response;
}

public async Task<TokenResponse> RequestAuthorizationCodeTokenAsync(
AuthorizationCodeTokenRequest request,
CancellationToken cancellationToken)
{
using HttpClient httpClient = _httpClientFactory.CreateClient();
var response =
await httpClient.RequestAuthorizationCodeTokenAsync(request, cancellationToken);
return response;
}
}
11 changes: 0 additions & 11 deletions src/Server/src/AuthTokenGenerator/Models/TokenRequestData.cs

This file was deleted.

12 changes: 6 additions & 6 deletions src/Server/src/GraphQL/Client/ClientMutations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading.Tasks;
using HotChocolate;
using HotChocolate.Types;
using IdentityModel.Client;
using IdOps.Abstractions;
using IdOps.Model;
using IdOps.Models;
Expand Down Expand Up @@ -64,16 +65,15 @@ public async Task<SaveClientPayload> RemoveClientSecretAsync(
[AuthorizeClientAuthoring(AccessMode.Write, includeTenantAuth: false)]
public async Task<RequestTokenPayload> RequestTokenAsync(
[Service] IIdentityService identityService,
[Service] IResultFactory<TokenRequestData, RequestTokenInput> requestResultFactory,
[Service] IResultFactory<TokenRequest, RequestTokenInput> requestResultFactory,
RequestTokenInput input,
CancellationToken cancellationToken)
{
var tokenRequestData =
await requestResultFactory.Create(input,cancellationToken);


TokenRequest tokenRequest =
await requestResultFactory.CreateRequestAsync(input,cancellationToken);

RequestTokenResult tokenResult =
await identityService.RequestTokenAsync(tokenRequestData, cancellationToken);
await identityService.RequestTokenAsync(tokenRequest, cancellationToken);

return new RequestTokenPayload(tokenResult);
}
Expand Down
70 changes: 48 additions & 22 deletions src/Server/src/GraphQL/Client/TokenRequestDataResultFactory.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using IdentityModel.Client;
using IdOps.Abstractions;
using IdOps.Model;
using IdOps.Models;
using IdOps.Security;

namespace IdOps.GraphQL;

public class TokenRequestDataResultFactory : IResultFactory<TokenRequestData, RequestTokenInput>
public class TokenRequestDataResultFactory : IResultFactory<TokenRequest, RequestTokenInput>
{
private readonly IEncryptionService _encryptionService;
private readonly IClientService _clientService;
private readonly IApiScopeService _scopeService;
private readonly IHttpClientFactory _httpClientFactory;

public TokenRequestDataResultFactory(
IEncryptionService encryptionService,
IClientService clientService,
IApiScopeService apiScopeService)
IApiScopeService apiScopeService,
IHttpClientFactory httpClientFactory)
{
_encryptionService = encryptionService;
_clientService = clientService;
_scopeService = apiScopeService;
_httpClientFactory = httpClientFactory;
}


public async Task<TokenRequestData> Create(
public async Task<TokenRequest> CreateRequestAsync(
RequestTokenInput input,
CancellationToken cancellationToken)
{
if (input.grantType == null)
{
throw new ArgumentNullException("No grants found");
}

return input.grantType switch
{
"client_credentials" => await BuildClientCredentialsTokenRequestAsync(input,
cancellationToken),
_ => throw new Exception()
};
}

private async Task<ClientCredentialsTokenRequest> BuildClientCredentialsTokenRequestAsync(
RequestTokenInput input,
CancellationToken cancellationToken)
{
using HttpClient httpClient = _httpClientFactory.CreateClient();
DiscoveryDocumentResponse disco = await httpClient.GetDiscoveryDocumentAsync(input.Authority, cancellationToken);

Client? client = await _clientService.GetByIdAsync(input.ClientId, cancellationToken);
if (client == null)
{
Expand All @@ -38,35 +63,36 @@ public async Task<TokenRequestData> Create(

var clientId = client.ClientId;

var secretEncrypted = client.ClientSecrets.First(secret => secret.Id.Equals(input.SecretId))
.EncryptedValue;
if (!client.AllowedGrantTypes.Contains(input.grantType))
{
throw new ArgumentException("Grant not valid");
}

var secretEncrypted = client.ClientSecrets.First(secret => secret.Id.Equals(input.SecretId)).EncryptedValue;
if (secretEncrypted == null)
{
throw new KeyNotFoundException("No encrypted secret found");
}

var secretDecrypted =
await _encryptionService.DecryptAsync(secretEncrypted, cancellationToken);
var secretDecrypted = await _encryptionService.DecryptAsync(secretEncrypted, cancellationToken);

if (client.AllowedGrantTypes == null)
{
throw new ArgumentNullException("No grant types found");
throw new ArgumentNullException("Client has no registered grant types");
}

string grantType = client.AllowedGrantTypes.Contains(input.grantType)
? input.grantType
: throw new ArgumentException("Grant not valid");

var scopeIds = client.AllowedScopes.ToList().ConvertAll(clientScope => clientScope.Id);
var scopeIds = client.AllowedScopes.Select(clientScope => clientScope.Id).ToList();
var scopes = await _scopeService.GetByIdsAsync(scopeIds, cancellationToken);
var scopeNames = scopes.Select(scope => scope.Name).ToList();

var tokenRequestData =
new TokenRequestData(input.Authority, clientId, secretDecrypted, grantType, scopeNames)
{
SaveTokens = input.SaveTokens
};
var scopeNames = string.Join(", ", scopes.Select(scope => scope.Name));

return tokenRequestData;
return new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
ClientSecret = secretDecrypted,
Scope = scopeNames,
GrantType = "client_credentials"
};
}

}
4 changes: 2 additions & 2 deletions src/Server/src/GraphQL/GraphQLServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using HotChocolate.Execution.Configuration;
using HotChocolate.Types;
using IdentityModel.Client;
using IdOps.Abstractions;
using IdOps.Authorization;
using IdOps.Data.Errors;
Expand All @@ -9,7 +10,6 @@
using IdOps.GraphQL.Hashing;
using IdOps.GraphQL.Publish;
using IdOps.Model;
using IdOps.Models;
using Microsoft.Extensions.DependencyInjection;

namespace IdOps.GraphQL
Expand All @@ -32,7 +32,7 @@ public static IIdOpsServerBuilder AddGraphQLServer(
.AddHttpResultSerializer<ForbiddenHttpResultSerializer>();

builder.Services.AddSingleton<
IResultFactory<TokenRequestData,RequestTokenInput>,
IResultFactory<TokenRequest,RequestTokenInput>,
TokenRequestDataResultFactory>();

return builder;
Expand Down
18 changes: 10 additions & 8 deletions src/Server/test/AuthTokenGenerator.Tests/IdentityServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task RequestTokenAsync_WithValidRequest_ShouldReturnToken(

//Act
RequestTokenResult _ =
await identityService.RequestTokenAsync(CreateTokenRequestData(grantType),
await identityService.RequestTokenAsync(CreateClientCredentialsTokenRequest(grantType),
CancellationToken.None);

//Assert
Expand All @@ -74,14 +74,16 @@ private void initializeMocks()
_tokenAnalyzerMock = new Mock<ITokenAnalyzer>(MockBehavior.Strict);
}

private TokenRequestData CreateTokenRequestData(string grantType)
private ClientCredentialsTokenRequest CreateClientCredentialsTokenRequest(string grantType)
{
var tokenRequestData = new TokenRequestData("http://localhost:1234",
"11111111111111111111111111111111",
"thisIsATestSecret" +
"111111111111111111111111111111111111111111111111111111111111111111111", grantType,
new[] { "scope1", "scope2"});
return tokenRequestData;
var tokenRequest = new ClientCredentialsTokenRequest();

tokenRequest.Address = "http://localhost:1234";
tokenRequest.ClientId = "11111111111111111111111111111111";
tokenRequest.ClientSecret = "thisIsATestSecret";
tokenRequest.GrantType = grantType;
tokenRequest.Scope = "scope1, scope2";
return tokenRequest;
}

private DiscoveryDocumentResponse CreateDiscoveryResponse()
Expand Down