Skip to content

Commit

Permalink
v3.0.0013
Browse files Browse the repository at this point in the history
Added more authentication methods.
  • Loading branch information
cmunden committed Jun 18, 2023
1 parent 2ecf6d0 commit d095ffb
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ out of the gate. There's nothing to change, unless you need a specific feature
implementations (ex. a different Http transport).

### Installation
Version 3.0.0011 of JEtsy is available from the Maven Central Repository [here](https://search.maven.org/search?q=g:com.notronix%20a:JEtsy)
Version 3.0.0012 of JEtsy is available from the Maven Central Repository [here](https://search.maven.org/search?q=g:com.notronix%20a:JEtsy)

<dependency>
<groupId>com.notronix</groupId>
<artifactId>JEtsy</artifactId>
<version>3.0.0011</version>
<version>3.0.0012</version>
</dependency>

### Usage (TODO: update to V3 implementation with OAuth2.0)
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group 'com.notronix'
version '3.0.0011'
version '3.0.0012'

sourceCompatibility = 1.8

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
import com.notronix.etsy.api.Resource;
import com.notronix.etsy.api.authentication.model.AccessToken;
import com.notronix.etsy.api.authentication.model.LegacyToken;
import com.notronix.etsy.api.authentication.model.OAuthConnector;
import com.notronix.etsy.api.authentication.model.RefreshToken;

public interface AuthResource<C> extends Resource
{
OAuthConnector createOAuthConnector();

PingMethod<C> createPingMethod();

GetAccessTokenMethod<C> createGetAccessTokenMethod(AppKey appKey);

RefreshTokenMethod<C> createRefreshTokenMethod(AppKey appKey, RefreshToken refreshToken);

ExchangeTokenMethod<C> createExchangeTokenMethod(AppKey appKey, LegacyToken legacyToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.notronix.etsy.api.authentication.method;

import com.notronix.etsy.api.AppKey;
import com.notronix.etsy.api.authentication.model.TokenResponse;
import com.notronix.etsy.api.common.method.Method;

public interface GetAccessTokenMethod<C> extends Method<TokenResponse, C>
{
void setAppKey(AppKey appKey);
void setRedirectURI(String redirectURI);
void setCode(String code);
void setCodeVerifier(String codeVerifier);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public enum GrantType
{
authorization_code,
refresh_token,
token_exchange
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.notronix.etsy.api.authentication.model;

import com.notronix.etsy.api.AppKey;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.notronix.etsy.api.MethodUtils.addIfProvided;
import static java.util.Objects.requireNonNull;

public interface OAuthConnector
{
String BASE_URL = "https://www.etsy.com/oauth/connect";

Function<String, String> urlEncoder = s -> {
try {
return URLEncoder.encode(requireNonNull(s), StandardCharsets.UTF_8.name())
.replace("+", "-")
.replace("/", "_")
.replace("=", "");
}
catch (UnsupportedEncodingException ex) {
throw new IllegalArgumentException("url encoding error", ex);
}
};

String getState();

String getCodeVerifier();

String getCodeChallenge();

String getConnectionURL(AppKey appKey, String redirectURL, Set<EtsyScope> scopes);

static OAuthConnector withValues(String state, String verifier, String challenge) {
return new OAuthConnector()
{
@Override
public String getState() {
return state;
}

@Override
public String getCodeVerifier() {
return verifier;
}

@Override
public String getCodeChallenge() {
return challenge;
}

@Override
public String getConnectionURL(AppKey appKey, String redirectURL, Set<EtsyScope> scopes) {
String url = addIfProvided(BASE_URL, "response_type", "code");
url = addIfProvided(url, "client_id", requireNonNull(appKey.getValue()));
url = addIfProvided(url, "redirect_uri", urlEncoder.apply(requireNonNull(redirectURL)));
url = addIfProvided(url, "scope", requireNonNull(scopes).stream().map(Enum::name)
.collect(Collectors.joining("%20")));
url = addIfProvided(url, "state", state);
url = addIfProvided(url, "code_challenge", challenge);
url = addIfProvided(url, "code_challenge_method", "S256");

return url;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
import com.notronix.etsy.api.listings.model.WhoMade;

import java.util.List;
import java.util.function.Predicate;

import static java.util.Objects.nonNull;

public interface CreateDraftListingMethod<C> extends Method<Listing, C>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,36 @@

import com.google.api.client.http.HttpContent;
import com.notronix.etsy.api.AppKey;
import com.notronix.etsy.api.authentication.method.*;
import com.notronix.etsy.api.authentication.method.AuthResource;
import com.notronix.etsy.api.authentication.method.GetAccessTokenMethod;
import com.notronix.etsy.api.authentication.model.AccessToken;
import com.notronix.etsy.api.authentication.model.LegacyToken;
import com.notronix.etsy.api.authentication.model.OAuthConnector;
import com.notronix.etsy.api.authentication.model.RefreshToken;
import org.apache.commons.codec.digest.DigestUtils;

import java.security.SecureRandom;
import java.util.Base64;

public class EtsyAuthResource implements AuthResource<HttpContent>
{
private static final int STATE_LENGTH = 10;
private static final int VERIFIER_LENGTH = 32;

@Override
public OAuthConnector createOAuthConnector() {
String state = buildRandomString(STATE_LENGTH);
String verifier = buildRandomString(VERIFIER_LENGTH);
String challenge = buildChallenge(verifier);

return OAuthConnector.withValues(state, verifier, challenge);
}

@Override
public GetAccessTokenMethod<HttpContent> createGetAccessTokenMethod(AppKey appKey) {
return new EtsyGetAccessTokenMethod().withAppKey(appKey);
}

@Override
public EtsyPingMethod createPingMethod() {
return new EtsyPingMethod();
Expand All @@ -28,4 +51,19 @@ public EtsyExchangeTokenMethod createExchangeTokenMethod(AppKey appKey, LegacyTo
public EtsyTokenScopesMethod createTokenScopesMethod(AccessToken accessToken) {
return new EtsyTokenScopesMethod().withToken(accessToken);
}

private static String buildRandomString(int length) {
byte[] bytes = new byte[length];
new SecureRandom().nextBytes(bytes);

return urlEncode(bytes);
}

private static String buildChallenge(String verifier) {
return urlEncode(DigestUtils.sha256(verifier));
}

private static String urlEncode(byte[] src) {
return Base64.getUrlEncoder().encodeToString(src).replace("=", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public String getRequestMethod() {
public HttpContent buildRequestContent(Marshaller marshaller) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("grant_type", GrantType.token_exchange.name());
parameters.put("client_id", requireNonNull(getAppKey().getValue()));
parameters.put("client_id", requireNonNull(appKey.getValue()));
parameters.put("legacy_token", requireNonNull(legacyToken.getValue()));

return new UrlEncodedContent(parameters);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.notronix.etsy.impl.authentication.method;

import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.UrlEncodedContent;
import com.notronix.etsy.api.AppKey;
import com.notronix.etsy.api.Marshaller;
import com.notronix.etsy.api.Unmarshaller;
import com.notronix.etsy.api.authentication.method.GetAccessTokenMethod;
import com.notronix.etsy.api.authentication.method.GrantType;
import com.notronix.etsy.api.authentication.model.TokenResponse;
import com.notronix.etsy.impl.AbstractEtsyMethod;
import com.notronix.etsy.impl.authentication.model.EtsyTokenResponse;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;

public class EtsyGetAccessTokenMethod extends AbstractEtsyMethod<TokenResponse>
implements GetAccessTokenMethod<HttpContent>
{
private AppKey appKey;
private String redirectURI;
private String code;
private String codeVerifier;

@Override
public EtsyTokenResponse buildResponseBody(Unmarshaller unmarshaller, String payload) {
return unmarshaller.unmarshal(payload, EtsyTokenResponse.class);
}

@Override
protected String getURI() {
return "/public/oauth/token";
}

@Override
public String getRequestMethod() {
return HttpMethods.POST;
}

@Override
public HttpContent buildRequestContent(Marshaller marshaller) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("grant_type", GrantType.authorization_code.name());
parameters.put("client_id", requireNonNull(appKey.getValue()));
parameters.put("redirect_uri", requireNonNull(redirectURI));
parameters.put("code", requireNonNull(code));
parameters.put("code_verifier", requireNonNull(codeVerifier));

return new UrlEncodedContent(parameters);
}

public AppKey getAppKey() {
return appKey;
}

@Override
public void setAppKey(AppKey appKey) {
this.appKey = appKey;
}

public EtsyGetAccessTokenMethod withAppKey(AppKey appKey) {
this.appKey = appKey;
return this;
}

public String getRedirectURI() {
return redirectURI;
}

@Override
public void setRedirectURI(String redirectURI) {
this.redirectURI = redirectURI;
}

public String getCode() {
return code;
}

@Override
public void setCode(String code) {
this.code = code;
}

public String getCodeVerifier() {
return codeVerifier;
}

@Override
public void setCodeVerifier(String codeVerifier) {
this.codeVerifier = codeVerifier;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public String getRequestMethod() {
public HttpContent buildRequestContent(Marshaller marshaller) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("grant_type", GrantType.refresh_token.name());
parameters.put("client_id", requireNonNull(getAppKey().getValue()));
parameters.put("client_id", requireNonNull(appKey.getValue()));
parameters.put("refresh_token", requireNonNull(refreshToken.getValue()));

return new UrlEncodedContent(parameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

public class InstantAdapter extends TypeAdapter<Instant>
{
private static final double ONE_THOUSAND = 1000d;

@Override
public void write(JsonWriter out, Instant value) throws IOException {
out.value(Math.floor((double) value.toEpochMilli() / 1000d));
out.value(Math.floor((double) value.toEpochMilli() / ONE_THOUSAND));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public EtsyUpdateVariationImagesMethod createUpdateVariationImagesMethod(AccessT
}

@Override
public UpdateListingMethod<HttpContent> createUpdateListingMethod(AccessToken accessToken) {
public EtsyUpdateListingMethod createUpdateListingMethod(AccessToken accessToken) {
return new EtsyUpdateListingMethod().withAccessToken(accessToken);
}
}

0 comments on commit d095ffb

Please sign in to comment.