Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
60e885b2f8
3 changed files with 232 additions and 16 deletions
|
@ -72,6 +72,7 @@
|
||||||
"gfm.css": "^1.1.1",
|
"gfm.css": "^1.1.1",
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.14",
|
||||||
"highlight.js": "^9.13.0",
|
"highlight.js": "^9.13.0",
|
||||||
|
"is-ip": "^2.0.0",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"linkifyjs": "^2.1.6",
|
"linkifyjs": "^2.1.6",
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
|
|
|
@ -15,6 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MatrixClientPeg from "./MatrixClientPeg";
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
|
import isIp from "is-ip";
|
||||||
|
import utils from 'matrix-js-sdk/lib/utils';
|
||||||
|
|
||||||
export const host = "matrix.to";
|
export const host = "matrix.to";
|
||||||
export const baseUrl = `https://${host}`;
|
export const baseUrl = `https://${host}`;
|
||||||
|
@ -90,7 +92,9 @@ export function pickServerCandidates(roomId) {
|
||||||
// Rationale for popular servers: It's hard to get rid of people when
|
// Rationale for popular servers: It's hard to get rid of people when
|
||||||
// they keep flocking in from a particular server. Sure, the server could
|
// they keep flocking in from a particular server. Sure, the server could
|
||||||
// be ACL'd in the future or for some reason be evicted from the room
|
// be ACL'd in the future or for some reason be evicted from the room
|
||||||
// however an event like that is unlikely the larger the room gets.
|
// however an event like that is unlikely the larger the room gets. If
|
||||||
|
// the server is ACL'd at the time of generating the link however, we
|
||||||
|
// shouldn't pick them. We also don't pick IP addresses.
|
||||||
|
|
||||||
// Note: we don't pick the server the room was created on because the
|
// Note: we don't pick the server the room was created on because the
|
||||||
// homeserver should already be using that server as a last ditch attempt
|
// homeserver should already be using that server as a last ditch attempt
|
||||||
|
@ -104,12 +108,29 @@ export function pickServerCandidates(roomId) {
|
||||||
// The receiving user can then manually append the known-good server to
|
// The receiving user can then manually append the known-good server to
|
||||||
// the list and magically have the link work.
|
// the list and magically have the link work.
|
||||||
|
|
||||||
|
const bannedHostsRegexps = [];
|
||||||
|
let allowedHostsRegexps = [new RegExp(".*")]; // default allow everyone
|
||||||
|
if (room.currentState) {
|
||||||
|
const aclEvent = room.currentState.getStateEvents("m.room.server_acl", "");
|
||||||
|
if (aclEvent && aclEvent.getContent()) {
|
||||||
|
const getRegex = (hostname) => new RegExp("^" + utils.globToRegexp(hostname, false) + "$");
|
||||||
|
|
||||||
|
const denied = aclEvent.getContent().deny || [];
|
||||||
|
denied.forEach(h => bannedHostsRegexps.push(getRegex(h)));
|
||||||
|
|
||||||
|
const allowed = aclEvent.getContent().allow || [];
|
||||||
|
allowedHostsRegexps = []; // we don't want to use the default rule here
|
||||||
|
allowed.forEach(h => allowedHostsRegexps.push(getRegex(h)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const populationMap: {[server:string]:number} = {};
|
const populationMap: {[server:string]:number} = {};
|
||||||
const highestPlUser = {userId: null, powerLevel: 0, serverName: null};
|
const highestPlUser = {userId: null, powerLevel: 0, serverName: null};
|
||||||
|
|
||||||
for (const member of room.getJoinedMembers()) {
|
for (const member of room.getJoinedMembers()) {
|
||||||
const serverName = member.userId.split(":").splice(1).join(":");
|
const serverName = member.userId.split(":").splice(1).join(":");
|
||||||
if (member.powerLevel > highestPlUser.powerLevel) {
|
if (member.powerLevel > highestPlUser.powerLevel && !isHostnameIpAddress(serverName)
|
||||||
|
&& !isHostInRegex(serverName, bannedHostsRegexps) && isHostInRegex(serverName, allowedHostsRegexps)) {
|
||||||
highestPlUser.userId = member.userId;
|
highestPlUser.userId = member.userId;
|
||||||
highestPlUser.powerLevel = member.powerLevel;
|
highestPlUser.powerLevel = member.powerLevel;
|
||||||
highestPlUser.serverName = serverName;
|
highestPlUser.serverName = serverName;
|
||||||
|
@ -125,8 +146,9 @@ export function pickServerCandidates(roomId) {
|
||||||
const beforePopulation = candidates.length;
|
const beforePopulation = candidates.length;
|
||||||
const serversByPopulation = Object.keys(populationMap)
|
const serversByPopulation = Object.keys(populationMap)
|
||||||
.sort((a, b) => populationMap[b] - populationMap[a])
|
.sort((a, b) => populationMap[b] - populationMap[a])
|
||||||
.filter(a => !candidates.includes(a));
|
.filter(a => !candidates.includes(a) && !isHostnameIpAddress(a)
|
||||||
for (let i = beforePopulation; i <= MAX_SERVER_CANDIDATES; i++) {
|
&& !isHostInRegex(a, bannedHostsRegexps) && isHostInRegex(a, allowedHostsRegexps));
|
||||||
|
for (let i = beforePopulation; i < MAX_SERVER_CANDIDATES; i++) {
|
||||||
const idx = i - beforePopulation;
|
const idx = i - beforePopulation;
|
||||||
if (idx >= serversByPopulation.length) break;
|
if (idx >= serversByPopulation.length) break;
|
||||||
candidates.push(serversByPopulation[idx]);
|
candidates.push(serversByPopulation[idx]);
|
||||||
|
@ -134,3 +156,34 @@ export function pickServerCandidates(roomId) {
|
||||||
|
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHostnameFromMatrixDomain(domain) {
|
||||||
|
if (!domain) return null;
|
||||||
|
|
||||||
|
// The hostname might have a port, so we convert it to a URL and
|
||||||
|
// split out the real hostname.
|
||||||
|
const parser = document.createElement('a');
|
||||||
|
parser.href = "https://" + domain;
|
||||||
|
return parser.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHostInRegex(hostname, regexps) {
|
||||||
|
hostname = getHostnameFromMatrixDomain(hostname);
|
||||||
|
if (!hostname) return true; // assumed
|
||||||
|
if (regexps.length > 0 && !regexps[0].test) throw new Error(regexps[0]);
|
||||||
|
|
||||||
|
return regexps.filter(h => h.test(hostname)).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHostnameIpAddress(hostname) {
|
||||||
|
hostname = getHostnameFromMatrixDomain(hostname);
|
||||||
|
if (!hostname) return false;
|
||||||
|
|
||||||
|
// is-ip doesn't want IPv6 addresses surrounded by brackets, so
|
||||||
|
// take them off.
|
||||||
|
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
||||||
|
hostname = hostname.substring(1, hostname.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isIp(hostname);
|
||||||
|
}
|
||||||
|
|
|
@ -150,7 +150,39 @@ describe('matrix-to', function() {
|
||||||
expect(pickedServers[2]).toBe("third");
|
expect(pickedServers[2]).toBe("third");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with IPv4 hostnames', function() {
|
it('should pick a maximum of 3 candidate servers', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:alpha",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:bravo",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:charlie",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:delta",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:echo",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not consider IPv4 hosts', function() {
|
||||||
peg.get().getRoom = () => {
|
peg.get().getRoom = () => {
|
||||||
return {
|
return {
|
||||||
getJoinedMembers: () => [
|
getJoinedMembers: () => [
|
||||||
|
@ -163,11 +195,10 @@ describe('matrix-to', function() {
|
||||||
};
|
};
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
expect(pickedServers).toExist();
|
expect(pickedServers).toExist();
|
||||||
expect(pickedServers.length).toBe(1);
|
expect(pickedServers.length).toBe(0);
|
||||||
expect(pickedServers[0]).toBe("127.0.0.1");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with IPv6 hostnames', function() {
|
it('should not consider IPv6 hosts', function() {
|
||||||
peg.get().getRoom = () => {
|
peg.get().getRoom = () => {
|
||||||
return {
|
return {
|
||||||
getJoinedMembers: () => [
|
getJoinedMembers: () => [
|
||||||
|
@ -180,11 +211,10 @@ describe('matrix-to', function() {
|
||||||
};
|
};
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
expect(pickedServers).toExist();
|
expect(pickedServers).toExist();
|
||||||
expect(pickedServers.length).toBe(1);
|
expect(pickedServers.length).toBe(0);
|
||||||
expect(pickedServers[0]).toBe("[::1]");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with IPv4 hostnames with ports', function() {
|
it('should not consider IPv4 hostnames with ports', function() {
|
||||||
peg.get().getRoom = () => {
|
peg.get().getRoom = () => {
|
||||||
return {
|
return {
|
||||||
getJoinedMembers: () => [
|
getJoinedMembers: () => [
|
||||||
|
@ -197,11 +227,10 @@ describe('matrix-to', function() {
|
||||||
};
|
};
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
expect(pickedServers).toExist();
|
expect(pickedServers).toExist();
|
||||||
expect(pickedServers.length).toBe(1);
|
expect(pickedServers.length).toBe(0);
|
||||||
expect(pickedServers[0]).toBe("127.0.0.1:8448");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with IPv6 hostnames with ports', function() {
|
it('should not consider IPv6 hostnames with ports', function() {
|
||||||
peg.get().getRoom = () => {
|
peg.get().getRoom = () => {
|
||||||
return {
|
return {
|
||||||
getJoinedMembers: () => [
|
getJoinedMembers: () => [
|
||||||
|
@ -214,8 +243,7 @@ describe('matrix-to', function() {
|
||||||
};
|
};
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
expect(pickedServers).toExist();
|
expect(pickedServers).toExist();
|
||||||
expect(pickedServers.length).toBe(1);
|
expect(pickedServers.length).toBe(0);
|
||||||
expect(pickedServers[0]).toBe("[::1]:8448");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with hostnames with ports', function() {
|
it('should work with hostnames with ports', function() {
|
||||||
|
@ -235,6 +263,140 @@ describe('matrix-to', function() {
|
||||||
expect(pickedServers[0]).toBe("example.org:8448");
|
expect(pickedServers[0]).toBe("example.org:8448");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not consider servers explicitly denied by ACLs', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:evilcorp.com",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:chat.evilcorp.com",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
currentState: {
|
||||||
|
getStateEvents: (type, key) => {
|
||||||
|
if (type !== "m.room.server_acl" || key !== "") return null;
|
||||||
|
return {
|
||||||
|
getContent: () => {
|
||||||
|
return {
|
||||||
|
deny: ["evilcorp.com", "*.evilcorp.com"],
|
||||||
|
allow: ["*"],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not consider servers not allowed by ACLs', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:evilcorp.com",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:chat.evilcorp.com",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
currentState: {
|
||||||
|
getStateEvents: (type, key) => {
|
||||||
|
if (type !== "m.room.server_acl" || key !== "") return null;
|
||||||
|
return {
|
||||||
|
getContent: () => {
|
||||||
|
return {
|
||||||
|
deny: [],
|
||||||
|
allow: [], // implies "ban everyone"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consider servers not explicitly banned by ACLs', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:evilcorp.com",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:chat.evilcorp.com",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
currentState: {
|
||||||
|
getStateEvents: (type, key) => {
|
||||||
|
if (type !== "m.room.server_acl" || key !== "") return null;
|
||||||
|
return {
|
||||||
|
getContent: () => {
|
||||||
|
return {
|
||||||
|
deny: ["*.evilcorp.com"], // evilcorp.com is still good though
|
||||||
|
allow: ["*"],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toEqual("evilcorp.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consider servers not disallowed by ACLs', function() {
|
||||||
|
peg.get().getRoom = () => {
|
||||||
|
return {
|
||||||
|
getJoinedMembers: () => [
|
||||||
|
{
|
||||||
|
userId: "@alice:evilcorp.com",
|
||||||
|
powerLevel: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:chat.evilcorp.com",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
currentState: {
|
||||||
|
getStateEvents: (type, key) => {
|
||||||
|
if (type !== "m.room.server_acl" || key !== "") return null;
|
||||||
|
return {
|
||||||
|
getContent: () => {
|
||||||
|
return {
|
||||||
|
deny: [],
|
||||||
|
allow: ["evilcorp.com"], // implies "ban everyone else"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
||||||
|
expect(pickedServers).toExist();
|
||||||
|
expect(pickedServers.length).toBe(1);
|
||||||
|
expect(pickedServers[0]).toEqual("evilcorp.com");
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate an event permalink for room IDs with no candidate servers', function() {
|
it('should generate an event permalink for room IDs with no candidate servers', function() {
|
||||||
peg.get().getRoom = () => null;
|
peg.get().getRoom = () => null;
|
||||||
const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
|
const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
|
||||||
|
|
Loading…
Reference in a new issue