def __split_group_message(self, msg: ReliableMessage, members: List[ID]) -> Optional[Content]: """ Split group message for each member """ messages = msg.split(members=members) success_list = [] failed_list = [] for item in messages: if item.delegate is None: item.delegate = msg.delegate if self.__forward_group_message(msg=item): success_list.append(item.receiver) else: failed_list.append(item.receiver) response = ReceiptCommand(message='Group message delivering') if len(success_list) > 0: response['success'] = ID.revert(success_list) if len(failed_list) > 0: response['failed'] = ID.revert(failed_list) # failed to get keys for this members, # query from sender by invite members sender = msg.sender group = msg.receiver cmd = GroupCommand.invite(group=group, members=failed_list) self.send_content(sender=None, receiver=sender, content=cmd) return response
def __load_records(self) -> Dict[str, ID]: path = self.__path() self.info('Loading ANS records from: %s' % path) dictionary = {} text = self.read_text(path=path) if text is not None: lines = text.splitlines() for record in lines: pair = record.split('\t') if len(pair) != 2: self.error('invalid record: %s' % record) continue k = pair[0] v = pair[1] dictionary[k] = ID.parse(identifier=v) # # Reserved names # dictionary['all'] = EVERYONE dictionary[EVERYONE.name] = EVERYONE dictionary[ANYONE.name] = ANYONE dictionary['owner'] = ANYONE dictionary['founder'] = ID.parse( identifier='moky@4DnqXWdTV8wuZgfqSCX9GjE2kNq7HJrUgQ' ) # 'Albert Moky' return dictionary
def __init__(self): super().__init__() # caches self.__ids = {} self.__private_keys = {} self.__metas = {} self.__profiles = {} self.__users = {} # load built-in users self.__load_user(identifier=ID.parse(self.HULK)) self.__load_user(identifier=ID.parse(self.MOKI))
def execute(self, cmd: Command, msg: ReliableMessage) -> Optional[Content]: assert isinstance(cmd, InviteCommand) or isinstance(cmd, ResetCommand), 'group command error: %s' % cmd facebook = self.facebook from ..facebook import Facebook assert isinstance(facebook, Facebook), 'entity delegate error: %s' % facebook # 0. check group group = cmd.group owner = facebook.owner(identifier=group) members = facebook.members(identifier=group) if owner is None or members is None or len(members) == 0: # FIXME: group profile lost? # FIXME: how to avoid strangers impersonating group members? return self.__temporary_save(cmd=cmd, sender=msg.sender) # 1. check permission sender = msg.sender if sender != owner: # not the owner? check assistants assistants = facebook.assistants(identifier=group) if assistants is None or sender not in assistants: text = '%s is not the owner/assistant of group %s, cannot reset members' % (sender, group) raise AssertionError(text) # 2. resetting members new_members = self.members(cmd=cmd) if new_members is None or len(new_members) == 0: raise ValueError('group command error: %s' % cmd) # 2.1. check owner if owner not in new_members: raise AssertionError('cannot expel owner (%s) of group: %s' % (owner, group)) # 2.2. build expelled-list remove_list = [] for item in members: if item not in new_members: # removing member found remove_list.append(item) # 2.3. build invited-list add_list = [] for item in new_members: if item not in members: # adding member found add_list.append(item) # 2.4. do reset if len(add_list) > 0 or len(remove_list) > 0: if facebook.save_members(members=new_members, identifier=group): if len(add_list) > 0: cmd['added'] = ID.revert(add_list) if len(remove_list) > 0: cmd['removed'] = ID.revert(remove_list) # 3. response (no need to response this group command) return None
def __broadcast_message(self, msg: ReliableMessage) -> Optional[Content]: """ Deliver message to everyone@everywhere, including all neighbours """ self.debug('broadcasting message from: %s' % msg.sender) if self.__traced(msg=msg, station=self.station): self.error('ignore traced msg: %s in %s' % (self.station, msg.get('traces'))) return None session_server = self.session_server # push to all neighbors connected th current station neighbors = self.__neighbors.copy() sent_neighbors = [] success = 0 for sid in neighbors: if sid == self.station: continue sessions = session_server.active_sessions(identifier=sid) if len(sessions) == 0: self.warning('remote station (%s) not connected, try later.' % sid) continue if self.__push_message(msg=msg, receiver=sid, sessions=sessions): sent_neighbors.append(sid) success += 1 # push to the bridge (octopus) of current station sid = self.station sessions = session_server.active_sessions(identifier=sid) if len(sessions) > 0: # tell the bridge ignore this neighbor stations sent_neighbors.append(sid) msg['sent_neighbors'] = ID.revert(sent_neighbors) self.__push_message(msg=msg, receiver=sid, sessions=sessions) # FIXME: what about the failures # response text = 'Message broadcast to %d/%d stations' % (success, len(neighbors)) res = TextContent(text=text) res.group = msg.group return res
def __init__(self, messenger: ClientMessenger): super().__init__() # delegate for send message self.messenger = messenger # group gid = ID.parse(identifier=group_naruto) self.__group = messenger.facebook.group(gid)
def do_profile(self, name: str): if self.client is None: self.info('login first') return facebook = g_messenger.facebook user = facebook.current_user profile = None if name is None: identifier = user.identifier elif name.startswith('{') and name.endswith('}'): identifier = user.identifier profile = json_decode(data=utf8_encode(string=name)) else: identifier = ID.parse(identifier=name) if identifier is None: self.info('I don\'t understand.') return if profile: private_key = facebook.private_key_for_signature(identifier=identifier) assert private_key is not None, 'failed to get private key for client: %s' % self.client # create new profile and set all properties tai = Document.create(doc_type=Document.VISA, identifier=identifier) for key in profile: tai.set_property(key, profile.get(key)) tai.sign(private_key=private_key) cmd = DocumentCommand.response(identifier=identifier, document=tai) else: cmd = DocumentCommand.query(identifier=identifier) self.client.send_command(cmd=cmd)
def create_server(identifier: str, host: str, port: int = 9394) -> Station: """ Create Local Server """ identifier = ID.parse(identifier=identifier) server = Station(identifier=identifier, host=host, port=port) g_facebook.cache_user(user=server) Log.info('local station created: %s' % server) return server
def upload_profile() -> Response: try: form = request.form.to_dict() identifier = form['ID'] meta = form['meta'] profile = form['profile'] identifier = ID.parse(identifier=identifier) # save meta if meta is None: meta = g_facebook.meta(identifier=identifier) if meta is None: raise LookupError('meta not found: %s' % identifier) else: meta = Meta.parse(meta=json.loads(meta)) if not g_facebook.save_meta(meta=meta, identifier=identifier): raise ValueError('meta not acceptable: %s' % identifier) # save profile if profile is None: raise ValueError('profile empty: %s' % identifier) else: profile = Document.parse(document=json.loads(profile)) if not g_facebook.save_document(document=profile): raise ValueError('profile not acceptable: %s' % identifier) # OK return user(identifier) except Exception as error: res = { 'code': 500, 'name': 'Internal Server Error', 'message': '%s' % error } xml = render_template('error.xml', result=res) return respond_xml(xml)
def test_identifier(self): print('\n---------------- %s' % self) str1 = 'gsp-s001@x77uVYBT1G48CLzW9iwe2dr5jhUNEM772G' id1 = ID(str1) self.assertEqual(id1.address.network, NetworkID.Station) arr1 = [str1] self.assertTrue(id1 in arr1)
def scan_ids(self) -> List[ID]: ids = [] directory = os.path.join(self.root, 'public') # get all files in messages directory and sort by filename files = os.listdir(directory) for filename in files: path = os.path.join(directory, filename, 'meta.js') if not os.path.exists(path): self.warning('meta file not exists: %s' % path) continue address = ID.parse(identifier=filename) if address is None: self.error('ID/address error: %s' % filename) continue meta = self.meta(identifier=address) if meta is None: self.error('meta error: %s' % address) else: identifier = meta.generate_identifier(network=address.type) self.debug('loaded meta for %s from %s: %s' % (identifier, path, meta)) # the ID contains 'username' now if identifier != address: # switch cache key # self.__caches.pop(address) self.__caches[identifier] = meta ids.append(identifier) self.debug('Scanned %d ID(s) from %s' % (len(ids), directory)) return ids
def assistants(self, identifier: ID) -> Optional[List[ID]]: array = super().assistants(identifier=identifier) if array is not None and len(array) > 0: return array # get from ANS robot = ID.parse(identifier='assistant') if robot is not None: return [robot]
def do_login(self, name: str): sender = ID.parse(identifier=name) if sender is None: self.info('unknown user: %s' % name) else: self.info('login as %s' % sender) self.login(identifier=sender) facebook = g_messenger.facebook self.prompt = Console.prompt + facebook.name(identifier=sender) + '$ '
def save_contacts(self, contacts: List[ID], user: ID) -> bool: assert contacts is not None, 'contacts cannot be empty' # store into memory cache self.__contacts[user] = contacts # store into local storage path = self.__contacts_path(identifier=user) self.info('Saving contacts into: %s' % path) contacts = ID.revert(members=contacts) text = '\n'.join(contacts) return self.write_text(text=text, path=path)
def save_members(self, members: List[ID], group: ID) -> bool: assert len( members) > 0, 'group members should not be empty: %s' % group # 1. store into memory cache self.__members[group] = members # 2. store into local storage path = self.__members_path(identifier=group) self.info('Saving members into: %s' % path) members = ID.revert(members=members) text = '\n'.join(members) return self.write_text(text=text, path=path)
def __roaming(self, cmd: LoginCommand, sender: ID) -> Optional[ID]: # check time expires old = self.database.login_command(identifier=sender) if old is not None: if cmd.time < old.time: return None # get station ID assert cmd.station is not None, 'login command error: %s' % cmd sid = ID.parse(identifier=cmd.station.get('ID')) if sid == g_station.identifier: return None return sid
def load_station(identifier: Union[ID, str], facebook: CommonFacebook) -> Station: """ Load station info from 'etc' directory :param identifier - station ID :param facebook - social network data source :return station with info from 'dims/etc/{address}/*' """ identifier = ID.parse(identifier=identifier) # check meta meta = facebook.meta(identifier=identifier) if meta is None: # load from 'etc' directory meta = Meta.parse(meta=load_station_info(identifier=identifier, filename='meta.js')) if meta is None: raise LookupError('failed to get meta for station: %s' % identifier) elif not facebook.save_meta(meta=meta, identifier=identifier): raise ValueError('meta error: %s' % meta) # check private key private_key = facebook.private_key_for_signature(identifier=identifier) if private_key is None: # load from 'etc' directory private_key = PrivateKey.parse(key=load_station_info(identifier=identifier, filename='secret.js')) if private_key is None: pass elif not facebook.save_private_key(key=private_key, identifier=identifier): raise AssertionError('failed to save private key for ID: %s, %s' % (identifier, private_key)) # check profile profile = load_station_info(identifier=identifier, filename='profile.js') if profile is None: raise LookupError('failed to get profile for station: %s' % identifier) Log.info('station profile: %s' % profile) name = profile.get('name') host = profile.get('host') port = profile.get('port') # create station if private_key is None: # remote station station = Station(identifier=identifier, host=host, port=port) else: # create profile profile = Document.create(doc_type=Document.PROFILE, identifier=identifier) profile.set_property('name', name) profile.set_property('host', host) profile.set_property('port', port) profile.sign(private_key=private_key) if not facebook.save_document(document=profile): raise AssertionError('failed to save profile: %s' % profile) # local station station = Station(identifier=identifier, host=host, port=port) facebook.cache_user(user=station) Log.info('station loaded: %s' % station) return station
def do_call(self, name: str): if self.client is None: self.info('login first') return receiver = ID.parse(identifier=name) if receiver is None: self.info('unknown user: %s' % name) else: facebook = g_messenger.facebook meta = facebook.meta(identifier=receiver) self.info('talking with %s now, meta=%s' % (receiver, meta)) # switch receiver self.receiver = receiver
def send(self, identifier: str) -> int: identifier = ID.parse(identifier=identifier) device = Device(identifier) tokens = device.tokens if tokens is None: print('Device token not found, failed to push message: %s' % self.__payload.alert) return 0 count = 0 for token in tokens: self.__client.send_notification(token_hex=token, notification=self.__payload) count += 1 print('Message has been sent to %d device(s)' % count) return count
def update_keys(self, key_map: dict) -> bool: """ Update cipher key table into memory cache :param key_map: cipher keys(with direction) from local storage :return: False on nothing changed """ changed = False for _from in key_map: sender = ID.parse(identifier=_from) table = key_map.get(_from) assert isinstance( table, dict), 'sender table error: %s, %s' % (_from, table) for _to in table: receiver = ID.parse(identifier=_to) pw = table.get(_to) key = SymmetricKey.parse(key=pw) # TODO: check whether exists an old key changed = True # cache key with direction self.__cache_cipher_key(key, sender, receiver) return changed
def user(address: str) -> Response: try: if address.find('@') < 0: address = Address.parse(address=address) identifier = ID.parse(identifier=address) info = g_worker.user_info(identifier=identifier) if info is None: messages = [] else: identifier = ID.parse(identifier=info.get('ID')) messages = g_worker.messages(identifier=identifier, start=0, count=20) xml = render_template('user.xml', user=info, messages=messages) except Exception as error: res = { 'code': 500, 'name': 'Internal Server Error', 'message': '%s' % error } xml = render_template('error.xml', result=res) return respond_xml(xml)
def __roaming(self, cmd: LoginCommand, sender: ID) -> Optional[ID]: # check time expires old = self.database.login_command(identifier=sender) if old is not None: if cmd.time < old.time: return None station = self.messenger.get_context(key='station') assert station is not None, 'current station not in the context' # get station ID assert cmd.station is not None, 'login command error: %s' % cmd sid = ID.parse(identifier=cmd.station.get('ID')) if sid == station.identifier: return None return sid
def contacts(self, user: ID) -> List[ID]: # try from memory cache array = self.__contacts.get(user) if array is None: # try from local storage path = self.__contacts_path(identifier=user) self.info('Loading contacts from: %s' % path) text = self.read_text(path=path) if text is None: array = [] else: array = ID.convert(members=text.splitlines()) # store into memory cache self.__contacts[user] = array return array
def members(self, group: ID) -> List[ID]: # 1. try from memory cache array = self.__members.get(group) if array is None: # 2. try from local storage path = self.__members_path(identifier=group) self.info('Loading members from: %s' % path) text = self.read_text(path=path) if text is None: array = [] else: array = ID.convert(members=text.splitlines()) # 3. store into memory cache self.__members[group] = array return array
def load_freshmen() -> List[ID]: freshmen = [] text = Storage.read_text(path=freshmen_file) if text is None: return freshmen array = text.splitlines() for item in array: identifier = ID.parse(identifier=item) if identifier is None: Log.error('ID error: %s' % item) elif identifier.type == NetworkType.MAIN: freshmen.append(identifier) else: # Log.error('Not a user ID: %s' % identifier) pass return freshmen
def users(self) -> list: array = [] anyone = ANYONE id_list = self.__recommended_users(address=anyone.address) for item in id_list: identifier = item.strip() if len(identifier) == 0: # skip empty lines continue identifier = ID.parse(identifier=identifier) info = self.user_info(identifier=identifier) if info is None: # user info not found continue array.append(info) return array
def execute(self, cmd: Command, msg: ReliableMessage) -> Optional[Content]: assert isinstance(cmd, InviteCommand), 'group command error: %s' % cmd facebook = self.facebook from ..facebook import Facebook assert isinstance(facebook, Facebook), 'entity delegate error: %s' % facebook # 0. check group group = cmd.group owner = facebook.owner(identifier=group) members = facebook.members(identifier=group) if owner is None or members is None or len(members) == 0: # NOTICE: group membership lost? # reset group members return self.__reset(cmd=cmd, msg=msg) # 1. check permission sender = msg.sender if sender not in members: # not a member? check assistants assistants = facebook.assistants(identifier=group) if assistants is None or sender not in assistants: raise AssertionError('only member/assistant can invite: %s' % msg) # 2. inviting members invite_list = self.members(cmd=cmd) if invite_list is None or len(invite_list) == 0: raise ValueError('invite command error: %s' % cmd) # 2.1. check for reset if sender == owner and owner in invite_list: # NOTICE: owner invites owner? # it means this should be a 'reset' command return self.__reset(cmd=cmd, msg=msg) # 2.2. build invited-list add_list = [] for item in invite_list: if item in members: continue # new member found add_list.append(item) members.append(item) # 2.3. do invite if len(add_list) > 0: if facebook.save_members(members=members, identifier=group): cmd['added'] = ID.revert(add_list) # 3. response (no need to response this group command) return None
def execute(self, cmd: Command, msg: ReliableMessage) -> Optional[Content]: assert isinstance(cmd, ExpelCommand), 'group command error: %s' % cmd facebook = self.facebook from ..facebook import Facebook assert isinstance(facebook, Facebook), 'entity delegate error: %s' % facebook # 0. check group group = cmd.group owner = facebook.owner(identifier=group) members = facebook.members(identifier=group) if owner is None or members is None or len(members) == 0: raise LookupError('Group not ready: %s' % group) # 1. check permission sender = msg.sender if sender != owner: # not the owner? check assistants assistants = facebook.assistants(identifier=group) if assistants is None or sender not in assistants: text = '%s is not the owner/assistant of group %s, cannot expel member' % ( sender, group) raise AssertionError(text) # 2. expelling members expel_list = self.members(cmd=cmd) if expel_list is None or len(expel_list) == 0: raise ValueError('expel command error: %s' % cmd) # 2.1. check owner if owner in expel_list: raise AssertionError('cannot expel owner %s of group %s' % (owner, group)) # 2.2. build removed-list remove_list = [] for item in expel_list: if item not in members: continue # expelled member found remove_list.append(item) members.remove(item) # 2.3. do expel if len(remove_list) > 0: if facebook.save_members(members=members, identifier=group): cmd['removed'] = ID.revert(remove_list) # 3. response (no need to response this group command) return None
def __traced(self, msg: ReliableMessage, station: ID) -> bool: traces = msg.get('traces') if traces is None: # broadcast message starts from here traces = [station] else: for node in traces: if isinstance(node, str): if station == node: return True elif isinstance(node, dict): if station == node.get('ID'): return True else: self.error('traces node error: %s' % node) # broadcast message go through here traces.append(station) msg['traces'] = ID.revert(members=traces) return False
def __roaming(self, receiver: ID) -> Optional[ID]: login = self.database.login_command(identifier=receiver) if login is None: return None station = login.station if station is None: return None # check time expires now = time.time() login_time = login.time if login_time is None: self.error('%s login time not set: %s' % (receiver, login)) return None if (now - login_time) > (3600 * 24 * 7): t_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(login_time)) self.debug('%s login expired: [%s] %s' % (receiver, t_str, login)) return None sid = ID.parse(identifier=station.get('ID')) if sid == self.station: return None return sid