Skip to content

Commit

Permalink
Added ClientGrant support.
Browse files Browse the repository at this point in the history
Added explicit RetryException.
  • Loading branch information
wasabii committed Jan 7, 2025
1 parent 21b99ee commit fd3a1bf
Show file tree
Hide file tree
Showing 20 changed files with 597 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;

namespace Alethic.Auth0.Operator.Core.Models.ClientGrant
{

public partial class ClientGrantConf
{

[JsonPropertyName("clientRef")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public V1ClientRef? ClientRef { get; set; }

[JsonPropertyName("audience")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public V1ResourceServerRef? Audience { get; set; }

[JsonPropertyName("organization_usage")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public OrganizationUsage? OrganizationUsage { get; set; }

[JsonPropertyName("allow_any_organization")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? AllowAnyOrganization { get; set; }

[JsonPropertyName("resourceServerRef")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string[]? Scopes { get; set; }

}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;

namespace Alethic.Auth0.Operator.Core.Models.Organization
namespace Alethic.Auth0.Operator.Core.Models
{

[JsonConverter(typeof(JsonStringEnumConverter))]
Expand Down
32 changes: 32 additions & 0 deletions src/Alethic.Auth0.Operator.Core/Models/V1ResourceServerRef.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Text.Json.Serialization;

namespace Alethic.Auth0.Operator.Core.Models
{

public class V1ResourceServerRef
{

[JsonPropertyName("namespace")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Namespace { get; set; }

[JsonPropertyName("name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Name { get; set; }

[JsonPropertyName("id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Id { get; set; }

/// <inheritdoc />
public override string ToString()
{
if (Id is not null)
return Id;
else
return $"{Namespace}/{Name}";
}

}

}
4 changes: 2 additions & 2 deletions src/Alethic.Auth0.Operator/Controllers/V1ClientController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ public V1ClientController(IKubernetesClient kube, EntityRequeue<V1Client> requeu
protected override string EntityTypeName => "Client";

/// <inheritdoc />
protected override async Task<IDictionary?> GetApi(IManagementApiClient api, string id, CancellationToken cancellationToken)
protected override async Task<IDictionary?> GetApi(IManagementApiClient api, string id, string defaultNamespace, CancellationToken cancellationToken)
{
return TransformToSystemTextJson<Client, IDictionary>(await api.Clients.GetAsync(id, cancellationToken: cancellationToken));
}

/// <inheritdoc />
protected override async Task<string?> FindApi(IManagementApiClient api, ClientConf conf, CancellationToken cancellationToken)
protected override async Task<string?> FindApi(IManagementApiClient api, ClientConf conf, string defaultNamespace, CancellationToken cancellationToken)
{
var list = await api.Clients.GetAllAsync(new GetClientsRequest() { Fields = "client_id,name" }, cancellationToken: cancellationToken);
var self = list.FirstOrDefault(i => i.Name == conf.Name);
Expand Down
141 changes: 141 additions & 0 deletions src/Alethic.Auth0.Operator/Controllers/V1ClientGrantController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Alethic.Auth0.Operator.Core.Models.ClientGrant;
using Alethic.Auth0.Operator.Models;

using Auth0.ManagementApi;
using Auth0.ManagementApi.Models;

using k8s.Models;

using KubeOps.Abstractions.Controller;
using KubeOps.Abstractions.Queue;
using KubeOps.Abstractions.Rbac;
using KubeOps.KubernetesClient;

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace Alethic.Auth0.Operator.Controllers
{

[EntityRbac(typeof(V1Tenant), Verbs = RbacVerb.List | RbacVerb.Get)]
[EntityRbac(typeof(V1Client), Verbs = RbacVerb.List | RbacVerb.Get)]
[EntityRbac(typeof(V1ResourceServer), Verbs = RbacVerb.List | RbacVerb.Get)]
[EntityRbac(typeof(V1ClientGrant), Verbs = RbacVerb.All)]
[EntityRbac(typeof(Eventsv1Event), Verbs = RbacVerb.Create)]
public class V1ClientGrantController :
V1TenantEntityController<V1ClientGrant, V1ClientGrant.SpecDef, V1ClientGrant.StatusDef, ClientGrantConf>,
IEntityController<V1ClientGrant>
{

/// <summary>
/// Initializes a new instance.
/// </summary>
/// <param name="kube"></param>
/// <param name="requeue"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
public V1ClientGrantController(IKubernetesClient kube, EntityRequeue<V1ClientGrant> requeue, IMemoryCache cache, ILogger<V1ClientGrantController> logger) :
base(kube, requeue, cache, logger)
{

}

/// <inheritdoc />
protected override string EntityTypeName => "ClientGrant";

/// <inheritdoc />
protected override async Task<IDictionary?> GetApi(IManagementApiClient api, string id, string defaultNamespace,CancellationToken cancellationToken)
{
var list = await api.ClientGrants.GetAllAsync(new GetClientGrantsRequest(), cancellationToken: cancellationToken);
var self = list.FirstOrDefault(i => i.Id == id);
if (self == null)
return null;

return TransformToSystemTextJson<ClientGrant, IDictionary>(self);
}

/// <inheritdoc />
protected override async Task<string?> FindApi(IManagementApiClient api, ClientGrantConf conf, string defaultNamespace, CancellationToken cancellationToken)
{
if (conf.ClientRef is null)
throw new InvalidOperationException("ClientRef is required.");
var clientId = await ResolveClientRefToId(conf.ClientRef, defaultNamespace, cancellationToken);
if (string.IsNullOrWhiteSpace(clientId))
throw new InvalidOperationException();

if (conf.Audience is null)
throw new InvalidOperationException("Audience is required.");
var audience = await ResolveResourceServerRefToIdentifier(conf.Audience, defaultNamespace, cancellationToken);
if (string.IsNullOrWhiteSpace(audience))
throw new InvalidOperationException();

var list = await api.ClientGrants.GetAllAsync(new GetClientGrantsRequest() { ClientId = clientId }, null, cancellationToken);
return list.Where(i => i.ClientId == clientId && i.Audience == audience).Select(i => i.Id).FirstOrDefault();
}

/// <inheritdoc />
protected override string? ValidateCreateConf(ClientGrantConf conf)
{
if (conf.ClientRef is null)
return "missing a value for ClientRef";
if (conf.Audience is null)
return "missing a value for Audience";

return null;
}

/// <inheritdoc />
protected override async Task<string> CreateApi(IManagementApiClient api, ClientGrantConf conf, string defaultNamespace, CancellationToken cancellationToken)
{
var req = new ClientGrantCreateRequest();
req.AllowAnyOrganization = conf.AllowAnyOrganization;
req.OrganizationUsage = Convert(conf.OrganizationUsage);
req.Scope = conf.Scopes?.ToList();
req.ClientId = await ResolveClientRefToId(conf.ClientRef, defaultNamespace, cancellationToken);
req.Audience = await ResolveResourceServerRefToIdentifier(conf.Audience, defaultNamespace, cancellationToken);

var self = await api.ClientGrants.CreateAsync(req, cancellationToken);
if (self is null)
throw new InvalidOperationException();

return self.Id;
}

/// <inheritdoc />
protected override async Task UpdateApi(IManagementApiClient api, string id, ClientGrantConf conf, string defaultNamespace, CancellationToken cancellationToken)
{
var req = new ClientGrantUpdateRequest();
req.AllowAnyOrganization = conf.AllowAnyOrganization;
req.OrganizationUsage = Convert(conf.OrganizationUsage);
req.Scope = conf.Scopes?.ToList();

await api.ClientGrants.UpdateAsync(id, req, cancellationToken);
}

/// <inheritdoc />
protected override Task DeleteApi(IManagementApiClient api, string id, CancellationToken cancellationToken)
{
return api.ClientGrants.DeleteAsync(id, cancellationToken);
}

global::Auth0.ManagementApi.Models.OrganizationUsage? Convert(global::Alethic.Auth0.Operator.Core.Models.OrganizationUsage? organizationUsage)
{
return organizationUsage switch
{
Core.Models.OrganizationUsage.Deny => global::Auth0.ManagementApi.Models.OrganizationUsage.Deny,
Core.Models.OrganizationUsage.Allow => global::Auth0.ManagementApi.Models.OrganizationUsage.Allow,
Core.Models.OrganizationUsage.Require => global::Auth0.ManagementApi.Models.OrganizationUsage.Require,
null => null,
_ => throw new InvalidOperationException(),
};
}

}

}
12 changes: 6 additions & 6 deletions src/Alethic.Auth0.Operator/Controllers/V1ConnectionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class V1ConnectionController :
/// <param name="requeue"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
public V1ConnectionController(IKubernetesClient kube, EntityRequeue<V1Connection> requeue, IMemoryCache cache, ILogger<V1ClientController> logger) :
public V1ConnectionController(IKubernetesClient kube, EntityRequeue<V1Connection> requeue, IMemoryCache cache, ILogger<V1ConnectionController> logger) :
base(kube, requeue, cache, logger)
{

Expand All @@ -56,7 +56,7 @@ public V1ConnectionController(IKubernetesClient kube, EntityRequeue<V1Connection
protected override string EntityTypeName => "Connection";

/// <inheritdoc />
protected override async Task<IDictionary?> GetApi(IManagementApiClient api, string id, CancellationToken cancellationToken)
protected override async Task<IDictionary?> GetApi(IManagementApiClient api, string id, string defaultNamespace, CancellationToken cancellationToken)
{
var self = await api.Connections.GetAsync(id, cancellationToken: cancellationToken);
if (self == null)
Expand Down Expand Up @@ -92,7 +92,7 @@ public V1ConnectionController(IKubernetesClient kube, EntityRequeue<V1Connection
};

/// <inheritdoc />
protected override async Task<string?> FindApi(IManagementApiClient api, ConnectionConf conf, CancellationToken cancellationToken)
protected override async Task<string?> FindApi(IManagementApiClient api, ConnectionConf conf, string defaultNamespace, CancellationToken cancellationToken)
{
var list = await api.Connections.GetAllAsync(new GetConnectionsRequest() { Fields = "id,name" }, pagination: (PaginationInfo?)null, cancellationToken: cancellationToken);
var self = list.FirstOrDefault(i => i.Name == conf.Name);
Expand Down Expand Up @@ -128,16 +128,16 @@ public V1ConnectionController(IKubernetesClient kube, EntityRequeue<V1Connection
}
else
{
Logger.LogDebug($"Attempting to resolve client reference {i.Namespace}/{i.Name}.");
Logger.LogDebug("Attempting to resolve ClientRef {Namespace}/{Name}.", i.Namespace, i.Name);

var client = await ResolveClientRef(i, defaultNamespace, cancellationToken);
if (client is null)
throw new InvalidOperationException($"Could not resolve ClientRef {i}.");

if (client.Status.Id is null)
throw new InvalidOperationException($"Referenced Client {client.Namespace()}/{client.Name()} has not been reconcilled.");
throw new RetryException($"Referenced Client {client.Namespace()}/{client.Name()} has not been reconciled.");

Logger.LogDebug($"Resolved client reference {i.Namespace}/{i.Name} to {client.Status.Id}.");
Logger.LogDebug("Resolved ClientRef {Namespace}/{Name} to {Id}.", i.Namespace, i.Name, client.Status.Id);
l.Add(client.Status.Id);
}
}
Expand Down
Loading

0 comments on commit fd3a1bf

Please sign in to comment.