mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-07-01 00:51:36 +00:00
Replace Synapse antispam module with a "rule server" counterpart
Fixes https://github.com/matrix-org/mjolnir/issues/37 **Note**: this completely replaces the existing Synapse antispam module. Admins are encouraged to upgrade upon release, though will have to make significant changes to how their configuration works.
This commit is contained in:
parent
e4e5c5e72d
commit
90640b6714
53
README.md
53
README.md
|
@ -89,7 +89,27 @@ Using the bot to manage your rooms is great, however if you want to use your ban
|
|||
is needed. Primarily meant to block invites from undesired homeservers/users, Mjolnir's
|
||||
Synapse module is a way to interpret ban lists and apply them to your entire homeserver.
|
||||
|
||||
First, install the module to your Synapse python environment:
|
||||
**Warning**: This module works by running generated Python code in your homeserver. The code
|
||||
is generated based off the rules provided in the ban lists, which may include crafted rules
|
||||
which may allow unrestricted access to your server. Only use lists you trust and do not
|
||||
use anyone else's infrastructure to get the ruleset from - only use infrastructure that
|
||||
you control.
|
||||
|
||||
If this is acceptable, the following steps may be performed.
|
||||
|
||||
First, run the Docker image for the rule server. This is what will be serving the generated
|
||||
Python for the Synapse antispam module to read from. This rule server will serve the Python
|
||||
off a webserver at `/api/v1/py_rules` which must be accessible by wherever Synapse is installed.
|
||||
It is not recommended to expose this webserver to the outside world.
|
||||
|
||||
```
|
||||
docker run --rm -d -v /etc/mjolnir/ruleserver.yaml:/data/config/production.yaml -p 127.0.0.0:8080:8080 matrixdotorg/mjolnir
|
||||
```
|
||||
|
||||
**Note**: the exact same Mjolnir image is used to run the rule server. To configure using the rule
|
||||
server instead of the bot function, see the `ruleServer` options in the config.
|
||||
|
||||
After that is running, install the module to your Synapse python environment:
|
||||
```
|
||||
pip install -e "git+https://github.com/matrix-org/mjolnir.git#egg=mjolnir&subdirectory=synapse_antispam"
|
||||
```
|
||||
|
@ -100,35 +120,18 @@ pip install -e "git+https://github.com/matrix-org/mjolnir.git#egg=mjolnir&subdir
|
|||
Then add the following to your `homeserver.yaml`:
|
||||
```yaml
|
||||
spam_checker:
|
||||
module: mjolnir.AntiSpam
|
||||
config:
|
||||
# Prevent servers/users in the ban lists from inviting users on this
|
||||
# server to rooms. Default true.
|
||||
block_invites: true
|
||||
# Flag messages sent by servers/users in the ban lists as spam. Currently
|
||||
# this means that spammy messages will appear as empty to users. Default
|
||||
# false.
|
||||
block_messages: false
|
||||
# Remove users from the user directory search by filtering matrix IDs and
|
||||
# display names by the entries in the user ban list. Default false.
|
||||
block_usernames: false
|
||||
# The room IDs of the ban lists to honour. Unlike other parts of Mjolnir,
|
||||
# this list cannot be room aliases or permalinks. This server is expected
|
||||
# to already be joined to the room - Mjolnir will not automatically join
|
||||
# these rooms.
|
||||
ban_lists:
|
||||
- "!roomid:example.org"
|
||||
- module: mjolnir.AntiSpam
|
||||
config:
|
||||
# Where the antispam module should periodically retrieve updated rules from. This
|
||||
# should be pointed at the Mjolnir rule server.
|
||||
rules_url: 'http://localhost:8080/api/v1/py_rules'
|
||||
```
|
||||
|
||||
*Note*: Although this is described as a "spam checker", it does much more than fight
|
||||
spam.
|
||||
|
||||
Be sure to change the configuration to match your setup. Your server is expected to
|
||||
already be participating in the ban lists - if it is not, you will need to have a user
|
||||
on your homeserver join. The antispam module will not join the rooms for you.
|
||||
|
||||
If you change the configuration, you will need to restart Synapse. You'll also need
|
||||
to restart Synapse to install the plugin.
|
||||
Be sure to change the configuration to match your setup. If you change the configuration,
|
||||
you will need to restart Synapse. You'll also need to restart Synapse to install the plugin.
|
||||
|
||||
## Development
|
||||
|
||||
|
|
|
@ -156,3 +156,73 @@ health:
|
|||
# The HTTP status code which reports that the bot is not healthy/ready.
|
||||
# Defaults to 418.
|
||||
unhealthyStatus: 418
|
||||
|
||||
# Optional configuration for the built-in rule server
|
||||
ruleServer:
|
||||
# When enabled, all bot functionality will be disabled. This means that if you
|
||||
# want to run a bot alongside a rule server then you'll need two instances of
|
||||
# this config: one for the bot and one for the rule server. The rule server is
|
||||
# disabled by default.
|
||||
#
|
||||
# Note that the config above should be as complete as possible, though the rule
|
||||
# server will ignore many options not applicable to it (protections, commands, etc)
|
||||
enabled: false
|
||||
|
||||
# The port to run the rule server on. Defaults to 8080. Note that if you're using
|
||||
# the healthz options then this port must be different. For health monitoring
|
||||
# without a healthz endpoint, use /api/v1/py_rules and look for a 200 OK response.
|
||||
port: 8080
|
||||
|
||||
# The address to listen for requests on. Defaults to all addresses. Typically for
|
||||
# Docker containers this will be 0.0.0.0 so the port mapping can function correctly.
|
||||
address: '0.0.0.0'
|
||||
|
||||
# The policy rooms (matrix.to URLs) to expose on the rule server. These should be
|
||||
# trusted rooms to avoid potential security risks with the Synapse module.
|
||||
listRooms:
|
||||
- "https://matrix.to/#/#yourroom:example.org"
|
||||
|
||||
# Restrictions that can be applied to users, servers, and whole rooms. These are
|
||||
# split into categories (eg: 'messages') which describe the sort of action the
|
||||
# entity will be taking. Under that are the kinds of entities that can be affected.
|
||||
# When the flag for an entity is `true`, it means that if a rule applies to that
|
||||
# kind of entity (eg: users) then it will be given to the antispam module to
|
||||
# process. When false, rules which apply to that entity will not be considered for
|
||||
# that action.
|
||||
blocks:
|
||||
# Whether or not to block messages (events) sent by an entity.
|
||||
messages:
|
||||
users: true
|
||||
rooms: true
|
||||
servers: true
|
||||
# Whether or not to block invites from the given entities
|
||||
invites:
|
||||
users: true
|
||||
rooms: true
|
||||
servers: true
|
||||
# Whether or not check a given user's username/displayname against the list rules.
|
||||
usernames:
|
||||
users: true
|
||||
rooms: false # rooms don't have usernames and can't be blocked.
|
||||
servers: false # the only rule which would apply is one for the local server.
|
||||
# Whether or not rooms to block rooms from being created by given entities. Rooms are
|
||||
# only created on the local server - this does not trigger for rooms which have been
|
||||
# 'discovered' by federation.
|
||||
roomCreate:
|
||||
users: true
|
||||
rooms: false # rooms don't create rooms
|
||||
servers: false # room creation only happens on the local server, which means banning yourself.
|
||||
# Whether or not a given entity should be blocked from creating an alias pointing at a room.
|
||||
# Like room creation, this only happens on the local server - remote aliases do not trigger these.
|
||||
makeAlias:
|
||||
users: true
|
||||
rooms: true # this would block rooms from receiving aliases on the local server
|
||||
servers: false # room aliases can only be created on the local server, so blocking yourself is not recommended
|
||||
# Whether or not to block rooms from being published by a given entity. Like other aspects of room
|
||||
# aliases and creation, this is only triggered for local attempts to publish a room to the server's
|
||||
# own directory - federated directories do not trigger this.
|
||||
publishRoom:
|
||||
users: true
|
||||
rooms: true # this would block the rooms from entering the directory
|
||||
servers: false # publishing happens locally, and it's not recommended to ban yourself
|
||||
|
||||
|
|
|
@ -27,6 +27,6 @@
|
|||
"config": "^3.3.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"js-yaml": "^3.13.1",
|
||||
"matrix-bot-sdk": "^0.5.4"
|
||||
"matrix-bot-sdk": "^0.5.9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,12 @@ export async function logMessage(level: LogLevel, module: string, message: strin
|
|||
if (!additionalRoomIds) additionalRoomIds = [];
|
||||
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
|
||||
|
||||
// Prefix logging if we're running in ruleserver mode
|
||||
if (config.ruleServer?.enabled) {
|
||||
module = `[RuleServer] ${module}`;
|
||||
message = `[RuleServer] ${message}`;
|
||||
}
|
||||
|
||||
if (config.RUNTIME.client && (config.verboseLogging || LogLevel.INFO.includes(level))) {
|
||||
let clientMessage = message;
|
||||
if (level === LogLevel.WARN) clientMessage = `⚠ | ${message}`;
|
||||
|
|
|
@ -17,6 +17,21 @@ limitations under the License.
|
|||
import * as config from "config";
|
||||
import { MatrixClient } from "matrix-bot-sdk";
|
||||
|
||||
export interface IBlock {
|
||||
users: boolean;
|
||||
rooms: boolean;
|
||||
servers: boolean;
|
||||
}
|
||||
|
||||
export interface IRuleServerBlocks {
|
||||
messages: IBlock;
|
||||
invites: IBlock;
|
||||
usernames: IBlock;
|
||||
roomCreate: IBlock;
|
||||
makeAlias: IBlock;
|
||||
publishRoom: IBlock;
|
||||
}
|
||||
|
||||
interface IConfig {
|
||||
homeserverUrl: string;
|
||||
accessToken: string;
|
||||
|
@ -59,6 +74,13 @@ interface IConfig {
|
|||
unhealthyStatus: number;
|
||||
};
|
||||
};
|
||||
ruleServer: {
|
||||
enabled: boolean;
|
||||
port: number;
|
||||
address: string;
|
||||
listRooms: string[];
|
||||
blocks: IRuleServerBlocks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Config options only set at runtime. Try to avoid using the objects
|
||||
|
@ -111,6 +133,44 @@ const defaultConfig: IConfig = {
|
|||
unhealthyStatus: 418,
|
||||
},
|
||||
},
|
||||
ruleServer: {
|
||||
enabled: false,
|
||||
port: 8080,
|
||||
address: '0.0.0.0',
|
||||
listRooms: [],
|
||||
blocks: {
|
||||
messages: {
|
||||
users: false,
|
||||
rooms: false,
|
||||
servers: false,
|
||||
},
|
||||
invites: {
|
||||
users: false,
|
||||
rooms: false,
|
||||
servers: false,
|
||||
},
|
||||
usernames: {
|
||||
users: false,
|
||||
rooms: false,
|
||||
servers: false,
|
||||
},
|
||||
roomCreate: {
|
||||
users: false,
|
||||
rooms: false,
|
||||
servers: false,
|
||||
},
|
||||
makeAlias: {
|
||||
users: false,
|
||||
rooms: false,
|
||||
servers: false,
|
||||
},
|
||||
publishRoom: {
|
||||
users: false,
|
||||
rooms: false,
|
||||
servers: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// Needed to make the interface happy.
|
||||
RUNTIME: {
|
||||
|
|
45
src/index.ts
45
src/index.ts
|
@ -31,6 +31,7 @@ import { logMessage } from "./LogProxy";
|
|||
import { MembershipEvent } from "matrix-bot-sdk/lib/models/events/MembershipEvent";
|
||||
import * as htmlEscape from "escape-html";
|
||||
import { Healthz } from "./health/healthz";
|
||||
import { RuleServer } from "./modules/RuleServer";
|
||||
|
||||
config.RUNTIME = {client: null};
|
||||
|
||||
|
@ -57,6 +58,40 @@ if (config.health.healthz.enabled) {
|
|||
|
||||
config.RUNTIME.client = client;
|
||||
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
|
||||
// Ensure we're in the management room
|
||||
LogService.info("index", "Resolving management room...");
|
||||
const managementRoomId = await client.resolveRoom(config.managementRoom);
|
||||
if (!joinedRooms.includes(managementRoomId)) {
|
||||
config.managementRoom = await client.joinRoom(config.managementRoom);
|
||||
} else {
|
||||
config.managementRoom = managementRoomId;
|
||||
}
|
||||
|
||||
// Switch to using the rule server if we need to
|
||||
if (config.ruleServer?.enabled) {
|
||||
await logMessage(LogLevel.INFO, "index", "Rule server is starting up.");
|
||||
|
||||
// Resolve all the rule server's watched rooms
|
||||
const banLists: BanList[] = [];
|
||||
for (const roomRef of config.ruleServer.listRooms) {
|
||||
const permalink = Permalinks.parseUrl(roomRef);
|
||||
if (!permalink.roomIdOrAlias) continue;
|
||||
|
||||
let roomId = await client.resolveRoom(permalink.roomIdOrAlias);
|
||||
if (!joinedRooms.includes(roomId)) {
|
||||
roomId = await client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
|
||||
}
|
||||
|
||||
banLists.push(new BanList(roomId, roomRef, client));
|
||||
}
|
||||
|
||||
const ruleServer = new RuleServer(client, banLists);
|
||||
await ruleServer.start();
|
||||
return;
|
||||
}
|
||||
|
||||
client.on("room.invite", async (roomId: string, inviteEvent: any) => {
|
||||
const membershipEvent = new MembershipEvent(inviteEvent);
|
||||
|
||||
|
@ -89,8 +124,6 @@ if (config.health.healthz.enabled) {
|
|||
const banLists: BanList[] = [];
|
||||
const protectedRooms: { [roomId: string]: string } = {};
|
||||
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
|
||||
// Ensure we're also joined to the rooms we're protecting
|
||||
LogService.info("index", "Resolving protected rooms...");
|
||||
for (const roomRef of config.protectedRooms) {
|
||||
|
@ -105,14 +138,6 @@ if (config.health.healthz.enabled) {
|
|||
protectedRooms[roomId] = roomRef;
|
||||
}
|
||||
|
||||
// Ensure we're also in the management room
|
||||
LogService.info("index", "Resolving management room...");
|
||||
const managementRoomId = await client.resolveRoom(config.managementRoom);
|
||||
if (!joinedRooms.includes(managementRoomId)) {
|
||||
config.managementRoom = await client.joinRoom(config.managementRoom);
|
||||
} else {
|
||||
config.managementRoom = managementRoomId;
|
||||
}
|
||||
await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
|
||||
|
||||
const bot = new Mjolnir(client, protectedRooms, banLists);
|
||||
|
|
|
@ -26,7 +26,7 @@ export function recommendationToStable(recommendation: string, unstable = true):
|
|||
|
||||
export class ListRule {
|
||||
|
||||
private glob: MatrixGlob;
|
||||
public readonly glob: MatrixGlob;
|
||||
|
||||
constructor(public readonly entity: string, private action: string, public readonly reason: string, public readonly kind: string) {
|
||||
this.glob = new MatrixGlob(entity);
|
||||
|
|
163
src/modules/RuleServer.ts
Normal file
163
src/modules/RuleServer.ts
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { LogLevel, MatrixClient } from "matrix-bot-sdk";
|
||||
import BanList, { ALL_RULE_TYPES } from "../models/BanList";
|
||||
import { logMessage } from "../LogProxy";
|
||||
import { Healthz } from "../health/healthz";
|
||||
import config, { IRuleServerBlocks } from "../config";
|
||||
import { ListRule } from "../models/ListRule";
|
||||
import * as http from "http";
|
||||
|
||||
interface IRule {
|
||||
search: string; // python
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
interface IPyRules {
|
||||
checks: {
|
||||
spam: IRule[];
|
||||
invites: IRule[];
|
||||
profiles: IRule[];
|
||||
createRoom: IRule[];
|
||||
createAlias: IRule[];
|
||||
publishRoom: IRule[];
|
||||
};
|
||||
}
|
||||
|
||||
export class RuleServer {
|
||||
private pyRules: IPyRules;
|
||||
|
||||
constructor(private client: MatrixClient, private banLists: BanList[]) {
|
||||
client.on("room.event", this.handleEvent.bind(this));
|
||||
}
|
||||
|
||||
public async start() {
|
||||
for (const list of this.banLists) {
|
||||
await list.updateList();
|
||||
}
|
||||
await this.rebuildRules();
|
||||
|
||||
await this.client.start();
|
||||
Healthz.isHealthy = true;
|
||||
|
||||
http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify({healthy: Healthz.isHealthy, ...this.pyRules}));
|
||||
}).listen(config.ruleServer.port, config.ruleServer.address, async () => {
|
||||
await logMessage(LogLevel.INFO, "RuleServer", "Rule server is running");
|
||||
});
|
||||
}
|
||||
|
||||
private async handleEvent(roomId: string, event: any) {
|
||||
const banList = this.banLists.find(b => b.roomId === roomId);
|
||||
if (!banList) return; // not useful to us
|
||||
if (!ALL_RULE_TYPES.includes(event['type'])) return; // not useful to us
|
||||
await banList.updateList();
|
||||
await this.rebuildRules();
|
||||
}
|
||||
|
||||
private async rebuildRules() {
|
||||
const userRules = this.banLists.map(b => b.userRules).reduce((a, c) => {a.push(...c); return a;}, []);
|
||||
const roomRules = this.banLists.map(b => b.roomRules).reduce((a, c) => {a.push(...c); return a;}, []);
|
||||
const serverRules = this.banLists.map(b => b.serverRules).reduce((a, c) => {a.push(...c); return a;}, []);
|
||||
|
||||
console.log({userRules, roomRules, serverRules});
|
||||
|
||||
this.pyRules = {
|
||||
checks: {
|
||||
spam: pythonForRulesCond('messages', {
|
||||
users: {rules: userRules, python: "event.get('sender', '')"},
|
||||
rooms: {rules: roomRules, python: "event.get('room_id', '')"},
|
||||
servers: {rules: serverRules, python: "UserID.from_string(event.get('sender', '')).domain"},
|
||||
}),
|
||||
invites: pythonForRulesCond('invites', {
|
||||
users: {rules: userRules, python: "inviter_user_id"},
|
||||
rooms: {rules: roomRules, python: "room_id"},
|
||||
servers: {rules: serverRules, python: "UserID.from_string(inviter_user_id).domain"},
|
||||
}),
|
||||
profiles: [
|
||||
...pythonForRulesCond('usernames', {
|
||||
users: {rules: userRules, python: 'user_profile["user_id"]'},
|
||||
rooms: null, // not possible
|
||||
servers: {rules: serverRules, python: 'UserID.from_string(user_profile["user_id"]).domain'},
|
||||
}),
|
||||
...pythonForRulesCond('usernames', {
|
||||
// run a second check on the user's display name
|
||||
users: {rules: userRules, python: 'user_profile["display_name"]'},
|
||||
rooms: null,
|
||||
servers: null,
|
||||
}),
|
||||
],
|
||||
createRoom: pythonForRulesCond('roomCreate', {
|
||||
users: {rules: userRules, python: "user_id"},
|
||||
rooms: null,
|
||||
servers: {rules: serverRules, python: "UserID.from_string(user_id).domain"},
|
||||
}),
|
||||
createAlias: pythonForRulesCond('makeAlias', {
|
||||
users: {rules: userRules, python: "user_id"},
|
||||
rooms: {rules: roomRules, python: "room_alias"},
|
||||
servers: {rules: serverRules, python: "UserID.from_string(user_id).domain"},
|
||||
}),
|
||||
publishRoom: pythonForRulesCond('publishRoom', {
|
||||
users: {rules: userRules, python: "user_id"},
|
||||
rooms: {rules: roomRules, python: "room_id"},
|
||||
servers: {rules: serverRules, python: "UserID.from_string(user_id).domain"},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
await logMessage(LogLevel.INFO, "RuleServer", "Python rule set updated");
|
||||
}
|
||||
}
|
||||
|
||||
// ==== mini python lib below ====
|
||||
|
||||
interface IPythonRuleEntity {
|
||||
rules: ListRule[];
|
||||
python: string;
|
||||
}
|
||||
|
||||
interface IPythonRule {
|
||||
users: IPythonRuleEntity;
|
||||
rooms: IPythonRuleEntity;
|
||||
servers: IPythonRuleEntity;
|
||||
}
|
||||
|
||||
function pythonForRulesCond(blockName: keyof IRuleServerBlocks, conf: IPythonRule): IRule[] {
|
||||
const generated: IRule[] = [];
|
||||
|
||||
if (config.ruleServer.blocks[blockName].users && conf.users) {
|
||||
for (const rule of conf.users.rules) {
|
||||
generated.push({pattern: rule.glob.regex.source, search: conf.users.python});
|
||||
}
|
||||
}
|
||||
|
||||
if (config.ruleServer.blocks[blockName].rooms && conf.rooms) {
|
||||
for (const rule of conf.rooms.rules) {
|
||||
generated.push({pattern: rule.glob.regex.source, search: conf.rooms.python});
|
||||
}
|
||||
}
|
||||
|
||||
if (config.ruleServer.blocks[blockName].servers && conf.servers) {
|
||||
for (const rule of conf.servers.rules) {
|
||||
generated.push({pattern: rule.glob.regex.source, search: conf.servers.python});
|
||||
}
|
||||
}
|
||||
|
||||
return generated;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
# Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -14,115 +14,145 @@
|
|||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
from .list_rule import ALL_RULE_TYPES, RECOMMENDATION_BAN
|
||||
from .ban_list import BanList
|
||||
import re
|
||||
from synapse.types import UserID
|
||||
from twisted.internet import task, reactor, defer
|
||||
|
||||
logger = logging.getLogger("synapse.contrib." + __name__)
|
||||
|
||||
class AntiSpam(object):
|
||||
def __init__(self, config, api):
|
||||
self.block_invites = config.get("block_invites", True)
|
||||
self.block_messages = config.get("block_messages", False)
|
||||
self.block_usernames = config.get("block_usernames", False)
|
||||
self.list_room_ids = config.get("ban_lists", [])
|
||||
self.rooms_to_lists = {} # type: Dict[str, BanList]
|
||||
self.api = api
|
||||
self._api = api
|
||||
self._config = config
|
||||
|
||||
# Now we build the ban lists so we can match them
|
||||
self.build_lists()
|
||||
# Start a timer to rip the new rules off the server. We hit the rule server
|
||||
# often to avoid missing rules - the response should be tens of milliseconds
|
||||
# in duration, though network issues are possible - Twisted should automatically
|
||||
# stack the calls for us.
|
||||
# HACK: Private member access (_hs)
|
||||
api._hs.get_clock().looping_call(self._update_rules, 5 * 1000)
|
||||
|
||||
def build_lists(self):
|
||||
for room_id in self.list_room_ids:
|
||||
self.build_list(room_id)
|
||||
# These are all arrays of compile()'d code from the ban list server.
|
||||
# First match wins. See _update_rules() for more info.
|
||||
self._code_spam_checks = []
|
||||
self._code_invite_checks = []
|
||||
self._code_profile_checks = []
|
||||
self._code_create_room_checks = []
|
||||
self._code_create_alias_checks = []
|
||||
self._code_publish_room_checks = []
|
||||
|
||||
def build_list(self, room_id):
|
||||
logger.info("Rebuilding ban list for %s" % (room_id))
|
||||
self.get_list_for_room(room_id).build()
|
||||
def _update_rules(self):
|
||||
async def run():
|
||||
try:
|
||||
logger.info("Updating mjolnir rules...")
|
||||
|
||||
def get_list_for_room(self, room_id):
|
||||
if room_id not in self.rooms_to_lists:
|
||||
self.rooms_to_lists[room_id] = BanList(api=self.api, room_id=room_id)
|
||||
return self.rooms_to_lists[room_id]
|
||||
# HACK: Private member access (_hs)
|
||||
resp = await self._api._hs.get_proxied_http_client().get_json(self._config['rules_url'])
|
||||
|
||||
def is_user_banned(self, user_id):
|
||||
for room_id in self.rooms_to_lists:
|
||||
ban_list = self.rooms_to_lists[room_id]
|
||||
for rule in ban_list.user_rules:
|
||||
if rule.matches(user_id):
|
||||
return rule.action == RECOMMENDATION_BAN
|
||||
return False
|
||||
# *** !! DANGER !! ***
|
||||
# We're about to trust that the admin has secured their network appropriately and
|
||||
# that the values returned by the configurable URL are safe to execute as real
|
||||
# python code. We're using eval(), which should mean that it can only be expressions,
|
||||
# however there is still risk of damages. We are knowingly processing the strings
|
||||
# returned and assuming they won't try to take control of the homeserver, or worse.
|
||||
# *** !! DANGER !! ***
|
||||
|
||||
def is_room_banned(self, invite_room_id):
|
||||
for room_id in self.rooms_to_lists:
|
||||
ban_list = self.rooms_to_lists[room_id]
|
||||
for rule in ban_list.room_rules:
|
||||
if rule.matches(invite_room_id):
|
||||
return rule.action == RECOMMENDATION_BAN
|
||||
return False
|
||||
self._code_spam_checks = self._compile_rules(resp['checks']['spam'])
|
||||
self._code_invite_checks = self._compile_rules(resp['checks']['invites'])
|
||||
self._code_profile_checks = self._compile_rules(resp['checks']['profiles'])
|
||||
self._code_create_room_checks = self._compile_rules(resp['checks']['createRoom'])
|
||||
self._code_create_alias_checks = self._compile_rules(resp['checks']['createAlias'])
|
||||
self._code_publish_room_checks = self._compile_rules(resp['checks']['publishRoom'])
|
||||
|
||||
def is_server_banned(self, server_name):
|
||||
for room_id in self.rooms_to_lists:
|
||||
ban_list = self.rooms_to_lists[room_id]
|
||||
for rule in ban_list.server_rules:
|
||||
if rule.matches(server_name):
|
||||
return rule.action == RECOMMENDATION_BAN
|
||||
return False
|
||||
logger.info("Updated mjolnir rules")
|
||||
except Exception as e:
|
||||
logger.warning("Error updating mjolnir rules: %s", e)
|
||||
|
||||
return defer.ensureDeferred(run())
|
||||
|
||||
def _compile_rules(self, rules):
|
||||
return [{
|
||||
"search": compile(rule["search"], '<string>', 'eval'),
|
||||
"pattern": rule["pattern"],
|
||||
} for rule in rules]
|
||||
|
||||
# --- spam checker interface below here ---
|
||||
|
||||
def check_event_for_spam(self, event):
|
||||
room_id = event.get("room_id", "")
|
||||
event_type = event.get("type", "")
|
||||
state_key = event.get("state_key", None)
|
||||
|
||||
# Rebuild the rules if there's an event for our ban lists
|
||||
if state_key is not None and event_type in ALL_RULE_TYPES and room_id in self.list_room_ids:
|
||||
logger.info("Received ban list event - updating list")
|
||||
self.get_list_for_room(room_id).build(with_event=event)
|
||||
return False # Ban list updates aren't spam
|
||||
|
||||
if not self.block_messages:
|
||||
return False # not spam (we aren't blocking messages)
|
||||
|
||||
sender = UserID.from_string(event.get("sender", ""))
|
||||
if self.is_user_banned(sender.to_string()):
|
||||
return True
|
||||
if self.is_server_banned(sender.domain):
|
||||
return True
|
||||
for check in self._code_spam_checks:
|
||||
params = {
|
||||
"event": event,
|
||||
"UserID": UserID,
|
||||
}
|
||||
search = eval(check["search"], {}, params)
|
||||
if re.search(check["pattern"], search):
|
||||
return True # is spam
|
||||
|
||||
return False # not spam (as far as we're concerned)
|
||||
|
||||
def user_may_invite(self, inviter_user_id, invitee_user_id, room_id):
|
||||
if not self.block_invites:
|
||||
return True # allowed (we aren't blocking invites)
|
||||
|
||||
sender = UserID.from_string(inviter_user_id)
|
||||
if self.is_user_banned(sender.to_string()):
|
||||
return False
|
||||
if self.is_room_banned(room_id):
|
||||
return False
|
||||
if self.is_server_banned(sender.domain):
|
||||
return False
|
||||
for check in self._code_invite_checks:
|
||||
params = {
|
||||
"inviter_user_id": inviter_user_id,
|
||||
"invitee_user_id": invitee_user_id,
|
||||
"room_id": room_id,
|
||||
"UserID": UserID,
|
||||
}
|
||||
search = eval(check["search"], {}, params)
|
||||
if re.search(check["pattern"], search):
|
||||
return False # is spam
|
||||
|
||||
return True # allowed (as far as we're concerned)
|
||||
|
||||
def check_username_for_spam(self, user_profile):
|
||||
if not self.block_usernames:
|
||||
return True # allowed (we aren't blocking based on usernames)
|
||||
for check in self._code_profile_checks:
|
||||
params = {
|
||||
"user_profile": user_profile,
|
||||
"UserID": UserID,
|
||||
}
|
||||
search = eval(check["search"], {}, params)
|
||||
if re.search(check["pattern"], search):
|
||||
return False # is spam
|
||||
|
||||
# Check whether the user ID or display name matches any of the banned
|
||||
# patterns.
|
||||
return self.is_user_banned(user_profile["user_id"]) or self.is_user_banned(user_profile["display_name"])
|
||||
return True # allowed (as far as we're concerned)
|
||||
|
||||
def user_may_create_room(self, user_id):
|
||||
return True # allowed
|
||||
for check in self._code_create_room_checks:
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"UserID": UserID,
|
||||
}
|
||||
search = eval(check["search"], {}, params)
|
||||
if re.search(check["pattern"], search):
|
||||
return False # is spam
|
||||
|
||||
return True # allowed (as far as we're concerned)
|
||||
|
||||
def user_may_create_room_alias(self, user_id, room_alias):
|
||||
return True # allowed
|
||||
for check in self._code_create_alias_checks:
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"room_alias": room_alias,
|
||||
"UserID": UserID,
|
||||
}
|
||||
search = eval(check["search"], {}, params)
|
||||
if re.search(check["pattern"], search):
|
||||
return False # is spam
|
||||
|
||||
return True # allowed (as far as we're concerned)
|
||||
|
||||
def user_may_publish_room(self, user_id, room_id):
|
||||
return True # allowed
|
||||
for check in self._code_publish_room_checks:
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"room_id": room_id,
|
||||
"UserID": UserID,
|
||||
}
|
||||
search = eval(check["search"], {}, params)
|
||||
if re.search(check["pattern"], search):
|
||||
return False # is spam
|
||||
|
||||
return True # allowed (as far as we're concerned)
|
||||
|
||||
@staticmethod
|
||||
def parse_config(config):
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
from .list_rule import ListRule, ALL_RULE_TYPES, USER_RULE_TYPES, SERVER_RULE_TYPES, ROOM_RULE_TYPES
|
||||
from twisted.internet import defer
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
|
||||
logger = logging.getLogger("synapse.contrib." + __name__)
|
||||
|
||||
class BanList(object):
|
||||
def __init__(self, api, room_id):
|
||||
self.api = api
|
||||
self.room_id = room_id
|
||||
self.server_rules = []
|
||||
self.user_rules = []
|
||||
self.room_rules = []
|
||||
self.build()
|
||||
|
||||
def build(self, with_event=None):
|
||||
@defer.inlineCallbacks
|
||||
def run(with_event):
|
||||
events = yield self.get_relevant_state_events()
|
||||
if with_event is not None:
|
||||
events = [*events, with_event]
|
||||
self.server_rules = []
|
||||
self.user_rules = []
|
||||
self.room_rules = []
|
||||
for event in events:
|
||||
event_type = event.get("type", "")
|
||||
state_key = event.get("state_key", "")
|
||||
content = event.get("content", {})
|
||||
if state_key is None:
|
||||
continue # Some message event got in here?
|
||||
|
||||
# Skip over events which are replaced by with_event. We do this
|
||||
# to ensure that when we rebuild the list we're using updated rules.
|
||||
if with_event is not None:
|
||||
w_event_type = with_event.get("type", "")
|
||||
w_state_key = with_event.get("state_key", "")
|
||||
w_event_id = with_event.event_id
|
||||
event_id = event.event_id
|
||||
if w_event_type == event_type and w_state_key == state_key and w_event_id != event_id:
|
||||
continue
|
||||
|
||||
entity = content.get("entity", None)
|
||||
recommendation = content.get("recommendation", None)
|
||||
reason = content.get("reason", None)
|
||||
if entity is None or recommendation is None or reason is None:
|
||||
continue # invalid event
|
||||
|
||||
logger.info("Adding rule %s/%s with action %s" % (event_type, entity, recommendation))
|
||||
rule = ListRule(entity=entity, action=recommendation, reason=reason, kind=event_type)
|
||||
if event_type in USER_RULE_TYPES:
|
||||
self.user_rules.append(rule)
|
||||
elif event_type in ROOM_RULE_TYPES:
|
||||
self.room_rules.append(rule)
|
||||
elif event_type in SERVER_RULE_TYPES:
|
||||
self.server_rules.append(rule)
|
||||
|
||||
run_as_background_process("mjolnir_build_ban_list", run, with_event)
|
||||
|
||||
def get_relevant_state_events(self):
|
||||
return self.api.get_state_events_in_room(self.room_id, [(t, None) for t in ALL_RULE_TYPES])
|
|
@ -1,52 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from synapse.util import glob_to_regex
|
||||
|
||||
RECOMMENDATION_BAN = "m.ban"
|
||||
RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"]
|
||||
|
||||
RULE_USER = "m.room.rule.user"
|
||||
RULE_ROOM = "m.room.rule.room"
|
||||
RULE_SERVER = "m.room.rule.server"
|
||||
USER_RULE_TYPES = [RULE_USER, "org.matrix.mjolnir.rule.user"]
|
||||
ROOM_RULE_TYPES = [RULE_ROOM, "org.matrix.mjolnir.rule.room"]
|
||||
SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"]
|
||||
ALL_RULE_TYPES = [*USER_RULE_TYPES, *ROOM_RULE_TYPES, *SERVER_RULE_TYPES]
|
||||
|
||||
def recommendation_to_stable(recommendation):
|
||||
if recommendation in RECOMMENDATION_BAN_TYPES:
|
||||
return RECOMMENDATION_BAN
|
||||
return None
|
||||
|
||||
def rule_type_to_stable(rule):
|
||||
if rule in USER_RULE_TYPES:
|
||||
return RULE_USER
|
||||
if rule in ROOM_RULE_TYPES:
|
||||
return RULE_ROOM
|
||||
if rule in SERVER_RULE_TYPES:
|
||||
return RULE_SERVER
|
||||
return None
|
||||
|
||||
class ListRule(object):
|
||||
def __init__(self, entity, action, reason, kind):
|
||||
self.entity = entity
|
||||
self.regex = glob_to_regex(entity)
|
||||
self.action = recommendation_to_stable(action)
|
||||
self.reason = reason
|
||||
self.kind = rule_type_to_stable(kind)
|
||||
|
||||
def matches(self, victim):
|
||||
return self.regex.match(victim)
|
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||
|
||||
setup(
|
||||
name="mjolnir",
|
||||
version="0.0.1",
|
||||
version="0.1.0",
|
||||
packages=find_packages(),
|
||||
description="Mjolnir Antispam",
|
||||
include_package_data=True,
|
||||
|
|
376
yarn.lock
376
yarn.lock
|
@ -99,13 +99,14 @@
|
|||
"@types/node" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express@^4.17.2":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
|
||||
integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
|
||||
"@types/express@^4.17.7":
|
||||
version "4.17.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.9.tgz#f5f2df6add703ff28428add52bdec8a1091b0a78"
|
||||
integrity sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
|
@ -153,6 +154,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.21.tgz#afcf913437eeb1f9e1994a62a03b1762ee4600d3"
|
||||
integrity sha512-fLwcSjMmDnjfk4FP7/QDiNzXSCEOGNvEe9eA6vaITmC784+Gm70wF7woaFQxUb2CpMjgLBhSPyhH0oIe1JS2uw==
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b"
|
||||
integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
|
||||
|
@ -198,12 +204,12 @@ accepts@~1.3.7:
|
|||
mime-types "~2.1.24"
|
||||
negotiator "0.6.2"
|
||||
|
||||
ajv@^6.5.5:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
|
||||
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
|
||||
ajv@^6.12.3:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
@ -286,11 +292,6 @@ array-flatten@1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||
|
||||
array-uniq@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
|
||||
|
||||
array-unique@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||
|
@ -356,7 +357,7 @@ base@^0.11.1:
|
|||
mixin-deep "^1.2.0"
|
||||
pascalcase "^0.1.1"
|
||||
|
||||
basic-auth@~2.0.0:
|
||||
basic-auth@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
|
||||
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
|
||||
|
@ -477,7 +478,7 @@ caseless@~0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
|
@ -486,14 +487,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
|
|||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
|
||||
|
@ -502,6 +495,14 @@ chalk@^4.0.0:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chokidar@3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6"
|
||||
|
@ -699,6 +700,11 @@ depd@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
depd@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
|
@ -724,38 +730,47 @@ diff@^4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
|
||||
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
|
||||
|
||||
dom-serializer@0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb"
|
||||
integrity sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==
|
||||
dom-serializer@^1.0.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
|
||||
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
domelementtype@1, domelementtype@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||
|
||||
domelementtype@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
|
||||
integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
|
||||
|
||||
domhandler@^2.3.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
||||
integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
|
||||
dependencies:
|
||||
domelementtype "1"
|
||||
domelementtype@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
|
||||
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
|
||||
|
||||
domutils@^1.5.1:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||
integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
|
||||
domhandler@^3.0.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a"
|
||||
integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==
|
||||
dependencies:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
domelementtype "^2.0.1"
|
||||
|
||||
domhandler@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e"
|
||||
integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==
|
||||
dependencies:
|
||||
domelementtype "^2.1.0"
|
||||
|
||||
domutils@^2.0.0:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
|
||||
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
|
||||
dependencies:
|
||||
dom-serializer "^1.0.1"
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
|
@ -780,11 +795,6 @@ encodeurl@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
entities@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||
|
||||
entities@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
||||
|
@ -962,10 +972,10 @@ extsprintf@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -1145,12 +1155,12 @@ har-schema@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
|
||||
|
||||
har-validator@~5.1.0:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
|
||||
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
|
||||
har-validator@~5.1.3:
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
|
||||
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
|
||||
dependencies:
|
||||
ajv "^6.5.5"
|
||||
ajv "^6.12.3"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
|
@ -1224,17 +1234,15 @@ htmlencode@^0.0.4:
|
|||
resolved "https://registry.yarnpkg.com/htmlencode/-/htmlencode-0.0.4.tgz#f7e2d6afbe18a87a78e63ba3308e753766740e3f"
|
||||
integrity sha1-9+LWr74YqHp45jujMI51N2Z0Dj8=
|
||||
|
||||
htmlparser2@^3.10.0:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
|
||||
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
|
||||
htmlparser2@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78"
|
||||
integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==
|
||||
dependencies:
|
||||
domelementtype "^1.3.1"
|
||||
domhandler "^2.3.0"
|
||||
domutils "^1.5.1"
|
||||
entities "^1.1.1"
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^3.1.1"
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^3.0.0"
|
||||
domutils "^2.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
http-errors@1.7.2:
|
||||
version "1.7.2"
|
||||
|
@ -1282,7 +1290,7 @@ inflight@^1.0.4:
|
|||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
@ -1651,36 +1659,16 @@ locate-path@^3.0.0:
|
|||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||
|
||||
lodash.escaperegexp@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
|
||||
|
||||
lodash.mergewith@^4.6.1:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
|
||||
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
||||
|
||||
lodash@4, lodash@^4.17.15:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
log-symbols@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
|
||||
|
@ -1699,12 +1687,12 @@ lowdb@^1.0.0:
|
|||
pify "^3.0.0"
|
||||
steno "^0.4.1"
|
||||
|
||||
lru-cache@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
||||
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
yallist "^4.0.0"
|
||||
|
||||
make-error@^1.1.1:
|
||||
version "1.3.5"
|
||||
|
@ -1723,24 +1711,24 @@ map-visit@^1.0.0:
|
|||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
matrix-bot-sdk@^0.5.4:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/matrix-bot-sdk/-/matrix-bot-sdk-0.5.4.tgz#8c26bef826bd0b3fc9b693c8d07b52c30d843fd7"
|
||||
integrity sha512-nUAE1WW7ast+Xsfux1+qBPmKSY6OG0RoZfeh0WfMP42JW6GRnNiY3hq7aiifomnJAMASZ6EwkdHS3ChWpIRYzg==
|
||||
matrix-bot-sdk@^0.5.9:
|
||||
version "0.5.9"
|
||||
resolved "https://registry.yarnpkg.com/matrix-bot-sdk/-/matrix-bot-sdk-0.5.9.tgz#f16018d8f6c3c6a2cd22e42a505973bcdad147a3"
|
||||
integrity sha512-KN7M/xg+SfgZD809gp/qlgy5sQB84PwpsZRhV2GLNIls2iOG3xes8dVnc9bxX8Cg1ueaIzuILtKdU4CFxcjzyA==
|
||||
dependencies:
|
||||
"@types/express" "^4.17.2"
|
||||
chalk "^3.0.0"
|
||||
"@types/express" "^4.17.7"
|
||||
chalk "^4.1.0"
|
||||
express "^4.17.1"
|
||||
glob-to-regexp "^0.4.1"
|
||||
hash.js "^1.1.7"
|
||||
htmlencode "^0.0.4"
|
||||
lowdb "^1.0.0"
|
||||
lru-cache "^5.1.1"
|
||||
mkdirp "^0.5.1"
|
||||
morgan "^1.9.1"
|
||||
request "^2.88.0"
|
||||
request-promise "^4.2.5"
|
||||
sanitize-html "^1.20.1"
|
||||
lru-cache "^6.0.0"
|
||||
mkdirp "^1.0.4"
|
||||
morgan "^1.10.0"
|
||||
request "^2.88.2"
|
||||
request-promise "^4.2.6"
|
||||
sanitize-html "^1.27.2"
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
|
@ -1850,6 +1838,11 @@ mkdirp@^0.5.1:
|
|||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
mkdirp@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
mocha@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.2.tgz#8e40d198acf91a52ace122cd7599c9ab857b29e6"
|
||||
|
@ -1880,16 +1873,16 @@ mocha@^7.1.2:
|
|||
yargs-parser "13.1.2"
|
||||
yargs-unparser "1.6.0"
|
||||
|
||||
morgan@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59"
|
||||
integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==
|
||||
morgan@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
|
||||
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
|
||||
dependencies:
|
||||
basic-auth "~2.0.0"
|
||||
basic-auth "~2.0.1"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
depd "~2.0.0"
|
||||
on-finished "~2.3.0"
|
||||
on-headers "~1.0.1"
|
||||
on-headers "~1.0.2"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -1941,11 +1934,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
number-is-nan@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
|
@ -2009,7 +1997,7 @@ on-finished@~2.3.0:
|
|||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
on-headers@~1.0.1:
|
||||
on-headers@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
|
||||
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
|
||||
|
@ -2040,6 +2028,11 @@ p-try@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
parse-srcset@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
|
||||
integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
@ -2090,10 +2083,10 @@ posix-character-classes@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
|
||||
|
||||
postcss@^7.0.5:
|
||||
version "7.0.18"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
|
||||
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
|
||||
postcss@^7.0.27:
|
||||
version "7.0.35"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
|
||||
integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
|
@ -2127,16 +2120,11 @@ proxy-addr@~2.0.5:
|
|||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.9.0"
|
||||
|
||||
psl@^1.1.24, psl@^1.1.28:
|
||||
psl@^1.1.28:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
|
||||
integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
|
||||
punycode@^2.1.0, punycode@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
|
@ -2177,15 +2165,6 @@ react-is@^16.8.4:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
|
||||
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==
|
||||
|
||||
readable-stream@^3.1.1:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
|
||||
integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readdirp@~3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839"
|
||||
|
@ -2211,27 +2190,27 @@ repeat-string@^1.6.1:
|
|||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
|
||||
|
||||
request-promise-core@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
|
||||
integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==
|
||||
request-promise-core@1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
|
||||
integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
lodash "^4.17.19"
|
||||
|
||||
request-promise@^4.2.5:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.5.tgz#186222c59ae512f3497dfe4d75a9c8461bd0053c"
|
||||
integrity sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==
|
||||
request-promise@^4.2.6:
|
||||
version "4.2.6"
|
||||
resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.6.tgz#7e7e5b9578630e6f598e3813c0f8eb342a27f0a2"
|
||||
integrity sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==
|
||||
dependencies:
|
||||
bluebird "^3.5.0"
|
||||
request-promise-core "1.1.3"
|
||||
request-promise-core "1.1.4"
|
||||
stealthy-require "^1.1.1"
|
||||
tough-cookie "^2.3.3"
|
||||
|
||||
request@^2.88.0:
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
|
||||
request@^2.88.2:
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.8.0"
|
||||
|
@ -2240,7 +2219,7 @@ request@^2.88.0:
|
|||
extend "~3.0.2"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.2"
|
||||
har-validator "~5.1.0"
|
||||
har-validator "~5.1.3"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
|
@ -2250,7 +2229,7 @@ request@^2.88.0:
|
|||
performance-now "^2.1.0"
|
||||
qs "~6.5.2"
|
||||
safe-buffer "^5.1.2"
|
||||
tough-cookie "~2.4.3"
|
||||
tough-cookie "~2.5.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
|
@ -2286,7 +2265,7 @@ safe-buffer@5.1.2:
|
|||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
|
||||
|
@ -2303,21 +2282,15 @@ safe-regex@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sanitize-html@^1.20.1:
|
||||
version "1.20.1"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.1.tgz#f6effdf55dd398807171215a62bfc21811bacf85"
|
||||
integrity sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==
|
||||
sanitize-html@^1.27.2:
|
||||
version "1.27.5"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.27.5.tgz#6c8149462adb23e360e1bb71cc0bae7f08c823c7"
|
||||
integrity sha512-M4M5iXDAUEcZKLXkmk90zSYWEtk5NH3JmojQxKxV371fnMh+x9t1rqdmXaGoyEHw3z/X/8vnFhKjGL5xFGOJ3A==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
htmlparser2 "^3.10.0"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.escaperegexp "^4.1.2"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.mergewith "^4.6.1"
|
||||
postcss "^7.0.5"
|
||||
srcset "^1.0.0"
|
||||
xtend "^4.0.1"
|
||||
htmlparser2 "^4.1.0"
|
||||
lodash "^4.17.15"
|
||||
parse-srcset "^1.0.2"
|
||||
postcss "^7.0.27"
|
||||
|
||||
semver@^5.3.0, semver@^5.7.0:
|
||||
version "5.7.1"
|
||||
|
@ -2459,14 +2432,6 @@ sprintf-js@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
srcset@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
||||
integrity sha1-pWad4StC87HV6D7QPHEEb8SPQe8=
|
||||
dependencies:
|
||||
array-uniq "^1.0.2"
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
|
@ -2552,13 +2517,6 @@ string.prototype.trimright@^2.1.0:
|
|||
define-properties "^1.1.3"
|
||||
function-bind "^1.1.1"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||
|
@ -2648,7 +2606,7 @@ toidentifier@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
tough-cookie@^2.3.3:
|
||||
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
||||
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
|
||||
|
@ -2656,14 +2614,6 @@ tough-cookie@^2.3.3:
|
|||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
|
||||
dependencies:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
ts-mocha@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-mocha/-/ts-mocha-7.0.0.tgz#f1549b48b46f53d7ae1dccbb26313c7879acb190"
|
||||
|
@ -2798,11 +2748,6 @@ use@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
utils-merge@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
|
@ -2860,30 +2805,17 @@ wrappy@1:
|
|||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xtend@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
|
||||
integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yargs-parser@13.1.2, yargs-parser@^13.1.1:
|
||||
version "13.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
|
||||
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^13.1.2:
|
||||
yargs-parser@13.1.2, yargs-parser@^13.1.1, yargs-parser@^13.1.2:
|
||||
version "13.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
|
||||
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
|
||||
|
|
Loading…
Reference in New Issue
Block a user