Skip to content

Commit e3cfd5a

Browse files
authored
Merge pull request #120 from svierne/feature_allowlist_blocklist
Implement allowlist and blocklist
2 parents 3632ef1 + 1c0537d commit e3cfd5a

File tree

4 files changed

+131
-1
lines changed

4 files changed

+131
-1
lines changed

matrix_reminder_bot/callbacks.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from matrix_reminder_bot.bot_commands import Command
1515
from matrix_reminder_bot.config import CONFIG
1616
from matrix_reminder_bot.errors import CommandError
17-
from matrix_reminder_bot.functions import send_text_to_room
17+
from matrix_reminder_bot.functions import is_allowed_user, send_text_to_room
1818
from matrix_reminder_bot.storage import Storage
1919

2020
logger = logging.getLogger(__name__)
@@ -71,6 +71,13 @@ async def message(self, room: MatrixRoom, event: RoomMessageText):
7171
if event.sender == self.client.user:
7272
return
7373

74+
# Ignore messages from disallowed users
75+
if not is_allowed_user(event.sender):
76+
logger.debug(
77+
f"Ignoring event {event.event_id} in room {room.room_id} as the sender {event.sender} is not allowed."
78+
)
79+
return
80+
7481
# Ignore broken events
7582
if not event.body:
7683
return
@@ -123,6 +130,11 @@ async def invite(self, room: MatrixRoom, event: InviteMemberEvent):
123130
"""Callback for when an invite is received. Join the room specified in the invite"""
124131
logger.debug(f"Got invite to {room.room_id} from {event.sender}.")
125132

133+
# Don't respond to invites from disallowed users
134+
if not is_allowed_user(event.sender):
135+
logger.debug(f"{event.sender} is not allowed, not responding to invite.")
136+
return
137+
126138
# Attempt to join 3 times before giving up
127139
for attempt in range(3):
128140
result = await self.client.join(room.room_id)

matrix_reminder_bot/config.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ def __init__(self):
4242

4343
self.timezone: str = ""
4444

45+
self.allowlist_enabled: bool = False
46+
self.allowlist_regexes: list[re.Pattern] = []
47+
48+
self.blocklist_enabled: bool = False
49+
self.blocklist_regexes: list[re.Pattern] = []
50+
4551
def read_config(self, filepath: str):
4652
if not os.path.isfile(filepath):
4753
raise ConfigError(f"Config file '{filepath}' does not exist")
@@ -122,6 +128,65 @@ def read_config(self, filepath: str):
122128
# Reminder configuration
123129
self.timezone = self._get_cfg(["reminders", "timezone"], default="Etc/UTC")
124130

131+
# Allowlist configuration
132+
allowlist_enabled = self._get_cfg(["allowlist", "enabled"], required=True)
133+
if not isinstance(allowlist_enabled, bool):
134+
raise ConfigError("allowlist.enabled must be a boolean value")
135+
self.allowlist_enabled = allowlist_enabled
136+
137+
self.allowlist_regexes = self._compile_regexes(
138+
["allowlist", "regexes"], required=True
139+
)
140+
141+
# Blocklist configuration
142+
blocklist_enabled = self._get_cfg(["blocklist", "enabled"], required=True)
143+
if not isinstance(blocklist_enabled, bool):
144+
raise ConfigError("blocklist.enabled must be a boolean value")
145+
self.blocklist_enabled = blocklist_enabled
146+
147+
self.blocklist_regexes = self._compile_regexes(
148+
["blocklist", "regexes"], required=True
149+
)
150+
151+
def _compile_regexes(
152+
self, path: list[str], required: bool = True
153+
) -> list[re.Pattern]:
154+
"""Compile a config option containing a list of strings into re.Pattern objects.
155+
156+
Args:
157+
path: The path to the config option.
158+
required: True, if the config option is mandatory.
159+
160+
Returns:
161+
A list of re.Pattern objects.
162+
163+
Raises:
164+
ConfigError:
165+
- If required is specified, but the config option does not exist.
166+
- If the config option is not a list of strings.
167+
- If the config option contains an invalid regular expression.
168+
"""
169+
170+
readable_path = ".".join(path)
171+
regex_strings = self._get_cfg(path, required=required) # raises ConfigError
172+
173+
if not isinstance(regex_strings, list) or (
174+
isinstance(regex_strings, list)
175+
and any(not isinstance(x, str) for x in regex_strings)
176+
):
177+
raise ConfigError(f"{readable_path} must be a list of strings")
178+
179+
compiled_regexes = []
180+
for regex in regex_strings:
181+
try:
182+
compiled_regexes.append(re.compile(regex))
183+
except re.error as e:
184+
raise ConfigError(
185+
f"'{e.pattern}' contained in {readable_path} is not a valid regular expression"
186+
)
187+
188+
return compiled_regexes
189+
125190
def _get_cfg(
126191
self,
127192
path: List[str],

matrix_reminder_bot/functions.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,29 @@ def make_pill(user_id: str, displayname: str = None) -> str:
115115
displayname = user_id
116116

117117
return f'<a href="https://matrix.to/#/{user_id}">{displayname}</a>'
118+
119+
120+
def is_allowed_user(user_id: str) -> bool:
121+
"""Returns if the bot is allowed to interact with the given user
122+
123+
Args:
124+
user_id: The MXID of the user.
125+
126+
Returns:
127+
True, if the bot is allowed to interact with the given user.
128+
"""
129+
allowed = not CONFIG.allowlist_enabled
130+
131+
if CONFIG.allowlist_enabled:
132+
for regex in CONFIG.allowlist_regexes:
133+
if regex.fullmatch(user_id):
134+
allowed = True
135+
break
136+
137+
if CONFIG.blocklist_enabled:
138+
for regex in CONFIG.blocklist_regexes:
139+
if regex.fullmatch(user_id):
140+
allowed = False
141+
break
142+
143+
return allowed

sample.config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,33 @@ reminders:
3636
# If not set, UTC will be used
3737
#timezone: "Europe/London"
3838

39+
# Restrict the bot to only respond to certain MXIDs
40+
allowlist:
41+
# Set to true to enable the allowlist
42+
enabled: false
43+
# A list of MXID regexes to be allowed
44+
# To allow a certain homeserver:
45+
# regexes: ["@[a-z0-9-_.]+:myhomeserver.tld"]
46+
# To allow a set of users:
47+
# regexes: ["@alice:someserver.tld", "@bob:anotherserver.tld"]
48+
# To allow nobody (same as blocking every MXID):
49+
# regexes: []
50+
regexes: []
51+
52+
# Prevent the bot from responding to certain MXIDs
53+
# If both allowlist and blocklist are enabled, blocklist entries takes precedence
54+
blocklist:
55+
# Set to true to enable the blocklist
56+
enabled: false
57+
# A list of MXID regexes to be blocked
58+
# To block a certain homeserver:
59+
# regexes: [".*:myhomeserver.tld"]
60+
# To block a set of users:
61+
# regexes: ["@alice:someserver.tld", "@bob:anotherserver.tld"]
62+
# To block absolutely everyone (same as allowing nobody):
63+
# regexes: [".*"]
64+
regexes: []
65+
3966
# Logging setup
4067
logging:
4168
# Logging level

0 commit comments

Comments
 (0)