Fix SSO and clear cookies (#4422)

* Fix SSO and clear cookies

* Fix unit tests

* Removed unnecessary argument

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>

Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
This commit is contained in:
Elias Nahum 2020-06-15 14:00:13 -04:00 committed by GitHub
parent e0d2fba243
commit 023109e250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 104 additions and 111 deletions

View file

@ -5,6 +5,7 @@ import {Keyboard, Platform} from 'react-native';
import {Navigation} from 'react-native-navigation';
import merge from 'deepmerge';
import {Preferences} from '@mm-redux/constants';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import EphemeralStore from '@store/ephemeral_store';
@ -83,7 +84,7 @@ export function resetToChannel(passProps = {}) {
}
export function resetToSelectServer(allowOtherServers) {
const theme = getThemeFromState();
const theme = Preferences.THEMES.default;
Navigation.setRoot({
root: {

View file

@ -157,33 +157,35 @@ class GlobalEventHandler {
emmProvider.handleManagedConfig(true);
};
onLogout = async () => {
Store.redux.dispatch(closeWebSocket(false));
Store.redux.dispatch(setServerVersion(''));
if (analytics) {
await analytics.reset();
}
removeAppCredentials();
deleteFileCache();
resetMomentLocale();
// TODO: Handle when multi-server support is added
clearCookiesAndWebData = async () => {
try {
await CookieManager.clearAll(Platform.OS === 'ios');
} catch (error) {
// Nothing to clear
}
PushNotifications.clearNotifications();
const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
const mainPath = cacheDir.split('/').slice(0, -1).join('/');
mattermostBucket.removePreference('cert');
mattermostBucket.removePreference('emm');
if (Platform.OS === 'ios') {
mattermostBucket.removeFile('entities');
} else {
switch (Platform.OS) {
case 'ios': {
const mainPath = RNFetchBlob.fs.dirs.DocumentDir.split('/').slice(0, -1).join('/');
const libraryDir = `${mainPath}/Library`;
const cookiesDir = `${libraryDir}/Cookies`;
const cookies = await RNFetchBlob.fs.exists(cookiesDir);
const webkitDir = `${libraryDir}/WebKit`;
const webkit = await RNFetchBlob.fs.exists(webkitDir);
if (cookies) {
RNFetchBlob.fs.unlink(cookiesDir);
}
if (webkit) {
RNFetchBlob.fs.unlink(webkitDir);
}
break;
}
case 'android': {
const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
const mainPath = cacheDir.split('/').slice(0, -1).join('/');
const cookies = await RNFetchBlob.fs.exists(`${mainPath}/app_webview/Cookies`);
const cookiesJ = await RNFetchBlob.fs.exists(`${mainPath}/app_webview/Cookies-journal`);
if (cookies) {
@ -193,7 +195,31 @@ class GlobalEventHandler {
if (cookiesJ) {
RNFetchBlob.fs.unlink(`${mainPath}/app_webview/Cookies-journal`);
}
break;
}
}
};
onLogout = async () => {
Store.redux.dispatch(closeWebSocket(false));
Store.redux.dispatch(setServerVersion(''));
if (analytics) {
await analytics.reset();
}
mattermostBucket.removePreference('cert');
mattermostBucket.removePreference('emm');
if (Platform.OS === 'ios') {
mattermostBucket.removeFile('entities');
}
removeAppCredentials();
deleteFileCache();
resetMomentLocale();
await this.clearCookiesAndWebData();
PushNotifications.clearNotifications();
if (this.launchApp) {
this.launchApp();

View file

@ -7,13 +7,11 @@ import {intlShape} from 'react-intl';
import {
Dimensions,
Image,
Platform,
ScrollView,
StyleSheet,
Text,
} from 'react-native';
import Button from 'react-native-button';
import CookieManager from '@react-native-community/cookies';
import {goToScreen} from '@actions/navigation';
import LocalConfig from '@assets/config';
@ -23,6 +21,7 @@ import FormattedText from '@components/formatted_text';
import StatusBar from '@components/status_bar';
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
import {ViewTypes} from '@constants';
import globalEventHandler from '@init/global_event_handler';
import {preventDoubleTap} from '@utils/tap';
import {GlobalStyles} from 'app/styles';
@ -50,11 +49,8 @@ export default class LoginOptions extends PureComponent {
const {intl} = this.context;
const screen = 'Login';
const title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'});
try {
await CookieManager.clearAll(Platform.OS === 'ios');
} catch (error) {
// Nothing to clear
}
globalEventHandler.clearCookiesAndWebData();
goToScreen(screen, title);
});
@ -62,11 +58,8 @@ export default class LoginOptions extends PureComponent {
const {intl} = this.context;
const screen = 'SSO';
const title = intl.formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'});
try {
await CookieManager.clearAll(Platform.OS === 'ios');
} catch (error) {
// Nothing to clear
}
globalEventHandler.clearCookiesAndWebData();
goToScreen(screen, title, {ssoType});
};

View file

@ -27,7 +27,7 @@ const HEADERS = {
'X-Mobile-App': 'mattermost',
};
const postMessageJS = "window.ReactNativeWebView.postMessage(document.body.innerText, '*');";
const postMessageJS = 'window.ReactNativeWebView.postMessage(document.body.innerText);';
// Used to make sure that OneLogin forms scale appropriately on both platforms.
const oneLoginFormScalingJS = `
@ -104,6 +104,44 @@ class SSO extends PureComponent {
}
}
componentWillUnmount() {
clearTimeout(this.cookiesTimeout);
}
extractCookie = (parsedUrl) => {
CookieManager.get(parsedUrl.origin, true).then((res) => {
const mmtoken = res.MMAUTHTOKEN;
const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken;
if (token) {
clearTimeout(this.cookiesTimeout);
this.setState({renderWebView: false});
const {
ssoLogin,
} = this.props.actions;
Client4.setToken(token);
if (this.props.serverUrl !== parsedUrl.origin) {
const original = urlParse(this.props.serverUrl);
// Check whether we need to set a sub-path
parsedUrl.set('pathname', original.pathname || '');
Client4.setUrl(parsedUrl.href);
}
ssoLogin(token).then((result) => {
if (result.error) {
this.onLoadEndError(result.error);
return;
}
this.goToChannel();
});
} else if (this.webView && !this.state.error) {
this.webView.injectJavaScript(postMessageJS);
this.cookiesTimeout = setTimeout(this.extractCookie.bind(null, parsedUrl), 250);
}
});
}
goToChannel = () => {
tracker.initialLoad = Date.now();
@ -122,6 +160,7 @@ class SSO extends PureComponent {
status_code: statusCode,
} = response;
if (id && message && statusCode !== 200) {
clearTimeout(this.cookiesTimeout);
this.setState({error: message});
}
}
@ -158,35 +197,7 @@ class SSO extends PureComponent {
}
if (isLastRedirect) {
CookieManager.get(parsed.origin, this.useWebkit).then((res) => {
const mmtoken = res.MMAUTHTOKEN;
const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken;
if (token) {
this.setState({renderWebView: false});
const {
ssoLogin,
} = this.props.actions;
Client4.setToken(token);
if (this.props.serverUrl !== parsed.origin) {
const original = urlParse(this.props.serverUrl);
// Check whether we need to set a sub-path
parsed.set('pathname', original.pathname || '');
Client4.setUrl(parsed.href);
}
ssoLogin(token).then((result) => {
if (result.error) {
this.onLoadEndError(result.error);
return;
}
this.goToChannel();
});
} else if (this.webView && !this.state.error) {
this.webView.injectJavaScript(postMessageJS);
}
});
this.extractCookie(parsed);
}
};
@ -235,9 +246,10 @@ class SSO extends PureComponent {
onShouldStartLoadWithRequest={() => true}
injectedJavaScript={jsCode}
onLoadEnd={this.onLoadEnd}
onMessage={messagingEnabled ? this.onMessage : null}
onMessage={(messagingEnabled || Platform.OS === 'android') ? this.onMessage : null}
sharedCookiesEnabled={Platform.OS === 'android'}
cacheEnabled={false}
useSharedProcessPool={false}
/>
);
}

View file

@ -207,7 +207,7 @@ PODS:
- React-jsinspector (0.62.2)
- react-native-cameraroll (1.6.2):
- React
- react-native-cookies (2.0.9):
- react-native-cookies (3.0.0):
- React
- react-native-document-picker (3.4.0):
- React
@ -597,7 +597,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da
React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493
react-native-cameraroll: 94bec91c68b94ac946c61b497b594bb38692c41b
react-native-cookies: bc1f91a504ee091f1998a1b6c7ff4b7a9ed5b80e
react-native-cookies: 6fe6a70d3b2fd8e81ddcd94d60a87727010d9b2e
react-native-document-picker: d694111879537cec2c258a1dcd2243d9df746824
react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53
react-native-image-picker: 668e72d0277dc8c12ae90e835507c1eddd2e4f85

6
package-lock.json generated
View file

@ -5223,9 +5223,9 @@
}
},
"@react-native-community/cookies": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/@react-native-community/cookies/-/cookies-2.0.9.tgz",
"integrity": "sha512-gZvnLQjkpZX//4/CIOX4lOFdfOT2gQi/Ov1t+K6D2KZ0GyqY1CQ9xe0F1NsRSIGciTUkxOPTeway/LRWgwHsxQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/cookies/-/cookies-3.0.0.tgz",
"integrity": "sha512-ms/CsrBlHffTyTRhXsKSmSK6WlWScG8wklfSmkr4WeeA0j0AQ1sknTg8YNsK0bMr6Ij1uDuvX9t96vZrFGpYqQ==",
"requires": {
"invariant": "^2.2.4"
}

View file

@ -10,7 +10,7 @@
"@babel/runtime": "7.9.6",
"@react-native-community/async-storage": "1.10.1",
"@react-native-community/cameraroll": "1.6.2",
"@react-native-community/cookies": "2.0.9",
"@react-native-community/cookies": "3.0.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "5.8.1",
"@react-navigation/native": "5.3.2",

View file

@ -1,39 +0,0 @@
diff --git a/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m b/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m
index 9cdc74d..5477405 100644
--- a/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m
+++ b/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m
@@ -139,7 +139,8 @@ + (BOOL)requiresMainQueueSetup
[cookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *allCookies) {
NSMutableDictionary *cookies = [NSMutableDictionary dictionary];
for (NSHTTPCookie *cookie in allCookies) {
- if ([topLevelDomain containsString:cookie.domain]) {
+ NSString *domainWithDot = [NSString stringWithFormat:@".%@", cookie.domain];
+ if([cookie.domain containsString:topLevelDomain] || [domainWithDot containsString:topLevelDomain]) {
[cookies setObject:[self createCookieData:cookie] forKey:cookie.name];
}
}
@@ -185,6 +186,8 @@ + (BOOL)requiresMainQueueSetup
}
resolve(nil);
}
+
+ [self deleteBinaryCookiesAndWebsiteData];
}
RCT_EXPORT_METHOD(clearByName:(NSString *) name
@@ -244,4 +247,15 @@ -(NSDictionary *)createCookieData:(NSHTTPCookie *)cookie
return cookieData;
}
+-(void) deleteBinaryCookiesAndWebsiteData {
+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];
+ NSString *cookiesPath = [libraryPath stringByAppendingString:@"/Cookies/Cookies.binarycookies"];
+ NSString *dataPath = [libraryPath stringByAppendingString:@"/WebKit/WebsiteData"];
+
+ NSError *error;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ [fileManager removeItemAtPath:cookiesPath error:&error];
+ [fileManager removeItemAtPath:dataPath error:&error];
+}
+
@end

View file

@ -122,7 +122,7 @@ jest.mock('react-native-device-info', () => {
jest.mock('rn-fetch-blob', () => ({
fs: {
dirs: {
DocumentDir: () => jest.fn(),
DocumentDir: '/data/com.mattermost.beta/documents',
CacheDir: '/data/com.mattermost.beta/cache',
},
exists: jest.fn(),