Skip to content

Commit

Permalink
Add Organizations in Client Credentials (#673)
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikprijck authored Nov 8, 2023
1 parent 1af6684 commit 6990db6
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 5 deletions.
6 changes: 2 additions & 4 deletions src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
using Auth0.AuthenticationApi.Models;
using Auth0.AuthenticationApi.Tokens;
using Auth0.Core.Http;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -189,6 +185,8 @@ public Task<AccessTokenResponse> GetTokenAsync(ClientCredentialsTokenRequest req
{ "client_id", request.ClientId },
{ "audience", request.Audience } };

body.AddIfNotEmpty("organization", request.Organization);

ApplyClientAuthentication(request, body);

return connection.SendAsync<AccessTokenResponse>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,13 @@ public class ClientCredentialsTokenRequest : IClientAuthentication
/// of Id Tokens.
/// </summary>
public JwtSignatureAlgorithm SigningAlgorithm { get; set; }

/// <summary>
/// Organization.
/// </summary>
/// <remarks>
/// This can be an Organization Name or ID. When included, the access token returned will include the org_id and org_name claims
/// </remarks>
public string Organization { get; set; }
}
}
62 changes: 62 additions & 0 deletions src/Auth0.ManagementApi/Clients/ClientGrantsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Auth0.ManagementApi.Clients
public class ClientGrantsClient : BaseClient, IClientGrantsClient
{
readonly JsonConverter[] converters = new JsonConverter[] { new PagedListConverter<ClientGrant>("client_grants") };
readonly JsonConverter[] organizationsConverters = new JsonConverter[] { new PagedListConverter<Organization>("organizations") };

/// <summary>
/// Initializes a new instance of <see cref="ClientGrantsClient"/>.
Expand Down Expand Up @@ -66,6 +67,8 @@ public Task<IPagedList<ClientGrant>> GetAllAsync(GetClientGrantsRequest request,
{"audience", request.Audience},
{"client_id", request.ClientId},
};

queryStrings.AddIfNotEmpty("allow_any_organization", request.AllowAnyOrganization?.ToString() ?? string.Empty);

if (pagination != null)
{
Expand All @@ -88,5 +91,64 @@ public Task<ClientGrant> UpdateAsync(string id, ClientGrantUpdateRequest request
{
return Connection.SendAsync<ClientGrant>(new HttpMethod("PATCH"), BuildUri($"client-grants/{EncodePath(id)}"), request, DefaultHeaders, cancellationToken: cancellationToken);
}

/// <summary>
/// Get the organizations associated to a client grant
/// </summary>
/// <param name="id">The identifier of the client grant.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{Organization}"/> containing the organizations requested.</returns>
public Task<IPagedList<Organization>> GetAllOrganizationsAsync(string id, CancellationToken cancellationToken = default)
{
var queryStrings = new Dictionary<string, string>();
return Connection.GetAsync<IPagedList<Organization>>(BuildUri($"client-grants/{EncodePath(id)}/organizations", queryStrings), DefaultHeaders, organizationsConverters, cancellationToken);

}
/// <summary>
/// Get the organizations associated to a client grant
/// </summary>
/// <param name="id">The identifier of the client grant.</param>
/// <param name="pagination">Specifies <see cref="PaginationInfo"/> to use in requesting paged results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{Organization}"/> containing the organizations requested.</returns>
public Task<IPagedList<Organization>> GetAllOrganizationsAsync(string id, PaginationInfo pagination, CancellationToken cancellationToken = default)
{
if (pagination == null)
{
throw new ArgumentNullException(nameof(pagination));
}

var queryStrings = new Dictionary<string, string>
{
["page"] = pagination.PageNo.ToString(),
["per_page"] = pagination.PerPage.ToString(),
["include_totals"] = pagination.IncludeTotals.ToString().ToLower()
};

return Connection.GetAsync<IPagedList<Organization>>(BuildUri($"client-grants/{EncodePath(id)}/organizations", queryStrings), DefaultHeaders, organizationsConverters, cancellationToken);
}

/// <summary>
/// Get the organizations associated to a client grant
/// </summary>
/// <param name="id">The identifier of the client grant.</param>
/// <param name="pagination">Specifies <see cref="CheckpointPaginationInfo"/> to use in requesting checkpoint-paginated results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{Organization}"/> containing the organizations requested.</returns>
public Task<IPagedList<Organization>> GetAllOrganizationsAsync(string id, CheckpointPaginationInfo pagination = null, CancellationToken cancellationToken = default)
{
if (pagination == null)
{
throw new ArgumentNullException(nameof(pagination));
}

var queryStrings = new Dictionary<string, string>
{
["from"] = pagination.From?.ToString(),
["take"] = pagination.Take.ToString()
};

return Connection.GetAsync<IPagedList<Organization>>(BuildUri($"client-grants/{EncodePath(id)}/organizations", queryStrings), DefaultHeaders, organizationsConverters, cancellationToken);
}
}
}
28 changes: 28 additions & 0 deletions src/Auth0.ManagementApi/Clients/IClientGrantsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,33 @@ public interface IClientGrantsClient
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The <see cref="ClientGrant"/> that has been updated.</returns>
Task<ClientGrant> UpdateAsync(string id, ClientGrantUpdateRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Get the organizations associated to a client grant
/// </summary>
/// <param name="id">The identifier of the client grant.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{Organization}"/> containing the organizations requested.</returns>
Task<IPagedList<Organization>> GetAllOrganizationsAsync(string id, CancellationToken cancellationToken = default);

/// <summary>
/// Get the organizations associated to a client grant
/// </summary>
/// <param name="id">The identifier of the client grant.</param>
/// <param name="pagination">Specifies <see cref="PaginationInfo"/> to use in requesting paged results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{Organization}"/> containing the organizations requested.</returns>
Task<IPagedList<Organization>> GetAllOrganizationsAsync(string id, PaginationInfo pagination,
CancellationToken cancellationToken = default);

/// <summary>
/// Get the organizations associated to a client grant
/// </summary>
/// <param name="id">The identifier of the client grant.</param>
/// <param name="pagination">Specifies <see cref="CheckpointPaginationInfo"/> to use in requesting checkpoint-paginated results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{Organization}"/> containing the organizations requested.</returns>
Task<IPagedList<Organization>> GetAllOrganizationsAsync(string id, CheckpointPaginationInfo pagination,
CancellationToken cancellationToken = default);
}
}
32 changes: 32 additions & 0 deletions src/Auth0.ManagementApi/Clients/IOrganizationsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,37 @@ public interface IOrganizationsClient
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous delete operation.</returns>
Task DeleteInvitationAsync(string organizationId, string invitationId, CancellationToken cancellationToken = default);

