/* * 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.client.googleapis.testing.auth.oauth2.MockTokenServerTransport; import com.google.api.client.googleapis.util.Utils; import com.google.api.client.json.gson.GsonFactory; import com.google.common.collect.ImmutableMap; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.internal.FirebaseCustomAuthToken; import com.google.firebase.database.MapBuilder; import com.google.firebase.internal.Log; import com.google.firebase.tasks.Task; 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.io.UnsupportedEncodingException; import java.nio.file.Files; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.json.JSONException; import org.json.JSONObject; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class FirebaseAuthTest { 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"; private static final String TAG = "FirebaseAuthTest"; private final FirebaseOptions firebaseOptions; private final boolean isCertCredential; public FirebaseAuthTest(FirebaseOptions baseOptions, boolean isCertCredential) { this.firebaseOptions = baseOptions; this.isCertCredential = isCertCredential; } @Parameters public static Collection<Object[]> data() throws Exception { // Initialize this test suite with all available credential implementations. return Arrays.asList( new Object[][] { { new FirebaseOptions.Builder().setCredential(createCertificateCredential()).build(), /* isCertCredential */ true }, { new FirebaseOptions.Builder().setCredential(createRefreshTokenCredential()).build(), /* isCertCredential */ false }, { new FirebaseOptions.Builder() .setCredential(createApplicationDefaultCredential()) .build(), /* isCertCredential */ false }, }); } private static FirebaseCredential createApplicationDefaultCredential() throws IOException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); // Set the GOOGLE_APPLICATION_CREDENTIALS environment variable for application-default // credentials. This requires us to write the credentials to the location specified by the // environment variable. 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); credentialsFile.deleteOnExit(); return FirebaseCredentials.applicationDefault(transport, Utils.getDefaultJsonFactory()); } private static FirebaseCredential createRefreshTokenCredential() throws JSONException, IOException { 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 refreshTokenStream = new ByteArrayInputStream(secretJson.toString(0).getBytes("UTF-8")); return FirebaseCredentials.fromRefreshToken( refreshTokenStream, transport, Utils.getDefaultJsonFactory()); } private static FirebaseCredential createCertificateCredential() throws IOException { MockTokenServerTransport transport = new MockTokenServerTransport(); transport.addServiceAccount(ServiceAccount.EDITOR.getEmail(), ACCESS_TOKEN); return FirebaseCredentials.fromCertificate( ServiceAccount.EDITOR.asStream(), transport, Utils.getDefaultJsonFactory()); } @Before public void setup() { TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); FirebaseApp.initializeApp(firebaseOptions); } @After public void cleanup() { TestOnlyImplFirebaseTrampolines.clearInstancesForTest(); } @Test public void testGetInstance() throws ExecutionException, InterruptedException { FirebaseAuth defaultAuth = FirebaseAuth.getInstance(); assertNotNull(defaultAuth); assertSame(defaultAuth, FirebaseAuth.getInstance()); String token = Tasks.await(TestOnlyImplFirebaseTrampolines.getToken(FirebaseApp.getInstance(), false)) .getToken(); Assert.assertTrue(!token.isEmpty()); } @Test public void testGetInstanceForApp() throws ExecutionException, InterruptedException { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testGetInstanceForApp"); FirebaseAuth auth = FirebaseAuth.getInstance(app); assertNotNull(auth); assertSame(auth, FirebaseAuth.getInstance(app)); String token = Tasks.await(TestOnlyImplFirebaseTrampolines.getToken(app, false)).getToken(); Assert.assertTrue(!token.isEmpty()); } @Test public void testAppDelete() throws ExecutionException, InterruptedException { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testAppDelete"); FirebaseAuth auth = FirebaseAuth.getInstance(app); assertNotNull(auth); app.delete(); try { FirebaseAuth.getInstance(app); fail("No error thrown when getting auth instance after deleting app"); } catch (IllegalStateException expected) { // ignore } } @Test public void testInvokeAfterAppDelete() throws ExecutionException, InterruptedException { if (!isCertCredential) { Log.i(TAG, "Skipping testInvokeAfterAppDelete for non-cert credential"); return; } FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInvokeAfterAppDelete"); FirebaseAuth auth = FirebaseAuth.getInstance(app); assertNotNull(auth); app.delete(); try { auth.createCustomToken("foo"); fail("No error thrown when invoking auth after deleting app"); } catch (IllegalStateException expected) { // ignore } } @Test public void testInitAfterAppDelete() throws ExecutionException, InterruptedException, TimeoutException { FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testInitAfterAppDelete"); FirebaseAuth auth1 = FirebaseAuth.getInstance(app); assertNotNull(auth1); app.delete(); app = FirebaseApp.initializeApp(firebaseOptions, "testInitAfterAppDelete"); FirebaseAuth auth2 = FirebaseAuth.getInstance(app); assertNotNull(auth2); assertNotSame(auth1, auth2); if (isCertCredential) { Task<String> task = auth2.createCustomToken("foo"); assertNotNull(task); assertNotNull(Tasks.await(task, TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); } } @Test public void testAppWithAuthVariableOverrides() throws ExecutionException, InterruptedException { Map<String, Object> authVariableOverrides = Collections.singletonMap("uid", (Object) "uid1"); FirebaseOptions options = new FirebaseOptions.Builder(firebaseOptions) .setDatabaseAuthVariableOverride(authVariableOverrides) .build(); FirebaseApp app = FirebaseApp.initializeApp(options, "testGetAppWithUid"); assertEquals("uid1", app.getOptions().getDatabaseAuthVariableOverride().get("uid")); String token = Tasks.await(TestOnlyImplFirebaseTrampolines.getToken(app, false)).getToken(); Assert.assertTrue(!token.isEmpty()); } @Test public void testCreateCustomToken() throws Exception { if (!isCertCredential) { Log.i(TAG, "Skipping testCreateCustomToken for non-cert credential"); return; } FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testCreateCustomToken"); FirebaseAuth auth = FirebaseAuth.getInstance(app); String token = Tasks.await(auth.createCustomToken("user1")); FirebaseCustomAuthToken parsedToken = FirebaseCustomAuthToken.parse(new GsonFactory(), token); assertEquals(parsedToken.getPayload().getUid(), "user1"); assertEquals(parsedToken.getPayload().getSubject(), ServiceAccount.EDITOR.getEmail()); assertEquals(parsedToken.getPayload().getIssuer(), ServiceAccount.EDITOR.getEmail()); assertNull(parsedToken.getPayload().getDeveloperClaims()); assertTrue(ServiceAccount.EDITOR.verifySignature(parsedToken)); } @Test public void testCreateCustomTokenWithDeveloperClaims() throws Exception { if (!isCertCredential) { Log.i(TAG, "Skipping testCreateCustomTokenWithDeveloperClaims for non-cert credential"); return; } FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testCreateCustomTokenWithDeveloperClaims"); FirebaseAuth auth = FirebaseAuth.getInstance(app); String token = Tasks.await(auth.createCustomToken("user1", MapBuilder.of("claim", "value"))); FirebaseCustomAuthToken parsedToken = FirebaseCustomAuthToken.parse(new GsonFactory(), token); assertEquals(parsedToken.getPayload().getUid(), "user1"); assertEquals(parsedToken.getPayload().getSubject(), ServiceAccount.EDITOR.getEmail()); assertEquals(parsedToken.getPayload().getIssuer(), ServiceAccount.EDITOR.getEmail()); assertEquals(parsedToken.getPayload().getDeveloperClaims().keySet().size(), 1); assertEquals(parsedToken.getPayload().getDeveloperClaims().get("claim"), "value"); assertTrue(ServiceAccount.EDITOR.verifySignature(parsedToken)); } @Test(expected = Exception.class) public void testServiceAccountUsedAsRefreshToken() throws Exception { FirebaseOptions options = new FirebaseOptions.Builder() .setCredential(FirebaseCredentials.fromRefreshToken(ServiceAccount.EDITOR.asStream())) .build(); FirebaseApp app = FirebaseApp.initializeApp(options, "testCreateCustomToken"); Assert.assertNotNull( Tasks.await(TestOnlyImplFirebaseTrampolines.getToken(app, false)).getToken()); } @Test public void testCredentialCertificateRequired() throws Exception { if (isCertCredential) { Log.i(TAG, "Skipping testCredentialCertificateRequired for cert credential"); return; } FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "testCredentialCertificateRequired"); try { Tasks.await(FirebaseAuth.getInstance(app).verifyIdToken("foo")); fail("Expected exception."); } catch (Exception expected) { Assert.assertEquals( "com.google.firebase.FirebaseException: Must initialize FirebaseApp with a certificate " + "credential to call verifyIdToken()", expected.getMessage()); } try { Tasks.await(FirebaseAuth.getInstance(app).createCustomToken("foo")); fail("Expected exception."); } catch (Exception expected) { Assert.assertEquals( "com.google.firebase.FirebaseException: Must initialize FirebaseApp with a certificate " + "credential to call createCustomToken()", expected.getMessage()); } } @Test(expected = IllegalArgumentException.class) public void testAuthExceptionNullErrorCode() { new FirebaseAuthException(null, "test"); } @Test(expected = IllegalArgumentException.class) public void testAuthExceptionEmptyErrorCode() { new FirebaseAuthException("", "test"); } }