/* * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.firebase.auth; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential; import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential.Builder; import com.google.api.client.googleapis.testing.auth.oauth2.MockTokenServerTransport; import com.google.api.client.googleapis.util.Utils; import com.google.common.collect.ImmutableMap; import com.google.firebase.auth.FirebaseCredentials.BaseCredential; import com.google.firebase.tasks.Tasks; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import org.json.JSONException; import org.json.JSONObject; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; /** * Tests for {@link FirebaseCredentials}. */ public class FirebaseCredentialsTest { private static final String ACCESS_TOKEN = "mockaccesstoken"; private static final String CLIENT_SECRET = "mockclientsecret"; private static final String CLIENT_ID = "mockclientid"; private static final String REFRESH_TOKEN = "mockrefreshtoken"; @Rule public final ExpectedException thrown = ExpectedException.none(); @Test(expected = NullPointerException.class) public void testNullCertificate() throws IOException { FirebaseCredentials.fromCertificate(null); } @Test(expected = NullPointerException.class) public void testNullRefreshToken() throws IOException { FirebaseCredentials.fromRefreshToken(null); } @Test public void defaultCredentialIsCached() { Assert.assertEquals( FirebaseCredentials.applicationDefault(), FirebaseCredentials.applicationDefault()); } @Test public void defaultCredentialDoesntRefetch() throws Exception { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); File credentialsFile = File.createTempFile("google-test-credentials", "json"); PrintWriter writer = new PrintWriter(Files.newBufferedWriter(credentialsFile.toPath(), UTF_8)); writer.print(ServiceAccount.EDITOR.asString()); writer.close(); Map<String, String> environmentVariables = ImmutableMap.<String, String>builder() .put("GOOGLE_APPLICATION_CREDENTIALS", credentialsFile.getAbsolutePath()) .build(); TestUtils.setEnvironmentVariables(environmentVariables); GoogleOAuthAccessToken token = Tasks.await( FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory()) .getAccessToken()); Assert.assertEquals(ACCESS_TOKEN, token.getAccessToken()); // We should still be able to fetch the token since the certificate is cached Assert.assertTrue(credentialsFile.delete()); token = Tasks.await( FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory()) .getAccessToken()); Assert.assertNotNull(token); Assert.assertEquals(ACCESS_TOKEN, token.getAccessToken()); } @Test public void canResolveTokenMoreThanOnce() throws ExecutionException, InterruptedException, IOException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); FirebaseCredential credential = FirebaseCredentials.fromCertificate( ServiceAccount.EDITOR.asStream(), transport, Utils.getDefaultJsonFactory()); Tasks.await(credential.getAccessToken()); Tasks.await(credential.getAccessToken()); } @Test public void certificateReadIsDoneSynchronously() throws ExecutionException, InterruptedException, IOException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); ByteArrayInputStream inputStream = new ByteArrayInputStream( ServiceAccount.EDITOR.asString().getBytes(Charset.defaultCharset())); FirebaseCredential credential = FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory()); Assert.assertEquals(0, inputStream.available()); inputStream.close(); Tasks.await(credential.getAccessToken()); } @Test public void certificateReadChecksForProjectId() throws ExecutionException, InterruptedException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); String accountWithoutProjectId = ServiceAccount.EDITOR.asString().replace("project_id", "missing"); ByteArrayInputStream inputStream = new ByteArrayInputStream(accountWithoutProjectId.getBytes(Charset.defaultCharset())); try { FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory()); Assert.fail(); } catch (IOException e) { Assert.assertEquals( "Failed to parse service account: 'project_id' must be set", e.getMessage()); Assert.assertTrue(e.getCause() instanceof JSONException); } } @Test public void certificateReadThrowsRuntimeException() throws ExecutionException, InterruptedException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); InputStream inputStream = new InputStream() { @Override public int read() throws IOException { throw new IOException("Expected"); } }; try { FirebaseCredentials.fromCertificate(inputStream, transport, Utils.getDefaultJsonFactory()); Assert.fail(); } catch (IOException e) { Assert.assertEquals("Expected", e.getMessage()); } } @Test public void nullThrowsRuntimeExceptionFromCertificate() throws ExecutionException, InterruptedException, IOException { final MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); thrown.expect(NullPointerException.class); FirebaseCredentials.fromCertificate(null, transport, Utils.getDefaultJsonFactory()); } @Test public void nullThrowsRuntimeExceptionFromRefreshToken() throws ExecutionException, InterruptedException, IOException { final MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); thrown.expect(NullPointerException.class); FirebaseCredentials.fromRefreshToken(null, transport, Utils.getDefaultJsonFactory()); } @Test public void refreshTokenReadIsDoneSynchronously() throws ExecutionException, InterruptedException, IOException, JSONException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addClient(CLIENT_ID, CLIENT_SECRET); transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN); JSONObject secretJson = new JSONObject(); secretJson.put("client_id", CLIENT_ID); secretJson.put("client_secret", CLIENT_SECRET); secretJson.put("refresh_token", REFRESH_TOKEN); secretJson.put("type", "authorized_user"); InputStream inputStream = new ByteArrayInputStream(secretJson.toString(0).getBytes("UTF-8")); FirebaseCredential credential = FirebaseCredentials.fromRefreshToken(inputStream, transport, Utils.getDefaultJsonFactory()); Assert.assertEquals(0, inputStream.available()); inputStream.close(); Tasks.await(credential.getAccessToken()); } @Test public void refreshTokenReadThrowsRuntimeException() throws ExecutionException, InterruptedException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); InputStream inputStream = new InputStream() { @Override public int read() throws IOException { throw new IOException("Expected"); } }; try { FirebaseCredentials.fromRefreshToken(inputStream, transport, Utils.getDefaultJsonFactory()); Assert.fail(); } catch (IOException e) { Assert.assertEquals("Expected", e.getMessage()); } } @Test public void tokenNotCached() throws Exception { final GoogleCredential googleCredential = new MockGoogleCredential(new Builder()); googleCredential.setAccessToken(ACCESS_TOKEN); googleCredential.setExpirationTimeMilliseconds(10L); TestCredential credential = new TestCredential(googleCredential); for (long i = 0; i < 10; i++) { Assert.assertEquals(ACCESS_TOKEN, Tasks.await(credential.getAccessToken()).getAccessToken()); Assert.assertEquals(i + 1, credential.getFetchCount()); } } @Test public void testTokenExpiration() throws Exception { final GoogleCredential googleCredential = new MockGoogleCredential(new Builder()); googleCredential.setAccessToken(ACCESS_TOKEN); TestCredential credential = new TestCredential(googleCredential); for (long i = 0; i < 10; i++) { long expiryTime = (i + 1) * 10L; googleCredential.setExpirationTimeMilliseconds(expiryTime); GoogleOAuthAccessToken googleToken = Tasks.await(credential.getAccessToken()); Assert.assertEquals(ACCESS_TOKEN, googleToken.getAccessToken()); Assert.assertEquals(expiryTime, googleToken.getExpiryTime()); } } private static class TestCredential extends BaseCredential { private final AtomicInteger fetchCount = new AtomicInteger(0); private final GoogleCredential googleCredential; TestCredential(GoogleCredential googleCredential) { super(new MockTokenServerTransport(), Utils.getDefaultJsonFactory()); this.googleCredential = googleCredential; } @Override GoogleCredential fetchCredential() throws IOException { return googleCredential; } @Override GoogleOAuthAccessToken fetchToken(GoogleCredential credential) throws IOException { try { return FirebaseCredentials.newAccessToken(credential); } finally { fetchCount.incrementAndGet(); } } int getFetchCount() { return fetchCount.get(); } } }