Allow UserType.private_key to be passed in as a string

This allows the UserType object to be serialized to for example
redis based background workers.

To get the real RsaKey object, use the UserType.rsa_private_key
property.
mentions
Jason Robinson 2019-08-29 22:50:57 +03:00
rodzic 3dd18e301e
commit da2d436fdf
6 zmienionych plików z 26 dodań i 18 usunięć

Wyświetl plik

@ -48,7 +48,7 @@
* The high level `fetchers.retrieve_remote_profile` signature has changed. It now expects as first parameter an `id` which for ActivityPub objects is the URL ID and for Diaspora objects is the handle. Additionally a `sender_key_fetcher` can be passed in as before to optimize public key fetching using a callable.
* The generator class `RFC7033Webfinger` now expects instead of an `id` the `handle` and `guid` of the profile.
* NodeInfo2 parser now returns the admin user in `handle` format instead of a Diaspora format URL.
* The high level inbound and outbound functions `inbound.handle_receive`, `outbound.handle_send` parameter `user` must now receive a `UserType` compatible object. This must have the attributes `id` and `private_key`. If Diaspora support is required then also `handle` and `guid` should exist. The type can be found as a class in `types.UserType`.
* The high level inbound and outbound functions `inbound.handle_receive`, `outbound.handle_send` parameter `user` must now receive a `UserType` compatible object. This must have the attribute `id`, and for `handle_send` also `private_key`. If Diaspora support is required then also `handle` and `guid` should exist. The type can be found as a class in `types.UserType`.
* The high level inbound function `inbound.handle_receive` first parameter has been changed to `request` which must be a `RequestType` compatible object. This must have the attribute `body` which corrresponds to the old `payload` parameter. For ActivityPub inbound requests the object must also contain `headers`, `method` and `url`.
* The outbound function `outbound.handle_send` parameter `recipients` structure has changed. It must now be a list of dictionaries, containing at minimum the following: `endpoint` for the recipient endpoint, `fid` for the recipient federation ID (ActivityPub only), `protocol` for the protocol to use and `public` as a boolean whether the payload should be treated as visible to anyone.

Wyświetl plik

