2023-08-10 15:30:40 -04:00
import logging
import hashlib
2023-08-12 12:53:50 -04:00
import json
2023-08-14 10:32:38 -04:00
import asyncio
2023-08-13 10:29:02 -04:00
from synapse . module_api import ModuleApi , NOT_SPAM
2023-08-10 15:30:40 -04:00
from synapse . api . errors import AuthError
2023-08-14 10:41:02 -04:00
from redlight_alert_bot import RedlightAlertBot
2023-08-10 15:30:40 -04:00
2023-08-13 11:32:06 -04:00
# Setting up logging:
2023-08-13 10:59:19 -04:00
file_handler = logging . FileHandler ( ' /var/log/matrix-synapse/redlight.log ' )
file_handler . setLevel ( logging . INFO )
formatter = logging . Formatter ( ' %(asctime)s - %(name)s - %(levelname)s - %(message)s ' )
file_handler . setFormatter ( formatter )
2023-08-10 15:30:40 -04:00
logger = logging . getLogger ( __name__ )
2023-08-13 10:59:19 -04:00
logger . setLevel ( logging . INFO )
logger . addHandler ( file_handler )
2023-08-13 11:32:06 -04:00
# Prevent logger's messages from propagating to the root logger.
2023-08-13 10:59:19 -04:00
logger . propagate = False
2023-08-10 15:30:40 -04:00
class RedlightClientModule :
def __init__ ( self , config : dict , api : ModuleApi ) :
self . _api = api
2023-08-14 10:32:38 -04:00
# Your homeserver's URL
self . _homeserver_url = " https:// " + config . get ( " homeserver_url " , " 127.0.0.1:8008 " )
# The API token of your redlight bot user
2023-08-14 10:41:02 -04:00
self . _redlight_alert_bot_token = config . get ( " redlight_alert_bot_token " , " " )
2023-08-14 10:32:38 -04:00
# The alert room your redlight bot will post too
self . _redlight_alert_room = config . get ( " redlight_alert_room " , " " )
# Redlight server endpoint, where we'll check if the room/user combination is allowed.
self . _redlight_endpoint = " https:// " + config . get ( " redlight_server " , " 127.0.0.1:8008 " ) + " /_matrix/loj/v1/abuse_lookup "
2023-08-15 18:41:29 -04:00
# Redlight API token
self . _redlight_api_token = config . get ( " redlight_api_token " , " " )
2023-08-17 10:10:25 -04:00
# Use the SimpleHttpClient from ModuleApi
self . http_client = api . http_client
2023-08-10 15:30:40 -04:00
2023-08-14 10:41:02 -04:00
# Create an instance of the RedlightAlertBot
self . bot = RedlightAlertBot ( self . _homeserver_url , self . _redlight_alert_bot_token ) # Adjust the homeserver and token as required
2023-08-14 10:32:38 -04:00
2023-08-10 15:30:40 -04:00
logger . info ( " RedLightClientModule initialized. " )
2023-08-14 10:41:02 -04:00
logger . info ( f " Redlight bot user token: { self . _redlight_alert_bot_token } " )
2023-08-14 10:32:38 -04:00
logger . info ( f " Redlight alert room: { self . _redlight_alert_room } " )
logger . info ( f " Redlight server endpoint set to: { self . _redlight_endpoint } " )
2023-08-10 15:30:40 -04:00
2023-08-13 11:32:06 -04:00
# Register the user_may_join_room function to be called by Synapse before a user joins a room.
2023-08-10 15:30:40 -04:00
api . register_spam_checker_callbacks (
user_may_join_room = self . user_may_join_room
)
@staticmethod
2023-08-17 06:35:54 -04:00
def hash_blake2 ( data : str ) - > str :
2023-08-17 07:02:39 -04:00
""" Hash the data with BLAKE2 for upstream comparison. """
room_id_hash = hashlib . blake2b ( data . encode ( ) , digest_size = 32 ) . hexdigest ( ) # Use hexdigest() instead of digest()
2023-08-17 06:35:54 -04:00
return room_id_hash
2023-08-10 15:30:40 -04:00
async def user_may_join_room (
self , user : str , room : str , is_invited : bool
2023-08-17 12:15:25 -04:00
) - > NOT_SPAM :
2023-08-10 15:30:40 -04:00
logger . info ( f " User { user } is attempting to join room { room } . Invitation status: { is_invited } . " )
2023-08-13 11:32:06 -04:00
# Double-hash the room and user IDs.
2023-08-17 06:35:54 -04:00
hashed_room_id = self . hash_blake2 ( room )
hashed_user_id = self . hash_blake2 ( user )
2023-08-10 15:30:40 -04:00
2023-08-17 10:10:25 -04:00
# Replace the Agent request logic with the BaseHttpClient request logic
2023-08-13 10:29:02 -04:00
try :
2023-08-17 10:10:25 -04:00
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 ' }
)
response_body = await response . content ( ) # Fetch the content of the response
# 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
2023-08-10 15:30:40 -04:00
def parse_config ( config : dict ) - > dict :
return config
2023-08-17 10:10:25 -04:00
# Factory function to create an instance of the RedlightClientModule
2023-08-10 15:30:40 -04:00
def create_module ( api : ModuleApi , config : dict ) - > RedlightClientModule :
2023-08-12 12:53:50 -04:00
return RedlightClientModule ( config , api )