Skip to content

Commit

Permalink
Merge pull request #1962 from firebase/md-fdl
Browse files Browse the repository at this point in the history
Add support for validating FDL links from custom domains. This feature is not yet enabled in the backend.
  • Loading branch information
dmandar authored Nov 12, 2018
2 parents f250f50 + 3531f3f commit bf1ce88
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 16 deletions.
6 changes: 6 additions & 0 deletions Example/DynamicLinks/App/iOS/DL-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,11 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>FirebaseDynamicLinksCustomDomains</key>
<array>
<string>https://google.com</string>
<string>https://google.com/one</string>
<string>https://a.firebase.com/mypath</string>
</array>
</dict>
</plist>
3 changes: 2 additions & 1 deletion Example/DynamicLinks/FDLBuilderTestAppObjC/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ - (BOOL)application:(UIApplication *)application

- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *))restorationHandler {
restorationHandler:
(void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler {
BOOL handled = [[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
Expand Down
42 changes: 28 additions & 14 deletions Example/DynamicLinks/FDLBuilderTestAppObjC/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand Down Expand Up @@ -55,22 +69,22 @@
</array>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>FirebaseDynamicLinksCustomDomains</key>
<array>
<string>https://mydomain.com</string>
<string>https://mydomain2.com</string>
<string>https://google.com</string>
<string>https://google.com</string>
<string>google</string>
<string>mydomain.com</string>
<string>https://mydomain</string>
<string>https://mydomain3.com</string>
<string>https://google.com/one</string>
<string>https://custom.com/one/two</string>
<string>https://custom1.com/one/</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
Expand Down
19 changes: 19 additions & 0 deletions Example/DynamicLinks/FDLBuilderTestAppObjCTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,24 @@
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;array&gt;
&lt;string&gt;https://mydomain.com&lt;/string&gt;
&lt;string&gt;https://mydomain2.com&lt;/string&gt;
&lt;string&gt;https://google.com&lt;/string&gt;
&lt;string&gt;https://google.com&lt;/string&gt;
&lt;string&gt;go&lt;/string&gt;
&lt;string&gt;g.co&lt;/string&gt;
&lt;string&gt;https://go&lt;/string&gt;
&lt;string&gt;https://g.co&lt;/string&gt;
&lt;string&gt;https://google.com/one&lt;/string&gt;
&lt;string&gt;https://custom.com/one/two&lt;/string&gt;
&lt;string&gt;https://custom1.com/one/&lt;/string&gt;
&lt;/array&gt;
&lt;/plist&gt;
</key>
<string></string>
</dict>
</plist>
51 changes: 51 additions & 0 deletions Example/DynamicLinks/Tests/FIRDynamicLinksTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,57 @@ - (void)testSelfDiagnoseCompletionCalled {
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}

#pragma mark - Custom domain tests
- (void)testValidCustomDomainNames {
// Entries in plist file:
// https://google.com
// https://google.com/one
// https://a.firebase.com/mypath

NSArray<NSString *> *urlStrings = @[
@"https://google.com/1", // Valid domain. Any path.
@"https://google.com/2", // Valid domain. Any path.
@"https://google.com/one", // Valid domain. Specified path.
@"https://a.firebase.com/mypath/", // Valid subdomain.
@"https://a.firebase.com/mypath/abcd/efgh", // Long path.
@"https://a.firebase.com/mypath?link=abcd&test=1", // Long path.
];

for (NSString *urlString in urlStrings) {
NSURL *url = [NSURL URLWithString:urlString];
BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

XCTAssertTrue(matchesShortLinkFormat,
@"Non-DDL domain URL matched short link format with URL: %@", url);
}
}

- (void)testInvalidCustomDomainNames {
// Entries in plist file:
// https://google.com
// https://google.com/one
// https://a.firebase.com/mypath

NSArray<NSString *> *urlStrings = @[
@"mydomain.com", // Domain not in plist. Also, no scheme.
@"http://mydomain", // Domain not in plist. No path.
@"google.com", // Valid domain. No scheme.
@"https://google.com", // Valid domain. No path.
@"http://google.com", // Valid domain. Invalid scheme.
@"https://google.co.in/abc", // Invalid domain starts with valid domain name.
@"https://firebase.com/mypath", // Invalid (sub)domain.
@"https://b.firebase.com/mypath" // Invalid subdomain.
];

for (NSString *urlString in urlStrings) {
NSURL *url = [NSURL URLWithString:urlString];
BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];

XCTAssertFalse(matchesShortLinkFormat,
@"Non-DDL domain URL matched short link format with URL: %@", url);
}
}

#pragma mark - Private Helpers

- (void)removeAllFIRApps {
Expand Down
9 changes: 9 additions & 0 deletions Firebase/DynamicLinks/FIRDynamicLinks.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
// We should only open url once. We use the following key to store the state in the user defaults.
static NSString *const kFIRDLOpenURLKey = @"com.google.appinvite.openURL";

// Custom domains to be whitelisted are optionally added as an array to the info.plist.
static NSString *const kInfoPlistCustomDomainsKey = @"FirebaseDynamicLinksCustomDomains";

NS_ASSUME_NONNULL_BEGIN

@interface FIRDynamicLinks () <FIRDLRetrievalProcessDelegate>
Expand Down Expand Up @@ -216,6 +219,12 @@ - (void)configureDynamicLinks:(FIRApp *)app {
}
[NSException raise:kFirebaseDurableDeepLinkErrorDomain format:@"%@", message];
}
// Check to see if FirebaseDynamicLinksCustomDomains array is present.
NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
NSArray *customDomains = infoDictionary[kInfoPlistCustomDomainsKey];
if (customDomains) {
FIRDLAddToWhiteListForCustomDomainsArray(customDomains);
}
}

- (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics {
Expand Down
5 changes: 5 additions & 0 deletions Firebase/DynamicLinks/Utilities/FDLUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,9 @@ BOOL FIRDLMatchesShortLinkFormat(NSURL *URL);
*/
NSString *FIRDLMatchTypeStringFromServerString(NSString *_Nullable serverMatchTypeString);

/**
Add custom domains from the info.plist to the internal whitelist.
*/
void FIRDLAddToWhiteListForCustomDomainsArray(NSArray *_Nonnull customDomains);

NS_ASSUME_NONNULL_END
39 changes: 38 additions & 1 deletion Firebase/DynamicLinks/Utilities/FDLUtilities.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
NSString *const kFIRDLParameterWeakMatchEndpoint = @"invitation_weakMatchEndpoint";
NSString *const kFIRDLParameterMatchMessage = @"match_message";
NSString *const kFIRDLParameterRequestIPVersion = @"request_ip_version";
static NSSet *FIRDLCustomDomains = nil;

NSURL *FIRDLCookieRetrievalURL(NSString *urlScheme, NSString *bundleID) {
static NSString *const kFDLBundleIDQueryParameterName = @"fdl_ios_bundle_id";
Expand Down Expand Up @@ -192,6 +193,23 @@ BOOL FIRDLOSVersionSupported(NSString *_Nullable systemVersion, NSString *minSup
return timeZoneName;
}

BOOL FIRDLIsURLForWhiteListedCustomDomain(NSURL *_Nullable URL) {
BOOL customDomainMatchFound = false;
for (NSURL *allowedCustomDomain in FIRDLCustomDomains) {
// All custom domain host names should match at a minimum.
if ([allowedCustomDomain.host isEqualToString:URL.host]) {
// Next, do a string compare to check if the full path matches as well.
if (([URL.absoluteString rangeOfString:allowedCustomDomain.absoluteString
options:NSCaseInsensitiveSearch | NSAnchoredSearch]
.location) == 0) {
customDomainMatchFound = true;
break;
}
}
}
return customDomainMatchFound;
}

BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) {
// Handle universal links with format |https://goo.gl/app/<appcode>?<parameters>|.
// Also support page.link format.
Expand All @@ -200,7 +218,11 @@ BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) {
// Handle universal links with format |https://<appcode>.app.goo.gl?<parameters>| and page.link.
BOOL isDDLWithSubdomain =
[URL.host hasSuffix:@".app.goo.gl"] || [URL.host hasSuffix:@".page.link"];
return isDDLWithAppcodeInPath || isDDLWithSubdomain;

// Handle universal links for custom domains.
BOOL isDDLWithCustomDomain = FIRDLIsURLForWhiteListedCustomDomain(URL);

return isDDLWithAppcodeInPath || isDDLWithSubdomain || isDDLWithCustomDomain;
}

BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) {
Expand All @@ -227,4 +249,19 @@ BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) {
return matchMap[serverMatchTypeString] ?: @"none";
}

void FIRDLAddToWhiteListForCustomDomainsArray(NSArray *_Nonnull customDomains) {
// Duplicates will be weeded out when converting to a set.
NSMutableArray *validCustomDomains =
[[NSMutableArray alloc] initWithCapacity:customDomains.count];
for (NSString *customDomainEntry in customDomains) {
NSURL *customDomainURL = [NSURL URLWithString:customDomainEntry];
// We require a valid scheme for each custom domain enumerated in the info.plist file.
if (customDomainURL && customDomainURL.scheme) {
[validCustomDomains addObject:customDomainURL];
}
}
// Duplicates will be weeded out when converting to a set.
FIRDLCustomDomains = [NSSet setWithArray:validCustomDomains];
}

NS_ASSUME_NONNULL_END

0 comments on commit bf1ce88

Please sign in to comment.