@ -39,9 +39,9 @@ def handle_create_payload(
mappers = importlib.import_module(f"federation.entities.{protocol_name}.mappers")
protocol = importlib.import_module(f"federation.protocols.{protocol_name}.protocol")
protocol = protocol.Protocol()
outbound_entity = mappers.get_outbound_entity(entity, author_user.private_key)
outbound_entity = mappers.get_outbound_entity(entity, author_user.rsa_private_key)
if parent_user:
outbound_entity.sign_with_parent(parent_user.private_key)
outbound_entity.sign_with_parent(parent_user.rsa_private_key)
send_as_user = parent_user if parent_user else author_user
data = protocol.build_send(entity=outbound_entity, from_user=send_as_user, to_user_key=to_user_key)
return data
@ -156,7 +156,7 @@ def handle_send(
logger.error("handle_send - failed to generate payload for %s, %s: %s", fid, endpoint, ex)
continue
payloads.append({
"auth": get_http_authentication(author_user.private_key, f"{author_user.id}#main-key"),
"auth": get_http_authentication(author_user.rsa_private_key, f"{author_user.id}#main-key"),
"payload": payload,
"content_type": 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
"urls": {endpoint},

Wyświetl plik

@ -106,7 +106,7 @@ class Protocol:
def _get_user_key(self):
if not getattr(self.user, "private_key", None):
raise EncryptedMessageError("Cannot decrypt private message without user key")
return self.user.private_key
return self.user.rsa_private_key
def get_sender(self):
return MagicEnvelope.get_sender(self.doc)
@ -150,7 +150,7 @@ class Protocol:
xml = entity.outbound_doc
else:
xml = entity.to_xml()
me = MagicEnvelope(etree.tostring(xml), private_key=from_user.private_key, author_handle=from_user.handle)
me = MagicEnvelope(etree.tostring(xml), private_key=from_user.rsa_private_key, author_handle=from_user.handle)
rendered = me.render()
if to_user_key:
return EncryptedPayload.encrypt(rendered, to_user_key)

Wyświetl plik

@ -10,15 +10,15 @@ from federation.protocols.diaspora.protocol import Protocol, identify_request
from federation.tests.fixtures.keys import PUBKEY, get_dummy_private_key
from federation.tests.fixtures.payloads import DIASPORA_PUBLIC_PAYLOAD, DIASPORA_ENCRYPTED_PAYLOAD, \
DIASPORA_RESHARE_PAYLOAD
from federation.types import RequestType
from federation.types import RequestType, UserType
class MockUser:
private_key = "foobar"
def __init__(self, nokey=False):
if nokey:
self.private_key = None
else:
self.private_key = get_dummy_private_key()
def mock_get_contact_key(contact):
@ -113,7 +113,7 @@ class TestDiasporaProtocol(DiasporaTestBase):
entity = DiasporaPost()
private_key = get_dummy_private_key()
outbound_entity = get_outbound_entity(entity, private_key)
data = protocol.build_send(outbound_entity, from_user=Mock(
data = protocol.build_send(outbound_entity, from_user=UserType(
private_key=private_key, id="johnny@localhost",
handle="johnny@localhost",
))
@ -133,7 +133,7 @@ class TestDiasporaProtocol(DiasporaTestBase):
entity = DiasporaPost()
private_key = get_dummy_private_key()
outbound_entity = get_outbound_entity(entity, private_key)
data = protocol.build_send(outbound_entity, to_user_key="public key", from_user=Mock(
data = protocol.build_send(outbound_entity, to_user_key="public key", from_user=UserType(
private_key=private_key, id="johnny@localhost",
handle="johnny@localhost",
))
@ -151,8 +151,8 @@ class TestDiasporaProtocol(DiasporaTestBase):
protocol = self.init_protocol()
outbound_doc = etree.fromstring("<xml>foo</xml>")
entity = Mock(outbound_doc=outbound_doc)
from_user = Mock(
id="foobar@domain.tld", private_key="barfoo", handle="foobar@domain.tld",
from_user = UserType(
id="foobar@domain.tld", private_key=get_dummy_private_key(), handle="foobar@domain.tld",
)
protocol.build_send(entity, from_user)
mock_me.assert_called_once_with(
@ -162,9 +162,9 @@ class TestDiasporaProtocol(DiasporaTestBase):
@patch("federation.protocols.diaspora.protocol.EncryptedPayload.decrypt")
def test_get_json_payload_magic_envelope(self, mock_decrypt):
protocol = Protocol()
protocol.user = MockUser()
protocol.user = UserType(id="foobar", private_key=get_dummy_private_key())
protocol.get_json_payload_magic_envelope("payload")
mock_decrypt.assert_called_once_with(payload="payload", private_key="foobar")
mock_decrypt.assert_called_once_with(payload="payload", private_key=get_dummy_private_key())
@patch.object(Protocol, "get_json_payload_magic_envelope", return_value=etree.fromstring("<foo>bar</foo>"))
def test_store_magic_envelope_doc_json_payload(self, mock_store):

Wyświetl plik

@ -5,6 +5,7 @@ import pytest
from federation.entities.diaspora.entities import DiasporaPost
from federation.outbound import handle_create_payload, handle_send
from federation.tests.fixtures.keys import get_dummy_private_key
from federation.types import UserType
from federation.utils.text import encode_if_text
@ -50,10 +51,10 @@ class TestHandleSend:
"protocol": "activitypub",
}
]
mock_author = Mock(
author = UserType(
private_key=key, id="foo@example.com", handle="foo@example.com",
)
handle_send(profile, mock_author, recipients)
handle_send(profile, author, recipients)
# Ensure first call is a private diaspora payload
args, kwargs = mock_send.call_args_list[0]

Wyświetl plik

@ -2,6 +2,7 @@ from enum import Enum
from typing import Optional, Dict, Union
import attr
from Crypto.PublicKey import RSA
from Crypto.PublicKey.RSA import RsaKey
@ -28,9 +29,15 @@ class ReceiverVariant(Enum):
@attr.s(frozen=True)
class UserType:
id: str = attr.ib()
private_key: Optional[RsaKey] = attr.ib(default=None)
private_key: Optional[Union[RsaKey, str]] = attr.ib(default=None)
receiver_variant: Optional[ReceiverVariant] = attr.ib(default=None)
# Required only if sending to Diaspora protocol platforms
handle: Optional[str] = attr.ib(default=None)
guid: Optional[str] = attr.ib(default=None)
@property
def rsa_private_key(self) -> RsaKey:
if isinstance(self.private_key, str):
return RSA.importKey(self.private_key)
return self.private_key