diff --git a/README.md b/README.md index a239798cb0..f2bccb2ce0 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,12 @@ public class Example { } } ``` +### Initialize the client when endpoints does not use basic authentication +The above example shows how to initialize the client in case the endpoints use basic authentication. When the endpoint does not require any authentication, use TwilioNoAuth client instead. +There are endpoints like Organization domain which uses bearer token authentication. Custom Clients needs to be used in such cases and initialize them with the values required for access token generation. + +To bypass the initialization step you can also use a custom token manager implementation. Token manager class should implement the Token interface and call a token generation endpoint of your choice. +Detailed examples [here](https://github.com/twilio/twilio-java/tree/main/examples) ### Environment Variables diff --git a/examples/BearerTokenAuthentication.md b/examples/BearerTokenAuthentication.md new file mode 100644 index 0000000000..2618436b32 --- /dev/null +++ b/examples/BearerTokenAuthentication.md @@ -0,0 +1,26 @@ + class BearerTokenAuthenticationExamples { + public static void main { + + private static final String GRANT_TYPE = "grant_type_to_be_used"; + private static final String CLIENT_SID = + "client_id_of_the_organization"; + private static final String CLIENT_SECRET = "client_secret_of_organization"; + private static final String ORGANISATION_ID = "id_of_the_organization"; + + //Getting access token - Method #1 + TwilioOrgsTokenAuth.init(GRANT_TYPE, CLIENT_ID, CLIENT_SECRET); + + //Getting access token - Method #2 + //To provide custom token manager implementation + //Need not call init method if customer token manager is passed + //TwilioOrgsTokenAuth.setTokenManager(new CustomTokenManagerImpl(GRANT_TYPE, CLIENT_ID, CLIENT_SECRET)); + + fetchAccountDetails(); + } + + private static void fetchAccountDetails() { + ResourceSet accountSet = Account.reader(ORGANISATION_ID).read(); + String accountSid = accountSet.iterator().next().getAccountSid(); + System.out.println(accountSid); + } + } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7bd580428f..c699731667 100644 --- a/pom.xml +++ b/pom.xml @@ -315,6 +315,17 @@ 1.10.19 test + + com.auth0 + java-jwt + 4.4.0 + + + com.fasterxml.jackson.core + jackson-databind + + + diff --git a/src/main/java/com/twilio/Domains.java b/src/main/java/com/twilio/Domains.java index 2c03699113..53b195a505 100644 --- a/src/main/java/com/twilio/Domains.java +++ b/src/main/java/com/twilio/Domains.java @@ -32,6 +32,7 @@ public enum Domains { NUMBERS("numbers"), OAUTH("oauth"), PREVIEW("preview"), + PREVIEWIAM("preview-iam"), PRICING("pricing"), PROXY("proxy"), ROUTES("routes"), diff --git a/src/main/java/com/twilio/TwilioNoAuth.java b/src/main/java/com/twilio/TwilioNoAuth.java new file mode 100644 index 0000000000..b4a646c8c8 --- /dev/null +++ b/src/main/java/com/twilio/TwilioNoAuth.java @@ -0,0 +1,48 @@ +package com.twilio; + +import com.twilio.annotations.Preview; +import com.twilio.http.noauth.NoAuthTwilioRestClient; +import lombok.Getter; + +import java.util.List; +import com.twilio.exception.AuthenticationException; + +@Preview +public class TwilioNoAuth { + @Getter + private static List userAgentExtensions; + private static String region = System.getenv("TWILIO_REGION"); + private static String edge = System.getenv("TWILIO_EDGE"); + + private static volatile NoAuthTwilioRestClient noAuthTwilioRestClient; + + private TwilioNoAuth() { + } + + public static NoAuthTwilioRestClient getRestClient() { + if (TwilioNoAuth.noAuthTwilioRestClient == null) { + synchronized (TwilioNoAuth.class) { + if (TwilioNoAuth.noAuthTwilioRestClient == null) { + TwilioNoAuth.noAuthTwilioRestClient = buildOAuthRestClient(); + } + } + } + return TwilioNoAuth.noAuthTwilioRestClient; + } + + private static NoAuthTwilioRestClient buildOAuthRestClient() { + + NoAuthTwilioRestClient.Builder builder = new NoAuthTwilioRestClient.Builder(); + + if (userAgentExtensions != null) { + builder.userAgentExtensions(TwilioNoAuth.userAgentExtensions); + } + + builder.region(TwilioNoAuth.region); + builder.edge(TwilioNoAuth.edge); + + return builder.build(); + } + + +} diff --git a/src/main/java/com/twilio/TwilioOrgsTokenAuth.java b/src/main/java/com/twilio/TwilioOrgsTokenAuth.java new file mode 100644 index 0000000000..be001967a2 --- /dev/null +++ b/src/main/java/com/twilio/TwilioOrgsTokenAuth.java @@ -0,0 +1,106 @@ +package com.twilio; + +import com.twilio.annotations.Preview; +import com.twilio.exception.AuthenticationException; +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import com.twilio.http.bearertoken.TokenManager; +import com.twilio.http.bearertoken.OrgsTokenManager; + +@Preview +public class TwilioOrgsTokenAuth { + private static String accessToken; + @Getter + private static List userAgentExtensions; + private static String region = System.getenv("TWILIO_REGION"); + private static String edge = System.getenv("TWILIO_EDGE"); + private static volatile BearerTokenTwilioRestClient restClient; + @Getter @Setter + private static TokenManager tokenManager; + + private static volatile ExecutorService executorService; + + private TwilioOrgsTokenAuth() { + } + + public static synchronized void init(String grantType, String clientId, String clientSecret) { + validateAuthCredentials(grantType, clientId, clientSecret); + tokenManager = new OrgsTokenManager(grantType, clientId, clientSecret); + } + public static synchronized void init(String grantType, String clientId, String clientSecret, String code, String redirectUri, String audience, String refreshToken, String scope) { + validateAuthCredentials(grantType, clientId, clientSecret); + tokenManager = new OrgsTokenManager(grantType, clientId, clientSecret, code, redirectUri, audience, refreshToken, scope); + } + + private static void validateAuthCredentials(String grantType, String clientId, String clientSecret){ + if (grantType == null) { + throw new AuthenticationException("Grant Type cannot be null"); + } + if (clientId == null) { + throw new AuthenticationException("Client Id cannot be null"); + } + if (clientSecret == null) { + throw new AuthenticationException("Client Secret cannot be null"); + } + return; + } + + public static BearerTokenTwilioRestClient getRestClient() { + if (TwilioOrgsTokenAuth.restClient == null) { + synchronized (TwilioOrgsTokenAuth.class) { + if (TwilioOrgsTokenAuth.restClient == null) { + TwilioOrgsTokenAuth.restClient = buildOAuthRestClient(); + } + } + } + return TwilioOrgsTokenAuth.restClient; + } + /** + * Returns the Twilio executor service. + * + * @return the Twilio executor service + */ + public static ExecutorService getExecutorService() { + if (TwilioOrgsTokenAuth.executorService == null) { + synchronized (TwilioOrgsTokenAuth.class) { + if (TwilioOrgsTokenAuth.executorService == null) { + TwilioOrgsTokenAuth.executorService = Executors.newCachedThreadPool(); + } + } + } + return TwilioOrgsTokenAuth.executorService; + } + + private static BearerTokenTwilioRestClient buildOAuthRestClient() { + + BearerTokenTwilioRestClient.Builder builder = new BearerTokenTwilioRestClient.Builder(); + + if (userAgentExtensions != null) { + builder.userAgentExtensions(TwilioOrgsTokenAuth.userAgentExtensions); + } + + builder.region(TwilioOrgsTokenAuth.region); + builder.edge(TwilioOrgsTokenAuth.edge); + if(TwilioOrgsTokenAuth.tokenManager == null){ + throw new AuthenticationException("Either initialize the authentications class or pass a custom token manager"); + } + builder.tokenManager(TwilioOrgsTokenAuth.tokenManager); + + return builder.build(); + } + + /** + * Invalidates the volatile state held in the Twilio singleton. + */ + private static void invalidate() { + TwilioOrgsTokenAuth.restClient = null; + TwilioOrgsTokenAuth.tokenManager = null; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/twilio/annotations/Beta.java b/src/main/java/com/twilio/annotations/Beta.java new file mode 100644 index 0000000000..f80596f57c --- /dev/null +++ b/src/main/java/com/twilio/annotations/Beta.java @@ -0,0 +1,9 @@ +package com.twilio.annotations; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Beta { + String value() default "This class/method is under beta and is subjected to change. Use with caution."; +} \ No newline at end of file diff --git a/src/main/java/com/twilio/annotations/Preview.java b/src/main/java/com/twilio/annotations/Preview.java new file mode 100644 index 0000000000..67d0e512a6 --- /dev/null +++ b/src/main/java/com/twilio/annotations/Preview.java @@ -0,0 +1,12 @@ +package com.twilio.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Preview { + String value() default "This class/method is under preview and is subjected to change. Use with caution."; +} \ No newline at end of file diff --git a/src/main/java/com/twilio/base/bearertoken/Creator.java b/src/main/java/com/twilio/base/bearertoken/Creator.java new file mode 100644 index 0000000000..8cb676cef6 --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Creator.java @@ -0,0 +1,50 @@ +package com.twilio.base.bearertoken; + +import com.twilio.TwilioOrgsTokenAuth; +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for creation of a resource. + * + * @param type of the resource + */ +public abstract class Creator { + + /** + * Execute an async request using default client. + * + * @return future that resolves to requested object + */ + public CompletableFuture createAsync() { + return createAsync(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to requested object + */ + public CompletableFuture createAsync(final BearerTokenTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> create(client), TwilioOrgsTokenAuth.getExecutorService()); + } + + /** + * Execute a request using default client. + * + * @return Requested object + */ + public T create() { + return create(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return Requested object + */ + public abstract T create(final BearerTokenTwilioRestClient client); +} diff --git a/src/main/java/com/twilio/base/bearertoken/Deleter.java b/src/main/java/com/twilio/base/bearertoken/Deleter.java new file mode 100644 index 0000000000..3b49cfd0ba --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Deleter.java @@ -0,0 +1,51 @@ +package com.twilio.base.bearertoken; + +import com.twilio.Twilio; +import com.twilio.TwilioOrgsTokenAuth; +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for deletes of a resource. + * + * @param type of the resource + */ +public abstract class Deleter { + + /** + * Execute an async request using default client. + * + * @return future that resolves to true if the object was deleted + */ + public CompletableFuture deleteAsync() { + return deleteAsync(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to true if the object was deleted + */ + public CompletableFuture deleteAsync(final BearerTokenTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> delete(client), Twilio.getExecutorService()); + } + + /** + * Execute a request using default client. + * + * @return true if the object was deleted + */ + public boolean delete() { + return delete(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return true if the object was deleted + */ + public abstract boolean delete(final BearerTokenTwilioRestClient client); +} diff --git a/src/main/java/com/twilio/base/bearertoken/Fetcher.java b/src/main/java/com/twilio/base/bearertoken/Fetcher.java new file mode 100644 index 0000000000..5654895a3f --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Fetcher.java @@ -0,0 +1,50 @@ +package com.twilio.base.bearertoken; + +import com.twilio.TwilioOrgsTokenAuth; +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for fetches of a resource. + * + * @param type of the resource + */ +public abstract class Fetcher { + + /** + * Execute an async request using default client. + * + * @return future that resolves to requested object + */ + public CompletableFuture fetchAsync() { + return fetchAsync(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to requested object + */ + public CompletableFuture fetchAsync(final BearerTokenTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> fetch(client), TwilioOrgsTokenAuth.getExecutorService()); + } + + /** + * Execute a request using default client. + * + * @return Requested object + */ + public T fetch() { + return fetch(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return Requested object + */ + public abstract T fetch(final BearerTokenTwilioRestClient client); +} diff --git a/src/main/java/com/twilio/base/bearertoken/Page.java b/src/main/java/com/twilio/base/bearertoken/Page.java new file mode 100644 index 0000000000..204c2e98a2 --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Page.java @@ -0,0 +1,270 @@ +package com.twilio.base.bearertoken; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.twilio.exception.ApiConnectionException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Page { + private final List records; + private final String firstPageUrl; + private final String firstPageUri; + private final String nextPageUrl; + private final String nextPageUri; + private final String previousPageUrl; + private final String previousPageUri; + private final String url; + private final String uri; + private final int pageSize; + + private Page(Builder b) { + this.records = b.records; + this.firstPageUri = b.firstPageUri; + this.firstPageUrl = b.firstPageUrl; + this.nextPageUri = b.nextPageUri; + this.nextPageUrl = b.nextPageUrl; + this.previousPageUri = b.previousPageUri; + this.previousPageUrl = b.previousPageUrl; + this.uri = b.uri; + this.url = b.url; + this.pageSize = b.pageSize; + } + + private String urlFromUri(String domain, String uri) { + return "https://" + domain + ".twilio.com" + uri; + } + + public List getRecords() { + return records; + } + + /** + * Generate first page url for a list result. + * + * @param domain domain to use + * @return the first page url + */ + public String getFirstPageUrl(String domain) { + if (firstPageUrl != null) { + return firstPageUrl; + } + + return urlFromUri(domain, firstPageUri); + } + + /** + * Generate next page url for a list result. + * + * @param domain domain to use + * @return the next page url + */ + public String getNextPageUrl(String domain) { + if (nextPageUrl != null) { + return nextPageUrl; + } + + return urlFromUri(domain, nextPageUri); + } + + /** + * Generate previous page url for a list result. + * + * @param domain domain to use + * @return the previous page url + */ + public String getPreviousPageUrl(String domain) { + if (previousPageUrl != null) { + return previousPageUrl; + } + + return urlFromUri(domain, previousPageUri); + } + + public int getPageSize() { + return pageSize; + } + + /** + * Generate page url for a list result. + * + * @param domain domain to use + * @return the page url + */ + public String getUrl(String domain) { + if (url != null) { + return url; + } + + return urlFromUri(domain, uri); + } + + public boolean hasNextPage() { + return (nextPageUri != null && !nextPageUri.isEmpty()) || (nextPageUrl != null && !nextPageUrl.isEmpty()); + } + + /** + * Create a new page of data from a json blob. + * + * @param recordKey key which holds the records + * @param json json blob + * @param recordType resource type + * @param mapper json parser + * @param record class type + * @return a page of records of type T + */ + public static Page fromJson(String recordKey, String json, Class recordType, ObjectMapper mapper) { + try { + List results = new ArrayList<>(); + JsonNode root = mapper.readTree(json); + JsonNode records = root.get(recordKey); + for (final JsonNode record : records) { + results.add(mapper.readValue(record.toString(), recordType)); + } + + JsonNode uriNode = root.get("uri"); + if (uriNode != null) { + return buildPage(root, results); + } else { + return buildNextGenPage(root, results); + } + + } catch (final IOException e) { + throw new ApiConnectionException( + "Unable to deserialize response: " + e.getMessage() + "\nJSON: " + json, e + ); + } + } + + private static Page buildPage(JsonNode root, List results) { + Builder builder = new Builder() + .uri(root.get("uri").asText()); + + JsonNode nextPageNode = root.get("next_page_uri"); + if (nextPageNode != null && !nextPageNode.isNull()) { + builder.nextPageUri(nextPageNode.asText()); + } + + JsonNode previousPageNode = root.get("previous_page_uri"); + if (previousPageNode != null && !previousPageNode.isNull()) { + builder.previousPageUri(previousPageNode.asText()); + } + + JsonNode firstPageNode = root.get("first_page_uri"); + if (firstPageNode != null && !firstPageNode.isNull()) { + builder.firstPageUri(firstPageNode.asText()); + } + + JsonNode pageSizeNode = root.get("page_size"); + if (pageSizeNode != null && !pageSizeNode.isNull()) { + builder.pageSize(pageSizeNode.asInt()); + } else { + builder.pageSize(results.size()); + } + + return builder.records(results).build(); + } + + private static Page buildNextGenPage(JsonNode root, List results) { + JsonNode meta = root.get("meta"); + Builder builder = new Builder<>(); + if(meta != null && meta.get("url") != null) { + + builder = builder.url(meta.get("url").asText()); + + + JsonNode nextPageNode = meta.get("next_page_url"); + if (!nextPageNode.isNull()) { + builder.nextPageUrl(nextPageNode.asText()); + } + + JsonNode previousPageNode = meta.get("previous_page_url"); + if (!previousPageNode.isNull()) { + builder.previousPageUrl(previousPageNode.asText()); + } + + JsonNode firstPageNode = meta.get("first_page_url"); + if (!firstPageNode.isNull()) { + builder.firstPageUrl(firstPageNode.asText()); + } + + JsonNode pageSizeNode = meta.get("page_size"); + if (!pageSizeNode.isNull()) { + builder.pageSize(pageSizeNode.asInt()); + } else { + builder.pageSize(results.size()); + } + } + + return builder.records(results).build(); + } + + private static class Builder { + private List records; + private String firstPageUrl; + private String firstPageUri; + private String nextPageUrl; + private String nextPageUri; + private String previousPageUrl; + private String previousPageUri; + private String uri; + private String url; + private int pageSize; + + public Builder records(List records) { + this.records = records; + return this; + } + + public Builder firstPageUri(String firstPageUri) { + this.firstPageUri = firstPageUri; + return this; + } + + public Builder firstPageUrl(String firstPageUrl) { + this.firstPageUrl = firstPageUrl; + return this; + } + + public Builder nextPageUri(String nextPageUri) { + this.nextPageUri = nextPageUri; + return this; + } + + public Builder nextPageUrl(String nextPageUrl) { + this.nextPageUrl = nextPageUrl; + return this; + } + + public Builder previousPageUri(String previousPageUri) { + this.previousPageUri = previousPageUri; + return this; + } + + public Builder previousPageUrl(String previousPageUrl) { + this.previousPageUrl = previousPageUrl; + return this; + } + + public Builder uri(String uri) { + this.uri = uri; + return this; + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder pageSize(int pageSize) { + this.pageSize = pageSize; + return this; + } + + public Page build() { + return new Page<>(this); + } + } +} diff --git a/src/main/java/com/twilio/base/bearertoken/Reader.java b/src/main/java/com/twilio/base/bearertoken/Reader.java new file mode 100644 index 0000000000..15e221a3f9 --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Reader.java @@ -0,0 +1,156 @@ +package com.twilio.base.bearertoken; + +import com.twilio.TwilioOrgsTokenAuth; +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for listing of a resource. + * + * @param type of the resource + */ +public abstract class Reader { + + private Integer pageSize; + private Long limit; + + /** + * Execute a request using default client. + * + * @return ResourceSet of objects + */ + public ResourceSet read() { + return read(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return ResourceSet of objects + */ + public abstract ResourceSet read(final BearerTokenTwilioRestClient client); + + /** + * Execute an async request using default client. + * + * @return future that resolves to the ResourceSet of objects + */ + public CompletableFuture> readAsync() { + return readAsync(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to the ResourceSet of objects + */ + public CompletableFuture> readAsync(final BearerTokenTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> read(client), TwilioOrgsTokenAuth.getExecutorService()); + } + + /** + * Fetch the first page of resources. + * + * @return Page containing the first pageSize of resources + */ + public Page firstPage() { + return firstPage(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Fetch the first page of resources using specified client. + * + * @param client client used to fetch + * @return Page containing the first pageSize of resources + */ + public abstract Page firstPage(final BearerTokenTwilioRestClient client); + + /** + * Retrieve the target page of resources. + * + * @param targetUrl API-generated URL for the requested results page + * @return Page containing the target pageSize of resources + */ + public Page getPage(final String targetUrl) { + return getPage(targetUrl, TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Retrieve the target page of resources. + * + * @param targetUrl API-generated URL for the requested results page + * @param client client used to fetch + * @return Page containing the target pageSize of resources + */ + public abstract Page getPage(final String targetUrl, final BearerTokenTwilioRestClient client); + + /** + * Fetch the following page of resources. + * + * @param page current page of resources + * @return Page containing the next pageSize of resources + */ + public Page nextPage(final Page page) { + return nextPage(page, TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Fetch the following page of resources using specified client. + * + * @param page current page of resources + * @param client client used to fetch + * @return Page containing the next pageSize of resources + */ + public abstract Page nextPage(final Page page, final BearerTokenTwilioRestClient client); + + /** + * Fetch the prior page of resources. + * + * @param page current page of resources + * @return Page containing the previous pageSize of resources + */ + public Page previousPage(final Page page) { + return previousPage(page, TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Fetch the prior page of resources using specified client. + * + * @param page current page of resources + * @param client client used to fetch + * @return Page containing the previous pageSize of resources + */ + public abstract Page previousPage(final Page page, final BearerTokenTwilioRestClient client); + + public Integer getPageSize() { + return pageSize; + } + + public Reader pageSize(final int pageSize) { + this.pageSize = pageSize; + return this; + } + + public Long getLimit() { + return limit; + } + + /** + * Sets the max number of records to read. + * + * @param limit max number of records to read + * @return this reader + */ + public Reader limit(final long limit) { + this.limit = limit; + + if (this.pageSize == null) { + this.pageSize = this.limit.intValue(); + } + + return this; + } +} diff --git a/src/main/java/com/twilio/base/bearertoken/Resource.java b/src/main/java/com/twilio/base/bearertoken/Resource.java new file mode 100644 index 0000000000..08dc228556 --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Resource.java @@ -0,0 +1,9 @@ +package com.twilio.base.bearertoken; + +import java.io.Serializable; + +public abstract class Resource implements Serializable { + + private static final long serialVersionUID = -5898012691404059591L; + +} diff --git a/src/main/java/com/twilio/base/bearertoken/ResourceSet.java b/src/main/java/com/twilio/base/bearertoken/ResourceSet.java new file mode 100644 index 0000000000..16bc369084 --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/ResourceSet.java @@ -0,0 +1,130 @@ +package com.twilio.base.bearertoken; + +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A collection of resources. + * + * @param type of the resource + */ +public class ResourceSet implements Iterable { + + private final Reader reader; + private final BearerTokenTwilioRestClient client; + + private boolean autoPaging; + private long pages = 1; + private long pageLimit = Long.MAX_VALUE; + private long processed = 0; + private Page page; + private Iterator iterator; + + /** + * Initialize the resource set. + * + * @param reader reader used to fetch next page + * @param client client used to make requests + * @param page page of data + */ + public ResourceSet(final Reader reader, final BearerTokenTwilioRestClient client, final Page page) { + this.reader = reader; + this.client = client; + this.page = page; + this.iterator = page.getRecords().iterator(); + this.autoPaging = true; + + if (reader.getLimit() != null) { + this.pageLimit = (long)(Math.ceil((double)reader.getLimit() / (double)page.getPageSize())); + } + } + + public boolean isAutoPaging() { + return autoPaging; + } + + public ResourceSet setAutoPaging(final boolean autoPaging) { + this.autoPaging = autoPaging; + return this; + } + + public Integer getPageSize() { + return page.getPageSize(); + } + + public ResourceSet setPageSize(final int pageSize) { + reader.pageSize(pageSize); + return this; + } + + public Long getLimit() { + return reader.getLimit(); + } + + public ResourceSet setLimit(final long limit) { + reader.limit(limit); + return this; + } + + public long getPageLimit() { + return pageLimit; + } + + @Override + public Iterator iterator() { + return new ResourceSetIterator<>(this); + } + + private void fetchNextPage() { + if (!page.hasNextPage() || pages >= pageLimit) { + return; + } + + pages++; + page = reader.nextPage(page, client); + iterator = page.getRecords().iterator(); + } + + private class ResourceSetIterator implements Iterator { + private final ResourceSet resourceSet; + + public ResourceSetIterator(final ResourceSet resourceSet) { + this.resourceSet = resourceSet; + } + + @Override + public boolean hasNext() { + if (resourceSet.getLimit() != null && resourceSet.processed >= resourceSet.getLimit()) { + return false; + } + + return resourceSet.iterator.hasNext(); + } + + @Override + public E next() { + if (resourceSet == null || resourceSet.iterator == null) { + throw new NoSuchElementException(); + } + + E element = resourceSet.iterator.next(); + if (resourceSet.isAutoPaging() && !resourceSet.iterator.hasNext()) { + resourceSet.fetchNextPage(); + } + + resourceSet.processed++; + return element; + } + + @Override + public void remove() { + if (resourceSet.iterator != null) { + resourceSet.processed++; + resourceSet.iterator.remove(); + } + } + + } +} diff --git a/src/main/java/com/twilio/base/bearertoken/Updater.java b/src/main/java/com/twilio/base/bearertoken/Updater.java new file mode 100644 index 0000000000..690e3604c5 --- /dev/null +++ b/src/main/java/com/twilio/base/bearertoken/Updater.java @@ -0,0 +1,50 @@ +package com.twilio.base.bearertoken; + +import com.twilio.TwilioOrgsTokenAuth; +import com.twilio.http.bearertoken.BearerTokenTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for updates of a resource. + * + * @param type of the resource + */ +public abstract class Updater { + + /** + * Execute an async request using default client. + * + * @return future that resolves to requested object + */ + public CompletableFuture updateAsync() { + return updateAsync(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to requested object + */ + public CompletableFuture updateAsync(final BearerTokenTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> update(client), TwilioOrgsTokenAuth.getExecutorService()); + } + + /** + * Execute a request using default client. + * + * @return Requested object + */ + public T update() { + return update(TwilioOrgsTokenAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return Requested object + */ + public abstract T update(final BearerTokenTwilioRestClient client); +} diff --git a/src/main/java/com/twilio/base/noauth/Creator.java b/src/main/java/com/twilio/base/noauth/Creator.java new file mode 100644 index 0000000000..c6d620cd86 --- /dev/null +++ b/src/main/java/com/twilio/base/noauth/Creator.java @@ -0,0 +1,52 @@ +package com.twilio.base.noauth; + +import com.twilio.Twilio; +import com.twilio.TwilioNoAuth; +import com.twilio.base.noauth.Resource; +import com.twilio.http.noauth.NoAuthTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for creation of a resource. + * + * @param type of the resource + */ +public abstract class Creator { + + /** + * Execute an async request using default client. + * + * @return future that resolves to requested object + */ + public CompletableFuture createAsync() { + return createAsync(TwilioNoAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to requested object + */ + public CompletableFuture createAsync(final NoAuthTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> create(client), Twilio.getExecutorService()); + } + + /** + * Execute a request using default client. + * + * @return Requested object + */ + public T create() { + return create(TwilioNoAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return Requested object + */ + public abstract T create(final NoAuthTwilioRestClient client); +} diff --git a/src/main/java/com/twilio/base/noauth/Fetcher.java b/src/main/java/com/twilio/base/noauth/Fetcher.java new file mode 100644 index 0000000000..28dda28d4e --- /dev/null +++ b/src/main/java/com/twilio/base/noauth/Fetcher.java @@ -0,0 +1,53 @@ +package com.twilio.base.noauth; + +import com.twilio.Twilio; + +import com.twilio.TwilioNoAuth; +import com.twilio.base.noauth.Resource; +import com.twilio.http.noauth.NoAuthTwilioRestClient; + +import java.util.concurrent.CompletableFuture; + +/** + * Executor for fetches of a resource. + * + * @param type of the resource + */ +public abstract class Fetcher { + + /** + * Execute an async request using default client. + * + * @return future that resolves to requested object + */ + public CompletableFuture fetchAsync() { + return fetchAsync(TwilioNoAuth.getRestClient()); + } + + /** + * Execute an async request using specified client. + * + * @param client client used to make request + * @return future that resolves to requested object + */ + public CompletableFuture fetchAsync(final NoAuthTwilioRestClient client) { + return CompletableFuture.supplyAsync(() -> fetch(client), Twilio.getExecutorService()); + } + + /** + * Execute a request using default client. + * + * @return Requested object + */ + public T fetch() { + return fetch(TwilioNoAuth.getRestClient()); + } + + /** + * Execute a request using specified client. + * + * @param client client used to make request + * @return Requested object + */ + public abstract T fetch(final NoAuthTwilioRestClient client); +} diff --git a/src/main/java/com/twilio/base/noauth/Resource.java b/src/main/java/com/twilio/base/noauth/Resource.java new file mode 100644 index 0000000000..88e316899f --- /dev/null +++ b/src/main/java/com/twilio/base/noauth/Resource.java @@ -0,0 +1,9 @@ +package com.twilio.base.noauth; + +import java.io.Serializable; + +public abstract class Resource implements Serializable { + + private static final long serialVersionUID = -5898012691404059592L; + +} diff --git a/src/main/java/com/twilio/http/HttpClient.java b/src/main/java/com/twilio/http/HttpClient.java index 71e595ff34..46938dfad2 100644 --- a/src/main/java/com/twilio/http/HttpClient.java +++ b/src/main/java/com/twilio/http/HttpClient.java @@ -39,7 +39,7 @@ public abstract class HttpClient { @Getter private Response lastResponse; @Getter - private Request lastRequest; + private IRequest lastRequest; /** * Make a request. @@ -47,7 +47,7 @@ public abstract class HttpClient { * @param request request to make * @return Response of the HTTP request */ - public Response reliableRequest(final Request request) { + public Response reliableRequest(final IRequest request) { return reliableRequest(request, RETRY_CODES, RETRIES, DELAY_MILLIS); } @@ -60,7 +60,7 @@ public Response reliableRequest(final Request request) { * @param delayMillis delays between retries * @return Response of the HTTP request */ - public Response reliableRequest(final Request request, final int[] retryCodes, int retries, + public Response reliableRequest(final IRequest request, final int[] retryCodes, int retries, final long delayMillis) { lastRequest = request; Response response = null; @@ -131,5 +131,6 @@ protected boolean shouldRetry(final Response response, final int[] retryCodes) { return false; } - public abstract Response makeRequest(final Request request); + public abstract Response makeRequest(final T request); + } diff --git a/src/main/java/com/twilio/http/HttpUtility.java b/src/main/java/com/twilio/http/HttpUtility.java index d31117c24b..4e54bc55b0 100644 --- a/src/main/java/com/twilio/http/HttpUtility.java +++ b/src/main/java/com/twilio/http/HttpUtility.java @@ -6,7 +6,7 @@ import java.util.List; @UtilityClass -class HttpUtility { +public class HttpUtility { public String getUserAgentString(final List userAgentExtensions) { StringBuilder userAgentString = new StringBuilder(); userAgentString.append("twilio-java/") diff --git a/src/main/java/com/twilio/http/IRequest.java b/src/main/java/com/twilio/http/IRequest.java new file mode 100644 index 0000000000..584951f34b --- /dev/null +++ b/src/main/java/com/twilio/http/IRequest.java @@ -0,0 +1,360 @@ +package com.twilio.http; + +import com.twilio.constant.EnumConstants; +import com.twilio.exception.ApiException; +import com.twilio.exception.InvalidRequestException; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class IRequest { + private static final String DEFAULT_REGION = "us1"; + + public static final String QUERY_STRING_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + public static final String QUERY_STRING_DATE_FORMAT = "yyyy-MM-dd"; + + protected final HttpMethod method; + protected final String url; + protected final Map> queryParams; + protected final Map> postParams; + protected final Map> headerParams; + + protected String region; + protected String edge; + + private List userAgentExtensions; + + private EnumConstants.ContentType contentType; + + private String body; + + /** + * Create a new API request. + * + * @param method HTTP method + * @param url url of request + */ + public IRequest(final HttpMethod method, final String url) { + this.method = method; + this.url = url; + this.queryParams = new HashMap<>(); + this.postParams = new HashMap<>(); + this.headerParams = new HashMap<>(); + } + + /** + * Create a new API request. + * + * @param method HTTP method + * @param domain Twilio domain + * @param uri uri of request + */ + public IRequest(final HttpMethod method, final String domain, final String uri) { + this(method, domain, uri, null); + } + + /** + * Create a new API request. + * + * @param method HTTP Method + * @param domain Twilio domain + * @param uri uri of request + * @param region region to make request + */ + public IRequest(final HttpMethod method, final String domain, final String uri, final String region + ) { + this.method = method; + this.url = "https://" + domain + ".twilio.com" + uri; + this.region = region; + this.queryParams = new HashMap<>(); + this.postParams = new HashMap<>(); + this.headerParams = new HashMap<>(); + } + + public HttpMethod getMethod() { + return method; + } + + public String getUrl() { + return url; + } + + public void setRegion(final String region) { + this.region = region; + } + + public void setEdge(final String edge) { + this.edge = edge; + } + + public void setUserAgentExtensions(List userAgentExtensions) { + this.userAgentExtensions = userAgentExtensions; + } + + public List getUserAgentExtensions() { + return this.userAgentExtensions; + } + + public EnumConstants.ContentType getContentType() { + return contentType; + } + + public void setContentType(EnumConstants.ContentType contentType) { + this.contentType = contentType; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + /** + * Build the URL for the request. + * + * @return URL for the request + */ + public URL constructURL() { + String params = encodeQueryParams(); + String stringUri = buildURL(); + + if (params.length() > 0) { + stringUri += "?" + params; + } + + try { + URI uri = new URI(stringUri); + return uri.toURL(); + } catch (final URISyntaxException e) { + throw new ApiException("Bad URI: " + e.getMessage()); + } catch (final MalformedURLException e) { + throw new ApiException("Bad URL: " + e.getMessage()); + } + } + + protected String buildURL() { + try { + final URL parsedUrl = new URL(url); + String host = parsedUrl.getHost(); + final String[] pieces = host.split("\\."); + + if (pieces.length > 1) { + final String product = pieces[0]; + final String domain = joinIgnoreNull(".", pieces[pieces.length - 2], pieces[pieces.length - 1]); + + String targetRegion = region; + String targetEdge = edge; + + if (pieces.length == 4) { // product.region.twilio.com + targetRegion = targetRegion != null ? targetRegion : pieces[1]; + } else if (pieces.length == 5) { // product.edge.region.twilio.com + targetEdge = targetEdge != null ? targetEdge : pieces[1]; + targetRegion = targetRegion != null ? targetRegion : pieces[2]; + } + + if (targetEdge != null && targetRegion == null) + targetRegion = DEFAULT_REGION; + + host = joinIgnoreNull(".", product, targetEdge, targetRegion, domain); + } + + String urlPort = parsedUrl.getPort() != -1 ? ":" + parsedUrl.getPort() : null; + String protocol = parsedUrl.getProtocol() + "://"; + String[] pathPieces = parsedUrl.getPath().split("/"); + for (int i = 0; i < pathPieces.length; i++) { + pathPieces[i] = URLEncoder.encode(pathPieces[i], "UTF-8"); + } + String encodedPath = String.join("/", pathPieces); + String query = parsedUrl.getQuery() != null ? "?" + parsedUrl.getQuery() : null; + String ref = parsedUrl.getRef() != null ? "#" + parsedUrl.getRef() : null; + String credentials = parsedUrl.getUserInfo() != null ? parsedUrl.getUserInfo() + "@" : null; + return joinIgnoreNull("", protocol, credentials, host, urlPort, encodedPath, query, ref); + } catch (final MalformedURLException | UnsupportedEncodingException e) { + throw new ApiException("Bad URL: " + e.getMessage()); + } + } + + /** + * Add query parameters for date ranges. + * + * @param name name of query parameter + * @param lowerBound lower bound of LocalDate range + * @param upperBound upper bound of LocalDate range + */ + public void addQueryDateRange(final String name, LocalDate lowerBound, LocalDate upperBound) { + if (lowerBound != null) { + String value = lowerBound.toString(); + addQueryParam(name + ">", value); + } + + if (upperBound != null) { + String value = upperBound.toString(); + addQueryParam(name + "<", value); + } + } + + /** + * Add query parameters for date ranges. + * + * @param name name of query parameter + * @param lowerBound lower bound of ZonedDateTime range + * @param upperBound upper bound of ZonedDateTime range + */ + public void addQueryDateTimeRange(final String name, ZonedDateTime lowerBound, ZonedDateTime upperBound) { + if (lowerBound != null) { + String value = lowerBound.withZoneSameInstant(ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern(QUERY_STRING_DATE_TIME_FORMAT)); + addQueryParam(name + ">", value); + } + + if (upperBound != null) { + String value = upperBound.withZoneSameInstant(ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern(QUERY_STRING_DATE_TIME_FORMAT)); + addQueryParam(name + "<", value); + } + } + + /** + * Add a query parameter. + * + * @param name name of parameter + * @param value value of parameter + */ + public void addQueryParam(final String name, final String value) { + addParam(queryParams, name, value); + } + + /** + * Add a form parameter. + * + * @param name name of parameter + * @param value value of parameter + */ + public void addPostParam(final String name, final String value) { + addParam(postParams, name, value); + } + + /** + * Add a header parameter. + * + * @param name name of parameter + * @param value value of parameter + */ + public void addHeaderParam(final String name, final String value) { + addParam(headerParams, name, value); + } + + private void addParam(final Map> params, final String name, final String value) { + if (value == null || value.equals("null")) + return; + + if (!params.containsKey(name)) { + params.put(name, new ArrayList()); + } + + params.get(name).add(value); + } + + /** + * Encode the form body. + * + * @return url encoded form body + */ + public String encodeFormBody() { + return encodeParameters(postParams); + } + + /** + * Encode the query parameters. + * + * @return url encoded query parameters + */ + public String encodeQueryParams() { + return encodeParameters(queryParams); + } + + private static String encodeParameters(final Map> params) { + List parameters = new ArrayList<>(); + + for (final Map.Entry> entry : params.entrySet()) { + try { + String encodedName = URLEncoder.encode(entry.getKey(), "UTF-8"); + for (final String value : entry.getValue()) { + if (value == null) { + continue; + } + + String encodedValue = URLEncoder.encode(value, "UTF-8"); + parameters.add(encodedName + "=" + encodedValue); + } + } catch (final UnsupportedEncodingException e) { + throw new InvalidRequestException("Couldn't encode params", entry.getKey(), e); + } + } + return joinIgnoreNull("&", parameters); + } + + private static String joinIgnoreNull(final String separator, final String... items) { + return joinIgnoreNull(separator, Arrays.asList(items)); + } + + private static String joinIgnoreNull(final String separator, final List items) { + final StringBuilder builder = new StringBuilder(); + + for (final String item : items) { + if (item != null) { + if (builder.length() > 0) { + builder.append(separator); + } + + builder.append(item); + } + } + + return builder.toString(); + } + + public Map> getQueryParams() { + return queryParams; + } + + public Map> getPostParams() { + return postParams; + } + + public Map> getHeaderParams() { + return headerParams; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Request other = (Request) o; + return Objects.equals(this.method, other.method) && + Objects.equals(this.buildURL(), other.buildURL()) && + Objects.equals(this.queryParams, other.queryParams) && + Objects.equals(this.postParams, other.postParams) && + Objects.equals(this.headerParams, other.headerParams); + } +} diff --git a/src/main/java/com/twilio/http/NetworkHttpClient.java b/src/main/java/com/twilio/http/NetworkHttpClient.java index 39bb3c7ac0..1abfc1e981 100644 --- a/src/main/java/com/twilio/http/NetworkHttpClient.java +++ b/src/main/java/com/twilio/http/NetworkHttpClient.java @@ -3,14 +3,7 @@ import com.twilio.Twilio; import com.twilio.constant.EnumConstants; import com.twilio.exception.ApiException; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - +import com.twilio.http.bearertoken.BearerTokenRequest; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; @@ -26,6 +19,13 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + public class NetworkHttpClient extends HttpClient { protected final org.apache.http.client.HttpClient client; @@ -57,7 +57,8 @@ public NetworkHttpClient(final RequestConfig requestConfig) { public NetworkHttpClient(final RequestConfig requestConfig, final SocketConfig socketConfig) { Collection headers = Arrays.asList( new BasicHeader("X-Twilio-Client", "java-" + Twilio.VERSION), - new BasicHeader(HttpHeaders.ACCEPT, "application/json"), + // new BasicHeader(HttpHeaders.ACCEPT, "application/json"), + //new BasicHeader(HttpHeaders.ACCEPT, "application/scim+json"), new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "utf-8") ); @@ -113,16 +114,24 @@ public NetworkHttpClient(HttpClientBuilder clientBuilder) { * @param request request to make * @return Response of the HTTP request */ - public Response makeRequest(final Request request) { + public Response makeRequest(final T request) { HttpMethod method = request.getMethod(); RequestBuilder builder = RequestBuilder.create(method.toString()) .setUri(request.constructURL().toString()) .setVersion(HttpVersion.HTTP_1_1) .setCharset(StandardCharsets.UTF_8); - - if (request.requiresAuthentication()) { - builder.addHeader(HttpHeaders.AUTHORIZATION, request.getAuthString()); + if (request instanceof Request) { + Request basicRequest = (Request) request; + // builder.setHeader(HttpHeaders.AUTHORIZATION, basicRequest.getAuthString(accessToken)); + if (basicRequest.requiresAuthentication()) { + builder.addHeader(HttpHeaders.AUTHORIZATION, basicRequest.getAuthString()); + } + } else if (request instanceof BearerTokenRequest) { + BearerTokenRequest bearerTokenRequest = (BearerTokenRequest) request; + if (bearerTokenRequest.requiresAuthentication()) { + builder.addHeader(HttpHeaders.AUTHORIZATION, bearerTokenRequest.getAuthString()); + } } for (Map.Entry> entry : request.getHeaderParams().entrySet()) { @@ -131,7 +140,9 @@ public Response makeRequest(final Request request) { } } - if (method == HttpMethod.POST || method == HttpMethod.PUT) { + if (method != HttpMethod.GET) { + // TODO: It will be removed after one RC Release. + if (request.getContentType() == null) request.setContentType(EnumConstants.ContentType.FORM_URLENCODED); if (EnumConstants.ContentType.JSON.getValue().equals(request.getContentType().getValue())) { HttpEntity entity = new StringEntity(request.getBody(), ContentType.APPLICATION_JSON); builder.setEntity(entity); diff --git a/src/main/java/com/twilio/http/Request.java b/src/main/java/com/twilio/http/Request.java index 36aa7a5e03..7c24765ee1 100644 --- a/src/main/java/com/twilio/http/Request.java +++ b/src/main/java/com/twilio/http/Request.java @@ -1,47 +1,17 @@ package com.twilio.http; -import com.twilio.constant.EnumConstants; - -import com.twilio.exception.ApiException; -import com.twilio.exception.InvalidRequestException; - -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; - -public class Request { +import java.util.Base64; +import java.util.Objects; +public class Request extends IRequest { private static final String DEFAULT_REGION = "us1"; public static final String QUERY_STRING_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; public static final String QUERY_STRING_DATE_FORMAT = "yyyy-MM-dd"; - - private final HttpMethod method; - private final String url; - private final Map> queryParams; - private final Map> postParams; - private final Map> headerParams; - - private String region; - private String edge; private String username; private String password; - private List userAgentExtensions; - - private EnumConstants.ContentType contentType; - - private String body; - /** * Create a new API request. * @@ -49,11 +19,7 @@ public class Request { * @param url url of request */ public Request(final HttpMethod method, final String url) { - this.method = method; - this.url = url; - this.queryParams = new HashMap<>(); - this.postParams = new HashMap<>(); - this.headerParams = new HashMap<>(); + super(method, url); } /** @@ -75,26 +41,8 @@ public Request(final HttpMethod method, final String domain, final String uri) { * @param uri uri of request * @param region region to make request */ - public Request( - final HttpMethod method, - final String domain, - final String uri, - final String region - ) { - this.method = method; - this.url = "https://" + domain + ".twilio.com" + uri; - this.region = region; - this.queryParams = new HashMap<>(); - this.postParams = new HashMap<>(); - this.headerParams = new HashMap<>(); - } - - public HttpMethod getMethod() { - return method; - } - - public String getUrl() { - return url; + public Request(final HttpMethod method, final String domain, final String uri, final String region) { + super(method, domain, uri, region); } public void setAuth(final String username, final String password) { @@ -102,38 +50,6 @@ public void setAuth(final String username, final String password) { this.password = password; } - public void setRegion(final String region) { - this.region = region; - } - - public void setEdge(final String edge) { - this.edge = edge; - } - - public void setUserAgentExtensions(List userAgentExtensions) { - this.userAgentExtensions = userAgentExtensions; - } - - public List getUserAgentExtensions() { - return this.userAgentExtensions; - } - - public EnumConstants.ContentType getContentType() { - return contentType; - } - - public void setContentType(EnumConstants.ContentType contentType) { - this.contentType = contentType; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - /** * Create auth string from username and password. * @@ -157,219 +73,6 @@ public boolean requiresAuthentication() { return username != null || password != null; } - /** - * Build the URL for the request. - * - * @return URL for the request - */ - public URL constructURL() { - String params = encodeQueryParams(); - String stringUri = buildURL(); - - if (params.length() > 0) { - stringUri += "?" + params; - } - - try { - URI uri = new URI(stringUri); - return uri.toURL(); - } catch (final URISyntaxException e) { - throw new ApiException("Bad URI: " + e.getMessage()); - } catch (final MalformedURLException e) { - throw new ApiException("Bad URL: " + e.getMessage()); - } - } - - private String buildURL() { - try { - final URL parsedUrl = new URL(url); - String host = parsedUrl.getHost(); - final String[] pieces = host.split("\\."); - - if (pieces.length > 1) { - final String product = pieces[0]; - final String domain = joinIgnoreNull(".", pieces[pieces.length - 2], pieces[pieces.length - 1]); - - String targetRegion = region; - String targetEdge = edge; - - if (pieces.length == 4) { // product.region.twilio.com - targetRegion = targetRegion != null ? targetRegion : pieces[1]; - } else if (pieces.length == 5) { // product.edge.region.twilio.com - targetEdge = targetEdge != null ? targetEdge : pieces[1]; - targetRegion = targetRegion != null ? targetRegion : pieces[2]; - } - - if (targetEdge != null && targetRegion == null) - targetRegion = DEFAULT_REGION; - - host = joinIgnoreNull(".", product, targetEdge, targetRegion, domain); - } - - String urlPort = parsedUrl.getPort() != -1 ? ":" + parsedUrl.getPort() : null; - String protocol = parsedUrl.getProtocol() + "://"; - String[] pathPieces = parsedUrl.getPath().split("/"); - for (int i = 0; i < pathPieces.length; i++) { - pathPieces[i] = URLEncoder.encode(pathPieces[i], "UTF-8"); - } - String encodedPath = String.join("/", pathPieces); - String query = parsedUrl.getQuery() != null ? "?" + parsedUrl.getQuery() : null; - String ref = parsedUrl.getRef() != null ? "#" + parsedUrl.getRef() : null; - String credentials = parsedUrl.getUserInfo() != null ? parsedUrl.getUserInfo() + "@" : null; - return joinIgnoreNull("", protocol, credentials, host, urlPort, encodedPath, query, ref); - } catch (final MalformedURLException | UnsupportedEncodingException e) { - throw new ApiException("Bad URL: "+ e.getMessage()); - } - } - - /** - * Add query parameters for date ranges. - * - * @param name name of query parameter - * @param lowerBound lower bound of LocalDate range - * @param upperBound upper bound of LocalDate range - */ - public void addQueryDateRange(final String name, LocalDate lowerBound, LocalDate upperBound) { - if (lowerBound != null) { - String value = lowerBound.toString(); - addQueryParam(name + ">", value); - } - - if (upperBound != null) { - String value = upperBound.toString(); - addQueryParam(name + "<", value); - } - } - - /** - * Add query parameters for date ranges. - * - * @param name name of query parameter - * @param lowerBound lower bound of ZonedDateTime range - * @param upperBound upper bound of ZonedDateTime range - */ - public void addQueryDateTimeRange(final String name, ZonedDateTime lowerBound, ZonedDateTime upperBound) { - if (lowerBound != null) { - String value = lowerBound.withZoneSameInstant(ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern(QUERY_STRING_DATE_TIME_FORMAT)); - addQueryParam(name + ">", value); - } - - if (upperBound != null) { - String value = upperBound.withZoneSameInstant(ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern(QUERY_STRING_DATE_TIME_FORMAT)); - addQueryParam(name + "<", value); - } - } - - /** - * Add a query parameter. - * - * @param name name of parameter - * @param value value of parameter - */ - public void addQueryParam(final String name, final String value) { - addParam(queryParams, name, value); - } - - /** - * Add a form parameter. - * - * @param name name of parameter - * @param value value of parameter - */ - public void addPostParam(final String name, final String value) { - addParam(postParams, name, value); - } - - /** - * Add a header parameter. - * - * @param name name of parameter - * @param value value of parameter - */ - public void addHeaderParam(final String name, final String value) { - addParam(headerParams, name, value); - } - - private void addParam(final Map> params, final String name, final String value) { - if (value == null || value.equals("null")) - return; - - if (!params.containsKey(name)) { - params.put(name, new ArrayList()); - } - - params.get(name).add(value); - } - - /** - * Encode the form body. - * - * @return url encoded form body - */ - public String encodeFormBody() { - return encodeParameters(postParams); - } - - /** - * Encode the query parameters. - * - * @return url encoded query parameters - */ - public String encodeQueryParams() { - return encodeParameters(queryParams); - } - - private static String encodeParameters(final Map> params) { - List parameters = new ArrayList<>(); - - for (final Map.Entry> entry : params.entrySet()) { - try { - String encodedName = URLEncoder.encode(entry.getKey(), "UTF-8"); - for (final String value : entry.getValue()) { - if (value == null) { - continue; - } - - String encodedValue = URLEncoder.encode(value, "UTF-8"); - parameters.add(encodedName + "=" + encodedValue); - } - } catch (final UnsupportedEncodingException e) { - throw new InvalidRequestException("Couldn't encode params", entry.getKey(), e); - } - } - return joinIgnoreNull("&", parameters); - } - - private static String joinIgnoreNull(final String separator, final String... items) { - return joinIgnoreNull(separator, Arrays.asList(items)); - } - - private static String joinIgnoreNull(final String separator, final List items) { - final StringBuilder builder = new StringBuilder(); - - for (final String item : items) { - if (item != null) { - if (builder.length() > 0) { - builder.append(separator); - } - - builder.append(item); - } - } - - return builder.toString(); - } - - public Map> getQueryParams() { - return queryParams; - } - - public Map> getPostParams() { - return postParams; - } - - public Map> getHeaderParams() { return headerParams; } - @Override public boolean equals(Object o) { if (this == o) { @@ -382,7 +85,7 @@ public boolean equals(Object o) { Request other = (Request) o; return Objects.equals(this.method, other.method) && - Objects.equals(this.buildURL(), other.buildURL()) && + Objects.equals(this.buildURL(), this.buildURL()) && Objects.equals(this.username, other.username) && Objects.equals(this.password, other.password) && Objects.equals(this.queryParams, other.queryParams) && diff --git a/src/main/java/com/twilio/http/ValidationClient.java b/src/main/java/com/twilio/http/ValidationClient.java index 38d48fde26..6473c1bff1 100644 --- a/src/main/java/com/twilio/http/ValidationClient.java +++ b/src/main/java/com/twilio/http/ValidationClient.java @@ -3,6 +3,7 @@ import com.twilio.Twilio; import com.twilio.constant.EnumConstants; import com.twilio.exception.ApiException; +import io.jsonwebtoken.SignatureAlgorithm; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; @@ -23,11 +24,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; - -import io.jsonwebtoken.SignatureAlgorithm; -import static io.jsonwebtoken.SignatureAlgorithm.PS256; import static io.jsonwebtoken.SignatureAlgorithm.RS256; public class ValidationClient extends HttpClient { @@ -167,7 +164,8 @@ public ValidationClient(final String accountSid, } @Override - public Response makeRequest(Request request) { + public Response makeRequest(IRequest iRequest) { + Request request = (Request)iRequest; RequestBuilder builder = RequestBuilder.create(request.getMethod().toString()) .setUri(request.constructURL().toString()) .setVersion(HttpVersion.HTTP_1_1) @@ -178,7 +176,9 @@ public Response makeRequest(Request request) { } HttpMethod method = request.getMethod(); - if (method == HttpMethod.POST || method == HttpMethod.PUT) { + if (method != HttpMethod.GET) { + // TODO: It will be removed after one RC Release. + if (request.getContentType() == null) request.setContentType(EnumConstants.ContentType.FORM_URLENCODED); if (EnumConstants.ContentType.JSON.getValue().equals(request.getContentType().getValue())) { HttpEntity entity = new StringEntity(request.getBody(), ContentType.APPLICATION_JSON); builder.setEntity(entity); diff --git a/src/main/java/com/twilio/http/ValidationInterceptor.java b/src/main/java/com/twilio/http/ValidationInterceptor.java index c3c391e84c..8e3cfcd441 100644 --- a/src/main/java/com/twilio/http/ValidationInterceptor.java +++ b/src/main/java/com/twilio/http/ValidationInterceptor.java @@ -2,6 +2,7 @@ import com.twilio.jwt.Jwt; import com.twilio.jwt.validation.ValidationToken; +import io.jsonwebtoken.SignatureAlgorithm; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; @@ -12,8 +13,6 @@ import java.util.Arrays; import java.util.List; -import io.jsonwebtoken.SignatureAlgorithm; - public class ValidationInterceptor implements HttpRequestInterceptor { private static final List HEADERS = Arrays.asList("authorization", "host"); diff --git a/src/main/java/com/twilio/http/bearertoken/BearerTokenRequest.java b/src/main/java/com/twilio/http/bearertoken/BearerTokenRequest.java new file mode 100644 index 0000000000..83976c2e45 --- /dev/null +++ b/src/main/java/com/twilio/http/bearertoken/BearerTokenRequest.java @@ -0,0 +1,38 @@ +package com.twilio.http.bearertoken; + +import com.twilio.http.HttpMethod; +import com.twilio.http.IRequest; + +public class BearerTokenRequest extends IRequest { + + private String accessToken; + + public BearerTokenRequest(HttpMethod method, String url) { + super(method, url); + } + + public BearerTokenRequest(HttpMethod method, String domain, String uri) { + super(method, domain, uri); + } + + public BearerTokenRequest(HttpMethod method, String domain, String uri, String region) { + super(method, domain, uri, region); + } + + /** + * Create auth string from accessToken. + * + * @return basic authentication string + */ + public String getAuthString() { + return "Bearer " + accessToken; + } + + public boolean requiresAuthentication() { + return accessToken != null; + } + + public void setAuth(String accessToken) { + this.accessToken = accessToken; + } +} diff --git a/src/main/java/com/twilio/http/bearertoken/BearerTokenTwilioRestClient.java b/src/main/java/com/twilio/http/bearertoken/BearerTokenTwilioRestClient.java new file mode 100644 index 0000000000..01b59096a6 --- /dev/null +++ b/src/main/java/com/twilio/http/bearertoken/BearerTokenTwilioRestClient.java @@ -0,0 +1,183 @@ +package com.twilio.http.bearertoken; + +import com.auth0.jwt.JWT; +import com.twilio.http.bearertoken.TokenManager; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.twilio.exception.AuthenticationException; +import com.twilio.http.HttpClient; +import com.twilio.http.NetworkHttpClient; +import com.twilio.http.Response; +import lombok.Getter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + + +/* + * Use this BearerToken Rest Client if no authentication is involved in an API. + */ +public class BearerTokenTwilioRestClient { + public static final int HTTP_STATUS_CODE_CREATED = 201; + public static final int HTTP_STATUS_CODE_NO_CONTENT = 204; + public static final int HTTP_STATUS_CODE_OK = 200; + public static final Predicate SUCCESS = i -> i != null && i >= 200 && i < 400; + @Getter + private final ObjectMapper objectMapper; + private String accessToken; + @Getter + private final String region; + @Getter + private final String edge; + @Getter + private final HttpClient httpClient; + @Getter + private final List userAgentExtensions; + @Setter + private final TokenManager tokenManager; + private static final Logger logger = LoggerFactory.getLogger(BearerTokenTwilioRestClient.class); + + private BearerTokenTwilioRestClient(BearerTokenTwilioRestClient.Builder b) { + this.region = b.region; + this.edge = b.edge; + this.httpClient = b.httpClient; + this.objectMapper = new ObjectMapper(); + this.userAgentExtensions = b.userAgentExtensions; + this.tokenManager = b.tokenManager; + + // This module configures the ObjectMapper to use + // public API methods for manipulating java.time.* + // classes. The alternative is to use reflection which + // generates warnings from the module system on Java 9+ + objectMapper.registerModule(new JavaTimeModule()); + } + + public static class Builder { + private String region; + private String edge; + private HttpClient httpClient; + private List userAgentExtensions; + private TokenManager tokenManager; + + public Builder() { + this.region = System.getenv("TWILIO_REGION"); + this.edge = System.getenv("TWILIO_EDGE"); + userAgentExtensions = new ArrayList<>(); + } + + public BearerTokenTwilioRestClient.Builder region(final String region) { + this.region = region; + return this; + } + + public BearerTokenTwilioRestClient.Builder edge(final String edge) { + this.edge = edge; + return this; + } + + public BearerTokenTwilioRestClient.Builder tokenManager(final TokenManager tokenManager) { + this.tokenManager = tokenManager; + return this; + } + + public BearerTokenTwilioRestClient.Builder httpClient(final HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public BearerTokenTwilioRestClient.Builder userAgentExtensions(final List userAgentExtensions) { + if (userAgentExtensions != null && !userAgentExtensions.isEmpty()) { + this.userAgentExtensions = new ArrayList<>(userAgentExtensions); + } + return this; + } + + public BearerTokenTwilioRestClient build() { + if (this.httpClient == null) { + this.httpClient = new NetworkHttpClient(); + } + return new BearerTokenTwilioRestClient(this); + } + } + public Response request(BearerTokenRequest request) { + + if (accessToken == null || accessToken.isEmpty() || isTokenExpired(this.accessToken)) { + synchronized (BearerTokenTwilioRestClient.class){ + if (accessToken == null || accessToken.isEmpty() || isTokenExpired(this.accessToken)) { + this.accessToken = tokenManager.fetchAccessToken(); + } + } + } + + request.setAuth(accessToken); + if (region != null) + request.setRegion(region); + if (edge != null) + request.setEdge(edge); + + if (userAgentExtensions != null && !userAgentExtensions.isEmpty()) { + request.setUserAgentExtensions(userAgentExtensions); + } + logRequest(request); + Response response = httpClient.reliableRequest(request); + if(response != null) { + int statusCode = response.getStatusCode(); + if (statusCode == 401) { + this.accessToken = tokenManager.fetchAccessToken(); + request.setAuth(accessToken); + response = httpClient.reliableRequest(request); + } + + if (logger.isDebugEnabled()) { + logger.debug("status code: {}", statusCode); + org.apache.http.Header[] responseHeaders = response.getHeaders(); + logger.debug("response headers:"); + for (int i = 0; i < responseHeaders.length; i++) { + logger.debug("responseHeader: {}", responseHeaders[i]); + } + } + } + + return response; + } + + public boolean isTokenExpired(String token) { + DecodedJWT jwt = JWT.decode(token); + Date expiresAt = jwt.getExpiresAt(); + // Add a buffer of 30 seconds + long bufferMilliseconds = 30 * 1000; + Date bufferExpiresAt = new Date(expiresAt.getTime() - bufferMilliseconds); + return bufferExpiresAt.before(new Date()); + } + + public void logRequest(final BearerTokenRequest request) { + if (logger.isDebugEnabled()) { + logger.debug("-- BEGIN Twilio API BearerTokenRequest --"); + logger.debug("request method: " + request.getMethod()); + logger.debug("request URL: " + request.constructURL().toString()); + final Map> queryParams = request.getQueryParams(); + final Map> headerParams = request.getHeaderParams(); + + if (queryParams != null && !queryParams.isEmpty()) { + logger.debug("query parameters: " + queryParams); + } + + if (headerParams != null && !headerParams.isEmpty()) { + logger.debug("header parameters: "); + for (String key : headerParams.keySet()) { + if (!key.toLowerCase().contains("authorization")) { + logger.debug(key + ": " + headerParams.get(key)); + } + } + } + logger.debug("-- END Twilio API BearerTokenRequest --"); + } + } +} diff --git a/src/main/java/com/twilio/http/bearertoken/OrgsTokenManager.java b/src/main/java/com/twilio/http/bearertoken/OrgsTokenManager.java new file mode 100644 index 0000000000..211cccce0c --- /dev/null +++ b/src/main/java/com/twilio/http/bearertoken/OrgsTokenManager.java @@ -0,0 +1,54 @@ +package com.twilio.http.bearertoken; + +import lombok.Setter; +import com.twilio.exception.ApiException; +import lombok.RequiredArgsConstructor; +import com.twilio.rest.previewiam.organizations.Token; +import com.twilio.rest.previewiam.organizations.TokenCreator; +public class OrgsTokenManager implements TokenManager{ + + private final String grantType; + private final String clientId; + private final String clientSecret; + private String code; + private String redirectUri; + private String audience; + private String refreshToken; + private String scope; + + public OrgsTokenManager(String grantType, String clientId, String clientSecret){ + this.grantType = grantType; + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + public OrgsTokenManager(String grantType, String clientId, String clientSecret, String code, String redirectUri, String audience, String refreshToken, String scope){ + this.grantType = grantType; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.code = code; + this.redirectUri = redirectUri; + this.audience = audience; + this.refreshToken = refreshToken; + this.scope = scope; + } + + public synchronized String fetchAccessToken(){ + TokenCreator tokenCreator = Token.creator(grantType, clientId).setClientSecret(clientSecret); + if (this.code != null) tokenCreator.setCode(code); + if (this.redirectUri != null) tokenCreator.setRedirectUri(redirectUri); + if (this.audience != null) tokenCreator.setAudience(audience); + if (this.refreshToken != null) tokenCreator.setRefreshToken(refreshToken); + if (this.scope != null) tokenCreator.setScope(scope); + Token token; + try { + token = tokenCreator.create(); + if(token == null || token.getAccessToken() == null){ + throw new ApiException("Token creation failed"); + } + } catch(Exception e){ + throw new ApiException("Token creation failed"); + } + return token.getAccessToken(); + } +} \ No newline at end of file diff --git a/src/main/java/com/twilio/http/bearertoken/TokenManager.java b/src/main/java/com/twilio/http/bearertoken/TokenManager.java new file mode 100644 index 0000000000..6dabdcd17d --- /dev/null +++ b/src/main/java/com/twilio/http/bearertoken/TokenManager.java @@ -0,0 +1,5 @@ +package com.twilio.http.bearertoken; + +public interface TokenManager { + public String fetchAccessToken(); +} \ No newline at end of file diff --git a/src/main/java/com/twilio/http/noauth/NoAuthHttpClient.java b/src/main/java/com/twilio/http/noauth/NoAuthHttpClient.java new file mode 100644 index 0000000000..209f1de50a --- /dev/null +++ b/src/main/java/com/twilio/http/noauth/NoAuthHttpClient.java @@ -0,0 +1,134 @@ +package com.twilio.http.noauth; + +import com.twilio.Twilio; +import com.twilio.constant.EnumConstants; +import com.twilio.exception.ApiException; +import com.twilio.http.HttpClient; +import com.twilio.http.HttpMethod; +import com.twilio.http.HttpUtility; +import com.twilio.http.IRequest; +import com.twilio.http.Response; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.config.SocketConfig; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeader; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class NoAuthHttpClient extends HttpClient { + protected final org.apache.http.client.HttpClient client; + public NoAuthHttpClient() { + this(DEFAULT_REQUEST_CONFIG); + } + + public NoAuthHttpClient(final RequestConfig requestConfig) { + this(requestConfig, DEFAULT_SOCKET_CONFIG); + } + + public NoAuthHttpClient(final RequestConfig requestConfig, final SocketConfig socketConfig) { + Collection headers = Arrays.asList( + new BasicHeader("X-Twilio-Client", "java-" + Twilio.VERSION), + new BasicHeader(HttpHeaders.ACCEPT, "application/json"), + new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "utf-8") + ); + + String googleAppEngineVersion = System.getProperty("com.google.appengine.runtime.version"); + boolean isGoogleAppEngine = googleAppEngineVersion != null && !googleAppEngineVersion.isEmpty(); + + org.apache.http.impl.client.HttpClientBuilder clientBuilder = HttpClientBuilder.create(); + + if (!isGoogleAppEngine) { + clientBuilder.useSystemProperties(); + } + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setDefaultSocketConfig(socketConfig); + /* + * Example: Lets say client has one server. + * There are 4 servers on edge handling client request. + * Each request takes on an average 500ms (2 request per second) + * Total number request can be server in a second from a route: 20 * 4 * 2 (DefaultMaxPerRoute * edge servers * request per second) + */ + connectionManager.setDefaultMaxPerRoute(20); + connectionManager.setMaxTotal(100); + + client = clientBuilder + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(requestConfig) + .setDefaultHeaders(headers) + .setRedirectStrategy(this.getRedirectStrategy()) + .build(); + } + + @Override + public Response makeRequest(IRequest request) { + HttpMethod method = request.getMethod(); + RequestBuilder builder = RequestBuilder.create(method.toString()) + .setUri(request.constructURL().toString()) + .setVersion(HttpVersion.HTTP_1_1) + .setCharset(StandardCharsets.UTF_8); + + for (Map.Entry> entry : request.getHeaderParams().entrySet()) { + for (String value : entry.getValue()) { + builder.addHeader(entry.getKey(), value); + } + } + + if (method != HttpMethod.GET) { + // TODO: It will be removed after one RC Release. + if (request.getContentType() == null) request.setContentType(EnumConstants.ContentType.FORM_URLENCODED); + if (EnumConstants.ContentType.JSON.getValue().equals(request.getContentType().getValue())) { + HttpEntity entity = new StringEntity(request.getBody(), ContentType.APPLICATION_JSON); + builder.setEntity(entity); + builder.addHeader( + HttpHeaders.CONTENT_TYPE, EnumConstants.ContentType.JSON.getValue()); + } else { + builder.addHeader( + HttpHeaders.CONTENT_TYPE, EnumConstants.ContentType.FORM_URLENCODED.getValue()); + for (Map.Entry> entry : request.getPostParams().entrySet()) { + for (String value : entry.getValue()) { + builder.addParameter(entry.getKey(), value); + } + } + } + + } + builder.addHeader(HttpHeaders.USER_AGENT, HttpUtility.getUserAgentString(request.getUserAgentExtensions())); + + HttpResponse response = null; + + try { + response = client.execute(builder.build()); + HttpEntity entity = response.getEntity(); + return new Response( + // Consume the entire HTTP response before returning the stream + entity == null ? null : new BufferedHttpEntity(entity).getContent(), + response.getStatusLine().getStatusCode(), + response.getAllHeaders() + ); + } catch (IOException e) { + throw new ApiException(e.getMessage(), e); + } finally { + + // Ensure this response is properly closed + HttpClientUtils.closeQuietly(response); + + } + + } +} diff --git a/src/main/java/com/twilio/http/noauth/NoAuthRequest.java b/src/main/java/com/twilio/http/noauth/NoAuthRequest.java new file mode 100644 index 0000000000..00cc7bf140 --- /dev/null +++ b/src/main/java/com/twilio/http/noauth/NoAuthRequest.java @@ -0,0 +1,19 @@ +package com.twilio.http.noauth; + +import com.twilio.http.HttpMethod; +import com.twilio.http.IRequest; + +public class NoAuthRequest extends IRequest { + + public NoAuthRequest(HttpMethod method, String url) { + super(method, url); + } + + public NoAuthRequest(HttpMethod method, String domain, String uri) { + super(method, domain, uri, null); + } + + public NoAuthRequest(HttpMethod method, String domain, String uri, String region) { + super(method, domain, uri, region); + } +} diff --git a/src/main/java/com/twilio/http/noauth/NoAuthTwilioRestClient.java b/src/main/java/com/twilio/http/noauth/NoAuthTwilioRestClient.java new file mode 100644 index 0000000000..89d1e28805 --- /dev/null +++ b/src/main/java/com/twilio/http/noauth/NoAuthTwilioRestClient.java @@ -0,0 +1,140 @@ +package com.twilio.http.noauth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.twilio.http.HttpClient; +import com.twilio.http.NetworkHttpClient; +import com.twilio.http.Response; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + + +/* + * Use this NoAuth Rest Client if no authentication is involved in an API. + */ +public class NoAuthTwilioRestClient { + @Getter + private final ObjectMapper objectMapper; + + @Getter + private final String region; + @Getter + private final String edge; + @Getter + private final HttpClient httpClient; + @Getter + private final List userAgentExtensions; + private static final Logger logger = LoggerFactory.getLogger(NoAuthTwilioRestClient.class); + + public static final Predicate SUCCESS = i -> i != null && i >= 200 && i < 400; + + private NoAuthTwilioRestClient(NoAuthTwilioRestClient.Builder b) { + this.region = b.region; + this.edge = b.edge; + this.httpClient = b.httpClient; + this.objectMapper = new ObjectMapper(); + this.userAgentExtensions = b.userAgentExtensions; + + // This module configures the ObjectMapper to use + // public API methods for manipulating java.time.* + // classes. The alternative is to use reflection which + // generates warnings from the module system on Java 9+ + objectMapper.registerModule(new JavaTimeModule()); + } + + public static class Builder { + private String region; + private String edge; + private HttpClient httpClient; + private List userAgentExtensions; + + public Builder() { + this.region = System.getenv("TWILIO_REGION"); + this.edge = System.getenv("TWILIO_EDGE"); + userAgentExtensions = new ArrayList<>(); + } + + public NoAuthTwilioRestClient.Builder region(final String region) { + this.region = region; + return this; + } + + public NoAuthTwilioRestClient.Builder edge(final String edge) { + this.edge = edge; + return this; + } + + public NoAuthTwilioRestClient.Builder httpClient(final HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public NoAuthTwilioRestClient.Builder userAgentExtensions(final List userAgentExtensions) { + if (userAgentExtensions != null && !userAgentExtensions.isEmpty()) { + this.userAgentExtensions = new ArrayList<>(userAgentExtensions); + } + return this; + } + + public NoAuthTwilioRestClient build() { + if (this.httpClient == null) { + this.httpClient = new NetworkHttpClient(); + } + return new NoAuthTwilioRestClient(this); + } + } + public Response request(NoAuthRequest request) { + if (region != null) + request.setRegion(region); + if (edge != null) + request.setEdge(edge); + + if (userAgentExtensions != null && !userAgentExtensions.isEmpty()) { + request.setUserAgentExtensions(userAgentExtensions); + } + logRequest(request); + Response response = httpClient.reliableRequest(request); + + if (logger.isDebugEnabled()) { + logger.debug("status code: {}", response.getStatusCode()); + org.apache.http.Header[] responseHeaders = response.getHeaders(); + logger.debug("response headers:"); + for (int i = 0; i < responseHeaders.length; i++) { + logger.debug("responseHeader: {}", responseHeaders[i]); + } + } + + return response; + } + + public void logRequest(final NoAuthRequest request) { + if (logger.isDebugEnabled()) { + logger.debug("-- BEGIN Twilio API NoAuthRequest --"); + logger.debug("request method: " + request.getMethod()); + logger.debug("request URL: " + request.constructURL().toString()); + final Map> queryParams = request.getQueryParams(); + final Map> headerParams = request.getHeaderParams(); + + if (queryParams != null && !queryParams.isEmpty()) { + logger.debug("query parameters: " + queryParams); + } + + if (headerParams != null && !headerParams.isEmpty()) { + logger.debug("header parameters: "); + for (String key : headerParams.keySet()) { + if (!key.toLowerCase().contains("authorization")) { + logger.debug(key + ": " + headerParams.get(key)); + } + } + } + + logger.debug("-- END Twilio API NoAuthRequest --"); + } + } +} diff --git a/src/main/java/com/twilio/rest/previewiam/organizations/Token.java b/src/main/java/com/twilio/rest/previewiam/organizations/Token.java new file mode 100644 index 0000000000..5e44c46024 --- /dev/null +++ b/src/main/java/com/twilio/rest/previewiam/organizations/Token.java @@ -0,0 +1,154 @@ +/* + * This code was generated by + * ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + * | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + * | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + * + * Organization Public API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package com.twilio.rest.previewiam.organizations; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.twilio.base.noauth.Resource; +import com.twilio.exception.ApiConnectionException; + +import com.twilio.exception.ApiException; + +import lombok.ToString; + +import java.io.IOException; +import java.io.InputStream; + +import java.util.Objects; + +import lombok.ToString; + + +@JsonIgnoreProperties(ignoreUnknown = true) +@ToString +public class Token extends Resource { + private static final long serialVersionUID = 258139119277894L; + + + + public static TokenCreator creator(final String grantType, final String clientId){ + return new TokenCreator(grantType, clientId); + } + + /** + * Converts a JSON String into a Token object using the provided ObjectMapper. + * + * @param json Raw JSON String + * @param objectMapper Jackson ObjectMapper + * @return Token object represented by the provided JSON + */ + public static Token fromJson(final String json, final ObjectMapper objectMapper) { + // Convert all checked exceptions to Runtime + try { + return objectMapper.readValue(json, Token.class); + } catch (final JsonMappingException | JsonParseException e) { + throw new ApiException(e.getMessage(), e); + } catch (final IOException e) { + throw new ApiConnectionException(e.getMessage(), e); + } + } + + /** + * Converts a JSON InputStream into a Token object using the provided + * ObjectMapper. + * + * @param json Raw JSON InputStream + * @param objectMapper Jackson ObjectMapper + * @return Token object represented by the provided JSON + */ + public static Token fromJson(final InputStream json, final ObjectMapper objectMapper) { + // Convert all checked exceptions to Runtime + try { + return objectMapper.readValue(json, Token.class); + } catch (final JsonMappingException | JsonParseException e) { + throw new ApiException(e.getMessage(), e); + } catch (final IOException e) { + throw new ApiConnectionException(e.getMessage(), e); + } + } + + private final String accessToken; + private final String refreshToken; + private final String idToken; + private final String tokenType; + private final Long expiresIn; + + @JsonCreator + private Token( + @JsonProperty("access_token") + final String accessToken, + + @JsonProperty("refresh_token") + final String refreshToken, + + @JsonProperty("id_token") + final String idToken, + + @JsonProperty("token_type") + final String tokenType, + + @JsonProperty("expires_in") + final Long expiresIn + ) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.idToken = idToken; + this.tokenType = tokenType; + this.expiresIn = expiresIn; + } + + public final String getAccessToken() { + return this.accessToken; + } + public final String getRefreshToken() { + return this.refreshToken; + } + public final String getIdToken() { + return this.idToken; + } + public final String getTokenType() { + return this.tokenType; + } + public final Long getExpiresIn() { + return this.expiresIn; + } + + @Override + public boolean equals(final Object o) { + if (this==o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Token other = (Token) o; + + return Objects.equals(accessToken, other.accessToken) && Objects.equals(refreshToken, other.refreshToken) && Objects.equals(idToken, other.idToken) && Objects.equals(tokenType, other.tokenType) && Objects.equals(expiresIn, other.expiresIn) ; + } + + @Override + public int hashCode() { + return Objects.hash(accessToken, refreshToken, idToken, tokenType, expiresIn); + } + + +} + diff --git a/src/main/java/com/twilio/rest/previewiam/organizations/TokenCreator.java b/src/main/java/com/twilio/rest/previewiam/organizations/TokenCreator.java new file mode 100644 index 0000000000..cdcf90352e --- /dev/null +++ b/src/main/java/com/twilio/rest/previewiam/organizations/TokenCreator.java @@ -0,0 +1,141 @@ +/* + * This code was generated by + * ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + * | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + * | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + * + * Organization Public API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package com.twilio.rest.previewiam.organizations; + +import com.twilio.constant.EnumConstants; +import com.twilio.exception.ApiConnectionException; +import com.twilio.exception.ApiException; +import com.twilio.exception.RestException; +import com.twilio.http.HttpMethod; +import com.twilio.http.Response; +import com.twilio.rest.Domains; + + + + +import com.twilio.base.noauth.Creator; +import com.twilio.http.noauth.NoAuthRequest; +import com.twilio.http.noauth.NoAuthTwilioRestClient; + +public class TokenCreator extends Creator{ + private String grantType; + private String clientId; + private String clientSecret; + private String code; + private String redirectUri; + private String audience; + private String refreshToken; + private String scope; + + public TokenCreator(final String grantType, final String clientId) { + this.grantType = grantType; + this.clientId = clientId; + } + + public TokenCreator setGrantType(final String grantType){ + this.grantType = grantType; + return this; + } + public TokenCreator setClientId(final String clientId){ + this.clientId = clientId; + return this; + } + public TokenCreator setClientSecret(final String clientSecret){ + this.clientSecret = clientSecret; + return this; + } + public TokenCreator setCode(final String code){ + this.code = code; + return this; + } + public TokenCreator setRedirectUri(final String redirectUri){ + this.redirectUri = redirectUri; + return this; + } + public TokenCreator setAudience(final String audience){ + this.audience = audience; + return this; + } + public TokenCreator setRefreshToken(final String refreshToken){ + this.refreshToken = refreshToken; + return this; + } + public TokenCreator setScope(final String scope){ + this.scope = scope; + return this; + } + + @Override + public Token create(final NoAuthTwilioRestClient client){ + String path = "/v1/token"; + + path = path.replace("{"+"grant_type"+"}", this.grantType.toString()); + path = path.replace("{"+"client_id"+"}", this.clientId.toString()); + + NoAuthRequest request = new NoAuthRequest( + HttpMethod.POST, + Domains.PREVIEWIAM.toString(), + path + ); + request.setContentType(EnumConstants.ContentType.FORM_URLENCODED); + addPostParams(request); + Response response = client.request(request); + if (response == null) { + throw new ApiConnectionException("Token creation failed: Unable to connect to server"); + } else if (!NoAuthTwilioRestClient.SUCCESS.test(response.getStatusCode())) { + RestException restException = RestException.fromJson(response.getStream(), client.getObjectMapper()); + if (restException == null) { + throw new ApiException("Server Error, no content", response.getStatusCode()); + } + throw new ApiException(restException); + } + + return Token.fromJson(response.getStream(), client.getObjectMapper()); + } + private void addPostParams(final NoAuthRequest request) { + if (grantType != null) { + request.addPostParam("grant_type", grantType); + + } + if (clientId != null) { + request.addPostParam("client_id", clientId); + + } + if (clientSecret != null) { + request.addPostParam("client_secret", clientSecret); + + } + if (code != null) { + request.addPostParam("code", code); + + } + if (redirectUri != null) { + request.addPostParam("redirect_uri", redirectUri); + + } + if (audience != null) { + request.addPostParam("audience", audience); + + } + if (refreshToken != null) { + request.addPostParam("refresh_token", refreshToken); + + } + if (scope != null) { + request.addPostParam("scope", scope); + + } + } +}