Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d09ded8
WebSocket & Fetch - SSL Pinning
djorkaeffalexandre Aug 9, 2019
d32f99a
Fix access file
djorkaeffalexandre Aug 9, 2019
1aea747
Add certificate by uri-path
djorkaeffalexandre Aug 10, 2019
606b3cf
Create password alert to certificate
djorkaeffalexandre Aug 10, 2019
1d3b7c8
Copy file from tmp to documents dir
djorkaeffalexandre Aug 12, 2019
570ce69
Remove certificate when logout from server
djorkaeffalexandre Aug 12, 2019
0b847ce
Merge branch 'develop' into feature.ssl-pinning
djorkaeffalexandre Aug 12, 2019
c29c491
Can remove certificate after pick
djorkaeffalexandre Aug 13, 2019
c5be1a9
Refactor
djorkaeffalexandre Aug 13, 2019
ba4b984
Reorder and fix delete
djorkaeffalexandre Aug 14, 2019
385cf17
Refactor state
djorkaeffalexandre Aug 14, 2019
f05371c
Switch rn-fetch-blob -> expo-file-system
djorkaeffalexandre Aug 14, 2019
2460e48
Merge
djorkaeffalexandre Aug 14, 2019
f799db6
Review
djorkaeffalexandre Aug 16, 2019
8de015b
Merge branch 'develop' into feature.ssl-pinning
diegolmello Aug 19, 2019
fe598f1
Update code
djorkaeffalexandre Aug 19, 2019
1a013a9
Merge branch 'develop' into feature.ssl-pinning
diegolmello Aug 23, 2019
cb98f2c
Merge branch 'develop' into feature.ssl-pinning
diegolmello Aug 26, 2019
c43de0a
Merge branch 'develop' into feature.ssl-pinning
diegolmello Aug 27, 2019
67ff95c
Merge branch 'develop' into feature.ssl-pinning
diegolmello Aug 27, 2019
213b1f2
Merge branch 'develop' into feature.ssl-pinning
diegolmello Aug 28, 2019
d972c7e
Merge branch 'develop' into feature.ssl-pinning
diegolmello Sep 2, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/actions/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ export function selectServerFailure() {
};
}

