-
Notifications
You must be signed in to change notification settings - Fork 617
Adding Util class for FIrebaseInstallations APIs. #676
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
Changes from 41 commits
d39159b
e321d7d
c25ca83
6c54e73
1923730
e79e3ad
bb41402
2851062
a2f7f64
cde2320
a27b347
53dc90b
a7982e1
7285408
19c5573
892fddf
8d3dc73
4ad27d7
7f45186
1b1766f
4ac3956
da9aac6
e9c10c2
ef6cf7d
284ab52
4332d7f
113216e
936e2f8
1f427eb
5462241
aa21227
23ff7f2
7da76c0
5152bf0
97f461a
124227c
1ccde5e
0a79342
71c90f0
d11417b
a2e0dba
8706286
34848c6
f4c44b0
44bef2f
e5e0d67
429581e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright 2019 Google LLC | ||
// | ||
// 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.installation; | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import androidx.test.runner.AndroidJUnit4; | ||
import org.junit.FixMethodOrder; | ||
import org.junit.runner.RunWith; | ||
import org.junit.runners.MethodSorters; | ||
|
||
/** | ||
* Instrumented test, which will execute on an Android device. | ||
* | ||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||
*/ | ||
@RunWith(AndroidJUnit4.class) | ||
@FixMethodOrder(MethodSorters.NAME_ASCENDING) | ||
public class FirebaseInstallationInstrumentedTest {} | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright 2019 Google LLC | ||
// | ||
// 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.installations; | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import androidx.annotation.NonNull; | ||
import com.google.firebase.FirebaseException; | ||
|
||
/** The class for all Exceptions thrown by {@link FirebaseInstallations}. */ | ||
public class FirebaseInstallationException extends FirebaseException { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public enum Status { | ||
SDK_INTERNAL_ERROR, | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest to sync with mertcan@ or Rayo about what status we want to expose to dev There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andirayo or @mmermerkaya Whats the high level exception code to be shown to developers in case FIS requests fail? Currently, I have SDK_INTERNAL_ERROR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would check with Mertcan's Web-SDK or Maksym's iOS-SDK, but as far as I remember (without checking) I think we are pretty explicit with our error messages. |
||
CLIENT_ERROR | ||
} | ||
|
||
@NonNull private final Status status; | ||
|
||
public FirebaseInstallationException(@NonNull Status status) { | ||
this.status = status; | ||
} | ||
|
||
public FirebaseInstallationException(@NonNull String message, @NonNull Status status) { | ||
super(message); | ||
this.status = status; | ||
} | ||
|
||
public FirebaseInstallationException( | ||
@NonNull String message, @NonNull Status status, @NonNull Throwable cause) { | ||
super(message, cause); | ||
this.status = status; | ||
} | ||
|
||
/** | ||
* Gets the status status for the operation that failed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. status status? |
||
* | ||
* @return the status for the FirebaseInstallationsException | ||
*/ | ||
@NonNull | ||
public Status getStatus() { | ||
return status; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Copyright 2019 Google LLC | ||
// | ||
// 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.installations; | ||
|
||
import androidx.annotation.NonNull; | ||
import java.nio.charset.Charset; | ||
import java.util.UUID; | ||
|
||
/** Util methods used for {@link FirebaseInstallations} */ | ||
class Utils { | ||
|
||
/** | ||
* 1 Byte with the first 4 header-bits set to the identifying FID prefix 0111 (0x7). Use this | ||
* constant to create FIDs or check the first byte of FIDs. This prefix is also used in legacy | ||
* Instance-IDs | ||
*/ | ||
public static final byte FID_4BIT_PREFIX = Byte.parseByte("01110000", 2); | ||
|
||
/** | ||
* Byte mask to remove the 4 header-bits of a given Byte. Use this constant with Java's Binary AND | ||
* Operator in order to remove the first 4 bits of a Byte and replacing it with the FID prefix. | ||
*/ | ||
public static final byte REMOVE_PREFIX_MASK = Byte.parseByte("00001111", 2); | ||
|
||
/** Length of new-format FIDs. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe add " (as introduced 2019)" |
||
public static final int FID_LENGTH = 22; | ||
|
||
/** | ||
* Creates a random FID of valid format without checking if the FID is already in use by any | ||
* Firebase Installation. | ||
* | ||
* <p>Note: Even though this method does not check with the FIS database if the returned FID is | ||
* already in use, the probability of collision is extremely and negligibly small! | ||
* | ||
* @return random FID value | ||
*/ | ||
@NonNull | ||
public static String createRandomFid() { | ||
// A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes. | ||
// We create 17 random bytes and ignore the last base64 character later. | ||
byte[] bytes = UUID.randomUUID().toString().substring(0, 17).getBytes(Charset.defaultCharset()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @diwu-arete Please check if this is inline with the comments on the doc There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is the right thing to do, and your encoding logic also looks good to me. But definitely please ping Rayo to take a look at these key logic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andirayo Can you please review the random fid generation logic? Thanks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does NOT look good to me. I don't know what #randomUUID() does, or more precisely I don't know what #toString() does, but I fear that this may return the default UUID format containing dashes ("-"). ======================= ======================= There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for detailed explanation. As discussed through emails, I have implemented the FID creation as per Maksyms approach and now creates 2^128 FIDs. wdyt of this approach? |
||
|
||
// Replace the first 4 random bits with the constant FID header of 0x7 (0b0111). | ||
bytes[0] = (byte) (FID_4BIT_PREFIX | (bytes[0] & REMOVE_PREFIX_MASK)); | ||
|
||
return encodeFidBase64UrlSafe(bytes); | ||
} | ||
|
||
/** | ||
* Converts a given byte-array (assumed to be an FID value) to base64-url-safe encoded | ||
* String-representation. | ||
* | ||
* <p>Note: The returned String has at most 22 characters, the length of FIDs. Thus, it is | ||
* recommended to deliver a byte-array containing at least 16.5 bytes. | ||
* | ||
* @param rawValue FID value to be encoded | ||
* @return (22-character or shorter) String containing the base64-encoded value | ||
*/ | ||
private static String encodeFidBase64UrlSafe(byte[] rawValue) { | ||
return new String( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I don't know enough of what is actually happening here, but why do we have to call "new String" here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. android.util.Base64.encode returns bytes. |
||
android.util.Base64.encode( | ||
rawValue, | ||
android.util.Base64.URL_SAFE | ||
| android.util.Base64.NO_PADDING | ||
| android.util.Base64.NO_WRAP), | ||
Charset.defaultCharset()) | ||
.substring(0, FID_LENGTH); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,10 +32,10 @@ public class FirebaseInstallationServiceClient { | |
"firebaseinstallations.googleapis.com"; | ||
private static final String CREATE_REQUEST_RESOURCE_NAME_FORMAT = "projects/%s/installations"; | ||
private static final String GENERATE_AUTH_TOKEN_REQUEST_RESOURCE_NAME_FORMAT = | ||
"projects/%s/installations/%s/auth:generate"; | ||
"projects/%s/installations/%s/authTokens:generate"; | ||
private static final String DELETE_REQUEST_RESOURCE_NAME_FORMAT = "projects/%s/installations/%s"; | ||
private static final String FIREBASE_INSTALLATIONS_API_VERSION = "v1"; | ||
private static final String FIREBASE_INSTALLATION_AUTH_VERSION = "FIS_V2"; | ||
private static final String FIREBASE_INSTALLATION_AUTH_VERSION = "FIS_v2"; | ||
|
||
private static final String CONTENT_TYPE_HEADER_KEY = "Content-Type"; | ||
private static final String ACCEPT_HEADER_KEY = "Accept"; | ||
|
@@ -50,12 +50,12 @@ public class FirebaseInstallationServiceClient { | |
|
||
@NonNull | ||
public InstallationResponse createFirebaseInstallation( | ||
long projectNumber, | ||
@NonNull String projectID, | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@NonNull String apiKey, | ||
@NonNull String firebaseInstallationId, | ||
@NonNull String appId) | ||
throws FirebaseInstallationServiceException { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
String resourceName = String.format(CREATE_REQUEST_RESOURCE_NAME_FORMAT, projectNumber); | ||
String resourceName = String.format(CREATE_REQUEST_RESOURCE_NAME_FORMAT, projectID); | ||
try { | ||
URL url = | ||
new URL( | ||
|
@@ -91,16 +91,16 @@ public InstallationResponse createFirebaseInstallation( | |
return readCreateResponse(httpsURLConnection); | ||
case 401: | ||
throw new FirebaseInstallationServiceException( | ||
UNAUTHORIZED_ERROR_MESSAGE, FirebaseInstallationServiceException.Code.UNAUTHORIZED); | ||
UNAUTHORIZED_ERROR_MESSAGE, FirebaseInstallationServiceException.Status.UNAUTHORIZED); | ||
default: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this ever happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a TODO in FirebaseInstallationsException class. I will send out another PR for exception handling changes. |
||
throw new FirebaseInstallationServiceException( | ||
INTERNAL_SERVER_ERROR_MESSAGE, | ||
FirebaseInstallationServiceException.Code.SERVER_ERROR); | ||
FirebaseInstallationServiceException.Status.SERVER_ERROR); | ||
} | ||
} catch (IOException e) { | ||
throw new FirebaseInstallationServiceException( | ||
NETWORK_ERROR_MESSAGE + e.getMessage(), | ||
FirebaseInstallationServiceException.Code.NETWORK_ERROR); | ||
FirebaseInstallationServiceException.Status.NETWORK_ERROR); | ||
} | ||
} | ||
|
||
|
@@ -109,15 +109,18 @@ private static JSONObject buildCreateFirebaseInstallationRequestBody(String fid, | |
JSONObject firebaseInstallationData = new JSONObject(); | ||
firebaseInstallationData.put("fid", fid); | ||
firebaseInstallationData.put("appId", appId); | ||
firebaseInstallationData.put("appVersion", FIREBASE_INSTALLATION_AUTH_VERSION); | ||
firebaseInstallationData.put("authVersion", FIREBASE_INSTALLATION_AUTH_VERSION); | ||
return firebaseInstallationData; | ||
} | ||
|
||
@NonNull | ||
public void deleteFirebaseInstallation( | ||
long projectNumber, @NonNull String apiKey, @NonNull String fid, @NonNull String refreshToken) | ||
@NonNull String projectID, | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@NonNull String apiKey, | ||
@NonNull String fid, | ||
@NonNull String refreshToken) | ||
throws FirebaseInstallationServiceException { | ||
String resourceName = String.format(DELETE_REQUEST_RESOURCE_NAME_FORMAT, projectNumber, fid); | ||
String resourceName = String.format(DELETE_REQUEST_RESOURCE_NAME_FORMAT, projectID, fid); | ||
try { | ||
URL url = | ||
new URL( | ||
|
@@ -131,7 +134,7 @@ public void deleteFirebaseInstallation( | |
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection(); | ||
httpsURLConnection.setDoOutput(true); | ||
httpsURLConnection.setRequestMethod("DELETE"); | ||
httpsURLConnection.addRequestProperty("Authorization", "FIS_V2 " + refreshToken); | ||
httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken); | ||
httpsURLConnection.addRequestProperty(CONTENT_TYPE_HEADER_KEY, JSON_CONTENT_TYPE); | ||
httpsURLConnection.addRequestProperty(CONTENT_ENCODING_HEADER_KEY, GZIP_CONTENT_ENCODING); | ||
|
||
|
@@ -141,25 +144,28 @@ public void deleteFirebaseInstallation( | |
return; | ||
case 401: | ||
throw new FirebaseInstallationServiceException( | ||
UNAUTHORIZED_ERROR_MESSAGE, FirebaseInstallationServiceException.Code.UNAUTHORIZED); | ||
UNAUTHORIZED_ERROR_MESSAGE, FirebaseInstallationServiceException.Status.UNAUTHORIZED); | ||
default: | ||
throw new FirebaseInstallationServiceException( | ||
INTERNAL_SERVER_ERROR_MESSAGE, | ||
FirebaseInstallationServiceException.Code.SERVER_ERROR); | ||
FirebaseInstallationServiceException.Status.SERVER_ERROR); | ||
} | ||
} catch (IOException e) { | ||
throw new FirebaseInstallationServiceException( | ||
NETWORK_ERROR_MESSAGE + e.getMessage(), | ||
FirebaseInstallationServiceException.Code.NETWORK_ERROR); | ||
FirebaseInstallationServiceException.Status.NETWORK_ERROR); | ||
} | ||
} | ||
|
||
@NonNull | ||
public InstallationTokenResult generateAuthToken( | ||
long projectNumber, @NonNull String apiKey, @NonNull String fid, @NonNull String refreshToken) | ||
@NonNull String projectID, | ||
@NonNull String apiKey, | ||
@NonNull String fid, | ||
@NonNull String refreshToken) | ||
throws FirebaseInstallationServiceException { | ||
String resourceName = | ||
String.format(GENERATE_AUTH_TOKEN_REQUEST_RESOURCE_NAME_FORMAT, projectNumber, fid); | ||
String.format(GENERATE_AUTH_TOKEN_REQUEST_RESOURCE_NAME_FORMAT, projectID, fid); | ||
try { | ||
URL url = | ||
new URL( | ||
|
@@ -173,7 +179,7 @@ public InstallationTokenResult generateAuthToken( | |
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection(); | ||
httpsURLConnection.setDoOutput(true); | ||
httpsURLConnection.setRequestMethod("POST"); | ||
httpsURLConnection.addRequestProperty("Authorization", "FIS_V2 " + refreshToken); | ||
httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken); | ||
httpsURLConnection.addRequestProperty(CONTENT_TYPE_HEADER_KEY, JSON_CONTENT_TYPE); | ||
httpsURLConnection.addRequestProperty(ACCEPT_HEADER_KEY, JSON_CONTENT_TYPE); | ||
httpsURLConnection.addRequestProperty(CONTENT_ENCODING_HEADER_KEY, GZIP_CONTENT_ENCODING); | ||
|
@@ -184,16 +190,16 @@ public InstallationTokenResult generateAuthToken( | |
return readGenerateAuthTokenResponse(httpsURLConnection); | ||
case 401: | ||
throw new FirebaseInstallationServiceException( | ||
UNAUTHORIZED_ERROR_MESSAGE, FirebaseInstallationServiceException.Code.UNAUTHORIZED); | ||
UNAUTHORIZED_ERROR_MESSAGE, FirebaseInstallationServiceException.Status.UNAUTHORIZED); | ||
default: | ||
throw new FirebaseInstallationServiceException( | ||
INTERNAL_SERVER_ERROR_MESSAGE, | ||
FirebaseInstallationServiceException.Code.SERVER_ERROR); | ||
FirebaseInstallationServiceException.Status.SERVER_ERROR); | ||
} | ||
} catch (IOException e) { | ||
throw new FirebaseInstallationServiceException( | ||
NETWORK_ERROR_MESSAGE + e.getMessage(), | ||
FirebaseInstallationServiceException.Code.NETWORK_ERROR); | ||
FirebaseInstallationServiceException.Status.NETWORK_ERROR); | ||
} | ||
} | ||
// Read the response from the createFirebaseInstallation API. | ||
|
@@ -214,7 +220,7 @@ private InstallationResponse readCreateResponse(HttpsURLConnection conn) throws | |
while (reader.hasNext()) { | ||
String key = reader.nextName(); | ||
if (key.equals("token")) { | ||
installationTokenResult.setAuthToken(reader.nextString()); | ||
installationTokenResult.setToken(reader.nextString()); | ||
} else if (key.equals("expiresIn")) { | ||
installationTokenResult.setTokenExpirationTimestampMillis(reader.nextLong()); | ||
} else { | ||
|
@@ -242,7 +248,7 @@ private InstallationTokenResult readGenerateAuthTokenResponse(HttpsURLConnection | |
while (reader.hasNext()) { | ||
String name = reader.nextName(); | ||
if (name.equals("token")) { | ||
builder.setAuthToken(reader.nextString()); | ||
builder.setToken(reader.nextString()); | ||
} else if (name.equals("expiresIn")) { | ||
builder.setTokenExpirationTimestampMillis(reader.nextLong()); | ||
} else { | ||
|
Uh oh!
There was an error while loading. Please reload this page.