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

Organization support for client credentials #733

Merged
merged 5 commits into from
Oct 14, 2024
Merged
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
2 changes: 1 addition & 1 deletion src/Auth0.AuthenticationApi/AuthenticationApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ private Task AssertIdTokenValid(string idToken, string audience, JwtSignatureAlg

private Uri BuildUri(string path)
{
return Utils.BuildUri(BaseUri.AbsoluteUri, path, null, null);
return Utils.BuildUri(BaseUri.AbsoluteUri, path, null, queryStrings:null);
}

private IDictionary<string, string> BuildHeaders(string accessToken)
Expand Down
103 changes: 75 additions & 28 deletions src/Auth0.Core/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,25 @@ internal static string Base64UrlEncode(byte[] input)

internal static Uri BuildUri(string baseUrl, string resource, IDictionary<string, string> urlSegments, IDictionary<string, string> queryStrings, bool includeEmptyParameters = false)
{
// Replace the URL Segments
if (urlSegments != null)
{
foreach (var urlSegment in urlSegments)
{
resource = resource.Replace($"{{{urlSegment.Key}}}", Uri.EscapeDataString(urlSegment.Value ?? String.Empty));
}
resource = ReplaceUrlSegments(resource, urlSegments);

// Remove trailing slash
resource = resource.TrimEnd('/');
}
var queryString = AddQueryString(queryStrings, includeEmptyParameters);

// Add the query strings
var queryString = queryStrings?.Aggregate(new StringBuilder(), (sb, kvp) =>
{
if (kvp.Value != null)
{
if (sb.Length > 0)
sb = sb.Append("&");
// If we have a querystring, append it to the resource
if (!string.IsNullOrEmpty(queryString))
{
resource = resource.Contains("?") ? $"{resource}&{queryString}" : $"{resource}?{queryString}";
}

sb.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
}
else if (includeEmptyParameters)
{
if (sb.Length > 0)
sb = sb.Append("&");
resource = CombineUriParts(baseUrl, resource);

sb.Append(Uri.EscapeDataString(kvp.Key));
}
return new Uri(resource, UriKind.RelativeOrAbsolute);
}
internal static Uri BuildUri(string baseUrl, string resource, IDictionary<string, string> urlSegments, IList<Tuple<string, string>> queryStringsTuple, bool includeEmptyParameters = false)
{
resource = ReplaceUrlSegments(resource, urlSegments);

return sb;
})
.ToString();
var queryString = AddQueryString(queryStringsTuple, includeEmptyParameters);

// If we have a querystring, append it to the resource
if (!string.IsNullOrEmpty(queryString))
Expand Down Expand Up @@ -101,6 +87,67 @@ internal static string CombineUriParts(params string[] uriParts)
}
return uri;
}
private static string AddQueryString(IList<Tuple<string, string>> queryStrings, bool includeEmptyParameters)
{
var sb = new StringBuilder();
// Add the query strings
foreach (var keyValuePair in queryStrings)
{
if (keyValuePair.Item2 != null)
{
if (sb.Length > 0)
sb = sb.Append("&");

sb.Append($"{Uri.EscapeDataString(keyValuePair.Item1)}={Uri.EscapeDataString(keyValuePair.Item2)}");
}
else if (includeEmptyParameters)
{
if (sb.Length > 0)
sb = sb.Append("&");

sb.Append(Uri.EscapeDataString(keyValuePair.Item1));
}
}
return sb.ToString();
}
private static string AddQueryString(IDictionary<string, string> queryStrings, bool includeEmptyParameters)
{
// Add the query strings
var queryString = queryStrings?.Aggregate(new StringBuilder(), (sb, kvp) =>
{
if (kvp.Value != null)
{
if (sb.Length > 0)
sb = sb.Append("&");

sb.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
}
else if (includeEmptyParameters)
{
if (sb.Length > 0)
sb = sb.Append("&");

sb.Append(Uri.EscapeDataString(kvp.Key));
}

return sb;
})
.ToString();
return queryString;
}
private static string ReplaceUrlSegments(string resource, IDictionary<string, string> urlSegments)
{
// Replace the URL Segments
if (urlSegments == null) return resource;
foreach (var urlSegment in urlSegments)
{
resource =
resource.Replace($"{{{urlSegment.Key}}}", Uri.EscapeDataString(urlSegment.Value ?? String.Empty));
}

// Remove trailing slash
resource = resource.TrimEnd('/');
return resource;
}
}
}
5 changes: 5 additions & 0 deletions src/Auth0.ManagementApi/Clients/BaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ protected Uri BuildUri(string resource, IDictionary<string, string> queryStrings
{
return Utils.BuildUri(BaseUri.AbsoluteUri, resource, null, queryStrings);
}

protected Uri BuildUri(string resource, IList<Tuple<string, string>> queryStrings)
{
return Utils.BuildUri(BaseUri.AbsoluteUri, resource, null, queryStrings);
}

/// <summary>
/// Encode a value so it can be successfully used in the path.
Expand Down
3 changes: 2 additions & 1 deletion src/Auth0.ManagementApi/Clients/ClientsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public Task<IPagedList<Client>> GetAllAsync(GetClientsRequest request, Paginatio
{"fields", request.Fields},
{"include_fields", request.IncludeFields?.ToString().ToLower()},
{"is_global", request.IsGlobal?.ToString().ToLower()},
{"is_first_party", request.IsFirstParty?.ToString().ToLower()}
{"is_first_party", request.IsFirstParty?.ToString().ToLower()},
{"q", request.Query?.ToLower()}
};

if (pagination != null)
Expand Down
10 changes: 10 additions & 0 deletions src/Auth0.ManagementApi/Clients/IResourceServersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ public interface IResourceServersClient
/// <returns>The <see cref="ResourceServer"/> that was requested.</returns>
Task<ResourceServer> GetAsync(string id, CancellationToken cancellationToken = default);

/// <summary>
/// Gets a list of all the resource servers.
/// </summary>
/// <param name="request">Contains the information required to retrieve the Resource Servers</param>
/// <param name="pagination">Specifies pagination info to use when requesting paged results.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A <see cref="IPagedList{ResourceServer}"/> containing the list of resource servers.</returns>
Task<IPagedList<ResourceServer>> GetAllAsync(ResourceServerGetRequest request, PaginationInfo pagination = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Gets a list of all the resource servers.
/// </summary>
Expand Down
19 changes: 13 additions & 6 deletions src/Auth0.ManagementApi/Clients/OrganizationsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -435,17 +436,23 @@ public Task<IPagedList<OrganizationClientGrant>> GetAllClientGrantsAsync(string
if (request == null)
throw new ArgumentNullException(nameof(request));

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

if (request.GrantIds != null)
{
queryStrings.AddRange(
request.GrantIds.Select(grantId => new Tuple<string, string>("grant_ids", grantId)));
}

if (pagination != null)
{
queryStrings["page"] = pagination.PageNo.ToString();
queryStrings["per_page"] = pagination.PerPage.ToString();
queryStrings["include_totals"] = pagination.IncludeTotals.ToString().ToLower();
queryStrings.Add(new Tuple<string, string>("page", pagination.PageNo.ToString()));
queryStrings.Add(new Tuple<string, string>("per_page", pagination.PerPage.ToString()));
queryStrings.Add(new Tuple<string, string>("include_totals", pagination.IncludeTotals.ToString().ToLower()));
}

return Connection.GetAsync<IPagedList<OrganizationClientGrant>>(BuildUri($"organizations/{EncodePath(organizationId)}/client-grants", queryStrings), DefaultHeaders, clientGrantsConverters, cancellationToken);
Expand Down
27 changes: 26 additions & 1 deletion src/Auth0.ManagementApi/Clients/ResourceServersClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -70,13 +71,37 @@ public Task<IPagedList<ResourceServer>> GetAllAsync(PaginationInfo pagination =
{
return Connection.GetAsync<IPagedList<ResourceServer>>(BuildUri("resource-servers",
pagination != null ? new Dictionary<string, string>
{
{
{"page", pagination.PageNo.ToString()},
{"per_page", pagination.PerPage.ToString()},
{"include_totals", pagination.IncludeTotals.ToString().ToLower()}
} : null), DefaultHeaders, converters, cancellationToken);
}

/// <inheritdoc />
public Task<IPagedList<ResourceServer>> GetAllAsync(ResourceServerGetRequest request,
PaginationInfo pagination = null, CancellationToken cancellationToken = default)
{
var queryStrings = new List<Tuple<string, string>>();

if (request?.Identifiers != null)
{
queryStrings.AddRange(
request.Identifiers.Select(
identifier => new Tuple<string, string>("identifiers", identifier)));
}

if (pagination != null)
{
queryStrings.Add(new Tuple<string, string>("page", pagination.PageNo.ToString()));
queryStrings.Add(new Tuple<string, string>("per_page", pagination.PerPage.ToString()));
queryStrings.Add(new Tuple<string, string>("include_totals", pagination.IncludeTotals.ToString().ToLower()));
}

return Connection.GetAsync<IPagedList<ResourceServer>>(
BuildUri("resource-servers", queryStrings), DefaultHeaders, converters, cancellationToken);
}

/// <summary>
/// Updates a resource server,
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Auth0.ManagementApi/Models/ClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ public abstract class ClientBase
/// </summary>
[JsonProperty("require_pushed_authorization_requests")]
public bool? RequirePushedAuthorizationRequests { get; set; }

/// <summary>
/// Defines the default Organization ID and flows
/// </summary>
[JsonProperty("default_organization")]
public DefaultOrganization DefaultOrganization { get; set; }
}

}
Expand Down
25 changes: 25 additions & 0 deletions src/Auth0.ManagementApi/Models/DefaultOrganization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Auth0.ManagementApi.Models
{
/// <summary>
/// Defines the default Organization ID and flows
/// </summary>
public class DefaultOrganization
{
/// <summary>
/// The default Organization ID to be used
/// </summary>
[JsonProperty("organization_id")]
public string OrganizationId { get; set; }

/// <summary>
/// The default Organization usage
/// </summary>
[JsonProperty("flows", ItemConverterType = typeof(StringEnumConverter))]
public Flows[] Flows { get; set; }

}
}
16 changes: 16 additions & 0 deletions src/Auth0.ManagementApi/Models/Flows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Runtime.Serialization;

namespace Auth0.ManagementApi.Models
{
/// <summary>
/// The default Organization usage
/// </summary>
public enum Flows
{
/// <summary>
/// Client-Credentials flow
/// </summary>
[EnumMember(Value = "client_credentials")]
ClientCredentials
}
}
5 changes: 5 additions & 0 deletions src/Auth0.ManagementApi/Models/GetClientsRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,10 @@ public class GetClientsRequest
/// Filter on whether or not a client is a first party client.
/// </summary>
public bool? IsFirstParty { get; set; } = null;

/// <summary>
/// Query in <a href ="http://www.lucenetutorial.com/lucene-query-syntax.html">Lucene query string syntax</a> to search for clients.
/// </summary>
public string Query { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace Auth0.ManagementApi.Models
{
public class OrganizationGetClientGrantsRequest
Expand All @@ -10,6 +12,11 @@ public class OrganizationGetClientGrantsRequest
/// <summary>
/// The Id of a client to filter by.
/// </summary>
public string ClientId { get; set; }
public string ClientId { get; set; }

/// <summary>
/// List of GrantIds to filter results on
/// </summary>
public IList<string> GrantIds { get; set; }
}
}
15 changes: 15 additions & 0 deletions src/Auth0.ManagementApi/Models/ResourceServerGetRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace Auth0.ManagementApi.Models
{
/// <summary>
/// Request structure for creating a new resource server
/// </summary>
public class ResourceServerGetRequest
{
/// <summary>
/// List of Identifier IDs to retrieve
/// </summary>
public IList<string> Identifiers { get; set; }
}
}
Loading
Loading