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