CBA support for iOS (#1767)

* CBA Support

* cba support for iOS Share extension

* Autologin with credentials

* Set initial config for ExperimentalClientSideCertEnable as false

* Fix mattermost-redux to cba branch

* feedback review

* Fix eslint
This commit is contained in:
Elias Nahum 2018-06-19 19:35:42 -04:00 committed by GitHub
parent 8372e95d4a
commit 7b18047800
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 521 additions and 187 deletions

1
.gitignore vendored
View file

@ -20,6 +20,7 @@ build/
*.perspectivev3
!default.perspectivev3
xcuserdata
xcshareddata
*.xccheckout
*.moved-aside
DerivedData

View file

@ -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';

View file

@ -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];

View file

@ -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();

View file

@ -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();
}

View file

@ -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,

View file

@ -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) {

View file

@ -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,

View file

@ -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) => {

View file

@ -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;

View file

@ -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);

View file

@ -12,6 +12,7 @@
"MobileNoticeURL": "https://about.mattermost.com/mobile-notice-txt/",
"SegmentApiKey": "oPPT2MA24VYZTBGDyXtUDa7u50lkPIE4",
"ExperimentalUsernamePressIsMention": false,
"ExperimentalClientSideCertEnable": false,
"HideEmailLoginExperimental": false,
"HideGitLabLoginExperimental": false,

BIN
ios/0155-keys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_0" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment version="2352" identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="CBAViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="s2F-x5-Lv6">
<rect key="frame" x="14" y="58" width="292" height="482"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="dataSource" destination="BYZ-38-t0r" id="2eX-Ud-Pqn"/>
<outlet property="delegate" destination="BYZ-38-t0r" id="uB2-Oo-O5Z"/>
</connections>
</tableView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fCA-uB-nbn">
<rect key="frame" x="20" y="20" width="79" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="79" id="gZp-zo-dTl"/>
</constraints>
<state key="normal" title="Import Key">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="openImportDocumentPicker:" destination="BYZ-38-t0r" eventType="touchUpInside" id="QQR-Nf-Rse"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hhu-eU-kQH">
<rect key="frame" x="258" y="20" width="48" height="30"/>
<state key="normal" title="Cancel">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="close:" destination="BYZ-38-t0r" eventType="touchUpInside" id="N2N-e8-aR3"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="s2F-x5-Lv6" firstAttribute="trailing" secondItem="hhu-eU-kQH" secondAttribute="trailing" id="22Z-D9-3i3"/>
<constraint firstItem="hhu-eU-kQH" firstAttribute="top" secondItem="k11-9p-T25" secondAttribute="top" id="77c-ha-usL"/>
<constraint firstItem="s2F-x5-Lv6" firstAttribute="centerX" secondItem="k11-9p-T25" secondAttribute="centerX" id="Kp3-FJ-pX7"/>
<constraint firstItem="s2F-x5-Lv6" firstAttribute="top" secondItem="fCA-uB-nbn" secondAttribute="bottom" constant="8" symbolic="YES" id="LS1-e5-n55"/>
<constraint firstItem="s2F-x5-Lv6" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="-2" id="Pco-NV-Fdf"/>
<constraint firstItem="fCA-uB-nbn" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="4" id="hjV-xC-rfW"/>
<constraint firstItem="fCA-uB-nbn" firstAttribute="baseline" secondItem="hhu-eU-kQH" secondAttribute="baseline" id="o5a-K8-ztC"/>
<constraint firstItem="k11-9p-T25" firstAttribute="bottom" secondItem="s2F-x5-Lv6" secondAttribute="bottom" constant="28" id="yUG-Ri-Rz9"/>
</constraints>
<viewLayoutGuide key="safeArea" id="k11-9p-T25"/>
</view>
<connections>
<outlet property="tableViewKeyChain" destination="s2F-x5-Lv6" id="GnK-3t-7Wi"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="564" y="538"/>
</scene>
</scenes>
</document>

View file

@ -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 = "<group>"; };
7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformRequests.m; sourceTree = "<group>"; };
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativePermissions.xcodeproj; path = "../node_modules/react-native-permissions/ios/ReactNativePermissions.xcodeproj"; sourceTree = "<group>"; };
7F9512EA208646CD00616E3E /* 0155-keys.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "0155-keys.png"; sourceTree = "<group>"; };
7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = "<group>"; };
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNTableView.xcodeproj; path = "../node_modules/react-native-tableview/RNTableView.xcodeproj"; sourceTree = "<group>"; };
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = "<group>"; };
@ -729,6 +732,7 @@
7FEB109A1F61019C0039A015 /* MattermostManaged.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MattermostManaged.m; path = Mattermost/MattermostManaged.m; sourceTree = "<group>"; };
7FEB109B1F61019C0039A015 /* UIImage+ImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ImageEffects.h"; path = "Mattermost/UIImage+ImageEffects.h"; sourceTree = "<group>"; };
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ImageEffects.m"; path = "Mattermost/UIImage+ImageEffects.m"; sourceTree = "<group>"; };
7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyShareConsumer.storyboard; sourceTree = "<group>"; };
7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MattermostBucket.h; sourceTree = "<group>"; };
7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = "<group>"; };
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 = "<group>";
@ -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/**",
);

View file

@ -54,7 +54,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@ -84,7 +83,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View file

@ -22,5 +22,9 @@
<array>
<string>group.com.mattermost.rnbeta</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.mattermost.rnbeta</string>
</array>
</dict>
</plist>

View file

@ -2,9 +2,27 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.mattermost.rnbeta</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.mattermost.rnbeta</string>
</array>
</dict>
</plist>

View file

@ -1,14 +1,20 @@
#import <Foundation/Foundation.h>
#import "MattermostBucket.h"
#import "KeyChainDataSource.h"
@interface SessionManager : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate>
@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;

View file

@ -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

View file

@ -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];

View file

@ -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',

View file

@ -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});

View file

@ -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';