def test_all(self): with self.subTest('Exception raised'): with self.assertRaisesRegex( UnauthorizedAction, 'Missing authorization by abraham@aaa.'): process_event(self.event) with self.subTest('Post not created'): with self.assertRaises(Post.DoesNotExist): Post.select().join(Server).where(Server.name == 'aaa', Post.path == '/post1').get() with self.subTest('Unverified signature saved'): signatures: List[UnverifiedSignature] = list( UnverifiedSignature.select().where( UnverifiedSignature.event == self.event)) self.assertEqual(len(signatures), 1) self.assertEqual(signatures[0].user, 'abraham@aaa') self.assertIsNone(signatures[0].post) self.assertEqual(self.event.signatures['abraham@aaa'], signatures[0].data.hex()) with self.subTest('Verified signature saved'): signatures: List[Signature] = list( Signature.select().where(Signature.event == self.event)) self.assertEqual(len(signatures), 1) self.assertEqual(signatures[0].keypair, self.bernard_keypair) self.assertIsNone(signatures[0].post) self.assertEqual(self.event.signatures['bernard@aaa'], signatures[0].data.hex())
class TestCommenting(AndreasTestCaseWithKeyPair): def setUpSafe(self): super().setUpSafe() self.post = Post() self.post.server = self.server self.post.path = '/post1' self.post.data = { 'body': 'How are you?', } self.post.save() UserPostRelation.create(source=self.abraham, type='wrote', target=self.post) event = Event() event.server = 'aaa' event.authors = ['bernard@aaa'] event.parent = 'aaa/post1' event.path = '/post1#c1' event.data = {'body': 'Good!'} event.signatures = { 'bernard@aaa': sign_post(event, self.bernard_keypair).hex(), } event.save() process_event(event) def test_comment_created(self): comment = Post.select().join(Server).where( Server.name == 'aaa', Post.path == '/post1#c1').get() PostPostRelation.get(PostPostRelation.source == comment, PostPostRelation.type == 'comments', PostPostRelation.target == self.post)
def setUpSafe(self): super().setUpSafe() self.post = Post() self.post.server = self.server self.post.path = '/post1' self.post.data = { 'body': 'How are you?', } self.post.save() UserPostRelation.create(source=self.abraham, type='wrote', target=self.post) event = Event() event.server = 'aaa' event.authors = ['bernard@aaa'] event.parent = 'aaa/post1' event.path = '/post1#c1' event.data = {'body': 'Good!'} event.signatures = { 'bernard@aaa': sign_post(event, self.bernard_keypair).hex(), } event.save() process_event(event)
class TestModifyPost(AndreasTestCaseWithKeyPair): """ With a pre-existing post, try to create/modify/remove three different fields in ``data``. Check that each field was processed correctly and that the fourth field wasn't affected. """ def setUpSafe(self): super().setUpSafe() self.post = Post() self.post.server = self.server self.post.authors = [self.abraham] self.post.path = '/post1' self.post.data = { 'title': 'Hello', 'subtitle': 'A hello world post', 'body': 'Hello, World!', } self.post.save() event = Event() event.server = 'aaa' event.authors = ['abraham@aaa'] event.path = '/post1' event.diff = { 'title': 'Hello (updated)', 'subtitle': None, 'tags': ['Aaa', 'Bbb', 'Ccc'], } event.signatures = { 'abraham@aaa': sign_post(event, self.abraham_keypair, data={ 'title': 'Hello (updated)', 'body': 'Hello, World!', 'tags': ['Aaa', 'Bbb', 'Ccc'], }).hex(), } event.save() process_event(event) self.post.reload() def test_title_updated(self): self.assertEqual('Hello (updated)', self.post.data['title']) def test_subtitle_removed(self): self.assertFalse('subtitle' in self.post.data) def test_body_untouched(self): self.assertEqual('Hello, World!', self.post.data['body']) def test_tags_added(self): self.assertEqual(['Aaa', 'Bbb', 'Ccc'], self.post.data['tags'])
def get_post(server: Union[Server, str, int], path: str) -> Post: """ Returns a post with given `path` on given `server`. :param server: The :class:`Server<andreas.models.core.Server>` object, or its id, or its name. :param path: The path to the required :class:`Post<andreas.models.core.Post>` on the server. """ if isinstance(server, str): return Post.select(Post, Server).join(Server).where( Server.name == server).where(Post.path == path).get() else: return Post.get(Post.server == server, Post.path == path)
def setUpSafe(self): super().setUpSafe() self.post = Post() self.post.server = self.server self.post.path = '/post1' self.post.data = {'foo': 'bar', 'bar': 'baz'} self.post.save() UserPostRelation.create(source=self.abraham, type='wrote', target=self.post)
def test_all(self): with self.subTest('Event is not valid yet'): with self.assertRaises(UnauthorizedAction): process_event(self.event) with self.subTest('Post not created'): with self.assertRaises(Post.DoesNotExist): Post.select().join(Server).where(Server.name == 'aaa', Post.path == '/post1').get() with self.subTest('Unverified signature saved'): signatures: List[UnverifiedSignature] = list( UnverifiedSignature.select().where( UnverifiedSignature.event == self.event)) self.assertEqual(len(signatures), 1) self.assertEqual(signatures[0].user, 'abraham@aaa') self.assertIsNone(signatures[0].post) self.assertEqual(self.event.signatures['abraham@aaa'], signatures[0].data.hex()) # And now event becomes valid with self.subTest(): self.load_abraham2() process_event(self.event) with self.subTest('Post created'): post: Post = Post.select().join(Server).where( Server.name == 'aaa', Post.path == '/post1').get() self.assertEqual(self.event.diff, post.data) with self.subTest('Unverified signature deleted'): with self.assertRaises(UnverifiedSignature.DoesNotExist): UnverifiedSignature.select().where( UnverifiedSignature.event == self.event).get() with self.subTest('Verified signature saved'): signatures: List[Signature] = list( Signature.select().where(Signature.event == self.event)) self.assertEqual(len(signatures), 1) self.assertEqual(signatures[0].keypair, self.abraham_keypair) self.assertEqual(signatures[0].post, post) self.assertEqual(self.event.signatures['abraham@aaa'], signatures[0].data.hex())
class TestSigningPost(_TestSigning): def setUpSafe(self): super().setUpSafe() self.post = Post() self.post.server = self.server self.post.path = '/post1' self.post.data = {'foo': 'bar', 'bar': 'baz'} self.post.save() UserPostRelation.create(source=self.abraham, type='wrote', target=self.post) def test_sign(self): self.assertEqual(sign_post(self.post, self.abraham_keypair), self.expected) def test_verify(self): verify_post(self.post, 'abraham@aaa', self.expected)
def setUpSafe(self): super().setUpSafe() self.post = Post() self.post.server = self.server self.post.authors = [self.abraham] self.post.path = '/post1' self.post.data = { 'title': 'Hello', 'subtitle': 'A hello world post', 'body': 'Hello, World!', } self.post.save() event = Event() event.server = 'aaa' event.authors = ['abraham@aaa'] event.path = '/post1' event.diff = { 'title': 'Hello (updated)', 'subtitle': None, 'tags': ['Aaa', 'Bbb', 'Ccc'], } event.signatures = { 'abraham@aaa': sign_post(event, self.abraham_keypair, data={ 'title': 'Hello (updated)', 'body': 'Hello, World!', 'tags': ['Aaa', 'Bbb', 'Ccc'], }).hex(), } event.save() process_event(event) self.post.reload()
def setUpSafe(self): super().setUpSafe() self.server_a: Server = Server.create(name='A') self.server_b: Server = Server.create(name='B') self.user_a1: User = User.create(server=self.server_a, name='A1') self.user_a2: User = User.create(server=self.server_a, name='A2') self.user_b1: User = User.create(server=self.server_b, name='B1') self.user_b2: User = User.create(server=self.server_b, name='B2') self.posts: Iterable[Post] = ( Post.create(server=self.server_a, user=self.user_a1, path='/post1', data={'body': 'Server A, Post #1'}), Post.create(server=self.server_a, user=self.user_a1, path='/post2', data={'body': 'Server A, Post #2'}), Post.create(server=self.server_a, user=self.user_a2, path='/post3', data={'body': 'Server A, Post #3'}), Post.create(server=self.server_a, user=self.user_a2, path='/post4', data={'body': 'Server A, Post #4'}), Post.create(server=self.server_b, user=self.user_b1, path='/post1', data={'body': 'Server B, Post #1'}), Post.create(server=self.server_b, user=self.user_b1, path='/post2', data={'body': 'Server B, Post #2'}), Post.create(server=self.server_b, user=self.user_b2, path='/post3', data={'body': 'Server B, Post #3'}), Post.create(server=self.server_b, user=self.user_b2, path='/post4', data={'body': 'Server B, Post #4'}), )
def test_post_created(self): post: Post = Post.select().join(Server).where( Server.name == 'aaa', Post.path == '/post1').get() with self.subTest(what='post content'): self.assertEqual(self.event.diff, post.data) with self.subTest(what='unverified signature'): us_list = list(UnverifiedSignature.select().where( UnverifiedSignature.post == post)) self.assertEqual(len(us_list), 1) us = us_list[0] with self.subTest(field='event'): self.assertEqual(us.event_id, self.event.id) with self.subTest(field='user'): self.assertEqual(us.user, 'bernard@aaa') with self.subTest(field='data'): self.assertEqual(us.data.hex(), self.event.signatures['bernard@aaa'])
def process_event(event: Event): # Users whose approvals we need for this post required_users: Set[User] = set() # Create or update the post with data provided in this event if event.path: server: Server = Server.get(Server.name == event.server) try: post = Post.get(Post.server == server, Post.path == event.path) except Post.DoesNotExist: post = Post(server=server, path=event.path) # Add/replace elements from the event, # remove elements which are nulls in the information provided by event for key, value in event.diff.items(): if value is not None: post.data[key] = value elif key in post.data: del post.data[key] # Save relations for user_string in event.authors: user = User.from_string(user_string) UserPostRelation.create_after(post, source=user, type='wrote', target=post) required_users.add(user) if event.parent: parent = get_post_by_identifier(event.parent) PostPostRelation.create_after(post, source=post, type='comments', target=parent) verified_signatures: List[Dict] = [] unverified_signatures: List[Dict] = [] verified_users: Set[User] = set() unverified_usernames: Set[str] = set() # Try to verify and save as many signatures as possible for user_string, signature_hex in event.signatures.items(): signature_data = bytes.fromhex(signature_hex) try: keypair = verify_post(post, user_string, signature_data, authors=event.authors) verified_signatures.append( dict(event=event, data=signature_data, keypair=keypair)) verified_users.add(keypair.user) except rsa.VerificationError: unverified_signatures.append( dict(event=event, data=signature_data, user=user_string)) unverified_usernames.add(user_string) # If we got all approvals, then we save post and fill post_id in all the signatures success = False if verified_users >= required_users: success = True post.save() for signature_data in chain(verified_signatures, unverified_signatures): signature_data['post'] = post # If we have already analyzed this event in the past # but had some user's signatures unverified and now some of them became verified, # delete the old unverified instances (UnverifiedSignature.delete().where( UnverifiedSignature.event == event).where( UnverifiedSignature.user << [repr(u) for u in verified_users]).execute()) # Save the signatures # We need them no matter what, even if we are going to raise the exception if verified_signatures: Signature.insert_many(verified_signatures).execute() if unverified_signatures: UnverifiedSignature.insert_many(unverified_signatures).execute() # Raise exception if post was not verified and therefore created/updated if not success: raise UnauthorizedAction(required_users, verified_users, unverified_usernames)
def test_all(self): post: Post = Post.select().join(Server).where( Server.name == 'aaa', Post.path == '/post1').get() self.assertEqual(self.event.diff, post.data)
def test_comment_created(self): comment = Post.select().join(Server).where( Server.name == 'aaa', Post.path == '/post1#c1').get() PostPostRelation.get(PostPostRelation.source == comment, PostPostRelation.type == 'comments', PostPostRelation.target == self.post)