diff --git a/src/main/java/com/xero/api/ApiClient.java b/src/main/java/com/xero/api/ApiClient.java index 04fa65fd..7545247f 100644 --- a/src/main/java/com/xero/api/ApiClient.java +++ b/src/main/java/com/xero/api/ApiClient.java @@ -32,6 +32,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.interfaces.RSAPublicKey; @@ -216,7 +217,7 @@ public HttpResponse revoke(String clientId, String clientSecret, String refreshT headers.setBasicAuthentication(clientId, clientSecret); // POST BODY WITH REFRESH TOKEN - String urlParameters = "token=" + refreshToken; + String urlParameters = "token=" + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8.name()); byte[] postData = urlParameters.getBytes( StandardCharsets.UTF_8 ); HttpContent content = new ByteArrayContent("application/x-www-form-urlencoded", postData); diff --git a/src/test/java/com/xero/api/ApiClientTest.java b/src/test/java/com/xero/api/ApiClientTest.java new file mode 100644 index 00000000..e1bc855b --- /dev/null +++ b/src/test/java/com/xero/api/ApiClientTest.java @@ -0,0 +1,55 @@ +package com.xero.api; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpContent; +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpMethods; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class ApiClientTest { + + @Test + public void revokeFormEncodesRefreshToken() throws IOException { + HttpTransport transport = mock(HttpTransport.class); + HttpRequestFactory requestFactory = mock(HttpRequestFactory.class); + HttpRequest request = mock(HttpRequest.class); + HttpResponse response = mock(HttpResponse.class); + ArgumentCaptor contentCaptor = ArgumentCaptor.forClass(HttpContent.class); + + when(transport.createRequestFactory()).thenReturn(requestFactory); + when(requestFactory.buildRequest( + eq(HttpMethods.POST), any(GenericUrl.class), contentCaptor.capture() + )).thenReturn(request); + when(request.setHeaders(any(HttpHeaders.class))).thenReturn(request); + when(request.setConnectTimeout(anyInt())).thenReturn(request); + when(request.setReadTimeout(anyInt())).thenReturn(request); + when(request.execute()).thenReturn(response); + + ApiClient apiClient = new ApiClient(null, transport, null, null, null); + apiClient.revoke("client-id", "client-secret", "refresh+token&part=value"); + + ByteArrayOutputStream requestBody = new ByteArrayOutputStream(); + contentCaptor.getValue().writeTo(requestBody); + + assertThat( + requestBody.toString(StandardCharsets.UTF_8.name()), + is("token=refresh%2Btoken%26part%3Dvalue") + ); + } +}