package org.edx.mobile.test; import com.google.gson.JsonObject; import com.google.inject.Injector; import org.edx.mobile.authentication.AuthResponse; import org.edx.mobile.http.HttpStatus; import org.edx.mobile.http.OauthRefreshTokenAuthenticator; import org.edx.mobile.module.prefs.LoginPrefs; import org.edx.mobile.test.util.MockDataUtil; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.robolectric.annotation.Config; import java.io.IOException; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import static org.edx.mobile.test.util.OkHttpTestUtil.defaultClient; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @Config(sdk = 18) public final class AuthenticationTests extends BaseTestCase { private static final String API_HOST_URL = "API_HOST_URL"; // Config key for API host url @Rule public final MockWebServer mockServer = new MockWebServer(); private OkHttpClient client = defaultClient(); private LoginPrefs loginPrefs; @Before public void setUp() throws Exception { mockServer.setDispatcher(dispatcher); super.setUp(); } @Override protected void inject(Injector injector) throws Exception { super.inject(injector); loginPrefs = injector.getInstance(LoginPrefs.class); loginPrefs.storeAuthTokenResponse(MockDataUtil.getMockResponse("post_oauth2_access_token", AuthResponse.class), LoginPrefs.AuthBackend.PASSWORD); } @Override protected JsonObject generateConfigProperties() throws IOException { // Add the mock host url in the test config properties JsonObject properties = super.generateConfigProperties(); properties.addProperty(API_HOST_URL, mockServer.url("/").toString()); return properties; } @Test public void testAuthenticate_forExpiredAccessToken() throws Exception { // create a client with the authenticator client = client.newBuilder() .authenticator(new OauthRefreshTokenAuthenticator(context)) .build(); // Build a new dummy request to trigger authenticator Request request = new Request.Builder() .url(mockServer.url("/dummy/endpoint/")) .header("Authorization", "expired_token") .build(); // Make request Response response = client.newCall(request).execute(); assertEquals(HttpStatus.OK, response.code()); assertEquals("Bearer dummy", response.request().header("Authorization")); // Assert the expired token request was sent RecordedRequest expiredRequest = mockServer.takeRequest(); assertEquals("/dummy/endpoint/", expiredRequest.getPath()); assertEquals("expired_token", expiredRequest.getHeader("Authorization")); // Assert the authenticator requests for a new access token using the refresh token RecordedRequest refreshTokenRequest = mockServer.takeRequest(); assertEquals("/oauth2/access_token/", refreshTokenRequest.getPath()); String actual_body = refreshTokenRequest.getBody().readUtf8(); assertTrue(actual_body.contains("grant_type=refresh_token")); assertTrue(actual_body.contains("refresh_token=dummy_refresh_token")); // Assert that the original request was made again with the new token. RecordedRequest refreshedRequest = mockServer.takeRequest(); assertEquals("/dummy/endpoint/", refreshedRequest.getPath()); assertEquals("Bearer dummy", refreshedRequest.getHeader("Authorization")); } @Test public void testAuthenticate_notForExpiredAccessToken() throws Exception { client = client.newBuilder() .authenticator(new OauthRefreshTokenAuthenticator(context)) .build(); Request request = new Request.Builder() .url(mockServer.url("/dummy/endpoint/")) .header("Authorization", "401_not_caused_by_expired_token") .build(); Response response = client.newCall(request).execute(); assertEquals(HttpStatus.UNAUTHORIZED, response.code()); assertEquals("401_not_caused_by_expired_token", response.request().header("Authorization")); } @Test public void testAuthenticate_withoutRefreshToken() throws Exception { loginPrefs.storeAuthTokenResponse(MockDataUtil.getMockResponse("post_oauth2_access_token_no_refresh_token", AuthResponse.class), LoginPrefs.AuthBackend.PASSWORD); client = client.newBuilder() .authenticator(new OauthRefreshTokenAuthenticator(context)) .build(); Request request = new Request.Builder() .url(mockServer.url("/dummy/endpoint/")) .header("Authorization", "expired_token") .build(); Response response = client.newCall(request).execute(); assertEquals(HttpStatus.UNAUTHORIZED, response.code()); assertEquals("expired_token", response.request().header("Authorization")); } final Dispatcher dispatcher = new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { MockResponse response = new MockResponse(); String path = request.getPath(); String header = request.getHeader("Authorization"); response.setResponseCode(HttpStatus.NOT_FOUND); try { if (path.equals("/oauth2/access_token/")) { response.setResponseCode(HttpStatus.OK).setBody(MockDataUtil.getMockResponse("post_oauth2_access_token")); } else if (path.equals("/dummy/endpoint/")) { switch (header) { case "expired_token": response.setResponseCode(HttpStatus.UNAUTHORIZED) .addHeader("Authorization", "old_access_token") .setBody(MockDataUtil.getMockResponse("401_expired_token_body")); break; case "Bearer dummy": response.setResponseCode(HttpStatus.OK); break; case "401_not_caused_by_expired_token": response.setResponseCode(HttpStatus.UNAUTHORIZED); break; } } } catch (IOException exception) { exception.printStackTrace(); } return response; } }; }