MM-15643 Send user to settings when passcode is required (#2836)
* MM-15643 Send user to settings when passcode is required * Allow access to the managed config in the iOS extensions
This commit is contained in:
parent
fe2fb1d857
commit
64fbff6e71
12 changed files with 159 additions and 6 deletions
|
|
@ -1,7 +1,9 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
|
|
@ -62,4 +64,11 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
|
|||
promise.resolve(Arguments.createMap());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
// Close the current activity and open the security settings.
|
||||
public void goToSecuritySettings() {
|
||||
getCurrentActivity().finish();
|
||||
getReactApplicationContext().startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,9 +261,9 @@ export const handleManagedConfig = async (eventFromEmmServer = false) => {
|
|||
|
||||
if (config && Object.keys(config).length) {
|
||||
app.setEMMEnabled(true);
|
||||
authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
|
||||
blurApplicationScreen = config.blurApplicationScreen && config.blurApplicationScreen === 'true';
|
||||
jailbreakProtection = config.jailbreakProtection && config.jailbreakProtection === 'true';
|
||||
authNeeded = config.inAppPinCode === 'true';
|
||||
blurApplicationScreen = config.blurApplicationScreen === 'true';
|
||||
jailbreakProtection = config.jailbreakProtection === 'true';
|
||||
vendor = config.vendor || 'Mattermost';
|
||||
|
||||
if (!state.entities.general.credentials.token) {
|
||||
|
|
@ -339,12 +339,47 @@ const handleAuthentication = async (vendor) => {
|
|||
mattermostManaged.quitApp();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
await showNotSecuredAlert(vendor);
|
||||
|
||||
mattermostManaged.quitApp();
|
||||
return false;
|
||||
}
|
||||
|
||||
app.setPerformingEMMAuthentication(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
function showNotSecuredAlert(vendor) {
|
||||
const translations = app.getTranslations();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const options = [];
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
options.push({
|
||||
text: translations[t('mobile.managed.settings')],
|
||||
onPress: () => {
|
||||
mattermostManaged.goToSecuritySettings();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
options.push({
|
||||
text: translations[t('mobile.managed.exit')],
|
||||
onPress: resolve,
|
||||
style: 'cancel',
|
||||
});
|
||||
|
||||
Alert.alert(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor),
|
||||
Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
|
||||
options,
|
||||
{cancelable: false, onDismiss: resolve},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const handleSwitchToDefaultChannel = (teamId) => {
|
||||
store.dispatch(selectDefaultChannel(teamId));
|
||||
};
|
||||
|
|
@ -415,7 +450,7 @@ const handleAppActive = async () => {
|
|||
if (app.emmEnabled && app.inBackgroundSince && authExpired) {
|
||||
try {
|
||||
const config = await mattermostManaged.getConfig();
|
||||
const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
|
||||
const authNeeded = config.inAppPinCode === 'true';
|
||||
if (authNeeded) {
|
||||
await handleAuthentication(config.vendor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export default {
|
|||
getCachedConfig: () => {
|
||||
return cachedConfig;
|
||||
},
|
||||
goToSecuritySettings: MattermostManaged.goToSecuritySettings,
|
||||
isDeviceSecure: async () => {
|
||||
try {
|
||||
return await LocalAuth.isDeviceSecure();
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ export default {
|
|||
|
||||
return cachedConfig;
|
||||
},
|
||||
goToSecuritySettings: () => {
|
||||
// Do nothing since iOS doesn't allow apps to do this
|
||||
},
|
||||
getCachedConfig: () => {
|
||||
return cachedConfig;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -272,7 +272,10 @@
|
|||
"mobile.managed.blocked_by": "Blocked by {vendor}",
|
||||
"mobile.managed.exit": "Exit",
|
||||
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
|
||||
"mobile.managed.not_secured.android": "This device must be secured with a screen lock to use Mattermost.",
|
||||
"mobile.managed.not_secured.ios": "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
"mobile.managed.secured_by": "Secured by {vendor}",
|
||||
"mobile.managed.settings": "Go to settings",
|
||||
"mobile.markdown.code.copy_code": "Copy Code",
|
||||
"mobile.markdown.code.plusMoreLines": "+{count, number} more {count, plural, one {line} other {lines}}",
|
||||
"mobile.markdown.image.too_large": "Image exceeds max dimensions of {maxWidth} by {maxHeight}:",
|
||||
|
|
|
|||
|
|
@ -76,10 +76,10 @@
|
|||
7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */; };
|
||||
7F43D63F1F6BFA19001FC614 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D8FEC21E80B5230091F3BD /* libBVLinearGradient.a */; };
|
||||
7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */; };
|
||||
7F4C2598227E3B11009144EF /* libRNCNetInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4C2595227E3AE9009144EF /* libRNCNetInfo.a */; };
|
||||
7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; };
|
||||
7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; };
|
||||
7F4C2598227E3B11009144EF /* libRNCNetInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F4C2595227E3AE9009144EF /* libRNCNetInfo.a */; };
|
||||
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; };
|
||||
7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
|
||||
7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; };
|
||||
|
|
@ -100,6 +100,7 @@
|
|||
7FF2AF8B2086483E00FFBDF4 /* KeyShareConsumer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */; };
|
||||
7FF31C1421330B7900680B75 /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FF31C1321330B4200680B75 /* libRNFetchBlob.a */; };
|
||||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
||||
84E3264B229834C30055068A /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E325FF229834C30055068A /* Config.swift */; };
|
||||
895C9A56B94A45C1BAF568FE /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */; };
|
||||
8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9263CF9B16054263B13EA23B /* libRNSafeArea.a */; };
|
||||
9358B95F95184EE0A4DCE629 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; };
|
||||
|
|
@ -853,6 +854,7 @@
|
|||
7FFE32BF1FD9CCAA0038C7A0 /* Sentry.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sentry.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSafeArea.xcodeproj; path = "../node_modules/react-native-safe-area/ios/RNSafeArea.xcodeproj"; sourceTree = "<group>"; };
|
||||
84E325FF229834C30055068A /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
|
||||
8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-LightItalic.ttf"; path = "../assets/fonts/Roboto-LightItalic.ttf"; sourceTree = "<group>"; };
|
||||
920B7301B6F84DDD80677487 /* RNGestureHandler.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNGestureHandler.xcodeproj; path = "../node_modules/react-native-gesture-handler/ios/RNGestureHandler.xcodeproj"; sourceTree = "<group>"; };
|
||||
9263CF9B16054263B13EA23B /* libRNSafeArea.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSafeArea.a; sourceTree = "<group>"; };
|
||||
|
|
@ -1271,6 +1273,7 @@
|
|||
7F240A1A220D3A2300637665 /* MattermostShare */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84E325FF229834C30055068A /* Config.swift */,
|
||||
7F72F2E4221113DF00F98FFF /* Images */,
|
||||
7F240A20220D3A2300637665 /* Info.plist */,
|
||||
7F240ACF220D4A6100637665 /* KeyChainDataSource.h */,
|
||||
|
|
@ -2627,6 +2630,7 @@
|
|||
7F240ADB220E089300637665 /* Item.swift in Sources */,
|
||||
7F240A1C220D3A2300637665 /* ShareViewController.swift in Sources */,
|
||||
7FABDFC22211A39000D0F595 /* Section.swift in Sources */,
|
||||
84E3264B229834C30055068A /* Config.swift in Sources */,
|
||||
7FABE00A2212650600D0F595 /* ChannelsViewController.swift in Sources */,
|
||||
7F240ADD220E094A00637665 /* TeamsViewController.swift in Sources */,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#import <React/RCTUtils.h>
|
||||
|
||||
@interface MattermostManaged : RCTEventEmitter <RCTBridgeModule>
|
||||
- (NSUserDefaults *)bucketByName:(NSString*)name;
|
||||
@property (nonatomic) NSUserDefaults *sharedUserDefaults;
|
||||
+ (void)sendConfigChangedEvent;
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#import "RCTUITextView.h"
|
||||
#import "MattermostManaged.h"
|
||||
#import <UploadAttachments/Constants.h>
|
||||
|
||||
@implementation MattermostManaged {
|
||||
bool hasListeners;
|
||||
|
|
@ -85,6 +86,8 @@ RCT_EXPORT_MODULE();
|
|||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:APP_GROUP_ID];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(managedConfigDidChange:) name:@"managedConfigDidChange" object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification
|
||||
object:nil
|
||||
|
|
@ -122,6 +125,13 @@ static NSString * const feedbackKey = @"com.apple.feedback.managed";
|
|||
|
||||
- (void) remoteConfigChanged {
|
||||
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
|
||||
NSDictionary *group = [self.sharedUserDefaults dictionaryForKey:configurationKey];
|
||||
|
||||
if (response && ![response isEqualToDictionary:group]) {
|
||||
// copies the managed configuration so it is accessible in the Extensions
|
||||
[self.sharedUserDefaults setObject:response forKey:configurationKey];
|
||||
}
|
||||
|
||||
if (hasListeners) {
|
||||
@try {
|
||||
[self sendEventWithName:@"managedConfigDidChange" body:response];
|
||||
|
|
|
|||
5
ios/MattermostShare/Config.swift
Normal file
5
ios/MattermostShare/Config.swift
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
let configurationKey = "com.apple.configuration.managed"
|
||||
|
||||
func getManagedConfig() -> [String : Any] {
|
||||
return UserDefaults.init(suiteName: APP_GROUP_ID)?.dictionary(forKey: configurationKey) ?? [:]
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import UIKit
|
|||
import Social
|
||||
import MobileCoreServices
|
||||
import UploadAttachments
|
||||
import LocalAuthentication
|
||||
|
||||
extension Bundle {
|
||||
var displayName: String? {
|
||||
|
|
@ -31,6 +32,44 @@ class ShareViewController: SLComposeServiceViewController {
|
|||
|
||||
title = Bundle.main.displayName
|
||||
placeholder = "Write a message..."
|
||||
|
||||
let config = getManagedConfig()
|
||||
if let inAppPinCode = config["inAppPinCode"] as? String, inAppPinCode == "true" {
|
||||
self.auth(vendor: config["vendor"] as? String)
|
||||
} else {
|
||||
self.loadData()
|
||||
}
|
||||
}
|
||||
|
||||
func auth(vendor: String?) {
|
||||
let context = LAContext()
|
||||
|
||||
var error: NSError?
|
||||
if !context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
|
||||
if let error = error, error.code == kLAErrorPasscodeNotSet {
|
||||
self.showErrorMessage(
|
||||
title: "",
|
||||
message: "This device must be secured with a passcode to use Mattermost.\n\nGo to Settings > Face ID & Passcode.",
|
||||
VC: self
|
||||
)
|
||||
} else {
|
||||
self.showErrorMessage(title: "", message: "Unable to authenticate device owner for Mattermost", VC: self)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let reason = "Secured by " + (vendor ?? "Mattermost")
|
||||
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
|
||||
if success {
|
||||
self.loadData()
|
||||
} else {
|
||||
self.showErrorMessage(title: "", message: "Unable to authenticate device owner for Mattermost", VC: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadData() {
|
||||
entities = store.getEntities(true) as [AnyHashable:Any]?
|
||||
sessionToken = store.getToken()
|
||||
serverURL = store.getServerUrl()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* Constants.h */; };
|
||||
7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE04C2213818900D0F595 /* MattermostBucket.m */; };
|
||||
7FABE055221387B500D0F595 /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE053221387B400D0F595 /* Constants.m */; };
|
||||
7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE057221388D600D0F595 /* UploadSessionManager.swift */; };
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
|
|||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Image,
|
||||
NativeModules,
|
||||
PermissionsAndroid,
|
||||
|
|
@ -175,6 +176,10 @@ export default class ExtensionPost extends PureComponent {
|
|||
} catch (err) {
|
||||
return this.onClose({nativeEvent: true});
|
||||
}
|
||||
} else {
|
||||
await this.showNotSecuredAlert(vendor);
|
||||
|
||||
return this.onClose({nativeEvent: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,6 +190,43 @@ export default class ExtensionPost extends PureComponent {
|
|||
return this.initialize();
|
||||
};
|
||||
|
||||
showNotSecuredAlert(vendor) {
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
Alert.alert(
|
||||
formatMessage({
|
||||
id: 'mobile.managed.blocked_by',
|
||||
defaultMessage: 'Blocked by {vendor}',
|
||||
}, {vendor}),
|
||||
formatMessage({
|
||||
id: 'mobile.managed.not_secured.android',
|
||||
defaultMessage: 'This device must be secured with a screen lock to use Mattermost.',
|
||||
}),
|
||||
[
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.managed.settings',
|
||||
defaultMessage: 'Go to settings',
|
||||
}),
|
||||
onPress: () => {
|
||||
mattermostManaged.goToSecuritySettings();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.managed.exit',
|
||||
defaultMessage: 'Exit',
|
||||
}),
|
||||
onPress: resolve,
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
{onDismiss: resolve}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getInputRef = (ref) => {
|
||||
this.input = ref;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue