Skip to content

Commit

Permalink
Merge pull request #12 from nextleap-project/share-claims-with-gossip…
Browse files Browse the repository at this point in the history
…ed-peers

Share claims with gossiped peers
  • Loading branch information
hpk42 authored Apr 17, 2018
2 parents 73f8648 + e19a799 commit e107b00
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 40 deletions.
90 changes: 58 additions & 32 deletions muacryptcc/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ def __init__(self, accountdir, store=None):
self.accountdir = accountdir
if not os.path.exists(accountdir):
os.makedirs(accountdir)
self.addr2root_hash = {}
self.addr2pk = {}
self.addr2cc_info = {}
self.store = store
self.init_crypto_identity()

Expand All @@ -38,40 +37,36 @@ def __init__(self, accountdir, store=None):
#
@hookimpl
def process_incoming_gossip(self, addr2pagh, account_key, dec_msg):
sender_addr = parse_email_addr(dec_msg["From"])
root_hash = dec_msg["GossipClaims"]
store = FileStore(dec_msg["ChainStore"])
store_url = dec_msg["ChainStore"]
self.register_peer(sender_addr, root_hash, store_url)

store = FileStore(store_url)
peers_chain = Chain(store, root_hash=ascii2bytes(root_hash))
assert peers_chain
view = View(peers_chain)
peers_pk = view.params.dh.pk
assert peers_pk
sender = parse_email_addr(dec_msg["From"])
self.addr2root_hash[sender] = root_hash
self.addr2pk[sender] = peers_pk
recipients = get_target_emailadr(dec_msg)
for recipient in recipients:
pagh = addr2pagh[recipient]
value = self.read_claim(recipient, chain=peers_chain)
for addr in recipients:
pagh = addr2pagh[addr]
value = self.read_claim(addr, chain=peers_chain)
if value:
# for now we can only read claims about ourselves...
# so if we get a value it must be our head imprint.
assert value['key'] == bytes2ascii(pagh.keydata)
# TODO: check for existing entries
if value['store_url']:
self.register_peer(addr, value['root_hash'], value['store_url'])

@hookimpl
def process_before_encryption(self, sender_addr, sender_keyhandle,
recipient2keydata, payload_msg, _account):
recipients = recipient2keydata.keys()
if not recipients:
addrs = recipient2keydata.keys()
if not addrs:
logging.error("no recipients found.\n")

for recipient in recipients:
key = recipient
value = dict(
key=bytes2ascii(recipient2keydata.get(recipient))
)
for reader in recipients:
pk = self.addr2pk.get(reader)
self.add_claim((key, value), access_pk=pk)
for addr in addrs:
self.add_claim(self.claim_about(addr, recipient2keydata.get(addr)))

for reader in addrs:
if self.can_share_with(reader):
self.share_claims(addrs, reader)

self.commit_to_chain()
payload_msg["GossipClaims"] = self.head_imprint
Expand Down Expand Up @@ -120,33 +115,64 @@ def fset(self, val):
def head_imprint(self):
return bytes2ascii(self.head)

def register_peer(self, addr, root_hash, store_url, pk=None):
if not pk:
store = FileStore(store_url)
chain = Chain(store, root_hash=ascii2bytes(root_hash))
assert chain
view = View(chain)
pk = view.params.dh.pk
assert pk
self.addr2cc_info[addr] = dict(
root_hash=root_hash,
store_url=store_url,
public_key=pk
)

def claim_about(self, addr, keydata):
info = self.addr2cc_info.get(addr) or {}
content = dict(
key=bytes2ascii(keydata),
store_url=info.get("store_url"),
root_hash=info.get("root_hash")
)
return (addr, content)

def commit_to_chain(self):
chain = self._get_current_chain()
with self.params.as_default():
self.head = self.state.commit(chain)

def read_claim(self, claimkey, chain=None, reader=None):
def read_claim(self, claimkey, chain=None):
if chain is None:
chain = self._get_current_chain()
if reader is None:
reader = self
try:
with reader.params.as_default():
with self.params.as_default():
value = View(chain)[claimkey.encode('utf-8')]
return json.loads(value.decode('utf-8'))
except (KeyError, ValueError):
return None

def add_claim(self, claim, access_pk=None):
def add_claim(self, claim):
key = claim[0].encode('utf-8')
value = json.dumps(claim[1]).encode('utf-8')
assert isinstance(key, bytes)
assert isinstance(value, bytes)
self.state[key] = value
with self.params.as_default():
self.state.grant_access(self.get_public_key(), [key])
if access_pk is not None:
self.state.grant_access(access_pk, [key])

