async def test_missing_field(client): data = { 'address': '*****@*****.**', 'timestamp': datetime.now(), } r = await client.post('/123/messages/add/', data=encoding.encode(data), headers=AUTH_HEADER) assert r.status == 400 assert await r.text() == '{"event_id": ["required field"]}\n'
async def test_authenticate_wrong_fields(client): data = { 'platform': PLATFORM, 'timestamp': TIMESTAMP, } r = await client.post('/authenticate', data=encoding.encode(data)) assert r.status == 400 assert await r.text() == '{"signature": ["required field"]}\n'
async def test_authenticate_failed(client): data = { 'platform': 'wham.com', 'timestamp': TIMESTAMP, 'signature': VALID_SIGNATURE } r = await client.post('/authenticate', data=encoding.encode(data)) assert r.status == 400 assert await r.text() == 'invalid signature\n'
async def _push_data(self, domains, action_attrs, event_id, **kwargs): action_attrs['item'] = action_attrs['item'] or '' path = '{conv}/{component}/{verb}/{item}'.format(**action_attrs) post_data = { 'address': action_attrs['address'], 'timestamp': action_attrs['timestamp'], 'event_id': event_id, 'kwargs': kwargs, } post_data = encoding.encode(post_data) cos = [self._post(domain, path, post_data) for domain in domains] # TODO better error checks await asyncio.gather(*cos, loop=self.loop)
async def test_domain_miss_match(client): data = { 'address': '*****@*****.**', 'timestamp': datetime.now(), 'event_id': '123', 'kwargs': { 'parent_id': '123', 'body': 'reply', } } r = await client.post('/123/messages/add/', data=encoding.encode(data), headers=AUTH_HEADER) assert await r.text() == '"example.com" does not use "already-authenticated.com"\n' assert r.status == 403
async def test_authenticate(client): data = { 'platform': PLATFORM, 'timestamp': TIMESTAMP, 'signature': VALID_SIGNATURE } r = await client.post('/authenticate', data=encoding.encode(data)) assert r.status == 201 r = encoding.decode(await r.read()) assert isinstance(r, dict) assert list(r.keys()) == ['key'] assert r['key'].startswith('foobar.com:2461536000:') assert len(r['key']) == 86
async def _authenticate_direct(self, domain, data): url = 'https://{}/authenticate'.format(domain) # TODO more error checks try: async with self.session.post(url, data=encoding.encode(data), headers=JSON_HEADER) as r: body = await r.read() except aiohttp.ClientOSError as e: # generally "could not resolve host" or "connection refused", # the exception is fairly useless at giving specifics raise ConnectionError('cannot connect to "{}"'.format(url)) from e else: if r.status != 201: raise FailedOutboundAuthentication('{} response {} != 201, response: {}'.format(url, r.status, body)) data = encoding.decode(body) return data['key']
async def auth(s): timestamp = now_unix_secs() msg = '{}:{}'.format(LOCAL_DOMAIN, timestamp) h = SHA256.new(msg.encode()) private_domain_key = Path('private8000.pem').read_text() key = RSA.importKey(private_domain_key) signer = PKCS1_v1_5.new(key) signature = base64.urlsafe_b64encode(signer.sign(h)).decode() auth_data = { 'platform': LOCAL_DOMAIN, 'timestamp': timestamp, 'signature': signature, } return await s.post('authenticate', data=encoding.encode(auth_data), headers=CT_HEADER)
async def test_missing_conversation(client): ts = datetime.now() action2 = Action('*****@*****.**', '123', Verbs.ADD, Components.MESSAGES, timestamp=ts) data = { 'address': '*****@*****.**', 'timestamp': ts, 'event_id': action2.calc_event_id(), 'kwargs': { 'parent_id': '123', 'body': 'reply', } } r = await client.post('/123/messages/add/', data=encoding.encode(data), headers=AUTH_HEADER) assert r.status == 400 content = await r.read() assert content == b'ConversationNotFound: conversation 123 not found\n'
async def act(request): auth, platform = await _check_token(request) body_data = await request.read() try: timezone = pytz.timezone(request.headers.get('timezone', 'utc')) except pytz.UnknownTimeZoneError as e: raise HTTPBadRequestStr(e.args[0]) from e try: obj = encoding.decode(body_data, tz=timezone) except ValueError as e: raise HTTPBadRequestStr('Error Decoding msgpack: {}'.format(e)) from e if not isinstance(obj, dict): raise HTTPBadRequestStr('request data is not a dictionary') v = Validator(ACT_SCHEMA) if not v(obj): raise HTTPBadRequestStr(json.dumps(v.errors, sort_keys=True)) address = obj.pop('address') address_domain = address[address.index('@') + 1:] try: await auth.check_domain_platform(address_domain, platform) except DomainPlatformMismatch as e: raise HTTPForbiddenStr(e.args[0]) from e conversation = request.match_info['conv'] component = request.match_info['component'] verb = request.match_info['verb'] item = request.match_info['item'] or None timestamp = obj.pop('timestamp') kwargs = obj.pop('kwargs', {}) action = Action(address, conversation, verb, component, item=item, timestamp=timestamp, event_id=obj['event_id'], parent_event_id=obj.get('parent_event_id')) controller = request.app['controller'] try: response = await controller.act(action, **kwargs) except Em2Exception as e: raise HTTPBadRequestStr('{}: {}'.format(e.__class__.__name__, e)) body = encoding.encode(response) if response else b'\n' return web.Response(body=body, status=201, content_type=encoding.MSGPACK_CONTENT_TYPE)
async def test_add_message(client): action = Action('*****@*****.**', None, Verbs.ADD) conv_id = await client.em2_ctrl.act(action, subject='foo bar', body='hi, how are you?') conv_ds = client.em2_ctrl.ds.new_conv_ds(conv_id, None) msg1_id = list(conv_ds.conv_obj['messages'])[0] ts = action.timestamp + timedelta(seconds=1) action2 = Action('*****@*****.**', conv_id, Verbs.ADD, Components.MESSAGES, timestamp=ts) data = { 'address': '*****@*****.**', 'timestamp': ts, 'event_id': action2.calc_event_id(), 'kwargs': { 'parent_id': msg1_id, 'body': 'reply', } } r = await client.post('/{}/messages/add/'.format(conv_id), data=encoding.encode(data), headers=AUTH_HEADER) assert r.status == 201 assert await r.text() == '\n'
async def authenticate(request): logger.info('authentication request from %s', get_ip(request)) body_data = await request.read() try: obj = encoding.decode(body_data) except ValueError as e: logger.info('bad request: invalid msgpack data') raise HTTPBadRequestStr('error decoding data: {}'.format(e)) from e v = Validator(AUTHENTICATION_SCHEMA) if not v(obj): raise HTTPBadRequestStr(json.dumps(v.errors, sort_keys=True)) auth = request.app['authenticator'] try: key = await auth.authenticate_platform(obj['platform'], obj['timestamp'], obj['signature']) except FailedInboundAuthentication as e: raise HTTPBadRequestStr(e.args[0]) from e return web.Response(body=encoding.encode({'key': key}), status=201, content_type=encoding.MSGPACK_CONTENT_TYPE)
async def publish(s): response = await auth(s) verb = Verbs.ADD component = Components.CONVERSATIONS address = '*****@*****.**' ts = datetime.utcnow().replace(tzinfo=timezone.utc) conv = hash_id(address, to_unix_ms(ts), 'foo bar', sha256=True) data = { 'address': address, 'timestamp': ts, 'event_id': hash_id(to_unix_ms(ts), address, conv, verb, component, None), 'kwargs': { 'data': PUBLISH_KWARGS }, } url = f'{conv}/{component}/{verb}/' headers = dict(Authorization=response['key'], **CT_HEADER) await s.post(url, data=encoding.encode(data), headers=headers)
async def test_valid_data_list(client): r = await client.post('/123/messages/add/', data=encoding.encode([1, 2, 3]), headers=AUTH_HEADER) assert r.status == 400 assert await r.text() == 'request data is not a dictionary\n'