diff --git a/.gitignore b/.gitignore
index b6686cbc7..8764da11b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ build/
*.perspectivev3
!default.perspectivev3
xcuserdata
+xcshareddata
*.xccheckout
*.moved-aside
DerivedData
diff --git a/app/components/custom_list/index.test.js b/app/components/custom_list/index.test.js
index 0df1be395..372a1e448 100644
--- a/app/components/custom_list/index.test.js
+++ b/app/components/custom_list/index.test.js
@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
+// See LICENSE.txt for license information.
import React from 'react';
import {configure, shallow} from 'enzyme';
diff --git a/app/components/file_attachment_list/file_attachment_document.js b/app/components/file_attachment_list/file_attachment_document.js
index ffe33b668..ceb412d61 100644
--- a/app/components/file_attachment_list/file_attachment_document.js
+++ b/app/components/file_attachment_list/file_attachment_document.js
@@ -22,8 +22,11 @@ import tinyColor from 'tinycolor2';
import {getFileUrl} from 'mattermost-redux/utils/file_utils.js';
import {DeviceTypes} from 'app/constants/';
+import mattermostBucket from 'app/mattermost_bucket';
import {changeOpacity} from 'app/utils/theme';
+import LocalConfig from 'assets/config';
+
import FileAttachmentIcon from './file_attachment_icon';
const {DOCUMENTS_PATH} = DeviceTypes;
@@ -106,6 +109,7 @@ export default class FileAttachmentDocument extends PureComponent {
this.setState({didCancel: false});
try {
+ const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId);
const isDir = await RNFetchBlob.fs.isDir(DOCUMENTS_PATH);
if (!isDir) {
try {
@@ -122,6 +126,7 @@ export default class FileAttachmentDocument extends PureComponent {
indicator: true,
overwrite: true,
path,
+ certificate,
};
const mime = data.mime_type.split(';')[0];
diff --git a/app/components/offline_indicator/offline_indicator.js b/app/components/offline_indicator/offline_indicator.js
index 80d2d38f9..ef5a9779c 100644
--- a/app/components/offline_indicator/offline_indicator.js
+++ b/app/components/offline_indicator/offline_indicator.js
@@ -16,7 +16,9 @@ import IonIcon from 'react-native-vector-icons/Ionicons';
import FormattedText from 'app/components/formatted_text';
import {ViewTypes} from 'app/constants';
+import mattermostBucket from 'app/mattermost_bucket';
import checkNetwork from 'app/utils/network';
+import LocalConfig from 'assets/config';
import {RequestStatus} from 'mattermost-redux/constants';
@@ -95,7 +97,7 @@ export default class OfflineIndicator extends Component {
this.setState({network: CONNECTING}, () => {
if (result) {
this.props.actions.connection(true);
- this.props.actions.initWebSocket(Platform.OS);
+ this.initializeWebSocket();
} else {
this.setState({network: OFFLINE});
}
@@ -154,6 +156,18 @@ export default class OfflineIndicator extends Component {
return IOS_TOP_PORTRAIT;
};
+ initializeWebSocket = async () => {
+ const {actions} = this.props;
+ const {initWebSocket} = actions;
+ const platform = Platform.OS;
+ let certificate = null;
+ if (platform === 'ios') {
+ certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId);
+ }
+
+ initWebSocket(platform, null, null, null, {certificate});
+ };
+
offline = () => {
this.setState({network: OFFLINE}, () => {
this.show();
diff --git a/app/fetch_preconfig.js b/app/fetch_preconfig.js
index c3c8af3e7..3cac7a6d6 100644
--- a/app/fetch_preconfig.js
+++ b/app/fetch_preconfig.js
@@ -1,12 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
+// See LICENSE.txt for license information.
+import {Platform} from 'react-native';
import RNFetchBlob from 'react-native-fetch-blob';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
+import mattermostBucket from 'app/mattermost_bucket';
+import LocalConfig from 'assets/config';
+
const HEADER_X_VERSION_ID = 'X-Version-Id';
const HEADER_X_CLUSTER_ID = 'X-Cluster-Id';
const HEADER_TOKEN = 'Token';
@@ -91,6 +95,15 @@ Client4.doFetchWithResponse = async (url, options) => {
};
};
-window.fetch = new RNFetchBlob.polyfill.Fetch({
- auto: true,
-}).build();
+if (Platform.OS === 'ios') {
+ mattermostBucket.getPreference('cert', LocalConfig.AppGroupId).then((certificate) => {
+ window.fetch = new RNFetchBlob.polyfill.Fetch({
+ auto: true,
+ certificate,
+ }).build();
+ });
+} else {
+ window.fetch = new RNFetchBlob.polyfill.Fetch({
+ auto: true,
+ }).build();
+}
diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js
index e917bfc90..f5aea1526 100644
--- a/app/screens/channel/channel.js
+++ b/app/screens/channel/channel.js
@@ -19,6 +19,7 @@ import KeyboardLayout from 'app/components/layout/keyboard_layout';
import OfflineIndicator from 'app/components/offline_indicator';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
+import mattermostBucket from 'app/mattermost_bucket';
import {preventDoubleTap} from 'app/utils/tap';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import PostTextbox from 'app/components/post_textbox';
@@ -175,13 +176,12 @@ export default class Channel extends PureComponent {
const {actions} = this.props;
const {
closeWebSocket,
- initWebSocket,
startPeriodicStatusUpdates,
stopPeriodicStatusUpdates,
} = actions;
if (open) {
- initWebSocket(Platform.OS);
+ this.initializeWebSocket();
startPeriodicStatusUpdates();
} else {
closeWebSocket(true);
@@ -204,6 +204,18 @@ export default class Channel extends PureComponent {
this.props.actions.selectDefaultTeam();
};
+ initializeWebSocket = async () => {
+ const {actions} = this.props;
+ const {initWebSocket} = actions;
+ const platform = Platform.OS;
+ let certificate = null;
+ if (platform === 'ios') {
+ certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId);
+ }
+
+ initWebSocket(platform, null, null, null, {certificate});
+ };
+
loadChannels = (teamId) => {
const {
loadChannelsIfNecessary,
diff --git a/app/screens/image_preview/downloader.ios.js b/app/screens/image_preview/downloader.ios.js
index 5641a9689..fc7ef6dca 100644
--- a/app/screens/image_preview/downloader.ios.js
+++ b/app/screens/image_preview/downloader.ios.js
@@ -12,8 +12,11 @@ import {intlShape} from 'react-intl';
import {Client4} from 'mattermost-redux/client';
import FormattedText from 'app/components/formatted_text';
+import mattermostBucket from 'app/mattermost_bucket';
import {emptyFunction} from 'app/utils/general';
+import LocalConfig from 'assets/config';
+
const {View: AnimatedView} = Animated;
export default class Downloader extends PureComponent {
@@ -294,12 +297,14 @@ export default class Downloader extends PureComponent {
}
if (downloadFile) {
+ const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId);
const imageUrl = Client4.getFileUrl(data.id);
const options = {
session: data.id,
timeout: 10000,
indicator: true,
overwrite: true,
+ certificate,
};
if (downloadPath && prompt) {
diff --git a/app/screens/select_server/index.js b/app/screens/select_server/index.js
index eef58a407..c453c58cd 100644
--- a/app/screens/select_server/index.js
+++ b/app/screens/select_server/index.js
@@ -5,8 +5,10 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getPing, resetPing, setServerVersion} from 'mattermost-redux/actions/general';
+import {login} from 'mattermost-redux/actions/users';
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
+import {getSession, handleSuccessfulLogin} from 'app/actions/views/login';
import {loadConfigAndLicense} from 'app/actions/views/root';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import getClientUpgrade from 'app/selectors/client_upgrade';
@@ -33,9 +35,12 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
+ handleSuccessfulLogin,
getPing,
+ getSession,
handleServerUrlChanged,
loadConfigAndLicense,
+ login,
resetPing,
setLastUpgradeCheck,
setServerVersion,
diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js
index ed29c4180..1be34586c 100644
--- a/app/screens/select_server/select_server.js
+++ b/app/screens/select_server/select_server.js
@@ -18,16 +18,20 @@ import {
View,
} from 'react-native';
import Button from 'react-native-button';
+import RNFetchBlob from 'react-native-fetch-blob';
import {Client4} from 'mattermost-redux/client';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import {UpgradeTypes} from 'app/constants/view';
+import mattermostBucket from 'app/mattermost_bucket';
+import PushNotifications from 'app/push_notifications';
import {GlobalStyles} from 'app/styles';
import checkUpgradeType from 'app/utils/client_upgrade';
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
import {preventDoubleTap} from 'app/utils/tap';
+import tracker from 'app/utils/time_tracker';
import LocalConfig from 'assets/config';
@@ -36,7 +40,10 @@ export default class SelectServer extends PureComponent {
actions: PropTypes.shape({
getPing: PropTypes.func.isRequired,
handleServerUrlChanged: PropTypes.func.isRequired,
+ handleSuccessfulLogin: PropTypes.func.isRequired,
+ getSession: PropTypes.func.isRequired,
loadConfigAndLicense: PropTypes.func.isRequired,
+ login: PropTypes.func.isRequired,
resetPing: PropTypes.func.isRequired,
setLastUpgradeCheck: PropTypes.func.isRequired,
setServerVersion: PropTypes.func.isRequired,
@@ -108,6 +115,23 @@ export default class SelectServer extends PureComponent {
}
}
+ goToNextScreen = (screen, title) => {
+ const {navigator, theme} = this.props;
+ navigator.push({
+ screen,
+ title,
+ animated: true,
+ backButtonTitle: '',
+ navigatorStyle: {
+ navBarHidden: LocalConfig.AutoSelectServerUrl,
+ disabledBackGesture: LocalConfig.AutoSelectServerUrl,
+ navBarTextColor: theme.sidebarHeaderTextColor,
+ navBarBackgroundColor: theme.sidebarHeaderBg,
+ navBarButtonColor: theme.sidebarHeaderTextColor,
+ },
+ });
+ };
+
handleNavigatorEvent = (event) => {
if (event.id === 'didDisappear') {
this.setState({
@@ -125,7 +149,7 @@ export default class SelectServer extends PureComponent {
title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Client Upgrade'}),
backButtonTitle: '',
navigatorStyle: {
- navBarHidden: false,
+ navBarHidden: LocalConfig.AutoSelectServerUrl,
disabledBackGesture: LocalConfig.AutoSelectServerUrl,
statusBarHidden: true,
statusBarHideWithNavBar: true,
@@ -138,11 +162,11 @@ export default class SelectServer extends PureComponent {
upgradeType,
},
});
- }
+ };
handleLoginOptions = (props = this.props) => {
const {intl} = this.context;
- const {config, license, theme} = props;
+ const {config, license} = props;
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
@@ -161,21 +185,21 @@ export default class SelectServer extends PureComponent {
title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'});
}
- this.props.navigator.push({
- screen,
- title,
- animated: true,
- backButtonTitle: '',
- navigatorStyle: {
- navBarHidden: LocalConfig.AutoSelectServerUrl,
- disabledBackGesture: LocalConfig.AutoSelectServerUrl,
- navBarTextColor: theme.sidebarHeaderTextColor,
- navBarBackgroundColor: theme.sidebarHeaderBg,
- navBarButtonColor: theme.sidebarHeaderTextColor,
- },
- });
-
this.props.actions.resetPing();
+
+ if (Platform.OS === 'ios' && LocalConfig.ExperimentalClientSideCertEnable) {
+ if (config.ExperimentalClientSideCertEnable === 'true' && config.ExperimentalClientSideCertCheck === 'primary') {
+ // log in automatically and send directly to the channel screen
+ this.loginWithCertificate();
+ return;
+ }
+
+ setTimeout(() => {
+ this.goToNextScreen(screen, title);
+ }, 350);
+ } else {
+ this.goToNextScreen(screen, title);
+ }
};
handleAndroidKeyboard = () => {
@@ -186,6 +210,44 @@ export default class SelectServer extends PureComponent {
this.setState({url});
};
+ loginWithCertificate = async () => {
+ const {intl, navigator} = this.props;
+
+ tracker.initialLoad = Date.now();
+
+ await this.props.actions.login('credential', 'password');
+ await this.props.actions.handleSuccessfulLogin();
+ const expiresAt = await this.props.actions.getSession();
+
+ if (expiresAt) {
+ PushNotifications.localNotificationSchedule({
+ date: new Date(expiresAt),
+ message: intl.formatMessage({
+ id: 'mobile.session_expired',
+ defaultMessage: 'Session Expired: Please log in to continue receiving notifications.',
+ }),
+ userInfo: {
+ localNotification: true,
+ },
+ });
+ }
+
+ navigator.resetTo({
+ screen: 'Channel',
+ title: '',
+ animated: false,
+ backButtonTitle: '',
+ navigatorStyle: {
+ animated: true,
+ animationType: 'fade',
+ navBarHidden: true,
+ statusBarHidden: false,
+ statusBarHideWithNavBar: false,
+ screenBackgroundColor: 'transparent',
+ },
+ });
+ };
+
onClick = preventDoubleTap(async () => {
const urlParse = require('url-parse');
const preUrl = urlParse(this.state.url, true);
@@ -212,7 +274,20 @@ export default class SelectServer extends PureComponent {
return;
}
- this.pingServer(url);
+ if (LocalConfig.ExperimentalClientSideCertEnable && Platform.OS === 'ios') {
+ RNFetchBlob.cba.selectCertificate((certificate) => {
+ if (certificate) {
+ mattermostBucket.setPreference('cert', certificate, LocalConfig.AppGroupId);
+ window.fetch = new RNFetchBlob.polyfill.Fetch({
+ auto: true,
+ certificate,
+ }).build();
+ this.pingServer(url);
+ }
+ });
+ } else {
+ this.pingServer(url);
+ }
});
pingServer = (url) => {
diff --git a/app/store/middleware.js b/app/store/middleware.js
index 0d2f1d14e..2ada8e05f 100644
--- a/app/store/middleware.js
+++ b/app/store/middleware.js
@@ -361,6 +361,7 @@ export function shareExtensionData() {
switch (action.type) {
case UserTypes.LOGOUT_SUCCESS:
+ mattermostBucket.removePreference('cert', Config.AppGroupId);
mattermostBucket.removePreference('emm', Config.AppGroupId);
mattermostBucket.removeFile('entities', Config.AppGroupId);
break;
diff --git a/app/utils/image_cache_manager.js b/app/utils/image_cache_manager.js
index f7ddab228..28627a8ef 100644
--- a/app/utils/image_cache_manager.js
+++ b/app/utils/image_cache_manager.js
@@ -6,6 +6,9 @@
import RNFetchBlob from 'react-native-fetch-blob';
import {DeviceTypes} from 'app/constants';
+import mattermostBucket from 'app/mattermost_bucket';
+
+import LocalConfig from 'assets/config';
const {IMAGES_PATH} = DeviceTypes;
@@ -31,12 +34,14 @@ export default class ImageCacheManager {
}
try {
+ const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId);
const options = {
session: uri,
timeout: 10000,
indicator: true,
overwrite: true,
path,
+ certificate,
};
this.downloadTask = await RNFetchBlob.config(options).fetch('GET', uri);
diff --git a/assets/base/config.json b/assets/base/config.json
index dc5a9c46d..0939daa2f 100644
--- a/assets/base/config.json
+++ b/assets/base/config.json
@@ -12,6 +12,7 @@
"MobileNoticeURL": "https://about.mattermost.com/mobile-notice-txt/",
"SegmentApiKey": "oPPT2MA24VYZTBGDyXtUDa7u50lkPIE4",
"ExperimentalUsernamePressIsMention": false,
+ "ExperimentalClientSideCertEnable": false,
"HideEmailLoginExperimental": false,
"HideGitLabLoginExperimental": false,
diff --git a/ios/0155-keys.png b/ios/0155-keys.png
new file mode 100644
index 000000000..94d62e31d
Binary files /dev/null and b/ios/0155-keys.png differ
diff --git a/ios/KeyShareConsumer.storyboard b/ios/KeyShareConsumer.storyboard
new file mode 100644
index 000000000..19e70b95c
--- /dev/null
+++ b/ios/KeyShareConsumer.storyboard
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 962280f1c..56768b5f5 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -46,7 +46,6 @@
7F292AA71E8ABB1100A450A3 /* splash.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA51E8ABB1100A450A3 /* splash.png */; };
7F380D0B1FDB3CFC0061AAD2 /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F93F9D91FBB726B0088E416 /* libRCTVideo.a */; };
7F3F66021FE426EE0085CA0E /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */; };
- 7F3F660F1FE4280D0085CA0E /* libReactNativeExceptionHandler.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FA7950B1F61A1A500C02924 /* libReactNativeExceptionHandler.a */; };
7F3F66101FE4281A0085CA0E /* libRNSentry.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FA795061F61A1A500C02924 /* libRNSentry.a */; };
7F43D5D61F6BF8C2001FC614 /* libRNLocalAuth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49871F3DFC30003A22BA /* libRNLocalAuth.a */; };
7F43D5D71F6BF8D0001FC614 /* libRNPasscodeStatus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49F81F3E0710003A22BA /* libRNPasscodeStatus.a */; };
@@ -69,6 +68,7 @@
7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6877B01E7835E50094B63F /* libToolTipMenu.a */; };
7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */; };
7F7D7F98201645E100D31155 /* libReactNativePermissions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F7D7F87201645D300D31155 /* libReactNativePermissions.a */; };
+ 7F95132F208646CE00616E3E /* 0155-keys.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F9512EA208646CD00616E3E /* 0155-keys.png */; };
7FB6006B1FE3116800DB284F /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 375218411F4B9E320035444B /* libRNFetchBlob.a */; };
7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; };
7FC200E81EBB65370099331B /* libReactNativeNavigation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */; };
@@ -79,11 +79,13 @@
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB10971F6101710039A015 /* BlurAppScreen.m */; };
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; };
7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */; };
+ 7FF2AF8B2086483E00FFBDF4 /* KeyShareConsumer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */; };
7FF7BE2C1FDEE4AE005E55FE /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; };
7FF7BE6D1FDEE5E8005E55FE /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */; };
7FF7BE6E1FDEE5E8005E55FE /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */; };
7FF7BE6F1FDF3CE4005E55FE /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; };
7FF7BE701FDF3EE7005E55FE /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */; };
+ 7FF7BE711FE004A3005E55FE /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37DD11281E79EBE1004111BA /* libRNDeviceInfo.a */; };
7FFDB3191FE3566C009E3BCF /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 005346E5C0E542BFABAE1411 /* FontAwesome.ttf */; };
7FFE329E1FD9CB650038C7A0 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFE329D1FD9CB650038C7A0 /* ShareViewController.m */; };
7FFE32A11FD9CB650038C7A0 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7FFE329F1FD9CB650038C7A0 /* MainInterface.storyboard */; };
@@ -720,6 +722,7 @@
7F6C47A31FE87E8C00F5A912 /* PerformRequests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformRequests.h; sourceTree = ""; };
7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformRequests.m; sourceTree = ""; };
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativePermissions.xcodeproj; path = "../node_modules/react-native-permissions/ios/ReactNativePermissions.xcodeproj"; sourceTree = ""; };
+ 7F9512EA208646CD00616E3E /* 0155-keys.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "0155-keys.png"; sourceTree = ""; };
7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; };
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNTableView.xcodeproj; path = "../node_modules/react-native-tableview/RNTableView.xcodeproj"; sourceTree = ""; };
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = ""; };
@@ -729,6 +732,7 @@
7FEB109A1F61019C0039A015 /* MattermostManaged.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MattermostManaged.m; path = Mattermost/MattermostManaged.m; sourceTree = ""; };
7FEB109B1F61019C0039A015 /* UIImage+ImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ImageEffects.h"; path = "Mattermost/UIImage+ImageEffects.h"; sourceTree = ""; };
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ImageEffects.m"; path = "Mattermost/UIImage+ImageEffects.m"; sourceTree = ""; };
+ 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyShareConsumer.storyboard; sourceTree = ""; };
7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MattermostBucket.h; sourceTree = ""; };
7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = ""; };
7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MattermostShare.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -832,6 +836,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7FFE32EA1FD9CD050038C7A0 /* libReact.a in Frameworks */,
7FFE32E71FD9CCD00038C7A0 /* libART.a in Frameworks */,
7F3F66101FE4281A0085CA0E /* libRNSentry.a in Frameworks */,
7F642DF1209353A400F3165E /* libRNDeviceInfo.a in Frameworks */,
@@ -846,7 +851,6 @@
7FFE32ED1FD9CD450038C7A0 /* libRCTNetwork.a in Frameworks */,
7FFE32EC1FD9CD360038C7A0 /* libRCTText.a in Frameworks */,
7FFE32EB1FD9CD170038C7A0 /* libRCTImage.a in Frameworks */,
- 7FFE32EA1FD9CD050038C7A0 /* libReact.a in Frameworks */,
7FFE32E91FD9CCF40038C7A0 /* libRCTAnimation.a in Frameworks */,
7FFE32E81FD9CCDE0038C7A0 /* libRCTCameraRoll.a in Frameworks */,
7FD8DEDE2029EDB7001AAC5E /* libRNTableView.a in Frameworks */,
@@ -973,15 +977,17 @@
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */,
+ 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */,
7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */,
7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */,
7FEB10991F61019C0039A015 /* MattermostManaged.h */,
7FEB109A1F61019C0039A015 /* MattermostManaged.m */,
- 7F292AA51E8ABB1100A450A3 /* splash.png */,
7FEB109B1F61019C0039A015 /* UIImage+ImageEffects.h */,
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */,
+ 7F9512EA208646CD00616E3E /* 0155-keys.png */,
+ 7F292AA51E8ABB1100A450A3 /* splash.png */,
);
name = Mattermost;
sourceTree = "";
@@ -1328,6 +1334,7 @@
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */,
7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */,
74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */,
+ 546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */,
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */,
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */,
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */,
@@ -1355,7 +1362,6 @@
EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */,
27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */,
50ECB1D221E44F51B5690DF2 /* FastImage.xcodeproj */,
- 546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */,
3B1E210BF3B649BBBF427977 /* ReactNativeExceptionHandler.xcodeproj */,
9552041247E749308CE7B412 /* RNSentry.xcodeproj */,
62A58E5E984E4CF1811620B8 /* RNCookieManagerIOS.xcodeproj */,
@@ -1494,6 +1500,9 @@
com.apple.BackgroundModes = {
enabled = 1;
};
+ com.apple.Keychain = {
+ enabled = 1;
+ };
com.apple.Push = {
enabled = 1;
};
@@ -1511,6 +1520,12 @@
com.apple.ApplicationGroups.iOS = {
enabled = 1;
};
+ com.apple.Keychain = {
+ enabled = 1;
+ };
+ com.apple.iCloud = {
+ enabled = 1;
+ };
};
};
};
@@ -2204,6 +2219,7 @@
F006936FC2884C24A1321FC0 /* MaterialIcons.ttf in Resources */,
7F292AA71E8ABB1100A450A3 /* splash.png in Resources */,
420A7328E12C4B72AEF420CE /* Octicons.ttf in Resources */,
+ 7FF2AF8B2086483E00FFBDF4 /* KeyShareConsumer.storyboard in Resources */,
7F292AA61E8ABB1100A450A3 /* LaunchScreen.xib in Resources */,
7F292A711E8AB73400A450A3 /* SplashScreenResource in Resources */,
2B4C9B708010475DA575B81D /* SimpleLineIcons.ttf in Resources */,
@@ -2212,6 +2228,7 @@
55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */,
D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */,
0111A42B7F264BCF8CBDE3ED /* OpenSans-ExtraBoldItalic.ttf in Resources */,
+ 7F95132F208646CE00616E3E /* 0155-keys.png in Resources */,
0C0D24F53F254F75869E5951 /* OpenSans-Italic.ttf in Resources */,
2D5296A8926B4D7FBAF2D6E2 /* OpenSans-Light.ttf in Resources */,
F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */,
@@ -2637,16 +2654,15 @@
"$(SRCROOT)/../node_modules/react-native-passcode-status/ios",
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
+ "$(SRCROOT)/../node_modules/react-native-navigation/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-local-auth/**",
+ "$(SRCROOT)/../node_modules/react-native-passcode-status/ios/**",
+ "$(SRCROOT)/../node_modules/jail-monkey/JailMonkey/**",
"$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
- "$(SRCROOT)/../node_modules/react-native-exception-handler/ios",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
- "$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS",
- "$(SRCROOT)/../node_modules/react-native-image-picker/ios",
- "$(SRCROOT)/../node_modules/react-native-youtube/**",
- "$(SRCROOT)/../node_modules/react-native-video/ios",
- "$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
- "$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
+ "$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS/**",
+ "$(SRCROOT)/../node_modules/react-native-video/ios/**",
"$(SRCROOT)/../node_modules/react-native-tableview/**",
"$(SRCROOT)/../node_modules/react-native-device-info/ios/**",
);
@@ -2697,16 +2713,15 @@
"$(SRCROOT)/../node_modules/react-native-passcode-status/ios",
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
+ "$(SRCROOT)/../node_modules/react-native-navigation/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-local-auth/**",
+ "$(SRCROOT)/../node_modules/react-native-passcode-status/ios/**",
+ "$(SRCROOT)/../node_modules/jail-monkey/JailMonkey/**",
"$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
- "$(SRCROOT)/../node_modules/react-native-exception-handler/ios",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
- "$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS",
- "$(SRCROOT)/../node_modules/react-native-image-picker/ios",
- "$(SRCROOT)/../node_modules/react-native-youtube/**",
- "$(SRCROOT)/../node_modules/react-native-video/ios",
- "$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
- "$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
+ "$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS/**",
+ "$(SRCROOT)/../node_modules/react-native-video/ios/**",
"$(SRCROOT)/../node_modules/react-native-tableview/**",
"$(SRCROOT)/../node_modules/react-native-device-info/ios/**",
);
diff --git a/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme b/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme
index b3aebbad1..bc78e5103 100644
--- a/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme
+++ b/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme
@@ -54,7 +54,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
group.com.mattermost.rnbeta
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.mattermost.rnbeta
+
diff --git a/ios/MattermostShare/MattermostShare.entitlements b/ios/MattermostShare/MattermostShare.entitlements
index 55fdcdea6..c3808dbda 100644
--- a/ios/MattermostShare/MattermostShare.entitlements
+++ b/ios/MattermostShare/MattermostShare.entitlements
@@ -2,9 +2,27 @@
+ com.apple.developer.icloud-container-identifiers
+
+ iCloud.$(CFBundleIdentifier)
+
+ com.apple.developer.icloud-services
+
+ CloudDocuments
+
+ com.apple.developer.ubiquity-container-identifiers
+
+ iCloud.$(CFBundleIdentifier)
+
+ com.apple.developer.ubiquity-kvstore-identifier
+ $(TeamIdentifierPrefix)$(CFBundleIdentifier)
com.apple.security.application-groups
group.com.mattermost.rnbeta
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.mattermost.rnbeta
+
diff --git a/ios/MattermostShare/SessionManager.h b/ios/MattermostShare/SessionManager.h
index 53823aed1..bb40d0c50 100644
--- a/ios/MattermostShare/SessionManager.h
+++ b/ios/MattermostShare/SessionManager.h
@@ -1,14 +1,20 @@
#import
#import "MattermostBucket.h"
+#import "KeyChainDataSource.h"
@interface SessionManager : NSObject
@property (nonatomic, copy) void (^savedCompletionHandler)(void);
@property MattermostBucket *bucket;
+@property (nonatomic, retain) KeyChainDataSource *keyChain;
+@property (nonatomic, strong) NSString *requestWithGroup;
+@property (nonatomic, strong) NSString *certificateName;
+@property (nonatomic) BOOL isBackground;
+(instancetype)sharedSession;
-(NSString *)getAppGroupIdFromRequestIdentifier:(NSString *) requestWithGroup;
-(NSURLSession *)createSessionForRequestRequest:(NSString *)requestId;
+-(void)setRequestWithGroup:(NSString *)requestWithGroup certificateName:(NSString *)certificateName;
-(void)setDataForRequest:(NSDictionary *)data forRequestWithGroup:(NSString *)requestId;
-(void)createPostForRequest:(NSString *)requestId;
-(NSURL*)tempContainerURL:(NSString*)appGroupId;
diff --git a/ios/MattermostShare/SessionManager.m b/ios/MattermostShare/SessionManager.m
index ae0e82387..64f8ca91d 100644
--- a/ios/MattermostShare/SessionManager.m
+++ b/ios/MattermostShare/SessionManager.m
@@ -2,6 +2,9 @@
#import "MattermostBucket.h"
@implementation SessionManager
+
+@synthesize keyChain;
+
+ (instancetype)sharedSession {
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
@@ -15,46 +18,213 @@
self = [super init];
if (self) {
self.bucket = [[MattermostBucket alloc] init];
+ self.keyChain = [[KeyChainDataSource alloc] initWithMode:KSM_Identities];
}
return self;
}
+-(void)setRequestWithGroup:(NSString *)requestWithGroup certificateName:(NSString *)certificateName {
+ self.requestWithGroup = requestWithGroup;
+ self.certificateName = certificateName;
+ self.isBackground = certificateName == nil;
+}
+
+-(void)setDataForRequest:(NSDictionary *)data forRequestWithGroup:(NSString *)requestWithGroup {
+ NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
+ [[self.bucket bucketByName:appGroupId] setObject:data forKey:requestWithGroup];
+}
+
+
-(NSString *)getAppGroupIdFromRequestIdentifier:(NSString *) requestWithGroup {
return [requestWithGroup componentsSeparatedByString:@"|"][1];
}
--(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
- if (self.savedCompletionHandler) {
- self.savedCompletionHandler();
- self.savedCompletionHandler = nil;
+-(NSDictionary *)getCredentialsForRequest:(NSString *)requestWithGroup {
+ NSString * appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
+ NSString *entitiesString = [self.bucket readFromFile:@"entities" appGroupId:appGroupId];
+ NSData *entitiesData = [entitiesString dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error;
+ NSDictionary *entities = [NSJSONSerialization JSONObjectWithData:entitiesData options:NSJSONReadingMutableContainers error:&error];
+ if (error != nil) {
+ return nil;
+ }
+ return [[entities objectForKey:@"general"] objectForKey:@"credentials"];
+}
+
+-(NSDictionary *)getDataForRequest:(NSString *)requestWithGroup {
+ NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
+ return [[self.bucket bucketByName:appGroupId] objectForKey:requestWithGroup];
+}
+
+-(NSURLSession *)createSessionForRequestRequest:(NSString *)requestWithGroup {
+ NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
+ NSURLSessionConfiguration* config;
+ if (self.isBackground) {
+ config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:requestWithGroup];
+ } else {
+ config = [NSURLSessionConfiguration defaultSessionConfiguration];
+ }
+
+ config.sharedContainerIdentifier = appGroupId;
+ return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
+}
+
+-(void) createPost:(NSDictionary *) post
+ withFiles:(NSArray *)files
+ credentials:(NSDictionary *) credentials
+ requestWithGroup:(NSString *)requestWithGroup {
+ NSString *serverUrl = [credentials objectForKey:@"url"];
+ NSString *token = [credentials objectForKey:@"token"];
+ NSString *channelId = [post objectForKey:@"channel_id"];
+ NSURLSession *session = [self createSessionForRequestRequest:requestWithGroup];
+ NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
+
+ for (id file in files) {
+ NSURL *filePath = [NSURL fileURLWithPath:[file objectForKey:@"filePath"]];
+ NSString *fileName = [file objectForKey:@"filename"];
+
+ NSError *err;
+ NSURL *tempContainerURL = [self tempContainerURL:appGroupId];
+ NSURL *destinationURL = [tempContainerURL URLByAppendingPathComponent: fileName];
+ BOOL bVal = [[NSFileManager defaultManager] copyItemAtURL:filePath toURL:destinationURL error:&err];
+
+ NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet];
+ NSString *encodedFilename = [[file objectForKey:@"filename"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
+ NSString *url = [serverUrl stringByAppendingString:@"/api/v4/files"];
+ NSString *queryString = [NSString stringWithFormat:@"?channel_id=%@&filename=%@", channelId, encodedFilename];
+ NSURL *filesUrl = [NSURL URLWithString:[url stringByAppendingString:queryString]];
+ NSMutableURLRequest *uploadRequest = [NSMutableURLRequest requestWithURL:filesUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:120.0];
+ [uploadRequest setHTTPMethod:@"POST"];
+ [uploadRequest setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"];
+ [uploadRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
+
+ NSURLSessionUploadTask *task = [session uploadTaskWithRequest:uploadRequest fromFile:destinationURL];
+ NSLog(@"Executing file request %@", fileName);
+ [task resume];
}
}
--(void)URLSession:(NSURLSession *)session task:(NSURLSessionDataTask *)task didCompleteWithError:(nullable NSError *)error {
- NSString *requestWithGroup = [[session configuration] identifier];
+-(void) createPost:(NSMutableDictionary *)post
+ withFileIds:(NSArray *)fileIds
+ credentials:(NSDictionary *) credentials {
+ NSString *serverUrl = [credentials objectForKey:@"url"];
+ NSString *token = [credentials objectForKey:@"token"];
- if(error != nil) {
- NSLog(@"ERROR %@", [error userInfo]);
- NSLog(@"invalidating session %@", requestWithGroup);
- [session invalidateAndCancel];
- } else if (requestWithGroup != nil) {
- NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
- NSURL *requestUrl = [[task originalRequest] URL];
+ if (fileIds != nil && [fileIds count] > 0) {
+ [post setObject:fileIds forKey:@"file_ids"];
+ }
+
+ NSError *error;
+ NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:&error];
+
+ if (error == nil) {
+ NSString* postAsString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding];
- NSDictionary *data = [self getDataForRequest:requestWithGroup];
- NSArray *files = [data objectForKey:@"files"];
- NSMutableArray *fileIds = [data objectForKey:@"file_ids"];
+ NSURL *createUrl = [NSURL URLWithString:[serverUrl stringByAppendingString:@"/api/v4/posts"]];
- if ([[requestUrl absoluteString] containsString:@"files"] &&
- [files count] == [fileIds count]) {
- [self sendPostRequestForId:requestWithGroup];
- [[self.bucket bucketByName:appGroupId] removeObjectForKey:requestWithGroup];
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:createUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0];
+ [request setHTTPMethod:@"POST"];
+ [request setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"];
+ [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
+ [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
+ [request setHTTPBody:[postAsString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
+ NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
+ NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request];
+ NSLog(@"Executing post request");
+ [createTask resume];
+ }
+}
+
+-(void)createPostForRequest:(NSString *)requestWithGroup {
+ NSDictionary *data = [self getDataForRequest:requestWithGroup];
+ NSDictionary *post = [data objectForKey:@"post"];
+ NSArray *files = [data objectForKey:@"files"];
+ NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup];
+
+ if (credentials == nil) {
+ return;
+ }
+
+ if (files != nil && [files count] > 0) {
+ [self createPost:post withFiles:files credentials:credentials requestWithGroup:requestWithGroup];
+ }
+ else {
+ [self createPost:[post mutableCopy] withFileIds:nil credentials:credentials];
+ }
+}
+
+-(void)sendPostRequestForId:(NSString *)requestWithGroup {
+ NSDictionary *data = [self getDataForRequest:requestWithGroup];
+ NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup];
+
+ NSMutableDictionary *post = [[data objectForKey:@"post"] mutableCopy];
+ NSArray *fileIds = [data objectForKey:@"file_ids"];
+
+ [self createPost:post withFileIds:fileIds credentials:credentials];
+}
+
+-(NSURL*)tempContainerURL:(NSString*)appGroupId {
+ NSFileManager *manager = [NSFileManager defaultManager];
+ NSURL *containerURL = [manager containerURLForSecurityApplicationGroupIdentifier:appGroupId];
+ NSURL *tempDirectoryURL = [containerURL URLByAppendingPathComponent:@"shareTempItems"];
+ if (![manager fileExistsAtPath:[tempDirectoryURL path]]) {
+ NSError *err;
+ [manager createDirectoryAtURL:tempDirectoryURL withIntermediateDirectories:YES attributes:nil error:&err];
+ if (err) {
+ return nil;
}
}
+
+ return tempDirectoryURL;
+}
+
+
+-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
+ NSLog(@"completition handler from normal challenge");
+ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
+ if (self.certificateName) {
+ SecIdentityRef identity = [keyChain GetIdentityByName:self.certificateName];
+ if (identity != nil) {
+ SecCertificateRef certificate = NULL;
+ OSStatus status = SecIdentityCopyCertificate(identity, &certificate);
+ if (!status) {
+ CFArrayRef emailAddresses = NULL;
+ SecCertificateCopyEmailAddresses(certificate, &emailAddresses);
+ NSArray *emails = (NSArray *)CFBridgingRelease(emailAddresses);
+ CFStringRef summaryRef = SecCertificateCopySubjectSummary(certificate);
+ NSString *tagstr = (NSString*)CFBridgingRelease(summaryRef);
+ NSString *email = @"";
+ if ([emails count] > 0){
+ email = [emails objectAtIndex:0];
+ }
+ NSLog(@"completition %@ %@", tagstr, email);
+ const void *certs[] = {certificate};
+ CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
+ NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
+ [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
+ completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
+ NSLog(@"completition handler for certificate");
+ return;
+ }
+ }
+ }
+ completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ NSLog(@"completition handler that should not be reached");
+ } else {
+ NSLog(@"completition handler regular stuff");
+ completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ }
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
- NSString *requestWithGroup = [[session configuration] identifier];
+ NSString *requestWithGroup;
+ if (self.isBackground) {
+ requestWithGroup = [[session configuration] identifier];
+ } else {
+ requestWithGroup = self.requestWithGroup;
+ }
NSURL *requestUrl = [[dataTask originalRequest] URL];
if ([[requestUrl absoluteString] containsString:@"files"]) {
@@ -84,135 +254,41 @@
}
}
--(void)setDataForRequest:(NSDictionary *)data forRequestWithGroup:(NSString *)requestWithGroup {
- NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
- [[self.bucket bucketByName:appGroupId] setObject:data forKey:requestWithGroup];
-}
-
--(NSDictionary *)getDataForRequest:(NSString *)requestWithGroup {
- NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
- return [[self.bucket bucketByName:appGroupId] objectForKey:requestWithGroup];
-}
-
--(NSURLSession *)createSessionForRequestRequest:(NSString *)requestWithGroup {
- NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
- NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:requestWithGroup];
- config.sharedContainerIdentifier = appGroupId;
- return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
-}
-
--(NSDictionary *)getCredentialsForRequest:(NSString *)requestWithGroup {
- NSString * appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
- NSString *entitiesString = [self.bucket readFromFile:@"entities" appGroupId:appGroupId];
- NSData *entitiesData = [entitiesString dataUsingEncoding:NSUTF8StringEncoding];
- NSError *error;
- NSDictionary *entities = [NSJSONSerialization JSONObjectWithData:entitiesData options:NSJSONReadingMutableContainers error:&error];
- if (error != nil) {
- return nil;
+-(void)URLSession:(NSURLSession *)session task:(NSURLSessionDataTask *)task didCompleteWithError:(nullable NSError *)error {
+ NSString *requestWithGroup;
+ if (self.isBackground) {
+ requestWithGroup = [[session configuration] identifier];
+ } else {
+ requestWithGroup = self.requestWithGroup;
}
- return [[entities objectForKey:@"general"] objectForKey:@"credentials"];
-}
--(void)createPostForRequest:(NSString *)requestWithGroup {
- NSDictionary *data = [self getDataForRequest:requestWithGroup];
- NSDictionary *post = [data objectForKey:@"post"];
- NSArray *files = [data objectForKey:@"files"];
-
- if (files != nil && [files count] > 0) {
- NSString *channelId = [post objectForKey:@"channel_id"];
- NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup];
+ if(error != nil) {
+ NSLog(@"completition ERROR %@", [error userInfo]);
+ NSLog(@"invalidating session %@", requestWithGroup);
+ [session invalidateAndCancel];
+ } else if (requestWithGroup != nil) {
NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup];
+ NSURL *requestUrl = [[task originalRequest] URL];
- if (credentials == nil) {
- return;
- }
+ NSDictionary *data = [self getDataForRequest:requestWithGroup];
+ NSArray *files = [data objectForKey:@"files"];
+ NSMutableArray *fileIds = [data objectForKey:@"file_ids"];
- NSString *serverUrl = [credentials objectForKey:@"url"];
- NSString *token = [credentials objectForKey:@"token"];
- NSURLSession *session = [self createSessionForRequestRequest:requestWithGroup];
-
- for (id file in files) {
- NSURL *filePath = [NSURL fileURLWithPath:[file objectForKey:@"filePath"]];
- NSString *fileName = [file objectForKey:@"filename"];
-
- NSError *err;
- NSURL *tempContainerURL = [self tempContainerURL:appGroupId];
- NSURL *destinationURL = [tempContainerURL URLByAppendingPathComponent: fileName];
- BOOL bVal = [[NSFileManager defaultManager] copyItemAtURL:filePath toURL:destinationURL error:&err];
-
- NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet];
- NSString *encodedFilename = [[file objectForKey:@"filename"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
- NSString *url = [serverUrl stringByAppendingString:@"/api/v4/files"];
- NSString *queryString = [NSString stringWithFormat:@"?channel_id=%@&filename=%@", channelId, encodedFilename];
- NSURL *filesUrl = [NSURL URLWithString:[url stringByAppendingString:queryString]];
- NSMutableURLRequest *uploadRequest = [NSMutableURLRequest requestWithURL:filesUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:120.0];
- [uploadRequest setHTTPMethod:@"POST"];
- [uploadRequest setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"];
- [uploadRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
-
- NSURLSessionUploadTask *task = [session uploadTaskWithRequest:uploadRequest fromFile:destinationURL];
- NSLog(@"Executing file request %@", fileName);
- [task resume];
+ if ([[requestUrl absoluteString] containsString:@"files"] &&
+ [files count] == [fileIds count]) {
+ [self sendPostRequestForId:requestWithGroup];
+ [[self.bucket bucketByName:appGroupId] removeObjectForKey:requestWithGroup];
}
} else {
- [self sendPostRequestForId:requestWithGroup];
+ NSLog(@"SOMETHING ELSE");
}
}
--(void)sendPostRequestForId:(NSString *)requestWithGroup {
- NSDictionary *data = [self getDataForRequest:requestWithGroup];
- NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup];
-
- if (credentials == nil) {
- return;
- }
-
- NSString *serverUrl = [credentials objectForKey:@"url"];
- NSString *token = [credentials objectForKey:@"token"];
- NSMutableDictionary *post = [[data objectForKey:@"post"] mutableCopy];
- NSArray *fileIds = [data objectForKey:@"file_ids"];
-
- if (fileIds != nil && [fileIds count] > 0) {
- [post setObject:fileIds forKey:@"file_ids"];
- }
-
- NSError *error;
- NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:&error];
-
- if (error == nil) {
- NSString* postAsString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding];
-
- NSURL *createUrl = [NSURL URLWithString:[serverUrl stringByAppendingString:@"/api/v4/posts"]];
-
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:createUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0];
- [request setHTTPMethod:@"POST"];
- [request setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"];
- [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
- [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
- [request setHTTPBody:[postAsString dataUsingEncoding:NSUTF8StringEncoding]];
-
- NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
- NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
- NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request];
- NSLog(@"Executing post request");
- [createTask resume];
+-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
+ if (self.savedCompletionHandler) {
+ self.savedCompletionHandler();
+ self.savedCompletionHandler = nil;
}
}
--(NSURL*)tempContainerURL:(NSString*)appGroupId {
- NSFileManager *manager = [NSFileManager defaultManager];
- NSURL *containerURL = [manager containerURLForSecurityApplicationGroupIdentifier:appGroupId];
- NSURL *tempDirectoryURL = [containerURL URLByAppendingPathComponent:@"shareTempItems"];
- if (![manager fileExistsAtPath:[tempDirectoryURL path]]) {
- NSError *err;
- [manager createDirectoryAtURL:tempDirectoryURL withIntermediateDirectories:YES attributes:nil error:&err];
- if (err) {
- return nil;
- }
- }
-
- return tempDirectoryURL;
-}
-
-
@end
diff --git a/ios/MattermostShare/ShareViewController.m b/ios/MattermostShare/ShareViewController.m
index ff9797715..14f406dfc 100644
--- a/ios/MattermostShare/ShareViewController.m
+++ b/ios/MattermostShare/ShareViewController.m
@@ -52,10 +52,12 @@ RCT_EXPORT_METHOD(close:(NSDictionary *)data appGroupId:(NSString *)appGroupId)
if (data != nil) {
NSString *requestId = [data objectForKey:@"requestId"];
NSString *useBackgroundUpload = [data objectForKey:@"useBackgroundUpload"];
- BOOL isBackgroundUpload = useBackgroundUpload ? [useBackgroundUpload boolValue] : NO;
+ NSString *certificateName = [data objectForKey:@"certificate"];
+ BOOL tryToUploadInTheBackgound = useBackgroundUpload ? [useBackgroundUpload boolValue] : NO;
- if (isBackgroundUpload) {
+ if (tryToUploadInTheBackgound) {
NSString *requestWithGroup = [NSString stringWithFormat:@"%@|%@", requestId, appGroupId];
+ [[SessionManager sharedSession] setRequestWithGroup:requestWithGroup certificateName:certificateName];
[[SessionManager sharedSession] setDataForRequest:data forRequestWithGroup:requestWithGroup];
[[SessionManager sharedSession] createPostForRequest:requestWithGroup];
diff --git a/packager/modulePaths.js b/packager/modulePaths.js
index 271d6a544..b8d55d28d 100644
--- a/packager/modulePaths.js
+++ b/packager/modulePaths.js
@@ -143,6 +143,7 @@ module.exports = ['./node_modules/react-native/Libraries/ART/ARTSerializablePath
'./node_modules/app/actions/views/thread.js',
'./node_modules/app/actions/views/typing.js',
'./node_modules/app/app.js',
+ './node_modules/app/fetch_preconfig.js',
'./node_modules/app/components/announcement_banner/announcement_banner.js',
'./node_modules/app/components/announcement_banner/index.js',
'./node_modules/app/components/at_mention/at_mention.js',
diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js
index 79b98fcf7..b4083eb9c 100644
--- a/share_extension/ios/extension_post.js
+++ b/share_extension/ios/extension_post.js
@@ -569,6 +569,7 @@ export default class ExtensionPost extends PureComponent {
if (currentUserId && authenticated) {
await this.emmAuthenticationIfNeeded();
+ const certificate = await mattermostBucket.getPreference('cert', Config.AppGroupId);
try {
// Check to see if the use still belongs to the channel
@@ -584,6 +585,7 @@ export default class ExtensionPost extends PureComponent {
post,
requestId: generateId().replace(/-/g, ''),
useBackgroundUpload: this.useBackgroundUpload,
+ certificate,
};
this.setState({sending: true});
diff --git a/share_extension/ios/index.js b/share_extension/ios/index.js
index c2776600d..1d653eb4e 100644
--- a/share_extension/ios/index.js
+++ b/share_extension/ios/index.js
@@ -8,6 +8,7 @@ import DeviceInfo from 'react-native-device-info';
import Config from 'assets/config';
import {getTranslations} from 'app/i18n';
+import 'app/fetch_preconfig';
import {captureExceptionWithoutState, initializeSentry, LOGGER_EXTENSION} from 'app/utils/sentry';