From 9b405b86ab31b62fa8e5bd95076950d24179eadd Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 28 Jul 2022 12:18:28 +1000 Subject: [PATCH] fix: do not attempt to connect to an sogs already join make sure we consider http://, https:// and no protocol at all to look for matches --- .../open_group_api/opengroupV2/ApiUtil.ts | 108 ++++++++- .../opengroupV2/JoinOpenGroupV2.ts | 10 +- ts/test/session/unit/sogsv3/ApiUtil_test.ts | 205 ++++++++++++++++++ ts/test/test-utils/utils/message.ts | 2 +- 4 files changed, 317 insertions(+), 8 deletions(-) create mode 100644 ts/test/session/unit/sogsv3/ApiUtil_test.ts diff --git a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts index f1ef2c4ec..095f3e666 100644 --- a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts +++ b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import _, { compact, flatten, isString } from 'lodash'; import { allowOnlyOneAtATime } from '../../../utils/Promise'; import { updateDefaultRooms, @@ -7,6 +7,8 @@ import { import { getCompleteUrlFromRoom } from '../utils/OpenGroupUtils'; import { parseOpenGroupV2 } from './JoinOpenGroupV2'; import { getAllRoomInfos } from '../sogsv3/sogsV3RoomInfos'; +import { OpenGroupData } from '../../../../data/opengroups'; +import { getConversationController } from '../../../conversations'; export type OpenGroupRequestCommonType = { serverUrl: string; @@ -35,9 +37,109 @@ export type OpenGroupV2InfoJoinable = OpenGroupV2Info & { }; // tslint:disable: no-http-string -const defaultServerUrl = 'http://116.203.70.33'; + +const legacyDefaultServerIP = '116.203.70.33'; +const defaultServer = 'https://open.getsession.org'; +const defaultServerHost = new window.URL(defaultServer).host; + +/** + * This function returns true if the server url given matches any of the sogs run by Session. + * It basically compares the hostname of the given server, to the hostname or the ip address of the session run sogs. + * + * Note: Exported for test only + */ +export function isSessionRunOpenGroup(server: string): boolean { + if (!server || !isString(server)) { + return false; + } + + const lowerCased = server.toLowerCase(); + let serverHost: string | undefined; + try { + const lowerCasedUrl = new window.URL(lowerCased); + serverHost = lowerCasedUrl.hostname; // hostname because we don't want the port to be part of this + if (!serverHost) { + throw new Error('Could not parse URL from serverURL'); + } + } catch (e) { + // plain ip are not recognized are url, but we want to allow the, + serverHost = lowerCased; + } + + const options = [legacyDefaultServerIP, defaultServerHost]; + return options.includes(serverHost); +} + +/** + * Returns true if we have not joined any rooms matching this roomID and any combination of serverURL. + * + * This will look for http, https, and no prefix string serverURL, but also takes care of checking hostname/ip for session run sogs + */ +export function hasExistingOpenGroup(server: string, roomId: string) { + if (!server || !isString(server)) { + return false; + } + + const serverNotLowerCased = server; + const serverLowerCase = serverNotLowerCased.toLowerCase(); + + let serverUrl: URL | undefined; + try { + serverUrl = new window.URL(serverLowerCase); + if (!serverUrl) { + throw new Error('failed to parse url in hasExistingOpenGroup'); + } + } catch (e) { + try { + serverUrl = new window.URL(`http://${serverLowerCase}`); + } catch (e) { + window.log.error(`hasExistingOpenGroup with ${serverNotLowerCased} with ${e.message}`); + + return false; + } + } + + // make sure that serverUrl.host has the port set in it + + const serverOptions: Set = new Set([ + serverLowerCase, + `${serverUrl.host}`, + `http://${serverUrl.host}`, + `https://${serverUrl.host}`, + ]); + + // If the server is run by Session then include all configurations in case one of the alternate configurations is used + if (isSessionRunOpenGroup(serverLowerCase)) { + serverOptions.add(defaultServerHost); + serverOptions.add(`http://${defaultServerHost})`); + serverOptions.add(`https://${defaultServerHost})`); + serverOptions.add(legacyDefaultServerIP); + serverOptions.add(`http://${legacyDefaultServerIP}`); + serverOptions.add(`https://${legacyDefaultServerIP}`); + } + + const rooms = flatten( + compact([...serverOptions].map(OpenGroupData.getV2OpenGroupRoomsByServerUrl)) + ); + + if (rooms.length === 0) { + // we didn't find any room matching any of that url. We cannot have join that serverURL yet then + + return false; + } + + // We did find some rooms by serverURL but now we need to make sure none of those matches the room we are about to join. + const matchingRoom = rooms.find(r => r.roomId === roomId); + + return Boolean( + matchingRoom && + matchingRoom.conversationId && + getConversationController().get(matchingRoom.conversationId) + ); +} + const defaultServerPublicKey = 'a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238'; -const defaultRoom = `${defaultServerUrl}/main?public_key=${defaultServerPublicKey}`; +const defaultRoom = `${defaultServer}/main?public_key=${defaultServerPublicKey}`; const loadDefaultRoomsSingle = () => allowOnlyOneAtATime( diff --git a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts index cb934086e..7b0365f35 100644 --- a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts +++ b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { OpenGroupData, OpenGroupV2Room } from '../../../../data/opengroups'; +import { OpenGroupV2Room } from '../../../../data/opengroups'; import { getConversationController } from '../../../conversations'; import { PromiseUtils, ToastUtils } from '../../../utils'; @@ -10,6 +10,7 @@ import { prefixify, publicKeyParam, } from '../utils/OpenGroupUtils'; +import { hasExistingOpenGroup } from './ApiUtil'; import { getOpenGroupManager } from './OpenGroupManagerV2'; // tslint:disable: variable-name @@ -66,11 +67,11 @@ async function joinOpenGroupV2(room: OpenGroupV2Room, fromConfigMessage: boolean const publicKey = room.serverPublicKey.toLowerCase(); const prefixedServer = prefixify(serverUrl); - const alreadyExist = OpenGroupData.getV2OpenGroupRoomByRoomId({ serverUrl, roomId }); + const alreadyExist = hasExistingOpenGroup(serverUrl, roomId); const conversationId = getOpenGroupV2ConversationId(serverUrl, roomId); const existingConvo = getConversationController().get(conversationId); - if (alreadyExist && existingConvo) { + if (alreadyExist) { window?.log?.warn('Skipping join opengroupv2: already exists'); return; } else if (existingConvo) { @@ -130,8 +131,9 @@ export async function joinOpenGroupV2WithUIEvents( } return false; } + const alreadyExist = hasExistingOpenGroup(parsedRoom.serverUrl, parsedRoom.roomId); const conversationID = getOpenGroupV2ConversationId(parsedRoom.serverUrl, parsedRoom.roomId); - if (getConversationController().get(conversationID)) { + if (alreadyExist || getConversationController().get(conversationID)) { if (showToasts) { ToastUtils.pushToastError('publicChatExists', window.i18n('publicChatExists')); } diff --git a/ts/test/session/unit/sogsv3/ApiUtil_test.ts b/ts/test/session/unit/sogsv3/ApiUtil_test.ts new file mode 100644 index 000000000..e5c1f0df9 --- /dev/null +++ b/ts/test/session/unit/sogsv3/ApiUtil_test.ts @@ -0,0 +1,205 @@ +// tslint:disable: chai-vague-errors no-unused-expression no-http-string max-func-body-length + +import { expect } from 'chai'; +import Sinon from 'sinon'; +import { OpenGroupData, OpenGroupV2Room } from '../../../../data/opengroups'; +import { ConversationCollection } from '../../../../models/conversation'; +import { ConversationTypeEnum } from '../../../../models/conversationAttributes'; +import { + hasExistingOpenGroup, + isSessionRunOpenGroup, +} from '../../../../session/apis/open_group_api/opengroupV2/ApiUtil'; +import { getOpenGroupV2ConversationId } from '../../../../session/apis/open_group_api/utils/OpenGroupUtils'; +import { getConversationController } from '../../../../session/conversations'; +import { stubData, stubOpenGroupData, stubWindowLog } from '../../../test-utils/utils'; + +describe('APIUtils', () => { + beforeEach(() => { + stubWindowLog(); + }); + afterEach(() => { + Sinon.restore(); + }); + + describe('isSessionRunOpenGroup', () => { + it('returns false undefined serverUrl', () => { + expect(isSessionRunOpenGroup(undefined as any)).to.be.false; + }); + it('returns false empty serverUrl', () => { + expect(isSessionRunOpenGroup('')).to.be.false; + }); + it('returns false invalid URL', () => { + expect(isSessionRunOpenGroup('kfdjfdfdl://sdkfjsd')).to.be.false; + }); + it('returns true if url matches ip without prefix', () => { + expect(isSessionRunOpenGroup('116.203.70.33')).to.be.true; + }); + it('returns true if url matches ip http prefix', () => { + expect(isSessionRunOpenGroup('http://116.203.70.33')).to.be.true; + }); + it('returns true if url matches ip https prefix', () => { + expect(isSessionRunOpenGroup('https://116.203.70.33')).to.be.true; + }); + it('returns true if url matches ip https prefix and port', () => { + expect(isSessionRunOpenGroup('https://116.203.70.33:443')).to.be.true; + }); + it('returns true if url matches ip http prefix and port', () => { + expect(isSessionRunOpenGroup('http://116.203.70.33:80')).to.be.true; + }); + it('returns true if url matches ip http prefix and custom port', () => { + expect(isSessionRunOpenGroup('http://116.203.70.33:4433')).to.be.true; + }); + + it('returns true if url matches hostname without prefix', () => { + expect(isSessionRunOpenGroup('open.getsession.org')).to.be.true; + }); + it('returns true if url matches hostname http prefix', () => { + expect(isSessionRunOpenGroup('http://open.getsession.org')).to.be.true; + }); + it('returns true if url matches hostname https prefix', () => { + expect(isSessionRunOpenGroup('https://open.getsession.org')).to.be.true; + }); + it('returns true if url matches hostname https prefix and port', () => { + expect(isSessionRunOpenGroup('https://open.getsession.org:443')).to.be.true; + }); + it('returns true if url matches hostname http prefix and port', () => { + expect(isSessionRunOpenGroup('http://open.getsession.org:80')).to.be.true; + }); + it('returns true if url matches hostname http prefix and port and not lowercased', () => { + expect(isSessionRunOpenGroup('http://open.GETSESSION.org:80')).to.be.true; + }); + it('returns true if url matches hostname http prefix and custom port', () => { + expect(isSessionRunOpenGroup('http://open.getsession.org:4433')).to.be.true; + }); + }); + + describe('hasExistingOpenGroup', () => { + it('returns false undefined serverUrl', () => { + expect(hasExistingOpenGroup(undefined as any, '')).to.be.false; + }); + it('returns false empty serverUrl', () => { + expect(hasExistingOpenGroup('', '')).to.be.false; + }); + describe('no matching room', () => { + beforeEach(async () => { + stubData('getAllConversations').resolves(new ConversationCollection([])); + stubData('saveConversation').resolves(); + stubData('getItemById').resolves(); + stubOpenGroupData('getAllV2OpenGroupRooms').resolves(); + getConversationController().reset(); + + await getConversationController().load(); + await OpenGroupData.opengroupRoomsLoad(); + }); + afterEach(() => { + Sinon.restore(); + }); + describe('is a session run opengroup', () => { + it('returns false if there no rooms matching that serverURL with http prefix', () => { + expect(hasExistingOpenGroup('http://116.203.70.33', 'roomId')).to.be.false; + }); + it('returns false if there no rooms matching that serverURL with https prefix', () => { + expect(hasExistingOpenGroup('https://116.203.70.33', 'roomId')).to.be.false; + }); + it('returns false if there no rooms matching that serverURL no prefix', () => { + expect(hasExistingOpenGroup('116.203.70.33', 'roomId')).to.be.false; + }); + it('returns false if there no rooms matching that serverURL no prefix with port', () => { + expect(hasExistingOpenGroup('http://116.203.70.33:4433', 'roomId')).to.be.false; + }); + + it('returns false if there no rooms matching that serverURL domain no prefix with port', () => { + expect(hasExistingOpenGroup('http://open.getsession.org:4433', 'roomId')).to.be.false; + }); + }); + describe('is NOT a SESSION run opengroup', () => { + it('returns false if there no rooms matching that serverURL with http prefix', () => { + expect(hasExistingOpenGroup('http://1.1.1.1', 'roomId')).to.be.false; + expect(hasExistingOpenGroup('http://1.1.1.1:4433', 'roomId')).to.be.false; + expect(hasExistingOpenGroup('http://plop.com:4433', 'roomId')).to.be.false; + expect(hasExistingOpenGroup('https://plop.com', 'roomId')).to.be.false; + }); + }); + }); + + describe('has matching rooms', () => { + let getV2OpenGroupRoomsByServerUrl: Sinon.SinonStub; + const convoIdOurIp = getOpenGroupV2ConversationId('116.203.70.33', 'fish'); + const convoIdOurUrl = getOpenGroupV2ConversationId('open.getsession.org', 'fishUrl'); + const convoIdNotOur = getOpenGroupV2ConversationId('open.somethingelse.org', 'fishElse'); + + beforeEach(async () => { + stubData('getAllConversations').resolves(new ConversationCollection([])); + stubData('saveConversation').resolves(); + stubData('getItemById').resolves(); + stubOpenGroupData('getAllV2OpenGroupRooms').resolves(); + getV2OpenGroupRoomsByServerUrl = stubOpenGroupData('getV2OpenGroupRoomsByServerUrl'); + getConversationController().reset(); + + await getConversationController().load(); + + const convoOurIp = await getConversationController().getOrCreateAndWait( + convoIdOurIp, + ConversationTypeEnum.GROUP + ); + convoOurIp.set({ active_at: Date.now() }); + const convoOurUrl = await getConversationController().getOrCreateAndWait( + convoIdOurUrl, + ConversationTypeEnum.GROUP + ); + convoOurUrl.set({ active_at: Date.now() }); + const convoNotOur = await getConversationController().getOrCreateAndWait( + convoIdNotOur, + ConversationTypeEnum.GROUP + ); + convoNotOur.set({ active_at: Date.now() }); + await OpenGroupData.opengroupRoomsLoad(); + }); + afterEach(() => { + Sinon.restore(); + }); + describe('is a session run opengroup', () => { + it('returns false if there no rooms matching that ip and roomID ', () => { + const rooms: Array = []; + getV2OpenGroupRoomsByServerUrl.returns(rooms); + expect(hasExistingOpenGroup('http://116.203.70.33', 'roomId')).to.be.false; + expect(hasExistingOpenGroup('116.203.70.33', 'roomId')).to.be.false; + expect(hasExistingOpenGroup('https://116.203.70.33', 'roomId')).to.be.false; + }); + it('returns true if there a room matching that ip and roomID ', () => { + const rooms: Array = [ + { + roomId: 'fish', + serverUrl: 'http://116.203.70.33', + serverPublicKey: 'whatever', + conversationId: convoIdOurIp, + }, + ]; + getV2OpenGroupRoomsByServerUrl.returns(rooms); + + expect(hasExistingOpenGroup('http://116.203.70.33', 'fish')).to.be.true; + expect(hasExistingOpenGroup('116.203.70.33', 'fish')).to.be.true; + expect(hasExistingOpenGroup('https://116.203.70.33', 'fish')).to.be.true; + expect(hasExistingOpenGroup('https://116.203.70.33', 'fish2')).to.be.false; + }); + + it('returns true if there a room matching that url and roomID ', () => { + const rooms: Array = [ + { + roomId: 'fishUrl', + serverUrl: 'http://open.getsession.org', + serverPublicKey: 'whatever', + conversationId: convoIdOurUrl, + }, + ]; + getV2OpenGroupRoomsByServerUrl.returns(rooms); + + expect(hasExistingOpenGroup('http://open.getsession.org', 'fishUrl')).to.be.true; + expect(hasExistingOpenGroup('open.getsession.org', 'fishUrl')).to.be.true; + expect(hasExistingOpenGroup('https://open.getsession.org', 'fishUrl')).to.be.true; + expect(hasExistingOpenGroup('https://open.getsession.org', 'fish2')).to.be.false; + }); + }); + }); + }); +}); diff --git a/ts/test/test-utils/utils/message.ts b/ts/test/test-utils/utils/message.ts index e9dd3ed16..922cd7865 100644 --- a/ts/test/test-utils/utils/message.ts +++ b/ts/test/test-utils/utils/message.ts @@ -43,7 +43,7 @@ export function generateOpenGroupVisibleMessage(): OpenGroupVisibleMessage { export function generateOpenGroupV2RoomInfos(): OpenGroupRequestCommonType { // tslint:disable-next-line: no-http-string - return { roomId: 'main', serverUrl: 'http://116.203.70.33' }; + return { roomId: 'main', serverUrl: 'http://open.getsession.org' }; } export function generateClosedGroupMessage(groupId?: string): ClosedGroupVisibleMessage {