Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to create FirebaseCredentials from GoogleCredentials #137

Merged
merged 5 commits into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ subprojects {
"com.google.http-client:google-http-client:1.29.0",
"com.google.http-client:google-http-client-jackson2:1.29.0",

"com.google.api-client:google-api-client:1.25.0",
"com.google.api-client:google-api-client:1.30.9",

"org.apache.httpcomponents:httpclient:4.5.5",

Expand Down
2 changes: 1 addition & 1 deletion client-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spine-web",
"version": "1.5.0",
"version": "1.5.2",
"license": "Apache-2.0",
"description": "A JS client for interacting with Spine applications.",
"homepage": "https://spine.io",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package io.spine.web.firebase;

import com.google.api.client.extensions.appengine.http.UrlFetchTransport;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
Expand Down Expand Up @@ -149,8 +148,7 @@ private static FirebaseClient createAuthorized(HttpTransport httpTransport,
FirebaseDatabase database,
FirebaseCredentials credentials,
Supplier<BackOff> backOff) {
GoogleCredential googleCredentials = credentials.credentials();
HttpRequestFactory requestFactory = httpTransport.createRequestFactory(googleCredentials);
HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credentials);
return RemoteDatabaseClient
.newBuilder()
.setDatabase(database)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,33 @@
package io.spine.web.firebase;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.annotations.VisibleForTesting;
import io.spine.annotation.Internal;
import org.checkerframework.checker.nullness.qual.Nullable;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static io.spine.util.Exceptions.newIllegalArgumentException;
import static java.util.Arrays.asList;

/**
* A Firebase Database credentials.
*
* <p>The underlying Google OAuth 2.0 service can be used as an authentication facility for the
* requests to Firebase REST API.
* <p>This type can be used as an authentication facility for the requests to Firebase REST API.
*
* <p>See <a href="https://firebase.google.com/docs/database/rest/auth">Firebase REST docs</a>.
*/
public final class FirebaseCredentials {
@SuppressWarnings("deprecation")
// Use deprecated `GoogleCredential` to retain backward compatibility.
public final class FirebaseCredentials implements HttpRequestInitializer {

private static final String AUTH_DATABASE = "https://www.googleapis.com/auth/firebase.database";
private static final String AUTH_USER_EMAIL = "https://www.googleapis.com/auth/userinfo.email";
Expand All @@ -49,14 +57,33 @@ public final class FirebaseCredentials {
*/
private static final Collection<String> AUTH_SCOPES = asList(AUTH_DATABASE, AUTH_USER_EMAIL);

private final GoogleCredential credentials;
/**
* The credential used for authentication.
*
* <p>Either this field or {@link #oldStyleCredential} will always be {@code null}.
*/
private final @Nullable GoogleCredentials credentials;

/**
* The credential used for authentication.
*
* <p>Either this field or {@link #credentials} will always be {@code null}.
*/
private final @Nullable GoogleCredential oldStyleCredential;

private FirebaseCredentials() {
this.credentials = null;
this.oldStyleCredential = null;
}

private FirebaseCredentials(GoogleCredential credentials) {
private FirebaseCredentials(GoogleCredentials credentials) {
this.credentials = credentials;
this.oldStyleCredential = null;
}

private FirebaseCredentials(GoogleCredential credential) {
this.credentials = null;
this.oldStyleCredential = credential;
}

/**
Expand All @@ -69,16 +96,32 @@ public static FirebaseCredentials empty() {
}

/**
* Creates a new {@code FirebaseCredentials} from the given {@link GoogleCredential}.
* Creates a new {@code FirebaseCredentials} from the given {@link GoogleCredentials}.
*
* <p>The method sets scopes required for Firebase.
* <p>This method sets scopes required to use the Firebase database.
*
* <p>The method is useful to create credentials from the
* {@linkplain GoogleCredential#getApplicationDefault() application default credentials}.
* <p>This method is useful to create credentials from the
* {@linkplain GoogleCredentials#getApplicationDefault() application default credentials}.
*
* @param credentials the credentials to create from
* @param credentials
* the credentials to create from
* @return a new instance of {@code FirebaseCredentials}
*/
public static FirebaseCredentials fromGoogleCredentials(GoogleCredentials credentials) {
checkNotNull(credentials);
GoogleCredentials scopedCredential = credentials.createScoped(AUTH_SCOPES);
return new FirebaseCredentials(scopedCredential);
}

/**
* Creates a new {@code FirebaseCredentials} from the given {@link GoogleCredentials}.
*
* <p>This method sets scopes required to use the Firebase database.
*
* @deprecated please use {@link #fromGoogleCredentials(GoogleCredentials)} or any other
* alternative instead
*/
@Deprecated
public static FirebaseCredentials fromGoogleCredentials(GoogleCredential credentials) {
checkNotNull(credentials);
GoogleCredential scopedCredential = credentials.createScoped(AUTH_SCOPES);
Expand All @@ -98,37 +141,50 @@ public static FirebaseCredentials fromGoogleCredentials(GoogleCredential credent
* @return a new instance of {@code FirebaseCredentials}
* @throws java.lang.IllegalArgumentException
* in case there are problems with parsing the given stream into
* {@link GoogleCredential}
* {@link GoogleCredentials}
*/
public static FirebaseCredentials fromStream(InputStream credentialStream) {
checkNotNull(credentialStream);
GoogleCredential credentials = parseCredentials(credentialStream);
GoogleCredentials credentials = parseCredentials(credentialStream);
return fromGoogleCredentials(credentials);
}

/**
* Checks if the instance of the {@code FirebaseCredentials} is empty.
* Authenticates a given {@link HttpRequest} with the wrapped Google credentials instance.
*
* @return {@code true} if {@code FirebaseCredentials} are empty and {@code false} otherwise
* @throws IllegalStateException
* if this instance of {@code FirebaseCredentials} is {@linkplain #isEmpty() empty}
*/
public boolean isEmpty() {
return credentials == null;
@Internal
@Override
public void initialize(HttpRequest request) throws IOException {
checkState(!isEmpty(),
"An empty credentials instance cannot serve as HTTP request initializer.");
if (isOldStyle()) {
oldStyleCredential.initialize(request);
} else {
HttpRequestInitializer adapter = new HttpCredentialsAdapter(credentials);
adapter.initialize(request);
}
}

/**
* Returns the underlying Google credentials.
* Checks if the instance of the {@code FirebaseCredentials} is empty.
*
* @return the underlying credentials or {@code null} if the credentials are
* {@linkplain #isEmpty() empty}
* @return {@code true} if {@code FirebaseCredentials} are empty and {@code false} otherwise
*/
@Nullable
GoogleCredential credentials() {
return credentials;
public boolean isEmpty() {
return credentials == null && oldStyleCredential == null;
}

@VisibleForTesting
boolean isOldStyle() {
return oldStyleCredential != null;
}

private static GoogleCredential parseCredentials(InputStream credentialStream) {
private static GoogleCredentials parseCredentials(InputStream credentialStream) {
try {
GoogleCredential credentials = GoogleCredential.fromStream(credentialStream);
GoogleCredentials credentials = GoogleCredentials.fromStream(credentialStream);
return credentials;
} catch (IOException e) {
throw newIllegalArgumentException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

package io.spine.web.firebase;

import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.testing.NullPointerTester;
Expand All @@ -39,16 +38,12 @@
import java.util.Date;

import static com.google.common.truth.Truth.assertThat;
import static io.spine.web.firebase.FirebaseCredentials.fromGoogleCredentials;
import static org.mockito.Mockito.mock;

@DisplayName("FirebaseClientFactory should")
@DisplayName("`FirebaseClientFactory` should")
class FirebaseClientFactoryTest extends UtilityClassTest<FirebaseClientFactory> {

private static final MockGoogleCredential GOOGLE_CREDENTIALS =
new MockGoogleCredential.Builder().build();
private static final FirebaseCredentials CREDENTIALS =
fromGoogleCredentials(GOOGLE_CREDENTIALS);
private static final FirebaseCredentials CREDENTIALS = FirebaseCredentials.empty();
private static final String FIREBASE_APP_NAME = FirebaseClientFactoryTest.class.getSimpleName();
private FirebaseDatabase database = mock(FirebaseDatabase.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@

package io.spine.web.firebase;

import com.google.api.client.googleapis.testing.auth.oauth2.MockGoogleCredential;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.testing.NullPointerTester;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import static com.google.common.truth.Truth.assertThat;
import static io.spine.testing.DisplayNames.NOT_ACCEPT_NULLS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertThrows;

@DisplayName("FirebaseCredentials should")
@DisplayName("`FirebaseCredentials` should")
class FirebaseCredentialsTest {

@Test
Expand All @@ -40,9 +44,43 @@ void passNullToleranceCheck() {
new NullPointerTester().testAllPublicStaticMethods(FirebaseCredentials.class);
}

@Nested
@DisplayName("be created")
class BeCreated {

@Test
@DisplayName("without credentials")
void empty() {
FirebaseCredentials credentials = FirebaseCredentials.empty();
assertThat(credentials.isEmpty()).isTrue();
}

@Test
@DisplayName("from `GoogleCredentials` instance")
void fromGoogleCredentials() {
GoogleCredentials googleCredentials = GoogleCredentials.newBuilder()
.build();
FirebaseCredentials credentials =
FirebaseCredentials.fromGoogleCredentials(googleCredentials);
assertThat(credentials.isEmpty()).isFalse();
assertThat(credentials.isOldStyle()).isFalse();
}

@SuppressWarnings("deprecation") // This test checks the deprecated method.
@Test
@DisplayName("from `GoogleCredential` instance")
void fromOldStyleCredentials() {
MockGoogleCredential googleCredential = new MockGoogleCredential.Builder().build();
FirebaseCredentials credentials =
FirebaseCredentials.fromGoogleCredentials(googleCredential);
assertThat(credentials.isEmpty()).isFalse();
assertThat(credentials.isOldStyle()).isTrue();
}
}

@SuppressWarnings({"CheckReturnValue", "ResultOfMethodCallIgnored"})
@Test
@DisplayName("throw IAE when created from invalid data")
@DisplayName("throw `IAE` when created from invalid data")
void throwOnInvalidData() {
String invalidCredentials = "invalid_credentials";
InputStream stream = toInputStream(invalidCredentials);
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/js-tests/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "client-js-tests",
"version": "1.5.0",
"version": "1.5.2",
"license": "Apache-2.0",
"description": "Tests of a `spine-web` JS library against the Spine-based application.",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

package io.spine.web.test.given;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
Expand Down Expand Up @@ -95,12 +94,9 @@ static Application create(BoundedContext boundedContext) {
private static FirebaseClient buildClient() {
Resource googleCredentialsFile = Resource.file("spine-dev.json");

// Same credentials but represented with different Java objects.
GoogleCredentials credentials;
GoogleCredential credential;
try {
credentials = GoogleCredentials.fromStream(googleCredentialsFile.open());
credential = GoogleCredential.fromStream(googleCredentialsFile.open());
} catch (IOException e) {
throw new IllegalStateException(e);
}
Expand All @@ -112,7 +108,7 @@ private static FirebaseClient buildClient() {
FirebaseApp.initializeApp(options);

FirebaseDatabase database = FirebaseDatabase.getInstance();
FirebaseCredentials firebaseCredentials = fromGoogleCredentials(credential);
FirebaseCredentials firebaseCredentials = fromGoogleCredentials(credentials);
FirebaseClient firebaseClient = remoteClient(database, firebaseCredentials);
return new TidyClient(firebaseClient);
}
Expand Down
Loading