Skip to content

Commit

Permalink
[fix/share-sheet-biometrical-unlock] Biometrical Unlock in Share Sheet (
Browse files Browse the repository at this point in the history
#1129)

* #5256 workaround for fixing the biometrical unlock in the share sheet, when opening from Boxer app. View disappeared after the biometrical unlock succeeded. Probably because 'completeRequest' was sent from the biometrical unlock extension, which caused a dismiss in the share sheet extension.

* - added new app lock setting if biometrical unlock should be used in share sheet
- share unlock and last application background date via app and extensions

* change default value to YES for using biometrical unlock in share sheet

* - normalize code indentation

* - normalize code indentation for better comparison

* - Branding: add -appURLSchemesForBundleURLName: method to retrieve URL schemes from the app bundle
- UIViewController+HostBundleID: category to return the bundle ID of the app hosting/invoking a (share) extension
- ShareNavigationController: set OCAppIdentity.hostAppBundleIdentifier using UIViewController+HostBundleID
- AppLockSettings:
	- replace "use-biometrical-unlock-share-sheet" boolean with "share-sheet-biometrical-unlock-by-app" dictionary
	- implement new logic to turn off biometric authentication based on share sheet invoking app
- AppLockManager:
	- make LAContext() optional throughout methods
	- move invocation of openURL to central place
	- remove fixed URL opening of owncloud:// and replace it with the dynamically composed URL AppLockSettings.biometricalAuthenticationRedirectionTargetURL
- PasscodeViewController: rename cancelButtonHidden to cancelButtonAvailable because the latter is what it actually does
- ownCloud Share Extension/Info.plist: add missing OCAppComponentIdentifier key so that OCAppIdentity.componentIdentifier returns the correct value

* - fix code comment

* - fix finding #1 by @jesmrec in #1129

Co-authored-by: Matthias Hühne <>
Co-authored-by: Felix Schwarz <fs-git@iospirit.com>
  • Loading branch information
hosy and felix-schwarz committed Aug 1, 2022
1 parent 3aa2fec commit 069c5ce
Show file tree
Hide file tree
Showing 14 changed files with 338 additions and 33 deletions.
2 changes: 1 addition & 1 deletion ios-sdk
2 changes: 2 additions & 0 deletions ownCloud Share Extension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
<string>group.com.owncloud.ios-app</string>
<key>OCAppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
<key>OCAppComponentIdentifier</key>
<string>shareExtension</string>
<key>OCHasFileProvider</key>
<true/>
<key>OCKeychainAccessGroupIdentifier</key>
Expand Down
8 changes: 8 additions & 0 deletions ownCloud Share Extension/ShareNavigationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ class ShareNavigationController: AppExtensionNavigationController {
self.setViewControllers([viewController], animated: false)
}
}

override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)

OCAppIdentity.shared.hostAppBundleIdentifier = parent?.oc_hostAppBundleIdentifier

Log.debug("Extension Host App Bundle ID: \(OCAppIdentity.shared.hostAppBundleIdentifier ?? "nil")")
}
}

