From 4e0814aa1ce016cfbeddc39e53c886fe34cf456d Mon Sep 17 00:00:00 2001 From: PC-Admin Date: Thu, 17 Aug 2023 22:10:25 +0800 Subject: [PATCH] cleanup - make redlight client use the SimpleHTTPClient available in ModuleApi instead of using twisted modules directly --- README.md | 5 +- redlight_client_module.py | 131 +++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 36278f1..a61102b 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ _"The red light means STOP!"_ +_CAUTION: This software is alpha quality and shouldn't be used by anybody._ + An advanced abuse mitigation tool. It's a Synapse module that allows server owners to either run a "redlight server", or to act as a "redlight client" to prevent their own users from accessing abusive rooms. It's designed to block child sexual abuse material (CSAM) and other abusive content on the Matrix network. This software attempts to resolve the complex problem of how to share pointers to rooms containing abusive content in order to block or report activity. These room lists are sensitive and sharing them can not only aid people in blocking this content but also direct bad actors to said content. @@ -51,7 +53,6 @@ Redlight is a community-driven project aimed at protecting the Matrix network's ## Roadmap 1) Get a basic prototype working. [DONE] -2) Use Synapses SimpleHttpClient instead of using twisted directly - +2) Use Synapses SimpleHttpClient instead of importing/using twisted directly - 3) Fix the hashing scheme and make it smarter - 4) Get a database on the redlight server - -5) \ No newline at end of file diff --git a/redlight_client_module.py b/redlight_client_module.py index 0a1dfed..e6e6855 100755 --- a/redlight_client_module.py +++ b/redlight_client_module.py @@ -5,13 +5,6 @@ import asyncio from typing import Union from synapse.module_api import ModuleApi, NOT_SPAM from synapse.api.errors import AuthError -from twisted.web.client import Agent, readBody -from twisted.web.http_headers import Headers -from twisted.web.iweb import IBodyProducer -from twisted.internet import reactor -from twisted.internet import defer -from twisted.web.iweb import IBodyProducer -from zope.interface import implementer from redlight_alert_bot import RedlightAlertBot # Setting up logging: @@ -27,23 +20,6 @@ logger.addHandler(file_handler) # Prevent logger's messages from propagating to the root logger. logger.propagate = False -# Define a custom producer to convert our JSON data for HTTP requests. -@implementer(IBodyProducer) -class _JsonProducer: - def __init__(self, data): - self._data = json.dumps(data).encode("utf-8") - self.length = len(self._data) - - def startProducing(self, consumer): - consumer.write(self._data) - return defer.succeed(None) - - def pauseProducing(self): - pass - - def stopProducing(self): - pass - class RedlightClientModule: def __init__(self, config: dict, api: ModuleApi): self._api = api @@ -57,7 +33,9 @@ class RedlightClientModule: self._redlight_endpoint = "https://" + config.get("redlight_server", "127.0.0.1:8008") + "/_matrix/loj/v1/abuse_lookup" # Redlight API token self._redlight_api_token = config.get("redlight_api_token", "") - self._agent = Agent(reactor) # Twisted agent for making HTTP requests. + + # Use the SimpleHttpClient from ModuleApi + self.http_client = api.http_client # Create an instance of the RedlightAlertBot self.bot = RedlightAlertBot(self._homeserver_url, self._redlight_alert_bot_token) # Adjust the homeserver and token as required @@ -88,59 +66,66 @@ class RedlightClientModule: hashed_room_id = self.hash_blake2(room) hashed_user_id = self.hash_blake2(user) - # Prepare the HTTP body. - body = _JsonProducer({ - "room_id_hash": hashed_room_id, - "user_id_hash": hashed_user_id, - "api_token": self._redlight_api_token - }) - - # Make the HTTP request to our redlight server. - response = await self._agent.request( - b"PUT", - self._redlight_endpoint.encode(), - Headers({'Content-Type': [b'application/json']}), - body - ) - - # Extract the response body. - response_body_bytes = await readBody(response) - response_body = response_body_bytes.decode("utf-8") - - # Log the response content - logger.info(f"Received response with code {response.code}. Content: {response_body}") - + # Replace the Agent request logic with the BaseHttpClient request logic try: - # Try to parse the response body as JSON. - response_json = json.loads(response_body) - except json.JSONDecodeError: - logger.error(f"Failed to decode response body: {response_body}") + response = await self.http_client.request( + "PUT", + self._redlight_endpoint, + data=json.dumps({ + "room_id_hash": hashed_room_id, + "user_id_hash": hashed_user_id, + "api_token": self._redlight_api_token + }).encode("utf-8"), + headers={'Content-Type': 'application/json'} + ) - # Handle the response based on its HTTP status code. - if response.code == 200: - logger.warn(f"User {user} not allowed to join restricted room. report_id: {response_json['report_id']} room_id: {room}.") - # Create the alert message - alert_message = f"WARNING: Incident detected! User {user} was attempting to access a restricted room. report_id: {response_json['report_id']}, For the room id please check your redlight logs." - # Start the synchronous send_alert_message method in a thread but don't await it - loop = asyncio.get_event_loop() - loop.run_in_executor(None, self.bot.send_alert_message, self._redlight_alert_room, alert_message) - # Throw a 403 error that the user will see - raise AuthError(403, "PERMISSION DENIED - This room violates server policy.") - elif response.code == 204: - logger.info(f"User {user} allowed to join room {room}.") - return NOT_SPAM # Allow the user to join. - else: - alert_message = f"Unexpected response code {response.code} with body {response_body}. Defaulting to allowing user {user} to join due to unexpected response code." - # Handle unexpected responses by alerting and logging them, and allowing the user to join as a fallback. - logger.error(alert_message) - loop = asyncio.get_event_loop() - loop.run_in_executor(None, self.bot.send_alert_message, self._redlight_alert_room, alert_message) - return NOT_SPAM + response_body = await response.content() # Fetch the content of the response -# Function to parse the module's configuration. + # Log the response content + logger.info(f"Received response with code {response.code}. Content: {response_body}. Response: {response}") + + # If HTTP response code is not 'No Content' + if response.code != 204: + try: + # Try to parse the response body as a JSON + response_json = json.loads(response_body) + except json.JSONDecodeError: + logger.error(f"Failed to decode response body: {response_body}") + + # Handle the response based on its HTTP status code + if response.code == 200: + logger.warn(f"User {user} not allowed to join restricted room. report_id: {response_json['report_id']} room_id: {room}.") + # Create the alert message + alert_message = f"WARNING: Incident detected! User {user} was attempting to access a restricted room. report_id: {response_json['report_id']}, For the room id please check your redlight logs." + # Start the synchronous send_alert_message method in a thread but don't await it + loop = asyncio.get_event_loop() + loop.run_in_executor(None, self.bot.send_alert_message, self._redlight_alert_room, alert_message) + # Throw a 403 error that the user will see + raise AuthError(403, "PERMISSION DENIED - This room violates server policy.") + elif response.code == 204: + logger.info(f"User {user} allowed to join room {room}.") + return NOT_SPAM # Allow the user to join + else: + alert_message = f"Unexpected response code {response.code} with body {response_body}. Defaulting to allowing user {user} to join due to unexpected response code." + # Handle unexpected responses by alerting and logging them, and allowing the user to join as a fallback + logger.error(alert_message) + loop = asyncio.get_event_loop() + loop.run_in_executor(None, self.bot.send_alert_message, self._redlight_alert_room, alert_message) + return NOT_SPAM + except AuthError as ae: + # This will catch the AuthError specifically and log it as an expected error + logger.info(f"User action denied with reason: {ae}") + raise # Re-raise the error after logging + except Exception as e: + # Handle any exceptions that arise from making the HTTP request + logger.error(f"HTTP request failed: {e}") + #return NOT_SPAM # Allow the user to join as a fallback + raise AuthError(403, "DEBUG: REQUEST FAILED") + +# Function to parse the module's configuration def parse_config(config: dict) -> dict: return config -# Factory function to create an instance of the RedlightClientModule. +# Factory function to create an instance of the RedlightClientModule def create_module(api: ModuleApi, config: dict) -> RedlightClientModule: return RedlightClientModule(config, api)