def init_service_mocks( self, wflows_rsp_q: asyncio.Queue = None, tadm_rsp_q: asyncio.Queue = None, ) -> mockserver.MockServer: # Very simple tornado request handler for tenantadm and workflows that # generates responses from items pushed onto an asyncio.Queue object, # the object can either be a callable or a 3-tuple with (code, header, # body). Callables are passed 'self' (RequestHandler) as argument. class RequestHandler(tornado.web.RequestHandler): def initialize(self, rsp_q): self.rsp_q = rsp_q def prepare(self): rsp = self.rsp_q.get_nowait() if callable(rsp): rsp(self) else: (status, hdr, body) = rsp self.set_status(status) for key, val in hdr.items(): self.add_header(key, val) if body: self.write(body) self.finish() with mockserver.run_fake(get_fake_tenantadm_addr()) as tadm: tadm.app.add_handlers( r".*", [( r".*", RequestHandler, { "rsp_q": tadm_rsp_q }, )], ) with mockserver.run_fake(get_fake_workflows_addr()) as wflows: wflows.app.add_handlers( r".*", [( r".*", RequestHandler, { "rsp_q": wflows_rsp_q }, )], ) yield tadm, wflows
def test_auth_req_fake_tenantadm_tenant_suspended(self, management_api, device_api, tenant_foobar, clean_migrated_db): d = Device() da = DevAuthorizer(tenant_token=tenant_foobar) url = device_api.auth_requests_url handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (401, {}, { 'request_id': 'test', 'error': 'account suspended' })), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 assert rsp.json()['error'] == 'account suspended' # request failed, so device should not even be listed as known for the # default tenant TestEnterprise.verify_tenant_dev_present(management_api, d.identity, '', present=False)
def test_auth_req_fake_tenantadm_valid_tenant_token( self, management_api, device_api, tenant_foobar): d = Device() da = DevAuthorizer(tenant_token=tenant_foobar) url = device_api.auth_requests_url handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (200, {}, { 'id': '507f191e810c19729de860ea', 'name': 'Acme', })), ] try: with orchestrator.run_fake_for_device_id(1) as server: with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 except bravado.exception.HTTPError as e: assert e.response.status_code == 204 # device should be appear in devices listing TestEnterprise.verify_tenant_dev_present(management_api, d.identity, tenant_foobar, present=True)
def run_fake_for_device_id(devid): handlers = [ ('POST', '/api/0.1.0/devices', device_add(devid)), ] with mockserver.run_fake(get_fake_inventory_addr(), handlers=handlers) as server: yield server
def run_fake_preauth(id_data, pubkey, ret_status): handlers = [('POST', '/api/management/v1/devauth/devices', handler_preauth(id_data, pubkey, ret_status))] with mockserver.run_fake(get_fake_deviceauth_addr(), handlers=handlers) as server: yield server
def run_fake_user_tenants(tenant_users, status='active'): """ Runs GET /tenants?username=<user>. Takes a dict of `{tenant: list of users}`, returns valid tenant with `tenant_id` for `user`, otherwise an empty list. Returned tenant will have the 'status' field set accordingly. """ user_to_tenant = {} for tenant, users in tenant_users.items(): for user in users: user_to_tenant[user] = tenant def fake_get_tenants_for_user(request): # extract username from query parameter user_args = request.arguments.get('username', '') req_user = user_args[0].decode() if len(user_args) > 0 else '' tenant = user_to_tenant.get(req_user, '') if tenant: return (200, {}, json.dumps([{"id": tenant, "status": status, "name": "foo", "tenant_token": "sometoken"}])) else: return (200, {}, '[]') handlers = [ ('GET', '/api/internal/v1/tenantadm/tenants', fake_get_tenants_for_user), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as server: yield server
def run_fake_create_user(user, status=201): handlers = [ ("POST", "/api/internal/v1/tenantadm/users", fake_create_user(user, status)) ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as server: yield server
def run_fake_for_device(device): handlers = [ ('PUT', '/api/0.1.0/devices/(.*)', auth_set_put_for_device(device)), ] with mockserver.run_fake(get_fake_deviceadm_addr(), handlers=handlers) as server: yield server
def tenantadm_fake_tenant_verify(): handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (200, {}, '')), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: yield fake
def run_fake_delete_device(devid, aid, ret_status): handlers = [ ('DELETE', '/api/management/v1/devauth/devices/(.*)/auth/(.*)', handler_delete_device(devid, aid, ret_status)), ] with mockserver.run_fake(get_fake_deviceauth_addr(), handlers=handlers) as server: yield server
def run_fake_delete_user(expected_tenant_id=None, expected_user_id=None): handlers = [('DELETE', '/api/internal/v1/tenantadm/tenants/(.*)/users/(.*)', fake_delete_user(expected_tenant_id, expected_user_id))] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as server: yield server
def run_fake_update_authset_status(devid, aid, status, ret_status): handlers = [ ('PUT', '/api/management/v1/devauth/devices/(.*)/auth/(.*)/status', handler_update_authset_status(devid, aid, status, ret_status)), ] with mockserver.run_fake(get_fake_deviceauth_addr(), handlers=handlers) as server: yield server
def run_fake_get_tenants(tenant_id, status=200): """ Runs just the GET /tenants endpoint for tests that don't need anything more. """ handlers = [ ("GET", "/api/internal/v1/tenantadm/tenants", fake_get_tenants(tenant_id, 200)) ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as server: yield server
def run_fake_for_device_id(devid): handlers = [ ('POST', '/api/workflow/provision_device', provision_device_handler(devid)), ('POST', '/api/workflow/decommission_device', decommission_device_handler(devid)), ] with mockserver.run_fake(get_fake_orchestrator_addr(), handlers=handlers) as server: yield server
def run_fake_update_user(tenant_id, id, update, status=204): """ Runs the update endpoint *and* the GET /tenants endpoint - it's packaged together because some tests will verify login after an update. """ handlers = [ ('PUT', '/api/internal/v1/tenantadm/tenants/'+tenant_id+'/users/'+id, fake_update_user(update, status)), ('GET', '/api/internal/v1/tenantadm/tenants', fake_get_tenants(tenant_id, 200)) ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as server: yield server
def devadm_fake_status_update(authset_id): def fake_status_update(request, aid): assert aid == authset_id return (200, {}, '') handlers = [ ('PUT', '/api/internal/v1/admission/devices/(.*)/status', fake_status_update), ] with mockserver.run_fake(deviceadm.get_fake_deviceadm_addr(), handlers=handlers) as server: yield server
def run_fake_for_device_id(devid, status=None): if status is None: handlers = [ ( "POST", "/api/v1/workflow/provision_device", provision_device_handler(devid), ), ( "POST", "/api/v1/workflow/decommission_device", decommission_device_handler(devid), ), ( "POST", "/api/v1/workflow/update_device_status", update_device_status_handler(devid), ), ( "POST", "/api/v1/workflow/update_device_inventory", update_device_inventory_handler(devid), ), ] else: handlers = [ ( "POST", "/api/v1/workflow/provision_device", provision_device_handler(devid, status), ), ( "POST", "/api/v1/workflow/decommission_device", decommission_device_handler(devid, status), ), ( "POST", "/api/v1/workflow/update_device_status", update_device_status_handler(devid, status), ), ( "POST", "/api/v1/workflow/update_device_inventory", update_device_inventory_handler(devid, status), ), ] with mockserver.run_fake(get_fake_orchestrator_addr(), handlers=handlers) as server: yield server
def test_delete_device(self): # try delete a nonexistent device try: self.delete_device('some-devid-foo') except bravado.exception.HTTPError as e: assert e.response.status_code == 404 # try delete an existing device, verify decommissioning workflow was started # setup single device and poke devauth dev = Device() da = DevAuthorizer() # poke devauth so that device appears with deviceadm.run_fake_for_device(dev) as server: rsp = device_auth_req(self.devapi.make_api_url("auth_requests"), da, dev) assert rsp.status_code == 401 mc = SimpleManagementClient() ourdev = mc.find_device_by_identity(dev.identity) assert ourdev # handler for orchestrator's job endpoint def decommission_device_handler(request): dreq = json.loads(request.body.decode()) self.log.info('decommision request %s', dreq) # verify that devauth tries to decommision correct device assert dreq.get('device_id', None) == ourdev.id # test is enforcing particular request ID assert dreq.get('request_id', None) == 'delete_device' # test is enforcing particular request ID assert dreq.get('authorization', None) == 'Bearer foobar' return (200, {}, '') handlers = [ ('POST', '/api/workflow/decommission_device', decommission_device_handler), ] with mockserver.run_fake(get_fake_orchestrator_addr(), handlers=handlers) as server: rsp = self.delete_device( ourdev.id, { 'X-MEN-RequestID': 'delete_device', 'Authorization': 'Bearer foobar', }) self.log.info('decommission request finished with status: %s', rsp.status_code) assert rsp.status_code == 204 found = mc.find_device_by_identity(dev.identity) assert not found
def request_token(device, dev_auth, url): handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (200, {}, { 'id': '507f191e810c19729de860ea', 'name': 'Acme', })), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, dev_auth, device) assert rsp.status_code == 200 dev_auth.parse_rsp_payload(device, rsp.text) return device.token
def test_auth_req_fake_tenantadm_invalid_tenant_token(self): d = Device() da = DevAuthorizer(tenant_token="bad-token") url = self.devapi.make_api_url("/auth_requests") handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (401, {}, '')), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 # request failed, so device should not even be listed as known self.verify_tenant_dev_present(d.identity, False, tenant='')
def tenant_foobar_devices(device_api, management_api, tenant_foobar, request): """Make unauthorized devices owned by tenant with ID 'foobar'. The fixture can be parametrized a number of devices to make. Yields a list of tuples: (instance of Device, instance of DevAuthorizer) """ handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (200, {}, '')), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: if not hasattr(request, 'param'): devcount = 1 else: devcount = int(request.param) yield make_devices(device_api, devcount, tenant_token=tenant_foobar)
def test_auth_req_fake_tenantadm_invalid_tenant_token(self, management_api, device_api, clean_migrated_db): d = Device() da = DevAuthorizer(tenant_token="bad-token") url = device_api.auth_requests_url handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (401, {}, '')), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 # request failed, so device should not even be listed as known for the # default tenant TestMultiTenant.verify_tenant_dev_present(management_api, d.identity, '', present=False)
def test_auth_req_fake_tenantadm_valid_tenant_token(self): d = Device() da = DevAuthorizer(tenant_token=make_fake_tenant_token( tenant='foobar')) url = self.devapi.make_api_url("/auth_requests") handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (200, {}, { 'id': '507f191e810c19729de860ea', 'name': 'Acme', })), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: with deviceadm.run_fake_for_device(d) as fakedevadm: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 # device should be appear in devices listing self.verify_tenant_dev_present(d.identity, tenant='foobar')
def mock_tenantadm_auth(tenant_addons): def tenantadm_handler(req): auth = req.headers["Authorization"] # jwt = <header (base64)>.<claims (base64)>.<signature (base64)> jwt_b64 = auth.split(".") if len(jwt_b64) > 1: print(jwt_b64) # Convert base64 from url- to std-encoding and append padding claims_b64 = jwt_b64[1].replace("+", "-").replace("?", "_") # Add padding claims_b64 += "=" * (-len(claims_b64) % 4) # Decode claims claims = base64.b64decode(claims_b64) d = json.loads(claims) tenant_id = d["mender.tenant"] return ( 200, {}, { "id": tenant_id, "name": "Acme", "addons": [{ "name": addon, "enabled": True } for addon in tenant_addons], }, ) else: return (500, {}, {}) with mockserver.run_fake( get_fake_tenantadm_addr(), handlers=[("POST", "/api/internal/v1/tenantadm/tenants/verify", tenantadm_handler)], ) as srv: yield srv
def run_fake_for_device_id(devid, status=None): if status is None: handlers = [ ('POST', '/api/v1/workflow/provision_device', provision_device_handler(devid)), ('POST', '/api/v1/workflow/decommission_device', decommission_device_handler(devid)), ('POST', '/api/v1/workflow/update_device_status', update_device_status_handler(devid)), ] else: handlers = [ ('POST', '/api/v1/workflow/provision_device', provision_device_handler(devid, status)), ('POST', '/api/v1/workflow/decommission_device', decommission_device_handler(devid, status)), ('POST', '/api/v1/workflow/update_device_status', update_device_status_handler(devid, status)), ] with mockserver.run_fake(get_fake_orchestrator_addr(), handlers=handlers) as server: yield server
def test_auth_req_fake_tenantadm_valid_tenant_token( self, management_api, device_api, tenant_foobar): d = Device() da = DevAuthorizer(tenant_token=tenant_foobar) url = device_api.auth_requests_url handlers = [ ('POST', '/api/internal/v1/tenantadm/tenants/verify', lambda _: (200, {}, { 'id': '507f191e810c19729de860ea', 'name': 'Acme', })), ] with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 # device should be appear in devices listing TestMultiTenant.verify_tenant_dev_present(management_api, d.identity, tenant_foobar, present=True)
def test_delete_device(self, management_api, devices): # try delete an existing device, verify decommissioning workflow was started # setup single device and poke devauth dev, _ = devices[0] ourdev = management_api.find_device_by_identity(dev.identity) assert ourdev # handler for orchestrator's job endpoint def decommission_device_handler(request): dreq = json.loads(request.body.decode()) print('decommision request', dreq) # verify that devauth tries to decommision correct device assert dreq.get('device_id', None) == ourdev.id # test is enforcing particular request ID assert dreq.get('request_id', None) == 'delete_device' # test is enforcing particular request ID assert dreq.get('authorization', None) == 'Bearer foobar' return (200, {}, '') handlers = [ ('POST', '/api/workflow/decommission_device', decommission_device_handler), ] with mockserver.run_fake(get_fake_orchestrator_addr(), handlers=handlers) as server: rsp = management_api.delete_device( ourdev.id, { 'X-MEN-RequestID': 'delete_device', 'Authorization': 'Bearer foobar', }) print('decommission request finished with status:', rsp.status_code) assert rsp.status_code == 204 found = management_api.find_device_by_identity(dev.identity) assert not found
def accepted_tenants_devices(device_api, management_api, clean_migrated_db, cli, request): """Fixture that sets up an accepted devices for tenants. The fixture can be parametrized with a tenants, number of devices and number of authentication sets. Yields a dict: [tenant ID: [device object, ...], ]""" requested = request.param tenants_devices = dict() url = device_api.auth_requests_url for (tenant, dev_count, auth_count) in requested: tenant_devices = [] cli.migrate(tenant=tenant) tenant_token = make_fake_tenant_token(tenant) for _ in range(int(dev_count)): d = Device() for i in range(int(auth_count)): d.rotate_key() da = DevAuthorizer(tenant_token=tenant_token) # poke devauth so that device appears handlers = [ ( "POST", "/api/internal/v1/tenantadm/tenants/verify", lambda _: ( 200, {}, { "id": "507f191e810c19729de860ea", "name": "Acme", }, ), ), ] try: with orchestrator.run_fake_for_device_id(1) as server: with mockserver.run_fake(get_fake_tenantadm_addr(), handlers=handlers) as fake: rsp = device_auth_req(url, da, d) assert rsp.status_code == 401 except bravado.exception.HTTPError as e: assert e.response.status_code == 204 # try to find our devices in all devices listing dev = management_api.find_device_by_identity( d.identity, Authorization="Bearer " + tenant_token) devid = dev.id for a in dev.auth_sets: if compare_keys(a.pubkey, d.public_key): aid = a.id break try: with orchestrator.run_fake_for_device_id(devid) as server: management_api.accept_device(devid, aid, Authorization="Bearer " + tenant_token) token = request_token(d, da, device_api.auth_requests_url) assert len(token) > 0 except bravado.exception.HTTPError as e: assert e.response.status_code == 204 assert dev tenant_devices.append(d) tenants_devices[tenant] = tenant_devices yield tenants_devices