diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index 9a94076e..3ce9dcc1 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -63,6 +63,7 @@ public class AuthAPI { private static final String PATH_REVOKE = "revoke"; private static final String PATH_PASSWORDLESS = "passwordless"; private static final String PATH_START = "start"; + private static final String KEY_ORGANIZATION = "organization"; private final Auth0HttpClient client; private final String clientId; @@ -715,12 +716,40 @@ public TokenRequest exchangePasswordlessOtp(String emailOrPhone, String realm, c * @return a Request to configure and execute. */ public TokenRequest requestToken(String audience) { + return requestToken(audience, null); + } + + /** + * Creates a request to get a Token for the given audience using the 'Client Credentials' grant. + * Default used realm is defined in the "API Authorization Settings" in the account's advanced settings in the Auth0 Dashboard. + * This operation requires that a client secret be configured for the {@code AuthAPI} client. + *
+     * {@code
+     * try {
+     *      TokenHolder result = authAPI.requestToken("https://myapi.me.auth0.com/users", "org_123")
+     *          .execute()
+     *          .getBody();
+     * } catch (Auth0Exception e) {
+     *      //Something happened
+     * }
+     * }
+     * 
+ * + * @see Client Credentials Flow API docs + * @param audience the audience of the API to request access to. + * @param org the organization name or ID to be included in the request. + * @return a Request to configure and execute. + */ + public TokenRequest requestToken(String audience, String org) { Asserts.assertNotNull(audience, "audience"); TokenRequest request = new TokenRequest(client, getTokenUrl()); request.addParameter(KEY_CLIENT_ID, clientId); request.addParameter(KEY_GRANT_TYPE, "client_credentials"); request.addParameter(KEY_AUDIENCE, audience); + if (org != null && !org.trim().isEmpty()) { + request.addParameter(KEY_ORGANIZATION, org); + } addClientAuthentication(request, true); return request; } diff --git a/src/main/java/com/auth0/client/mgmt/ClientGrantsEntity.java b/src/main/java/com/auth0/client/mgmt/ClientGrantsEntity.java index d33bf667..99fd5f01 100644 --- a/src/main/java/com/auth0/client/mgmt/ClientGrantsEntity.java +++ b/src/main/java/com/auth0/client/mgmt/ClientGrantsEntity.java @@ -1,8 +1,10 @@ package com.auth0.client.mgmt; import com.auth0.client.mgmt.filter.ClientGrantsFilter; +import com.auth0.client.mgmt.filter.PageFilter; import com.auth0.json.mgmt.clientgrants.ClientGrant; import com.auth0.json.mgmt.clientgrants.ClientGrantsPage; +import com.auth0.json.mgmt.organizations.OrganizationsPage; import com.auth0.net.BaseRequest; import com.auth0.net.Request; import com.auth0.net.VoidRequest; @@ -61,24 +63,44 @@ public Request list(ClientGrantsFilter filter) { * @return a Request to execute. */ public Request create(String clientId, String audience, String[] scope) { + return create(clientId, audience, scope, null, null); + } + + /** + * Create a Client Grant. A token with scope create:client_grants is needed. + * See https://auth0.com/docs/api/management/v2#!/Client_Grants/post_client_grants + * + * @param clientId the application's client id to associate this grant with. + * @param audience the audience of the grant. + * @param scope the scope to grant. + * @param orgUsage Defines whether organizations can be used with client credentials exchanges for this grant. (defaults to deny when not defined) + * @param allowAnyOrg If true, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations. + * @return a Request to execute. + */ + public Request create(String clientId, String audience, String[] scope, String orgUsage, Boolean allowAnyOrg) { Asserts.assertNotNull(clientId, "client id"); Asserts.assertNotNull(audience, "audience"); Asserts.assertNotNull(scope, "scope"); String url = baseUrl - .newBuilder() - .addPathSegments("api/v2/client-grants") - .build() - .toString(); + .newBuilder() + .addPathSegments("api/v2/client-grants") + .build() + .toString(); BaseRequest request = new BaseRequest<>(client, tokenProvider, url, HttpMethod.POST, new TypeReference() { }); request.addParameter("client_id", clientId); request.addParameter("audience", audience); request.addParameter("scope", scope); + if (orgUsage != null && !orgUsage.trim().isEmpty()) { + request.addParameter("organization_usage", orgUsage); + } + if (allowAnyOrg != null) { + request.addParameter("allow_any_organization", allowAnyOrg); + } return request; } - /** * Delete an existing Client Grant. A token with scope delete:client_grants is needed. * See https://auth0.com/docs/api/management/v2#!/Client_Grants/delete_client_grants_by_id @@ -107,18 +129,63 @@ public Request delete(String clientGrantId) { * @return a Request to execute. */ public Request update(String clientGrantId, String[] scope) { + return update(clientGrantId, scope, null, null); + } + + /** + * Update an existing Client Grant. A token with scope update:client_grants is needed. + * See https://auth0.com/docs/api/management/v2#!/Client_Grants/patch_client_grants_by_id + * + * @param clientGrantId the client grant id. + * @param scope the scope to grant. + * @param orgUsage Defines whether organizations can be used with client credentials exchanges for this grant. (defaults to deny when not defined) + * @param allowAnyOrg If true, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations. + * @return a Request to execute. + */ + public Request update(String clientGrantId, String[] scope, String orgUsage, Boolean allowAnyOrg) { Asserts.assertNotNull(clientGrantId, "client grant id"); Asserts.assertNotNull(scope, "scope"); String url = baseUrl - .newBuilder() - .addPathSegments("api/v2/client-grants") - .addPathSegment(clientGrantId) - .build() - .toString(); + .newBuilder() + .addPathSegments("api/v2/client-grants") + .addPathSegment(clientGrantId) + .build() + .toString(); BaseRequest request = new BaseRequest<>(client, tokenProvider, url, HttpMethod.PATCH, new TypeReference() { }); request.addParameter("scope", scope); + if (orgUsage != null && !orgUsage.trim().isEmpty()) { + request.addParameter("organization_usage", orgUsage); + } + if (allowAnyOrg != null) { + request.addParameter("allow_any_organization", allowAnyOrg); + } return request; } + + /** + * Returns the organizations associated with this client grant. A token with scope {@code read:organization_client_grants} is required. + * @param clientGrantId the client grant ID. + * @param filter an optional filter to limit results. + * @return a request to execute. + */ + public Request listOrganizations(String clientGrantId, PageFilter filter) { + Asserts.assertNotNull(clientGrantId, "client grant ID"); + HttpUrl.Builder builder = baseUrl + .newBuilder() + .addPathSegments("api/v2/client-grants") + .addPathSegment(clientGrantId) + .addPathSegment("organizations"); + + if (filter != null) { + for (Map.Entry e : filter.getAsMap().entrySet()) { + builder.addQueryParameter(e.getKey(), String.valueOf(e.getValue())); + } + } + + String url = builder.build().toString(); + return new BaseRequest<>(client, tokenProvider, url, HttpMethod.GET, new TypeReference() { + }); + } } diff --git a/src/main/java/com/auth0/client/mgmt/OrganizationsEntity.java b/src/main/java/com/auth0/client/mgmt/OrganizationsEntity.java index 94f20d2b..dcb9f3ed 100644 --- a/src/main/java/com/auth0/client/mgmt/OrganizationsEntity.java +++ b/src/main/java/com/auth0/client/mgmt/OrganizationsEntity.java @@ -1,9 +1,6 @@ package com.auth0.client.mgmt; -import com.auth0.client.mgmt.filter.BaseFilter; -import com.auth0.client.mgmt.filter.FieldsFilter; -import com.auth0.client.mgmt.filter.InvitationsFilter; -import com.auth0.client.mgmt.filter.PageFilter; +import com.auth0.client.mgmt.filter.*; import com.auth0.json.mgmt.roles.RolesPage; import com.auth0.json.mgmt.organizations.*; import com.auth0.net.BaseRequest; @@ -602,6 +599,73 @@ public Request deleteInvitation(String orgId, String invitationId) { return new VoidRequest(client, tokenProvider, url, HttpMethod.DELETE); } + /** + * Get the client grants associated with this organization. A token with scope {@code read:organization_client_grants} is required. + * @param orgId the organization ID. + * @param filter an optional filter to refine results. + * @return a request to execute. + */ + public Request listClientGrants(String orgId, OrganizationClientGrantsFilter filter) { + Asserts.assertNotNull(orgId, "organization ID"); + + HttpUrl.Builder builder = baseUrl + .newBuilder() + .addPathSegments(ORGS_PATH) + .addPathSegments(orgId) + .addPathSegment("client-grants"); + + applyFilter(filter, builder); + + String url = builder.build().toString(); + + return new BaseRequest<>(client, tokenProvider, url, HttpMethod.GET, new TypeReference() {}); + } + + /** + * Associate a client grant with an organization. A token with scope {@code create:organization_client_grants} is required. + * @param orgId the organization ID. + * @param addOrganizationClientGrantRequestBody the body of the request containing information about the client grant to associate. + * @return a request to execute. + */ + public Request addClientGrant(String orgId, CreateOrganizationClientGrantRequestBody addOrganizationClientGrantRequestBody) { + Asserts.assertNotNull(orgId, "organization ID"); + Asserts.assertNotNull(addOrganizationClientGrantRequestBody, "client grant"); + + String url = baseUrl + .newBuilder() + .addPathSegments(ORGS_PATH) + .addPathSegment(orgId) + .addPathSegment("client-grants") + .build() + .toString(); + + BaseRequest request = new BaseRequest<>(client, tokenProvider, url, HttpMethod.POST, new TypeReference() {}); + request.setBody(addOrganizationClientGrantRequestBody); + return request; + } + + /** + * Remove a client grant from an organization. A token with scope {@code delete:organization_client_grants} is required. + * @param orgId the organization ID. + * @param grantId the client grant ID. + * @return a request to execute. + */ + public Request deleteClientGrant(String orgId, String grantId) { + Asserts.assertNotNull(orgId, "organization ID"); + Asserts.assertNotNull(grantId, "client grant ID"); + + String url = baseUrl + .newBuilder() + .addPathSegments(ORGS_PATH) + .addPathSegment(orgId) + .addPathSegment("client-grants") + .addPathSegment(grantId) + .build() + .toString(); + + return new VoidRequest(client, tokenProvider, url, HttpMethod.DELETE); + } + private void applyFilter(BaseFilter filter, HttpUrl.Builder builder) { if (filter != null) { for (Map.Entry e : filter.getAsMap().entrySet()) { diff --git a/src/main/java/com/auth0/client/mgmt/filter/ClientGrantsFilter.java b/src/main/java/com/auth0/client/mgmt/filter/ClientGrantsFilter.java index 94f6ab39..15da9b9e 100644 --- a/src/main/java/com/auth0/client/mgmt/filter/ClientGrantsFilter.java +++ b/src/main/java/com/auth0/client/mgmt/filter/ClientGrantsFilter.java @@ -31,6 +31,16 @@ public ClientGrantsFilter withAudience(String audience) { return this; } + /** + * Filter by {@code allow_any_organization} + * @param allowAnyOrganization only retrieve items with the {@code allow_any_organization} value specfied. + * @return this filter instance. + */ + public ClientGrantsFilter withAllowAnyOrganization(Boolean allowAnyOrganization) { + parameters.put("allow_any_organization", allowAnyOrganization); + return this; + } + /** * Filter by page * diff --git a/src/main/java/com/auth0/client/mgmt/filter/OrganizationClientGrantsFilter.java b/src/main/java/com/auth0/client/mgmt/filter/OrganizationClientGrantsFilter.java new file mode 100644 index 00000000..e607da02 --- /dev/null +++ b/src/main/java/com/auth0/client/mgmt/filter/OrganizationClientGrantsFilter.java @@ -0,0 +1,54 @@ +package com.auth0.client.mgmt.filter; + +/** + * Class used to filter the results received when listing the client grants associated with an organization. + * Related to the {@link com.auth0.client.mgmt.OrganizationsEntity} entity. + */ +public class OrganizationClientGrantsFilter extends BaseFilter { + + /** + * Filter by client id + * + * @param clientId only retrieve items with this client id. + * @return this filter instance + */ + public OrganizationClientGrantsFilter withClientId(String clientId) { + parameters.put("client_id", clientId); + return this; + } + + /** + * Filter by audience + * + * @param audience only retrieve the item with this audience. + * @return this filter instance + */ + public OrganizationClientGrantsFilter withAudience(String audience) { + parameters.put("audience", audience); + return this; + } + + /** + * Filter by page + * + * @param pageNumber the page number to retrieve. + * @param amountPerPage the amount of items per page to retrieve. + * @return this filter instance + */ + public OrganizationClientGrantsFilter withPage(int pageNumber, int amountPerPage) { + parameters.put("page", pageNumber); + parameters.put("per_page", amountPerPage); + return this; + } + + /** + * Include the query summary + * + * @param includeTotals whether to include or not the query summary. + * @return this filter instance + */ + public OrganizationClientGrantsFilter withTotals(boolean includeTotals) { + parameters.put("include_totals", includeTotals); + return this; + } +} diff --git a/src/main/java/com/auth0/json/mgmt/clientgrants/ClientGrant.java b/src/main/java/com/auth0/json/mgmt/clientgrants/ClientGrant.java index b8952184..86a6a3a4 100644 --- a/src/main/java/com/auth0/json/mgmt/clientgrants/ClientGrant.java +++ b/src/main/java/com/auth0/json/mgmt/clientgrants/ClientGrant.java @@ -22,6 +22,10 @@ public class ClientGrant { private String audience; @JsonProperty("scope") private List scope; + @JsonProperty("organization_usage") + private String organizationUsage; + @JsonProperty("allow_any_organization") + private Boolean allowAnyOrganization; /** * Getter for the id of the client grant. @@ -92,4 +96,32 @@ public List getScope() { public void setScope(List scope) { this.scope = scope; } + + /** + * @return the organization use + */ + public String getOrganizationUsage() { + return organizationUsage; + } + + /** + * @param organizationUsage the value of {@code organization_usage} to set. + */ + public void setOrganizationUsage(String organizationUsage) { + this.organizationUsage = organizationUsage; + } + + /** + * @return the value of {@code allow_any_organization} + */ + public Boolean getAllowAnyOrganization() { + return allowAnyOrganization; + } + + /** + * @param allowAnyOrganization the value of {@code allow_any_organization} to set. + */ + public void setAllowAnyOrganization(Boolean allowAnyOrganization) { + this.allowAnyOrganization = allowAnyOrganization; + } } diff --git a/src/main/java/com/auth0/json/mgmt/organizations/CreateOrganizationClientGrantRequestBody.java b/src/main/java/com/auth0/json/mgmt/organizations/CreateOrganizationClientGrantRequestBody.java new file mode 100644 index 00000000..451276db --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/organizations/CreateOrganizationClientGrantRequestBody.java @@ -0,0 +1,28 @@ +package com.auth0.json.mgmt.organizations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents the body of the request to send when associating a client grant with an organization. + * @see com.auth0.client.mgmt.OrganizationsEntity#addClientGrant(String, CreateOrganizationClientGrantRequestBody) + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CreateOrganizationClientGrantRequestBody { + + @JsonProperty("grant_id") + private String grantId; + + /** + * Create a new instance. + * @param grantId the ID of the grant. + */ + @JsonCreator + public CreateOrganizationClientGrantRequestBody(String grantId) { + this.grantId = grantId; + } + +} diff --git a/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrant.java b/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrant.java new file mode 100644 index 00000000..358fd379 --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrant.java @@ -0,0 +1,95 @@ +package com.auth0.json.mgmt.organizations; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * Represents a client grant associated with an organization. + * @see com.auth0.client.mgmt.OrganizationsEntity + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class OrganizationClientGrant { + @JsonProperty("id") + private String id; + @JsonProperty("client_id") + private String clientId; + @JsonProperty("audience") + private String audience; + @JsonProperty("scope") + private List scope; + + /** + * Getter for the id of the client grant. + * + * @return the id. + */ + @JsonProperty("id") + public String getId() { + return id; + } + + /** + * Getter for the client id of the application. + * + * @return the application's client id. + */ + @JsonProperty("client_id") + public String getClientId() { + return clientId; + } + + /** + * Setter for the application's client id. + * + * @param clientId the application's client id to set. + */ + @JsonProperty("client_id") + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * Getter for the audience. + * + * @return the audience. + */ + @JsonProperty("audience") + public String getAudience() { + return audience; + } + + /** + * Setter for the audience. + * + * @param audience the audience to set. + */ + @JsonProperty("audience") + public void setAudience(String audience) { + this.audience = audience; + } + + /** + * Getter for the scope. + * + * @return the scope. + */ + @JsonProperty("scope") + public List getScope() { + return scope; + } + + /** + * Setter for the scope. + * + * @param scope the scope to set. + */ + @JsonProperty("scope") + public void setScope(List scope) { + this.scope = scope; + } + +} diff --git a/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPage.java b/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPage.java new file mode 100644 index 00000000..faa2961c --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPage.java @@ -0,0 +1,27 @@ +package com.auth0.json.mgmt.organizations; + +import com.auth0.json.mgmt.Page; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +import java.util.List; + +/** + * Represents a page of a response when getting the client grants associated with an organization. + * @see OrganizationClientGrant + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonDeserialize(using = OrganizationClientGrantsPageDeserializer.class) +public class OrganizationClientGrantsPage extends Page { + + public OrganizationClientGrantsPage(List items) { + super(items); + } + + public OrganizationClientGrantsPage(Integer start, Integer length, Integer total, Integer limit, List items) { + super(start, length, total, limit, items); + } + +} diff --git a/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPageDeserializer.java b/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPageDeserializer.java new file mode 100644 index 00000000..c334e759 --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPageDeserializer.java @@ -0,0 +1,25 @@ +package com.auth0.json.mgmt.organizations; + +import com.auth0.json.mgmt.PageDeserializer; + +import java.util.List; + +/** + * Parses a paged response into its {@linkplain OrganizationClientGrant} representation. + */ +public class OrganizationClientGrantsPageDeserializer extends PageDeserializer { + + OrganizationClientGrantsPageDeserializer() { + super(OrganizationClientGrant.class, "client_grants"); + } + + @Override + protected OrganizationClientGrantsPage createPage(List items) { + return new OrganizationClientGrantsPage(items); + } + + @Override + protected OrganizationClientGrantsPage createPage(Integer start, Integer length, Integer total, Integer limit, List items) { + return new OrganizationClientGrantsPage(start, length, total, limit, items); + } +} diff --git a/src/test/java/com/auth0/client/MockServer.java b/src/test/java/com/auth0/client/MockServer.java index 26a1bcac..9df874b2 100644 --- a/src/test/java/com/auth0/client/MockServer.java +++ b/src/test/java/com/auth0/client/MockServer.java @@ -127,6 +127,9 @@ public class MockServer { public static final String ORGANIZATION_CONNECTION = "src/test/resources/mgmt/organization_connection.json"; public static final String ORGANIZATION_MEMBER_ROLES_LIST = "src/test/resources/mgmt/organization_member_roles_list.json"; public static final String ORGANIZATION_MEMBER_ROLES_PAGED_LIST = "src/test/resources/mgmt/organization_member_roles_paged_list.json"; + public static final String ORGANIZATION_CLIENT_GRANTS = "src/test/resources/mgmt/organization_client_grants.json"; + public static final String ORGANIZATION_CLIENT_GRANTS_PAGED_LIST = "src/test/resources/mgmt/organization_client_grants_paged_list.json"; + public static final String ORGANIZATION_CLIENT_GRANT = "src/test/resources/mgmt/organization_client_grant.json"; public static final String INVITATION = "src/test/resources/mgmt/invitation.json"; public static final String INVITATIONS_LIST = "src/test/resources/mgmt/invitations_list.json"; public static final String INVITATIONS_PAGED_LIST = "src/test/resources/mgmt/invitations_paged_list.json"; diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index f33c9291..fa6cee49 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -848,6 +848,61 @@ public void shouldCreateLogInWithClientCredentialsGrantRequest() throws Exceptio assertThat(body, hasEntry("client_id", CLIENT_ID)); assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, not(hasEntry("organization", any(String.class)))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldCreateLogInWithClientCredentialsGrantRequestWithOrg() throws Exception { + TokenRequest request = api.requestToken("https://myapi.auth0.com/users", "org_123"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "client_credentials")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, hasEntry("organization", "org_123")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldCreateLogInWithClientCredentialsGrantRequestWithoutOrgWhenEmpty() throws Exception { + TokenRequest request = api.requestToken("https://myapi.auth0.com/users", " "); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "client_credentials")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, not(hasKey("organization"))); assertThat(response, is(notNullValue())); assertThat(response.getAccessToken(), not(emptyOrNullString())); diff --git a/src/test/java/com/auth0/client/mgmt/ClientGrantsEntityTest.java b/src/test/java/com/auth0/client/mgmt/ClientGrantsEntityTest.java index 16220665..8d810c94 100644 --- a/src/test/java/com/auth0/client/mgmt/ClientGrantsEntityTest.java +++ b/src/test/java/com/auth0/client/mgmt/ClientGrantsEntityTest.java @@ -1,8 +1,10 @@ package com.auth0.client.mgmt; import com.auth0.client.mgmt.filter.ClientGrantsFilter; +import com.auth0.client.mgmt.filter.PageFilter; import com.auth0.json.mgmt.clientgrants.ClientGrant; import com.auth0.json.mgmt.clientgrants.ClientGrantsPage; +import com.auth0.json.mgmt.organizations.OrganizationsPage; import com.auth0.net.Request; import com.auth0.net.client.HttpMethod; import okhttp3.mockwebserver.RecordedRequest; @@ -83,6 +85,7 @@ public void shouldListClientGrantsWithTotals() throws Exception { public void shouldListClientGrantsWithAdditionalProperties() throws Exception { ClientGrantsFilter filter = new ClientGrantsFilter() .withAudience("https://myapi.auth0.com") + .withAllowAnyOrganization(true) .withClientId("u9e3hh3e9j2fj9092ked"); Request request = api.clientGrants().list(filter); assertThat(request, is(notNullValue())); @@ -96,6 +99,7 @@ public void shouldListClientGrantsWithAdditionalProperties() throws Exception { assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); assertThat(recordedRequest, hasQueryParameter("audience", "https://myapi.auth0.com")); assertThat(recordedRequest, hasQueryParameter("client_id", "u9e3hh3e9j2fj9092ked")); + assertThat(recordedRequest, hasQueryParameter("allow_any_organization", "true")); assertThat(response, is(notNullValue())); assertThat(response.getItems(), hasSize(2)); @@ -141,6 +145,58 @@ public void shouldCreateClientGrant() throws Exception { assertThat(body, hasEntry("audience", "audience")); assertThat(body, hasKey("scope")); assertThat((Iterable) body.get("scope"), contains("openid")); + assertThat(body, not(hasKey("organization_usage"))); + assertThat(body, not(hasKey("allow_any_organization"))); + + assertThat(response, is(notNullValue())); + } + + @Test + public void shouldCreateClientGrantWithOrgParams() throws Exception { + Request request = api.clientGrants().create("clientId", "audience", new String[]{"openid"}, "allow", false); + assertThat(request, is(notNullValue())); + + server.jsonResponse(MGMT_CLIENT_GRANT, 200); + ClientGrant response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/api/v2/client-grants")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body.size(), is(5)); + assertThat(body, hasEntry("client_id", "clientId")); + assertThat(body, hasEntry("audience", "audience")); + assertThat(body, hasKey("scope")); + assertThat((Iterable) body.get("scope"), contains("openid")); + assertThat(body, hasEntry("organization_usage", "allow")); + assertThat(body, hasEntry("allow_any_organization", false)); + + assertThat(response, is(notNullValue())); + } + + @Test + public void createClientGrantWithOrgParamsShouldNotSendEmptyOrgUsage() throws Exception { + Request request = api.clientGrants().create("clientId", "audience", new String[]{"openid"}, " ", false); + assertThat(request, is(notNullValue())); + + server.jsonResponse(MGMT_CLIENT_GRANT, 200); + ClientGrant response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/api/v2/client-grants")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body.size(), is(4)); + assertThat(body, hasEntry("client_id", "clientId")); + assertThat(body, hasEntry("audience", "audience")); + assertThat(body, hasKey("scope")); + assertThat((Iterable) body.get("scope"), contains("openid")); + assertThat(body, not(hasKey("organization_usage"))); + assertThat(body, hasEntry("allow_any_organization", false)); assertThat(response, is(notNullValue())); } @@ -196,8 +252,97 @@ public void shouldUpdateClientGrant() throws Exception { Map body = bodyFromRequest(recordedRequest); assertThat(body.size(), is(1)); assertThat((ArrayList) body.get("scope"), contains("openid", "profile")); + assertThat(body, not(hasKey("organization_usage"))); + assertThat(body, not(hasKey("allow_any_organization"))); + + assertThat(response, is(notNullValue())); + } + + @Test + public void shouldUpdateClientGrantWithOrgParams() throws Exception { + Request request = api.clientGrants().update("1", new String[]{"openid", "profile"}, "allow", true); + assertThat(request, is(notNullValue())); + + server.jsonResponse(MGMT_CLIENT_GRANT, 200); + ClientGrant response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.PATCH, "/api/v2/client-grants/1")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body.size(), is(3)); + assertThat((ArrayList) body.get("scope"), contains("openid", "profile")); + assertThat(body, hasEntry("organization_usage", "allow")); + assertThat(body, hasEntry("allow_any_organization", true)); + + assertThat(response, is(notNullValue())); + } + + @Test + public void updateClientGrantWithOrgParamsShouldNotSendEmptyOrgUsage() throws Exception { + Request request = api.clientGrants().update("1", new String[]{"openid", "profile"}, " ", true); + assertThat(request, is(notNullValue())); + + server.jsonResponse(MGMT_CLIENT_GRANT, 200); + ClientGrant response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.PATCH, "/api/v2/client-grants/1")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body.size(), is(2)); + assertThat((ArrayList) body.get("scope"), contains("openid", "profile")); + assertThat(body, not(hasKey("organization_usage"))); + assertThat(body, hasEntry("allow_any_organization", true)); + + assertThat(response, is(notNullValue())); + } + + @Test + public void shouldListOrganizationsWithoutFilter() throws Exception { + Request request = api.clientGrants().listOrganizations("grant-id", null); + assertThat(request, is(notNullValue())); + + server.jsonResponse(ORGANIZATIONS_LIST, 200); + OrganizationsPage response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/api/v2/client-grants/grant-id/organizations")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + assertThat(response, is(notNullValue())); + assertThat(response.getItems(), hasSize(2)); + } + + @Test + public void shouldListOrganizationsWithPage() throws Exception { + PageFilter filter = new PageFilter().withPage(23, 5); + Request request = api.clientGrants().listOrganizations("grant-id", filter); + assertThat(request, is(notNullValue())); + + server.jsonResponse(ORGANIZATIONS_PAGED_LIST, 200); + OrganizationsPage response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/api/v2/client-grants/grant-id/organizations")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + assertThat(recordedRequest, hasQueryParameter("page", "23")); + assertThat(recordedRequest, hasQueryParameter("per_page", "5")); assertThat(response, is(notNullValue())); + assertThat(response.getItems(), hasSize(2)); } + @Test + public void shouldThrowOnGetOrganizationsWithNullId() { + verifyThrows(IllegalArgumentException.class, + () -> api.clientGrants().listOrganizations(null, new PageFilter()), + "'client grant ID' cannot be null!"); + } } diff --git a/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java b/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java index 6eb1d8ec..be99e6f1 100644 --- a/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java +++ b/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java @@ -3,6 +3,7 @@ import com.auth0.client.MockServer; import com.auth0.client.mgmt.filter.FieldsFilter; import com.auth0.client.mgmt.filter.InvitationsFilter; +import com.auth0.client.mgmt.filter.OrganizationClientGrantsFilter; import com.auth0.client.mgmt.filter.PageFilter; import com.auth0.json.mgmt.organizations.*; import com.auth0.json.mgmt.roles.RolesPage; @@ -1030,4 +1031,122 @@ public void shouldDeleteInvitation() throws Exception { assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); } + + @Test + public void shouldListClientGrantsWithoutFilter() throws Exception { + Request request = api.organizations().listClientGrants("orgId", null); + assertThat(request, is(notNullValue())); + + server.jsonResponse(ORGANIZATION_CLIENT_GRANTS, 200); + OrganizationClientGrantsPage response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/api/v2/organizations/orgId/client-grants")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + assertThat(response, is(notNullValue())); + assertThat(response.getItems(), hasSize(2)); + } + + @Test + public void shouldListClientGrantsWithFilter() throws Exception { + OrganizationClientGrantsFilter filter = new OrganizationClientGrantsFilter(); + filter + .withClientId("clientId") + .withAudience("https://api-identifier/") + .withPage(1, 2) + .withTotals(true); + + Request request = api.organizations().listClientGrants("orgId", filter); + assertThat(request, is(notNullValue())); + + server.jsonResponse(ORGANIZATION_CLIENT_GRANTS_PAGED_LIST, 200); + OrganizationClientGrantsPage response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/api/v2/organizations/orgId/client-grants")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + assertThat(recordedRequest, hasQueryParameter("page", "1")); + assertThat(recordedRequest, hasQueryParameter("per_page", "2")); + assertThat(recordedRequest, hasQueryParameter("include_totals", "true")); + assertThat(recordedRequest, hasQueryParameter("audience", "https://api-identifier/")); + assertThat(recordedRequest, hasQueryParameter("client_id", "clientId")); + + assertThat(response, is(notNullValue())); + assertThat(response.getItems(), hasSize(1)); + } + + @Test + public void shouldThrowOnGetClientGrantsWithNullOrgId() { + verifyThrows(IllegalArgumentException.class, + () -> api.organizations().listClientGrants(null, null), + "'organization ID' cannot be null!"); + } + + @Test + public void shouldCreateClientGrant() throws Exception { + CreateOrganizationClientGrantRequestBody requestBody = new CreateOrganizationClientGrantRequestBody("grant-id"); + + Request request = api.organizations().addClientGrant("org_123", requestBody); + + assertThat(request, is(notNullValue())); + + server.jsonResponse(MGMT_CLIENT_GRANT, 201); + OrganizationClientGrant response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/api/v2/organizations/org_123/client-grants")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, aMapWithSize(1)); + assertThat(body, hasEntry("grant_id", "grant-id")); + + assertThat(response, is(notNullValue())); + } + + @Test + public void shouldThrowOnCreateClientGrantWithNullOrgId() { + verifyThrows(IllegalArgumentException.class, + () -> api.organizations().addClientGrant(null, new CreateOrganizationClientGrantRequestBody("id")), + "'organization ID' cannot be null!"); + } + + @Test + public void shouldThrowOnCreateClientGreatWithNullGrant() { + verifyThrows(IllegalArgumentException.class, + () -> api.organizations().addClientGrant("org_1213", null), + "'client grant' cannot be null!"); + } + + @Test + public void shouldDeleteClientGrant() throws Exception { + Request request = api.organizations().deleteClientGrant("org_abc", "grant_123"); + assertThat(request, is(notNullValue())); + + server.emptyResponse(204); + request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.DELETE, "/api/v2/organizations/org_abc/client-grants/grant_123")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + } + + @Test + public void shouldThrowOnDeleteClientGrantWithNullOrgId() { + verifyThrows(IllegalArgumentException.class, + () -> api.organizations().deleteClientGrant(null, "grant-id"), + "'organization ID' cannot be null!"); + } + + @Test + public void shouldThrowOnDeleteClientGreatWithNullGrant() { + verifyThrows(IllegalArgumentException.class, + () -> api.organizations().deleteClientGrant("org_1213", null), + "'client grant ID' cannot be null!"); + } } diff --git a/src/test/java/com/auth0/json/mgmt/ClientGrantTest.java b/src/test/java/com/auth0/json/mgmt/ClientGrantTest.java index 4462d86d..cade1f90 100644 --- a/src/test/java/com/auth0/json/mgmt/ClientGrantTest.java +++ b/src/test/java/com/auth0/json/mgmt/ClientGrantTest.java @@ -12,7 +12,7 @@ public class ClientGrantTest extends JsonTest { - private static final String json = "{\"client_id\":\"clientId\",\"audience\":\"aud\",\"scope\":[\"one\",\"two\"]}"; + private static final String json = "{\"client_id\":\"clientId\",\"audience\":\"aud\",\"scope\":[\"one\",\"two\"],\"organization_usage\": \"allow\",\"allow_any_organization\":true}"; private static final String readOnlyJson = "{\"id\":\"grantId\"}"; @Test @@ -21,12 +21,16 @@ public void shouldSerialize() throws Exception { grant.setAudience("aud"); grant.setClientId("clientId"); grant.setScope(Arrays.asList("one", "two")); + grant.setOrganizationUsage("require"); + grant.setAllowAnyOrganization(true); String serialized = toJSON(grant); assertThat(serialized, is(notNullValue())); assertThat(serialized, JsonMatcher.hasEntry("client_id", "clientId")); assertThat(serialized, JsonMatcher.hasEntry("audience", "aud")); assertThat(serialized, JsonMatcher.hasEntry("scope", Arrays.asList("one", "two"))); + assertThat(serialized, JsonMatcher.hasEntry("organization_usage", "require")); + assertThat(serialized, JsonMatcher.hasEntry("allow_any_organization", true)); } @Test @@ -38,6 +42,8 @@ public void shouldDeserialize() throws Exception { assertThat(grant.getAudience(), is("aud")); assertThat(grant.getClientId(), is("clientId")); assertThat(grant.getScope(), contains("one", "two")); + assertThat(grant.getOrganizationUsage(), is("allow")); + assertThat(grant.getAllowAnyOrganization(), is(true)); } @Test diff --git a/src/test/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantTest.java b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantTest.java new file mode 100644 index 00000000..5d44821f --- /dev/null +++ b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantTest.java @@ -0,0 +1,48 @@ +package com.auth0.json.mgmt.organizations; + +import com.auth0.json.JsonMatcher; +import com.auth0.json.JsonTest; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class OrganizationClientGrantTest extends JsonTest { + + @Test + public void shouldSerialize() throws Exception { + OrganizationClientGrant clientGrant = new OrganizationClientGrant(); + clientGrant.setClientId("client-id"); + clientGrant.setAudience("https://api-identifier/"); + clientGrant.setScope(Arrays.asList("read:messages", "write:messages")); + + String serialized = toJSON(clientGrant); + assertThat(serialized, is(notNullValue())); + assertThat(serialized, JsonMatcher.hasEntry("client_id", "client-id")); + assertThat(serialized, JsonMatcher.hasEntry("audience", "https://api-identifier/")); + assertThat(serialized, JsonMatcher.hasEntry("scope", Arrays.asList("read:messages", "write:messages"))); + } + + @Test + public void shouldDeserialize() throws Exception { + String json = "{\n" + + " \"id\": \"1\",\n" + + " \"client_id\": \"clientId1\",\n" + + " \"audience\": \"audience1\",\n" + + " \"scope\": [\n" + + " \"openid\",\n" + + " \"profile\"\n" + + " ]\n" + + "}"; + + OrganizationClientGrant clientGrant = fromJSON(json, OrganizationClientGrant.class); + + assertThat(clientGrant, is(notNullValue())); + + assertThat(clientGrant.getAudience(), is("audience1")); + assertThat(clientGrant.getClientId(), is("clientId1")); + assertThat(clientGrant.getScope(), contains("openid", "profile")); + } +} diff --git a/src/test/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPageTest.java b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPageTest.java new file mode 100644 index 00000000..89aa3fe9 --- /dev/null +++ b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationClientGrantsPageTest.java @@ -0,0 +1,71 @@ +package com.auth0.json.mgmt.organizations; + +import com.auth0.json.JsonTest; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class OrganizationClientGrantsPageTest extends JsonTest { + + private static final String JSON_WITHOUT_TOTALS = "[\n" + + " {\n" + + " \"id\": \"cgr_4pI9a42haOLLWnwq\",\n" + + " \"client_id\": \"client-id\",\n" + + " \"audience\": \"https://api-identifier\",\n" + + " \"scope\": [\n" + + " \"update:items\",\n" + + " \"read:messages\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"id\": \"cgr_D018f9kmBmwbZod\",\n" + + " \"client_id\": \"client-id\",\n" + + " \"audience\": \"https://api-identifier\",\n" + + " \"scope\": []\n" + + " }\n" + + "]"; + + private static final String JSON_WITH_TOTALS = "{\n" + + " \"total\": 13,\n" + + " \"start\": 0,\n" + + " \"limit\": 1,\n" + + " \"client_grants\": [\n" + + " {\n" + + " \"id\": \"cgr_3aidkk3skLVOM3Ay7\",\n" + + " \"client_id\": \"client-id\",\n" + + " \"audience\": \"https://api-identifier/\",\n" + + " \"scope\": [\n" + + " \"read:messages\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + + @Test + public void shouldDeserializeWithoutTotals() throws Exception { + OrganizationClientGrantsPage page = fromJSON(JSON_WITHOUT_TOTALS, OrganizationClientGrantsPage.class); + + assertThat(page, is(notNullValue())); + assertThat(page.getStart(), is(nullValue())); + assertThat(page.getLength(), is(nullValue())); + assertThat(page.getTotal(), is(nullValue())); + assertThat(page.getLimit(), is(nullValue())); + assertThat(page.getNext(), is(nullValue())); + assertThat(page.getItems(), is(notNullValue())); + assertThat(page.getItems().size(), is(2)); + } + + @Test + public void shouldDeserializeWithTotals() throws Exception { + OrganizationClientGrantsPage page = fromJSON(JSON_WITH_TOTALS, OrganizationClientGrantsPage.class); + + assertThat(page, is(notNullValue())); + assertThat(page.getStart(), is(0)); + assertThat(page.getTotal(), is(13)); + assertThat(page.getLimit(), is(1)); + assertThat(page.getNext(), is(nullValue())); + assertThat(page.getItems(), is(notNullValue())); + assertThat(page.getItems().size(), is(1)); + } +} diff --git a/src/test/resources/mgmt/organization_client_grant.json b/src/test/resources/mgmt/organization_client_grant.json new file mode 100644 index 00000000..83f6255e --- /dev/null +++ b/src/test/resources/mgmt/organization_client_grant.json @@ -0,0 +1,9 @@ +{ + "id": "1", + "client_id": "clientId1", + "audience": "audience1", + "scope": [ + "openid", + "profile" + ] +} diff --git a/src/test/resources/mgmt/organization_client_grants.json b/src/test/resources/mgmt/organization_client_grants.json new file mode 100644 index 00000000..8f5eaec1 --- /dev/null +++ b/src/test/resources/mgmt/organization_client_grants.json @@ -0,0 +1,17 @@ +[ + { + "id": "cgr_4pI9a42haOLLWnwq", + "client_id": "client-id", + "audience": "https://api-identifier", + "scope": [ + "update:items", + "read:messages" + ] + }, + { + "id": "cgr_D018f9kmBmwbZod", + "client_id": "client-id", + "audience": "https://api-identifier", + "scope": [] + } +] diff --git a/src/test/resources/mgmt/organization_client_grants_paged_list.json b/src/test/resources/mgmt/organization_client_grants_paged_list.json new file mode 100644 index 00000000..fe52b21b --- /dev/null +++ b/src/test/resources/mgmt/organization_client_grants_paged_list.json @@ -0,0 +1,15 @@ +{ + "total": 13, + "start": 0, + "limit": 1, + "client_grants": [ + { + "id": "cgr_3aidkk3skLVOM3Ay7", + "client_id": "client-id", + "audience": "https://api-identifier/", + "scope": [ + "read:messages" + ] + } + ] +}