2023-06-24 00:23:53 +00:00
import requests
import json
from datetime import datetime
import OpenSSL . crypto
import base64
import hashlib
import uuid
import copy
import os
import secrets
2023-06-26 12:58:41 +00:00
import markdown
2023-06-24 00:23:53 +00:00
import fediseer . exceptions as e
from pythorhead import Lemmy
2023-09-13 22:26:03 +00:00
from mastodon import Mastodon
2023-06-24 00:23:53 +00:00
from loguru import logger
from fediseer . database import functions as database
2023-06-24 11:43:19 +00:00
from fediseer . consts import SUPPORTED_SOFTWARE , FEDISEER_VERSION
2023-06-24 00:23:53 +00:00
from fediseer . fediverse import get_admin_for_software
2023-09-13 22:26:03 +00:00
from fediseer import enums
2023-06-24 00:23:53 +00:00
2023-09-13 22:26:03 +00:00
class ActivityPubPM :
2023-06-24 00:23:53 +00:00
private_key = None
2023-09-13 22:26:03 +00:00
mastodon = None
2023-06-24 00:23:53 +00:00
def __init__ ( self ) :
with open ( ' private.pem ' , ' rb ' ) as file :
private_key_data = file . read ( )
self . private_key = OpenSSL . crypto . load_privatekey ( OpenSSL . crypto . FILETYPE_PEM , private_key_data )
self . document_core = {
" type " : " Create " ,
" actor " : " https://fediseer.com/api/v1/user/fediseer " ,
" @context " : [
" https://www.w3.org/ns/activitystreams " ,
" https: //w3id.org/security/v1 "
] ,
" object " : {
" attributedTo " : " https://fediseer.com/api/v1/user/fediseer " ,
} ,
}
2023-09-13 22:26:03 +00:00
self . mastodon = Mastodon (
access_token = ' pytooter_usercred.secret ' ,
api_base_url = f " https:// { os . environ [ ' MASTODON_INSTANCE ' ] } "
)
2023-06-24 00:23:53 +00:00
def send_pm_to_right_software ( self , message , username , domain , software ) :
software_map = {
" lemmy " : self . send_lemmy_pm ,
" mastodon " : self . send_mastodon_pm ,
2023-06-27 09:06:28 +00:00
" friendica " : self . send_mastodon_pm ,
2023-06-24 00:23:53 +00:00
" fediseer " : self . send_fediseer_pm ,
}
2023-09-13 22:26:03 +00:00
if software in software_map :
return software_map [ software ] ( message , username , domain )
raise e . BadRequest ( " This software does not have direct PM implemented. Please retry using a MASTODON pm_proxy setting. " )
2023-06-24 00:23:53 +00:00
def send_fediseer_pm ( self , message , username , domain ) :
document = copy . deepcopy ( self . document_core )
document [ " to " ] = [ f " https://lemmy.dbzer0.com/u/db0 " ]
2023-06-26 12:58:41 +00:00
document [ " object " ] [ " content " ] = markdown . markdown ( message )
2023-06-24 00:23:53 +00:00
document [ " object " ] [ " type " ] = " ChatMessage "
document [ " object " ] [ " mediaType " ] = " text/html "
document [ " object " ] [ " to " ] = [ f " https://lemmy.dbzer0.com/u/db0 " ]
document [ " object " ] [ " source " ] = {
" content " : message ,
" mediaType " : " text/markdown " ,
}
2023-06-26 12:58:41 +00:00
return self . send_pm ( document , domain )
2023-06-24 00:23:53 +00:00
def send_lemmy_pm ( self , message , username , domain ) :
document = copy . deepcopy ( self . document_core )
document [ " to " ] = [ f " https:// { domain } /u/ { username } " ]
2023-06-26 12:58:41 +00:00
document [ " object " ] [ " content " ] = markdown . markdown ( message )
2023-06-24 00:23:53 +00:00
document [ " object " ] [ " type " ] = " ChatMessage "
document [ " object " ] [ " mediaType " ] = " text/html "
document [ " object " ] [ " to " ] = [ f " https:// { domain } /u/ { username } " ]
document [ " object " ] [ " source " ] = {
" content " : message ,
" mediaType " : " text/markdown " ,
}
2023-06-26 12:58:41 +00:00
return self . send_pm ( document , domain )
2023-06-24 00:23:53 +00:00
def send_mastodon_pm ( self , message , username , domain ) :
document = copy . deepcopy ( self . document_core )
2023-06-26 12:58:41 +00:00
document [ " object " ] [ " content " ] = markdown . markdown ( message )
2023-06-24 00:23:53 +00:00
document [ " object " ] [ " type " ] = " Note "
2023-06-26 12:58:41 +00:00
document [ " object " ] [ " to " ] = f " https:// { domain } /users/ { username } "
document [ " object " ] [ " tag " ] = [
{
" type " : " Mention " ,
" to " : f " @ { username } " ,
" href " : f " https:// { domain } /users/ { username } "
}
]
return self . send_pm ( document , domain )
2023-06-24 00:23:53 +00:00
2023-06-26 12:58:41 +00:00
def send_pm ( self , document , domain ) :
2023-06-24 00:23:53 +00:00
document [ " id " ] = f " https://fediseer.com/ { uuid . uuid4 ( ) } "
document [ " object " ] [ " id " ] = f " https://fediseer.com/ { uuid . uuid4 ( ) } "
document [ " object " ] [ " published " ] = datetime . utcnow ( ) . strftime ( " % Y- % m- %d T % H: % M: % SZ " )
document = json . dumps ( document , indent = 4 )
digest = hashlib . sha256 ( document . encode ( ' utf-8 ' ) ) . digest ( )
encoded_digest = base64 . b64encode ( digest ) . decode ( ' utf-8 ' )
digest_header = " SHA-256= " + encoded_digest
date = datetime . utcnow ( ) . strftime ( ' %a , %d % b % Y % H: % M: % S GMT ' )
signed_string = f " (request-target): post /inbox \n host: { domain } \n date: { date } \n digest: { digest_header } "
signature = OpenSSL . crypto . sign ( self . private_key , signed_string . encode ( ' utf-8 ' ) , ' sha256 ' )
encoded_signature = base64 . b64encode ( signature ) . decode ( ' utf-8 ' )
header = f ' keyId= " https://fediseer.com/api/v1/user/fediseer " ,headers= " (request-target) host date digest " ,signature= " { encoded_signature } " '
headers = {
' Host ' : domain ,
' Date ' : date ,
' Signature ' : header ,
' Digest ' : digest_header ,
2023-06-24 11:43:19 +00:00
' Content-Type ' : ' application/ld+json; profile= " http://www.w3.org/ns/activitystreams " ' ,
" Sec-Fetch-Dest " : " document " ,
" Sec-Fetch-Mode " : " navigate " ,
" Sec-Fetch-Site " : " none " ,
" Sec-Fetch-User " : " ?1 " ,
" Sec-GPC " : " 1 " ,
" User-Agent " : f " Fediseer/ { FEDISEER_VERSION } " ,
2023-06-24 00:23:53 +00:00
}
url = f " https:// { domain } /inbox "
response = requests . post ( url , data = document , headers = headers )
return response . ok
2023-09-13 22:26:03 +00:00
def pm_new_api_key ( self , domain : str , username : str , software : str , requestor = None , proxy = None ) :
2023-06-24 00:23:53 +00:00
api_key = secrets . token_urlsafe ( 16 )
if requestor :
2023-09-13 22:26:03 +00:00
pm_content = f " user ' { requestor } ' has initiated an API Key reset for your domain { domain } on the [Fediseer](https://fediseer.com) \n \n The new API key is \n \n { api_key } \n \n **Please purge this message after storing the API key or use the Fediseer API to generate a new API key without PM** "
else :
pm_content = f " Your API Key for domain { domain } is \n \n { api_key } \n \n Use this to perform operations on the [Fediseer](https://fediseer.com). \n \n **Please purge this message after storing the API key or use the Fediseer API to generate a new API key without PM** "
if proxy == enums . PMProxy . MASTODON :
self . mastodon_proxy_pm ( pm_content , username , domain )
2023-06-24 00:23:53 +00:00
else :
2023-09-13 22:26:03 +00:00
if not self . send_pm_to_right_software (
message = pm_content ,
username = username ,
domain = domain ,
software = software
) :
raise e . BadRequest ( " API Key PM failed " )
2023-09-05 09:22:52 +00:00
return api_key
2023-09-13 22:26:03 +00:00
def pm_new_key_notification ( self , domain : str , username : str , software : str , requestor : str , proxy = None ) :
2023-09-05 09:22:52 +00:00
api_key = secrets . token_urlsafe ( 16 )
pm_content = f " user ' { requestor } ' has initiated an API Key reset for your domain { domain } on the [Fediseer](https://fediseer.com) \n \n The new API key was provided in the response already \n "
logger . info ( f " user ' { requestor } ' reset the API key for { username } @ { domain } on the response. " )
2023-09-13 22:26:03 +00:00
if proxy == enums . PMProxy . MASTODON :
self . mastodon_proxy_pm ( pm_content , username , domain )
else :
if not self . send_pm_to_right_software (
message = pm_content ,
username = username ,
domain = domain ,
software = software
) :
raise e . BadRequest ( " API Key PM failed " )
2023-06-24 00:23:53 +00:00
return api_key
2023-09-13 22:26:03 +00:00
def pm_new_proxy_switch ( self , new_proxy : enums . PMProxy , old_proxy : enums . PMProxy , instance : str , requestor : str ) :
if new_proxy == enums . PMProxy . NONE :
pm_content = f " user ' { requestor } ' has switched the fediseer messaging for { instance . domain } to not use a proxy (was { old_proxy . name } ). "
else :
pm_content = f " user ' { requestor } ' has switched the fediseer messaging for { instance . domain } to use a { new_proxy . name } proxy (was { old_proxy . name } ). "
logger . info ( f " user ' { requestor } ' changed instance pm_proxy setting from { old_proxy } to { new_proxy } for { instance . domain } . " )
admins = [ a . username for a in database . find_admins_by_instance ( instance ) ]
for admin_username in admins :
if instance . domain == " lemmy.dbzer0.com " and admin_username != ' db0 ' : # Debug
logger . debug ( f " skipping admin { admin_username } for debug " )
continue
for proxy in [ new_proxy , old_proxy ] :
if proxy == enums . PMProxy . MASTODON :
self . mastodon_proxy_pm ( pm_content , admin_username , instance . domain )
else :
if not self . send_pm_to_right_software (
message = pm_content ,
username = admin_username ,
domain = instance . domain ,
software = instance . software
) :
raise e . BadRequest ( " API Key PM failed " )
2023-06-24 00:23:53 +00:00
def pm_admins ( self , message : str , domain : str , software : str , instance ) :
2023-09-13 22:26:03 +00:00
proxy = None
2023-06-24 00:23:53 +00:00
admins = database . find_admins_by_instance ( instance )
if not admins :
2023-09-07 16:22:21 +00:00
try :
admins = get_admin_for_software ( software , domain )
except Exception as err :
2023-09-13 22:26:03 +00:00
if software not in SUPPORTED_SOFTWARE :
logger . warning ( f " Failed to figure out admins from { software } : { domain } " )
2023-09-07 16:22:21 +00:00
raise e . BadRequest ( f " Failed to retrieve admin list: { err } " )
2023-06-24 00:23:53 +00:00
else :
admins = [ a . username for a in admins ]
2023-09-13 22:26:03 +00:00
proxy = instance . pm_proxy
2023-06-24 00:23:53 +00:00
if not admins :
raise e . BadRequest ( f " Could not determine admins for { domain } " )
for admin_username in admins :
2023-09-13 22:26:03 +00:00
if proxy == enums . PMProxy . MASTODON :
self . mastodon_proxy_pm ( message , admin_username , domain )
else :
if not self . send_pm_to_right_software (
message = message ,
username = admin_username ,
domain = domain ,
software = software
) :
raise e . BadRequest ( " Admin PM Failed " )
2023-06-24 00:23:53 +00:00
2023-09-13 22:26:03 +00:00
def mastodon_proxy_pm ( self , message , username , domain ) :
try :
self . mastodon . status_post (
status = f " @ { username } @ { domain } { message } " ,
visibility = " direct " ,
)
except Exception as err :
raise e . BadRequest ( f " PM via Mastodon Proxy Failed: { err } " )
2023-06-24 00:23:53 +00:00
activitypub_pm = ActivityPubPM ( )