You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Comrad/comrad/backend/operators.py

403 lines
12 KiB
Python

4 years ago
# internal imports
4 years ago
import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..')))
from comrad import *
from comrad.backend import *
4 years ago
# BEGIN PHONE BOOK (in memory singleton mapping)
PHONEBOOK = {}
4 years ago
CALLBACKS = {}
4 years ago
# Factory constructor
def Comrad(name=None,pubkey=None,*x,**y):
if issubclass(type(name),Operator): return name
if name and not pubkey and type(name)==bytes:
pubkey=b64enc(name)
4 years ago
name=None
4 years ago
from comrad.backend.the_operator import TheOperator
from comrad.backend.the_telephone import TheTelephone
4 years ago
from comrad.backend.comrades import ComradX
4 years ago
global PHONEBOOK
# already have?
if not name and not pubkey: return ComradX()
4 years ago
if name in PHONEBOOK: return PHONEBOOK[name]
pk64 = None if not pubkey else b64enc(pubkey)
if pk64 in PHONEBOOK: return PHONEBOOK[pk64]
4 years ago
global CALLBACKS
callbacks = y.get('callbacks',{})
callbacks = callbacks if callbacks else CALLBACKS
CALLBACKS = callbacks
# print(f'finding Comrad {name} / {pubkey} for the first time!')
4 years ago
# operator?
if name==OPERATOR_NAME:
4 years ago
commie = TheOperator(callbacks=callbacks) #(*x,**y)
4 years ago
if name==TELEPHONE_NAME:
4 years ago
commie = TheTelephone(callbacks=callbacks) #(*x,**y)
4 years ago
else:
4 years ago
# print('booting new commie')
commie = ComradX(name,*x,**y)
4 years ago
4 years ago
4 years ago
# print('found!',name,PHONEBOOK[name],PHONEBOOK[name].keychain())
4 years ago
PHONEBOOK[name] = commie
pubkey=commie.find_pubkey(name)
if pubkey: PHONEBOOK[pubkey.data_b64] = commie
4 years ago
4 years ago
return commie
4 years ago
4 years ago
4 years ago
# from comrad.constants import OPERATOR_ROUTES
4 years ago
class Operator(Keymaker):
4 years ago
ROUTES = [
4 years ago
'register_new_user',
'login',
'deliver_msg',
'introduce',
4 years ago
'deliver_post',
'get_updates'
4 years ago
]
4 years ago
4 years ago
4 years ago
def __eq__(self,other):
4 years ago
if not self.pubkey or not other.pubkey: return False
return self.pubkey.data == other.pubkey.data
4 years ago
def __init__(self,
name=None,
pubkey=None,
keychain = {},
path_crypt_keys=PATH_CRYPT_CA_KEYS,
4 years ago
path_crypt_data=PATH_CRYPT_CA_DATA,
callbacks={},
getpass_func=None
):
4 years ago
global PHONEBOOK
4 years ago
# call Keymaker's intro
super().__init__(
name=name,
keychain=keychain,
path_crypt_keys=path_crypt_keys,
4 years ago
path_crypt_data=path_crypt_data,
callbacks=callbacks,
getpass_func=getpass_func
)
4 years ago
# add to phonebook
4 years ago
if name:
PHONEBOOK[name]=self
pubkey=self.find_pubkey(name)
if pubkey:
PHONEBOOK[pubkey.data_b64]=self
4 years ago
self._inbox_crypt=None
4 years ago
4 years ago
4 years ago
@property
def phone(self):
from comrad.backend.the_telephone import TheTelephone
4 years ago
if type(self)==TheTelephone: return self
4 years ago
if hasattr(self,'_phone'): return self._phone
4 years ago
4 years ago
global TELEPHONE,TELEPHONE_KEYCHAIN
if TELEPHONE: return TELEPHONE
4 years ago
4 years ago
self._phone=TELEPHONE=TheTelephone(callbacks=self._callbacks)
4 years ago
4 years ago
return TELEPHONE
@property
def op(self):
from comrad.backend.the_operator import TheOperator
4 years ago
if type(self)==TheOperator: return self
if hasattr(self,'_op'): return self._op
4 years ago
global OPERATOR,OPERATOR_KEYCHAIN
if OPERATOR: return OPERATOR
4 years ago
4 years ago
self._op=OPERATOR=TheOperator(callbacks=self._callbacks)
4 years ago
4 years ago
return OPERATOR
4 years ago
4 years ago
def compose_msg_to(self,msg,another):
4 years ago
if not self.privkey or not self.pubkey:
raise ComradException('I appear not yet to have an encryption keypair.',self,self.name,self.pubkey,self.privkey,self.keychain())
4 years ago
if not another.name or not another.pubkey:
4 years ago
self.log(f'I {self} failed to compose a message to {another} ?')
raise ComradException('I do not know the Comrad I am writing to.')
4 years ago
4 years ago
# otherwise create msg
4 years ago
frompub = self.pubkey.data if hasattr(self.pubkey,'data') else self.pubkey
topub = another.pubkey.data if hasattr(another.pubkey,'data') else another.pubkey
4 years ago
msg_d = {
4 years ago
'from':frompub,
4 years ago
'from_name':self.name,
4 years ago
'to':topub,
4 years ago
'to_name':another.name,
'msg':msg,
'timestamp':time.time()
4 years ago
}
4 years ago
self.log(f'I am {self} packaging a message to {another}: {msg_d}')
from comrad.backend.messages import Message
4 years ago
4 years ago
msg_obj = Message(msg_d)
4 years ago
4 years ago
# encrypt!
4 years ago
# msg_obj.encrypt()
4 years ago
return msg_obj
4 years ago
4 years ago
4 years ago
def __repr__(self):
clsname=(type(self)).__name__
4 years ago
#name = clsname+' '+
4 years ago
name = '@'+ (self.name if self.name else '?') # if self.name!=clsname else clsname
4 years ago
# try:
# keystr= 'on device: ' + ('+'.join(self.top_keys) if self.pubkey else '')
# except TypeError:
# keystr=''
# # if self.pubkey:
keystr=''
4 years ago
if False:
4 years ago
pubk=self.pubkey_b64.decode()
4 years ago
pubk=pubk[-5:]
4 years ago
pubk = f' ({pubk})'# if pubk else ''
else:
pubk = ''
4 years ago
return f'{name}' #' ({keystr})'
4 years ago
4 years ago
4 years ago
4 years ago
def route_msg(self,msg_obj,reencrypt=True,new_data=None):
4 years ago
# decrypt
4 years ago
# self.log('got msg_obj!',msg_obj)
4 years ago
if msg_obj.is_encrypted:
msg_obj.decrypt()
4 years ago
# try route
4 years ago
if msg_obj.route:
4 years ago
data,route = msg_obj.data, msg_obj.route
4 years ago
if not hasattr(self,route) or route not in self.ROUTES:
raise ComradException(f'Not a valid route!: {route}')
4 years ago
# route it!
4 years ago
self.log(f'Routing msg to {self}.{route}():\n\n{dict_format(msg_obj.data,tab=4)}')
4 years ago
func = getattr(self,route)
# new_data = func(**data)
new_data = func(msg_obj)
4 years ago
self.log(f'New data was received back from {self}.{route}() route:\b\b{new_data}')
4 years ago
msg_obj.msg = msg_obj.msg_d['msg'] = new_data
4 years ago
# try passing it on?
if msg_obj.has_embedded_msg:
new_data = self.route_msg(msg_obj.msg)
4 years ago
msg_obj.msg = msg_obj.msg_d['msg'] = new_data
4 years ago
4 years ago
if not new_data or not reencrypt:
4 years ago
# end of the line?
return msg_obj
4 years ago
# time to turn around and encrypt
4 years ago
# @unsure?``
4 years ago
# from comrad.backend.comrades import Comrad
# if self != self.phone and type(self)!=Comrad:
4 years ago
# # if client, let the request rest
# return msg_obj
4 years ago
4 years ago
# # if remote operator, keep going?
# self.log('time to flip msg around and return to sender. v1:',msg_obj,dict_format(msg_obj.msg_d))#,new_data,reencrypt,msg_obj.route)
4 years ago
4 years ago
# new_msg_obj = msg_obj.to_whom.compose_msg_to(
# msg=new_data,
# another=msg_obj.from_whom
# ) #msg_obj.mark_return_to_sender()
# self.log('returning to sender as:',new_msg_obj)
new_msg_obj = msg_obj.return_to_sender(new_data)
4 years ago
# encrypt
4 years ago
if reencrypt:
4 years ago
# self.log('reencrypting v1',new_msg_obj)
4 years ago
new_msg_obj.encrypt()
4 years ago
# self.log('reencrypting v2',new_msg_obj)
4 years ago
4 years ago
4 years ago
return new_msg_obj
4 years ago
async def ring_ring(self,msg,to_whom,get_resp_from=None,route=None,caller=None):
4 years ago
# ring ring
from comrad.cli.artcode import ART_PHONE_SM1
4 years ago
import textwrap as tw
4 years ago
4 years ago
if caller!=self:
from comrad.cli.artcode import ART_PHONE_SM1
4 years ago
self.log(f'ring ring! I the {self} have received a message from {caller},\n which I will now encrypt and send along to {to_whom}.\n {ART_PHONE_SM1} ')
4 years ago
else:
4 years ago
pass
4 years ago
self.log(f'I ({self}) will now compose and send an encrypted message to {to_whom}.')
4 years ago
4 years ago
if route and type(msg)==dict and not ROUTE_KEYNAME in msg:
msg[ROUTE_KEYNAME] = route
4 years ago
4 years ago
# get encr msg obj
4 years ago
4 years ago
msg_obj = self.compose_msg_to(
msg,
4 years ago
to_whom
4 years ago
)
4 years ago
self.log(f'Here is the message I will now encrypt and to send to {to_whom}:\n\n {dict_format(msg_obj.msg,tab = 2)}')
4 years ago
# encrypting
msg_obj.encrypt()
4 years ago
# pass through the telephone wire by the get_resp_from function
4 years ago
if not get_resp_from: get_resp_from=to_whom.ring_ring
resp_msg_obj = await get_resp_from(msg_obj.msg_d,caller=caller)
self.log('resp_msg_obj <-',str(resp_msg_obj))
4 years ago
if not resp_msg_obj or type(resp_msg_obj)==dict and not resp_msg_obj.get('success'):
return {'status':'I cannot reach the @Operator. Perhaps she has stepped out. Please hang up and try again later.','success':False,'res':resp_msg_obj}
# exit()
4 years ago
4 years ago
# decrypt
4 years ago
if resp_msg_obj.is_encrypted:
resp_msg_obj.decrypt()
4 years ago
# route back?
4 years ago
new_msg_obj=self.route_msg(resp_msg_obj,reencrypt=False)
new_msg = new_msg_obj.msg
# print('new_msg_obj',new_msg_obj)
# print('new_msg',new_msg)
return new_msg
4 years ago
4 years ago
def pronto_pronto(self, msg_obj):
4 years ago
self.log(f'''*ring *ring*
...
4 years ago
{self}: pronto?
4 years ago
{msg_obj.from_whom}: Ciao ciao. I have a message for you:\n{msg_obj}\n''')
4 years ago
4 years ago
return self.route_msg(msg_obj,reencrypt=True)
4 years ago
4 years ago
## inboxes?
4 years ago
def get_inbox_crypt(self,
4 years ago
crypt=None,
uri=None,
4 years ago
prefix='/inbox/'):
4 years ago
if not crypt: crypt=self.crypt_data
if not uri: uri=self.uri
4 years ago
prefix+=self.name+'/'
4 years ago
self.log(f'I am {self.name} and my uri is {self.uri}.')
4 years ago
inbox_crypt = CryptList(
crypt=self.crypt_data,
4 years ago
keyname=uri,
4 years ago
prefix=prefix,
4 years ago
)
4 years ago
self.log('--> inbox crypt:',uri,prefix,inbox_crypt.values)
return inbox_crypt
4 years ago
def delete_post(self,post_id,**y):
return self.delete_posts([post_id],**y)
def delete_posts(self,
post_ids,
inbox_uri=None,
inbox_prefix='/inbox/',
post_prefix='/post/'):
# delete from posts
deleted_post_ids=[]
for post_id in post_ids:
4 years ago
self.log('deleting post id?',post_id)
del_res = self.crypt_data.delete(
4 years ago
post_id,
4 years ago
prefix=f'{post_prefix}{self.name}/',
)
#del_res = 1
self.log('del_res =',del_res)
if del_res:
4 years ago
deleted_post_ids.append(post_id)
self.log('deleted_post_ids',deleted_post_ids,'...')
res = {
4 years ago
'deleted':deleted_post_ids,
4 years ago
}
# delete from inbox
4 years ago
#inbox_uri = self.uri if not inbox_uri else inbox_uri
# if inbox_uri:
# inbox_db=self.get_inbox_crypt(
# uri=inbox_uri,
# )
# res['deleted_from_inbox']=inbox_db.remove(
# deleted_post_ids
# )
inbox_db=self.get_inbox_crypt(
prefix='/inbox/'
)
self.log('inbox!?',inbox_db.values)
res['deleted_from_inbox']=inbox_db.remove(post_ids)
4 years ago
self.log('-->',res)
4 years ago
res['success']=True
4 years ago
res['status']=f'Deleted {len(deleted_post_ids)} posts.'
4 years ago
return res
@property
def inbox_db(self):
if not hasattr(self,'_inbox_db'):
self._inbox_db=self.get_inbox_crypt(
prefix='/inbox/'
)
return self._inbox_db
@property
def inbox_unread_db(self):
if not hasattr(self,'_inbox_unread_db'):
self._inbox_unread_db=self.get_inbox_crypt(
prefix='/inbox/unread/',
)
return self._inbox_unread_db
@property
def inbox_read_db(self):
if not hasattr(self,'_inbox_read_db'):
self._inbox_read_db=self.get_inbox_crypt(
prefix='/inbox/read/',
)
return self._inbox_read_db