Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/main/java/com/auth0/client/mgmt/ManagementApiBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package com.auth0.client.mgmt;

import com.auth0.client.mgmt.core.*;
import com.auth0.net.Telemetry;
import com.auth0.utils.Asserts;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
Expand All @@ -27,6 +29,8 @@ public class ManagementApiBuilder {

private String customDomain = null;

private Telemetry telemetry = null;

// Domain-based initialization fields
private String domain = null;
private String clientId = null;
Expand Down Expand Up @@ -188,6 +192,24 @@ public ManagementApiBuilder addHeader(String name, String value) {
return this;
}

/**
* Configure the telemetry sent to Auth0 on every request. Use this when building an SDK on top of
* auth0-java so that requests are attributed to the wrapping library, with auth0-java reported as the
* underlying library in the telemetry environment.
*
* <p>If not set, telemetry defaults to identifying this library as {@code auth0-java}.
*
* @param name The name of the library sending the requests (e.g. the wrapping SDK's name)
* @param version The version of the library sending the requests
* @return This builder for method chaining
*/
public ManagementApiBuilder withTelemetry(String name, String version) {
Asserts.assertNotNull(name, "name");
this.telemetry =
new Telemetry(name, version, Telemetry.class.getPackage().getImplementationVersion());
return this;
}

protected ClientOptions buildClientOptions() {
ClientOptions.Builder builder = ClientOptions.builder();
setEnvironment(builder);
Expand All @@ -203,6 +225,9 @@ protected ClientOptions buildClientOptions() {
builder.addHeader(CustomDomainInterceptor.HEADER_NAME, this.customDomain);
builder.addInterceptor(new CustomDomainInterceptor());
}
if (this.telemetry != null) {
builder.telemetry(this.telemetry);
}
setAdditional(builder);
return builder.build();
}
Expand Down
73 changes: 66 additions & 7 deletions src/main/java/com/auth0/client/mgmt/core/ClientOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,31 @@ public final class ClientOptions {

private final int maxRetries;

private final Telemetry telemetry;

private ClientOptions(
Environment environment,
Map<String, String> headers,
Map<String, Supplier<String>> headerSuppliers,
OkHttpClient httpClient,
int timeout,
int maxRetries) {
int maxRetries,
Telemetry telemetry) {
this.environment = environment;
this.headers = new HashMap<>();
this.headers.putAll(headers);

Telemetry telemetry =
new Telemetry("auth0-java", Telemetry.class.getPackage().getImplementationVersion());
if (telemetry.getValue() != null) {
this.headers.put("Auth0-Client", telemetry.getValue());
Telemetry resolvedTelemetry = telemetry != null
? telemetry
: new Telemetry("auth0-java", Telemetry.class.getPackage().getImplementationVersion());
if (resolvedTelemetry.getValue() != null) {
this.headers.put("Auth0-Client", resolvedTelemetry.getValue());
}
this.headerSuppliers = headerSuppliers;
this.httpClient = httpClient;
this.timeout = timeout;
this.maxRetries = maxRetries;
this.telemetry = telemetry;
}

public Environment environment() {
Expand Down Expand Up @@ -92,6 +97,32 @@ public int maxRetries() {
return this.maxRetries;
}

/**
* The telemetry explicitly configured on this instance, or {@code null} if it was left to
* default to {@code auth0-java}. Package-private so {@link Builder#from(ClientOptions)} can
* carry it over; not part of the public API.
*/
Telemetry telemetry() {
return this.telemetry;
}

/**
* The static headers configured on this instance, including the resolved {@code Auth0-Client}
* header. Package-private so {@link Builder#from(ClientOptions)} can carry them over; not part
* of the public API.
*/
Map<String, String> headers() {
return this.headers;
}

/**
* The dynamic header suppliers configured on this instance. Package-private so
* {@link Builder#from(ClientOptions)} can carry them over; not part of the public API.
*/
Map<String, Supplier<String>> headerSuppliers() {
return this.headerSuppliers;
}

public static Builder builder() {
return new Builder();
}
Expand All @@ -113,6 +144,8 @@ public static class Builder {

private LogConfig logging = null;

private Telemetry telemetry = null;

public Builder environment(Environment environment) {
this.environment = environment;
return this;
Expand Down Expand Up @@ -173,6 +206,17 @@ public Builder addInterceptor(Interceptor interceptor) {
return this;
}

/**
* Internal plumbing. Sets the pre-built {@link Telemetry} used for the {@code Auth0-Client}
* header. Prefer {@link com.auth0.client.mgmt.ManagementApiBuilder#withTelemetry(String, String)},
* which constructs the telemetry with the correct auth0-java version. Passing a hand-built
* {@link Telemetry} here bypasses that and is not a supported way to identify a wrapping SDK.
*/
public Builder telemetry(Telemetry telemetry) {
this.telemetry = telemetry;
return this;
}

public ClientOptions build() {
OkHttpClient.Builder httpClientBuilder =
this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder();
Expand Down Expand Up @@ -205,17 +249,32 @@ public ClientOptions build() {
this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000);

return new ClientOptions(
environment, headers, headerSuppliers, httpClient, this.timeout.get(), this.maxRetries);
environment,
headers,
headerSuppliers,
httpClient,
this.timeout.get(),
this.maxRetries,
this.telemetry);
}

/**
* Create a new Builder initialized with values from an existing ClientOptions
* Create a new Builder initialized with values from an existing ClientOptions.
*
* <p>Interceptors and logging are not copied explicitly: they are already baked into the
* {@link OkHttpClient} carried over here, and {@link #build()} preserves them via
* {@code newBuilder()}. Copying them would attach them a second time. The same applies to
* the retry interceptor, hence {@code maxRetries} is carried over for reporting only.
*/
public static Builder from(ClientOptions clientOptions) {
Builder builder = new Builder();
builder.environment = clientOptions.environment();
builder.timeout = Optional.of(clientOptions.timeout(null));
builder.httpClient = clientOptions.httpClient();
builder.maxRetries = clientOptions.maxRetries();
builder.telemetry = clientOptions.telemetry();
builder.headers.putAll(clientOptions.headers());
builder.headerSuppliers.putAll(clientOptions.headerSuppliers());
return builder;
}
}
Expand Down
28 changes: 26 additions & 2 deletions src/main/java/com/auth0/net/client/DefaultHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.auth0.client.LoggingOptions;
import com.auth0.client.ProxyOptions;
import com.auth0.net.RateLimitInterceptor;
import com.auth0.net.Telemetry;
import com.auth0.net.TelemetryInterceptor;
import com.auth0.utils.Asserts;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -19,7 +21,7 @@
* to the Auth0 APIs. Instances can be configured and created using the {@link Builder}.
* <p>
* To minimize resource usage, instances should be created once and used in both the
* {@link com.auth0.client.mgmt.ManagementAPI} and {@link com.auth0.client.auth.AuthAPI}

Check warning on line 24 in src/main/java/com/auth0/net/client/DefaultHttpClient.java

View workflow job for this annotation

GitHub Actions / gradle

Tag @link: reference not found: com.auth0.client.mgmt.ManagementAPI
* API clients.
* </p>
* <p>
Expand Down Expand Up @@ -57,7 +59,7 @@
clientBuilder.readTimeout(sanitizeTimeout(builder.readTimeout), TimeUnit.SECONDS);
clientBuilder.connectTimeout(sanitizeTimeout(builder.connectTimeout), TimeUnit.SECONDS);
clientBuilder.addInterceptor(getLoggingInterceptor(builder.loggingOptions));
clientBuilder.addInterceptor(getTelemetryInterceptor(builder.telemetryEnabled));
clientBuilder.addInterceptor(getTelemetryInterceptor(builder.telemetryEnabled, builder.telemetry));
clientBuilder.addInterceptor(getRateLimitInterceptor(builder.maxRetries));
clientBuilder.dispatcher(getDispatcher(builder.maxRequests, builder.maxRequestsPerHost));

Expand Down Expand Up @@ -243,8 +245,11 @@
}
}

private TelemetryInterceptor getTelemetryInterceptor(boolean telemetryEnabled) {
private TelemetryInterceptor getTelemetryInterceptor(boolean telemetryEnabled, Telemetry telemetry) {
TelemetryInterceptor interceptor = new TelemetryInterceptor();
if (telemetry != null) {
interceptor.setTelemetry(telemetry);
}
interceptor.setEnabled(telemetryEnabled);
return interceptor;
}
Expand Down Expand Up @@ -279,6 +284,7 @@
private ProxyOptions proxyOptions;
private LoggingOptions loggingOptions;
private boolean telemetryEnabled = true;
private Telemetry telemetry;
private int maxRetries = 3;
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
Expand Down Expand Up @@ -337,6 +343,24 @@
return this;
}

/**
* Configure the telemetry sent to Auth0 on every request. Use this when building an SDK on top of
* auth0-java so that requests are attributed to the wrapping library, with auth0-java reported as the
* underlying library in the telemetry environment.
* <p>
* If not set, telemetry defaults to identifying this library as {@code auth0-java}.
*
* @param name the name of the library sending the requests (e.g. the wrapping SDK's name).
* @param version the version of the library sending the requests.
* @return this builder instance.
*/
public Builder withTelemetry(String name, String version) {
Asserts.assertNotNull(name, "name");
this.telemetry =
new Telemetry(name, version, Telemetry.class.getPackage().getImplementationVersion());
return this;
}

/**
* Sets the maximum number of consecutive retries for API requests that fail due to rate-limits being reached.
* By default, rate-limited requests will be retried a maximum of three times. To disable retries on rate-limit
Expand Down
57 changes: 57 additions & 0 deletions src/test/java/com/auth0/client/auth/AuthAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
import com.auth0.net.BaseRequest;
import com.auth0.net.Request;
import com.auth0.net.SignUpRequest;
import com.auth0.net.Telemetry;
import com.auth0.net.TokenRequest;
import com.auth0.net.client.Auth0HttpClient;
import com.auth0.net.client.Auth0HttpRequest;
import com.auth0.net.client.Auth0HttpResponse;
import com.auth0.net.client.DefaultHttpClient;
import com.auth0.net.client.HttpMethod;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.FileReader;
import java.net.URLDecoder;
Expand Down Expand Up @@ -261,6 +264,60 @@ public void shouldCreateUserInfoRequest() throws Exception {
assertThat(identities.get(0), hasEntry("isSocial", false));
}

// Telemetry

private JsonNode decodeTelemetryHeader(RecordedRequest request) throws Exception {
String header = request.getHeader("Auth0-Client");
assertThat(header, is(notNullValue()));
return ObjectMapperProvider.getMapper().readTree(Base64.getUrlDecoder().decode(header));
}

@Test
public void shouldSendDefaultTelemetry() throws Exception {
Request<UserInfo> request = api.userInfo("accessToken");
server.jsonResponse(AUTH_USER_INFO, 200);
request.execute();

JsonNode telemetry = decodeTelemetryHeader(server.takeRequest());
assertThat(telemetry.get("name").asText(), is("auth0-java"));
}

@Test
public void shouldSendCustomTelemetryWhenConfigured() throws Exception {
AuthAPI customApi = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID, CLIENT_SECRET)
.withHttpClient(DefaultHttpClient.newBuilder()
.withTelemetry("my-wrapper-sdk", "1.2.3")
.build())
.build();

Request<UserInfo> request = customApi.userInfo("accessToken");
server.jsonResponse(AUTH_USER_INFO, 200);
request.execute();

JsonNode telemetry = decodeTelemetryHeader(server.takeRequest());
assertThat(telemetry.get("name").asText(), is("my-wrapper-sdk"));
assertThat(telemetry.get("version").asText(), is("1.2.3"));
}

@Test
public void shouldNestAuth0JavaInTelemetryEnv() throws Exception {
String value = new Telemetry("my-wrapper-sdk", "1.2.3", "auth0-java-9.9.9").getValue();

JsonNode telemetry =
ObjectMapperProvider.getMapper().readTree(Base64.getUrlDecoder().decode(value));
assertThat(telemetry.get("name").asText(), is("my-wrapper-sdk"));
assertThat(telemetry.get("version").asText(), is("1.2.3"));
assertThat(telemetry.get("env").get("auth0-java").asText(), is("auth0-java-9.9.9"));
}

@Test
public void shouldThrowWhenTelemetryNameIsNull() {
verifyThrows(
IllegalArgumentException.class,
() -> DefaultHttpClient.newBuilder().withTelemetry(null, "1.2.3"),
"'name' cannot be null!");
}

// Reset Password

@Test
Expand Down
Loading
Loading