extension UserInterfaceContext : UserInterfaceContextProvider {
Expand Down
16 changes: 16 additions & 0 deletions ownCloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@
DC3BE0D82077BC5D002A0AC0 /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; };
DC3BE0DA2077BC6B002A0AC0 /* ownCloudSDK.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DC3BE0DF2077CC14002A0AC0 /* ClientRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */; };
DC3DDF06287E1C0800E5586D /* UIViewController+HostBundleID.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3DDF03287E1AC200E5586D /* UIViewController+HostBundleID.m */; };
DC3DDF07287E1C0E00E5586D /* UIViewController+HostBundleID.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3DDF02287E1AC200E5586D /* UIViewController+HostBundleID.h */; settings = {ATTRIBUTES = (Public, ); }; };
DC3DEC7B22AFA1F000F3352D /* DownloadItemsHUDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3DEC7A22AFA1F000F3352D /* DownloadItemsHUDViewController.swift */; };
DC3F4522271A23A000ED2383 /* AcknowledgementsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F4521271A23A000ED2383 /* AcknowledgementsTableViewController.swift */; };
DC4332002472E1B4002DC0E5 /* OCLicenseEMMProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = DC4331FE2472E1B4002DC0E5 /* OCLicenseEMMProvider.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -1239,6 +1241,8 @@
DC3BE0DC2077CC13002A0AC0 /* ClientQueryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientQueryViewController.swift; sourceTree = "<group>"; };
DC3BE0DD2077CC13002A0AC0 /* ClientRootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientRootViewController.swift; sourceTree = "<group>"; };
DC3BE0E02077CD4B002A0AC0 /* Synchronized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Synchronized.swift; sourceTree = "<group>"; };
DC3DDF02287E1AC200E5586D /* UIViewController+HostBundleID.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+HostBundleID.h"; sourceTree = "<group>"; };
DC3DDF03287E1AC200E5586D /* UIViewController+HostBundleID.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+HostBundleID.m"; sourceTree = "<group>"; };
DC3DEC7A22AFA1F000F3352D /* DownloadItemsHUDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadItemsHUDViewController.swift; sourceTree = "<group>"; };
DC3DEC7C22AFFE8E00F3352D /* KVOWaiter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVOWaiter.swift; sourceTree = "<group>"; };
DC3DEC7F22B03AE700F3352D /* CardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2329,6 +2333,15 @@
path = Client;
sourceTree = "<group>";
};
DC3DDEFE287E1AA500E5586D /* UIKit Extensions */ = {
isa = PBXGroup;
children = (
DC3DDF03287E1AC200E5586D /* UIViewController+HostBundleID.m */,
DC3DDF02287E1AC200E5586D /* UIViewController+HostBundleID.h */,
);
path = "UIKit Extensions";
sourceTree = "<group>";
};
DC422448207CAED60006A2A6 /* Theming */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2651,6 +2664,7 @@
DCC832E5242CB14E00153F8C /* Notifications */,
DC774E5422F44DF6000B11A1 /* SDK Extensions */,
DC0030BE2350B1CE00BB8570 /* Tools */,
DC3DDEFE287E1AA500E5586D /* UIKit Extensions */,
DC774E5B22F44E4A000B11A1 /* ZIP Archive */,
DC774E6522F44EA7000B11A1 /* Resources */,
);
Expand Down Expand Up @@ -3157,6 +3171,7 @@
DCFEFE2A236876BD009A142F /* OCLicenseManager.h in Headers */,
DCFEFE4923687C83009A142F /* OCLicenseEntitlement.h in Headers */,
DC4332002472E1B4002DC0E5 /* OCLicenseEMMProvider.h in Headers */,
DC3DDF07287E1C0E00E5586D /* UIViewController+HostBundleID.h in Headers */,
DCFEFE39236877A7009A142F /* OCLicenseFeature.h in Headers */,
DC23D1DA238F391200423F62 /* OCLicenseAppStoreReceipt.h in Headers */,
DC70398526128B89009F2DC1 /* NSString+ByteCountParser.h in Headers */,
Expand Down Expand Up @@ -4292,6 +4307,7 @@
DCFEFE50236880B5009A142F /* OCLicenseOffer.m in Sources */,
DC0030C12350B1CE00BB8570 /* NSData+Encoding.m in Sources */,
DC774E5F22F44E57000B11A1 /* ZIPArchive.m in Sources */,
DC3DDF06287E1C0800E5586D /* UIViewController+HostBundleID.m in Sources */,
DCDBB60B2525306000FAD707 /* NotificationAuthErrorForwarder.m in Sources */,
DCD71E8027427463001592C6 /* BuildOptions.m in Sources */,
DC080CE5238AE3F40044C5D2 /* OCLicenseAppStoreProvider.m in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions ownCloudAppFramework/AppLock Settings/AppLockSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN
@property(assign,nonatomic) BOOL lockEnabled;
@property(assign,nonatomic) NSInteger lockDelay;
@property(assign,nonatomic) BOOL biometricalSecurityEnabled;
@property(assign,nonatomic) BOOL biometricalSecurityEnabledinShareSheet;
@property(readonly,nonatomic,nullable) NSURL *biometricalAuthenticationRedirectionTargetURL;

@property(readonly,nonatomic) BOOL isPasscodeEnforced;
@property(readonly,nonatomic) NSInteger requiredPasscodeDigits;
Expand All @@ -46,5 +48,6 @@ extern OCClassSettingsKey OCClassSettingsKeyRequiredPasscodeDigits;
extern OCClassSettingsKey OCClassSettingsKeyMaximumPasscodeDigits;
extern OCClassSettingsKey OCClassSettingsKeyPasscodeLockDelay;
extern OCClassSettingsKey OCClassSettingsKeyPasscodeUseBiometricalUnlock;
extern OCClassSettingsKey OCClassSettingsKeyPasscodeShareSheetBiometricalUnlockByApp;

NS_ASSUME_NONNULL_END
147 changes: 139 additions & 8 deletions ownCloudAppFramework/AppLock Settings/AppLockSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

#import "AppLockSettings.h"
#import "Branding.h"

@implementation AppLockSettings

Expand Down Expand Up @@ -54,7 +55,19 @@ + (OCClassSettingsIdentifier)classSettingsIdentifier
OCClassSettingsKeyPasscodeEnforced : @(NO),
OCClassSettingsKeyRequiredPasscodeDigits : @(4),
OCClassSettingsKeyMaximumPasscodeDigits : @(6),
OCClassSettingsKeyPasscodeUseBiometricalUnlock : @(NO)
OCClassSettingsKeyPasscodeUseBiometricalUnlock : @(NO),
OCClassSettingsKeyPasscodeShareSheetBiometricalUnlockByApp : @{
@"default" : @{
@"allow" : @(YES)
},

// For unknown reasons invoking biometric authentication from the
// share sheet in Boxer leads to dismissal of the entire share sheet,
// so (as of July 2022) we hardcode it as an exception here
@"com.air-watch.boxer" : @{
@"allow" : @(NO)
}
}
});
}

Expand Down Expand Up @@ -89,9 +102,16 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata
OCClassSettingsMetadataKeyCategory : @"Passcode"
},

OCClassSettingsKeyPasscodeUseBiometricalUnlock : @{
OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean,
OCClassSettingsMetadataKeyDescription : @"Controls wether the biometrical unlock will be enabled automatically.",
OCClassSettingsKeyPasscodeUseBiometricalUnlock : @{
OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeBoolean,
OCClassSettingsMetadataKeyDescription : @"Controls wether the biometrical unlock will be enabled automatically.",
OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced,
OCClassSettingsMetadataKeyCategory : @"Passcode"
},

OCClassSettingsKeyPasscodeShareSheetBiometricalUnlockByApp : @{
OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeDictionary,
OCClassSettingsMetadataKeyDescription : @"Controls the biometrical unlock availability in the share sheet, with per-app level control.",
OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusAdvanced,
OCClassSettingsMetadataKeyCategory : @"Passcode"
}
Expand Down Expand Up @@ -128,21 +148,131 @@ - (void)setLockDelay:(NSInteger)lockDelay

- (BOOL)biometricalSecurityEnabled
{
NSNumber *useBiometricalUnlock;
NSNumber *useBiometricalUnlockNumber;
BOOL useBiometricalUnlock = NO;

if ((useBiometricalUnlock = [_userDefaults objectForKey:@"security-settings-use-biometrical"]) != nil)
if ((useBiometricalUnlockNumber = [_userDefaults objectForKey:@"security-settings-use-biometrical"]) != nil)
{
return (useBiometricalUnlock.boolValue);
useBiometricalUnlock = useBiometricalUnlockNumber.boolValue;
}
else
{
useBiometricalUnlock = [[self classSettingForOCClassSettingsKey:OCClassSettingsKeyPasscodeUseBiometricalUnlock] boolValue];
}

return ([[self classSettingForOCClassSettingsKey:OCClassSettingsKeyPasscodeUseBiometricalUnlock] boolValue]);
if (useBiometricalUnlock)
{
// Apple share extension specific settings
if ([OCAppIdentity.sharedAppIdentity.componentIdentifier isEqual:OCAppComponentIdentifierShareExtension])
{
return ([self biometricalSecurityEnabledinShareSheet]);
}
}

return (useBiometricalUnlock);
}

- (void)setBiometricalSecurityEnabled:(BOOL)biometricalSecurityEnabled
{
[_userDefaults setBool:biometricalSecurityEnabled forKey:@"security-settings-use-biometrical"];
}

- (NSDictionary<NSString*,id> *)_shareSheetBiometricalAttributesForApp:(NSString *)hostAppID
{
NSDictionary<NSString*,NSDictionary *> *shareSheetBiometricalUnlockByApp = [self classSettingForOCClassSettingsKey:OCClassSettingsKeyPasscodeShareSheetBiometricalUnlockByApp];
NSDictionary<NSString*,id> *attributesForApp = nil;

if ([shareSheetBiometricalUnlockByApp isKindOfClass:NSDictionary.class])
{
if (shareSheetBiometricalUnlockByApp[hostAppID] != nil)
{
attributesForApp = OCTypedCast(shareSheetBiometricalUnlockByApp[hostAppID], NSDictionary);
}
else
{
attributesForApp = OCTypedCast(shareSheetBiometricalUnlockByApp[@"default"], NSDictionary);
}
}

return (attributesForApp);
}

- (NSDictionary<NSString*,id> *)_shareSheetBiometricalAttributes
{
NSString *hostAppID;

if ((hostAppID = OCAppIdentity.sharedAppIdentity.hostAppBundleIdentifier) == nil)
{
hostAppID = @"default";
}

return ([self _shareSheetBiometricalAttributesForApp:hostAppID]);
}

- (BOOL)biometricalSecurityEnabledinShareSheet
{
NSNumber *useBiometricalUnlock;

if ((useBiometricalUnlock = [_userDefaults objectForKey:@"security-settings-use-biometrical-share-sheet"]) != nil)
{
return (useBiometricalUnlock.boolValue);
}

NSDictionary<NSString*,id> *shareSheetAttributesForApp = nil;

if ((shareSheetAttributesForApp = [self _shareSheetBiometricalAttributes]) != nil)
{
NSNumber *enabled;

if ((enabled = OCTypedCast(shareSheetAttributesForApp[@"allow"], NSNumber)) != nil)
{
return (enabled.boolValue);
}
}

return (YES);
}

- (void)setBiometricalSecurityEnabledinShareSheet:(BOOL)biometricalSecurityEnabledinShareSheet
{
[_userDefaults setBool:biometricalSecurityEnabledinShareSheet forKey:@"security-settings-use-biometrical-share-sheet"];
}

- (NSURL *)biometricalAuthenticationRedirectionTargetURL
{
if ([OCAppIdentity.sharedAppIdentity.componentIdentifier isEqual:OCAppComponentIdentifierShareExtension])
{
// Only in share extension
NSDictionary<NSString*,id> *shareSheetAttributesForApp = nil;

if ((shareSheetAttributesForApp = [self _shareSheetBiometricalAttributes]) != nil)
{
NSString *trampolineURLString;

// For apps with a trampoline URL, determine the target URL to initiate the authentication trampoline
if ((trampolineURLString = shareSheetAttributesForApp[@"trampoline-url"]) != nil)
{
NSString *toAppURLScheme;

if ((toAppURLScheme = [Branding.sharedBranding appURLSchemesForBundleURLName:nil].firstObject) != nil)
{
NSString *targetURLString = [NSString stringWithFormat:@"%@://?authenticateForApp=%@", toAppURLScheme, OCAppIdentity.sharedAppIdentity.hostAppBundleIdentifier];

return ([NSURL URLWithString:targetURLString]);
}
}
}
}

return (nil);
}

// Counterpart to .biometricalAuthenticationRedirectionTargetURL for use in the app (not implemented)
//- (NSURL *)biometricalAuthenticationReturnURL
//{
// return (nil);
//}

- (BOOL)isPasscodeEnforced
{
NSNumber *isPasscodeEnforced = [self classSettingForOCClassSettingsKey:OCClassSettingsKeyPasscodeEnforced];
Expand Down Expand Up @@ -190,3 +320,4 @@ - (BOOL)lockDelayUserSettable
OCClassSettingsKey OCClassSettingsKeyMaximumPasscodeDigits = @"maximumPasscodeDigits";
OCClassSettingsKey OCClassSettingsKeyPasscodeLockDelay = @"lockDelay";
OCClassSettingsKey OCClassSettingsKeyPasscodeUseBiometricalUnlock = @"use-biometrical-unlock";
OCClassSettingsKey OCClassSettingsKeyPasscodeShareSheetBiometricalUnlockByApp = @"share-sheet-biometrical-unlock-by-app";
2 changes: 2 additions & 0 deletions ownCloudAppFramework/Branding/Branding.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ typedef NSString* BrandingImageName NS_TYPED_EXTENSIBLE_ENUM;

@property(strong,nullable,nonatomic,readonly) NSBundle *appBundle; //!< Bundle of the main app

- (NSArray<NSString *> *)appURLSchemesForBundleURLName:(nullable NSString *)bundleURLName; //!< URL schemes from the app's Info.plist matching the provided CFBundleURLName.

@property(strong) NSDictionary<OCClassSettingsKey, BrandingLegacyKeyPath> *legacyKeyPathsByClassSettingsKeys;
- (void)registerLegacyKeyPath:(BrandingLegacyKeyPath)keyPath forClassSettingsKey:(OCClassSettingsKey)classSettingsKey;

Expand Down
29 changes: 29 additions & 0 deletions ownCloudAppFramework/Branding/Branding.m
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,35 @@ - (NSDictionary *)userDefaultsDefaultValues
return ([self computedValueForClassSettingsKey:BrandingKeyUserDefaultsDefaultValues]);
}

- (NSArray<NSString *> *)appURLSchemesForBundleURLName:(nullable NSString *)bundleURLName
{
NSBundle *appBundle;
NSMutableArray<NSString *> *appURLSchemes = [NSMutableArray new];

if ((appBundle = self.appBundle) != nil)
{
NSArray<NSDictionary *> *urlSchemeDictionaries;

if ((urlSchemeDictionaries = [appBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]) != nil)
{
for (NSDictionary<NSString *, id> *urlSchemesDict in urlSchemeDictionaries)
{
if ((bundleURLName == nil) || [bundleURLName isEqual:urlSchemesDict[@"CFBundleURLName"]])
{
NSArray<NSString *> *urlSchemes;

if ((urlSchemes = urlSchemesDict[@"CFBundleURLSchemes"]) != nil)
{
[appURLSchemes addObjectsFromArray:urlSchemes];
}
}
}
}
}

return (appURLSchemes);
}

- (NSArray<BrandingFileImportMethod> *)disabledImportMethods
{
return ([self computedValueForClassSettingsKey:BrandingKeyDisabledImportMethods]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// UIViewController+HostBundleID.h
// ownCloud
//
// Created by Felix Schwarz on 12.07.22.
// Copyright © 2022 ownCloud GmbH. All rights reserved.
//

/*
* Copyright (C) 2022, ownCloud GmbH.
*
* This code is covered by the GNU Public License Version 3.
*
* For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/
* You should have received a copy of this license along with this program. If not, see <http://www.gnu.org/licenses/gpl-3.0.en.html>.
*
*/

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIViewController (HostBundleID)

@property(nullable,readonly) NSString *oc_hostAppBundleIdentifier;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 069c5ce

Please sign in to comment.