Skip to content

Commit

Permalink
Add support for 'scope' claim with multiple scopes
Browse files Browse the repository at this point in the history
+ add tests
  • Loading branch information
tony2001 authored and simo5 committed Jul 8, 2024
1 parent cabac91 commit f105494
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 0 deletions.
20 changes: 20 additions & 0 deletions jwcrypto/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ def _check_check_claims(self, check_claims):
self._check_string_claim('iss', check_claims)
self._check_string_claim('sub', check_claims)
self._check_array_or_string_claim('aud', check_claims)
self._check_string_claim('scope', check_claims)
self._check_integer_claim('exp', check_claims)
self._check_integer_claim('nbf', check_claims)
self._check_integer_claim('iat', check_claims)
Expand Down Expand Up @@ -556,7 +557,26 @@ def _check_provided_claims(self):
"'%s'" % (name,
claims[name],
value))
elif name == 'scope':
if value is not None:
if not isinstance(claims[name], str):
raise JWTInvalidClaimValue(
"Invalid '%s' value. Scope list has to be "
"a string, got a %s instead: %s" % (
name, type(claims[name]), str(claims[name])))

found = False
got_scopes = claims[name].split()
for s in got_scopes:
if s == value:
found = True
break

if not found:
raise JWTInvalidClaimValue(
"Invalid '%s' value. Scope list '%s' does not "
"contain the required scope '%s'" % (
name, claims[name], value))
else:
if value is not None and value != claims[name]:
raise JWTInvalidClaimValue(
Expand Down
62 changes: 62 additions & 0 deletions jwcrypto/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,68 @@ def test_unexpected(self):
jwt.JWT(jwt=enctok, key=key)
key.key_ops = None

def test_claims_scope(self):
key = jwk.JWK().generate(kty='oct')

string_header = '{"alg":"HS256"}'

# no scopes provided
claims = '{}'
t = jwt.JWT(string_header, claims)
t.make_signed_token(key)
token = t.serialize()
self.assertRaises(jwt.JWTMissingClaim, jwt.JWT, jwt=token,
key=key, check_claims={"scope": "read"})

# non-string scopes
claims = '{"scope": 12345}'
t = jwt.JWT(string_header, claims)
t.make_signed_token(key)
token = t.serialize()
self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token,
key=key, check_claims={"scope": "read"})

# empty scopes
claims = '{"scope": ""}'
t = jwt.JWT(string_header, claims)
t.make_signed_token(key)
token = t.serialize()
self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token,
key=key, check_claims={"scope": "read"})

# one correct scope
claims = '{"scope":"read"}'
t = jwt.JWT(string_header, claims)
t.make_signed_token(key)
token = t.serialize()
jwt.JWT(jwt=token, key=key, check_claims={"scope": "read"})
self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token,
key=key, check_claims={"scope": "write"})

# multiple scopes including the correct one
claims = '{"scope":"view read write"}'
t = jwt.JWT(string_header, claims)
t.make_signed_token(key)
token = t.serialize()
jwt.JWT(jwt=token, key=key, check_claims={"scope": "view"})
jwt.JWT(jwt=token, key=key, check_claims={"scope": "read"})
jwt.JWT(jwt=token, key=key, check_claims={"scope": "write"})
self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token,
key=key, check_claims={"scope": "wrong"})

# one correct scope, invalid value
claims = '{"scope":"read"}'
t = jwt.JWT(string_header, claims)
t.make_signed_token(key)
token = t.serialize()
self.assertRaises(jwt.JWTInvalidClaimFormat, jwt.JWT, jwt=token,
key=key, check_claims={"scope": 123})
self.assertRaises(jwt.JWTInvalidClaimFormat, jwt.JWT, jwt=token,
key=key, check_claims={"scope": ["test", "wrong"]})

# finally make sure it doesn't raise if not checked.
jwt.JWT(jwt=token, key=key)


class ConformanceTests(unittest.TestCase):

Expand Down

0 comments on commit f105494

Please sign in to comment.