diff --git a/pyproject.toml b/pyproject.toml index 0e2d9ab..0575425 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "hatchling.build" [project] name = "synapse-invite-checker" -description = 'Synapse module to handle TIM contact management and invite permissions' +description = 'Synapse module to invite permissions' readme = "README.md" requires-python = ">=3.11" license = "AGPL-3.0-only" keywords = [] authors = [ - { name = "Nicolas Werner", email = "n.werner@famedly.com" }, + { name = "Nils Büchner", email = "n.buechner@ubuntu.com" }, ] classifiers = [ "Development Status :: 4 - Beta", @@ -21,9 +21,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "twisted", - "cachetools", - "asyncache", + "matrix-synapase" ] version = "0.2.0" @@ -31,49 +29,3 @@ version = "0.2.0" Documentation = "https://github.com/famedly/synapse-invite-checker#synapse-invite-checker" Issues = "https://github.com/famedly/synapse-invite-checker/-/issues" Source = "https://github.com/famedly/synapse-invite-checker/" - -[tool.hatch.envs.default] -dependencies = [ - "black", - "pytest", - "pytest-cov", - "mock", - "matrix-synapse" # we don't depend on synapse directly to prevent pip from pulling the wrong synapse, when we just want to install the module -] -[tool.hatch.envs.default.scripts] -cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=synapse_invite_checker --cov=tests" -format = "black ." -# For CI use -head-cov = "pytest --cov-report=lcov:../head.lcov --cov-config=pyproject.toml --cov=synapse_invite_checker --cov=tests" -base-cov = "pytest --cov-report=lcov:../base.lcov --cov-config=pyproject.toml --cov=synapse_invite_checker --cov=tests" - -[tool.hatch.envs.ci.scripts] -format = "black --check ." - -[tool.coverage.run] -branch = true -parallel = true -omit = ["tests/*"] - -[tool.ruff] -target-version = "py311" - -[tool.ruff.lint] -ignore = [ - "FBT001", - "FBT002", - "TRY002", - "TRY003", - "PLW0603", - "N802" -] - -[tool.ruff.per-file-ignores] -"tests/*" = ["RUF012", "S101", "PLR2004", "N803", "SLF001", "S105"] - -[tool.coverage.report] -exclude_lines = [ - "no cov", - "if __name__ == .__main__.:", - "if TYPE_CHECKING:", -] diff --git a/synapse_invite_checker/invite_checker.py b/synapse_invite_checker/invite_checker.py index be8b69c..27eacbd 100644 --- a/synapse_invite_checker/invite_checker.py +++ b/synapse_invite_checker/invite_checker.py @@ -1,7 +1,7 @@ import treq import json +import time from twisted.internet.defer import inlineCallbacks, returnValue -from cachetools import TTLCache from synapse.module_api import ModuleApi, errors from synapse.types import UserID import logging @@ -28,9 +28,9 @@ class InviteChecker: self.use_allowlist = self.config.use_allowlist self.use_blocklist = self.config.use_blocklist - # Cache for allowlist/blocklist (TTL = 60 seconds) - self.cache = TTLCache(maxsize=1, ttl=60) - + # Cache for allowlist/blocklist + self.cache_expiry_time = 60 # Cache expiry in seconds + self.cache_timestamp = 0 # Timestamp of when the cache was last updated self.blocklist = set() self.allowlist = set() self.allow_all_invites_on_error = False # Flag to allow all invites if the JSON fetch fails @@ -62,7 +62,6 @@ class InviteChecker: logger.error(f"Error while fetching JSON: {str(e)}") returnValue(None) - @inlineCallbacks def update_blocklist_allowlist(self): """Fetch and update the blocklist and allowlist from the URLs.""" @@ -77,8 +76,8 @@ class InviteChecker: self.blocklist = set(json_data.get('blocklist', [])) self.allowlist = set(json_data.get('allowlist', [])) - # Cache the data - self.cache['blocklist_allowlist'] = (self.blocklist, self.allowlist) + # Update cache timestamp + self.cache_timestamp = time.time() logger.info(f"Updated allowlist: {self.allowlist}") logger.info(f"Updated blocklist: {self.blocklist}") else: @@ -89,15 +88,17 @@ class InviteChecker: @inlineCallbacks def get_blocklist_allowlist(self): """Return cached blocklist/allowlist or fetch new data if cache is expired.""" + current_time = time.time() + + # Check if cache is expired + if current_time - self.cache_timestamp > self.cache_expiry_time: + yield self.update_blocklist_allowlist() + if self.allow_all_invites_on_error: logger.info("Skipping allowlist/blocklist checks because of previous JSON fetch failure.") returnValue((set(), set())) # Return empty sets to indicate no checks should be applied - try: - returnValue(self.cache['blocklist_allowlist']) - except KeyError: - yield self.update_blocklist_allowlist() - returnValue(self.cache['blocklist_allowlist']) + returnValue((self.blocklist, self.allowlist)) @inlineCallbacks def user_may_invite(self, inviter: str, invitee: str, room_id: str): @@ -115,14 +116,14 @@ class InviteChecker: # allowlist check (if enabled) if self.use_allowlist: - logger.info(f"allowlist enabled. Checking domain: {inviter_domain}") + logger.info(f"Allowlist enabled. Checking domain: {inviter_domain}") if inviter_domain in allowlist: logger.info(f"Invite allowed: {inviter_domain} is in the allowlist") returnValue("NOT_SPAM") # Allow if the domain is in the allowlist # blocklist check (if enabled) if self.use_blocklist: - logger.info(f"blocklist enabled. Checking domain: {inviter_domain}") + logger.info(f"Blocklist enabled. Checking domain: {inviter_domain}") if inviter_domain in blocklist: logger.info(f"Invite blocked: {inviter_domain} is in the blocklist") returnValue(errors.Codes.FORBIDDEN) # Deny if the domain is in the blocklist