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

FIS: Additional FIRInstallationsItem validation #6570

Merged
merged 12 commits into from
Sep 30, 2020
3 changes: 3 additions & 0 deletions FirebaseInstallations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# v1.7.1 -- Unreleased
- [changed] Additional `FIRInstallationsItem` validation to catch potential storage issues. (#6570)

# v1.7.0 -- M78
- [changed] Use ephemeral `NSURLSession` to prevent caching of request/response. (#6226)
- [changed] Backoff added for some error to prevent unnecessary API requests. (#6232)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer);

+ (FBLPromise *)rejectedPromiseWithError:(NSError *)error;

+ (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code
failureReason:(nullable NSString *)failureReason
underlyingError:(nullable NSError *)underlyingError;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ + (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code
underlyingError:(nullable NSError *)underlyingError {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[NSUnderlyingErrorKey] = underlyingError;
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
userInfo[NSLocalizedFailureReasonErrorKey] =
failureReason
?: [NSString
stringWithFormat:@"Underlying error: %@", underlyingError.localizedDescription];

return [NSError errorWithDomain:kFirebaseInstallationsErrorDomain code:code userInfo:userInfo];
}
Expand Down
7 changes: 7 additions & 0 deletions FirebaseInstallations/Source/Library/FIRInstallationsItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSString *)identifier;

/** Validates if all the required item fields are populated and values don't explicitly conflict
* with each other.
* @param outError A reference to be populated with an error containing validation failure details.
* @return `YES` if the item it valid, `NO` otherwise.
*/
- (BOOL)isValid:(NSError *_Nullable *)outError;

/**
* The installation identifier.
* @param appID A `FirebaseApp` identifier.
Expand Down
57 changes: 57 additions & 0 deletions FirebaseInstallations/Source/Library/FIRInstallationsItem.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h"
#import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h"

#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h"

@implementation FIRInstallationsItem

- (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName {
Expand All @@ -37,6 +39,7 @@ - (nonnull id)copyWithZone:(nullable NSZone *)zone {
clone.refreshToken = [self.refreshToken copy];
clone.authToken = [self.authToken copy];
clone.registrationStatus = self.registrationStatus;
clone.IIDDefaultToken = [self.IIDDefaultToken copy];

return clone;
}
Expand All @@ -63,6 +66,60 @@ - (nonnull NSString *)identifier {
return [[self class] identifierWithAppID:self.appID appName:self.firebaseAppName];
}

- (BOOL)isValid:(NSError *_Nullable *)outError {
NSMutableArray<NSString *> *validationIssues = [NSMutableArray array];

if (self.appID.length == 0) {
[validationIssues addObject:@"`appID` must not be empty"];
}

if (self.firebaseAppName.length == 0) {
[validationIssues addObject:@"`firebaseAppName` must not be empty"];
}

if (self.firebaseInstallationID.length == 0) {
[validationIssues addObject:@"`firebaseInstallationID` must not be empty"];
}

switch (self.registrationStatus) {
case FIRInstallationStatusUnknown:
[validationIssues addObject:@"invalid `registrationStatus`"];
break;

case FIRInstallationStatusRegistered:
if (self.refreshToken == 0) {
[validationIssues addObject:@"registered installation must have non-empty `refreshToken`"];
}

if (self.authToken.token == 0) {
[validationIssues
addObject:@"registered installation must have non-empty `authToken.token`"];
}

if (self.authToken.expirationDate == nil) {
[validationIssues
addObject:@"registered installation must have non-empty `authToken.expirationDate`"];
}

case FIRInstallationStatusUnregistered:
break;
}

BOOL isValid = validationIssues.count == 0;

if (!isValid && outError) {
NSString *failureReason =
[NSString stringWithFormat:@"FIRInstallationsItem validation errors: %@",
[validationIssues componentsJoinedByString:@", "]];
*outError =
[FIRInstallationsErrorUtil installationsErrorWithCode:FIRInstallationsErrorCodeUnknown
failureReason:failureReason
underlyingError:nil];
}

return isValid;
}

+ (NSString *)identifierWithAppID:(NSString *)appID appName:(NSString *)appName {
return [appID stringByAppendingString:appName];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extern NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCr
extern NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated;
extern NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated;
extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration;
extern NSString *const kFIRInstallationsMessageCodeCorruptedStoredInstallation;

// FIRInstallationsStoredItem.m
extern NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated = @"I-FIS002001";
NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated = @"I-FIS002002";
NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration = @"I-FIS002003";
NSString *const kFIRInstallationsMessageCodeCorruptedStoredInstallation = @"I-FIS002004";

// FIRInstallationsStoredItem.m
NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch = @"I-FIS003000";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ - (instancetype)initWithURLSession:(NSURLSession *)URLSession
#pragma mark - Public

- (FBLPromise<FIRInstallationsItem *> *)registerInstallation:(FIRInstallationsItem *)installation {
return [self registerRequestWithInstallation:installation]
return [self validateInstallation:installation]
.then(^id _Nullable(FIRInstallationsItem *_Nullable validInstallation) {
return [self registerRequestWithInstallation:validInstallation];
})
.then(^id _Nullable(NSURLRequest *_Nullable request) {
return [self sendURLRequest:request];
})
Expand Down Expand Up @@ -138,7 +141,9 @@ - (instancetype)initWithURLSession:(NSURLSession *)URLSession
NSURL *URL = [NSURL URLWithString:URLString];

NSDictionary *bodyDict = @{
@"fid" : installation.firebaseInstallationID,
// `firebaseInstallationID` is validated before but let's make sure it is not `nil` one more
// time to prevent a crash.
@"fid" : installation.firebaseInstallationID ?: @"",
@"authVersion" : @"FIS_v2",
@"appId" : installation.appID,
@"sdkVersion" : [self SDKVersion]
Expand Down Expand Up @@ -346,6 +351,20 @@ - (NSString *)SDKVersion {
return [NSString stringWithFormat:@"i:%s", FIRInstallationsVersionStr];
}

#pragma mark - Validation

- (FBLPromise<FIRInstallationsItem *> *)validateInstallation:(FIRInstallationsItem *)installation {
FBLPromise<FIRInstallationsItem *> *result = [FBLPromise pendingPromise];

NSError *validationError;
if ([installation isValid:&validationError]) {
[result fulfill:installation];
} else {
[result reject:validationError];
}
return result;
}

#pragma mark - JSON

- (void)setJSONHTTPBody:(NSDictionary<NSString *, id> *)body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,11 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID
- (FBLPromise<FIRInstallationsItem *> *)getStoredInstallation {
return [self.installationsStore installationForAppID:self.appID appName:self.appName].validate(
^BOOL(FIRInstallationsItem *installation) {
BOOL isValid = NO;
switch (installation.registrationStatus) {
case FIRInstallationStatusUnregistered:
case FIRInstallationStatusRegistered:
isValid = YES;
break;

case FIRInstallationStatusUnknown:
isValid = NO;
break;
}

NSError *validationError;
BOOL isValid = [installation isValid:&validationError];
FIRLogWarning(
kFIRLoggerInstallations, kFIRInstallationsMessageCodeCorruptedStoredInstallation,
@"Stored installation validation error: %@", validationError.localizedDescription);
return isValid;
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "projects/project-id/installations/eapzYQai_g8flVQyfKoGs7",
"refreshToken": "aaaaaaabbbbbbbbcccccccccdddddddd00000000",
"authToken": {
"token": "aaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbb.cccccccccccccccccccccccc",
"expiresIn": "604800s"
}
}
Loading