/// <summary>
/// Get client grants associated with an organization.
/// </summary>
/// <param name="organizationId">The id of the organization for which you want to retrieve the client grants.</param>
/// <param name="request">Specifies criteria to use when querying client grants for the organization.</param>
/// <param name="pagination">Specifies <see cref="PaginationInfo"/> to use in requesting paged results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{ClientGrant}"/> containing the client grants requested.</returns>
Task<IPagedList<OrganizationClientGrant>> GetAllClientGrantsAsync(string organizationId,
OrganizationGetClientGrantsRequest request, PaginationInfo pagination = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Associate a client grant with an organization
/// </summary>
/// <param name="organizationId">The id of the organization to which you want to associate the client grant.</param>
/// <param name="request">The <see cref="OrganizationCreateClientGrantRequest"/> containing the properties of the Client Grant to associate with the organization.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The new <see cref="ClientGrant"/> that has been created.</returns>
Task<OrganizationClientGrant> CreateClientGrantAsync(string organizationId,
OrganizationCreateClientGrantRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Remove a client grant from an organization.
/// </summary>
/// <param name="organizationId">The id of the organization for which you want to delete the client grant.</param>
/// <param name="clientGrantId">The id of the client grant you want to delete from the organization</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous delete operation.</returns>
Task DeleteClientGrantAsync(string organizationId, string clientGrantId,
CancellationToken cancellationToken = default);
}
}
54 changes: 54 additions & 0 deletions src/Auth0.ManagementApi/Clients/OrganizationsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class OrganizationsClient : BaseClient, IOrganizationsClient
readonly JsonConverter[] memberRolesConverters = new JsonConverter[] { new PagedListConverter<Role>("roles") };
readonly JsonConverter[] membersCheckpointConverters = new JsonConverter[] { new CheckpointPagedListConverter<OrganizationMember>("members") };
readonly JsonConverter[] invitationsConverters = new JsonConverter[] { new PagedListConverter<OrganizationInvitation>("invitations") };
readonly JsonConverter[] clientGrantsConverters = new JsonConverter[] { new PagedListConverter<OrganizationClientGrant>("client_grants") };

