From 7eb02d2e8281e3ef0b39810987e99216a002474c Mon Sep 17 00:00:00 2001 From: David Teller Date: Wed, 21 Apr 2021 11:30:22 +0200 Subject: [PATCH] DSL: Let's get back to a DSL in which we send rules through Matrix, instead of a JSON server. --- dsl.ts | 360 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 199 insertions(+), 161 deletions(-) diff --git a/dsl.ts b/dsl.ts index 191690f..1296ac3 100644 --- a/dsl.ts +++ b/dsl.ts @@ -1,184 +1,222 @@ /** - * A format for sending spam-checker rules from a rule server to - * a synapse spam-checker. + * A format of messages designed to allow communication with a configurable + * spamchecker designed around the Synapse Spamcheck API. * - * The intended use is that the spam-checker will regularly + * - We call an implementation of the Synapse Spamcheck API that supports this + * protocol a *Checker*. + * - The Checker is configured to receive instructions in a set of rooms + * Room1, Room2, ... RoomN. This set of rooms does not change during the + * lifetime of the Checker. + * - Any member of Room1, ..., RoomN with authorization to send messages + * (aka a *Controller*) can send instructions to the Checker. We expect + * that the Controller will be an implementation of Mjölnir or Carlotta. * - * GET https://example.org/mjolnir/rules?since=timestamp - * - * And receive an update of the rules since timestamp. + * A the time of this writing, the Checker supports only simple rules. + * + * - A number of string properties are exposed to rules. + * - A rule is a string property and a literal/regexp. + * - During execution, if the regexp appears in the value of the string + * property, the message/user account creation request is rejected. + * - Otherwise, it is accepted. */ -interface Literal {} -interface Regexp {} +/** + * ------------------- Messages from controller ---------------------- + */ -/// A list of additions/removal. -/// -/// Additions are resolved *after* removals. -/// -/// Type parameter `T` is used as a phantom type to distinguish between -/// literal strings (`Literal`) or regexp strings (`Regexp`). -type Patch = { - /// Remove some or all items. - remove?: "clear" | string[], +type MessageFromControllerContent = { + type: "org.matrix.spamcheck.control", + content: + /// Update matchers on a single property. + StringRuleUpdate | ObjectRuleUpdate + /// Reset the Checker to remove all rules. + | ClearEverything + /// Request a snapshot of the current list of rules from the Checker. + | ShowMeTheseRules +}; - /// Add items. - add?: string[], +/** + * ---------------- Properties that the Checker can match against ------- + */ + +enum StringProperty { + /// The following properties point to strings. + "org.matrix.spamcheck.user_may_invite.inviter_user_id", + "org.matrix.spamcheck.user_may_invite.new_member_user_id", + "org.matrix.spamcheck.user_may_invite.room_id", + "org.matrix.spamcheck.user_may_create_room.user_id", + "org.matrix.spamcheck.user_may_create_room_alias.user_id", + "org.matrix.spamcheck.user_may_create_room_alias.desired_alias", + "org.matrix.spamcheck.user_may_publish_room.publisher_user_id", + "org.matrix.spamcheck.user_may_publish_room.room_id", + "org.matrix.spamcheck.check_username_for_spam.user_id", + "org.matrix.spamcheck.check_username_for_spam.display_name", + "org.matrix.spamcheck.check_username_for_spam.avatar_url", + "org.matrix.spamcheck.check_registration_for_spam_deny.maybe_user_name", + "org.matrix.spamcheck.check_registration_for_spam_deny.maybe_email", + "org.matrix.spamcheck.check_registration_for_spam_deny.user_agent", + "org.matrix.spamcheck.check_registration_for_spam_deny.ip", + "org.matrix.spamcheck.check_registration_for_spam_deny.maybe_auth_provider_id", + "org.matrix.spamcheck.check_registration_for_spam_shadowban.maybe_user_name", + "org.matrix.spamcheck.check_registration_for_spam_shadowban.maybe_email", + "org.matrix.spamcheck.check_registration_for_spam_shadowban.user_agent", + "org.matrix.spamcheck.check_registration_for_spam_shadowban.ip", + "org.matrix.spamcheck.check_registration_for_spam_shadowban.maybe_auth_provider_id", +} + +enum ObjectProperty { + /// The following properties point to objects. + "org.matrix.spamcheck.check_event_for_spam.event", } /** - * An update to an elementary spam rule, attempting to match a single value - * (e.g. a user_id) against a number of regexps. - * - * The rule will **reject** (i.e. consider as spam) if the value **contains** - * any of the regexps or literals. - * - * The rule will **accept** (i.e. let pass) otherwise. + * ---------------- Checker Matching primitives ------- */ -type Update = { - /// Any number of literals to add/remove. - /// - /// Whenever possible, prefer literals to regexps, as they are both - /// more faster and more memory-efficient. - literals?: Patch, - /// Any number of regexps to add/remove. - /// - /// The regexps will be or-ed and compiled spamcheck-side. - /// - /// These regexps MUST follow the specs of https://docs.python.org/3/library/re.html . - regexps?: Patch, - }; - -/** - * An antispam rule for `user_may_invite`. - */ -type InviteUpdates = { - /// The full user id of the inviter. - inviter_user_id: Update, - - /// The full user id if the potential new room member. - new_member_user_id: Update, - - /// The room id. - room_id: Update, -}; - -/** - * An antispam rule for `user_may_create_room`. - */ -type RoomCreateUpdates = { - /// The full user id of the room creator. - user_id: Update, -}; - -/** - * An antispam rule for `user_may_create_room_alias`. - */ -type AliasCreateUpdates = { - /// The full user id of the alias creator. - user_id: Update, - - /// The human-readable alias. - desired_alias: Update, -}; - -/** - * An antispam rule for `user_may_publish_room`. - */ -type PublishRoomUpdates = { - /// The full user id of the publisher. - publisher_user_id: Update, - - /// The room id. - room_id: Update, -}; - -/** - * An antispam rule for `check_username_for_spam`. - */ -type CheckUsernameForSpamUpdates = { - /// The full user id of the user. - user_id: Update, - - /// The display name of the user. - display_name: Update, - - /// The URL towards the avatar of the user. - avatar_url: Update, -}; - -/** - * An antispam rule for `check_registration_for_spam`. - */ -type CheckRegistrationForSpamUpdates = { - /// The username, if available. - maybe_user_name: Update, - - /// The email used for registration, if available. - maybe_email: Update, - - /// The list of user agents used during registration (possibly empty). - /// A registration will be considered spammy if any of the - /// user agents matches the rule. - user_agent: Update, - - /// The list of IPs used during registration (possibly empty). - /// A registration will be considered spammy if any of the - /// IPs matches the rule. - ip: Update, - - /// The auth provider, if available, e.g. "oidc", "saml", ... - maybe_auth_provider_id: Update -}; - -/** - * An antispam rule for `check_event_for_spam`. - */ -type CheckEventForSpamUpdates = { - /// A path of fields within the event object. +type Matcher = + /// Matching against a literal string. /// - /// e.g. `"content.formatted_body"` will return `event.content.formatted_body`. + /// If the string property contains this literal string, the rule **matches** + /// i.e. the spamcheck will **reject** the operation. Matches are case-insensitive. /// - /// If any of the fields is not present, the rule will **not** match (i.e. the - /// message will not be considered spam). - event: { [path: string]: Update }, -}; + /// # Example + /// + /// If literal `hailhydra` is a matcher for string property + /// `org.matrix.spamcheck.check_registration_for_spam_deny.maybe_user_name`, + /// user won't be able to register an account with the name 'hailHydra'. + { literal: string } + + /// Matching against a regexp. + /// + /// If the string property is matched by this regexp, the rule **matches** + /// i.e. the spamcheck will **reject** the operation. Matches are case-insensitive. + /// + /// # Example + /// + /// If regexp `h[ae]il.*hydra` is a matcher for string property + /// `org.matrix.spamcheck.check_registration_for_spam_deny.maybe_user_name`, + /// user won't be able to register an account with the name 'hEil hYdra'. + | { regexp: string } /** - * The complete list of rules for an instance of the spam-checker. + * ------------------- Snapshot of rules ---------------------- */ -type GlobalUpdate = { - /// If the rules match, user cannot invite other user. - user_may_invite?: InviteUpdates, - /// If the rules match, user cannot create room. - user_may_create_room?: RoomCreateUpdates, +/** + * The current matchers on a property that points to an object. + */ +type StringPropertyMatchers = { + property: StringProperty, + matchers: Matcher[], +} - /// If the rules match, user cannot create an alias for the room. - user_may_create_room_alias?: AliasCreateUpdates, +/** + * The current matchers on a property that points to an object. + */ +type ObjectPropertyMatchers = { + property: ObjectProperty, + matchers: { + [path: string]: Matcher[] + }, +} - /// If the rules match, user cannot make the room public. - user_may_publish_room?: PublishRoomUpdates, +/** + * A list of rules we wish to see. + */ +type RuleToShow = + /// All the matchers for a StringProperty. + StringProperty + /// All the matchers for a single path in a ObjectProperty. + | { + property: ObjectProperty, + path: string + } + /// All the matchers for all the paths in an ObjectProperty. + | ObjectProperty; - /// If the rules match, register user and immediately shadowban them. - check_registration_for_spam_shadowban?: CheckRegistrationForSpamUpdates, +/** + * Show current rules. + */ +type ShowMeTheseRules = { + "org.matrix.spamcheck.action": "snapshot", - /// If the rules of `check_registration_for_spam_shadowban` do NOT - /// match but these rules match, deny registration. - check_registration_for_spam_deny?: CheckRegistrationForSpamUpdates, - - /// If the rules match, deny registration, regardless of - /// the result of `check_registration_for_spam_shadowban` - /// and `check_registration_for_spam_deny`. - check_username_for_spam?: CheckUsernameForSpamUpdates, - - /// If the rules match, event will bounce. - check_event_for_spam?: CheckEventForSpamUpdates, - - /// The instant of the latest update, in milliseconds since the Unix epoch. + /// The list of rules to show: /// - /// This value is meant to be passed as argument when requesting - /// more recent update. - latest_update_ts: number, -}; + /// `*` for all rules + property: RuleToShow[] | "*"; +} + +/** + * A message with a snapshot of rules, as requested per `ShowMeTheseRules`. + */ +type RuleSnapshotMessage = { + type: "org.matrix.spamcheck.snapshot", + content: { + dump: (StringPropertyMatchers | ObjectPropertyMatchers)[] + } +} + + +const MESSAGE_RULE_DUMP_TYPE = "org.matrix.spamcheck.dump"; +/** + * ------------------- Updating rules ---------------------- + */ + +/// A list of additions/removal. +/// +/// Resolved in the following order: +/// +/// 1. remove; +/// 2. add; +type Patch = { + /// Remove some or all items. + remove?: "org.matrix.spamcheck.clear" | Matcher[], + + /// Add items. + add?: Matcher[], +} + + +/** + * An update to a single rule, for a property that points to a string. + */ +type StringRuleUpdate = { + "org.matrix.spamcheck.action": "update", + + /// The property affected by this rule change. + property: StringProperty, + + /// Matchers to add/remove. + patch: Patch, +} + +/** + * An update to a single rule, for a property that points to an object. + */ +type ObjectRuleUpdate = { + "org.matrix.spamcheck.action": "update", + + /// The property affected by this rule change. + /// + /// This property points to an object `o`. + property: ObjectProperty, + + /// The path to follow within object `o` to access + /// a string value. + /// + /// If the path is not defined or the value is not + /// a string, the message is considered a non-match, + /// i.e. it can pass. + path: string, + + /// Matchers to add/remove. + patch: Patch, +} + + +/** + * Remove all the rules. + */ +type ClearEverything = { + "org.matrix.spamcheck.action": "clear", +}