From 82aa16698668375881e9edcab46965e4be3f5b83 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Mon, 5 Nov 2018 10:36:02 +0200 Subject: Add: owner metadata --- muck/authz.py | 3 +++ muck/authz_tests.py | 7 +++++++ muck_poc | 15 +++++++++------ yarns/100-happy.yarn | 4 ++++ yarns/900-implements.yarn | 19 ++++++++++++------- yarns/lib.py | 26 ++++++++++++-------------- 6 files changed, 47 insertions(+), 27 deletions(-) diff --git a/muck/authz.py b/muck/authz.py index a9d5dda..bafd880 100644 --- a/muck/authz.py +++ b/muck/authz.py @@ -34,6 +34,9 @@ class AuthorizationChecker: required_scopes = set(required_scopes) return scopes.intersection(required_scopes) == required_scopes + def get_claims_from_token(self, r): + return self._get_token(r) + def _get_token(self, r): authz = r.get_authorization() try: diff --git a/muck/authz_tests.py b/muck/authz_tests.py index fffb96b..37ada26 100644 --- a/muck/authz_tests.py +++ b/muck/authz_tests.py @@ -52,3 +52,10 @@ class AuthorizationCheckerTests(unittest.TestCase): def test_allows_for_acceptable_request(self): r = self.create_request(['foo']) self.assertTrue(self.ac.request_is_allowed(r, 'GET', ['foo'])) + + def test_returns_claims_from_token(self): + r = self.create_request(['foo']) + expected = { + 'scope': 'foo', + } + self.assertTrue(self.ac.get_claims_from_token(r), expected) diff --git a/muck_poc b/muck_poc index 4b19266..3da1b4a 100755 --- a/muck_poc +++ b/muck_poc @@ -79,23 +79,25 @@ class MuckAPI: r = muck.Request(method=bottle.request.method) r.add_headers(bottle.request.headers) if self._ac.request_is_allowed(r, req_method, [req_scope]): - return callback() + claims = self._ac.get_claims_from_token(r) + return callback(claims) logging.error('Access denied') return bottle.HTTPError(401) return check_authz - def _create_res(self): + def _create_res(self, claims): res = self._get_json_body() meta = { 'id': self._gen.new_id(), 'rev': self._gen.new_id(), + 'owner': claims.get('sub'), } create = muck.CreateChange(meta, res) self._store.change(create) return self._create_response(201, 'create', meta, res) - def _update_res(self): + def _update_res(self, claims): rid = self._get_resource_id() try: meta, _ = self._get_existing(rid) @@ -112,7 +114,7 @@ class MuckAPI: self._store.change(update) return self._create_response(200, 'change', meta, res) - def _show_res(self): + def _show_res(self, claims): rid = self._get_resource_id() try: meta, res = self._get_existing(rid) @@ -120,7 +122,7 @@ class MuckAPI: return e return self._create_response(200, 'show', meta, res) - def _delete_res(self): + def _delete_res(self, claims): rid = self._get_resource_id() try: meta, res = self._get_existing(rid) @@ -130,7 +132,7 @@ class MuckAPI: self._store.change(delete) return self._create_response(200, 'delete', meta, res) - def _search_res(self): + def _search_res(self, claims): body = self._get_json_body() cond = body.get('cond') ms = self._store.get_memory_store() @@ -174,6 +176,7 @@ class MuckAPI: return { 'Muck-ID': meta['id'], 'Muck-Revision': meta['rev'], + 'Muck-Owner': meta['owner'], } diff --git a/yarns/100-happy.yarn b/yarns/100-happy.yarn index 6e099ce..5a5ba32 100644 --- a/yarns/100-happy.yarn +++ b/yarns/100-happy.yarn @@ -21,6 +21,7 @@ Create a simple resource. Remember its id. THEN status code is 201 THEN remember resource id as ID THEN remember resource revision as REV1 + THEN response has header "Muck-Owner: tomjon" WHEN user tomjon makes request GET /status THEN status code is 200 @@ -33,6 +34,7 @@ Retrieve the resource. THEN response body is { "foo": "bar" } THEN response has header "Muck-Id: ${ID}" THEN response has header "Muck-Revision: ${REV1}" + THEN response has header "Muck-Owner: tomjon" Update the resource. @@ -54,6 +56,7 @@ Check the resource has been updated. THEN response body is { "foo": "foobar" } THEN response has header "Muck-Id: ${ID}" THEN response has header "Muck-Revision: ${REV2}" + THEN response has header "Muck-Owner: tomjon" Restart Muck. The resource should still exist. @@ -63,6 +66,7 @@ Restart Muck. The resource should still exist. THEN response body is { "foo": "foobar" } THEN response has header "Muck-Id: ${ID}" THEN response has header "Muck-Revision: ${REV2}" + THEN response has header "Muck-Owner: tomjon" Search for the resource. First with a condition that is no longer true. diff --git a/yarns/900-implements.yarn b/yarns/900-implements.yarn index 227be26..c81d1ef 100644 --- a/yarns/900-implements.yarn +++ b/yarns/900-implements.yarn @@ -17,22 +17,22 @@ IMPLEMENTS WHEN user (\S+) makes request POST /res with body (.*) user = get_expanded_match() body = get_expanded_match() - POST('/res', {}, json.loads(body)) + POST(user, '/res', {}, json.loads(body)) IMPLEMENTS WHEN user (\S+) makes request GET /res with header "(\S+): (.+)" user = get_expanded_match() header = get_expanded_match() value = get_expanded_match() - GET('/res', {header:value}) + GET(user, '/res', {header:value}) IMPLEMENTS WHEN user (\S+) makes request GET /status user = get_expanded_match() - GET('/status', {}) + GET(user, '/status', {}) IMPLEMENTS WHEN user (\S+) makes request GET /search with body (.+) user = get_expanded_match() body = json.loads(get_expanded_match()) - GET('/search', {}, body=body) + GET(user, '/search', {}, body=body) IMPLEMENTS WHEN user (\S+) makes request PUT /res with header "(\S+): (.+)" and header "(\S+): (.+)" and body (.+) user = get_expanded_match() @@ -45,13 +45,13 @@ header1: value1, header2: value2, } - PUT('/res', headers, json.loads(body)) + PUT(user, '/res', headers, json.loads(body)) IMPLEMENTS WHEN user (\S+) makes request DELETE /res with header "(\S+): (.+)" user = get_expanded_match() header = get_expanded_match() value = get_expanded_match() - DELETE('/res', {header:value}) + DELETE(user, '/res', {header:value}) ## Checking HTTP responses @@ -70,7 +70,12 @@ IMPLEMENTS THEN response has header "(\S+): (.+)" name = get_next_match() expected = get_expanded_match() - assertEqual(get_header(name), expected) + actual = get_header(name) + print 'header:', name + print 'actual:', repr(actual) + print 'expected:', repr(expected) + print 'response headers:', V['response_headers'] + assertEqual(actual, expected) IMPLEMENTS THEN response body is (.+) expected = get_expanded_match() diff --git a/yarns/lib.py b/yarns/lib.py index ee36f9f..583dff5 100644 --- a/yarns/lib.py +++ b/yarns/lib.py @@ -59,21 +59,18 @@ def start_muck(): subprocess.check_call(argv) V['base_url'] = 'http://127.0.0.1:{}'.format(12765) - V['token'] = create_test_token() - def stop_muck(): pid = int(read('muck.pid')) os.kill(pid, signal.SIGTERM) -def create_test_token(): +def create_test_token(sub): key_filename = os.path.join(srcdir, 'test-key') key_text = open(key_filename).read() iss = 'test-issuer' aud = 'test-audience' - sub = 'test-user' scopes = ['create', 'update', 'show', 'delete'] lifetime = 3600 @@ -95,28 +92,29 @@ def create_token(key_text, iss, aud, sub, scopes, lifetime): return token.decode('ascii') -def POST(path, headers, body): - return request(requests.post, path, headers, body) +def POST(sub, path, headers, body): + return request(sub, requests.post, path, headers, body) -def PUT(path, headers, body): - return request(requests.put, path, headers, body) +def PUT(sub, path, headers, body): + return request(sub, requests.put, path, headers, body) -def GET(path, headers, body=None): - return request(requests.get, path, headers, body=body) +def GET(sub, path, headers, body=None): + return request(sub, requests.get, path, headers, body=body) -def DELETE(path, headers): - return request(requests.delete, path, headers) +def DELETE(sub, path, headers): + return request(sub, requests.delete, path, headers) -def request(func, path, headers, body=None): +def request(sub, func, path, headers, body=None): url = '{}{}'.format(V['base_url'], path) if 'Content-Type' not in headers: headers['Content-Type'] = json_mime_type if 'Authorization' not in headers: - headers['Authorization'] = 'Bearer {}'.format(V['token']) + token = create_test_token(sub) + headers['Authorization'] = 'Bearer {}'.format(token) if body is not None: body = json.dumps(body) V['request_url'] = url -- cgit v1.2.1