Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-scitokens for
openSUSE:Factory checked in at 2026-04-04 19:05:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-scitokens (Old)
and /work/SRC/openSUSE:Factory/.python-scitokens.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-scitokens"
Sat Apr 4 19:05:37 2026 rev:8 rq:1344371 version:1.8.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-scitokens/python-scitokens.changes
2024-10-10 22:11:22.105997070 +0200
+++
/work/SRC/openSUSE:Factory/.python-scitokens.new.21863/python-scitokens.changes
2026-04-04 19:07:07.412402126 +0200
@@ -1,0 +2,13 @@
+Thu Apr 2 15:09:31 UTC 2026 - Markéta Machová <[email protected]>
+
+- CVE-2026-32714: usage of str.format() to construct SQL queries
+ can lead to SQL Injection (bsc#1261203)
+ * CVE-2026-32714.patch
+- CVE-2026-32716: incorrectly validates scope paths can allow
+ access sibling paths (bsc#1261202)
+ * CVE-2026-32716.patch
+- CVE-2026-32727: normalizing path before check can lead to path
+ traversal (bsc#1261201)
+ * CVE-2026-32727.patch
+
+-------------------------------------------------------------------
New:
----
CVE-2026-32714.patch
CVE-2026-32716.patch
CVE-2026-32727.patch
----------(New B)----------
New: can lead to SQL Injection (bsc#1261203)
* CVE-2026-32714.patch
- CVE-2026-32716: incorrectly validates scope paths can allow
New: access sibling paths (bsc#1261202)
* CVE-2026-32716.patch
- CVE-2026-32727: normalizing path before check can lead to path
New: traversal (bsc#1261201)
* CVE-2026-32727.patch
----------(New E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-scitokens.spec ++++++
--- /var/tmp/diff_new_pack.vjOAIC/_old 2026-04-04 19:07:07.980425415 +0200
+++ /var/tmp/diff_new_pack.vjOAIC/_new 2026-04-04 19:07:07.980425415 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-scitokens
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -25,6 +25,12 @@
License: Apache-2.0
URL: https://scitokens.org
Source:
https://github.com/scitokens/scitokens/archive/refs/tags/v%{version}.tar.gz#/%{bname}-%{version}.tar.gz
+# PATCH-FIX-UPSTREAM CVE-2026-32714.patch bsc#1261203
+Patch0: CVE-2026-32714.patch
+# PATCH-FIX-UPSTREAM CVE-2026-32716.patch bsc#1261202
+Patch1: CVE-2026-32716.patch
+# PATCH-FIX-UPSTREAM CVE-2026-32727.patch bsc#1261201
+Patch2: CVE-2026-32727.patch
BuildRequires: %{python_module PyJWT >= 2.2}
BuildRequires: %{python_module cryptography}
BuildRequires: %{python_module pip}
++++++ CVE-2026-32714.patch ++++++
>From 3dba108853f2f4a6c0f2325c03779bf083c41cf2 Mon Sep 17 00:00:00 2001
From: Derek Weitzel <[email protected]>
Date: Fri, 13 Mar 2026 10:44:27 -0500
Subject: [PATCH] Refactor KeyCache SQL queries to use parameterized statements
for security and add regression tests for SQL injection prevention
---
src/scitokens/utils/keycache.py | 31 ++++----
tests/test_keycache.py | 134 ++++++++++++++++++++++++++++++++
2 files changed, 149 insertions(+), 16 deletions(-)
Index: scitokens-1.8.1/src/scitokens/utils/keycache.py
===================================================================
--- scitokens-1.8.1.orig/src/scitokens/utils/keycache.py
+++ scitokens-1.8.1/src/scitokens/utils/keycache.py
@@ -76,7 +76,7 @@ class KeyCache(object):
conn = sqlite3.connect(self.cache_location)
conn.row_factory = sqlite3.Row
curs = conn.cursor()
- curs.execute("DELETE FROM keycache WHERE issuer = '{}' AND key_id =
'{}'".format(issuer, key_id))
+ curs.execute("DELETE FROM keycache WHERE issuer = ? AND key_id = ?",
[issuer, key_id])
KeyCache._addkeyinfo(curs, issuer, key_id, public_key,
cache_timer=cache_timer, next_update=next_update)
conn.commit()
conn.close()
@@ -87,14 +87,13 @@ class KeyCache(object):
Given an open database cursor to a key cache, insert a key.
"""
# Add the key to the cache
- insert_key_statement = "INSERT INTO keycache VALUES('{issuer}',
'{expiration}', '{key_id}', \
- '{keydata}', '{next_update}')"
+ insert_key_statement = "INSERT INTO keycache VALUES(?, ?, ?, ?, ?)"
keydata = {
'pub_key': public_key.public_bytes(Encoding.PEM,
PublicFormat.SubjectPublicKeyInfo).decode('ascii'),
}
- curs.execute(insert_key_statement.format(issuer=issuer,
expiration=time.time()+cache_timer, key_id=key_id,
- keydata=json.dumps(keydata),
next_update=time.time()+next_update))
+ curs.execute(insert_key_statement, [issuer, time.time()+cache_timer,
key_id,
+ json.dumps(keydata),
time.time()+next_update])
if curs.rowcount != 1:
raise UnableToWriteKeyCache("Unable to insert into key cache")
@@ -129,8 +128,7 @@ class KeyCache(object):
# Open the connection to the database
conn = sqlite3.connect(self.cache_location)
curs = conn.cursor()
- curs.execute("DELETE FROM keycache WHERE issuer = '{}' AND key_id =
'{}'".format(issuer,
- key_id))
+ curs.execute("DELETE FROM keycache WHERE issuer = ? AND key_id = ?",
[issuer, key_id])
conn.commit()
conn.close()
@@ -145,14 +143,16 @@ class KeyCache(object):
:returns: None if no key is found. Else, returns the public key
"""
# Check the sql database
- key_query = ("SELECT * FROM keycache WHERE "
- "issuer = '{issuer}'")
- if key_id != None:
- key_query += " AND key_id = '{key_id}'"
+ if key_id is not None:
+ key_query = "SELECT * FROM keycache WHERE issuer = ? AND key_id =
?"
+ query_params = [issuer, key_id]
+ else:
+ key_query = "SELECT * FROM keycache WHERE issuer = ?"
+ query_params = [issuer]
conn = sqlite3.connect(self.cache_location)
conn.row_factory = sqlite3.Row
curs = conn.cursor()
- curs.execute(key_query.format(issuer=issuer, key_id=key_id))
+ curs.execute(key_query, query_params)
row = curs.fetchone()
conn.commit()
Index: scitokens-1.8.1/tests/test_keycache.py
===================================================================
--- scitokens-1.8.1.orig/tests/test_keycache.py
+++ scitokens-1.8.1/tests/test_keycache.py
@@ -264,5 +264,122 @@ class TestKeyCache(unittest.TestCase):
create_webserver.shutdown_server()
+import sqlite3
+
+class TestKeyCacheSQLInjection(unittest.TestCase):
+ """
+ Regression tests to verify that SQL injection via issuer/key_id is not
possible.
+ """
+
+ def setUp(self):
+ self.tmp_dir = tempfile.mkdtemp()
+ self.old_xdg = os.environ.get('XDG_CACHE_HOME', None)
+ os.environ['XDG_CACHE_HOME'] = self.tmp_dir
+ self.keycache = KeyCache()
+
+ # Generate a test key pair
+ self.private_key = generate_private_key(
+ public_exponent=65537,
+ key_size=2048,
+ backend=default_backend()
+ )
+ self.public_key = self.private_key.public_key()
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp_dir)
+ if self.old_xdg:
+ os.environ['XDG_CACHE_HOME'] = self.old_xdg
+
+ def _count_rows(self):
+ conn = sqlite3.connect(self.keycache.cache_location)
+ curs = conn.cursor()
+ curs.execute("SELECT COUNT(*) FROM keycache")
+ count = curs.fetchone()[0]
+ conn.close()
+ return count
+
+ def test_injection_in_issuer_does_not_delete_other_rows(self):
+ """
+ With the old .format() pattern, an issuer like "x' OR '1'='1" in a
+ DELETE would wipe every row. Parameterized queries treat it as a
+ literal value, so no rows other than the exact match are affected.
+ """
+ # Insert a legitimate row
+ self.keycache.addkeyinfo("https://legit.example.com/", "key1",
+ self.public_key, cache_timer=3600)
+ self.assertEqual(self._count_rows(), 1)
+
+ # Attempt injection via issuer in addkeyinfo (which DELETEs first)
+ malicious_issuer = "x' OR '1'='1"
+ self.keycache.addkeyinfo(malicious_issuer, "evil_key",
+ self.public_key, cache_timer=3600)
+
+ # The legitimate row must still exist, plus the new malicious-literal
row
+ self.assertEqual(self._count_rows(), 2)
+
+ def test_injection_in_key_id_does_not_delete_other_rows(self):
+ """
+ A malicious key_id should not be able to affect other rows.
+ """
+ self.keycache.addkeyinfo("https://legit.example.com/", "key1",
+ self.public_key, cache_timer=3600)
+ self.assertEqual(self._count_rows(), 1)
+
+ malicious_key_id = "x' OR '1'='1"
+ self.keycache.addkeyinfo("https://other.example.com/",
malicious_key_id,
+ self.public_key, cache_timer=3600)
+
+ self.assertEqual(self._count_rows(), 2)
+
+ def test_delete_cache_entry_with_injection_string(self):
+ """
+ _delete_cache_entry with a crafted issuer must not delete unrelated
rows.
+ """
+ self.keycache.addkeyinfo("https://legit.example.com/", "key1",
+ self.public_key, cache_timer=3600)
+ self.assertEqual(self._count_rows(), 1)
+
+ # Try to delete with an injection string — should match nothing
+ self.keycache._delete_cache_entry("x' OR '1'='1", "key1")
+ self.assertEqual(self._count_rows(), 1)
+
+ def test_union_select_injection_is_literal(self):
+ """
+ A UNION SELECT payload in the issuer should be stored as a literal
+ value, not interpreted as SQL.
+ """
+ malicious_issuer = "x' UNION SELECT * FROM keycache --"
+ self.keycache.addkeyinfo(malicious_issuer, "key1",
+ self.public_key, cache_timer=3600)
+ self.assertEqual(self._count_rows(), 1)
+
+ # The stored issuer should be the literal malicious string
+ conn = sqlite3.connect(self.keycache.cache_location)
+ curs = conn.cursor()
+ curs.execute("SELECT issuer FROM keycache")
+ row = curs.fetchone()
+ conn.close()
+ self.assertEqual(row[0], malicious_issuer)
+
+ def test_getkeyinfo_injection_issuer_no_leak(self):
+ """
+ getkeyinfo with an injection payload in issuer must not return
+ rows belonging to a different issuer.
+ """
+ self.keycache.addkeyinfo("https://legit.example.com/", "key1",
+ self.public_key, cache_timer=3600)
+
+ # This injection string would match all rows with the old code
+ malicious_issuer = "x' OR '1'='1"
+ # getkeyinfo will not find a cached row and will try to download,
+ # which will fail — that's expected. The important thing is it
+ # does NOT return the legit key.
+ try:
+ result = self.keycache.getkeyinfo(malicious_issuer, "key1")
+ except Exception:
+ result = None
+ self.assertIsNone(result)
+
+
if __name__ == '__main__':
unittest.main()
++++++ CVE-2026-32716.patch ++++++
>From 7a237c0f642efb9e8c36ac564b745895cca83583 Mon Sep 17 00:00:00 2001
From: Derek Weitzel <[email protected]>
Date: Fri, 13 Mar 2026 10:49:49 -0500
Subject: [PATCH] Add scope path matching logic and corresponding unit tests
for Enforcer
---
src/scitokens/scitokens.py | 15 +++++++++++---
tests/test_scitokens.py | 40 ++++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 3 deletions(-)
Index: scitokens-1.8.1/src/scitokens/scitokens.py
===================================================================
--- scitokens-1.8.1.orig/src/scitokens/scitokens.py
+++ scitokens-1.8.1/src/scitokens/scitokens.py
@@ -679,6 +679,16 @@ class Enforcer(object):
norm_path = '/'
return (authz, norm_path)
+ @staticmethod
+ def _scope_path_matches(requested_path, allowed_path):
+ if allowed_path == '/':
+ return True
+ if requested_path == allowed_path:
+ return True
+ if allowed_path.endswith('/'):
+ return requested_path.startswith(allowed_path)
+ return requested_path.startswith(allowed_path + '/')
+
def _validate_scp(self, value):
if not isinstance(value, list):
value = [value]
@@ -689,7 +699,7 @@ class Enforcer(object):
norm_requested_path = urltools.normalize_path(self._test_path)
for scope in value:
authz, norm_path = self._check_scope(scope)
- if (self._test_authz == authz) and
norm_requested_path.startswith(norm_path):
+ if (self._test_authz == authz) and
self._scope_path_matches(norm_requested_path, norm_path):
return True
return False
else:
@@ -709,7 +719,7 @@ class Enforcer(object):
# Split on spaces
for scope in value.split(" "):
authz, norm_path = self._check_scope(scope)
- if (self._test_authz == authz) and
norm_requested_path.startswith(norm_path):
+ if (self._test_authz == authz) and
self._scope_path_matches(norm_requested_path, norm_path):
return True
return False
else:
@@ -718,4 +728,3 @@ class Enforcer(object):
authz, norm_path = self._check_scope(scope)
self._token_scopes.add((authz, norm_path))
return True
-
Index: scitokens-1.8.1/tests/test_scitokens.py
===================================================================
--- scitokens-1.8.1.orig/tests/test_scitokens.py
+++ scitokens-1.8.1/tests/test_scitokens.py
@@ -193,6 +193,26 @@ class TestEnforcer(unittest.TestCase):
with self.assertRaises(scitokens.scitokens.InvalidPathError):
print(enf.test(self._token, "write", "~/foo"))
+ def test_enforce_scp_path_boundaries(self):
+ enf = scitokens.Enforcer(self._test_issuer)
+ enf.add_validator("foo", self.always_accept)
+
+ self._token["scp"] = ["read:/john"]
+ self.assertTrue(enf.test(self._token, "read", "/john"),
msg=enf.last_failure)
+ self.assertTrue(enf.test(self._token, "read", "/john/file"),
msg=enf.last_failure)
+ self.assertFalse(enf.test(self._token, "read", "/johnathan"),
msg=enf.last_failure)
+ self.assertFalse(enf.test(self._token, "read", "/johnny"),
msg=enf.last_failure)
+
+ self._token["scp"] = ["read:/john/file"]
+ self.assertFalse(enf.test(self._token, "read", "/john"),
msg=enf.last_failure)
+
+ self._token["scp"] = ["read:/"]
+ self.assertTrue(enf.test(self._token, "read", "/arbitrary/path"),
msg=enf.last_failure)
+
+ self._token["scp"] = ["read://john"]
+ self.assertTrue(enf.test(self._token, "read", "//john//file"),
msg=enf.last_failure)
+ self.assertFalse(enf.test(self._token, "read", "//johnathan"),
msg=enf.last_failure)
+
def test_enforce_scope(self):
"""
Test the Enforcer object.
@@ -225,6 +245,26 @@ class TestEnforcer(unittest.TestCase):
with self.assertRaises(scitokens.scitokens.InvalidPathError):
print(enf.test(self._token, "write", "~/foo"))
+ def test_enforce_scope_path_boundaries(self):
+ enf = scitokens.Enforcer(self._test_issuer)
+ enf.add_validator("foo", self.always_accept)
+
+ self._token["scope"] = "read:/john"
+ self.assertTrue(enf.test(self._token, "read", "/john"),
msg=enf.last_failure)
+ self.assertTrue(enf.test(self._token, "read", "/john/file"),
msg=enf.last_failure)
+ self.assertFalse(enf.test(self._token, "read", "/johnathan"),
msg=enf.last_failure)
+ self.assertFalse(enf.test(self._token, "read", "/johnny"),
msg=enf.last_failure)
+
+ self._token["scope"] = "read:/john/file"
+ self.assertFalse(enf.test(self._token, "read", "/john"),
msg=enf.last_failure)
+
+ self._token["scope"] = "read:/"
+ self.assertTrue(enf.test(self._token, "read", "/arbitrary/path"),
msg=enf.last_failure)
+
+ self._token["scope"] = "read://john"
+ self.assertTrue(enf.test(self._token, "read", "//john//file"),
msg=enf.last_failure)
+ self.assertFalse(enf.test(self._token, "read", "//johnathan"),
msg=enf.last_failure)
+
def test_aud(self):
"""
++++++ CVE-2026-32727.patch ++++++
>From bd7e8c08690a3ed13431f04e262f00cd0fb8a9d3 Mon Sep 17 00:00:00 2001
From: Derek Weitzel <[email protected]>
Date: Fri, 13 Mar 2026 14:33:38 -0500
Subject: [PATCH 1/3] Enhance path traversal protection in Enforcer with
additional checks and unit tests
---
src/scitokens/scitokens.py | 29 +++++++++++-
tests/test_scitokens.py | 95 ++++++++++++++++++++++++++++++++++++++
2 files changed, 122 insertions(+), 2 deletions(-)
Index: scitokens-1.8.1/src/scitokens/scitokens.py
===================================================================
--- scitokens-1.8.1.orig/src/scitokens/scitokens.py
+++ scitokens-1.8.1/src/scitokens/scitokens.py
@@ -10,6 +10,7 @@ import time
import os
import jwt
+import re
from . import urltools
import logging
@@ -462,7 +463,7 @@ class InvalidPathError(EnforcementError)
Test paths must be absolute paths (start with '/')
"""
-class InvalidAuthorizationResource(EnforcementError):
+class InvalidAuthorizationResource(ValidationFailure, EnforcementError):
"""
A scope was encountered with an invalid authorization.
@@ -674,12 +675,36 @@ class Enforcer(object):
path = info[1]
if not path.startswith("/"):
raise InvalidAuthorizationResource("Token contains a relative
path in scope")
- norm_path = urltools.normalize_path(path)
+ norm_path = self._normalize_scope_path(path)
else:
norm_path = '/'
return (authz, norm_path)
@staticmethod
+ def _decode_scope_path_segment(segment):
+ normalized_segment = re.sub(
+ r"%([0-9A-Fa-f]{2})",
+ lambda match: "%" + match.group(1).lower(),
+ segment,
+ )
+ return urltools.unquote(normalized_segment, exceptions='/?+#')
+
+ @classmethod
+ def _normalize_scope_path(cls, path):
+ for segment in path.split("/"):
+ if cls._decode_scope_path_segment(segment) == "..":
+ raise InvalidAuthorizationResource("Token contains path
traversal in scope")
+ normalized = urltools.normalize_path(path)
+ # Defense-in-depth: verify the normalized path hasn't escaped root
+ # via double-encoding or other tricks that bypass the segment check.
+ if not normalized.startswith("/"):
+ raise InvalidAuthorizationResource("Token contains path traversal
in scope")
+ for segment in normalized.split("/"):
+ if segment == "..":
+ raise InvalidAuthorizationResource("Token contains path
traversal in scope")
+ return normalized
+
+ @staticmethod
def _scope_path_matches(requested_path, allowed_path):
if allowed_path == '/':
return True
Index: scitokens-1.8.1/tests/test_scitokens.py
===================================================================
--- scitokens-1.8.1.orig/tests/test_scitokens.py
+++ scitokens-1.8.1/tests/test_scitokens.py
@@ -213,6 +213,28 @@ class TestEnforcer(unittest.TestCase):
self.assertTrue(enf.test(self._token, "read", "//john//file"),
msg=enf.last_failure)
self.assertFalse(enf.test(self._token, "read", "//johnathan"),
msg=enf.last_failure)
+ def test_enforce_scp_path_traversal(self):
+ enf = scitokens.Enforcer(self._test_issuer)
+ enf.add_validator("foo", self.always_accept)
+
+ bad_scopes = [
+ ("read:/home/user1/..", "/home/user2"),
+ ("read:/anything/..", "/etc/passwd"),
+ ("read:/foo/%2e%2e/bar", "/bar"),
+ ("read:/foo/.%2e/bar", "/bar"),
+ ("read:/foo/%2e./bar", "/bar"),
+ ("read:/foo/%2E%2E/bar", "/bar"),
+ ]
+
+ for scope, requested_path in bad_scopes:
+ self._token["scp"] = scope
+ self.assertFalse(enf.test(self._token, "read", requested_path),
msg=enf.last_failure)
+ self.assertIn("path traversal", enf.last_failure)
+
+ self._token["scp"] = "read:/foo/%2e%2e/bar"
+ with
self.assertRaises(scitokens.scitokens.InvalidAuthorizationResource):
+ enf.generate_acls(self._token)
+
def test_enforce_scope(self):
"""
Test the Enforcer object.
@@ -265,6 +287,74 @@ class TestEnforcer(unittest.TestCase):
self.assertTrue(enf.test(self._token, "read", "//john//file"),
msg=enf.last_failure)
self.assertFalse(enf.test(self._token, "read", "//johnathan"),
msg=enf.last_failure)
+ def test_enforce_scope_path_traversal(self):
+ enf = scitokens.Enforcer(self._test_issuer)
+ enf.add_validator("foo", self.always_accept)
+
+ bad_scopes = [
+ ("read:/home/user1/..", "/home/user2"),
+ ("read:/anything/..", "/etc/passwd"),
+ ("read:/foo/%2e%2e/bar", "/bar"),
+ ("read:/foo/.%2e/bar", "/bar"),
+ ("read:/foo/%2e./bar", "/bar"),
+ ("read:/foo/%2E%2E/bar", "/bar"),
+ ]
+
+ for scope, requested_path in bad_scopes:
+ self._token["scope"] = scope
+ self.assertFalse(enf.test(self._token, "read", requested_path),
msg=enf.last_failure)
+ self.assertIn("path traversal", enf.last_failure)
+
+ self._token["scope"] = "read:/foo/%2e%2e/bar"
+ with
self.assertRaises(scitokens.scitokens.InvalidAuthorizationResource):
+ enf.generate_acls(self._token)
+
+ def test_enforce_scope_path_traversal_double_encoded(self):
+ """
+ Defense-in-depth: double-encoded and other encoding variations must
+ not allow path traversal even if the pre-normalization segment check
+ doesn't catch them.
+ """
+ enf = scitokens.Enforcer(self._test_issuer)
+ enf.add_validator("foo", self.always_accept)
+
+ # Double-encoded '..' (%252e%252e decodes once to %2e%2e)
+ # These should either be caught or treated as opaque literal segments
+ # — never resolved to actual '..' traversal.
+ double_encoded_scopes = [
+ "read:/foo/%252e%252e/bar",
+ "read:/foo/%252E%252E/bar",
+ "read:/foo/%252e./bar",
+ "read:/foo/.%252e/bar",
+ ]
+ for scope in double_encoded_scopes:
+ self._token["scope"] = scope
+ # Must not grant access to /bar (the traversed path)
+ self.assertFalse(
+ enf.test(self._token, "read", "/bar"),
+ msg="Scope {!r} should not grant access to /bar".format(scope),
+ )
+
+ def test_normalize_scope_path_rejects_traversal(self):
+ """
+ Test that _normalize_scope_path rejects traversal and encoded
+ traversal paths, and still accepts benign normalized paths.
+ """
+ enforcer_cls = scitokens.scitokens.Enforcer
+
+ # These should all be rejected
+ for bad_path in ["/a/../b", "/a/%2e%2e/b", "/a/.%2e/b", "/a/%2e./b"]:
+ with self.assertRaises(
+ scitokens.scitokens.InvalidAuthorizationResource,
+ msg="Path {!r} should be rejected".format(bad_path),
+ ):
+ enforcer_cls._normalize_scope_path(bad_path)
+
+ # Valid paths must still work
+ for good_path in ["/a/b/c", "/a/b/../c".replace("..", "safe"), "/",
"/a/"]:
+ result = enforcer_cls._normalize_scope_path(good_path)
+ self.assertTrue(result.startswith("/"))
+
def test_aud(self):
"""
@@ -373,6 +463,10 @@ class TestEnforcer(unittest.TestCase):
with
self.assertRaises(scitokens.scitokens.InvalidAuthorizationResource):
print(enf.generate_acls(self._token))
+ self._token['scope'] = 'read:/foo/%2e%2e/bar'
+ with
self.assertRaises(scitokens.scitokens.InvalidAuthorizationResource):
+ enf.generate_acls(self._token)
+
def test_sub(self):
"""
Verify that tokens with the `sub` set are accepted.