entry : map.entrySet()) {
+ params.add(new Parameter(entry.getKey(), entry.getValue()));
+ }
}
}
- public void add(final String key, final String value) {
+ public void add(String key, String value) {
params.add(new Parameter(key, value));
}
@@ -44,9 +43,9 @@ public String appendTo(String url) {
if (queryString.equals(EMPTY_STRING)) {
return url;
} else {
- url += url.indexOf(QUERY_STRING_SEPARATOR) == -1 ? QUERY_STRING_SEPARATOR : PARAM_SEPARATOR;
- url += queryString;
- return url;
+ return url
+ + (url.indexOf(QUERY_STRING_SEPARATOR) == -1 ? QUERY_STRING_SEPARATOR : PARAM_SEPARATOR)
+ + queryString;
}
}
@@ -60,20 +59,20 @@ public String asFormUrlEncodedString() {
}
final StringBuilder builder = new StringBuilder();
- for (final Parameter p : params) {
- builder.append('&').append(p.asUrlEncodedPair());
+ for (Parameter p : params) {
+ builder.append(PARAM_SEPARATOR).append(p.asUrlEncodedPair());
}
- return builder.toString().substring(1);
+ return builder.substring(1);
}
- public void addAll(final ParameterList other) {
+ public void addAll(ParameterList other) {
params.addAll(other.getParams());
}
- public void addQuerystring(final String queryString) {
- if (queryString != null && queryString.length() > 0) {
- for (final String param : queryString.split(PARAM_SEPARATOR)) {
- final String pair[] = param.split(PAIR_SEPARATOR);
+ public void addQuerystring(String queryString) {
+ if (queryString != null && !queryString.isEmpty()) {
+ for (String param : queryString.split(PARAM_SEPARATOR)) {
+ final String[] pair = param.split(PAIR_SEPARATOR);
final String key = OAuthEncoder.decode(pair[0]);
final String value = pair.length > 1 ? OAuthEncoder.decode(pair[1]) : EMPTY_STRING;
params.add(new Parameter(key, value));
@@ -81,7 +80,7 @@ public void addQuerystring(final String queryString) {
}
}
- public boolean contains(final Parameter param) {
+ public boolean contains(Parameter param) {
return params.contains(param);
}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/model/Response.java b/scribejava-core/src/main/java/com/github/scribejava/core/model/Response.java
index d320d94e0..553c6cda5 100644
--- a/scribejava-core/src/main/java/com/github/scribejava/core/model/Response.java
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/model/Response.java
@@ -2,73 +2,78 @@
import java.io.IOException;
import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.UnknownHostException;
-import java.util.HashMap;
import java.util.Map;
-import com.github.scribejava.core.exceptions.OAuthException;
import com.github.scribejava.core.utils.StreamUtils;
+import java.io.Closeable;
-public class Response {
+/**
+ * An HTTP response.
+ *
+ *
+ * This response may contain a non-null body stream of the HttpUrlConnection. If so, this body must be closed to avoid
+ * leaking resources. Use either {@link #getBody()} or {@link #close()} to close the body.
+ */
+public class Response implements Closeable {
- private int code;
- private String message;
+ private final int code;
+ private final String message;
+ private final Map headers;
private String body;
private InputStream stream;
- private Map headers;
+ private Closeable[] closeables;
+ private boolean closed;
- public Response(final int code, final String message, final Map headers, final String body, final InputStream stream) {
+ private Response(int code, String message, Map headers) {
this.code = code;
- this.headers = headers;
- this.body = body;
this.message = message;
- this.stream = stream;
+ this.headers = headers;
}
- Response(final HttpURLConnection connection) throws IOException {
- try {
- connection.connect();
- code = connection.getResponseCode();
- message = connection.getResponseMessage();
- headers = parseHeaders(connection);
- stream = isSuccessful() ? connection.getInputStream() : connection.getErrorStream();
- } catch (UnknownHostException e) {
- throw new OAuthException("The IP address of a host could not be determined.", e);
- }
+ public Response(int code, String message, Map headers, InputStream stream,
+ Closeable... closeables) {
+ this(code, message, headers);
+ this.stream = stream;
+ this.closeables = closeables;
}
- private String parseBodyContents() {
- body = StreamUtils.getStreamContents(getStream());
- return body;
+ public Response(int code, String message, Map headers, String body) {
+ this(code, message, headers);
+ this.body = body;
}
- private Map parseHeaders(final HttpURLConnection conn) {
- final Map headers = new HashMap<>();
- for (final String key : conn.getHeaderFields().keySet()) {
- headers.put(key, conn.getHeaderFields().get(key).get(0));
+ private String parseBodyContents() throws IOException {
+ if (stream == null) {
+ return null;
}
- return headers;
+ if ("gzip".equals(getHeader("Content-Encoding"))) {
+ body = StreamUtils.getGzipStreamContents(stream);
+ } else {
+ body = StreamUtils.getStreamContents(stream);
+ }
+ return body;
}
- public final boolean isSuccessful() {
- return getCode() >= 200 && getCode() < 400;
+ public boolean isSuccessful() {
+ return code >= 200 && code < 400;
}
/**
- * Obtains the HTTP Response body
+ * Returns the response body as a string, closing the stream that backs it. Idempotent.
*
- * @return response body
+ * @return body as string
+ * @throws IOException IO Exception
*/
- public String getBody() {
+ public String getBody() throws IOException {
return body == null ? parseBodyContents() : body;
}
/**
- * Obtains the meaningful stream of the HttpUrlConnection, either inputStream or errorInputStream, depending on the status code
+ * Obtains the meaningful stream of the HttpUrlConnection, either inputStream or errorInputStream, depending on the
+ * status code
*
* @return input stream / error stream
*/
- protected InputStream getStream() {
+ public InputStream getStream() {
return stream;
}
@@ -77,12 +82,13 @@ protected InputStream getStream() {
*
* @return the status code
*/
- public final int getCode() {
+ public int getCode() {
return code;
}
/**
- * Obtains the HTTP status message. Returns null if the message can not be discerned from the response (not valid HTTP)
+ * Obtains the HTTP status message. Returns null if the message can not be discerned from the response
+ * (not valid HTTP)
*
* @return the status message
*/
@@ -106,17 +112,43 @@ public Map getHeaders() {
*
* @return header value or null.
*/
- public String getHeader(final String name) {
+ public String getHeader(String name) {
return headers.get(name);
}
@Override
public String toString() {
- return "Response{" +
- "code=" + code +
- ", message='" + message + '\'' +
- ", body='" + body + '\'' +
- ", headers=" + headers +
- '}';
+ return "Response{"
+ + "code=" + code
+ + ", message='" + message + '\''
+ + ", body='" + body + '\''
+ + ", headers=" + headers
+ + '}';
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (closed) {
+ return;
+ }
+ IOException ioException = null;
+ if (closeables != null) {
+ for (Closeable closeable : closeables) {
+ if (closeable == null) {
+ continue;
+ }
+ try {
+ closeable.close();
+ } catch (IOException ioE) {
+ if (ioException != null) {
+ ioException = ioE;
+ }
+ }
+ }
+ }
+ if (ioException != null) {
+ throw ioException;
+ }
+ closed = true;
}
}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/model/ScribeJavaConfig.java b/scribejava-core/src/main/java/com/github/scribejava/core/model/ScribeJavaConfig.java
deleted file mode 100644
index 171bbac1a..000000000
--- a/scribejava-core/src/main/java/com/github/scribejava/core/model/ScribeJavaConfig.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.github.scribejava.core.model;
-
-public abstract class ScribeJavaConfig {
-
- private static ForceTypeOfHttpRequest forceTypeOfHttpRequests = ForceTypeOfHttpRequest.NONE;
-
- public static ForceTypeOfHttpRequest getForceTypeOfHttpRequests() {
- return forceTypeOfHttpRequests;
- }
-
- public static void setForceTypeOfHttpRequests(ForceTypeOfHttpRequest forceTypeOfHttpRequests) {
- ScribeJavaConfig.forceTypeOfHttpRequests = forceTypeOfHttpRequests;
- }
-}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/model/SignatureType.java b/scribejava-core/src/main/java/com/github/scribejava/core/model/SignatureType.java
deleted file mode 100644
index 26ffe054e..000000000
--- a/scribejava-core/src/main/java/com/github/scribejava/core/model/SignatureType.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.github.scribejava.core.model;
-
-public enum SignatureType {
-
- Header,
- QueryString
-}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/model/Token.java b/scribejava-core/src/main/java/com/github/scribejava/core/model/Token.java
index 816c06902..ede417c03 100644
--- a/scribejava-core/src/main/java/com/github/scribejava/core/model/Token.java
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/model/Token.java
@@ -1,48 +1,20 @@
package com.github.scribejava.core.model;
import java.io.Serializable;
-import com.github.scribejava.core.utils.Preconditions;
/**
- * Represents an OAuth token (either request or access token) and its secret
- *
- * @author Pablo Fernandez
+ * Represents an abstract OAuth (1 and 2) token (either request or access token)
*/
-public class Token implements Serializable {
+public abstract class Token implements Serializable {
- private static final long serialVersionUID = 715000866082812683L;
+ private static final long serialVersionUID = -8409640649946468092L;
- private final String token;
- private final String secret;
private final String rawResponse;
- /**
- * Default constructor
- *
- * @param token token value. Can't be null.
- * @param secret token secret. Can't be null.
- */
- public Token(final String token, final String secret) {
- this(token, secret, null);
- }
-
- public Token(final String token, final String secret, final String rawResponse) {
- Preconditions.checkNotNull(token, "Token can't be null");
- Preconditions.checkNotNull(secret, "Secret can't be null");
-
- this.token = token;
- this.secret = secret;
+ protected Token(String rawResponse) {
this.rawResponse = rawResponse;
}
- public String getToken() {
- return token;
- }
-
- public String getSecret() {
- return secret;
- }
-
public String getRawResponse() {
if (rawResponse == null) {
throw new IllegalStateException(
@@ -53,9 +25,9 @@ public String getRawResponse() {
public String getParameter(String parameter) {
String value = null;
- for (String str : this.getRawResponse().split("&")) {
+ for (String str : rawResponse.split("&")) {
if (str.startsWith(parameter + '=')) {
- String[] part = str.split("=");
+ final String[] part = str.split("=");
if (part.length > 1) {
value = part[1].trim();
}
@@ -64,45 +36,4 @@ public String getParameter(String parameter) {
}
return value;
}
-
- @Override
- public String toString() {
- return String.format("Token[%s , %s]", token, secret);
- }
-
- /**
- * @return true if the token is empty (token = "", secret = "")
- */
- public boolean isEmpty() {
- return "".equals(this.token) && "".equals(this.secret);
- }
-
- /**
- * Factory method
- *
- * Useful for two legged OAuth.
- *
- * @return empty token (token = "", secret = "")
- */
- public static Token empty() {
- return new Token("", "");
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- final Token that = (Token) o;
- return token.equals(that.getToken()) && secret.equals(that.getSecret());
- }
-
- @Override
- public int hashCode() {
- return 31 * token.hashCode() + secret.hashCode();
- }
}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/model/Verb.java b/scribejava-core/src/main/java/com/github/scribejava/core/model/Verb.java
index 943459700..8df5fd3f3 100644
--- a/scribejava-core/src/main/java/com/github/scribejava/core/model/Verb.java
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/model/Verb.java
@@ -2,10 +2,31 @@
/**
* An enumeration containing the most common HTTP Verbs.
- *
- * @author Pablo Fernandez
*/
public enum Verb {
- GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
+ GET(false), POST(true), PUT(true), DELETE(false, true), HEAD(false), OPTIONS(false), TRACE(false), PATCH(true);
+
+ private final boolean requiresBody;
+ private final boolean permitBody;
+
+ Verb(boolean requiresBody) {
+ this(requiresBody, requiresBody);
+ }
+
+ Verb(boolean requiresBody, boolean permitBody) {
+ if (requiresBody && !permitBody) {
+ throw new IllegalArgumentException();
+ }
+ this.requiresBody = requiresBody;
+ this.permitBody = permitBody;
+ }
+
+ public boolean isRequiresBody() {
+ return requiresBody;
+ }
+
+ public boolean isPermitBody() {
+ return permitBody;
+ }
}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/model/Verifier.java b/scribejava-core/src/main/java/com/github/scribejava/core/model/Verifier.java
deleted file mode 100644
index 2f2ef21da..000000000
--- a/scribejava-core/src/main/java/com/github/scribejava/core/model/Verifier.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.github.scribejava.core.model;
-
-import com.github.scribejava.core.utils.Preconditions;
-
-/**
- * Represents an OAuth verifier code.
- *
- * @author Pablo Fernandez
- */
-public class Verifier {
-
- private final String value;
-
- /**
- * Default constructor.
- *
- * @param value verifier value
- */
- public Verifier(String value) {
- Preconditions.checkNotNull(value, "Must provide a valid string as verifier");
- this.value = value;
- }
-
- public String getValue() {
- return value;
- }
-}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/oauth/AccessTokenRequestParams.java b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/AccessTokenRequestParams.java
new file mode 100644
index 000000000..d45377a56
--- /dev/null
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/AccessTokenRequestParams.java
@@ -0,0 +1,79 @@
+package com.github.scribejava.core.oauth;
+
+import com.github.scribejava.core.builder.ScopeBuilder;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * not thread safe
+ */
+public class AccessTokenRequestParams {
+
+ private final String code;
+ private String pkceCodeVerifier;
+ private String scope;
+ private Map extraParameters;
+
+ public AccessTokenRequestParams(String code) {
+ this.code = code;
+ }
+
+ public static AccessTokenRequestParams create(String code) {
+ return new AccessTokenRequestParams(code);
+ }
+
+ public AccessTokenRequestParams pkceCodeVerifier(String pkceCodeVerifier) {
+ this.pkceCodeVerifier = pkceCodeVerifier;
+ return this;
+ }
+
+ public AccessTokenRequestParams scope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public AccessTokenRequestParams scope(ScopeBuilder scope) {
+ this.scope = scope.build();
+ return this;
+ }
+
+ public AccessTokenRequestParams addExtraParameters(Map extraParameters) {
+ if (extraParameters == null || extraParameters.isEmpty()) {
+ return this;
+ }
+ if (this.extraParameters == null) {
+ extraParameters = new HashMap<>();
+ }
+ this.extraParameters.putAll(extraParameters);
+ return this;
+ }
+
+ public AccessTokenRequestParams addExtraParameter(String name, String value) {
+ if (this.extraParameters == null) {
+ extraParameters = new HashMap<>();
+ }
+ this.extraParameters.put(name, value);
+ return this;
+ }
+
+ public AccessTokenRequestParams setExtraParameters(Map extraParameters) {
+ this.extraParameters = extraParameters;
+ return this;
+ }
+
+ public Map getExtraParameters() {
+ return extraParameters;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getPkceCodeVerifier() {
+ return pkceCodeVerifier;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/oauth/AuthorizationUrlBuilder.java b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/AuthorizationUrlBuilder.java
new file mode 100644
index 000000000..fd02f60dd
--- /dev/null
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/AuthorizationUrlBuilder.java
@@ -0,0 +1,61 @@
+package com.github.scribejava.core.oauth;
+
+import com.github.scribejava.core.pkce.PKCE;
+import com.github.scribejava.core.pkce.PKCEService;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AuthorizationUrlBuilder {
+
+ private final OAuth20Service oauth20Service;
+
+ private String state;
+ private Map additionalParams;
+ private PKCE pkce;
+ private String scope;
+
+ public AuthorizationUrlBuilder(OAuth20Service oauth20Service) {
+ this.oauth20Service = oauth20Service;
+ }
+
+ public AuthorizationUrlBuilder state(String state) {
+ this.state = state;
+ return this;
+ }
+
+ public AuthorizationUrlBuilder additionalParams(Map additionalParams) {
+ this.additionalParams = additionalParams;
+ return this;
+ }
+
+ public AuthorizationUrlBuilder pkce(PKCE pkce) {
+ this.pkce = pkce;
+ return this;
+ }
+
+ public AuthorizationUrlBuilder initPKCE() {
+ this.pkce = PKCEService.defaultInstance().generatePKCE();
+ return this;
+ }
+
+ public AuthorizationUrlBuilder scope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ public PKCE getPkce() {
+ return pkce;
+ }
+
+ public String build() {
+ final Map params;
+ if (pkce == null) {
+ params = additionalParams;
+ } else {
+ params = additionalParams == null ? new HashMap() : new HashMap<>(additionalParams);
+ params.putAll(pkce.getAuthorizationUrlParams());
+ }
+ return oauth20Service.getApi().getAuthorizationUrl(oauth20Service.getResponseType(), oauth20Service.getApiKey(),
+ oauth20Service.getCallback(), scope == null ? oauth20Service.getDefaultScope() : scope, state, params);
+ }
+}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aService.java b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aService.java
new file mode 100644
index 000000000..f48308b2e
--- /dev/null
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aService.java
@@ -0,0 +1,220 @@
+package com.github.scribejava.core.oauth;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import com.github.scribejava.core.builder.api.DefaultApi10a;
+import com.github.scribejava.core.builder.api.OAuth1SignatureType;
+import com.github.scribejava.core.httpclient.HttpClient;
+import com.github.scribejava.core.httpclient.HttpClientConfig;
+import com.github.scribejava.core.model.OAuth1AccessToken;
+import com.github.scribejava.core.model.OAuth1RequestToken;
+import com.github.scribejava.core.model.OAuthAsyncRequestCallback;
+import com.github.scribejava.core.model.OAuthConstants;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * OAuth 1.0a implementation of {@link OAuthService}
+ */
+public class OAuth10aService extends OAuthService {
+
+ private static final String VERSION = "1.0";
+ private final DefaultApi10a api;
+ private final String scope;
+
+ public OAuth10aService(DefaultApi10a api, String apiKey, String apiSecret, String callback, String scope,
+ OutputStream debugStream, String userAgent, HttpClientConfig httpClientConfig, HttpClient httpClient) {
+ super(apiKey, apiSecret, callback, debugStream, userAgent, httpClientConfig, httpClient);
+ this.api = api;
+ this.scope = scope;
+ }
+
+ public OAuth1RequestToken getRequestToken() throws IOException, InterruptedException, ExecutionException {
+ if (isDebug()) {
+ log("obtaining request token from %s", api.getRequestTokenEndpoint());
+ }
+ final OAuthRequest request = prepareRequestTokenRequest();
+
+ log("sending request...");
+ try ( Response response = execute(request)) {
+ if (isDebug()) {
+ final String body = response.getBody();
+ log("response status code: %s", response.getCode());
+ log("response body: %s", body);
+ }
+ return api.getRequestTokenExtractor().extract(response);
+ }
+ }
+
+ public Future getRequestTokenAsync() {
+ return getRequestTokenAsync(null);
+ }
+
+ public Future getRequestTokenAsync(OAuthAsyncRequestCallback callback) {
+ if (isDebug()) {
+ log("async obtaining request token from %s", api.getRequestTokenEndpoint());
+ }
+ final OAuthRequest request = prepareRequestTokenRequest();
+ return execute(request, callback, new OAuthRequest.ResponseConverter() {
+ @Override
+ public OAuth1RequestToken convert(Response response) throws IOException {
+ final OAuth1RequestToken token = getApi().getRequestTokenExtractor().extract(response);
+ response.close();
+ return token;
+ }
+ });
+ }
+
+ protected OAuthRequest prepareRequestTokenRequest() {
+ final OAuthRequest request = new OAuthRequest(api.getRequestTokenVerb(), api.getRequestTokenEndpoint());
+ String callback = getCallback();
+ if (callback == null) {
+ callback = OAuthConstants.OOB;
+ }
+ if (isDebug()) {
+ log("setting oauth_callback to %s", callback);
+ }
+ request.addOAuthParameter(OAuthConstants.CALLBACK, callback);
+ addOAuthParams(request, "");
+ appendSignature(request);
+ return request;
+ }
+
+ protected void addOAuthParams(OAuthRequest request, String tokenSecret) {
+ request.addOAuthParameter(OAuthConstants.TIMESTAMP, api.getTimestampService().getTimestampInSeconds());
+ request.addOAuthParameter(OAuthConstants.NONCE, api.getTimestampService().getNonce());
+ request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, getApiKey());
+ request.addOAuthParameter(OAuthConstants.SIGN_METHOD, api.getSignatureService().getSignatureMethod());
+ request.addOAuthParameter(OAuthConstants.VERSION, getVersion());
+ if (scope != null) {
+ request.addOAuthParameter(OAuthConstants.SCOPE, scope);
+ }
+ request.addOAuthParameter(OAuthConstants.SIGNATURE, getSignature(request, tokenSecret));
+
+ if (isDebug()) {
+ log("appended additional OAuth parameters: %s", request.getOauthParameters());
+ }
+ }
+
+ public OAuth1AccessToken getAccessToken(OAuth1RequestToken requestToken, String oauthVerifier)
+ throws IOException, InterruptedException, ExecutionException {
+ if (isDebug()) {
+ log("obtaining access token from %s", api.getAccessTokenEndpoint());
+ }
+ final OAuthRequest request = prepareAccessTokenRequest(requestToken, oauthVerifier);
+ try ( Response response = execute(request)) {
+ return api.getAccessTokenExtractor().extract(response);
+ }
+ }
+
+ public Future getAccessTokenAsync(OAuth1RequestToken requestToken, String oauthVerifier) {
+ return getAccessTokenAsync(requestToken, oauthVerifier, null);
+ }
+
+ /**
+ * Start the request to retrieve the access token. The optionally provided callback will be called with the Token
+ * when it is available.
+ *
+ * @param requestToken request token (obtained previously or null)
+ * @param oauthVerifier oauth_verifier
+ * @param callback optional callback
+ * @return Future
+ */
+ public Future getAccessTokenAsync(OAuth1RequestToken requestToken, String oauthVerifier,
+ OAuthAsyncRequestCallback callback) {
+ if (isDebug()) {
+ log("async obtaining access token from %s", api.getAccessTokenEndpoint());
+ }
+ final OAuthRequest request = prepareAccessTokenRequest(requestToken, oauthVerifier);
+ return execute(request, callback, new OAuthRequest.ResponseConverter() {
+ @Override
+ public OAuth1AccessToken convert(Response response) throws IOException {
+ final OAuth1AccessToken token = getApi().getAccessTokenExtractor().extract(response);
+ response.close();
+ return token;
+ }
+ });
+ }
+
+ protected OAuthRequest prepareAccessTokenRequest(OAuth1RequestToken requestToken, String oauthVerifier) {
+ final OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
+ request.addOAuthParameter(OAuthConstants.TOKEN, requestToken.getToken());
+ request.addOAuthParameter(OAuthConstants.VERIFIER, oauthVerifier);
+ if (isDebug()) {
+ log("setting token to: %s and verifier to: %s", requestToken, oauthVerifier);
+ }
+ addOAuthParams(request, requestToken.getTokenSecret());
+ appendSignature(request);
+ return request;
+ }
+
+ public void signRequest(OAuth1AccessToken token, OAuthRequest request) {
+ if (isDebug()) {
+ log("signing request: %s", request.getCompleteUrl());
+ }
+
+ if (!token.isEmpty() || api.isEmptyOAuthTokenParamIsRequired()) {
+ request.addOAuthParameter(OAuthConstants.TOKEN, token.getToken());
+ }
+ if (isDebug()) {
+ log("setting token to: %s", token);
+ }
+ addOAuthParams(request, token.getTokenSecret());
+ appendSignature(request);
+ }
+
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
+
+ /**
+ * Returns the URL where you should redirect your users to authenticate your application.
+ *
+ * @param requestToken the request token you need to authorize
+ * @return the URL where you should redirect your users
+ */
+ public String getAuthorizationUrl(OAuth1RequestToken requestToken) {
+ return api.getAuthorizationUrl(requestToken);
+ }
+
+ private String getSignature(OAuthRequest request, String tokenSecret) {
+ log("generating signature...");
+ final String baseString = api.getBaseStringExtractor().extract(request);
+ final String signature = api.getSignatureService().getSignature(baseString, getApiSecret(), tokenSecret);
+
+ if (isDebug()) {
+ log("base string is: %s", baseString);
+ log("signature is: %s", signature);
+ }
+ return signature;
+ }
+
+ protected void appendSignature(OAuthRequest request) {
+ final OAuth1SignatureType signatureType = api.getSignatureType();
+ switch (signatureType) {
+ case HEADER:
+ log("using Http Header signature");
+
+ final String oauthHeader = api.getHeaderExtractor().extract(request);
+ request.addHeader(OAuthConstants.HEADER, oauthHeader);
+ break;
+ case QUERY_STRING:
+ log("using Querystring signature");
+
+ for (Map.Entry oauthParameter : request.getOauthParameters().entrySet()) {
+ request.addQuerystringParameter(oauthParameter.getKey(), oauthParameter.getValue());
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unknown new Signature Type '" + signatureType + "'.");
+ }
+ }
+
+ public DefaultApi10a getApi() {
+ return api;
+ }
+}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aServiceImpl.java b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aServiceImpl.java
deleted file mode 100644
index 87b0dccf6..000000000
--- a/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth10aServiceImpl.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package com.github.scribejava.core.oauth;
-
-import com.ning.http.client.ProxyServer;
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.Future;
-import com.github.scribejava.core.builder.api.DefaultApi10a;
-import com.github.scribejava.core.model.AbstractRequest;
-import com.github.scribejava.core.model.OAuthAsyncRequestCallback;
-import com.github.scribejava.core.model.OAuthConfig;
-import com.github.scribejava.core.model.OAuthConstants;
-import com.github.scribejava.core.model.OAuthRequest;
-import com.github.scribejava.core.model.OAuthRequestAsync;
-import com.github.scribejava.core.model.Response;
-import com.github.scribejava.core.model.Token;
-import com.github.scribejava.core.model.Verifier;
-import com.github.scribejava.core.services.Base64Encoder;
-import com.github.scribejava.core.utils.MapUtils;
-
-/**
- * OAuth 1.0a implementation of {@link OAuthService}
- *
- * @author Pablo Fernandez
- */
-public class OAuth10aServiceImpl extends OAuthService {
-
- private static final String VERSION = "1.0";
- private final DefaultApi10a api;
-
- /**
- * Default constructor
- *
- * @param api OAuth1.0a api information
- * @param config OAuth 1.0a configuration param object
- */
- public OAuth10aServiceImpl(final DefaultApi10a api, final OAuthConfig config) {
- super(config);
- this.api = api;
- }
-
- @Override
- public Token getRequestToken() {
- final OAuthConfig config = getConfig();
- config.log("obtaining request token from " + api.getRequestTokenEndpoint());
- final OAuthRequest request = new OAuthRequest(api.getRequestTokenVerb(), api.getRequestTokenEndpoint(), this);
-
- config.log("setting oauth_callback to " + config.getCallback());
- request.addOAuthParameter(OAuthConstants.CALLBACK, config.getCallback());
- addOAuthParams(request, OAuthConstants.EMPTY_TOKEN);
- appendSignature(request);
-
- config.log("sending request...");
- final Response response = request.send();
- final String body = response.getBody();
-
- config.log("response status code: " + response.getCode());
- config.log("response body: " + body);
- return api.getRequestTokenExtractor().extract(body);
- }
-
- private void addOAuthParams(final AbstractRequest request, final Token token) {
- final OAuthConfig config = getConfig();
- request.addOAuthParameter(OAuthConstants.TIMESTAMP, api.getTimestampService().getTimestampInSeconds());
- request.addOAuthParameter(OAuthConstants.NONCE, api.getTimestampService().getNonce());
- request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, config.getApiKey());
- request.addOAuthParameter(OAuthConstants.SIGN_METHOD, api.getSignatureService().getSignatureMethod());
- request.addOAuthParameter(OAuthConstants.VERSION, getVersion());
- if (config.hasScope()) {
- request.addOAuthParameter(OAuthConstants.SCOPE, config.getScope());
- }
- request.addOAuthParameter(OAuthConstants.SIGNATURE, getSignature(request, token));
-
- config.log("appended additional OAuth parameters: " + MapUtils.toString(request.getOauthParameters()));
- }
-
- @Override
- public Token getAccessToken(final Token requestToken, final Verifier verifier) {
- final OAuthConfig config = getConfig();
- config.log("obtaining access token from " + api.getAccessTokenEndpoint());
- final OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint(), this);
- prepareAccessTokenRequest(request, requestToken, verifier);
- final Response response = request.send();
- return api.getAccessTokenExtractor().extract(response.getBody());
- }
-
- @Override
- public Future getAccessTokenAsync(final Token requestToken, final Verifier verifier, final OAuthAsyncRequestCallback callback) {
- return getAccessTokenAsync(requestToken, verifier, callback, null);
- }
-
- @Override
- public Future getAccessTokenAsync(final Token requestToken, final Verifier verifier, final OAuthAsyncRequestCallback callback,
- final ProxyServer proxyServer) {
- final OAuthConfig config = getConfig();
- config.log("async obtaining access token from " + api.getAccessTokenEndpoint());
- final OAuthRequestAsync request = new OAuthRequestAsync(api.getAccessTokenVerb(), api.getAccessTokenEndpoint(), this);
- prepareAccessTokenRequest(request, requestToken, verifier);
- return request.sendAsync(callback, new OAuthRequestAsync.ResponseConverter() {
- @Override
- public Token convert(final com.ning.http.client.Response response) throws IOException {
- return getApi().getAccessTokenExtractor().extract(OAuthRequestAsync.RESPONSE_CONVERTER.convert(response).getBody());
- }
- }, proxyServer);
- }
-
- private void prepareAccessTokenRequest(final AbstractRequest request, final Token requestToken, final Verifier verifier) {
- final OAuthConfig config = getConfig();
- request.addOAuthParameter(OAuthConstants.TOKEN, requestToken.getToken());
- request.addOAuthParameter(OAuthConstants.VERIFIER, verifier.getValue());
- config.log("setting token to: " + requestToken + " and verifier to: " + verifier);
- addOAuthParams(request, requestToken);
- appendSignature(request);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void signRequest(final Token token, final AbstractRequest request) {
- final OAuthConfig config = getConfig();
- config.log("signing request: " + request.getCompleteUrl());
-
- // Do not append the token if empty. This is for two legged OAuth calls.
- if (!token.isEmpty()) {
- request.addOAuthParameter(OAuthConstants.TOKEN, token.getToken());
- }
- config.log("setting token to: " + token);
- addOAuthParams(request, token);
- appendSignature(request);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String getVersion() {
- return VERSION;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String getAuthorizationUrl(final Token requestToken) {
- return api.getAuthorizationUrl(requestToken);
- }
-
- private String getSignature(final AbstractRequest request, final Token token) {
- final OAuthConfig config = getConfig();
- config.log("generating signature...");
- config.log("using base64 encoder: " + Base64Encoder.type());
- final String baseString = api.getBaseStringExtractor().extract(request);
- final String signature = api.getSignatureService().getSignature(baseString, config.getApiSecret(), token.
- getSecret());
-
- config.log("base string is: " + baseString);
- config.log("signature is: " + signature);
- return signature;
- }
-
- private void appendSignature(final AbstractRequest request) {
- final OAuthConfig config = getConfig();
- switch (config.getSignatureType()) {
- case Header:
- config.log("using Http Header signature");
-
- final String oauthHeader = api.getHeaderExtractor().extract(request);
- request.addHeader(OAuthConstants.HEADER, oauthHeader);
- break;
- case QueryString:
- config.log("using Querystring signature");
-
- for (final Map.Entry entry : request.getOauthParameters().entrySet()) {
- request.addQuerystringParameter(entry.getKey(), entry.getValue());
- }
- break;
- }
- }
-
- public DefaultApi10a getApi() {
- return api;
- }
-}
diff --git a/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth20Service.java b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth20Service.java
new file mode 100644
index 000000000..82052e351
--- /dev/null
+++ b/scribejava-core/src/main/java/com/github/scribejava/core/oauth/OAuth20Service.java
@@ -0,0 +1,676 @@
+package com.github.scribejava.core.oauth;
+
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
+import com.github.scribejava.core.httpclient.HttpClient;
+import com.github.scribejava.core.httpclient.HttpClientConfig;
+import com.github.scribejava.core.model.DeviceAuthorization;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuth2AccessTokenErrorResponse;
+import com.github.scribejava.core.model.OAuth2Authorization;
+import com.github.scribejava.core.model.OAuthAsyncRequestCallback;
+import com.github.scribejava.core.model.OAuthConstants;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth2.OAuth2Error;
+import com.github.scribejava.core.pkce.PKCE;
+import com.github.scribejava.core.revoke.TokenTypeHint;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+public class OAuth20Service extends OAuthService {
+
+ private static final String VERSION = "2.0";
+ private final DefaultApi20 api;
+ private final String responseType;
+ private final String defaultScope;
+
+ public OAuth20Service(DefaultApi20 api, String apiKey, String apiSecret, String callback, String defaultScope,
+ String responseType, OutputStream debugStream, String userAgent, HttpClientConfig httpClientConfig,
+ HttpClient httpClient) {
+ super(apiKey, apiSecret, callback, debugStream, userAgent, httpClientConfig, httpClient);
+ this.responseType = responseType;
+ this.api = api;
+ this.defaultScope = defaultScope;
+ }
+
+ // ===== common OAuth methods =====
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getVersion() {
+ return VERSION;
+ }
+
+ public void signRequest(String accessToken, OAuthRequest request) {
+ api.getBearerSignature().signRequest(accessToken, request);
+ }
+
+ public void signRequest(OAuth2AccessToken accessToken, OAuthRequest request) {
+ signRequest(accessToken == null ? null : accessToken.getAccessToken(), request);
+ }
+
+ /**
+ * Returns the URL where you should redirect your users to authenticate your application.
+ *
+ * @return the URL where you should redirect your users
+ */
+ public String getAuthorizationUrl() {
+ return createAuthorizationUrlBuilder().build();
+ }
+
+ public String getAuthorizationUrl(String state) {
+ return createAuthorizationUrlBuilder()
+ .state(state)
+ .build();
+ }
+
+ /**
+ * Returns the URL where you should redirect your users to authenticate your application.
+ *
+ * @param additionalParams any additional GET params to add to the URL
+ * @return the URL where you should redirect your users
+ */
+ public String getAuthorizationUrl(Map additionalParams) {
+ return createAuthorizationUrlBuilder()
+ .additionalParams(additionalParams)
+ .build();
+ }
+
+ public String getAuthorizationUrl(PKCE pkce) {
+ return createAuthorizationUrlBuilder()
+ .pkce(pkce)
+ .build();
+ }
+
+ public AuthorizationUrlBuilder createAuthorizationUrlBuilder() {
+ return new AuthorizationUrlBuilder(this);
+ }
+
+ public DefaultApi20 getApi() {
+ return api;
+ }
+
+ public OAuth2Authorization extractAuthorization(String redirectLocation) {
+ final OAuth2Authorization authorization = new OAuth2Authorization();
+ int end = redirectLocation.indexOf('#');
+ if (end == -1) {
+ end = redirectLocation.length();
+ }
+ for (String param : redirectLocation.substring(redirectLocation.indexOf('?') + 1, end).split("&")) {
+ final String[] keyValue = param.split("=");
+ if (keyValue.length == 2) {
+ try {
+ switch (keyValue[0]) {
+ case "code":
+ authorization.setCode(URLDecoder.decode(keyValue[1], "UTF-8"));
+ break;
+ case "state":
+ authorization.setState(URLDecoder.decode(keyValue[1], "UTF-8"));
+ break;
+ default: //just ignore any other param;
+ }
+ } catch (UnsupportedEncodingException ueE) {
+ throw new IllegalStateException("jvm without UTF-8, really?", ueE);
+ }
+ }
+ }
+ return authorization;
+ }
+
+ public String getResponseType() {
+ return responseType;
+ }
+
+ public String getDefaultScope() {
+ return defaultScope;
+ }
+
+ protected void logRequestWithParams(String requestDescription, OAuthRequest request) {
+ if (isDebug()) {
+ log("created " + requestDescription + " request with body params [%s], query string params [%s]",
+ request.getBodyParams().asFormUrlEncodedString(),
+ request.getQueryStringParams().asFormUrlEncodedString());
+ }
+ }
+
+ // ===== common AccessToken request methods =====
+ //protected to facilitate mocking
+ protected OAuth2AccessToken sendAccessTokenRequestSync(OAuthRequest request)
+ throws IOException, InterruptedException, ExecutionException {
+ if (isDebug()) {
+ log("send request for access token synchronously to %s", request.getCompleteUrl());
+ }
+ try (Response response = execute(request)) {
+ if (isDebug()) {
+ log("response status code: %s", response.getCode());
+ log("response body: %s", response.getBody());
+ }
+
+ return api.getAccessTokenExtractor().extract(response);
+ }
+ }
+
+ //protected to facilitate mocking
+ protected Future sendAccessTokenRequestAsync(OAuthRequest request) {
+ return sendAccessTokenRequestAsync(request, null);
+ }
+
+ //protected to facilitate mocking
+ protected Future sendAccessTokenRequestAsync(OAuthRequest request,
+ OAuthAsyncRequestCallback callback) {
+ if (isDebug()) {
+ log("send request for access token asynchronously to %s", request.getCompleteUrl());
+ }
+
+ return execute(request, callback, new OAuthRequest.ResponseConverter() {
+ @Override
+ public OAuth2AccessToken convert(Response response) throws IOException {
+ log("received response for access token");
+ if (isDebug()) {
+ log("response status code: %s", response.getCode());
+ log("response body: %s", response.getBody());
+ }
+ final OAuth2AccessToken token = api.getAccessTokenExtractor().extract(response);
+ response.close();
+ return token;
+ }
+ });
+ }
+
+ // ===== get AccessToken authorisation code flow methods =====
+ protected OAuthRequest createAccessTokenRequest(AccessTokenRequestParams params) {
+ final OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
+
+ api.getClientAuthentication().addClientAuthentication(request, getApiKey(), getApiSecret());
+
+ request.addParameter(OAuthConstants.CODE, params.getCode());
+ final String callback = getCallback();
+ if (callback != null) {
+ request.addParameter(OAuthConstants.REDIRECT_URI, callback);
+ }
+ final String scope = params.getScope();
+ if (scope != null) {
+ request.addParameter(OAuthConstants.SCOPE, scope);
+ } else if (defaultScope != null) {
+ request.addParameter(OAuthConstants.SCOPE, defaultScope);
+ }
+ request.addParameter(OAuthConstants.GRANT_TYPE, OAuthConstants.AUTHORIZATION_CODE);
+
+ final String pkceCodeVerifier = params.getPkceCodeVerifier();
+ if (pkceCodeVerifier != null) {
+ request.addParameter(PKCE.PKCE_CODE_VERIFIER_PARAM, pkceCodeVerifier);
+ }
+
+ final Map extraParameters = params.getExtraParameters();
+ if (extraParameters != null && !extraParameters.isEmpty()) {
+ for (Map.Entry extraParameter : extraParameters.entrySet()) {
+ request.addParameter(extraParameter.getKey(), extraParameter.getValue());
+ }
+ }
+
+ logRequestWithParams("access token", request);
+ return request;
+ }
+
+ public Future getAccessTokenAsync(String code) {
+ return getAccessToken(AccessTokenRequestParams.create(code), null);
+ }
+
+ public Future getAccessTokenAsync(AccessTokenRequestParams params) {
+ return getAccessToken(params, null);
+ }
+
+ public OAuth2AccessToken getAccessToken(String code) throws IOException, InterruptedException, ExecutionException {
+ return getAccessToken(AccessTokenRequestParams.create(code));
+ }
+
+ public OAuth2AccessToken getAccessToken(AccessTokenRequestParams params)
+ throws IOException, InterruptedException, ExecutionException {
+ return sendAccessTokenRequestSync(createAccessTokenRequest(params));
+ }
+
+ /**
+ * Start the request to retrieve the access token. The optionally provided callback will be called with the Token
+ * when it is available.
+ *
+ * @param params params
+ * @param callback optional callback
+ * @return Future
+ */
+ public Future getAccessToken(AccessTokenRequestParams params,
+ OAuthAsyncRequestCallback