From 76fba90d32ed21f48b8fd5d28a206579fd4ecdd1 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Wed, 16 Apr 2025 13:50:12 +0530 Subject: [PATCH 01/12] added draft changes for Auth API --- src/main/java/com/auth0/net/Response.java | 4 ++ src/main/java/com/auth0/net/ResponseImpl.java | 58 +++++++++++++++++++ src/main/java/com/auth0/net/TokenQuota.java | 25 ++++++++ .../java/com/auth0/net/TokenQuotaBucket.java | 19 ++++++ 4 files changed, 106 insertions(+) create mode 100644 src/main/java/com/auth0/net/TokenQuota.java create mode 100644 src/main/java/com/auth0/net/TokenQuotaBucket.java diff --git a/src/main/java/com/auth0/net/Response.java b/src/main/java/com/auth0/net/Response.java index 4064f1b9c..c6ee75425 100644 --- a/src/main/java/com/auth0/net/Response.java +++ b/src/main/java/com/auth0/net/Response.java @@ -23,4 +23,8 @@ public interface Response { * @return the HTTP status code. */ int getStatusCode(); + + TokenQuotaBucket getClientQuotaLimit(); + + TokenQuotaBucket getOrganizationQuotaLimit(); } diff --git a/src/main/java/com/auth0/net/ResponseImpl.java b/src/main/java/com/auth0/net/ResponseImpl.java index 7cd35b996..38e1daa0c 100644 --- a/src/main/java/com/auth0/net/ResponseImpl.java +++ b/src/main/java/com/auth0/net/ResponseImpl.java @@ -29,4 +29,62 @@ public T getBody() { public int getStatusCode() { return statusCode; } + + @Override + public TokenQuotaBucket getClientQuotaLimit() { + String quotaHeader = headers.get("X-Quota-Client-Limit"); + if (quotaHeader != null) { + return parseQuota(quotaHeader); + } + return null; + } + + @Override + public TokenQuotaBucket getOrganizationQuotaLimit() { + String quotaHeader = headers.get("X-Quota-Organization-Limit"); + if (quotaHeader != null) { + return parseQuota(quotaHeader); + } + return null; + } + + public static TokenQuotaBucket parseQuota(String tokenQuota) { + + TokenQuota perHour = null; + TokenQuota perDay = null; + + String[] parts = tokenQuota.split(","); + for (String part : parts) { + String[] attributes = part.split(";"); + int quota = 0, remaining = 0, time = 0; + + for (String attribute : attributes) { + String[] keyValue = attribute.split("="); + if (keyValue.length != 2) continue; + + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + switch (key) { + case "q": + quota = Integer.parseInt(value); + break; + case "r": + remaining = Integer.parseInt(value); + break; + case "t": + time = Integer.parseInt(value); + break; + } + } + + if (attributes[0].contains("per_hour")) { + perHour = new TokenQuota(quota, remaining, time); + } else if (attributes[0].contains("per_day")) { + perDay = new TokenQuota(quota, remaining, time); + } + } + + return new TokenQuotaBucket(perHour, perDay); + } } diff --git a/src/main/java/com/auth0/net/TokenQuota.java b/src/main/java/com/auth0/net/TokenQuota.java new file mode 100644 index 000000000..6f18598c0 --- /dev/null +++ b/src/main/java/com/auth0/net/TokenQuota.java @@ -0,0 +1,25 @@ +package com.auth0.net; + +public class TokenQuota { + private int quota; + private int remaining; + private int time; + + public TokenQuota(int quota, int remaining, int time) { + this.quota = quota; + this.remaining = remaining; + this.time = time; + } + + public int getQuota() { + return quota; + } + + public int getRemaining() { + return remaining; + } + + public int getTime() { + return time; + } +} diff --git a/src/main/java/com/auth0/net/TokenQuotaBucket.java b/src/main/java/com/auth0/net/TokenQuotaBucket.java new file mode 100644 index 000000000..e51272641 --- /dev/null +++ b/src/main/java/com/auth0/net/TokenQuotaBucket.java @@ -0,0 +1,19 @@ +package com.auth0.net; + +public class TokenQuotaBucket { + private TokenQuota perHour; + private TokenQuota perDay; + + public TokenQuotaBucket(TokenQuota perHour, TokenQuota perDay) { + this.perHour = perHour; + this.perDay = perDay; + } + + public TokenQuota getPerHour() { + return perHour; + } + + public TokenQuota getPerDay() { + return perDay; + } +} From 1377e5aa892a182693139171129c5a94f15eab20 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Thu, 17 Apr 2025 17:38:30 +0530 Subject: [PATCH 02/12] added management API changes --- .../auth/TokenQuotaLimit.java} | 6 +- .../com/auth0/json/mgmt/client/Client.java | 19 +++++ .../json/mgmt/organizations/Organization.java | 19 +++++ .../com/auth0/json/mgmt/tenants/Clients.java | 35 ++++++++++ .../json/mgmt/tenants/DefaultTokenQuota.java | 59 ++++++++++++++++ .../json/mgmt/tenants/Organizations.java | 34 +++++++++ .../com/auth0/json/mgmt/tenants/Tenant.java | 37 ++++++++++ .../mgmt/tokenquota/ClientCredentials.java | 60 ++++++++++++++++ .../json/mgmt/tokenquota/TokenQuota.java | 32 +++++++++ src/main/java/com/auth0/net/ResponseImpl.java | 10 +-- .../java/com/auth0/net/TokenQuotaBucket.java | 12 ++-- .../auth0/json/mgmt/client/ClientTest.java | 27 +++++++- .../auth0/json/mgmt/tenants/TenantTest.java | 69 ++++++++++++++++++- 13 files changed, 405 insertions(+), 14 deletions(-) rename src/main/java/com/auth0/{net/TokenQuota.java => json/auth/TokenQuotaLimit.java} (73%) create mode 100644 src/main/java/com/auth0/json/mgmt/tenants/Clients.java create mode 100644 src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java create mode 100644 src/main/java/com/auth0/json/mgmt/tenants/Organizations.java create mode 100644 src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java create mode 100644 src/main/java/com/auth0/json/mgmt/tokenquota/TokenQuota.java diff --git a/src/main/java/com/auth0/net/TokenQuota.java b/src/main/java/com/auth0/json/auth/TokenQuotaLimit.java similarity index 73% rename from src/main/java/com/auth0/net/TokenQuota.java rename to src/main/java/com/auth0/json/auth/TokenQuotaLimit.java index 6f18598c0..93852f854 100644 --- a/src/main/java/com/auth0/net/TokenQuota.java +++ b/src/main/java/com/auth0/json/auth/TokenQuotaLimit.java @@ -1,11 +1,11 @@ -package com.auth0.net; +package com.auth0.json.auth; -public class TokenQuota { +public class TokenQuotaLimit { private int quota; private int remaining; private int time; - public TokenQuota(int quota, int remaining, int time) { + public TokenQuotaLimit(int quota, int remaining, int time) { this.quota = quota; this.remaining = remaining; this.time = time; diff --git a/src/main/java/com/auth0/json/mgmt/client/Client.java b/src/main/java/com/auth0/json/mgmt/client/Client.java index 0b0e705e1..35be65d26 100644 --- a/src/main/java/com/auth0/json/mgmt/client/Client.java +++ b/src/main/java/com/auth0/json/mgmt/client/Client.java @@ -1,5 +1,6 @@ package com.auth0.json.mgmt.client; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -104,6 +105,8 @@ public class Client { private Boolean requireProofOfPossession; @JsonProperty("default_organization") private ClientDefaultOrganization defaultOrganization; + @JsonProperty("token_quota") + private TokenQuota tokenQuota; /** * Getter for the name of the tenant this client belongs to. @@ -907,5 +910,21 @@ public ClientDefaultOrganization getDefaultOrganization() { public void setDefaultOrganization(ClientDefaultOrganization defaultOrganization) { this.defaultOrganization = defaultOrganization; } + + /** + * Getter for the token quota configuration. + * @return the token quota configuration. + */ + public TokenQuota getTokenQuota() { + return tokenQuota; + } + + /** + * Setter for the token quota configuration. + * @param tokenQuota the token quota configuration to set. + */ + public void setTokenQuota(TokenQuota tokenQuota) { + this.tokenQuota = tokenQuota; + } } diff --git a/src/main/java/com/auth0/json/mgmt/organizations/Organization.java b/src/main/java/com/auth0/json/mgmt/organizations/Organization.java index 4d298f0c5..1b7142492 100644 --- a/src/main/java/com/auth0/json/mgmt/organizations/Organization.java +++ b/src/main/java/com/auth0/json/mgmt/organizations/Organization.java @@ -1,6 +1,7 @@ package com.auth0.json.mgmt.organizations; import com.auth0.client.mgmt.filter.PageFilter; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -29,6 +30,8 @@ public class Organization { private Branding branding; @JsonProperty("enabled_connections") private List enabledConnections; + @JsonProperty("token_quota") + private TokenQuota tokenQuota; public Organization() {} @@ -132,4 +135,20 @@ public List getEnabledConnections() { public void setEnabledConnections(List enabledConnections) { this.enabledConnections = enabledConnections; } + + /** + * @return the token quota of this Organization. + */ + public TokenQuota getTokenQuota() { + return tokenQuota; + } + + /** + * Sets the token quota of this Organization. + * + * @param tokenQuota the token quota of this Organization. + */ + public void setTokenQuota(TokenQuota tokenQuota) { + this.tokenQuota = tokenQuota; + } } diff --git a/src/main/java/com/auth0/json/mgmt/tenants/Clients.java b/src/main/java/com/auth0/json/mgmt/tenants/Clients.java new file mode 100644 index 000000000..49db8743e --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/tenants/Clients.java @@ -0,0 +1,35 @@ +package com.auth0.json.mgmt.tenants; + +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Clients { + @JsonProperty("client_credentials") + private ClientCredentials clientCredentials; + + /** + * Default constructor for Clients. + */ + public Clients() { + } + + /** + * Constructor for Clients. + * + * @param clientCredentials the client credentials + */ + public Clients(ClientCredentials clientCredentials) { + this.clientCredentials = clientCredentials; + } + + /** + * @return the client credentials + */ + public ClientCredentials getClientCredentials() { + return clientCredentials; + } +} diff --git a/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java b/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java new file mode 100644 index 000000000..202d16adc --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java @@ -0,0 +1,59 @@ +package com.auth0.json.mgmt.tenants; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DefaultTokenQuota { + @JsonProperty("clients") + private Clients client; + + @JsonProperty("organizations") + private Organizations organizations; + + /** + * Default constructor for DefaultTokenQuota. + */ + public DefaultTokenQuota() {} + + /** + * Constructor for DefaultTokenQuota. + * + * @param client the clients + * @param organizations the organizations + */ + public DefaultTokenQuota(Clients client, Organizations organizations) { + this.client = client; + this.organizations = organizations; + } + + /** + * @return the clients + */ + public Clients getClient() { + return client; + } + + /** + * @param client the clients to set + */ + public void setClient(Clients client) { + this.client = client; + } + + /** + * @return the organizations + */ + public Organizations getOrganizations() { + return organizations; + } + + /** + * @param organizations the organizations to set + */ + public void setOrganizations(Organizations organizations) { + this.organizations = organizations; + } +} diff --git a/src/main/java/com/auth0/json/mgmt/tenants/Organizations.java b/src/main/java/com/auth0/json/mgmt/tenants/Organizations.java new file mode 100644 index 000000000..56c2d85d8 --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/tenants/Organizations.java @@ -0,0 +1,34 @@ +package com.auth0.json.mgmt.tenants; + +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Organizations { + @JsonProperty("client_credentials") + private ClientCredentials clientCredentials; + + /** + * Default constructor for Organizations. + */ + public Organizations() {} + + /** + * Constructor for Organizations. + * + * @param clientCredentials the client credentials + */ + public Organizations(ClientCredentials clientCredentials) { + this.clientCredentials = clientCredentials; + } + + /** + * @return the client credentials + */ + public ClientCredentials getClientCredentials() { + return clientCredentials; + } +} diff --git a/src/main/java/com/auth0/json/mgmt/tenants/Tenant.java b/src/main/java/com/auth0/json/mgmt/tenants/Tenant.java index a48dc8c52..817db0007 100644 --- a/src/main/java/com/auth0/json/mgmt/tenants/Tenant.java +++ b/src/main/java/com/auth0/json/mgmt/tenants/Tenant.java @@ -58,6 +58,11 @@ public class Tenant { @JsonProperty("mtls") private Mtls mtls; + @JsonProperty("authorization_response_iss_parameter_supported") + private boolean authorizationResponseIssParameterSupported; + + @JsonProperty("default_token_quota") + private DefaultTokenQuota defaultTokenQuota; /** * Getter for the change password page customization. @@ -397,4 +402,36 @@ public Mtls getMtls() { public void setMtls(Mtls mtls) { this.mtls = mtls; } + + /** + * @return the value of the {@code authorization_response_iss_parameter_supported} field + */ + public boolean isAuthorizationResponseIssParameterSupported() { + return authorizationResponseIssParameterSupported; + } + + /** + * Sets the value of the {@code authorization_response_iss_parameter_supported} field + * + * @param authorizationResponseIssParameterSupported the value of the {@code authorization_response_iss_parameter_supported} field + */ + public void setAuthorizationResponseIssParameterSupported(boolean authorizationResponseIssParameterSupported) { + this.authorizationResponseIssParameterSupported = authorizationResponseIssParameterSupported; + } + + /** + * @return the value of the {@code default_token_quota} field + */ + public DefaultTokenQuota getDefaultTokenQuota() { + return defaultTokenQuota; + } + + /** + * Sets the value of the {@code default_token_quota} field + * + * @param defaultTokenQuota the value of the {@code default_token_quota} field + */ + public void setDefaultTokenQuota(DefaultTokenQuota defaultTokenQuota) { + this.defaultTokenQuota = defaultTokenQuota; + } } diff --git a/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java b/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java new file mode 100644 index 000000000..1d7e36b08 --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java @@ -0,0 +1,60 @@ +package com.auth0.json.mgmt.tokenquota; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ClientCredentials { + @JsonProperty("per_day") + private int perDay; + @JsonProperty("per_hour") + private int perHour; + @JsonProperty("enforce") + private boolean enforce; + + /** + * @return the number of client credentials allowed per day + */ + public int getPerDay() { + return perDay; + } + + /** + * Sets the number of client credentials allowed per day. + * + * @param perDay the number of client credentials allowed per day + */ + public void setPerDay(int perDay) { + this.perDay = perDay; + } + + /** + * @return the number of client credentials allowed per hour + */ + public int getPerHour() { + return perHour; + } + + /** + * Sets the number of client credentials allowed per hour. + * + * @param perHour the number of client credentials allowed per hour + */ + public void setPerHour(int perHour) { + this.perHour = perHour; + } + + /** + * @return true if the quota is enforced, false otherwise + */ + public boolean isEnforce() { + return enforce; + } + + /** + * Sets whether the quota is enforced. + * + * @param enforce true to enforce the quota, false otherwise + */ + public void setEnforce(boolean enforce) { + this.enforce = enforce; + } +} diff --git a/src/main/java/com/auth0/json/mgmt/tokenquota/TokenQuota.java b/src/main/java/com/auth0/json/mgmt/tokenquota/TokenQuota.java new file mode 100644 index 000000000..e11dadac2 --- /dev/null +++ b/src/main/java/com/auth0/json/mgmt/tokenquota/TokenQuota.java @@ -0,0 +1,32 @@ +package com.auth0.json.mgmt.tokenquota; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TokenQuota { + @JsonProperty("client_credentials") + private ClientCredentials clientCredentials; + + /** + * Default constructor for Clients. + */ + public TokenQuota() {} + /** + * Constructor for Clients. + * + * @param clientCredentials the client credentials + */ + public TokenQuota(ClientCredentials clientCredentials) { + this.clientCredentials = clientCredentials; + } + + /** + * @return the client credentials + */ + public ClientCredentials getClientCredentials() { + return clientCredentials; + } +} diff --git a/src/main/java/com/auth0/net/ResponseImpl.java b/src/main/java/com/auth0/net/ResponseImpl.java index 38e1daa0c..b9b8d5f3b 100644 --- a/src/main/java/com/auth0/net/ResponseImpl.java +++ b/src/main/java/com/auth0/net/ResponseImpl.java @@ -1,5 +1,7 @@ package com.auth0.net; +import com.auth0.json.auth.TokenQuotaLimit; + import java.util.Collections; import java.util.Map; @@ -50,8 +52,8 @@ public TokenQuotaBucket getOrganizationQuotaLimit() { public static TokenQuotaBucket parseQuota(String tokenQuota) { - TokenQuota perHour = null; - TokenQuota perDay = null; + TokenQuotaLimit perHour = null; + TokenQuotaLimit perDay = null; String[] parts = tokenQuota.split(","); for (String part : parts) { @@ -79,9 +81,9 @@ public static TokenQuotaBucket parseQuota(String tokenQuota) { } if (attributes[0].contains("per_hour")) { - perHour = new TokenQuota(quota, remaining, time); + perHour = new TokenQuotaLimit(quota, remaining, time); } else if (attributes[0].contains("per_day")) { - perDay = new TokenQuota(quota, remaining, time); + perDay = new TokenQuotaLimit(quota, remaining, time); } } diff --git a/src/main/java/com/auth0/net/TokenQuotaBucket.java b/src/main/java/com/auth0/net/TokenQuotaBucket.java index e51272641..e5d4d5d8a 100644 --- a/src/main/java/com/auth0/net/TokenQuotaBucket.java +++ b/src/main/java/com/auth0/net/TokenQuotaBucket.java @@ -1,19 +1,21 @@ package com.auth0.net; +import com.auth0.json.auth.TokenQuotaLimit; + public class TokenQuotaBucket { - private TokenQuota perHour; - private TokenQuota perDay; + private TokenQuotaLimit perHour; + private TokenQuotaLimit perDay; - public TokenQuotaBucket(TokenQuota perHour, TokenQuota perDay) { + public TokenQuotaBucket(TokenQuotaLimit perHour, TokenQuotaLimit perDay) { this.perHour = perHour; this.perDay = perDay; } - public TokenQuota getPerHour() { + public TokenQuotaLimit getPerHour() { return perHour; } - public TokenQuota getPerDay() { + public TokenQuotaLimit getPerDay() { return perDay; } } diff --git a/src/test/java/com/auth0/json/mgmt/client/ClientTest.java b/src/test/java/com/auth0/json/mgmt/client/ClientTest.java index 05bbd465f..301976258 100644 --- a/src/test/java/com/auth0/json/mgmt/client/ClientTest.java +++ b/src/test/java/com/auth0/json/mgmt/client/ClientTest.java @@ -2,6 +2,8 @@ import com.auth0.json.JsonMatcher; import com.auth0.json.JsonTest; +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import org.hamcrest.collection.IsMapContaining; import org.junit.jupiter.api.Test; @@ -140,7 +142,14 @@ public class ClientTest extends JsonTest { " \"default_organization\": {\n" + " \"flows\": [\"client_credentials\"],\n" + " \"organizations_id\": \"org_id\"\n" + - " }\n" + + " },\n" + + " \"token_quota\": {\n" + + " \"client_credentials\": {\n" + + " \"per_hour\": 10,\n" + + " \"per_day\": 100,\n" + + " \"enforce\": true\n" + + " }\n" + + " }" + "}"; @Test @@ -223,6 +232,14 @@ public void shouldSerialize() throws Exception { defaultOrganization.setOrganizationId("org_id"); client.setDefaultOrganization(defaultOrganization); + ClientCredentials clientCredentials = new ClientCredentials(); + clientCredentials.setPerDay(100); + clientCredentials.setPerHour(20); + clientCredentials.setEnforce(true); + + TokenQuota tokenQuota = new TokenQuota(clientCredentials); + client.setTokenQuota(tokenQuota); + String serialized = toJSON(client); assertThat(serialized, is(notNullValue())); @@ -264,6 +281,8 @@ public void shouldSerialize() throws Exception { assertThat(serialized, JsonMatcher.hasEntry("compliance_level", "fapi1_adv_pkj_par")); assertThat(serialized, JsonMatcher.hasEntry("require_proof_of_possession", true)); assertThat(serialized, JsonMatcher.hasEntry("default_organization", notNullValue())); + assertThat(serialized, JsonMatcher.hasEntry("token_quota", notNullValue())); + assertThat(serialized, containsString("\"token_quota\":{\"client_credentials\":{\"per_day\":100,\"per_hour\":20,\"enforce\":true}}")); } @Test @@ -341,6 +360,12 @@ public void shouldDeserialize() throws Exception { assertThat(client.getSignedRequest().getCredentials().get(0).getUpdatedAt(), is(Date.from(Instant.parse("2024-03-14T11:34:28.893Z")))); assertThat(client.getRequireProofOfPossession(), is(true)); + + assertThat(client.getTokenQuota(), is(notNullValue())); + assertThat(client.getTokenQuota().getClientCredentials(), is(notNullValue())); + assertThat(client.getTokenQuota().getClientCredentials().getPerDay(), is(100)); + assertThat(client.getTokenQuota().getClientCredentials().getPerHour(), is(10)); + assertThat(client.getTokenQuota().getClientCredentials().isEnforce(), is(true)); } @Test diff --git a/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java b/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java index b923d584c..464decc8e 100644 --- a/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java +++ b/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java @@ -2,6 +2,7 @@ import com.auth0.json.JsonMatcher; import com.auth0.json.JsonTest; +import com.auth0.json.mgmt.tokenquota.ClientCredentials; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -12,7 +13,51 @@ public class TenantTest extends JsonTest { - private static final String json = "{\"change_password\":{},\"guardian_mfa_page\":{},\"default_audience\":\"https://domain.auth0.com/myapi\",\"default_directory\":\"Username-Password-Authentication\",\"error_page\":{},\"flags\":{},\"friendly_name\":\"My-Tenant\",\"picture_url\":\"https://pic.to/123\",\"support_email\":\"support@auth0.com\",\"support_url\":\"https://support.auth0.com\",\"allowed_logout_urls\":[\"https://domain.auth0.com/logout\"], \"session_lifetime\":24, \"idle_session_lifetime\":0.5, \"session_cookie\":{\"mode\": \"persistent\"}, \"acr_values_supported\":[\"string1\",\"string2\"], \"pushed_authorization_requests_supported\": true, \"remove_alg_from_jwks\": true, \"mtls\": {\"enable_endpoint_aliases\": true}}"; + private static final String json = "{\n" + + " \"change_password\": {},\n" + + " \"guardian_mfa_page\": {},\n" + + " \"default_audience\": \"https://domain.auth0.com/myapi\",\n" + + " \"default_directory\": \"Username-Password-Authentication\",\n" + + " \"error_page\": {},\n" + + " \"flags\": {},\n" + + " \"friendly_name\": \"My-Tenant\",\n" + + " \"picture_url\": \"https://pic.to/123\",\n" + + " \"support_email\": \"support@auth0.com\",\n" + + " \"support_url\": \"https://support.auth0.com\",\n" + + " \"allowed_logout_urls\": [\n" + + " \"https://domain.auth0.com/logout\"\n" + + " ],\n" + + " \"session_lifetime\": 24,\n" + + " \"idle_session_lifetime\": 0.5,\n" + + " \"session_cookie\": {\n" + + " \"mode\": \"persistent\"\n" + + " },\n" + + " \"acr_values_supported\": [\n" + + " \"string1\",\n" + + " \"string2\"\n" + + " ],\n" + + " \"pushed_authorization_requests_supported\": true,\n" + + " \"remove_alg_from_jwks\": true,\n" + + " \"mtls\": {\n" + + " \"enable_endpoint_aliases\": true\n" + + " },\n" + + " \"default_token_quota\": {\n" + + " \"clients\": {\n" + + " \"client_credentials\": {\n" + + " \"per_day\": 100,\n" + + " \"per_hour\": 20,\n" + + " \"enforce\": true\n" + + " }\n" + + " },\n" + + " \"organizations\": {\n" + + " \"client_credentials\": {\n" + + " \"per_day\": 100,\n" + + " \"per_hour\": 20,\n" + + " \"enforce\": true\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; @Test @@ -35,6 +80,15 @@ public void shouldSerialize() throws Exception { tenant.setAcrValuesSupported(Collections.singletonList("supported acr value")); tenant.setPushedAuthorizationRequestsSupported(true); tenant.setRemoveAlgFromJwks(true); + + ClientCredentials clientCredentials = new ClientCredentials(); + clientCredentials.setPerDay(100); + clientCredentials.setPerHour(20); + clientCredentials.setEnforce(true); + Clients clientTokenQuota = new Clients(clientCredentials); + Organizations organizationTokenQuota = new Organizations(clientCredentials); + tenant.setDefaultTokenQuota(new DefaultTokenQuota(clientTokenQuota, organizationTokenQuota)); + Mtls mtls = new Mtls(); mtls.setEnableEndpointAliases(true); tenant.setMtls(mtls); @@ -60,6 +114,8 @@ public void shouldSerialize() throws Exception { assertThat(serialized, JsonMatcher.hasEntry("pushed_authorization_requests_supported", true)); assertThat(serialized, JsonMatcher.hasEntry("remove_alg_from_jwks", true)); assertThat(serialized, JsonMatcher.hasEntry("enable_endpoint_aliases", notNullValue())); + assertThat(serialized, JsonMatcher.hasEntry("mtls", notNullValue())); + assertThat(serialized, JsonMatcher.hasEntry("default_token_quota", notNullValue())); } @Test @@ -87,6 +143,17 @@ public void shouldDeserialize() throws Exception { assertThat(tenant.getRemoveAlgFromJwks(), is(true)); assertThat(tenant.getMtls(), is(notNullValue())); assertThat(tenant.getMtls().getEnableEndpointAliases(), is(true)); + assertThat(tenant.getDefaultTokenQuota(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getClient(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials().getPerDay(), is(100)); + assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials().getPerHour(), is(20)); + assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials().isEnforce(), is(true)); + assertThat(tenant.getDefaultTokenQuota().getOrganizations(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getOrganizations().getClientCredentials(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getOrganizations().getClientCredentials().getPerDay(), is(100)); + assertThat(tenant.getDefaultTokenQuota().getOrganizations().getClientCredentials().getPerHour(), is(20)); + assertThat(tenant.getDefaultTokenQuota().getOrganizations().getClientCredentials().isEnforce(), is(true)); } } From 8f61fa502ac262e0b86a73c152f2025affcc2a81 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Mon, 21 Apr 2025 17:43:09 +0530 Subject: [PATCH 03/12] modified Rate Limit Exception --- .../auth0/exception/RateLimitException.java | 28 ++++++- src/main/java/com/auth0/net/BaseRequest.java | 13 ++- src/main/java/com/auth0/net/Response.java | 4 - src/main/java/com/auth0/net/ResponseImpl.java | 58 ------------- .../auth0/utils/HttpResponseHeadersUtils.java | 84 +++++++++++++++++++ 5 files changed, 121 insertions(+), 66 deletions(-) create mode 100644 src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java diff --git a/src/main/java/com/auth0/exception/RateLimitException.java b/src/main/java/com/auth0/exception/RateLimitException.java index 738a941f1..4287b1209 100644 --- a/src/main/java/com/auth0/exception/RateLimitException.java +++ b/src/main/java/com/auth0/exception/RateLimitException.java @@ -1,5 +1,7 @@ package com.auth0.exception; +import com.auth0.net.TokenQuotaBucket; + import java.util.Map; /** @@ -15,21 +17,27 @@ public class RateLimitException extends APIException { private final long limit; private final long remaining; private final long reset; + private final TokenQuotaBucket clientQuotaLimit; + private final TokenQuotaBucket organizationQuotaLimit; private static final int STATUS_CODE_TOO_MANY_REQUEST = 429; - public RateLimitException(long limit, long remaining, long reset, Map values) { + public RateLimitException(long limit, long remaining, long reset, TokenQuotaBucket clientQuotaLimit, TokenQuotaBucket organizationQuotaLimit, Map values) { super(values, STATUS_CODE_TOO_MANY_REQUEST); this.limit = limit; this.remaining = remaining; this.reset = reset; + this.clientQuotaLimit = clientQuotaLimit; + this.organizationQuotaLimit = organizationQuotaLimit; } - public RateLimitException(long limit, long remaining, long reset) { + public RateLimitException(long limit, long remaining, long reset, TokenQuotaBucket clientQuotaLimit, TokenQuotaBucket organizationQuotaLimit) { super("Rate limit reached", STATUS_CODE_TOO_MANY_REQUEST, null); this.limit = limit; this.remaining = remaining; this.reset = reset; + this.clientQuotaLimit = clientQuotaLimit; + this.organizationQuotaLimit = organizationQuotaLimit; } /** @@ -56,4 +64,20 @@ public long getReset() { return reset; } + /** + * Getter for the client quota limit. + * @return The client quota limit or null if missing. + */ + public TokenQuotaBucket getClientQuotaLimit() { + return clientQuotaLimit; + } + + /** + * Getter for the organization quota limit. + * @return The organization quota limit or null if missing. + */ + public TokenQuotaBucket getOrganizationQuotaLimit() { + return organizationQuotaLimit; + } + } diff --git a/src/main/java/com/auth0/net/BaseRequest.java b/src/main/java/com/auth0/net/BaseRequest.java index 44f5e1b4d..892f6bc62 100644 --- a/src/main/java/com/auth0/net/BaseRequest.java +++ b/src/main/java/com/auth0/net/BaseRequest.java @@ -6,6 +6,7 @@ import com.auth0.exception.RateLimitException; import com.auth0.json.ObjectMapperProvider; import com.auth0.net.client.*; +import com.auth0.utils.HttpResponseHeadersUtils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.MapType; @@ -219,13 +220,21 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response) long remaining = Long.parseLong(response.getHeader("x-ratelimit-remaining", "-1")); long reset = Long.parseLong(response.getHeader("x-ratelimit-reset", "-1")); + TokenQuotaBucket clientQuotaLimit = null; + TokenQuotaBucket organizationQuotaLimit = null; + + if (response.getHeaders().containsKey("x-rate-limit-remaining") && response.getHeaders().get("x-rate-limit-remaining").equals("0")) { + clientQuotaLimit = HttpResponseHeadersUtils.getClientQuotaLimit(response.getHeaders()); + organizationQuotaLimit = HttpResponseHeadersUtils.getOrganizationQuotaLimit(response.getHeaders()); + } + String payload = response.getBody(); MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class); try { Map values = mapper.readValue(payload, mapType); - return new RateLimitException(limit, remaining, reset, values); + return new RateLimitException(limit, remaining, reset, clientQuotaLimit, organizationQuotaLimit, values); } catch (IOException e) { - return new RateLimitException(limit, remaining, reset); + return new RateLimitException(limit, remaining, reset, clientQuotaLimit, organizationQuotaLimit); } } diff --git a/src/main/java/com/auth0/net/Response.java b/src/main/java/com/auth0/net/Response.java index c6ee75425..4064f1b9c 100644 --- a/src/main/java/com/auth0/net/Response.java +++ b/src/main/java/com/auth0/net/Response.java @@ -23,8 +23,4 @@ public interface Response { * @return the HTTP status code. */ int getStatusCode(); - - TokenQuotaBucket getClientQuotaLimit(); - - TokenQuotaBucket getOrganizationQuotaLimit(); } diff --git a/src/main/java/com/auth0/net/ResponseImpl.java b/src/main/java/com/auth0/net/ResponseImpl.java index b9b8d5f3b..75fd9a6f9 100644 --- a/src/main/java/com/auth0/net/ResponseImpl.java +++ b/src/main/java/com/auth0/net/ResponseImpl.java @@ -31,62 +31,4 @@ public T getBody() { public int getStatusCode() { return statusCode; } - - @Override - public TokenQuotaBucket getClientQuotaLimit() { - String quotaHeader = headers.get("X-Quota-Client-Limit"); - if (quotaHeader != null) { - return parseQuota(quotaHeader); - } - return null; - } - - @Override - public TokenQuotaBucket getOrganizationQuotaLimit() { - String quotaHeader = headers.get("X-Quota-Organization-Limit"); - if (quotaHeader != null) { - return parseQuota(quotaHeader); - } - return null; - } - - public static TokenQuotaBucket parseQuota(String tokenQuota) { - - TokenQuotaLimit perHour = null; - TokenQuotaLimit perDay = null; - - String[] parts = tokenQuota.split(","); - for (String part : parts) { - String[] attributes = part.split(";"); - int quota = 0, remaining = 0, time = 0; - - for (String attribute : attributes) { - String[] keyValue = attribute.split("="); - if (keyValue.length != 2) continue; - - String key = keyValue[0].trim(); - String value = keyValue[1].trim(); - - switch (key) { - case "q": - quota = Integer.parseInt(value); - break; - case "r": - remaining = Integer.parseInt(value); - break; - case "t": - time = Integer.parseInt(value); - break; - } - } - - if (attributes[0].contains("per_hour")) { - perHour = new TokenQuotaLimit(quota, remaining, time); - } else if (attributes[0].contains("per_day")) { - perDay = new TokenQuotaLimit(quota, remaining, time); - } - } - - return new TokenQuotaBucket(perHour, perDay); - } } diff --git a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java new file mode 100644 index 000000000..124e21c8a --- /dev/null +++ b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java @@ -0,0 +1,84 @@ +package com.auth0.utils; + +import com.auth0.json.auth.TokenQuotaLimit; +import com.auth0.net.TokenQuotaBucket; + +import java.util.Map; + +public class HttpResponseHeadersUtils { + + /** + * Gets the client token quota limits from the provided headers. + * + * @param headers the HTTP response headers. + * @return a TokenQuotaBucket containing client rate limits, or null if not present. + */ + public static TokenQuotaBucket getClientQuotaLimit(Map headers) { + String quotaHeader = headers.get("X-Quota-Client-Limit"); + if( quotaHeader == null) { + quotaHeader = headers.get("x-quota-client-limit"); + } + if (quotaHeader != null) { + return parseQuota(quotaHeader); + } + return null; + } + + /** + * Gets the organization token quota limits from the provided headers. + * + * @param headers the HTTP response headers. + * @return a TokenQuotaBucket containing organization rate limits, or null if not present. + */ + public static TokenQuotaBucket getOrganizationQuotaLimit(Map headers) { + String quotaHeader = headers.get("X-Quota-Organization-Limit"); + if( quotaHeader == null) { + quotaHeader = headers.get("x-quota-organization-limit"); + } + if (quotaHeader != null) { + return parseQuota(quotaHeader); + } + return null; + } + + public static TokenQuotaBucket parseQuota(String tokenQuota) { + + TokenQuotaLimit perHour = null; + TokenQuotaLimit perDay = null; + + String[] parts = tokenQuota.split(","); + for (String part : parts) { + String[] attributes = part.split(";"); + int quota = 0, remaining = 0, time = 0; + + for (String attribute : attributes) { + String[] keyValue = attribute.split("="); + if (keyValue.length != 2) continue; + + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + switch (key) { + case "q": + quota = Integer.parseInt(value); + break; + case "r": + remaining = Integer.parseInt(value); + break; + case "t": + time = Integer.parseInt(value); + break; + } + } + + if (attributes[0].contains("per_hour")) { + perHour = new TokenQuotaLimit(quota, remaining, time); + } else if (attributes[0].contains("per_day")) { + perDay = new TokenQuotaLimit(quota, remaining, time); + } + } + + return new TokenQuotaBucket(perHour, perDay); + } + +} From c15a250559e41effd91cd0afeba6627b403732b6 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Sun, 27 Apr 2025 20:24:15 +0530 Subject: [PATCH 04/12] Modified RateLimitException --- .../auth0/exception/RateLimitException.java | 96 +++++++++++++++++-- src/main/java/com/auth0/net/BaseRequest.java | 27 +++++- .../auth0/utils/HttpResponseHeadersUtils.java | 8 +- .../java/com/auth0/client/MockServer.java | 31 ++++++ .../java/com/auth0/net/BaseRequestTest.java | 56 +++++++++++ .../com/auth0/net/MultipartRequestTest.java | 51 ++++++++++ 6 files changed, 254 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/auth0/exception/RateLimitException.java b/src/main/java/com/auth0/exception/RateLimitException.java index 4287b1209..6482f3707 100644 --- a/src/main/java/com/auth0/exception/RateLimitException.java +++ b/src/main/java/com/auth0/exception/RateLimitException.java @@ -17,27 +17,24 @@ public class RateLimitException extends APIException { private final long limit; private final long remaining; private final long reset; - private final TokenQuotaBucket clientQuotaLimit; - private final TokenQuotaBucket organizationQuotaLimit; + + private TokenQuotaBucket clientQuotaLimit; + private TokenQuotaBucket organizationQuotaLimit; private static final int STATUS_CODE_TOO_MANY_REQUEST = 429; - public RateLimitException(long limit, long remaining, long reset, TokenQuotaBucket clientQuotaLimit, TokenQuotaBucket organizationQuotaLimit, Map values) { + public RateLimitException(long limit, long remaining, long reset, Map values) { super(values, STATUS_CODE_TOO_MANY_REQUEST); this.limit = limit; this.remaining = remaining; this.reset = reset; - this.clientQuotaLimit = clientQuotaLimit; - this.organizationQuotaLimit = organizationQuotaLimit; } - public RateLimitException(long limit, long remaining, long reset, TokenQuotaBucket clientQuotaLimit, TokenQuotaBucket organizationQuotaLimit) { + public RateLimitException(long limit, long remaining, long reset) { super("Rate limit reached", STATUS_CODE_TOO_MANY_REQUEST, null); this.limit = limit; this.remaining = remaining; this.reset = reset; - this.clientQuotaLimit = clientQuotaLimit; - this.organizationQuotaLimit = organizationQuotaLimit; } /** @@ -80,4 +77,87 @@ public TokenQuotaBucket getOrganizationQuotaLimit() { return organizationQuotaLimit; } + /** + * Builder class for creating instances of RateLimitException. + */ + public static class Builder { + private long limit; + private long remaining; + private long reset; + private TokenQuotaBucket clientQuotaLimit; + private TokenQuotaBucket organizationQuotaLimit; + private Map values; + + /** + * Constructor for the Builder. + * @param limit The maximum number of requests available in the current time frame. + * @param remaining The number of remaining requests in the current time frame. + * @param reset The UNIX timestamp of the expected time when the rate limit will reset. + */ + public Builder(long limit, long remaining, long reset) { + this.limit = limit; + this.remaining = remaining; + this.reset = reset; + } + + /** + * Constructor for the Builder. + * @param limit The maximum number of requests available in the current time frame. + * @param remaining The number of remaining requests in the current time frame. + * @param reset The UNIX timestamp of the expected time when the rate limit will reset. + * @param values The values map. + */ + public Builder(long limit, long remaining, long reset, Map values) { + this.limit = limit; + this.remaining = remaining; + this.reset = reset; + this.values = values; + } + + /** + * Sets the client quota limit. + * @param clientQuotaLimit The client quota limit. + * @return The Builder instance. + */ + public Builder clientQuotaLimit(TokenQuotaBucket clientQuotaLimit) { + this.clientQuotaLimit = clientQuotaLimit; + return this; + } + + /** + * Sets the organization quota limit. + * @param organizationQuotaLimit The organization quota limit. + * @return The Builder instance. + */ + public Builder organizationQuotaLimit(TokenQuotaBucket organizationQuotaLimit) { + this.organizationQuotaLimit = organizationQuotaLimit; + return this; + } + + /** + * Sets the values map. + * @param values The values map. + * @return The Builder instance. + */ + public Builder values(Map values) { + this.values = values; + return this; + } + + public RateLimitException build() { + RateLimitException exception = (this.values != null) + ? new RateLimitException(this.limit, this.remaining, this.reset, this.values) + : new RateLimitException(this.limit, this.remaining, this.reset); + + if(this.clientQuotaLimit != null) { + exception.clientQuotaLimit = this.clientQuotaLimit; + } + if(this.organizationQuotaLimit != null) { + exception.organizationQuotaLimit = this.organizationQuotaLimit; + } + + return exception; + } + } + } diff --git a/src/main/java/com/auth0/net/BaseRequest.java b/src/main/java/com/auth0/net/BaseRequest.java index 892f6bc62..8d43df26c 100644 --- a/src/main/java/com/auth0/net/BaseRequest.java +++ b/src/main/java/com/auth0/net/BaseRequest.java @@ -223,7 +223,7 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response) TokenQuotaBucket clientQuotaLimit = null; TokenQuotaBucket organizationQuotaLimit = null; - if (response.getHeaders().containsKey("x-rate-limit-remaining") && response.getHeaders().get("x-rate-limit-remaining").equals("0")) { + if (remaining == 0) { clientQuotaLimit = HttpResponseHeadersUtils.getClientQuotaLimit(response.getHeaders()); organizationQuotaLimit = HttpResponseHeadersUtils.getOrganizationQuotaLimit(response.getHeaders()); } @@ -232,9 +232,30 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response) MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class); try { Map values = mapper.readValue(payload, mapType); - return new RateLimitException(limit, remaining, reset, clientQuotaLimit, organizationQuotaLimit, values); + + RateLimitException.Builder builder = new RateLimitException.Builder(limit, remaining, reset, values); + + if (clientQuotaLimit != null) { + builder.clientQuotaLimit(clientQuotaLimit); + } + + if (organizationQuotaLimit != null) { + builder.organizationQuotaLimit(organizationQuotaLimit); + } + + return builder.build(); } catch (IOException e) { - return new RateLimitException(limit, remaining, reset, clientQuotaLimit, organizationQuotaLimit); + RateLimitException.Builder builder = new RateLimitException.Builder(limit, remaining, reset); + + if (clientQuotaLimit != null) { + builder.clientQuotaLimit(clientQuotaLimit); + } + + if (organizationQuotaLimit != null) { + builder.organizationQuotaLimit(organizationQuotaLimit); + } + + return builder.build(); } } diff --git a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java index 124e21c8a..a8449cf2b 100644 --- a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java +++ b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java @@ -14,9 +14,9 @@ public class HttpResponseHeadersUtils { * @return a TokenQuotaBucket containing client rate limits, or null if not present. */ public static TokenQuotaBucket getClientQuotaLimit(Map headers) { - String quotaHeader = headers.get("X-Quota-Client-Limit"); + String quotaHeader = headers.get("auth0-Quota-Client-Limit"); if( quotaHeader == null) { - quotaHeader = headers.get("x-quota-client-limit"); + quotaHeader = headers.get("auth0-quota-client-limit"); } if (quotaHeader != null) { return parseQuota(quotaHeader); @@ -31,9 +31,9 @@ public static TokenQuotaBucket getClientQuotaLimit(Map headers) * @return a TokenQuotaBucket containing organization rate limits, or null if not present. */ public static TokenQuotaBucket getOrganizationQuotaLimit(Map headers) { - String quotaHeader = headers.get("X-Quota-Organization-Limit"); + String quotaHeader = headers.get("auth0-Quota-Organization-Limit"); if( quotaHeader == null) { - quotaHeader = headers.get("x-quota-organization-limit"); + quotaHeader = headers.get("auth0-quota-organization-limit"); } if (quotaHeader != null) { return parseQuota(quotaHeader); diff --git a/src/test/java/com/auth0/client/MockServer.java b/src/test/java/com/auth0/client/MockServer.java index 93193de93..e857d903e 100644 --- a/src/test/java/com/auth0/client/MockServer.java +++ b/src/test/java/com/auth0/client/MockServer.java @@ -1,5 +1,6 @@ package com.auth0.client; +import com.auth0.net.TokenQuotaBucket; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.MapType; @@ -230,6 +231,36 @@ public void rateLimitReachedResponse(long limit, long remaining, long reset, Str server.enqueue(response); } + public void rateLimitReachedResponse(long limit, long remaining, long reset, + String clientQuotaLimit, String organizationQuotaLimit) throws IOException { + rateLimitReachedResponse(limit, remaining, reset, null, clientQuotaLimit, organizationQuotaLimit); + } + + public void rateLimitReachedResponse(long limit, long remaining, long reset, String path, String clientQuotaLimit, String organizationQuotaLimit) throws IOException { + MockResponse response = new MockResponse().setResponseCode(429); + if (limit != -1) { + response.addHeader("x-ratelimit-limit", String.valueOf(limit)); + } + if (remaining != -1) { + response.addHeader("x-ratelimit-remaining", String.valueOf(remaining)); + } + if (reset != -1) { + response.addHeader("x-ratelimit-reset", String.valueOf(reset)); + } + if (clientQuotaLimit != null) { + response.addHeader("auth0-quota-client-limit", clientQuotaLimit); + } + if (organizationQuotaLimit != null) { + response.addHeader("auth0-quota-organization-limit", organizationQuotaLimit); + } + if (path != null) { + response + .addHeader("Content-Type", "application/json") + .setBody(readTextFile(path)); + } + server.enqueue(response); + } + public void textResponse(String path, int statusCode) throws IOException { MockResponse response = new MockResponse() .setResponseCode(statusCode) diff --git a/src/test/java/com/auth0/net/BaseRequestTest.java b/src/test/java/com/auth0/net/BaseRequestTest.java index 5fe9f0023..9cdf97ad9 100644 --- a/src/test/java/com/auth0/net/BaseRequestTest.java +++ b/src/test/java/com/auth0/net/BaseRequestTest.java @@ -7,6 +7,7 @@ import com.auth0.exception.Auth0Exception; import com.auth0.exception.RateLimitException; import com.auth0.json.auth.TokenHolder; +import com.auth0.json.auth.TokenQuotaLimit; import com.auth0.net.client.*; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; @@ -197,6 +198,8 @@ public void shouldAddHeaders() throws Exception { request.addParameter("non_empty", "body"); request.addHeader("Extra-Info", "this is a test"); request.addHeader("Authorization", "Bearer my_access_token"); + request.addHeader("X-Client-Quota", getTokenQuotaString()); + request.addHeader("X-Organization-Quota", getTokenQuotaString()); server.jsonResponse(AUTH_TOKENS, 200); request.execute().getBody(); @@ -425,6 +428,33 @@ public void shouldParseRateLimitException() throws Exception { assertThat(rateLimitException.getReset(), Matchers.is(5L)); } + @Test + public void shouldParseRateLimitExceptionWithZeroRemaining() throws Exception { + BaseRequest request = new BaseRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.GET, listType); + server.rateLimitReachedResponse(100, 0, 5, RATE_LIMIT_ERROR, getTokenQuotaString(), getTokenQuotaString()); + Exception exception = null; + try { + request.execute().getBody(); + server.takeRequest(); + } catch (Exception e) { + exception = e; + } + assertThat(exception, Matchers.is(notNullValue())); + assertThat(exception, Matchers.is(Matchers.instanceOf(RateLimitException.class))); + assertThat(exception.getCause(), Matchers.is(nullValue())); + assertThat(exception.getMessage(), Matchers.is("Request failed with status code 429: Global limit has been reached")); + RateLimitException rateLimitException = (RateLimitException) exception; + assertThat(rateLimitException.getDescription(), Matchers.is("Global limit has been reached")); + assertThat(rateLimitException.getError(), Matchers.is("too_many_requests")); + assertThat(rateLimitException.getValue("non_existing_key"), Matchers.is(nullValue())); + assertThat(rateLimitException.getStatusCode(), Matchers.is(429)); + assertThat(rateLimitException.getLimit(), Matchers.is(100L)); + assertThat(rateLimitException.getRemaining(), Matchers.is(0L)); + assertThat(rateLimitException.getReset(), Matchers.is(5L)); + assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getQuota(), Matchers.is(getTokenQuota().getPerDay().getQuota())); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getQuota(), Matchers.is(getTokenQuota().getPerDay().getQuota())); + } + @Test public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception { BaseRequest request = new BaseRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.GET, listType); @@ -448,5 +478,31 @@ public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception { assertThat(rateLimitException.getLimit(), Matchers.is(-1L)); assertThat(rateLimitException.getRemaining(), Matchers.is(-1L)); assertThat(rateLimitException.getReset(), Matchers.is(-1L)); + assertThat(rateLimitException.getClientQuotaLimit(), Matchers.is(nullValue())); + assertThat(rateLimitException.getOrganizationQuotaLimit(), Matchers.is(nullValue())); + } + + private TokenQuotaBucket getTokenQuota() { + TokenQuotaLimit perHourLimit = new TokenQuotaLimit(100, 80, 3600); + TokenQuotaLimit perDayLimit = new TokenQuotaLimit(100, 90, 86400); + return new TokenQuotaBucket(perHourLimit, perDayLimit); + } + + public String getTokenQuotaString() { + TokenQuotaLimit perHourLimit = new TokenQuotaLimit(100, 80, 3600); + TokenQuotaLimit perDayLimit = new TokenQuotaLimit(100, 90, 86400); + + StringBuilder builder = new StringBuilder(); + + builder.append(String.format("b=per_hour;q=%d;r=%d;t=%d", + perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getTime())); + + if (builder.length() > 0) { + builder.append(","); + } + builder.append(String.format("b=per_day;q=%d;r=%d;t=%d", + perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getTime())); + + return builder.toString(); } } diff --git a/src/test/java/com/auth0/net/MultipartRequestTest.java b/src/test/java/com/auth0/net/MultipartRequestTest.java index 9eb1f61f7..5062851fd 100644 --- a/src/test/java/com/auth0/net/MultipartRequestTest.java +++ b/src/test/java/com/auth0/net/MultipartRequestTest.java @@ -6,6 +6,7 @@ import com.auth0.exception.Auth0Exception; import com.auth0.exception.RateLimitException; import com.auth0.json.auth.TokenHolder; +import com.auth0.json.auth.TokenQuotaLimit; import com.auth0.net.client.Auth0HttpClient; import com.auth0.net.client.Auth0MultipartRequestBody; import com.auth0.net.client.DefaultHttpClient; @@ -18,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.mockwebserver.RecordedRequest; +import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -360,6 +362,32 @@ public void shouldParseRateLimitsHeaders() throws Exception { assertThat(rateLimitException.getReset(), is(5L)); } + @Test + public void shouldParseRateLimitsWithAllHeaders() throws Exception { + MultipartRequest request = new MultipartRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.POST, listType); + request.addPart("non_empty", "body"); + server.rateLimitReachedResponse(100, 10, 5, getTokenQuotaString(), getTokenQuotaString()); + Exception exception = null; + try { + request.execute().getBody(); + server.takeRequest(); + } catch (Exception e) { + exception = e; + } + assertThat(exception, is(notNullValue())); + assertThat(exception, is(instanceOf(RateLimitException.class))); + assertThat(exception.getCause(), is(nullValue())); + assertThat(exception.getMessage(), is("Request failed with status code 429: Rate limit reached")); + RateLimitException rateLimitException = (RateLimitException) exception; + assertThat(rateLimitException.getDescription(), is("Rate limit reached")); + assertThat(rateLimitException.getError(), is(nullValue())); + assertThat(rateLimitException.getValue("non_existing_key"), is(nullValue())); + assertThat(rateLimitException.getStatusCode(), is(429)); + assertThat(rateLimitException.getLimit(), is(100L)); + assertThat(rateLimitException.getRemaining(), is(10L)); + assertThat(rateLimitException.getReset(), is(5L)); + } + @Test public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception { MultipartRequest request = new MultipartRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.POST, listType); @@ -384,6 +412,29 @@ public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception { assertThat(rateLimitException.getLimit(), is(-1L)); assertThat(rateLimitException.getRemaining(), is(-1L)); assertThat(rateLimitException.getReset(), is(-1L)); + assertThat(rateLimitException.getClientQuotaLimit(), Matchers.is(nullValue())); + assertThat(rateLimitException.getOrganizationQuotaLimit(), Matchers.is(nullValue())); } + public String getTokenQuotaString() { + TokenQuotaLimit perHourLimit = new TokenQuotaLimit(100, 80, 3600); + TokenQuotaLimit perDayLimit = new TokenQuotaLimit(100, 90, 86400); + + StringBuilder builder = new StringBuilder(); + + if (perHourLimit != null) { + builder.append(String.format("b=per_hour;q=%d;r=%d;t=%d", + perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getTime())); + } + + if (perDayLimit != null) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(String.format("b=per_day;q=%d;r=%d;t=%d", + perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getTime())); + } + + return builder.toString(); + } } From 2bc16ee660e57940dbaea0744c7dc1c277703a90 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Mon, 28 Apr 2025 23:56:24 +0530 Subject: [PATCH 05/12] Modified Base Request --- src/main/java/com/auth0/net/BaseRequest.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/auth0/net/BaseRequest.java b/src/main/java/com/auth0/net/BaseRequest.java index 8d43df26c..ee13f3246 100644 --- a/src/main/java/com/auth0/net/BaseRequest.java +++ b/src/main/java/com/auth0/net/BaseRequest.java @@ -220,13 +220,8 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response) long remaining = Long.parseLong(response.getHeader("x-ratelimit-remaining", "-1")); long reset = Long.parseLong(response.getHeader("x-ratelimit-reset", "-1")); - TokenQuotaBucket clientQuotaLimit = null; - TokenQuotaBucket organizationQuotaLimit = null; - - if (remaining == 0) { - clientQuotaLimit = HttpResponseHeadersUtils.getClientQuotaLimit(response.getHeaders()); - organizationQuotaLimit = HttpResponseHeadersUtils.getOrganizationQuotaLimit(response.getHeaders()); - } + TokenQuotaBucket clientQuotaLimit = HttpResponseHeadersUtils.getClientQuotaLimit(response.getHeaders()); + TokenQuotaBucket organizationQuotaLimit = HttpResponseHeadersUtils.getOrganizationQuotaLimit(response.getHeaders()); String payload = response.getBody(); MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class); From 6570f7ea88d9d82dbdb5792a3ddf846f877a6338 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 2 May 2025 17:45:01 +0530 Subject: [PATCH 06/12] Moved TokenQuotaLimit class to different package --- .../auth0/exception/RateLimitException.java | 8 ++------ .../json/mgmt/tenants/DefaultTokenQuota.java | 18 +++++++++--------- .../mgmt/tokenquota/ClientCredentials.java | 17 +++++++++++++++++ src/main/java/com/auth0/net/ResponseImpl.java | 2 -- .../java/com/auth0/net/TokenQuotaBucket.java | 2 -- .../{json/auth => net}/TokenQuotaLimit.java | 2 +- .../auth0/utils/HttpResponseHeadersUtils.java | 12 +++--------- .../auth0/json/mgmt/tenants/TenantTest.java | 10 +++++----- .../java/com/auth0/net/BaseRequestTest.java | 1 - .../com/auth0/net/MultipartRequestTest.java | 1 - 10 files changed, 37 insertions(+), 36 deletions(-) rename src/main/java/com/auth0/{json/auth => net}/TokenQuotaLimit.java (93%) diff --git a/src/main/java/com/auth0/exception/RateLimitException.java b/src/main/java/com/auth0/exception/RateLimitException.java index 6482f3707..5a1575e98 100644 --- a/src/main/java/com/auth0/exception/RateLimitException.java +++ b/src/main/java/com/auth0/exception/RateLimitException.java @@ -149,12 +149,8 @@ public RateLimitException build() { ? new RateLimitException(this.limit, this.remaining, this.reset, this.values) : new RateLimitException(this.limit, this.remaining, this.reset); - if(this.clientQuotaLimit != null) { - exception.clientQuotaLimit = this.clientQuotaLimit; - } - if(this.organizationQuotaLimit != null) { - exception.organizationQuotaLimit = this.organizationQuotaLimit; - } + exception.clientQuotaLimit = this.clientQuotaLimit; + exception.organizationQuotaLimit = this.organizationQuotaLimit; return exception; } diff --git a/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java b/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java index 202d16adc..2d5ae414d 100644 --- a/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java +++ b/src/main/java/com/auth0/json/mgmt/tenants/DefaultTokenQuota.java @@ -8,7 +8,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class DefaultTokenQuota { @JsonProperty("clients") - private Clients client; + private Clients clients; @JsonProperty("organizations") private Organizations organizations; @@ -21,26 +21,26 @@ public DefaultTokenQuota() {} /** * Constructor for DefaultTokenQuota. * - * @param client the clients + * @param clients the clients * @param organizations the organizations */ - public DefaultTokenQuota(Clients client, Organizations organizations) { - this.client = client; + public DefaultTokenQuota(Clients clients, Organizations organizations) { + this.clients = clients; this.organizations = organizations; } /** * @return the clients */ - public Clients getClient() { - return client; + public Clients getClients() { + return clients; } /** - * @param client the clients to set + * @param clients the clients to set */ - public void setClient(Clients client) { - this.client = client; + public void setClients(Clients clients) { + this.clients = clients; } /** diff --git a/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java b/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java index 1d7e36b08..ec0f684c9 100644 --- a/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java +++ b/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java @@ -9,6 +9,23 @@ public class ClientCredentials { private int perHour; @JsonProperty("enforce") private boolean enforce; + /** + * Default constructor for ClientCredentials. + */ + public ClientCredentials() {} + + /** + * Constructor for ClientCredentials. + * + * @param perDay the number of client credentials allowed per day + * @param perHour the number of client credentials allowed per hour + * @param enforce true if the quota is enforced, false otherwise + */ + public ClientCredentials(int perDay, int perHour, boolean enforce) { + this.perDay = perDay; + this.perHour = perHour; + this.enforce = enforce; + } /** * @return the number of client credentials allowed per day diff --git a/src/main/java/com/auth0/net/ResponseImpl.java b/src/main/java/com/auth0/net/ResponseImpl.java index 75fd9a6f9..7cd35b996 100644 --- a/src/main/java/com/auth0/net/ResponseImpl.java +++ b/src/main/java/com/auth0/net/ResponseImpl.java @@ -1,7 +1,5 @@ package com.auth0.net; -import com.auth0.json.auth.TokenQuotaLimit; - import java.util.Collections; import java.util.Map; diff --git a/src/main/java/com/auth0/net/TokenQuotaBucket.java b/src/main/java/com/auth0/net/TokenQuotaBucket.java index e5d4d5d8a..17f0aaf64 100644 --- a/src/main/java/com/auth0/net/TokenQuotaBucket.java +++ b/src/main/java/com/auth0/net/TokenQuotaBucket.java @@ -1,7 +1,5 @@ package com.auth0.net; -import com.auth0.json.auth.TokenQuotaLimit; - public class TokenQuotaBucket { private TokenQuotaLimit perHour; private TokenQuotaLimit perDay; diff --git a/src/main/java/com/auth0/json/auth/TokenQuotaLimit.java b/src/main/java/com/auth0/net/TokenQuotaLimit.java similarity index 93% rename from src/main/java/com/auth0/json/auth/TokenQuotaLimit.java rename to src/main/java/com/auth0/net/TokenQuotaLimit.java index 93852f854..f2fb0b88b 100644 --- a/src/main/java/com/auth0/json/auth/TokenQuotaLimit.java +++ b/src/main/java/com/auth0/net/TokenQuotaLimit.java @@ -1,4 +1,4 @@ -package com.auth0.json.auth; +package com.auth0.net; public class TokenQuotaLimit { private int quota; diff --git a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java index a8449cf2b..986cf240a 100644 --- a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java +++ b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java @@ -1,6 +1,6 @@ package com.auth0.utils; -import com.auth0.json.auth.TokenQuotaLimit; +import com.auth0.net.TokenQuotaLimit; import com.auth0.net.TokenQuotaBucket; import java.util.Map; @@ -14,10 +14,7 @@ public class HttpResponseHeadersUtils { * @return a TokenQuotaBucket containing client rate limits, or null if not present. */ public static TokenQuotaBucket getClientQuotaLimit(Map headers) { - String quotaHeader = headers.get("auth0-Quota-Client-Limit"); - if( quotaHeader == null) { - quotaHeader = headers.get("auth0-quota-client-limit"); - } + String quotaHeader = headers.get("auth0-quota-client-limit"); if (quotaHeader != null) { return parseQuota(quotaHeader); } @@ -31,10 +28,7 @@ public static TokenQuotaBucket getClientQuotaLimit(Map headers) * @return a TokenQuotaBucket containing organization rate limits, or null if not present. */ public static TokenQuotaBucket getOrganizationQuotaLimit(Map headers) { - String quotaHeader = headers.get("auth0-Quota-Organization-Limit"); - if( quotaHeader == null) { - quotaHeader = headers.get("auth0-quota-organization-limit"); - } + String quotaHeader = headers.get("auth0-quota-organization-limit"); if (quotaHeader != null) { return parseQuota(quotaHeader); } diff --git a/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java b/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java index 464decc8e..de5f478e4 100644 --- a/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java +++ b/src/test/java/com/auth0/json/mgmt/tenants/TenantTest.java @@ -144,11 +144,11 @@ public void shouldDeserialize() throws Exception { assertThat(tenant.getMtls(), is(notNullValue())); assertThat(tenant.getMtls().getEnableEndpointAliases(), is(true)); assertThat(tenant.getDefaultTokenQuota(), is(notNullValue())); - assertThat(tenant.getDefaultTokenQuota().getClient(), is(notNullValue())); - assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials(), is(notNullValue())); - assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials().getPerDay(), is(100)); - assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials().getPerHour(), is(20)); - assertThat(tenant.getDefaultTokenQuota().getClient().getClientCredentials().isEnforce(), is(true)); + assertThat(tenant.getDefaultTokenQuota().getClients(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getClients().getClientCredentials(), is(notNullValue())); + assertThat(tenant.getDefaultTokenQuota().getClients().getClientCredentials().getPerDay(), is(100)); + assertThat(tenant.getDefaultTokenQuota().getClients().getClientCredentials().getPerHour(), is(20)); + assertThat(tenant.getDefaultTokenQuota().getClients().getClientCredentials().isEnforce(), is(true)); assertThat(tenant.getDefaultTokenQuota().getOrganizations(), is(notNullValue())); assertThat(tenant.getDefaultTokenQuota().getOrganizations().getClientCredentials(), is(notNullValue())); assertThat(tenant.getDefaultTokenQuota().getOrganizations().getClientCredentials().getPerDay(), is(100)); diff --git a/src/test/java/com/auth0/net/BaseRequestTest.java b/src/test/java/com/auth0/net/BaseRequestTest.java index 9cdf97ad9..aa1614b94 100644 --- a/src/test/java/com/auth0/net/BaseRequestTest.java +++ b/src/test/java/com/auth0/net/BaseRequestTest.java @@ -7,7 +7,6 @@ import com.auth0.exception.Auth0Exception; import com.auth0.exception.RateLimitException; import com.auth0.json.auth.TokenHolder; -import com.auth0.json.auth.TokenQuotaLimit; import com.auth0.net.client.*; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/src/test/java/com/auth0/net/MultipartRequestTest.java b/src/test/java/com/auth0/net/MultipartRequestTest.java index 5062851fd..e73c4397d 100644 --- a/src/test/java/com/auth0/net/MultipartRequestTest.java +++ b/src/test/java/com/auth0/net/MultipartRequestTest.java @@ -6,7 +6,6 @@ import com.auth0.exception.Auth0Exception; import com.auth0.exception.RateLimitException; import com.auth0.json.auth.TokenHolder; -import com.auth0.json.auth.TokenQuotaLimit; import com.auth0.net.client.Auth0HttpClient; import com.auth0.net.client.Auth0MultipartRequestBody; import com.auth0.net.client.DefaultHttpClient; From e2a9f6d1c475d98487ba7c433707a48cc4b4f654 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 6 May 2025 17:02:09 +0530 Subject: [PATCH 07/12] added retry after in rate limit exception --- .../auth0/exception/RateLimitException.java | 21 ++++++++++++++++ src/main/java/com/auth0/net/BaseRequest.java | 24 +++++++------------ .../auth0/utils/HttpResponseHeadersUtils.java | 4 ++-- .../java/com/auth0/client/MockServer.java | 13 ++++++---- .../java/com/auth0/net/BaseRequestTest.java | 2 +- .../com/auth0/net/MultipartRequestTest.java | 21 +++++++++++++++- src/test/resources/mgmt/tenant.json | 16 +++++++++++++ 7 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/auth0/exception/RateLimitException.java b/src/main/java/com/auth0/exception/RateLimitException.java index 5a1575e98..9d2a15a77 100644 --- a/src/main/java/com/auth0/exception/RateLimitException.java +++ b/src/main/java/com/auth0/exception/RateLimitException.java @@ -20,6 +20,7 @@ public class RateLimitException extends APIException { private TokenQuotaBucket clientQuotaLimit; private TokenQuotaBucket organizationQuotaLimit; + private long retryAfter; private static final int STATUS_CODE_TOO_MANY_REQUEST = 429; @@ -77,6 +78,14 @@ public TokenQuotaBucket getOrganizationQuotaLimit() { return organizationQuotaLimit; } + /** + * Getter for the retry after time in seconds. + * @return The retry after time in seconds or -1 if missing. + */ + public long getRetryAfter() { + return retryAfter; + } + /** * Builder class for creating instances of RateLimitException. */ @@ -86,6 +95,7 @@ public static class Builder { private long reset; private TokenQuotaBucket clientQuotaLimit; private TokenQuotaBucket organizationQuotaLimit; + private long retryAfter; private Map values; /** @@ -134,6 +144,16 @@ public Builder organizationQuotaLimit(TokenQuotaBucket organizationQuotaLimit) { return this; } + /** + * Sets the retry after time in seconds. + * @param retryAfter The retry after time in seconds. + * @return The Builder instance. + */ + public Builder retryAfter(long retryAfter) { + this.retryAfter = retryAfter; + return this; + } + /** * Sets the values map. * @param values The values map. @@ -151,6 +171,7 @@ public RateLimitException build() { exception.clientQuotaLimit = this.clientQuotaLimit; exception.organizationQuotaLimit = this.organizationQuotaLimit; + exception.retryAfter = this.retryAfter; return exception; } diff --git a/src/main/java/com/auth0/net/BaseRequest.java b/src/main/java/com/auth0/net/BaseRequest.java index ee13f3246..2ddcfc5f0 100644 --- a/src/main/java/com/auth0/net/BaseRequest.java +++ b/src/main/java/com/auth0/net/BaseRequest.java @@ -223,6 +223,8 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response) TokenQuotaBucket clientQuotaLimit = HttpResponseHeadersUtils.getClientQuotaLimit(response.getHeaders()); TokenQuotaBucket organizationQuotaLimit = HttpResponseHeadersUtils.getOrganizationQuotaLimit(response.getHeaders()); + long retryAfter = Long.parseLong(response.getHeader("retry-after", "-1")); + String payload = response.getBody(); MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class); try { @@ -230,26 +232,16 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response) RateLimitException.Builder builder = new RateLimitException.Builder(limit, remaining, reset, values); - if (clientQuotaLimit != null) { - builder.clientQuotaLimit(clientQuotaLimit); - } - - if (organizationQuotaLimit != null) { - builder.organizationQuotaLimit(organizationQuotaLimit); - } + builder.clientQuotaLimit(clientQuotaLimit); + builder.organizationQuotaLimit(organizationQuotaLimit); + builder.retryAfter(retryAfter); return builder.build(); } catch (IOException e) { RateLimitException.Builder builder = new RateLimitException.Builder(limit, remaining, reset); - - if (clientQuotaLimit != null) { - builder.clientQuotaLimit(clientQuotaLimit); - } - - if (organizationQuotaLimit != null) { - builder.organizationQuotaLimit(organizationQuotaLimit); - } - + builder.clientQuotaLimit(clientQuotaLimit); + builder.organizationQuotaLimit(organizationQuotaLimit); + builder.retryAfter(retryAfter); return builder.build(); } } diff --git a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java index 986cf240a..5b2a22833 100644 --- a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java +++ b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java @@ -14,7 +14,7 @@ public class HttpResponseHeadersUtils { * @return a TokenQuotaBucket containing client rate limits, or null if not present. */ public static TokenQuotaBucket getClientQuotaLimit(Map headers) { - String quotaHeader = headers.get("auth0-quota-client-limit"); + String quotaHeader = headers.get("auth0-client-quota-limit"); if (quotaHeader != null) { return parseQuota(quotaHeader); } @@ -28,7 +28,7 @@ public static TokenQuotaBucket getClientQuotaLimit(Map headers) * @return a TokenQuotaBucket containing organization rate limits, or null if not present. */ public static TokenQuotaBucket getOrganizationQuotaLimit(Map headers) { - String quotaHeader = headers.get("auth0-quota-organization-limit"); + String quotaHeader = headers.get("auth0-organization-quota-limit"); if (quotaHeader != null) { return parseQuota(quotaHeader); } diff --git a/src/test/java/com/auth0/client/MockServer.java b/src/test/java/com/auth0/client/MockServer.java index e857d903e..161522bdb 100644 --- a/src/test/java/com/auth0/client/MockServer.java +++ b/src/test/java/com/auth0/client/MockServer.java @@ -232,11 +232,11 @@ public void rateLimitReachedResponse(long limit, long remaining, long reset, Str } public void rateLimitReachedResponse(long limit, long remaining, long reset, - String clientQuotaLimit, String organizationQuotaLimit) throws IOException { - rateLimitReachedResponse(limit, remaining, reset, null, clientQuotaLimit, organizationQuotaLimit); + String clientQuotaLimit, String organizationQuotaLimit, long retryAfter) throws IOException { + rateLimitReachedResponse(limit, remaining, reset, null, clientQuotaLimit, organizationQuotaLimit, retryAfter); } - public void rateLimitReachedResponse(long limit, long remaining, long reset, String path, String clientQuotaLimit, String organizationQuotaLimit) throws IOException { + public void rateLimitReachedResponse(long limit, long remaining, long reset, String path, String clientQuotaLimit, String organizationQuotaLimit, long retryAfter) throws IOException { MockResponse response = new MockResponse().setResponseCode(429); if (limit != -1) { response.addHeader("x-ratelimit-limit", String.valueOf(limit)); @@ -248,10 +248,13 @@ public void rateLimitReachedResponse(long limit, long remaining, long reset, Str response.addHeader("x-ratelimit-reset", String.valueOf(reset)); } if (clientQuotaLimit != null) { - response.addHeader("auth0-quota-client-limit", clientQuotaLimit); + response.addHeader("auth0-client-quota-limit", clientQuotaLimit); } if (organizationQuotaLimit != null) { - response.addHeader("auth0-quota-organization-limit", organizationQuotaLimit); + response.addHeader("auth0-organization-quota-limit", organizationQuotaLimit); + } + if(retryAfter != -1) { + response.addHeader("retry-after", String.valueOf(retryAfter)); } if (path != null) { response diff --git a/src/test/java/com/auth0/net/BaseRequestTest.java b/src/test/java/com/auth0/net/BaseRequestTest.java index aa1614b94..9cbd7306f 100644 --- a/src/test/java/com/auth0/net/BaseRequestTest.java +++ b/src/test/java/com/auth0/net/BaseRequestTest.java @@ -430,7 +430,7 @@ public void shouldParseRateLimitException() throws Exception { @Test public void shouldParseRateLimitExceptionWithZeroRemaining() throws Exception { BaseRequest request = new BaseRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.GET, listType); - server.rateLimitReachedResponse(100, 0, 5, RATE_LIMIT_ERROR, getTokenQuotaString(), getTokenQuotaString()); + server.rateLimitReachedResponse(100, 0, 5, RATE_LIMIT_ERROR, getTokenQuotaString(), getTokenQuotaString(), 1000); Exception exception = null; try { request.execute().getBody(); diff --git a/src/test/java/com/auth0/net/MultipartRequestTest.java b/src/test/java/com/auth0/net/MultipartRequestTest.java index e73c4397d..cace5fcf1 100644 --- a/src/test/java/com/auth0/net/MultipartRequestTest.java +++ b/src/test/java/com/auth0/net/MultipartRequestTest.java @@ -365,7 +365,7 @@ public void shouldParseRateLimitsHeaders() throws Exception { public void shouldParseRateLimitsWithAllHeaders() throws Exception { MultipartRequest request = new MultipartRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.POST, listType); request.addPart("non_empty", "body"); - server.rateLimitReachedResponse(100, 10, 5, getTokenQuotaString(), getTokenQuotaString()); + server.rateLimitReachedResponse(100, 10, 5, getTokenQuotaString(), getTokenQuotaString(), 1000); Exception exception = null; try { request.execute().getBody(); @@ -385,6 +385,25 @@ public void shouldParseRateLimitsWithAllHeaders() throws Exception { assertThat(rateLimitException.getLimit(), is(100L)); assertThat(rateLimitException.getRemaining(), is(10L)); assertThat(rateLimitException.getReset(), is(5L)); + assertThat(rateLimitException.getRetryAfter(), is(1000L)); + assertThat(rateLimitException.getClientQuotaLimit(), is(notNullValue())); + assertThat(rateLimitException.getClientQuotaLimit().getPerHour(), is(notNullValue())); + assertThat(rateLimitException.getClientQuotaLimit().getPerDay(), is(notNullValue())); + assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getQuota(), is(100)); + assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getRemaining(), is(80)); + assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getTime(), is(3600)); + assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getQuota(), is(100)); + assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getRemaining(), is(90)); + assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getTime(), is(86400)); + assertThat(rateLimitException.getOrganizationQuotaLimit(), is(notNullValue())); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour(), is(notNullValue())); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay(), is(notNullValue())); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getQuota(), is(100)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getRemaining(), is(80)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getTime(), is(3600)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getQuota(), is(100)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getRemaining(), is(90)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getTime(), is(86400)); } @Test diff --git a/src/test/resources/mgmt/tenant.json b/src/test/resources/mgmt/tenant.json index 66195f797..0c7cd8b67 100644 --- a/src/test/resources/mgmt/tenant.json +++ b/src/test/resources/mgmt/tenant.json @@ -38,5 +38,21 @@ "remove_alg_from_jwks": true, "mtls": { "enable_endpoint_aliases": true + }, + "default_token_quota": { + "clients": { + "client_credentials": { + "per_day": 100, + "per_hour": 20, + "enforce": true + } + }, + "organizations": { + "client_credentials": { + "per_day": 100, + "per_hour": 20, + "enforce": true + } + } } } From 807bc230f9cc0e0c838d2f2736793a550beaba2b Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 6 May 2025 18:41:16 +0530 Subject: [PATCH 08/12] added more test cases --- .../mgmt/tokenquota/ClientCredentials.java | 18 ++++--- .../java/com/auth0/net/TokenQuotaBucket.java | 16 ++++++ .../java/com/auth0/net/TokenQuotaLimit.java | 5 ++ .../auth0/utils/HttpResponseHeadersUtils.java | 4 ++ .../mgmt/organizations/OrganizationsTest.java | 52 ++++++++++++++----- 5 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java b/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java index ec0f684c9..b7d52ef4c 100644 --- a/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java +++ b/src/main/java/com/auth0/json/mgmt/tokenquota/ClientCredentials.java @@ -1,12 +1,16 @@ package com.auth0.json.mgmt.tokenquota; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) public class ClientCredentials { @JsonProperty("per_day") - private int perDay; + private Integer perDay; @JsonProperty("per_hour") - private int perHour; + private Integer perHour; @JsonProperty("enforce") private boolean enforce; /** @@ -21,7 +25,7 @@ public ClientCredentials() {} * @param perHour the number of client credentials allowed per hour * @param enforce true if the quota is enforced, false otherwise */ - public ClientCredentials(int perDay, int perHour, boolean enforce) { + public ClientCredentials(Integer perDay, Integer perHour, boolean enforce) { this.perDay = perDay; this.perHour = perHour; this.enforce = enforce; @@ -30,7 +34,7 @@ public ClientCredentials(int perDay, int perHour, boolean enforce) { /** * @return the number of client credentials allowed per day */ - public int getPerDay() { + public Integer getPerDay() { return perDay; } @@ -39,14 +43,14 @@ public int getPerDay() { * * @param perDay the number of client credentials allowed per day */ - public void setPerDay(int perDay) { + public void setPerDay(Integer perDay) { this.perDay = perDay; } /** * @return the number of client credentials allowed per hour */ - public int getPerHour() { + public Integer getPerHour() { return perHour; } @@ -55,7 +59,7 @@ public int getPerHour() { * * @param perHour the number of client credentials allowed per hour */ - public void setPerHour(int perHour) { + public void setPerHour(Integer perHour) { this.perHour = perHour; } diff --git a/src/main/java/com/auth0/net/TokenQuotaBucket.java b/src/main/java/com/auth0/net/TokenQuotaBucket.java index 17f0aaf64..e6c192c21 100644 --- a/src/main/java/com/auth0/net/TokenQuotaBucket.java +++ b/src/main/java/com/auth0/net/TokenQuotaBucket.java @@ -1,19 +1,35 @@ package com.auth0.net; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) public class TokenQuotaBucket { private TokenQuotaLimit perHour; private TokenQuotaLimit perDay; + /** + * Constructor for TokenQuotaBucket. + */ public TokenQuotaBucket(TokenQuotaLimit perHour, TokenQuotaLimit perDay) { this.perHour = perHour; this.perDay = perDay; } + /** + * @return the number of client credentials allowed per hour + */ public TokenQuotaLimit getPerHour() { return perHour; } + /** + * @return the number of client credentials allowed per hour + */ public TokenQuotaLimit getPerDay() { return perDay; } + + } diff --git a/src/main/java/com/auth0/net/TokenQuotaLimit.java b/src/main/java/com/auth0/net/TokenQuotaLimit.java index f2fb0b88b..9e991c9c0 100644 --- a/src/main/java/com/auth0/net/TokenQuotaLimit.java +++ b/src/main/java/com/auth0/net/TokenQuotaLimit.java @@ -1,5 +1,10 @@ package com.auth0.net; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) public class TokenQuotaLimit { private int quota; private int remaining; diff --git a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java index 5b2a22833..788bb542b 100644 --- a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java +++ b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java @@ -72,6 +72,10 @@ public static TokenQuotaBucket parseQuota(String tokenQuota) { } } + if(perHour == null && perDay == null) { + return null; + } + return new TokenQuotaBucket(perHour, perDay); } diff --git a/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java index 66aff9737..4518e7d78 100644 --- a/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java +++ b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java @@ -2,6 +2,8 @@ import com.auth0.json.JsonMatcher; import com.auth0.json.JsonTest; +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -18,20 +20,27 @@ public class OrganizationsTest extends JsonTest { @Test public void shouldDeserialize() throws Exception { String orgJson = "{\n" + - " \"id\": \"org_abc\",\n" + - " \"name\": \"org-name\",\n" + - " \"display_name\": \"display name\",\n" + - " \"branding\": {\n" + - " \"logo_url\": \"https://some-url.com/\",\n" + - " \"colors\": {\n" + - " \"primary\": \"#FF0000\",\n" + - " \"page_background\": \"#FF0000\"\n" + - " }\n" + - " },\n" + - " \"metadata\": {\n" + - " \"key1\": \"val1\"\n" + + " \"id\": \"org_abc\",\n" + + " \"name\": \"org-name\",\n" + + " \"display_name\": \"display name\",\n" + + " \"branding\": {\n" + + " \"logo_url\": \"https://some-url.com/\",\n" + + " \"colors\": {\n" + + " \"primary\": \"#FF0000\",\n" + + " \"page_background\": \"#FF0000\"\n" + " }\n" + - " }"; + " },\n" + + " \"metadata\": {\n" + + " \"key1\": \"val1\"\n" + + " },\n" + + " \"token_quota\": {\n" + + " \"client_credentials\": {\n" + + " \"per_hour\": 10,\n" + + " \"per_day\": 100,\n" + + " \"enforce\": true\n" + + " }\n" + + " }\n" + + "}"; Organization org = fromJSON(orgJson, Organization.class); assertThat(org, is(notNullValue())); @@ -45,6 +54,11 @@ public void shouldDeserialize() throws Exception { assertThat(org.getBranding().getColors().getPageBackground(), is("#FF0000")); assertThat(org.getMetadata(), is(notNullValue())); assertThat(org.getMetadata().get("key1"), is("val1")); + assertThat(org.getTokenQuota(), is(notNullValue())); + assertThat(org.getTokenQuota().getClientCredentials(), is(notNullValue())); + assertThat(org.getTokenQuota().getClientCredentials().getPerHour(), is(10)); + assertThat(org.getTokenQuota().getClientCredentials().getPerDay(), is(100)); + assertThat(org.getTokenQuota().getClientCredentials().isEnforce(), is(true)); } @Test @@ -70,6 +84,9 @@ public void shouldSerialize() throws Exception { enabledConnections.add(enabledConnection); organization.setEnabledConnections(enabledConnections); + TokenQuota tokenQuota = new TokenQuota(new ClientCredentials(10, 100, true)); + organization.setTokenQuota(tokenQuota); + String serialized = toJSON(organization); assertThat(serialized, is(notNullValue())); assertThat(serialized, JsonMatcher.hasEntry("name", "org-name")); @@ -77,5 +94,14 @@ public void shouldSerialize() throws Exception { assertThat(serialized, JsonMatcher.hasEntry("metadata", metadata)); assertThat(serialized, JsonMatcher.hasEntry("enabled_connections", is(notNullValue()))); assertThat(serialized, JsonMatcher.hasEntry("branding", is(notNullValue()))); + assertThat(serialized, JsonMatcher.hasEntry("branding", JsonMatcher.hasEntry("logo_url", "https://some-url.com"))); + assertThat(serialized, JsonMatcher.hasEntry("branding", JsonMatcher.hasEntry("colors", is(notNullValue())))); + assertThat(serialized, JsonMatcher.hasEntry("branding", JsonMatcher.hasEntry("colors", JsonMatcher.hasEntry("primary", "#FF0000")))); + assertThat(serialized, JsonMatcher.hasEntry("branding", JsonMatcher.hasEntry("colors", JsonMatcher.hasEntry("page_background", "#DD0000")))); + assertThat(serialized, JsonMatcher.hasEntry("token_quota", is(notNullValue()))); + assertThat(serialized, JsonMatcher.hasEntry("token_quota", JsonMatcher.hasEntry("client_credentials", is(notNullValue())))); + assertThat(serialized, JsonMatcher.hasEntry("token_quota", JsonMatcher.hasEntry("client_credentials", JsonMatcher.hasEntry("per_hour", 10)))); + assertThat(serialized, JsonMatcher.hasEntry("token_quota", JsonMatcher.hasEntry("client_credentials", JsonMatcher.hasEntry("per_day", 100)))); + assertThat(serialized, JsonMatcher.hasEntry("token_quota", JsonMatcher.hasEntry("client_credentials", JsonMatcher.hasEntry("enforce", true)))); } } From 6fc238b1bd9c37dd6228a6e3df05c1008e82f31f Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 6 May 2025 18:44:55 +0530 Subject: [PATCH 09/12] added more test cases --- .../com/auth0/json/mgmt/organizations/OrganizationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java index 4518e7d78..ea5cdd22f 100644 --- a/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java +++ b/src/test/java/com/auth0/json/mgmt/organizations/OrganizationsTest.java @@ -84,7 +84,7 @@ public void shouldSerialize() throws Exception { enabledConnections.add(enabledConnection); organization.setEnabledConnections(enabledConnections); - TokenQuota tokenQuota = new TokenQuota(new ClientCredentials(10, 100, true)); + TokenQuota tokenQuota = new TokenQuota(new ClientCredentials(100, 10, true)); organization.setTokenQuota(tokenQuota); String serialized = toJSON(organization); From 3fe46573873609a915a0545e6fa3f1194747b976 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Tue, 6 May 2025 19:35:43 +0530 Subject: [PATCH 10/12] updated test case coverage --- .../auth0/client/mgmt/ClientsEntityTest.java | 45 ++++++++++++++++-- .../client/mgmt/OrganizationEntityTest.java | 41 ++++++++++++++++- .../auth0/client/mgmt/TenantsEntityTest.java | 46 +++++++++++++++++-- src/test/resources/mgmt/client.json | 9 +++- src/test/resources/mgmt/clients_list.json | 7 +++ src/test/resources/mgmt/organization.json | 9 +++- .../resources/mgmt/organizations_list.json | 7 +++ 7 files changed, 152 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/auth0/client/mgmt/ClientsEntityTest.java b/src/test/java/com/auth0/client/mgmt/ClientsEntityTest.java index 4e574254a..8c656d9ce 100644 --- a/src/test/java/com/auth0/client/mgmt/ClientsEntityTest.java +++ b/src/test/java/com/auth0/client/mgmt/ClientsEntityTest.java @@ -5,6 +5,8 @@ import com.auth0.json.mgmt.client.Client; import com.auth0.json.mgmt.client.ClientsPage; import com.auth0.json.mgmt.client.Credential; +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import com.auth0.net.Request; import com.auth0.net.client.HttpMethod; import okhttp3.mockwebserver.RecordedRequest; @@ -163,7 +165,6 @@ public void shouldGetClient() throws Exception { assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/api/v2/clients/1")); assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); - assertThat(response, is(notNullValue())); } @@ -213,7 +214,15 @@ public void shouldThrowOnCreateClientWithNullData() { @Test public void shouldCreateClient() throws Exception { - Request request = api.clients().create(new Client("My Application")); + Client clientCreate = new Client("My Application"); + ClientCredentials clientCredentials = new ClientCredentials(); + clientCredentials.setPerDay(100); + clientCredentials.setPerHour(20); + clientCredentials.setEnforce(true); + + TokenQuota tokenQuota = new TokenQuota(clientCredentials); + clientCreate.setTokenQuota(tokenQuota); + Request request = api.clients().create(clientCreate); assertThat(request, is(notNullValue())); server.jsonResponse(MGMT_CLIENT, 200); @@ -225,9 +234,18 @@ public void shouldCreateClient() throws Exception { assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); Map body = bodyFromRequest(recordedRequest); - assertThat(body.size(), is(1)); + assertThat(body.size(), is(2)); assertThat(body, hasEntry("name", "My Application")); + // Verify the token_quota structure + Map tokenQuotaMap = (Map) body.get("token_quota"); + assertThat(tokenQuotaMap, is(notNullValue())); + Map clientCredentialsMap = (Map) tokenQuotaMap.get("client_credentials"); + assertThat(clientCredentialsMap, is(notNullValue())); + assertThat(clientCredentialsMap, hasEntry("per_day", 100)); + assertThat(clientCredentialsMap, hasEntry("per_hour", 20)); + assertThat(clientCredentialsMap, hasEntry("enforce", true)); + assertThat(response, is(notNullValue())); } @@ -268,7 +286,15 @@ public void shouldThrowOnUpdateClientWithNullData() { @Test public void shouldUpdateClient() throws Exception { - Request request = api.clients().update("1", new Client("My Application")); + Client clientUpdate = new Client( "My Application"); + ClientCredentials clientCredentials = new ClientCredentials(); + clientCredentials.setPerDay(100); + clientCredentials.setPerHour(20); + clientCredentials.setEnforce(true); + + TokenQuota tokenQuota = new TokenQuota(clientCredentials); + clientUpdate.setTokenQuota(tokenQuota); + Request request = api.clients().update("1", clientUpdate); assertThat(request, is(notNullValue())); server.jsonResponse(MGMT_CLIENT, 200); @@ -280,9 +306,18 @@ public void shouldUpdateClient() throws Exception { assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); Map body = bodyFromRequest(recordedRequest); - assertThat(body.size(), is(1)); + assertThat(body.size(), is(2)); assertThat(body, hasEntry("name", "My Application")); + Map tokenQuotaMap = (Map) body.get("token_quota"); + assertThat(tokenQuotaMap, is(notNullValue())); + Map clientCredentialsMap = (Map) tokenQuotaMap.get("client_credentials"); + assertThat(clientCredentialsMap, is(notNullValue())); + assertThat(clientCredentialsMap, hasEntry("per_day", 100)); + assertThat(clientCredentialsMap, hasEntry("per_hour", 20)); + assertThat(clientCredentialsMap, hasEntry("enforce", true)); + + assertThat(response, is(notNullValue())); } diff --git a/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java b/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java index bfa209562..2928ed95b 100644 --- a/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java +++ b/src/test/java/com/auth0/client/mgmt/OrganizationEntityTest.java @@ -11,6 +11,8 @@ import com.auth0.json.mgmt.organizations.*; import com.auth0.json.mgmt.resourceserver.ResourceServer; import com.auth0.json.mgmt.roles.RolesPage; +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import com.auth0.net.Request; import com.auth0.net.client.HttpMethod; import okhttp3.mockwebserver.RecordedRequest; @@ -181,6 +183,14 @@ public void shouldCreateOrganization() throws Exception { enabledConnections.add(enabledConnection); orgToCreate.setEnabledConnections(enabledConnections); + ClientCredentials clientCredentials = new ClientCredentials(); + clientCredentials.setPerDay(100); + clientCredentials.setPerHour(20); + clientCredentials.setEnforce(true); + + TokenQuota tokenQuota = new TokenQuota(clientCredentials); + orgToCreate.setTokenQuota(tokenQuota); + Request request = api.organizations().create(orgToCreate); assertThat(request, is(notNullValue())); @@ -193,12 +203,21 @@ public void shouldCreateOrganization() throws Exception { assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); Map body = bodyFromRequest(recordedRequest); - assertThat(body, aMapWithSize(5)); + assertThat(body, aMapWithSize(6)); assertThat(body, hasEntry("name", "test-org")); assertThat(body, hasEntry("display_name", "display name")); assertThat(body, hasEntry("metadata", metadata)); assertThat(body, hasEntry(is("enabled_connections"), is(notNullValue()))); assertThat(body, hasEntry(is("branding"), is(notNullValue()))); + assertThat(body, hasEntry(is("token_quota"), is(notNullValue()))); + Map tokenQuotaMap = (Map) body.get("token_quota"); + assertThat(tokenQuotaMap, is(notNullValue())); + Map clientCredentialsMap = (Map) tokenQuotaMap.get("client_credentials"); + assertThat(clientCredentialsMap, is(notNullValue())); + assertThat(clientCredentialsMap, hasEntry("per_day", 100)); + assertThat(clientCredentialsMap, hasEntry("per_hour", 20)); + assertThat(clientCredentialsMap, hasEntry("enforce", true)); + assertThat(response, is(notNullValue())); } @@ -234,6 +253,14 @@ public void shouldUpdateOrganization() throws Exception { metadata.put("key1", "val1"); orgToUpdate.setMetadata(metadata); + ClientCredentials clientCredentials = new ClientCredentials(); + clientCredentials.setPerDay(100); + clientCredentials.setPerHour(20); + clientCredentials.setEnforce(true); + + TokenQuota tokenQuota = new TokenQuota(clientCredentials); + orgToUpdate.setTokenQuota(tokenQuota); + Request request = api.organizations().update("org_abc", orgToUpdate); assertThat(request, is(notNullValue())); @@ -246,11 +273,21 @@ public void shouldUpdateOrganization() throws Exception { assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); Map body = bodyFromRequest(recordedRequest); - assertThat(body, aMapWithSize(4)); + assertThat(body, aMapWithSize(5)); assertThat(body, hasEntry("name", "test-org")); assertThat(body, hasEntry("display_name", "display name")); assertThat(body, hasEntry("metadata", metadata)); assertThat(body, hasEntry(is("branding"), is(notNullValue()))); + assertThat(body, hasEntry(is("token_quota"), is(notNullValue()))); + + Map tokenQuotaMap = (Map) body.get("token_quota"); + assertThat(tokenQuotaMap, is(notNullValue())); + Map clientCredentialsMap = (Map) tokenQuotaMap.get("client_credentials"); + assertThat(clientCredentialsMap, is(notNullValue())); + assertThat(clientCredentialsMap, hasEntry("per_day", 100)); + assertThat(clientCredentialsMap, hasEntry("per_hour", 20)); + assertThat(clientCredentialsMap, hasEntry("enforce", true)); + assertThat(response, is(notNullValue())); } diff --git a/src/test/java/com/auth0/client/mgmt/TenantsEntityTest.java b/src/test/java/com/auth0/client/mgmt/TenantsEntityTest.java index daa082f4e..5abe4e9c7 100644 --- a/src/test/java/com/auth0/client/mgmt/TenantsEntityTest.java +++ b/src/test/java/com/auth0/client/mgmt/TenantsEntityTest.java @@ -1,18 +1,25 @@ package com.auth0.client.mgmt; import com.auth0.client.mgmt.filter.FieldsFilter; +import com.auth0.json.mgmt.tenants.Clients; +import com.auth0.json.mgmt.tenants.DefaultTokenQuota; +import com.auth0.json.mgmt.tenants.Organizations; import com.auth0.json.mgmt.tenants.Tenant; +import com.auth0.json.mgmt.tokenquota.ClientCredentials; +import com.auth0.json.mgmt.tokenquota.TokenQuota; import com.auth0.net.Request; import com.auth0.net.client.HttpMethod; import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.Test; +import java.util.Map; + import static com.auth0.AssertsUtil.verifyThrows; import static com.auth0.client.MockServer.MGMT_TENANT; +import static com.auth0.client.MockServer.*; import static com.auth0.client.RecordedRequestMatcher.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; public class TenantsEntityTest extends BaseMgmtEntityTest { @@ -60,7 +67,14 @@ public void shouldThrowOnUpdateTenantSettingsWithNullData() { @Test public void shouldUpdateTenantSettings() throws Exception { - Request request = api.tenants().update(new Tenant()); + Tenant tenant = new Tenant(); + + DefaultTokenQuota defaultTokenQuota = new DefaultTokenQuota( + new Clients(new ClientCredentials(100, 20, true)), + new Organizations(new ClientCredentials(100, 20, true))); + + tenant.setDefaultTokenQuota(defaultTokenQuota); + Request request = api.tenants().update(tenant); assertThat(request, is(notNullValue())); server.jsonResponse(MGMT_TENANT, 200); @@ -71,6 +85,32 @@ public void shouldUpdateTenantSettings() throws Exception { assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); assertThat(recordedRequest, hasHeader("Authorization", "Bearer apiToken")); + + // Parse and validate the request body + Map body = bodyFromRequest(recordedRequest); + assertThat(body, is(notNullValue())); + + Map tokenQuotaMap = (Map) body.get("default_token_quota"); + assertThat(tokenQuotaMap, is(notNullValue())); + + // Validate "clients" nested structure + Map clientsMap = (Map) tokenQuotaMap.get("clients"); + assertThat(clientsMap, is(notNullValue())); + Map clientCredentialsMap = (Map) clientsMap.get("client_credentials"); + assertThat(clientCredentialsMap, is(notNullValue())); + assertThat(clientCredentialsMap, hasEntry("per_day", 100)); + assertThat(clientCredentialsMap, hasEntry("per_hour", 20)); + assertThat(clientCredentialsMap, hasEntry("enforce", true)); + + // Validate "organizations" nested structure + Map organizationsMap = (Map) tokenQuotaMap.get("organizations"); + assertThat(organizationsMap, is(notNullValue())); + Map organizationCredentialsMap = (Map) organizationsMap.get("client_credentials"); + assertThat(organizationCredentialsMap, is(notNullValue())); + assertThat(organizationCredentialsMap, hasEntry("per_day", 100)); + assertThat(organizationCredentialsMap, hasEntry("per_hour", 20)); + assertThat(organizationCredentialsMap, hasEntry("enforce", true)); + assertThat(response, is(notNullValue())); } } diff --git a/src/test/resources/mgmt/client.json b/src/test/resources/mgmt/client.json index a3395042b..967c9104c 100644 --- a/src/test/resources/mgmt/client.json +++ b/src/test/resources/mgmt/client.json @@ -91,5 +91,12 @@ ] }, "compliance_level": "fapi1_adv_pkj_par", - "require_proof_of_possession": true + "require_proof_of_possession": true, + "token_quota": { + "client_credentials": { + "per_day": 100, + "per_hour": 20, + "enforce": true + } + } } diff --git a/src/test/resources/mgmt/clients_list.json b/src/test/resources/mgmt/clients_list.json index e5a091aa4..208b5ff5a 100644 --- a/src/test/resources/mgmt/clients_list.json +++ b/src/test/resources/mgmt/clients_list.json @@ -72,6 +72,13 @@ "team_id": "9JA89QQLNQ", "app_bundle_identifier": "com.my.bundle.id" } + }, + "token_quota": { + "client_credentials": { + "per_day": 100, + "per_hour": 20, + "enforce": true + } } }, { diff --git a/src/test/resources/mgmt/organization.json b/src/test/resources/mgmt/organization.json index 3782c22c7..1453d8f94 100644 --- a/src/test/resources/mgmt/organization.json +++ b/src/test/resources/mgmt/organization.json @@ -17,5 +17,12 @@ "connection_id": "con-1", "assign_membership_on_login": false } - ] + ], + "token_quota": { + "client_credentials": { + "per_day": 100, + "per_hour": 20, + "enforce": true + } + } } diff --git a/src/test/resources/mgmt/organizations_list.json b/src/test/resources/mgmt/organizations_list.json index 504f7fd21..2b765439b 100644 --- a/src/test/resources/mgmt/organizations_list.json +++ b/src/test/resources/mgmt/organizations_list.json @@ -12,6 +12,13 @@ }, "metadata": { "key1": "val1" + }, + "token_quota": { + "client_credentials": { + "per_day": 100, + "per_hour": 20, + "enforce": true + } } }, { From da620e95d9dc66f994decc4fae6936fe5345347e Mon Sep 17 00:00:00 2001 From: tanya732 Date: Thu, 15 May 2025 08:51:41 +0530 Subject: [PATCH 11/12] Updated time attribute to resetAfter --- .../java/com/auth0/net/TokenQuotaLimit.java | 10 +- .../java/com/auth0/net/BaseRequestTest.java | 4 +- .../com/auth0/net/MultipartRequestTest.java | 12 +- .../utils/HttpResponseHeadersUtilsTest.java | 119 ++++++++++++++++++ 4 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 src/test/java/com/auth0/utils/HttpResponseHeadersUtilsTest.java diff --git a/src/main/java/com/auth0/net/TokenQuotaLimit.java b/src/main/java/com/auth0/net/TokenQuotaLimit.java index 9e991c9c0..966147ed3 100644 --- a/src/main/java/com/auth0/net/TokenQuotaLimit.java +++ b/src/main/java/com/auth0/net/TokenQuotaLimit.java @@ -8,12 +8,12 @@ public class TokenQuotaLimit { private int quota; private int remaining; - private int time; + private int resetAfter; - public TokenQuotaLimit(int quota, int remaining, int time) { + public TokenQuotaLimit(int quota, int remaining, int resetAfter) { this.quota = quota; this.remaining = remaining; - this.time = time; + this.resetAfter = resetAfter; } public int getQuota() { @@ -24,7 +24,7 @@ public int getRemaining() { return remaining; } - public int getTime() { - return time; + public int getResetAfter() { + return resetAfter; } } diff --git a/src/test/java/com/auth0/net/BaseRequestTest.java b/src/test/java/com/auth0/net/BaseRequestTest.java index 9cbd7306f..4521e8bd9 100644 --- a/src/test/java/com/auth0/net/BaseRequestTest.java +++ b/src/test/java/com/auth0/net/BaseRequestTest.java @@ -494,13 +494,13 @@ public String getTokenQuotaString() { StringBuilder builder = new StringBuilder(); builder.append(String.format("b=per_hour;q=%d;r=%d;t=%d", - perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getTime())); + perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getResetAfter())); if (builder.length() > 0) { builder.append(","); } builder.append(String.format("b=per_day;q=%d;r=%d;t=%d", - perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getTime())); + perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getResetAfter())); return builder.toString(); } diff --git a/src/test/java/com/auth0/net/MultipartRequestTest.java b/src/test/java/com/auth0/net/MultipartRequestTest.java index cace5fcf1..848a8df8b 100644 --- a/src/test/java/com/auth0/net/MultipartRequestTest.java +++ b/src/test/java/com/auth0/net/MultipartRequestTest.java @@ -391,19 +391,19 @@ public void shouldParseRateLimitsWithAllHeaders() throws Exception { assertThat(rateLimitException.getClientQuotaLimit().getPerDay(), is(notNullValue())); assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getQuota(), is(100)); assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getRemaining(), is(80)); - assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getTime(), is(3600)); + assertThat(rateLimitException.getClientQuotaLimit().getPerHour().getResetAfter(), is(3600)); assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getQuota(), is(100)); assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getRemaining(), is(90)); - assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getTime(), is(86400)); + assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getResetAfter(), is(86400)); assertThat(rateLimitException.getOrganizationQuotaLimit(), is(notNullValue())); assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour(), is(notNullValue())); assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay(), is(notNullValue())); assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getQuota(), is(100)); assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getRemaining(), is(80)); - assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getTime(), is(3600)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerHour().getResetAfter(), is(3600)); assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getQuota(), is(100)); assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getRemaining(), is(90)); - assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getTime(), is(86400)); + assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getResetAfter(), is(86400)); } @Test @@ -442,7 +442,7 @@ public String getTokenQuotaString() { if (perHourLimit != null) { builder.append(String.format("b=per_hour;q=%d;r=%d;t=%d", - perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getTime())); + perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getResetAfter())); } if (perDayLimit != null) { @@ -450,7 +450,7 @@ public String getTokenQuotaString() { builder.append(","); } builder.append(String.format("b=per_day;q=%d;r=%d;t=%d", - perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getTime())); + perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getResetAfter())); } return builder.toString(); diff --git a/src/test/java/com/auth0/utils/HttpResponseHeadersUtilsTest.java b/src/test/java/com/auth0/utils/HttpResponseHeadersUtilsTest.java new file mode 100644 index 000000000..77ddc8918 --- /dev/null +++ b/src/test/java/com/auth0/utils/HttpResponseHeadersUtilsTest.java @@ -0,0 +1,119 @@ +package com.auth0.utils; + +import com.auth0.net.TokenQuotaBucket; +import com.auth0.net.TokenQuotaLimit; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class HttpResponseHeadersUtilsTest { + + @Test + public void testGetClientQuotaLimitWithValidHeader() { + Map headers = new HashMap<>(); + headers.put("auth0-client-quota-limit", "per_hour;q=100;r=50;t=3600,per_day;q=1000;r=500;t=86400"); + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.getClientQuotaLimit(headers); + + assertNotNull(quotaBucket); + assertNotNull(quotaBucket.getPerHour()); + assertEquals(100, quotaBucket.getPerHour().getQuota()); + assertEquals(50, quotaBucket.getPerHour().getRemaining()); + assertEquals(3600, quotaBucket.getPerHour().getResetAfter()); + + assertNotNull(quotaBucket.getPerDay()); + assertEquals(1000, quotaBucket.getPerDay().getQuota()); + assertEquals(500, quotaBucket.getPerDay().getRemaining()); + assertEquals(86400, quotaBucket.getPerDay().getResetAfter()); + } + + @Test + public void testGetClientQuotaLimitWithMissingHeader() { + Map headers = new HashMap<>(); + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.getClientQuotaLimit(headers); + + assertNull(quotaBucket); + } + + @Test + public void testGetClientQuotaLimitWithOneBucketHeader() { + Map headers = new HashMap<>(); + headers.put("auth0-client-quota-limit", "per_hour;q=200;r=100;t=3600"); + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.getClientQuotaLimit(headers); + + assertNotNull(quotaBucket); + assertNotNull(quotaBucket.getPerHour()); + assertEquals(200, quotaBucket.getPerHour().getQuota()); + assertEquals(100, quotaBucket.getPerHour().getRemaining()); + assertEquals(3600, quotaBucket.getPerHour().getResetAfter()); + + assertNull(quotaBucket.getPerDay()); + } + + @Test + public void testGetOrganizationQuotaLimitWithValidHeader() { + Map headers = new HashMap<>(); + headers.put("auth0-organization-quota-limit", "per_hour;q=200;r=100;t=3600"); + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.getOrganizationQuotaLimit(headers); + + assertNotNull(quotaBucket); + assertNotNull(quotaBucket.getPerHour()); + assertEquals(200, quotaBucket.getPerHour().getQuota()); + assertEquals(100, quotaBucket.getPerHour().getRemaining()); + assertEquals(3600, quotaBucket.getPerHour().getResetAfter()); + + assertNull(quotaBucket.getPerDay()); + } + + @Test + public void testGetOrganizationQuotaLimitWithMissingHeader() { + Map headers = new HashMap<>(); + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.getOrganizationQuotaLimit(headers); + + assertNull(quotaBucket); + } + + @Test + public void testParseQuotaWithValidInput() { + String quotaHeader = "per_hour;q=300;r=150;t=3600,per_day;q=3000;r=1500;t=86400"; + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.parseQuota(quotaHeader); + + assertNotNull(quotaBucket); + assertNotNull(quotaBucket.getPerHour()); + assertEquals(300, quotaBucket.getPerHour().getQuota()); + assertEquals(150, quotaBucket.getPerHour().getRemaining()); + assertEquals(3600, quotaBucket.getPerHour().getResetAfter()); + + assertNotNull(quotaBucket.getPerDay()); + assertEquals(3000, quotaBucket.getPerDay().getQuota()); + assertEquals(1500, quotaBucket.getPerDay().getRemaining()); + assertEquals(86400, quotaBucket.getPerDay().getResetAfter()); + } + + @Test + public void testParseQuotaWithInvalidInput() { + String quotaHeader = "invalid_format"; + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.parseQuota(quotaHeader); + + assertNull(quotaBucket); + } + + @Test + public void testParseQuotaWithEmptyInput() { + String quotaHeader = ""; + + TokenQuotaBucket quotaBucket = HttpResponseHeadersUtils.parseQuota(quotaHeader); + + assertNull(quotaBucket); + } +} From a3b4557dbf1bc0f136f15bf3a304bd377b19fe74 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Thu, 29 May 2025 12:28:30 +0530 Subject: [PATCH 12/12] Covered Edge Case --- src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java index 788bb542b..12d4b14dc 100644 --- a/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java +++ b/src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java @@ -65,9 +65,9 @@ public static TokenQuotaBucket parseQuota(String tokenQuota) { } } - if (attributes[0].contains("per_hour")) { + if (attributes.length >0 && attributes[0].contains("per_hour")) { perHour = new TokenQuotaLimit(quota, remaining, time); - } else if (attributes[0].contains("per_day")) { + } else if (attributes.length >0 && attributes[0].contains("per_day")) { perDay = new TokenQuotaLimit(quota, remaining, time); } }