/// <summary>
/// Initializes a new instance of <see cref="ClientsClient"/>.
Expand Down Expand Up @@ -420,6 +421,59 @@ public Task DeleteInvitationAsync(string organizationId, string invitationId, Ca
{
return Connection.SendAsync<object>(HttpMethod.Delete, BuildUri($"organizations/{EncodePath(organizationId)}/invitations/{EncodePath(invitationId)}"), null, DefaultHeaders, cancellationToken: cancellationToken);
}

/// <summary>
/// Get client grants associated to an organization.
/// </summary>
/// <param name="organizationId">The id of the organization for which you want to retrieve the client grants.</param>
/// <param name="request">Specifies criteria to use when querying client grants for the organization.</param>
/// <param name="pagination">Specifies <see cref="PaginationInfo"/> to use in requesting paged results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{ClientGrant}"/> containing the client grants requested.</returns>
public Task<IPagedList<OrganizationClientGrant>> GetAllClientGrantsAsync(string organizationId, OrganizationGetClientGrantsRequest request, PaginationInfo pagination = null, CancellationToken cancellationToken = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));

var queryStrings = new Dictionary<string, string>
{
{"audience", request.Audience},
{"client_id", request.ClientId},
};

if (pagination != null)
{
queryStrings["page"] = pagination.PageNo.ToString();
queryStrings["per_page"] = pagination.PerPage.ToString();
queryStrings["include_totals"] = pagination.IncludeTotals.ToString().ToLower();
}

return Connection.GetAsync<IPagedList<OrganizationClientGrant>>(BuildUri($"organizations/{EncodePath(organizationId)}/client-grants", queryStrings), DefaultHeaders, clientGrantsConverters, cancellationToken);
}

/// <summary>
/// Associate a client grant with an organization
/// </summary>
/// <param name="organizationId">The id of the organization to which you want to associate the client grants.</param>
/// <param name="request">The <see cref="OrganizationCreateClientGrantRequest"/> containing the properties of the Client Grant to associate with the organization.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>The new <see cref="ClientGrant"/> that has been created.</returns>
public Task<OrganizationClientGrant> CreateClientGrantAsync(string organizationId, OrganizationCreateClientGrantRequest request, CancellationToken cancellationToken = default)
{
return Connection.SendAsync<OrganizationClientGrant>(HttpMethod.Post, BuildUri($"organizations/{EncodePath(organizationId)}/client-grants"), request, DefaultHeaders, cancellationToken: cancellationToken);
}