def can_share_with(self, peer):
reader_info = self.addr2cc_info.get(peer) or {}
return bool(reader_info.get('public_key'))

def share_claims(self, claim_keys, reader):
claim_keys = [key.encode('utf-8') for key in claim_keys]
reader_info = self.addr2cc_info.get(reader) or {}
pk = reader_info.get("public_key")
assert pk
with self.params.as_default():
self.state.grant_access(pk, claim_keys)

def _get_current_chain(self):
return Chain(self.store, root_hash=self.head)
33 changes: 29 additions & 4 deletions tests/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ def test_claim_headers_in_encrypted_mail(account_maker):
assert cc2.read_claim(acc1.addr)


def test_claims_contain_keys(account_maker):
def test_claims_contain_keys_and_cc_reference(account_maker):
acc1, acc2 = account_maker(), account_maker()
send_mail(acc1, acc2)

cc2, ac2 = get_cc_and_ac(send_encrypted_mail(acc2, acc1))
cc1, ac1 = get_cc_and_ac(send_encrypted_mail(acc1, acc2))

data = cc1.read_claim(acc2.addr, reader=cc2)
data = cc2.read_claim(acc2.addr, chain=cc1)
assert data['key'] == bytes2ascii(ac2.keydata)
assert data['root_hash'] == cc2.head_imprint
assert data['store_url'] == cc2.store._dir


def test_gossip_claims(account_maker):
Expand All @@ -48,10 +50,33 @@ def test_gossip_claims(account_maker):
cc3, ac3 = get_cc_and_ac(send_encrypted_mail(acc3, acc1))
cc1, ac1 = get_cc_and_ac(send_encrypted_mail(acc1, [acc2, acc3]))

data = cc1.read_claim(acc3.addr, reader=cc2)
data = cc2.read_claim(acc3.addr, chain=cc1)
assert data['key'] == bytes2ascii(ac3.keydata)


def test_reply_to_gossip_claims(account_maker):
acc1, acc2, acc3 = account_maker(), account_maker(), account_maker()
send_mail(acc1, acc2)
send_mail(acc1, acc3)
send_encrypted_mail(acc3, acc1)

cc2, ac2 = get_cc_and_ac(send_encrypted_mail(acc2, acc1))
cc1, ac1 = get_cc_and_ac(send_encrypted_mail(acc1, [acc2, acc3]))
cc3, ac3 = get_cc_and_ac(send_encrypted_mail(acc3, [acc1, acc2]))

data = cc2.read_claim(acc2.addr, chain=cc3)
assert data['key'] == bytes2ascii(ac2.keydata)
assert data['root_hash'] == cc2.head_imprint


def test_ac_gossip_works(account_maker):
acc1, acc2, acc3 = account_maker(), account_maker(), account_maker()
send_mail(acc3, acc1)
send_mail(acc2, acc1)
send_encrypted_mail(acc1, [acc2, acc3])
send_encrypted_mail(acc3, [acc1, acc2])


# send a mail from acc1 with autocrypt key to acc2
def send_mail(acc1, acc2):
msg = gen_ac_mail_msg(acc1, acc2)
Expand All @@ -67,7 +92,7 @@ def send_encrypted_mail(sender, recipients):
"""
if isinstance(recipients, Account):
recipients = [recipients]
msg = gen_ac_mail_msg(sender, recipients, payload="hello ä umlaut", charset="utf8")
msg = gen_ac_mail_msg(sender, recipients, payload="hello", charset="utf8")
enc_msg = sender.encrypt_mime(msg, [r.addr for r in recipients]).enc_msg
for rec in recipients:
processed = rec.process_incoming(enc_msg)
Expand Down
10 changes: 6 additions & 4 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ def test_add_claim_with_access_control(make_account):
cc_alice.commit_to_chain()
assert cc_alice.read_claim("bob_hair")

cc_alice.add_claim(claim=("bob_feet", "4"), access_pk=bob_pk)
cc_alice.register_peer('bob', cc_bob.head_imprint, '', pk=bob_pk)
cc_alice.add_claim(claim=("bob_feet", "4"))
cc_alice.share_claims(["bob_feet"], reader='bob')
cc_alice.commit_to_chain()
assert cc_alice.read_claim("bob_feet", reader=cc_bob)
assert cc_alice.read_claim("bob_feet", reader=cc_alice)
assert not cc_alice.read_claim("bob_hair", reader=cc_bob)
assert cc_alice.read_claim("bob_feet")
assert cc_bob.read_claim("bob_feet", chain=cc_alice)
assert not cc_bob.read_claim("bob_hair", chain=cc_alice)

0 comments on commit e107b00

Please sign in to comment.