/* * 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.database.integration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableMap; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.auth.FirebaseCredentials; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.TestHelpers; import com.google.firebase.database.ValueEventListener; import com.google.firebase.tasks.OnCompleteListener; import com.google.firebase.tasks.Task; import com.google.firebase.testing.IntegrationTestUtils; import com.google.firebase.testing.IntegrationTestUtils.AppHttpClient; import com.google.firebase.testing.IntegrationTestUtils.ResponseInfo; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.IOException; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; public class FirebaseDatabaseAuthTestIT { private static FirebaseApp masterApp; @BeforeClass public static void setUpClass() throws IOException { masterApp = IntegrationTestUtils.ensureDefaultApp(); setDatabaseRules(); } @Before public void prepareApp() { TestHelpers.wrapForErrorHandling(masterApp); } @After public void checkAndCleanupApp() { TestHelpers.assertAndUnwrapErrorHandlers(masterApp); } @Test public void testAuthWithValidCertificateCredential() throws InterruptedException { FirebaseDatabase db = FirebaseDatabase.getInstance(); assertWriteSucceeds(db.getReference()); assertReadSucceeds(db.getReference()); } @Test public void testAuthWithInvalidCertificateCredential() throws InterruptedException, IOException { FirebaseOptions options = new FirebaseOptions.Builder() .setDatabaseUrl(IntegrationTestUtils.getDatabaseUrl()) .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.NONE.asStream())) .build(); FirebaseApp app = FirebaseApp.initializeApp(options, "DatabaseServerAuthTestNoRole"); FirebaseDatabase db = FirebaseDatabase.getInstance(app); // TODO: Ideally, we would find a way to verify the correct log output. assertWriteTimeout(db.getReference()); } @Test public void testDatabaseAuthVariablesAuthorization() throws InterruptedException { Map<String, Object> authVariableOverrides = ImmutableMap.<String, Object>of( "uid", "test", "custom", "secret" ); FirebaseOptions options = new FirebaseOptions.Builder(masterApp.getOptions()) .setDatabaseAuthVariableOverride(authVariableOverrides) .build(); FirebaseApp testUidApp = FirebaseApp.initializeApp(options, "testGetAppWithUid"); FirebaseDatabase masterDb = FirebaseDatabase.getInstance(masterApp); FirebaseDatabase testAuthOverridesDb = FirebaseDatabase.getInstance(testUidApp); assertWriteSucceeds(masterDb.getReference()); // "test" UID can only read/write to /test-uid-only and /test-custom-field-only locations. assertWriteFails(testAuthOverridesDb.getReference()); assertWriteSucceeds(testAuthOverridesDb.getReference("test-uid-only")); assertReadSucceeds(testAuthOverridesDb.getReference("test-uid-only")); assertWriteSucceeds(testAuthOverridesDb.getReference("test-custom-field-only")); assertReadSucceeds(testAuthOverridesDb.getReference("test-custom-field-only")); } @Test public void testDatabaseAuthVariablesNoAuthorization() throws InterruptedException { FirebaseOptions options = new FirebaseOptions.Builder(masterApp.getOptions()) .setDatabaseAuthVariableOverride(null) .build(); FirebaseApp testUidApp = FirebaseApp.initializeApp(options, "testServiceAccountDatabaseWithNoAuth"); FirebaseDatabase masterDb = FirebaseDatabase.getInstance(masterApp); FirebaseDatabase testAuthOverridesDb = FirebaseDatabase.getInstance(testUidApp); assertWriteSucceeds(masterDb.getReference()); assertWriteFails(testAuthOverridesDb.getReference("test-uid-only")); assertReadFails(testAuthOverridesDb.getReference("test-uid-only")); assertWriteFails(testAuthOverridesDb.getReference("test-custom-field-only")); assertReadFails(testAuthOverridesDb.getReference("test-custom-field-only")); assertWriteSucceeds(testAuthOverridesDb.getReference("test-noauth-only")); } private static void assertWriteSucceeds(DatabaseReference ref) throws InterruptedException { doWrite(ref, /*shouldSucceed=*/ true, /*shouldTimeout=*/ false); } private static void assertWriteFails(DatabaseReference ref) throws InterruptedException { doWrite(ref, /*shouldSucceed=*/ false, /*shouldTimeout=*/ false); } private static void assertWriteTimeout(DatabaseReference ref) throws InterruptedException { doWrite(ref, /*shouldSucceed=*/ false, /*shouldTimeout=*/ true); } private static void assertReadSucceeds(DatabaseReference ref) throws InterruptedException { doRead(ref, /*shouldSucceed=*/ true, /*shouldTimeout=*/ false); } private static void assertReadFails(DatabaseReference ref) throws InterruptedException { doRead(ref, /*shouldSucceed=*/ false, /*shouldTimeout=*/ false); } private static void doWrite( DatabaseReference ref, final boolean shouldSucceed, final boolean shouldTimeout) throws InterruptedException { final CountDownLatch lock = new CountDownLatch(1); final AtomicBoolean success = new AtomicBoolean(false); ref.setValue("wrote something") .addOnCompleteListener( new OnCompleteListener<Void>() { @Override public void onComplete(Task<Void> task) { success.compareAndSet(false, task.isSuccessful()); lock.countDown(); } }); boolean finished = lock.await(TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); if (shouldTimeout) { assertTrue("Write finished (expected to timeout).", !finished); } else if (shouldSucceed) { assertTrue("Write timed out (expected to succeed)", finished); assertTrue("Write failed (expected to succeed).", success.get()); } else { assertTrue("Write timed out (expected to fail).", finished); assertTrue("Write successful (expected to fail).", !success.get()); } } private static void doRead( DatabaseReference ref, final boolean shouldSucceed, final boolean shouldTimeout) throws InterruptedException { final CountDownLatch lock = new CountDownLatch(1); final AtomicBoolean success = new AtomicBoolean(false); ref.addListenerForSingleValueEvent( new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { success.compareAndSet(false, true); lock.countDown(); } @Override public void onCancelled(DatabaseError databaseError) { lock.countDown(); } }); boolean finished = lock.await(TestUtils.TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); if (shouldTimeout) { assertTrue("Read finished (expected to timeout).", !finished); } else if (shouldSucceed) { assertTrue("Read timed out (expected to succeed).", finished); assertTrue("Read failed (expected to succeed).", success.get()); } else { assertTrue("Read timed out (expected to fail).", finished); assertTrue("Read successful (expected to fail).", !success.get()); } } private static void setDatabaseRules() throws IOException { // TODO: Use more than uid in rule Set rules so the only allowed operation is writing to // /test-uid-only by user with uid 'test'. String rules = "{\n" + " \"rules\": {\n" + " \"test-uid-only\": {\n" + " \".read\": \"auth.uid == 'test'\",\n" + " \".write\": \"auth.uid == 'test'\"\n" + " },\n" + " \"test-custom-field-only\": {\n" + " \".read\": \"auth.custom == 'secret'\",\n" + " \".write\": \"auth.custom == 'secret'\"\n" + " },\n" + " \"test-noauth-only\": {\n" + " \".write\": \"auth == null\"\n" + " }\n" + " }\n" + "}"; AppHttpClient client = new AppHttpClient(); ResponseInfo info = client.put("/.settings/rules.json", rules); assertEquals(200, info.getStatus()); } }