diff --git a/src/main/java/com/restfb/DefaultFacebookClient.java b/src/main/java/com/restfb/DefaultFacebookClient.java index 79f938ab5..efdc74d8d 100644 --- a/src/main/java/com/restfb/DefaultFacebookClient.java +++ b/src/main/java/com/restfb/DefaultFacebookClient.java @@ -93,13 +93,15 @@ public class DefaultFacebookClient extends BaseFacebookClient implements Faceboo /** * Version of API endpoint. */ - protected Version apiVersion = Version.UNVERSIONED; + protected Version apiVersion; /** * By default this is false, so real http DELETE is used */ protected boolean httpDeleteFallback; + protected boolean accessTokenInHeader; + protected DefaultFacebookClient() { this(Version.LATEST); } @@ -196,6 +198,17 @@ public DefaultFacebookClient(String accessToken, String appSecret, WebRequestor graphFacebookExceptionGenerator = new DefaultFacebookExceptionGenerator(); } + /** + * Switch between access token in header and access token in query parameters (default) + * + * @param accessTokenInHttpHeader + * true use access token as header field, false use access token as query parameter + * (default) + */ + public void setHeaderAuthorization(boolean accessTokenInHttpHeader) { + this.accessTokenInHeader = accessTokenInHttpHeader; + } + /** * override the default facebook exception generator to provide a custom handling for the facebook error objects * @@ -258,8 +271,8 @@ public Connection fetchConnection(String connection, Class connectionT public Connection fetchConnectionPage(final String connectionPageUrl, Class connectionType) { String connectionJson; if (!isBlank(accessToken) && !isBlank(appSecret)) { - connectionJson = makeRequestAndProcessResponse(() -> webRequestor.executeGet(String.format("%s&%s=%s", connectionPageUrl, - urlEncode(APP_SECRET_PROOF_PARAM_NAME), obtainAppSecretProof(accessToken, appSecret)))); + connectionJson = makeRequestAndProcessResponse(() -> webRequestor.executeGet(String.format("%s&%s=%s", + connectionPageUrl, urlEncode(APP_SECRET_PROOF_PARAM_NAME), obtainAppSecretProof(accessToken, appSecret)))); } else { connectionJson = makeRequestAndProcessResponse(() -> webRequestor.executeGet(connectionPageUrl)); } @@ -343,7 +356,8 @@ public T publish(String connection, Class objectType, List T publish(String connection, Class objectType, BinaryAttachment binaryAttachment, Parameter... parameters) { - List attachments = Optional.ofNullable(binaryAttachment).map(Collections::singletonList).orElse(null); + List attachments = + Optional.ofNullable(binaryAttachment).map(Collections::singletonList).orElse(null); return publish(connection, objectType, attachments, parameters); } @@ -441,7 +455,8 @@ public AccessToken obtainAppAccessToken(String appId, String appSecret) { @Override public DeviceCode fetchDeviceCode(ScopeBuilder scope) { verifyParameterPresence(SCOPE, scope); - ObjectUtil.requireNotNull(accessToken, () -> new IllegalStateException("access token is required to fetch a device access token")); + ObjectUtil.requireNotNull(accessToken, + () -> new IllegalStateException("access token is required to fetch a device access token")); String response = makeRequest("device/login", true, false, null, Parameter.with("type", "device_code"), Parameter.with(SCOPE, scope.toString())); @@ -453,7 +468,8 @@ public AccessToken obtainDeviceAccessToken(String code) throws FacebookDeviceTok FacebookDeviceTokenPendingException, FacebookDeviceTokenDeclinedException, FacebookDeviceTokenSlowdownException { verifyParameterPresence("code", code); - ObjectUtil.requireNotNull(accessToken, () -> new IllegalStateException("access token is required to fetch a device access token")); + ObjectUtil.requireNotNull(accessToken, + () -> new IllegalStateException("access token is required to fetch a device access token")); try { String response = makeRequest("device/login_status", true, false, null, Parameter.with("type", "device_token"), @@ -724,15 +740,19 @@ protected String makeRequest(String endpoint, final boolean executeAsPost, final final String parameterString = toParameterString(parameters); return makeRequestAndProcessResponse(() -> { - if (executeAsDelete && !isHttpDeleteFallback()) { - return webRequestor.executeDelete(fullEndpoint + "?" + parameterString); - } + if (accessTokenInHeader) { + webRequestor.setAccessToken(this.accessToken); + } - if (executeAsPost) { - return webRequestor.executePost(fullEndpoint, parameterString, binaryAttachments); - } + if (executeAsDelete && !isHttpDeleteFallback()) { + return webRequestor.executeDelete(fullEndpoint + "?" + parameterString); + } + + if (executeAsPost) { + return webRequestor.executePost(fullEndpoint, parameterString, binaryAttachments); + } - return webRequestor.executeGet(fullEndpoint + "?" + parameterString); + return webRequestor.executeGet(fullEndpoint + "?" + parameterString); }); } @@ -749,7 +769,7 @@ public String obtainAppSecretProof(String accessToken, String appSecret) { /** * returns if the fallback post method (true) is used or the http delete (false) * - * @return + * @return {@code true} if POST is used instead of HTTP DELETE (default) */ public boolean isHttpDeleteFallback() { return httpDeleteFallback; @@ -836,7 +856,7 @@ protected String toParameterString(Parameter... parameters) { * If an error occurs when building the parameter string. */ protected String toParameterString(boolean withJsonParameter, Parameter... parameters) { - if (!isBlank(accessToken)) { + if (!isBlank(accessToken) && !accessTokenInHeader) { parameters = parametersWithAdditionalParameter(Parameter.with(ACCESS_TOKEN_PARAM_NAME, accessToken), parameters); } diff --git a/src/main/java/com/restfb/DefaultWebRequestor.java b/src/main/java/com/restfb/DefaultWebRequestor.java index 558de2879..049a22baf 100644 --- a/src/main/java/com/restfb/DefaultWebRequestor.java +++ b/src/main/java/com/restfb/DefaultWebRequestor.java @@ -70,6 +70,8 @@ public class DefaultWebRequestor implements WebRequestor { private DebugHeaderInfo debugHeaderInfo; + private ThreadLocal accessToken = new ThreadLocal<>(); + /** * By default this is true, to prevent breaking existing usage */ @@ -79,6 +81,11 @@ protected enum HttpMethod { GET, DELETE, POST } + @Override + public void setAccessToken(final String accessToken) { + this.accessToken = ThreadLocal.withInitial(() -> accessToken); + } + @Override public Response executeGet(String url) throws IOException { return execute(url, HttpMethod.GET); @@ -116,6 +123,8 @@ public Response executePost(String url, String parameters, List @@ -333,6 +348,8 @@ private Response execute(String url, HttpMethod httpMethod) throws IOException { httpUrlConnection.setUseCaches(false); httpUrlConnection.setRequestMethod(httpMethod.name()); + initHeaderAccessToken(httpUrlConnection); + // Allow subclasses to customize the connection if they'd like to - set // their own headers, timeouts, etc. customizeConnection(httpUrlConnection); diff --git a/src/main/java/com/restfb/WebRequestor.java b/src/main/java/com/restfb/WebRequestor.java index e90a1ee43..d1186f63b 100644 --- a/src/main/java/com/restfb/WebRequestor.java +++ b/src/main/java/com/restfb/WebRequestor.java @@ -93,6 +93,8 @@ public String toString() { } } + void setAccessToken(String accessToken); + /** * Given a Facebook API endpoint URL, execute a {@code GET} against it. * diff --git a/src/test/java/com/restfb/ClasspathWebRequestor.java b/src/test/java/com/restfb/ClasspathWebRequestor.java index ad25be9dd..9a1b99569 100644 --- a/src/test/java/com/restfb/ClasspathWebRequestor.java +++ b/src/test/java/com/restfb/ClasspathWebRequestor.java @@ -61,6 +61,11 @@ public Response executePost(String url, String parameters) { return response; } + @Override + public void setAccessToken(String accessToken) { + + } + /** * @see com.restfb.WebRequestor#executeGet(java.lang.String) */ diff --git a/src/test/java/com/restfb/DefaultWebRequestorTest.java b/src/test/java/com/restfb/DefaultWebRequestorTest.java index 9251961be..bb7e5d503 100644 --- a/src/test/java/com/restfb/DefaultWebRequestorTest.java +++ b/src/test/java/com/restfb/DefaultWebRequestorTest.java @@ -57,6 +57,19 @@ class DefaultWebRequestorTest { @BeforeEach void setup() throws IOException { doReturn(mockUrlConnection).when(requestor).openConnection(any(URL.class)); + requestor.setAccessToken(null); + } + + @Test + void checkGet_withAccessToken() throws IOException { + requestor.setAccessToken("accesstoken"); + String resultString = "This is just a simple Test"; + when(mockUrlConnection.getResponseCode()).thenReturn(200); + InputStream stream = new ByteArrayInputStream(resultString.getBytes(StandardCharsets.UTF_8)); + when(mockUrlConnection.getInputStream()).thenReturn(stream); + WebRequestor.Response response = requestor.executeGet("http://www.example.org"); + + verify(mockUrlConnection).setRequestProperty("Authorization", "Bearer accesstoken"); } @Test @@ -75,6 +88,7 @@ void checkGet() throws IOException { verify(mockUrlConnection).setReadTimeout(180000); verify(mockUrlConnection).setUseCaches(false); verify(mockUrlConnection).setRequestMethod(DefaultWebRequestor.HttpMethod.GET.name()); + verify(mockUrlConnection, never()).setRequestProperty(eq("Authorization"), anyString()); verify(mockUrlConnection).connect(); verify(requestor).executeGet(anyString()); verify(requestor, never()).executePost(anyString(), anyString()); @@ -83,6 +97,19 @@ void checkGet() throws IOException { verify(requestor).fetchResponse(mockUrlConnection); } + @Test + void checkPost_withAccessToken() throws IOException { + requestor.setAccessToken("accesstoken"); + when(mockUrlConnection.getOutputStream()).thenReturn(mockOutputStream); + String resultString = "This is just a simple Test"; + when(mockUrlConnection.getResponseCode()).thenReturn(200); + InputStream stream = new ByteArrayInputStream(resultString.getBytes(StandardCharsets.UTF_8)); + when(mockUrlConnection.getInputStream()).thenReturn(stream); + WebRequestor.Response response = requestor.executePost(exampleUrl, ""); + + verify(mockUrlConnection).setRequestProperty("Authorization", "Bearer accesstoken"); + } + @Test void checkPost_NoBinary() throws IOException { when(mockUrlConnection.getOutputStream()).thenReturn(mockOutputStream); @@ -101,6 +128,7 @@ void checkPost_NoBinary() throws IOException { verify(mockUrlConnection).setUseCaches(false); verify(mockUrlConnection).setDoOutput(true); verify(mockUrlConnection).setRequestMethod(DefaultWebRequestor.HttpMethod.POST.name()); + verify(mockUrlConnection, never()).setRequestProperty(eq("Authorization"), anyString()); verify(mockUrlConnection).connect(); verify(requestor).executePost(same(exampleUrl), anyString()); verify(requestor, never()).executeGet(anyString()); @@ -132,6 +160,7 @@ void checkPost_WithBinary() throws IOException { verify(mockUrlConnection).setUseCaches(false); verify(mockUrlConnection).setDoOutput(true); verify(mockUrlConnection).setRequestProperty("Connection", "Keep-Alive"); + verify(mockUrlConnection, never()).setRequestProperty(eq("Authorization"), anyString()); verify(mockUrlConnection).setRequestMethod(DefaultWebRequestor.HttpMethod.POST.name()); verify(mockUrlConnection).connect(); verify(requestor).executePost(same(exampleUrl), anyString(), anyList()); diff --git a/src/test/java/com/restfb/FakeWebRequestor.java b/src/test/java/com/restfb/FakeWebRequestor.java index 8e1a60394..7ff8566af 100644 --- a/src/test/java/com/restfb/FakeWebRequestor.java +++ b/src/test/java/com/restfb/FakeWebRequestor.java @@ -41,6 +41,8 @@ public class FakeWebRequestor implements WebRequestor { private Response predefinedResponse; + private String accessToken; + public FakeWebRequestor() { this(null); } @@ -49,6 +51,11 @@ public FakeWebRequestor() { this.predefinedResponse = predefinedResponse; } + @Override + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + @Override public Response executeGet(String url) throws IOException { this.savedUrl = url;