cleanup - make redlight client use the SimpleHTTPClient available in ModuleApi instead of using twisted modules directly

This commit is contained in:
PC-Admin 2023-08-17 22:10:25 +08:00
parent 76b3115ade
commit 4e0814aa1c
2 changed files with 61 additions and 75 deletions

View File

@ -6,6 +6,8 @@
_"The red light means STOP!"_ _"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. 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. 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 ## Roadmap
1) Get a basic prototype working. [DONE] 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 - 3) Fix the hashing scheme and make it smarter -
4) Get a database on the redlight server - 4) Get a database on the redlight server -
5)

View File

@ -5,13 +5,6 @@ import asyncio
from typing import Union from typing import Union
from synapse.module_api import ModuleApi, NOT_SPAM from synapse.module_api import ModuleApi, NOT_SPAM
from synapse.api.errors import AuthError 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 from redlight_alert_bot import RedlightAlertBot
# Setting up logging: # Setting up logging:
@ -27,23 +20,6 @@ logger.addHandler(file_handler)
# Prevent logger's messages from propagating to the root logger. # Prevent logger's messages from propagating to the root logger.
logger.propagate = False 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: class RedlightClientModule:
def __init__(self, config: dict, api: ModuleApi): def __init__(self, config: dict, api: ModuleApi):
self._api = api 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" self._redlight_endpoint = "https://" + config.get("redlight_server", "127.0.0.1:8008") + "/_matrix/loj/v1/abuse_lookup"
# Redlight API token # Redlight API token
self._redlight_api_token = config.get("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 # Create an instance of the RedlightAlertBot
self.bot = RedlightAlertBot(self._homeserver_url, self._redlight_alert_bot_token) # Adjust the homeserver and token as required 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_room_id = self.hash_blake2(room)
hashed_user_id = self.hash_blake2(user) hashed_user_id = self.hash_blake2(user)
# Prepare the HTTP body. # Replace the Agent request logic with the BaseHttpClient request logic
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}")
try: try:
# Try to parse the response body as JSON. response = await self.http_client.request(
response_json = json.loads(response_body) "PUT",
except json.JSONDecodeError: self._redlight_endpoint,
logger.error(f"Failed to decode response body: {response_body}") 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. response_body = await response.content() # Fetch the content of the response
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
# 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: def parse_config(config: dict) -> dict:
return config 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: def create_module(api: ModuleApi, config: dict) -> RedlightClientModule:
return RedlightClientModule(config, api) return RedlightClientModule(config, api)