summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-11-05 09:56:36 +0200
committerLars Wirzenius <liw@liw.fi>2018-11-05 09:56:36 +0200
commitdf351c2661cd838e1b7de521f1fcd4f84acdf4a0 (patch)
tree5ebbab360420bcd299d53ddd330b654fc67837e4
parent5a74ba9553fd21465e04dd4ace7007e2f865d7f1 (diff)
downloadqvisqve-df351c2661cd838e1b7de521f1fcd4f84acdf4a0.tar.gz
Add: sub fields to clients, tokens created by client-cred grant
-rw-r--r--NEWS5
-rw-r--r--qvisqve/authn_entity_manager.py9
-rw-r--r--qvisqve/authn_entity_manager_tests.py15
-rw-r--r--qvisqve/token_router.py3
-rw-r--r--yarns/200-client-creds.yarn29
-rw-r--r--yarns/900-local.yarn4
-rw-r--r--yarns/lib.py1
7 files changed, 65 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index d6729ac..cd65c84 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,11 @@ Version 0.10+git, not yet released
* Change: the HTML code for the login form is now in a separate file,
so it is easier to style.
+* API clients may now have a `sub` field, and if they do, tokens
+ created using the client credentials grant variant of OAuth2 get
+ their `sub` claim set accordingly. This is a first step towards
+ allowing users to authorize API clients to act on their behalf.
+
Version 0.9, released 2018-02-09
---------------------------------
diff --git a/qvisqve/authn_entity_manager.py b/qvisqve/authn_entity_manager.py
index f84634c..e3d454a 100644
--- a/qvisqve/authn_entity_manager.py
+++ b/qvisqve/authn_entity_manager.py
@@ -71,6 +71,15 @@ class ClientManager(AuthenticatingEntityManager):
def __init__(self, rs):
super().__init__(rs, 'client')
+ def get_subject(self, username):
+ user = self.get(username)
+ return user.get('sub')
+
+ def set_subject(self, username, sub):
+ user = self.get(username)
+ user['sub'] = sub
+ self.create(username, user)
+
class UserManager(AuthenticatingEntityManager):
diff --git a/qvisqve/authn_entity_manager_tests.py b/qvisqve/authn_entity_manager_tests.py
index 0d51d4b..579791f 100644
--- a/qvisqve/authn_entity_manager_tests.py
+++ b/qvisqve/authn_entity_manager_tests.py
@@ -94,6 +94,21 @@ class ClientManagerTests(unittest.TestCase):
self.cm.set_secret(client['id'], secret)
self.assertTrue(self.cm.is_valid_secret(client['id'], secret))
+ def test_has_no_subject_initially(self):
+ client = {
+ 'id': 'test-client',
+ }
+ self.cm.create(client['id'], client)
+ self.assertEqual(self.cm.get_subject(client['id']), None)
+
+ def test_sets_subject(self):
+ client = {
+ 'id': 'test-client',
+ }
+ self.cm.create(client['id'], client)
+ self.cm.set_subject(client['id'], 'tomjon')
+ self.assertEqual(self.cm.get_subject(client['id']), 'tomjon')
+
class UserManagerTests(unittest.TestCase):
diff --git a/qvisqve/token_router.py b/qvisqve/token_router.py
index 4778063..bd3744c 100644
--- a/qvisqve/token_router.py
+++ b/qvisqve/token_router.py
@@ -106,7 +106,8 @@ class ClientCredentialsGrant(Grant):
if s in allowed
)
- token = self._generator.new_token(client_id, scope)
+ sub = self._clients.get_subject(client_id)
+ token = self._generator.new_token(client_id, scope, subject_id=sub)
return qvisqve.ok_response({
'access_token': token,
'token_type': 'Bearer',
diff --git a/yarns/200-client-creds.yarn b/yarns/200-client-creds.yarn
index 9eff22a..418e9b9 100644
--- a/yarns/200-client-creds.yarn
+++ b/yarns/200-client-creds.yarn
@@ -102,3 +102,32 @@ scopes, as described above.
AND token expires in an hour
FINALLY Qvisqve is stopped
+
+API client tied to a subject
+-----------------------------------------------------------------------------
+
+ SCENARIO get token using client credentials tied to a subject
+
+ GIVEN an API client "bigco"
+ AND API client has secret "secrit"
+ AND API client has allowed scopes "read write"
+ AND API client has subject "tomjon"
+
+ AND a Qvisqve configuration for "https://qvisqve.example.com"
+ AND Qvisqve configuration has a token lifetime of 3600
+ AND a running Qvisqve instance
+
+ WHEN client requests POST /token
+ ... with client_id "bigco", client_secret "secrit", and
+ ... scopes "read write delete"
+
+ THEN HTTP status code is 200 OK
+ AND Content-Type is application/json
+ AND body is a correctly signed JWT token
+ AND token has claim iss as "https://qvisqve.example.com"
+ AND token has claim sub as "tomjon"
+ AND token has claim aud as "bigco"
+ AND token has claim scope as "read write"
+ AND token expires in an hour
+
+ FINALLY Qvisqve is stopped
diff --git a/yarns/900-local.yarn b/yarns/900-local.yarn
index cae5db1..276e656 100644
--- a/yarns/900-local.yarn
+++ b/yarns/900-local.yarn
@@ -31,6 +31,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
scopes = get_next_match()
V['allowed_scopes'] = scopes.split()
+ IMPLEMENTS GIVEN API client has subject "(.+)"
+ sub = get_next_match()
+ V['sub'] = sub
+
IMPLEMENTS GIVEN a Qvisqve configuration for "(.+)"
V['iss'] = get_next_match()
diff --git a/yarns/lib.py b/yarns/lib.py
index a9ba526..9d57143 100644
--- a/yarns/lib.py
+++ b/yarns/lib.py
@@ -190,6 +190,7 @@ def start_qvisqve():
client = {
'hashed_secret': sh.hash(V['client_secret']),
'allowed_scopes': V['allowed_scopes'],
+ 'sub': V['sub'],
}
filename = os.path.join(store, 'client', V['client_id'])