/// <summary>
/// Remove a client grant from an organization.
/// </summary>
/// <param name="organizationId">The id of the organization for which you want to delete the client grant.</param>
/// <param name="clientGrantId">The id of the client grant you want to delete from the organization</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous delete operation.</returns>
public Task DeleteClientGrantAsync(string organizationId, string clientGrantId, CancellationToken cancellationToken = default)
{
return Connection.SendAsync<object>(HttpMethod.Delete, BuildUri($"organizations/{EncodePath(organizationId)}/client-grants/{EncodePath(clientGrantId)}"), null, DefaultHeaders, cancellationToken: cancellationToken);
}

}
}
17 changes: 17 additions & 0 deletions src/Auth0.ManagementApi/Models/ClientGrantBase.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Auth0.ManagementApi.Models
{
Expand All @@ -25,5 +26,21 @@ public class ClientGrantBase
/// </summary>
[JsonProperty("scope")]
public List<string> Scope { get; set; }

/// <summary>
/// Defines whether organizations can be used with client credentials exchanges for this grant. (defaults to deny when not defined)
/// </summary>
/// <remarks>
/// Possible values: [deny, allow, require]
/// </remarks>
[JsonProperty("organization_usage")]
[JsonConverter(typeof(StringEnumConverter))]
public OrganizationUsage? OrganizationUsage { get; set; }

/// <summary>
/// If enabled, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations.
/// </summary>
[JsonProperty("allow_any_organization")]
public bool? AllowAnyOrganization { get; set; }
}
}
16 changes: 16 additions & 0 deletions src/Auth0.ManagementApi/Models/ClientGrantUpdateRequest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Auth0.ManagementApi.Models
{
Expand All @@ -14,5 +15,20 @@ public class ClientGrantUpdateRequest
[JsonProperty("scope")]
public List<string> Scope { get; set; }

/// <summary>
/// Defines whether organizations can be used with client credentials exchanges for this grant. (defaults to deny when not defined)
/// </summary>
/// <remarks>
/// Possible values: [deny, allow, require]
/// </remarks>
[JsonProperty("organization_usage")]
[JsonConverter(typeof(StringEnumConverter))]
public OrganizationUsage? OrganizationUsage { get; set; }

/// <summary>
/// If enabled, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations.
/// </summary>
[JsonProperty("allow_any_organization")]
public bool? AllowAnyOrganization { get; set; }
}
}
7 changes: 6 additions & 1 deletion src/Auth0.ManagementApi/Models/GetClientGrantsRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class GetClientGrantsRequest
/// <summary>
/// The Id of a client to filter by.
/// </summary>
public string ClientId { get; set; }
public string ClientId { get; set; }

/// <summary>
/// If enabled, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations.
/// </summary>
public bool? AllowAnyOrganization { get; set; }
}
}
32 changes: 32 additions & 0 deletions src/Auth0.ManagementApi/Models/OrganizationClientGrant.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Auth0.ManagementApi.Models
{
public class OrganizationClientGrant
{
/// <summary>
/// Gets or sets the identifier for a Client Grant.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }

/// <summary>
/// Gets or sets the audience
/// </summary>
[JsonProperty("audience")]
public string Audience { get; set; }

/// <summary>
/// Gets or sets the identifier of the <see cref="Client"/>
/// </summary>
[JsonProperty("client_id")]
public string ClientId { get; set; }

/// <summary>
/// Gets or sets the list of scopes
/// </summary>
[JsonProperty("scope")]
public List<string> Scope { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Newtonsoft.Json;

namespace Auth0.ManagementApi.Models
{
public class OrganizationCreateClientGrantRequest
{
/// <summary>
/// A Client Grant ID to add to the organization.
/// </summary>
[JsonProperty("grant_id")]
public string GrantId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Auth0.ManagementApi.Models
{
public class OrganizationGetClientGrantsRequest
{
/// <summary>
/// URL Encoded audience of a client grant to filter.
/// </summary>
public string Audience { get; set; }

/// <summary>
/// The Id of a client to filter by.
/// </summary>
public string ClientId { get; set; }
}
}
Loading

0 comments on commit 6990db6

Please sign in to comment.