export function serverRequest(server) {
export function serverRequest(server, certificate = null) {
return {
type: SERVER.REQUEST,
server
server,
certificate
};
}

Expand Down
5 changes: 5 additions & 0 deletions app/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default {
and: 'and',
announcement: 'announcement',
Announcement: 'Announcement',
Apply_Your_Certificate: 'Apply Your Certificate',
ARCHIVE: 'ARCHIVE',
archive: 'archive',
are_typing: 'are typing',
Expand Down Expand Up @@ -137,6 +138,8 @@ export default {
Copied_to_clipboard: 'Copied to clipboard!',
Copy: 'Copy',
Permalink: 'Permalink',
Certificate_password: 'Certificate Password',
Whats_the_password_for_your_certificate: 'What\'s the password for your certificate?',
Create_account: 'Create an account',
Create_Channel: 'Create Channel',
Created_snippet: 'Created a snippet',
Expand All @@ -155,6 +158,7 @@ export default {
Disable_notifications: 'Disable notifications',
Discussions: 'Discussions',
Dont_Have_An_Account: 'Don\'t have an account?',
Do_you_have_a_certificate: 'Do you have a certificate?',
Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?',
edit: 'edit',
edited: 'edited',
Expand Down Expand Up @@ -427,6 +431,7 @@ export default {
you: 'you',
You: 'You',
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
Your_certificate: 'Your Certificate',
Version_no: 'Version: {{version}}',
You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!',
Change_Language: 'Change Language',
Expand Down
8 changes: 8 additions & 0 deletions app/lib/rocketchat.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { AsyncStorage, InteractionManager } from 'react-native';
import semver from 'semver';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import RNUserDefaults from 'rn-user-defaults';
import * as FileSystem from 'expo-file-system';

import reduxStore from './createStore';
import defaultSettings from '../constants/settings';
import messagesStatus from '../constants/messagesStatus';
import database from './realm';
import log from '../utils/log';
import { isIOS, getBundleId } from '../utils/deviceInfo';
import { extractHostname } from '../utils/server';

import {
setUser, setLoginServices, loginRequest, loginFailure, logout
Expand Down Expand Up @@ -364,6 +366,12 @@ const RocketChat = {
try {
const servers = await RNUserDefaults.objectForKey(SERVERS);
await RNUserDefaults.setObjectForKey(SERVERS, servers && servers.filter(srv => srv[SERVER_URL] !== server));
// clear certificate for server - SSL Pinning
const certificate = await RNUserDefaults.objectForKey(extractHostname(server));
if (certificate && certificate.path) {
await RNUserDefaults.clear(extractHostname(server));
await FileSystem.deleteAsync(certificate.path);
}
} catch (error) {
console.log('logout_rn_user_defaults', error);
}
Expand Down
7 changes: 6 additions & 1 deletion app/sagas/selectServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { setUser } from '../actions/login';
import RocketChat from '../lib/rocketchat';
import database from '../lib/realm';
import log from '../utils/log';
import { extractHostname } from '../utils/server';
import I18n from '../i18n';
import { SERVERS, TOKEN, SERVER_URL } from '../constants/userDefaults';

Expand Down Expand Up @@ -77,8 +78,12 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
}
};

const handleServerRequest = function* handleServerRequest({ server }) {
const handleServerRequest = function* handleServerRequest({ server, certificate }) {
try {
if (certificate) {
yield RNUserDefaults.setObjectForKey(extractHostname(server), certificate);
}

const serverInfo = yield getServerInfo({ server });

const loginServicesLength = yield RocketChat.getLoginServices(server);
Expand Down
18 changes: 18 additions & 0 deletions app/utils/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
Extract hostname from url
url = 'https://open.rocket.chat/method'
hostname = 'open.rocket.chat'
*/
export const extractHostname = (url) => {
Comment thread
djorkaeffalexandre marked this conversation as resolved.
let hostname;

if (url.indexOf('//') > -1) {
[,, hostname] = url.split('/');
} else {
[hostname] = url.split('/');
}
[hostname] = hostname.split(':');
[hostname] = hostname.split('?');

return hostname;
};
118 changes: 111 additions & 7 deletions app/views/NewServerView.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity, View, Alert, LayoutAnimation
} from 'react-native';
import { connect } from 'react-redux';
import { SafeAreaView } from 'react-navigation';
import * as FileSystem from 'expo-file-system';
import DocumentPicker from 'react-native-document-picker';
import ActionSheet from 'react-native-action-sheet';
import isEqual from 'deep-equal';

import { serverRequest } from '../actions/server';
import sharedStyles from './Styles';
Expand All @@ -18,6 +22,7 @@ import { isIOS, isNotch } from '../utils/deviceInfo';
import { CustomIcon } from '../lib/Icons';
import StatusBar from '../containers/StatusBar';
import { COLOR_PRIMARY } from '../constants/colors';
import log from '../utils/log';

const styles = StyleSheet.create({
image: {
Expand All @@ -41,6 +46,22 @@ const styles = StyleSheet.create({
position: 'absolute',
paddingHorizontal: 9,
left: 15
},
certificatePicker: {
flex: 1,
marginTop: 40,
alignItems: 'center',
justifyContent: 'center'
},
chooseCertificateTitle: {
fontSize: 15,
...sharedStyles.textRegular,
...sharedStyles.textColorDescription
},
chooseCertificate: {
fontSize: 15,
...sharedStyles.textSemibold,
...sharedStyles.textColorHeaderBack
}
});

Expand All @@ -61,9 +82,19 @@ class NewServerView extends React.Component {
constructor(props) {
super(props);
const server = props.navigation.getParam('server');

// Cancel
this.options = [I18n.t('Cancel')];
this.CANCEL_INDEX = 0;

// Delete
this.options.push(I18n.t('Delete'));
this.DELETE_INDEX = 1;

this.state = {
text: server || '',
autoFocus: !server
autoFocus: !server,
certificate: null
};
}

Expand All @@ -76,11 +107,14 @@ class NewServerView extends React.Component {
}

shouldComponentUpdate(nextProps, nextState) {
const { text } = this.state;
const { text, certificate } = this.state;
const { connecting } = this.props;
if (nextState.text !== text) {
return true;
}
if (!isEqual(nextState.certificate, certificate)) {
return true;
}
if (nextProps.connecting !== connecting) {
return true;
}
Expand All @@ -91,13 +125,51 @@ class NewServerView extends React.Component {
this.setState({ text });
}

submit = () => {
const { text } = this.state;
submit = async() => {
const { text, certificate } = this.state;
const { connectServer } = this.props;
let cert = null;

if (certificate) {
const certificatePath = `${ FileSystem.documentDirectory }/${ certificate.name }`;
try {
await FileSystem.copyAsync({ from: certificate.path, to: certificatePath });
} catch (error) {
log('err_save_certificate', error);
}
cert = {
path: this.uriToPath(certificatePath), // file:// isn't allowed by obj-C
password: certificate.password
};
}

if (text) {
Keyboard.dismiss();
connectServer(this.completeUrl(text));
connectServer(this.completeUrl(text), cert);
}
}

chooseCertificate = async() => {
try {
const res = await DocumentPicker.pick({
type: ['com.rsa.pkcs-12']
});
const { uri: path, name } = res;
Alert.prompt(
I18n.t('Certificate_password'),
I18n.t('Whats_the_password_for_your_certificate'),
[
{
text: 'OK',
onPress: password => this.saveCertificate({ path, name, password })
}
],
'secure-text',
);
} catch (error) {
if (!DocumentPicker.isCancel(error)) {
log('err_choose_certificate', error);
}
}
}

Expand All @@ -120,6 +192,25 @@ class NewServerView extends React.Component {
return url.replace(/\/+$/, '');
}

uriToPath = uri => uri.replace('file://', '');

saveCertificate = (certificate) => {
LayoutAnimation.easeInEaseOut();
this.setState({ certificate });
}

handleDelete = () => this.setState({ certificate: null }); // We not need delete file from DocumentPicker because it is a temp file

showActionSheet = () => {
ActionSheet.showActionSheetWithOptions({
options: this.options,
cancelButtonIndex: this.CANCEL_INDEX,
destructiveButtonIndex: this.DELETE_INDEX
}, (actionIndex) => {
if (actionIndex === this.DELETE_INDEX) { this.handleDelete(); }
});
}

renderBack = () => {
const { navigation } = this.props;

Expand All @@ -142,6 +233,18 @@ class NewServerView extends React.Component {
);
}

renderCertificatePicker = () => {
const { certificate } = this.state;
return (
<View style={styles.certificatePicker}>
<Text style={styles.chooseCertificateTitle}>{certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')}</Text>
<TouchableOpacity onPress={certificate ? this.showActionSheet : this.chooseCertificate} testID='new-server-choose-certificate'>
<Text style={styles.chooseCertificate}>{certificate ? certificate.name : I18n.t('Apply_Your_Certificate')}</Text>
</TouchableOpacity>
</View>
);
}

render() {
const { connecting } = this.props;
const { text, autoFocus } = this.state;
Expand Down Expand Up @@ -175,6 +278,7 @@ class NewServerView extends React.Component {
loading={connecting}
testID='new-server-view-button'
/>
{ isIOS ? this.renderCertificatePicker() : null }
</SafeAreaView>
</ScrollView>
{this.renderBack()}
Expand All @@ -188,7 +292,7 @@ const mapStateToProps = state => ({
});

const mapDispatchToProps = dispatch => ({
connectServer: server => dispatch(serverRequest(server))
connectServer: (server, certificate) => dispatch(serverRequest(server, certificate))
});

export default connect(mapStateToProps, mapDispatchToProps)(NewServerView);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"commonmark-react-renderer": "git+https://github.com/RocketChat/commonmark-react-renderer.git",
"deep-equal": "^1.0.1",
"ejson": "2.2.0",
"expo-file-system": "^6.0.2",
"expo-av": "^6.0.0",
"expo-haptics": "6.0.0",
"expo-web-browser": "^6.0.0",
Expand Down
Loading