[MM-39885] Migrate unit tests to Jest, fleshed out tests for common/util, a bunch of cleanup (#1852)
* [MM-39885] Migrate unit tests to Jest, fleshed out tests for common/util/url * Typo fix * Oops * I found more tests! * Fixed a bug * Oops again * Tests for common/utils/util * A bunch of cleanup * Oops
This commit is contained in:
@@ -11,6 +11,3 @@ import './window_test.js';
|
|||||||
import './browser/index_test.js';
|
import './browser/index_test.js';
|
||||||
import './browser/modal_test.js';
|
import './browser/modal_test.js';
|
||||||
import './browser/settings_test.js';
|
import './browser/settings_test.js';
|
||||||
|
|
||||||
import './main/user_activity_monitor_test.js';
|
|
||||||
import './utils/util_test.js';
|
|
||||||
|
@@ -1,106 +0,0 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import assert from 'assert';
|
|
||||||
|
|
||||||
import urlUtils from '../../../src/common/utils/url';
|
|
||||||
|
|
||||||
describe('Utils', () => {
|
|
||||||
describe('isValidURL', () => {
|
|
||||||
it('should be true for a valid web url', () => {
|
|
||||||
const testURL = 'https://developers.mattermost.com/';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a valid, non-https web url', () => {
|
|
||||||
const testURL = 'http://developers.mattermost.com/';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for an invalid, self-defined, top-level domain', () => {
|
|
||||||
const testURL = 'https://www.example.x';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a file download url', () => {
|
|
||||||
const testURL = 'https://community.mattermost.com/api/v4/files/ka3xbfmb3ffnmgdmww8otkidfw?download=1';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a permalink url', () => {
|
|
||||||
const testURL = 'https://community.mattermost.com/test-channel/pl/pdqowkij47rmbyk78m5hwc7r6r';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a valid, internal domain', () => {
|
|
||||||
const testURL = 'https://mattermost.company-internal';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a second, valid internal domain', () => {
|
|
||||||
const testURL = 'https://serverXY/mattermost';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a valid, non-https internal domain', () => {
|
|
||||||
const testURL = 'http://mattermost.local';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be true for a valid, non-https, ip address with port number', () => {
|
|
||||||
const testURL = 'http://localhost:8065';
|
|
||||||
assert.equal(urlUtils.isValidURL(testURL), true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('isValidURI', () => {
|
|
||||||
it('should be true for a deeplink url', () => {
|
|
||||||
const testURL = 'mattermost://community-release.mattermost.com/core/channels/developers';
|
|
||||||
assert.equal(urlUtils.isValidURI(testURL), true);
|
|
||||||
});
|
|
||||||
it('should be false for a malicious url', () => {
|
|
||||||
const testURL = String.raw`mattermost:///" --data-dir "\\deans-mbp\mattermost`;
|
|
||||||
assert.equal(urlUtils.isValidURI(testURL), false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('isInternalURL', () => {
|
|
||||||
it('should be false for different hosts', () => {
|
|
||||||
const currentURL = new URL('http://localhost/team/channel1');
|
|
||||||
const targetURL = new URL('http://example.com/team/channel2');
|
|
||||||
const basename = '/';
|
|
||||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be false for same hosts, non-matching basename', () => {
|
|
||||||
const currentURL = new URL('http://localhost/subpath/team/channel1');
|
|
||||||
const targetURL = new URL('http://localhost/team/channel2');
|
|
||||||
const basename = '/subpath';
|
|
||||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be true for same hosts, matching basename', () => {
|
|
||||||
const currentURL = new URL('http://localhost/subpath/team/channel1');
|
|
||||||
const targetURL = new URL('http://localhost/subpath/team/channel2');
|
|
||||||
const basename = '/subpath';
|
|
||||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be true for same hosts, default basename', () => {
|
|
||||||
const currentURL = new URL('http://localhost/team/channel1');
|
|
||||||
const targetURL = new URL('http://localhost/team/channel2');
|
|
||||||
const basename = '/';
|
|
||||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be true for same hosts, default basename, empty target path', () => {
|
|
||||||
const currentURL = new URL('http://localhost/team/channel1');
|
|
||||||
const targetURL = new URL('http://localhost/');
|
|
||||||
const basename = '/';
|
|
||||||
assert.equal(urlUtils.isInternalURL(targetURL, currentURL, basename), true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getHost', () => {
|
|
||||||
it('should return the origin of a well formed url', () => {
|
|
||||||
const myurl = 'https://mattermost.com/download';
|
|
||||||
assert.equal(urlUtils.getHost(myurl), 'https://mattermost.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shoud raise an error on malformed urls', () => {
|
|
||||||
const myurl = 'http://example.com:-80/';
|
|
||||||
assert.throws(() => urlUtils.getHost(myurl), Error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
6664
package-lock.json
generated
6664
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -43,7 +43,7 @@
|
|||||||
"test:e2e:nobuild": "cross-env NODE_ENV=test npm-run-all test:e2e:build test:e2e:run",
|
"test:e2e:nobuild": "cross-env NODE_ENV=test npm-run-all test:e2e:build test:e2e:run",
|
||||||
"test:e2e:build": "webpack-cli --bail --config webpack.config.test.js",
|
"test:e2e:build": "webpack-cli --bail --config webpack.config.test.js",
|
||||||
"test:e2e:run": "electron-mocha -r @babel/register --reporter mocha-circleci-reporter dist/tests/e2e_bundle.js",
|
"test:e2e:run": "electron-mocha -r @babel/register --reporter mocha-circleci-reporter dist/tests/e2e_bundle.js",
|
||||||
"test:unit": "npm-run-all test:unit:build test:unit:run",
|
"test:unit": "jest",
|
||||||
"test:unit:build": "cross-env NODE_ENV=test webpack-cli --bail --config webpack.config.test.js",
|
"test:unit:build": "cross-env NODE_ENV=test webpack-cli --bail --config webpack.config.test.js",
|
||||||
"test:unit:run": "cross-env NODE_ENV=test mocha --reporter mocha-circleci-reporter dist/tests/test_bundle.js",
|
"test:unit:run": "cross-env NODE_ENV=test mocha --reporter mocha-circleci-reporter dist/tests/test_bundle.js",
|
||||||
"package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:mac-universal package:linux",
|
"package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:mac-universal package:linux",
|
||||||
@@ -59,6 +59,20 @@
|
|||||||
"check-build-config:run": "node -r @babel/register scripts/check_build_config.js",
|
"check-build-config:run": "node -r @babel/register scripts/check_build_config.js",
|
||||||
"check-types": "tsc"
|
"check-types": "tsc"
|
||||||
},
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleDirectories": [
|
||||||
|
"",
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"ts",
|
||||||
|
"tsx",
|
||||||
|
"js",
|
||||||
|
"jsx",
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"testMatch": ["**/src/**/*.test.js"]
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.14.5",
|
"@babel/cli": "^7.14.5",
|
||||||
"@babel/core": "^7.2.0",
|
"@babel/core": "^7.2.0",
|
||||||
@@ -108,6 +122,7 @@
|
|||||||
"eslint-plugin-react": "7.22.0",
|
"eslint-plugin-react": "7.22.0",
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
"image-webpack-loader": "5.0.0",
|
"image-webpack-loader": "5.0.0",
|
||||||
|
"jest": "^27.3.1",
|
||||||
"mdi-react": "^6.2.0",
|
"mdi-react": "^6.2.0",
|
||||||
"mini-css-extract-plugin": "1.6.0",
|
"mini-css-extract-plugin": "1.6.0",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
// Copyright (c) 2015-2016 Yuya Ochiai
|
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
import deepmerge from 'deepmerge';
|
|
||||||
|
|
||||||
export default function deepMergeProxy<T>(x: Partial<T>, y: Partial<T>, options: deepmerge.Options) {
|
|
||||||
return deepmerge(x, y, options); // due to webpack conversion
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) 2015-2016 Yuya Ochiai
|
|
||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import os from 'os';
|
|
||||||
const releaseSplit = os.release().split('.');
|
|
||||||
|
|
||||||
export default {
|
|
||||||
major: parseInt(releaseSplit[0], 10),
|
|
||||||
minor: parseInt(releaseSplit[1], 10),
|
|
||||||
isLowerThanOrEqualWindows8_1(): boolean {
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// consider Windows 7 and later.
|
|
||||||
return (this.major <= 6 && this.minor <= 3);
|
|
||||||
},
|
|
||||||
};
|
|
@@ -13,20 +13,4 @@ export class MattermostServer {
|
|||||||
throw new Error('Invalid url for creating a server');
|
throw new Error('Invalid url for creating a server');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getServerInfo = () => {
|
|
||||||
// does the server have a subpath?
|
|
||||||
const normalizedPath = this.url.pathname.toLowerCase();
|
|
||||||
const subpath = normalizedPath.endsWith('/') ? normalizedPath : `${normalizedPath}/`;
|
|
||||||
return {origin: this.url.origin, subpath, url: this.url.toString()};
|
|
||||||
}
|
|
||||||
|
|
||||||
sameOrigin = (otherURL: string) => {
|
|
||||||
const parsedUrl = urlUtils.parseURL(otherURL);
|
|
||||||
return parsedUrl && this.url.origin === parsedUrl.origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
equals = (otherServer: MattermostServer) => {
|
|
||||||
return (this.name === otherServer.name) && (this.url.toString() === otherServer.url.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,31 @@ export const DEFAULT_WINDOW_HEIGHT = 700;
|
|||||||
export const MINIMUM_WINDOW_WIDTH = 700;
|
export const MINIMUM_WINDOW_WIDTH = 700;
|
||||||
export const MINIMUM_WINDOW_HEIGHT = 240;
|
export const MINIMUM_WINDOW_HEIGHT = 240;
|
||||||
|
|
||||||
|
// supported custom login paths (oath, saml)
|
||||||
|
export const customLoginRegexPaths = [
|
||||||
|
/^\/oauth\/authorize$/i,
|
||||||
|
/^\/oauth\/deauthorize$/i,
|
||||||
|
/^\/oauth\/access_token$/i,
|
||||||
|
/^\/oauth\/[A-Za-z0-9]+\/complete$/i,
|
||||||
|
/^\/oauth\/[A-Za-z0-9]+\/login$/i,
|
||||||
|
/^\/oauth\/[A-Za-z0-9]+\/signup$/i,
|
||||||
|
/^\/api\/v3\/oauth\/[A-Za-z0-9]+\/complete$/i,
|
||||||
|
/^\/signup\/[A-Za-z0-9]+\/complete$/i,
|
||||||
|
/^\/login\/[A-Za-z0-9]+\/complete$/i,
|
||||||
|
/^\/login\/sso\/saml$/i,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const nonTeamUrlPaths = [
|
||||||
|
'plugins',
|
||||||
|
'signup',
|
||||||
|
'login',
|
||||||
|
'admin',
|
||||||
|
'channel',
|
||||||
|
'post',
|
||||||
|
'oauth',
|
||||||
|
'admin_console',
|
||||||
|
];
|
||||||
|
|
||||||
export const localeTranslations: Record<string, string> = {
|
export const localeTranslations: Record<string, string> = {
|
||||||
'af': 'Afrikaans',
|
'af': 'Afrikaans',
|
||||||
'af-ZA': 'Afrikaans (South Africa)',
|
'af-ZA': 'Afrikaans (South Africa)',
|
||||||
|
@@ -2,17 +2,415 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import assert from 'assert';
|
import urlUtils, {getFormattedPathName, isUrlType, equalUrlsIgnoringSubpath, equalUrlsWithSubpath} from 'common/utils/url';
|
||||||
|
|
||||||
import urlUtils from 'common/utils/url';
|
jest.mock('common/tabs/TabView', () => ({
|
||||||
|
getServerView: (srv, tab) => {
|
||||||
|
return {
|
||||||
|
name: `${srv.name}_${tab.name}`,
|
||||||
|
url: `${srv.url}${srv.url.toString().endsWith('/') ? '' : '/'}${tab.name.split('-')[1] || ''}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('common/utils/url', () => {
|
||||||
|
describe('isValidURL', () => {
|
||||||
|
it('should be true for a valid web url', () => {
|
||||||
|
const testURL = 'https://developers.mattermost.com/';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a valid, non-https web url', () => {
|
||||||
|
const testURL = 'http://developers.mattermost.com/';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for an invalid, self-defined, top-level domain', () => {
|
||||||
|
const testURL = 'https://www.example.x';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a file download url', () => {
|
||||||
|
const testURL = 'https://community.mattermost.com/api/v4/files/ka3xbfmb3ffnmgdmww8otkidfw?download=1';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a permalink url', () => {
|
||||||
|
const testURL = 'https://community.mattermost.com/test-channel/pl/pdqowkij47rmbyk78m5hwc7r6r';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a valid, internal domain', () => {
|
||||||
|
const testURL = 'https://mattermost.company-internal';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a second, valid internal domain', () => {
|
||||||
|
const testURL = 'https://serverXY/mattermost';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a valid, non-https internal domain', () => {
|
||||||
|
const testURL = 'http://mattermost.local';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be true for a valid, non-https, ip address with port number', () => {
|
||||||
|
const testURL = 'http://localhost:8065';
|
||||||
|
expect(urlUtils.isValidURL(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('isValidURI', () => {
|
||||||
|
it('should be true for a deeplink url', () => {
|
||||||
|
const testURL = 'mattermost://community-release.mattermost.com/core/channels/developers';
|
||||||
|
expect(urlUtils.isValidURI(testURL)).toBe(true);
|
||||||
|
});
|
||||||
|
it('should be false for a malicious url', () => {
|
||||||
|
const testURL = String.raw`mattermost:///" --data-dir "\\deans-mbp\mattermost`;
|
||||||
|
expect(urlUtils.isValidURI(testURL)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getHost', () => {
|
||||||
|
it('should return the origin of a well formed url', () => {
|
||||||
|
const myurl = 'https://mattermost.com/download';
|
||||||
|
expect(urlUtils.getHost(myurl)).toBe('https://mattermost.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shoud raise an error on malformed urls', () => {
|
||||||
|
const myurl = 'http://example.com:-80/';
|
||||||
|
expect(() => {
|
||||||
|
urlUtils.getHost(myurl);
|
||||||
|
}).toThrow(SyntaxError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('URL', () => {
|
|
||||||
describe('parseURL', () => {
|
describe('parseURL', () => {
|
||||||
|
it('should return the URL if it is already a URL', () => {
|
||||||
|
const url = new URL('http://mattermost.com');
|
||||||
|
expect(urlUtils.parseURL(url)).toBe(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when a bad url is passed', () => {
|
||||||
|
const badURL = 'not-a-real-url-at-all';
|
||||||
|
expect(urlUtils.parseURL(badURL)).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it('should remove duplicate slashes in a URL when parsing', () => {
|
it('should remove duplicate slashes in a URL when parsing', () => {
|
||||||
const urlWithExtraSlashes = 'https://mattermost.com//sub//path//example';
|
const urlWithExtraSlashes = 'https://mattermost.com//sub//path//example';
|
||||||
const parsedURL = urlUtils.parseURL(urlWithExtraSlashes);
|
const parsedURL = urlUtils.parseURL(urlWithExtraSlashes);
|
||||||
|
|
||||||
assert.strictEqual(parsedURL.toString(), 'https://mattermost.com/sub/path/example');
|
expect(parsedURL.toString()).toBe('https://mattermost.com/sub/path/example');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isInternalURL', () => {
|
||||||
|
it('should return false on different hosts', () => {
|
||||||
|
const baseURL = new URL('http://mattermost.com');
|
||||||
|
const externalURL = new URL('http://google.com');
|
||||||
|
|
||||||
|
expect(urlUtils.isInternalURL(externalURL, baseURL)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false on different ports', () => {
|
||||||
|
const baseURL = new URL('http://mattermost.com:8080');
|
||||||
|
const externalURL = new URL('http://mattermost.com:9001');
|
||||||
|
|
||||||
|
expect(urlUtils.isInternalURL(externalURL, baseURL)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false on different subpaths', () => {
|
||||||
|
const baseURL = new URL('http://mattermost.com/sub/path/');
|
||||||
|
const externalURL = new URL('http://mattermost.com/different/sub/path');
|
||||||
|
|
||||||
|
expect(urlUtils.isInternalURL(externalURL, baseURL)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if matching', () => {
|
||||||
|
const baseURL = new URL('http://mattermost.com/');
|
||||||
|
const externalURL = new URL('http://mattermost.com');
|
||||||
|
|
||||||
|
expect(urlUtils.isInternalURL(externalURL, baseURL)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if matching with subpath', () => {
|
||||||
|
const baseURL = new URL('http://mattermost.com/sub/path/');
|
||||||
|
const externalURL = new URL('http://mattermost.com/sub/path');
|
||||||
|
|
||||||
|
expect(urlUtils.isInternalURL(externalURL, baseURL)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if subpath of', () => {
|
||||||
|
const baseURL = new URL('http://mattermost.com/');
|
||||||
|
const externalURL = new URL('http://mattermost.com/sub/path');
|
||||||
|
|
||||||
|
expect(urlUtils.isInternalURL(externalURL, baseURL)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getFormattedPathName', () => {
|
||||||
|
it('should format all to lower case', () => {
|
||||||
|
const unformattedPathName = '/aAbBbB/cC/DdeR/';
|
||||||
|
expect(getFormattedPathName(unformattedPathName)).toBe('/aabbbb/cc/dder/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add trailing slash', () => {
|
||||||
|
const unformattedPathName = '/aAbBbB/cC/DdeR';
|
||||||
|
expect(getFormattedPathName(unformattedPathName)).toBe('/aabbbb/cc/dder/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isUrlType', () => {
|
||||||
|
const serverURL = new URL('http://mattermost.com');
|
||||||
|
const urlType = 'url-type';
|
||||||
|
|
||||||
|
it('should identify base url', () => {
|
||||||
|
const adminURL = new URL(`http://mattermost.com/${urlType}`);
|
||||||
|
expect(isUrlType('url-type', serverURL, adminURL)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should identify url of correct type', () => {
|
||||||
|
const adminURL = new URL(`http://mattermost.com/${urlType}/some/path`);
|
||||||
|
expect(isUrlType('url-type', serverURL, adminURL)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not identify other url', () => {
|
||||||
|
const adminURL = new URL('http://mattermost.com/some/other/path');
|
||||||
|
expect(isUrlType('url-type', serverURL, adminURL)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getView', () => {
|
||||||
|
const servers = [
|
||||||
|
{
|
||||||
|
name: 'server-1',
|
||||||
|
url: 'http://server-1.com',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-type1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-type2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'server-2',
|
||||||
|
url: 'http://server-2.com/subpath',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab-type1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-type2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should match the correct server - base URL', () => {
|
||||||
|
const inputURL = new URL('http://server-1.com');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toStrictEqual({name: 'server-1_tab', url: 'http://server-1.com/'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the correct server - base tab', () => {
|
||||||
|
const inputURL = new URL('http://server-1.com/team');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toStrictEqual({name: 'server-1_tab', url: 'http://server-1.com/'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the correct server - different tab', () => {
|
||||||
|
const inputURL = new URL('http://server-1.com/type1/app');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toStrictEqual({name: 'server-1_tab-type1', url: 'http://server-1.com/type1'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for server with subpath and URL without', () => {
|
||||||
|
const inputURL = new URL('http://server-2.com');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for server with subpath and URL with wrong subpath', () => {
|
||||||
|
const inputURL = new URL('http://server-2.com/different/subpath');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the correct server with a subpath - base URL', () => {
|
||||||
|
const inputURL = new URL('http://server-2.com/subpath');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toStrictEqual({name: 'server-2_tab', url: 'http://server-2.com/subpath/'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the correct server with a subpath - base tab', () => {
|
||||||
|
const inputURL = new URL('http://server-2.com/subpath/team');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toStrictEqual({name: 'server-2_tab', url: 'http://server-2.com/subpath/'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the correct server with a subpath - different tab', () => {
|
||||||
|
const inputURL = new URL('http://server-2.com/subpath/type2/team');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toStrictEqual({name: 'server-2_tab-type2', url: 'http://server-2.com/subpath/type2'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for wrong server', () => {
|
||||||
|
const inputURL = new URL('http://server-3.com');
|
||||||
|
expect(urlUtils.getView(inputURL, servers)).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('equalUrls', () => {
|
||||||
|
it('base urls', () => {
|
||||||
|
const url1 = new URL('http://server-1.com');
|
||||||
|
const url2 = new URL('http://server-1.com');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2)).toBe(true);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('different urls', () => {
|
||||||
|
const url1 = new URL('http://server-1.com');
|
||||||
|
const url2 = new URL('http://server-2.com');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2)).toBe(false);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('same host, different subpath', () => {
|
||||||
|
const url1 = new URL('http://server-1.com/subpath');
|
||||||
|
const url2 = new URL('http://server-1.com');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2)).toBe(true);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('same host and subpath', () => {
|
||||||
|
const url1 = new URL('http://server-1.com/subpath');
|
||||||
|
const url2 = new URL('http://server-1.com/subpath');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2)).toBe(true);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('same host, different URL scheme', () => {
|
||||||
|
const url1 = new URL('http://server-1.com');
|
||||||
|
const url2 = new URL('mattermost://server-1.com');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2)).toBe(false);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('same host, different URL scheme, with ignore scheme', () => {
|
||||||
|
const url1 = new URL('http://server-1.com');
|
||||||
|
const url2 = new URL('mattermost://server-1.com');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2, true)).toBe(true);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2, true)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('same host, different ports', () => {
|
||||||
|
const url1 = new URL('http://server-1.com:8080');
|
||||||
|
const url2 = new URL('http://server-1.com');
|
||||||
|
expect(equalUrlsIgnoringSubpath(url1, url2, true)).toBe(false);
|
||||||
|
expect(equalUrlsWithSubpath(url1, url2, true)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isCustomLoginURL', () => {
|
||||||
|
it('should match correct URL', () => {
|
||||||
|
expect(urlUtils.isCustomLoginURL(
|
||||||
|
'http://server.com/oauth/authorize',
|
||||||
|
{
|
||||||
|
url: 'http://server.com',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
url: 'http://server.com',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])).toBe(true);
|
||||||
|
});
|
||||||
|
it('should not match incorrect URL', () => {
|
||||||
|
expect(urlUtils.isCustomLoginURL(
|
||||||
|
'http://server.com/oauth/notauthorize',
|
||||||
|
{
|
||||||
|
url: 'http://server.com',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
url: 'http://server.com',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])).toBe(false);
|
||||||
|
});
|
||||||
|
it('should not match base URL', () => {
|
||||||
|
expect(urlUtils.isCustomLoginURL(
|
||||||
|
'http://server.com/',
|
||||||
|
{
|
||||||
|
url: 'http://server.com',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
url: 'http://server.com',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])).toBe(false);
|
||||||
|
});
|
||||||
|
it('should match with subpath', () => {
|
||||||
|
expect(urlUtils.isCustomLoginURL(
|
||||||
|
'http://server.com/subpath/oauth/authorize',
|
||||||
|
{
|
||||||
|
url: 'http://server.com/subpath',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
url: 'http://server.com/subpath',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])).toBe(true);
|
||||||
|
});
|
||||||
|
it('should not match with different subpath', () => {
|
||||||
|
expect(urlUtils.isCustomLoginURL(
|
||||||
|
'http://server.com/subpath/oauth/authorize',
|
||||||
|
{
|
||||||
|
url: 'http://server.com/different/subpath',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
url: 'http://server.com/different/subpath',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])).toBe(false);
|
||||||
|
});
|
||||||
|
it('should not match with oauth subpath', () => {
|
||||||
|
expect(urlUtils.isCustomLoginURL(
|
||||||
|
'http://server.com/oauth/authorize',
|
||||||
|
{
|
||||||
|
url: 'http://server.com/oauth/authorize',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'a',
|
||||||
|
url: 'http://server.com/oauth/authorize',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -6,28 +6,10 @@ import {isHttpsUri, isHttpUri, isUri} from 'valid-url';
|
|||||||
import {TeamWithTabs} from 'types/config';
|
import {TeamWithTabs} from 'types/config';
|
||||||
import {ServerFromURL} from 'types/utils';
|
import {ServerFromURL} from 'types/utils';
|
||||||
|
|
||||||
import buildConfig from '../config/buildConfig';
|
import buildConfig from 'common/config/buildConfig';
|
||||||
import {MattermostServer} from '../servers/MattermostServer';
|
import {MattermostServer} from 'common/servers/MattermostServer';
|
||||||
import {getServerView} from '../tabs/TabView';
|
import {getServerView} from 'common/tabs/TabView';
|
||||||
|
import {customLoginRegexPaths, nonTeamUrlPaths} from 'common/utils/constants';
|
||||||
// supported custom login paths (oath, saml)
|
|
||||||
const customLoginRegexPaths = [
|
|
||||||
/^\/oauth\/authorize$/i,
|
|
||||||
/^\/oauth\/deauthorize$/i,
|
|
||||||
/^\/oauth\/access_token$/i,
|
|
||||||
/^\/oauth\/[A-Za-z0-9]+\/complete$/i,
|
|
||||||
/^\/oauth\/[A-Za-z0-9]+\/login$/i,
|
|
||||||
/^\/oauth\/[A-Za-z0-9]+\/signup$/i,
|
|
||||||
/^\/api\/v3\/oauth\/[A-Za-z0-9]+\/complete$/i,
|
|
||||||
/^\/signup\/[A-Za-z0-9]+\/complete$/i,
|
|
||||||
/^\/login\/[A-Za-z0-9]+\/complete$/i,
|
|
||||||
/^\/login\/sso\/saml$/i,
|
|
||||||
];
|
|
||||||
|
|
||||||
function getDomain(inputURL: URL | string) {
|
|
||||||
const parsedURL = parseURL(inputURL);
|
|
||||||
return parsedURL?.origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidURL(testURL: string) {
|
function isValidURL(testURL: string) {
|
||||||
return Boolean(isHttpUri(testURL) || isHttpsUri(testURL)) && Boolean(parseURL(testURL));
|
return Boolean(isHttpUri(testURL) || isHttpsUri(testURL)) && Boolean(parseURL(testURL));
|
||||||
@@ -53,18 +35,17 @@ function getHost(inputURL: URL | string) {
|
|||||||
if (parsedURL) {
|
if (parsedURL) {
|
||||||
return parsedURL.origin;
|
return parsedURL.origin;
|
||||||
}
|
}
|
||||||
throw new Error(`Couldn't parse url: ${inputURL}`);
|
throw new SyntaxError(`Couldn't parse url: ${inputURL}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInternalURL determines if the target url is internal to the application.
|
// isInternalURL determines if the target url is internal to the application.
|
||||||
// - currentURL is the current url inside the webview
|
// - currentURL is the current url inside the webview
|
||||||
// - basename is the global export from the Mattermost application defining the subpath, if any
|
function isInternalURL(targetURL: URL, currentURL: URL) {
|
||||||
function isInternalURL(targetURL: URL, currentURL: URL, basename = '/') {
|
|
||||||
if (targetURL.host !== currentURL.host) {
|
if (targetURL.host !== currentURL.host) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(targetURL.pathname || '/').startsWith(basename)) {
|
if (!equalUrlsWithSubpath(targetURL, currentURL) && !(targetURL.pathname || '/').startsWith(currentURL.pathname)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +65,7 @@ function getServerInfo(serverUrl: URL | string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFormattedPathName(pn: string) {
|
export function getFormattedPathName(pn: string) {
|
||||||
return pn.endsWith('/') ? pn.toLowerCase() : `${pn}/`;
|
return pn.endsWith('/') ? pn.toLowerCase() : `${pn.toLowerCase()}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getManagedResources() {
|
function getManagedResources() {
|
||||||
@@ -95,72 +76,46 @@ function getManagedResources() {
|
|||||||
return buildConfig.managedResources || [];
|
return buildConfig.managedResources || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAdminUrl(serverUrl: URL | string, inputUrl: URL | string) {
|
export function isUrlType(urlType: string, serverUrl: URL | string, inputURL: URL | string) {
|
||||||
const parsedURL = parseURL(inputUrl);
|
if (!serverUrl || !inputURL) {
|
||||||
const server = getServerInfo(serverUrl);
|
|
||||||
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server.url, parsedURL))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}/admin_console/`) ||
|
|
||||||
parsedURL.pathname.toLowerCase().startsWith('/admin_console/'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTeamUrl(serverUrl: URL | string, inputUrl: URL | string, withApi?: boolean) {
|
|
||||||
if (!serverUrl || !inputUrl) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const parsedURL = parseURL(inputUrl);
|
|
||||||
|
const parsedURL = parseURL(inputURL);
|
||||||
const server = getServerInfo(serverUrl);
|
const server = getServerInfo(serverUrl);
|
||||||
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server.url, parsedURL))) {
|
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server.url, parsedURL))) {
|
||||||
return null;
|
return false;
|
||||||
|
}
|
||||||
|
return (getFormattedPathName(parsedURL.pathname).startsWith(`${server.subpath}${urlType}/`) ||
|
||||||
|
getFormattedPathName(parsedURL.pathname).startsWith(`/${urlType}/`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAdminUrl(serverUrl: URL | string, inputURL: URL | string) {
|
||||||
|
return isUrlType('admin_console', serverUrl, inputURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTeamUrl(serverUrl: URL | string, inputURL: URL | string, withApi?: boolean) {
|
||||||
|
const parsedURL = parseURL(inputURL);
|
||||||
|
const server = getServerInfo(serverUrl);
|
||||||
|
if (!parsedURL || !server || (!equalUrlsIgnoringSubpath(server.url, parsedURL))) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pre process nonTeamUrlPaths
|
const paths = [...getManagedResources(), ...nonTeamUrlPaths];
|
||||||
let nonTeamUrlPaths = [
|
|
||||||
'plugins',
|
|
||||||
'signup',
|
|
||||||
'login',
|
|
||||||
'admin',
|
|
||||||
'channel',
|
|
||||||
'post',
|
|
||||||
'oauth',
|
|
||||||
'admin_console',
|
|
||||||
];
|
|
||||||
const managedResources = getManagedResources();
|
|
||||||
nonTeamUrlPaths = nonTeamUrlPaths.concat(managedResources);
|
|
||||||
|
|
||||||
if (withApi) {
|
if (withApi) {
|
||||||
nonTeamUrlPaths.push('api');
|
paths.push('api');
|
||||||
}
|
}
|
||||||
return !(nonTeamUrlPaths.some((testPath) => (
|
return !(paths.some((testPath) => isUrlType(testPath, serverUrl, inputURL)));
|
||||||
parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${testPath}/`) ||
|
|
||||||
parsedURL.pathname.toLowerCase().startsWith(`/${testPath}/`))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPluginUrl(serverUrl: URL | string, inputURL: URL | string) {
|
function isPluginUrl(serverUrl: URL | string, inputURL: URL | string) {
|
||||||
const server = getServerInfo(serverUrl);
|
return isUrlType('plugins', serverUrl, inputURL);
|
||||||
const parsedURL = parseURL(inputURL);
|
|
||||||
if (!parsedURL || !server) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
equalUrlsIgnoringSubpath(server.url, parsedURL) &&
|
|
||||||
(parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}plugins/`) ||
|
|
||||||
parsedURL.pathname.toLowerCase().startsWith('/plugins/')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isManagedResource(serverUrl: URL | string, inputURL: URL | string) {
|
function isManagedResource(serverUrl: URL | string, inputURL: URL | string) {
|
||||||
const server = getServerInfo(serverUrl);
|
const paths = [...getManagedResources()];
|
||||||
const parsedURL = parseURL(inputURL);
|
return paths.some((testPath) => isUrlType(testPath, serverUrl, inputURL));
|
||||||
if (!parsedURL || !server) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const managedResources = getManagedResources();
|
|
||||||
|
|
||||||
return (
|
|
||||||
equalUrlsIgnoringSubpath(server.url, parsedURL) && managedResources && managedResources.length &&
|
|
||||||
managedResources.some((managedResource) => (parsedURL.pathname.toLowerCase().startsWith(`${server.subpath}${managedResource}/`) || parsedURL.pathname.toLowerCase().startsWith(`/${managedResource}/`))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getView(inputURL: URL | string, teams: TeamWithTabs[], ignoreScheme = false): ServerFromURL | undefined {
|
function getView(inputURL: URL | string, teams: TeamWithTabs[], ignoreScheme = false): ServerFromURL | undefined {
|
||||||
@@ -172,20 +127,27 @@ function getView(inputURL: URL | string, teams: TeamWithTabs[], ignoreScheme = f
|
|||||||
let secondOption;
|
let secondOption;
|
||||||
teams.forEach((team) => {
|
teams.forEach((team) => {
|
||||||
const srv = new MattermostServer(team.name, team.url);
|
const srv = new MattermostServer(team.name, team.url);
|
||||||
team.tabs.forEach((tab) => {
|
|
||||||
|
// sort by length so that we match the highest specificity last
|
||||||
|
const filteredTabs = team.tabs.map((tab) => {
|
||||||
const tabView = getServerView(srv, tab);
|
const tabView = getServerView(srv, tab);
|
||||||
const parsedServerUrl = parseURL(tabView.url);
|
const parsedServerUrl = parseURL(tabView.url);
|
||||||
if (parsedServerUrl) {
|
return {tabView, parsedServerUrl};
|
||||||
|
});
|
||||||
|
|
||||||
|
filteredTabs.sort((a, b) => a.tabView.url.toString().length - b.tabView.url.toString().length);
|
||||||
|
filteredTabs.forEach((tab) => {
|
||||||
|
if (tab.parsedServerUrl) {
|
||||||
// check server and subpath matches (without subpath pathname is \ so it always matches)
|
// check server and subpath matches (without subpath pathname is \ so it always matches)
|
||||||
if (equalUrlsWithSubpath(parsedServerUrl, parsedURL, ignoreScheme)) {
|
if (getFormattedPathName(tab.parsedServerUrl.pathname) !== '/' && equalUrlsWithSubpath(tab.parsedServerUrl, parsedURL, ignoreScheme)) {
|
||||||
firstOption = {name: tabView.name, url: parsedServerUrl.toString()};
|
firstOption = {name: tab.tabView.name, url: tab.parsedServerUrl.toString()};
|
||||||
}
|
}
|
||||||
if (equalUrlsIgnoringSubpath(parsedServerUrl, parsedURL, ignoreScheme)) {
|
if (getFormattedPathName(tab.parsedServerUrl.pathname) === '/' && equalUrlsIgnoringSubpath(tab.parsedServerUrl, parsedURL, ignoreScheme)) {
|
||||||
// in case the user added something on the path that doesn't really belong to the server
|
// in case the user added something on the path that doesn't really belong to the server
|
||||||
// there might be more than one that matches, but we can't differentiate, so last one
|
// there might be more than one that matches, but we can't differentiate, so last one
|
||||||
// is as good as any other in case there is no better match (e.g.: two subpath servers with the same origin)
|
// is as good as any other in case there is no better match (e.g.: two subpath servers with the same origin)
|
||||||
// e.g.: https://community.mattermost.com/core
|
// e.g.: https://community.mattermost.com/core
|
||||||
secondOption = {name: tabView.name, url: parsedServerUrl.toString()};
|
secondOption = {name: tab.tabView.name, url: tab.parsedServerUrl.toString()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -194,14 +156,14 @@ function getView(inputURL: URL | string, teams: TeamWithTabs[], ignoreScheme = f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// next two functions are defined to clarify intent
|
// next two functions are defined to clarify intent
|
||||||
function equalUrlsWithSubpath(url1: URL, url2: URL, ignoreScheme?: boolean) {
|
export function equalUrlsWithSubpath(url1: URL, url2: URL, ignoreScheme?: boolean) {
|
||||||
if (ignoreScheme) {
|
if (ignoreScheme) {
|
||||||
return url1.host === url2.host && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase());
|
return url1.host === url2.host && getFormattedPathName(url2.pathname).startsWith(getFormattedPathName(url1.pathname));
|
||||||
}
|
}
|
||||||
return url1.origin === url2.origin && url2.pathname.toLowerCase().startsWith(url1.pathname.toLowerCase());
|
return url1.origin === url2.origin && getFormattedPathName(url2.pathname).startsWith(getFormattedPathName(url1.pathname));
|
||||||
}
|
}
|
||||||
|
|
||||||
function equalUrlsIgnoringSubpath(url1: URL, url2: URL, ignoreScheme?: boolean) {
|
export function equalUrlsIgnoringSubpath(url1: URL, url2: URL, ignoreScheme?: boolean) {
|
||||||
if (ignoreScheme) {
|
if (ignoreScheme) {
|
||||||
return url1.host.toLowerCase() === url2.host.toLowerCase();
|
return url1.host.toLowerCase() === url2.host.toLowerCase();
|
||||||
}
|
}
|
||||||
@@ -227,27 +189,18 @@ function isCustomLoginURL(url: URL | string, server: ServerFromURL, teams: TeamW
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const urlPath = parsedURL.pathname;
|
const urlPath = parsedURL.pathname;
|
||||||
if (subpath !== '' && subpath !== '/' && urlPath.startsWith(subpath)) {
|
const replacement = subpath.endsWith('/') ? '/' : '';
|
||||||
const replacement = subpath.endsWith('/') ? '/' : '';
|
const replacedPath = urlPath.replace(subpath, replacement);
|
||||||
const replacedPath = urlPath.replace(subpath, replacement);
|
|
||||||
for (const regexPath of customLoginRegexPaths) {
|
|
||||||
if (replacedPath.match(regexPath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is no subpath, or we are adding the team and got redirected to the real server it'll be caught here
|
|
||||||
for (const regexPath of customLoginRegexPaths) {
|
for (const regexPath of customLoginRegexPaths) {
|
||||||
if (urlPath.match(regexPath)) {
|
if (replacedPath.match(regexPath)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getDomain,
|
|
||||||
isValidURL,
|
isValidURL,
|
||||||
isValidURI,
|
isValidURI,
|
||||||
isInternalURL,
|
isInternalURL,
|
||||||
|
91
src/common/utils/util.test.js
Normal file
91
src/common/utils/util.test.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import Utils from 'common/utils/util';
|
||||||
|
|
||||||
|
describe('common/utils/util', () => {
|
||||||
|
describe('shorten', () => {
|
||||||
|
it('should shorten based on max', () => {
|
||||||
|
const string = '123456789012345678901234567890';
|
||||||
|
expect(Utils.shorten(string, 10)).toBe('1234567...');
|
||||||
|
});
|
||||||
|
it('should use DEFAULT_MAX for max < 4', () => {
|
||||||
|
const string = '123456789012345678901234567890';
|
||||||
|
expect(Utils.shorten(string, 3)).toBe('12345678901234567...');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isVersionGreaterThanOrEqualTo', () => {
|
||||||
|
test('should consider two empty versions as equal', () => {
|
||||||
|
const a = '';
|
||||||
|
const b = '';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider different strings without components as equal', () => {
|
||||||
|
const a = 'not a server version';
|
||||||
|
const b = 'also not a server version';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider different malformed versions normally (not greater than case)', () => {
|
||||||
|
const a = '1.2.3';
|
||||||
|
const b = '1.2.4';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider different malformed versions normally (greater than case)', () => {
|
||||||
|
const a = '1.2.4';
|
||||||
|
const b = '1.2.3';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should work correctly for different numbers of digits', () => {
|
||||||
|
const a = '10.0.1';
|
||||||
|
const b = '4.8.0';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider an empty version as not greater than or equal', () => {
|
||||||
|
const a = '';
|
||||||
|
const b = '4.7.1.dev.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider the same versions equal', () => {
|
||||||
|
const a = '4.7.1.dev.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
const b = '4.7.1.dev.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider different release versions (not greater than case)', () => {
|
||||||
|
const a = '4.7.0.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
const b = '4.7.1.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider different release versions (greater than case)', () => {
|
||||||
|
const a = '4.7.1.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
const b = '4.7.0.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should consider different build numbers unequal', () => {
|
||||||
|
const a = '4.7.1.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
const b = '4.7.1.13.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore different config hashes', () => {
|
||||||
|
const a = '4.7.1.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
const b = '4.7.1.12.c51676437bc02ada78f3a0a0a2203c61.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore different licensed statuses', () => {
|
||||||
|
const a = '4.7.1.13.c51676437bc02ada78f3a0a0a2203c60.false';
|
||||||
|
const b = '4.7.1.12.c51676437bc02ada78f3a0a0a2203c60.true';
|
||||||
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,8 +1,7 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import assert from 'assert';
|
|
||||||
|
|
||||||
import UserActivityMonitor from '../../../src/main/UserActivityMonitor';
|
import UserActivityMonitor from './UserActivityMonitor';
|
||||||
|
|
||||||
describe('UserActivityMonitor', () => {
|
describe('UserActivityMonitor', () => {
|
||||||
describe('updateIdleTime', () => {
|
describe('updateIdleTime', () => {
|
||||||
@@ -10,7 +9,7 @@ describe('UserActivityMonitor', () => {
|
|||||||
const userActivityMonitor = new UserActivityMonitor();
|
const userActivityMonitor = new UserActivityMonitor();
|
||||||
const idleTime = Math.round(Date.now() / 1000);
|
const idleTime = Math.round(Date.now() / 1000);
|
||||||
userActivityMonitor.updateIdleTime(idleTime);
|
userActivityMonitor.updateIdleTime(idleTime);
|
||||||
assert.equal(userActivityMonitor.userIdleTime, idleTime);
|
expect(userActivityMonitor.userIdleTime).toBe(idleTime);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,11 +22,11 @@ describe('UserActivityMonitor', () => {
|
|||||||
|
|
||||||
it('should set user status to active', () => {
|
it('should set user status to active', () => {
|
||||||
userActivityMonitor.setActivityState(true);
|
userActivityMonitor.setActivityState(true);
|
||||||
assert.equal(userActivityMonitor.userIsActive, true);
|
expect(userActivityMonitor.userIsActive).toBe(true);
|
||||||
});
|
});
|
||||||
it('should set user status to inactive', () => {
|
it('should set user status to inactive', () => {
|
||||||
userActivityMonitor.setActivityState(false);
|
userActivityMonitor.setActivityState(false);
|
||||||
assert.equal(userActivityMonitor.userIsActive, false);
|
expect(userActivityMonitor.userIsActive).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,28 +39,28 @@ describe('UserActivityMonitor', () => {
|
|||||||
|
|
||||||
it('should emit a non-system triggered status event indicating a user is active', () => {
|
it('should emit a non-system triggered status event indicating a user is active', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(userIsActive && !isSystemEvent, true);
|
expect(userIsActive && !isSystemEvent).toBe(true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.setActivityState(true, false);
|
userActivityMonitor.setActivityState(true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a non-system triggered status event indicating a user is inactive', () => {
|
it('should emit a non-system triggered status event indicating a user is inactive', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(!userIsActive && !isSystemEvent, true);
|
expect(!userIsActive && !isSystemEvent).toBe(true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.setActivityState(false, false);
|
userActivityMonitor.setActivityState(false, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a system triggered status event indicating a user is active', () => {
|
it('should emit a system triggered status event indicating a user is active', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(userIsActive && isSystemEvent, true);
|
expect(userIsActive && isSystemEvent).toBe(true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.setActivityState(true, true);
|
userActivityMonitor.setActivityState(true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a system triggered status event indicating a user is inactive', () => {
|
it('should emit a system triggered status event indicating a user is inactive', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(!userIsActive && isSystemEvent, true);
|
expect(!userIsActive && isSystemEvent).toBe(true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.setActivityState(false, true);
|
userActivityMonitor.setActivityState(false, true);
|
||||||
});
|
});
|
@@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {app, Notification} from 'electron';
|
import {app, Notification} from 'electron';
|
||||||
|
|
||||||
import {MentionOptions} from 'types/notification';
|
import {MentionOptions} from 'types/notification';
|
||||||
|
|
||||||
import osVersion from 'common/osVersion';
|
import Utils from 'common/utils/util';
|
||||||
|
|
||||||
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
const assetsDir = path.resolve(app.getAppPath(), 'assets');
|
||||||
const appIconURL = path.resolve(assetsDir, 'appicon_48.png');
|
const appIconURL = path.resolve(assetsDir, 'appicon_48.png');
|
||||||
@@ -30,7 +32,7 @@ export class Mention extends Notification {
|
|||||||
// Notification Center shows app's icon, so there were two icons on the notification.
|
// Notification Center shows app's icon, so there were two icons on the notification.
|
||||||
Reflect.deleteProperty(options, 'icon');
|
Reflect.deleteProperty(options, 'icon');
|
||||||
}
|
}
|
||||||
const isWin7 = (process.platform === 'win32' && osVersion.isLowerThanOrEqualWindows8_1() && DEFAULT_WIN7);
|
const isWin7 = (process.platform === 'win32' && !Utils.isVersionGreaterThanOrEqualTo(os.version(), '6.3') && DEFAULT_WIN7);
|
||||||
const customSound = String(!options.silent && ((options.data && options.data.soundName !== 'None' && options.data.soundName) || isWin7));
|
const customSound = String(!options.silent && ((options.data && options.data.soundName !== 'None' && options.data.soundName) || isWin7));
|
||||||
if (customSound) {
|
if (customSound) {
|
||||||
options.silent = true;
|
options.silent = true;
|
||||||
|
@@ -2,10 +2,12 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import assert from 'assert';
|
import TrustedOriginsStore from 'main/trustedOrigins';
|
||||||
|
import {BASIC_AUTH_PERMISSION} from 'common/permissions';
|
||||||
|
|
||||||
import TrustedOriginsStore from 'main/trustedOrigins.ts';
|
jest.mock('electron-log', () => ({
|
||||||
import {BASIC_AUTH_PERMISSION} from 'common/permissions.ts';
|
error: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
function mockTOS(fileName, returnvalue) {
|
function mockTOS(fileName, returnvalue) {
|
||||||
const tos = new TrustedOriginsStore(fileName);
|
const tos = new TrustedOriginsStore(fileName);
|
||||||
@@ -20,24 +22,28 @@ describe('Trusted Origins', () => {
|
|||||||
it('should be empty if there is no file', () => {
|
it('should be empty if there is no file', () => {
|
||||||
const tos = mockTOS('emptyfile', null);
|
const tos = mockTOS('emptyfile', null);
|
||||||
tos.load();
|
tos.load();
|
||||||
assert.deepEqual(tos.data.size, 0);
|
expect(tos.data.size).toStrictEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if data isn\'t an object', () => {
|
it('should throw an error if data isn\'t an object', () => {
|
||||||
const tos = mockTOS('notobject', 'this is not my object!');
|
const tos = mockTOS('notobject', 'this is not my object!');
|
||||||
|
|
||||||
assert.throws(tos.load, SyntaxError);
|
expect(() => {
|
||||||
|
tos.load();
|
||||||
|
}).toThrow(SyntaxError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if data isn\'t in the expected format', () => {
|
it('should throw an error if data isn\'t in the expected format', () => {
|
||||||
const tos = mockTOS('badobject', '{"https://mattermost.com": "this is not my object!"}');
|
const tos = mockTOS('badobject', '{"https://mattermost.com": "this is not my object!"}');
|
||||||
assert.throws(tos.load, /^Error: Provided TrustedOrigins file does not validate, using defaults instead\.$/);
|
expect(() => {
|
||||||
|
tos.load();
|
||||||
|
}).toThrow(/^Provided TrustedOrigins file does not validate, using defaults instead\.$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should drop keys that aren\'t urls', () => {
|
it('should drop keys that aren\'t urls', () => {
|
||||||
const tos = mockTOS('badobject2', `{"this is not an uri": {"${BASIC_AUTH_PERMISSION}": true}}`);
|
const tos = mockTOS('badobject2', `{"this is not an uri": {"${BASIC_AUTH_PERMISSION}": true}}`);
|
||||||
tos.load();
|
tos.load();
|
||||||
assert.equal(typeof tos.data['this is not an uri'], 'undefined');
|
expect(typeof tos.data['this is not an uri']).toBe('undefined');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain valid data if everything goes right', () => {
|
it('should contain valid data if everything goes right', () => {
|
||||||
@@ -47,7 +53,7 @@ describe('Trusted Origins', () => {
|
|||||||
}};
|
}};
|
||||||
const tos = mockTOS('okfile', JSON.stringify(value));
|
const tos = mockTOS('okfile', JSON.stringify(value));
|
||||||
tos.load();
|
tos.load();
|
||||||
assert.deepEqual(Object.fromEntries(tos.data.entries()), value);
|
expect(Object.fromEntries(tos.data.entries())).toStrictEqual(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('validate testing permissions', () => {
|
describe('validate testing permissions', () => {
|
||||||
@@ -62,19 +68,19 @@ describe('Trusted Origins', () => {
|
|||||||
const tos = mockTOS('permission_test', JSON.stringify(value));
|
const tos = mockTOS('permission_test', JSON.stringify(value));
|
||||||
tos.load();
|
tos.load();
|
||||||
it('tos should contain 2 elements', () => {
|
it('tos should contain 2 elements', () => {
|
||||||
assert.equal(tos.data.size, 2);
|
expect(tos.data.size).toBe(2);
|
||||||
});
|
});
|
||||||
it('should say ok if the permission is set', () => {
|
it('should say ok if the permission is set', () => {
|
||||||
assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), true);
|
expect(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION)).toBe(true);
|
||||||
});
|
});
|
||||||
it('should say ko if the permission is set to false', () => {
|
it('should say ko if the permission is set to false', () => {
|
||||||
assert.equal(tos.checkPermission('https://notmattermost.com', BASIC_AUTH_PERMISSION), false);
|
expect(tos.checkPermission('https://notmattermost.com', BASIC_AUTH_PERMISSION)).toBe(false);
|
||||||
});
|
});
|
||||||
it('should say ko if the uri is not set', () => {
|
it('should say ko if the uri is not set', () => {
|
||||||
assert.equal(tos.checkPermission('https://undefined.com', BASIC_AUTH_PERMISSION), null);
|
expect(tos.checkPermission('https://undefined.com', BASIC_AUTH_PERMISSION)).toBe(undefined);
|
||||||
});
|
});
|
||||||
it('should say null if the permission is unknown', () => {
|
it('should say null if the permission is unknown', () => {
|
||||||
assert.equal(tos.checkPermission('https://mattermost.com'), null);
|
expect(tos.checkPermission('https://mattermost.com')).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,9 +96,9 @@ describe('Trusted Origins', () => {
|
|||||||
const tos = mockTOS('permission_test', JSON.stringify(value));
|
const tos = mockTOS('permission_test', JSON.stringify(value));
|
||||||
tos.load();
|
tos.load();
|
||||||
it('deleting revokes access', () => {
|
it('deleting revokes access', () => {
|
||||||
assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), true);
|
expect(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION)).toBe(true);
|
||||||
tos.delete('https://mattermost.com');
|
tos.delete('https://mattermost.com');
|
||||||
assert.equal(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION), null);
|
expect(tos.checkPermission('https://mattermost.com', BASIC_AUTH_PERMISSION)).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -304,7 +304,7 @@ export class MattermostView extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateTarget = (e: Event, url: string) => {
|
handleUpdateTarget = (e: Event, url: string) => {
|
||||||
if (!url || !this.tab.server.sameOrigin(url)) {
|
if (!url || !urlUtils.isInternalURL(new URL(url), this.tab.server.url)) {
|
||||||
this.emit(UPDATE_TARGET_URL, url);
|
this.emit(UPDATE_TARGET_URL, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user