pypsyc/mjacob2/pypsyc/server/person.py

189 lines
7.4 KiB
Python

"""
pypsyc.server.person
~~~~~~~~~~~~~~~~~~~~
:copyright: 2010 by Manuel Jacob
:license: MIT
"""
import hmac
from hashlib import sha256
from pypsyc.server.multicast import ContextSlave
from pypsyc.protocol import (RoutingErrorRelaying, LinkingServer,
MessageRelaying, AuthenticationError, EntryDeniedError,
ContextMaster as ContextMasterProtocol, FriendshipPendingError,
FriendshipEstablishedError, FriendshipServer as FriendshipProtocol,
ClientInterfaceServer as ClientInterfaceProtocol)
from pypsyc.server import _TreeNode
from pypsyc.util import schedule
class Resource(_TreeNode):
def __init__(self, parent, resource, circuit):
_TreeNode.__init__(self, parent=parent, name=resource)
self.circuit = circuit
if hasattr(circuit, 'allowed_sources'):
circuit.allowed_sources.append(parent.uni.chain(resource))
def handle_packet(self, header, content):
self.circuit.send(header, content)
class Person(object):
def __init__(self, entity):
self.entity = entity
self.uni = entity.uni
self.routing_error_relaying = RoutingErrorRelaying(self)
entity.add_handler(self.routing_error_relaying)
entity.add_handler(LinkingServer(self))
self.message_relaying = MessageRelaying(self)
entity.add_handler(self.message_relaying)
entity.add_handler(ContextMasterProtocol(self))
self.context_slave = ContextSlave(self)
self.friendship_protocol = FriendshipProtocol(self)
entity.add_handler(self.friendship_protocol)
entity.add_handler(ClientInterfaceProtocol(self))
def register(self, password):
"""
Register this person entity with the given password. This method has to
be called after the entity was created.
"""
sql = ('CREATE TABLE IF NOT EXISTS passwords ('
'person TEXT PRIMARY KEY, password BLOB)')
self.entity.server.database.execute(sql)
sql = ('CREATE TABLE IF NOT EXISTS friendships ('
'person TEXT, uni TEXT, state TEXT, PRIMARY KEY (person, uni))')
self.entity.server.database.execute(sql)
sql = 'INSERT INTO passwords VALUES (?, ?)'
self.entity.server.database.execute(sql, self.uni, password)
def unknown_target_error(self, uni):
for i in self.entity.children:
self.routing_error_relaying.relay_unknown_target_error(
self.uni.chain(i), uni)
def delivery_failed_error(self, uni, message):
for i in self.entity.children:
self.routing_error_relaying.relay_delivery_failed_error(
self.uni.chain(i), uni, message)
def authenticate(self, pw, circuit, resource):
sql = 'SELECT password FROM passwords WHERE person = ?'
db_pass = self.entity.server.database.fetch(sql, self.uni)[0][0]
type_, req_pass, nonce = pw
assert type_ == 'hmac'
if req_pass != hmac.new(db_pass, nonce, sha256).digest():
raise AuthenticationError("Incorrect password.")
Resource(self.entity, resource, circuit)
self.friendship_protocol.cast_presence(7)
schedule(self._after_authentication, self.uni.chain(resource))
def _after_authentication(self, uni):
sql = 'SELECT uni, state FROM friendships WHERE person = ?'
rows = self.entity.server.database.fetch(sql, self.uni)
friendships = dict((i[0], {'state': i[1]}) for i in rows)
self.friendship_protocol.send_friendships(uni, friendships)
for context, state in rows:
if state == 'established':
self.context_slave.enter(context, uni)
def unlink(self, resource):
del self.entity.children[resource]
self.friendship_protocol.cast_presence(1)
self.context_slave.leave_all()
def private_message_relay(self, source, target, message):
if source.is_descendant_of(self.uni):
self.message_relaying.send_private_message(target, message)
def private_message(self, source, message):
for i in self.entity.children:
self.message_relaying.relay_private_message(
source, self.uni.chain(i), message)
def friendship_request(self, uni):
state = self._get_friendship_state(uni)
if state == 'none':
self._update_friendship(uni, 'offered', True)
return 'pending'
elif state == 'pending':
self._update_friendship(uni, 'established', False)
self.context_slave.enter(uni)
return 'established'
elif state == 'offered':
return 'pending'
elif state == 'established':
return 'established'
else:
raise Exception("unknown friendship state: " + state)
def friendship_cancel(self, uni):
self.context_slave.leave(uni)
self._update_friendship(uni, None)
def client_add_friend(self, uni):
state = self._get_friendship_state(uni)
if state == 'none':
self.friendship_protocol.establish(uni)
self._update_friendship(uni, 'pending', True)
elif state == 'pending':
raise FriendshipPendingError
elif state == 'offered':
self.friendship_protocol.establish(uni)
self._update_friendship(uni, 'established', False)
self.context_slave.enter(uni)
elif state == 'established':
raise FriendshipEstablishedError
else:
raise Exception("unknown friendship state: " + state)
def client_remove_friend(self, uni):
self.context_slave.leave(uni)
self.friendship_protocol.remove(uni)
self._update_friendship(uni, None)
def _get_friendship_state(self, uni):
sql = 'SELECT state FROM friendships WHERE person = ? AND uni = ?'
ret = self.entity.server.database.fetch(sql, self.uni, uni)
if ret:
return ret[0][0]
return 'none'
def _update_friendship(self, uni, state, insert=False):
if state is None:
sql = 'DELETE FROM friendships WHERE person = ? AND uni = ?'
self.entity.server.database.execute(sql, self.uni, uni)
elif insert:
sql = 'INSERT INTO friendships VALUES(?, ?, ?)'
self.entity.server.database.execute(sql, self.uni, uni, state)
else:
sql = ('UPDATE friendships SET state = ? '
'WHERE person = ? AND uni = ?')
self.entity.server.database.execute(sql, state, self.uni, uni)
for resource in self.entity.children:
if state:
self.friendship_protocol.send_updated_friendship(
self.uni.chain(resource), uni, {'state': state})
else:
self.friendship_protocol.send_removed_friendship(
self.uni.chain(resource), uni)
def enter_request(self, uni):
if self._get_friendship_state(uni) == 'established':
return self.entity.context_master.add_member(uni)
else:
raise EntryDeniedError("You are not my friend!")
def leave_context(self, uni):
self.entity.context_master.remove_member(uni)
def client_presence(self, availability):
self.friendship_protocol.cast_presence(availability)
def client_subscribe(self, uni, resource_uni):
self.context_slave.enter(uni, resource_uni)
def client_unsubscribe(self, uni, resource_uni):
self.context_slave.leave(uni, resource_uni)