From 90c18551a1353963f0f6972ff04f8413e12de19a Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Mon, 22 Dec 2025 18:06:00 -0800 Subject: [PATCH 1/2] extend system allowed role support to have wildcards and multiple values Signed-off-by: Henry Avetisyan --- .../athenz/zts/token/OAuthTokenScope.java | 51 +++-- .../com/yahoo/athenz/zts/ZTSImplTest.java | 66 ++++++ .../com/yahoo/athenz/zts/ZTSTestUtils.java | 7 + .../athenz/zts/token/OAuthTokenScopeTest.java | 194 ++++++++++++++++++ 4 files changed, 304 insertions(+), 14 deletions(-) diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java index d48bfd14c1a..807a2877349 100644 --- a/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java @@ -66,7 +66,7 @@ public OAuthTokenScope(final String scope, int maxDomains, DynamicConfigCsv syst // openid :group. // openid [:domain]+ - String systemAllowedRole = null; + Set userSystemAllowedRoles = null; Map> scopeRoleNames = new HashMap<>(); Map> scopeGroupNames = new HashMap<>(); for (String scopeItem : scopeList) { @@ -114,12 +114,12 @@ public OAuthTokenScope(final String scope, int maxDomains, DynamicConfigCsv syst // if the role is one of our authorized roles, we're not going // to process it right away to avoid counting it against // the configured max domain setting. We'll process it - // at the end without checking the domain limit. However, we - // still allow only a single authorized role to be specified - // so if we have multiple, we'll handle the second one as a - // regular role scope - if (systemAllowedRole == null && systemAllowedRoles != null && systemAllowedRoles.hasItem(scopeItem)) { - systemAllowedRole = scopeItem; + // at the end without checking the domain limit. + if (systemAllowedRoles != null && isSystemAllowedRoles(systemAllowedRoles, scopeItem)) { + if (userSystemAllowedRoles == null) { + userSystemAllowedRoles = new HashSet<>(); + } + userSystemAllowedRoles.add(scopeItem); } else { final String scopeDomainName = scopeItem.substring(0, idx); addScopeDomain(scopeDomainName, scope, true); @@ -156,14 +156,16 @@ public OAuthTokenScope(final String scope, int maxDomains, DynamicConfigCsv syst } } - // process our authorized role if one was specified + // process our authorized roles if any were specified - if (systemAllowedRole != null) { - int idx = systemAllowedRole.indexOf(OBJECT_ROLE); - final String scopeDomainName = systemAllowedRole.substring(0, idx); - addScopeDomain(scopeDomainName, scope, false); - scopeRoleNames.putIfAbsent(scopeDomainName, new HashSet<>()); - scopeRoleNames.get(scopeDomainName).add(systemAllowedRole.substring(idx + OBJECT_ROLE.length())); + if (userSystemAllowedRoles != null) { + for (String userSystemAllowedRole : userSystemAllowedRoles) { + int idx = userSystemAllowedRole.indexOf(OBJECT_ROLE); + final String scopeDomainName = userSystemAllowedRole.substring(0, idx); + addScopeDomain(scopeDomainName, scope, false); + scopeRoleNames.putIfAbsent(scopeDomainName, new HashSet<>()); + scopeRoleNames.get(scopeDomainName).add(userSystemAllowedRole.substring(idx + OBJECT_ROLE.length())); + } } // if the scope response is set to true then we had @@ -248,6 +250,27 @@ void addScopeDomain(final String scopeDomainName, final String scope, boolean en } } + boolean isSystemAllowedRoles(DynamicConfigCsv systemAllowedRoles, String scopeItem) { + if (systemAllowedRoles == null) { + return false; + } + // first we'll check if our scope item matches any of the allowed roles + if (systemAllowedRoles.hasItem(scopeItem)) { + return true; + } + // now we'll get the full list and check there as well in case + // we have wildcard matches for the given item + for (String allowedRolePattern : systemAllowedRoles.getStringsList()) { + if (allowedRolePattern.contains("*")) { + final String regex = allowedRolePattern.replace("*", ".*"); + if (scopeItem.matches(regex)) { + return true; + } + } + } + return false; + } + ResourceException error(final String message, final String scope) { LOGGER.error("oauth token request error: {} - {}", message, scope); return new ResourceException(ResourceException.BAD_REQUEST, diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java index 07a36788576..9d732b47b8e 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java @@ -41,6 +41,7 @@ import com.yahoo.athenz.common.server.store.impl.ZMSFileChangeLogStore; import com.yahoo.athenz.common.server.util.ResourceUtils; import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigBoolean; +import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigCsv; import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigInteger; import com.yahoo.athenz.common.server.util.config.dynamic.DynamicConfigLong; import com.yahoo.athenz.common.server.workload.WorkloadRecord; @@ -13297,6 +13298,71 @@ public void testGetOIDCResponseRolesMultipleDomains() { } } + @Test + public void testGetOIDCResponseRolesWildcardMatch() throws Exception { + + System.setProperty(FilePrivateKeyStore.ATHENZ_PROP_PRIVATE_KEY, "src/test/resources/unit_test_zts_at_private.pem"); + + ZTSTestUtils.setStaticField(IdTokenScope.class, "systemAllowedRoles", new DynamicConfigCsv("weather:role.*")); + ZTSTestUtils.setStaticField(IdTokenScope.class, "maxDomains", 1); + + CloudStore cloudStore = new CloudStore(); + ZTSImpl ztsImpl = new ZTSImpl(cloudStore, store); + // set back to our zts rsa private key + System.setProperty(FilePrivateKeyStore.ATHENZ_PROP_PRIVATE_KEY, "src/test/resources/unit_test_zts_private.pem"); + + Principal principal = SimplePrincipal.create("user_domain", "user", + "v=U1;d=user_domain;n=user;s=signature", 0, null); + ResourceContext context = createResourceContext(principal); + + SignedDomain signedDomain1 = createSignedDomain("coretech", "sports", "api", true, null); + store.processSignedDomain(signedDomain1, false); + + SignedDomain signedDomain2 = createSignedDomain("weather", "sports", "api", true, null); + store.processSignedDomain(signedDomain2, false); + + ServerPrivateKey privateKey = getServerPrivateKey(ztsImpl, ztsImpl.keyAlgoForJsonWebObjects); + + // first ask for all the roles, and we should get failure since + // the domain limit is set to 1 + + try { + ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", + "https://localhost:4443/zts", "openid roles coretech:domain weather:domain", + null, "nonce", "", null, null, null, null, null); + fail(); + } catch (ResourceException ex) { + assertTrue(ex.getMessage().contains("Multiple domains in scope"), ex.getMessage()); + } + + // now let's ask for the specific roles in weather domain which should match + // our system allowed role attribute + + Response response = ztsImpl.getOIDCResponse(context, "id_token", "coretech.api", + "https://localhost:4443/zts", "openid roles coretech:domain weather:role.writers", + null, "nonce", "", null, null, null, null, null); + + JWTClaimsSet claimsSet = getClaimsFromResponse(response, privateKey.getKey(), null); + List userRoles; + try { + assertNotNull(claimsSet); + assertEquals(claimsSet.getSubject(), "user_domain.user"); + assertEquals(claimsSet.getAudience().get(0), "coretech.api"); + assertEquals(claimsSet.getStringClaim("nonce"), "nonce"); + assertEquals(claimsSet.getIssuer(), ztsImpl.ztsOpenIDIssuer); + userRoles = claimsSet.getStringListClaim("groups"); + assertNotNull(userRoles); + assertEquals(userRoles.size(), 2); + assertTrue(userRoles.contains("coretech:role.writers")); + assertTrue(userRoles.contains("weather:role.writers")); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + + ZTSTestUtils.setStaticField(IdTokenScope.class, "systemAllowedRoles", null); + ZTSTestUtils.setStaticField(IdTokenScope.class, "maxDomains", 10); + } + @Test public void testGeSignPrivateKey() { diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java index 802c33424d8..318d473be72 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSTestUtils.java @@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory; import java.io.File; +import java.lang.reflect.Field; import java.security.PrivateKey; import java.util.*; import java.util.concurrent.TimeUnit; @@ -582,4 +583,10 @@ public static SignedDomain signedAuthorizedProviderDomain(PrivateKey privateKey) return signedDomain; } + + public static void setStaticField(Class clazz, String fieldName, Object value) throws Exception { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, value); + } } diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java index 3e095292af6..b338293df23 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java @@ -177,4 +177,198 @@ public void testOauthTokenScopeMaxDomainsWithAuthorizedRoles() { assertEquals(domains.size(), 1); assertTrue(domains.contains("sports")); } + + @Test + public void testIsSystemAllowedRolesNullConfig() { + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + assertFalse(scope.isSystemAllowedRoles(null, "system:role.reader")); + } + + @Test + public void testIsSystemAllowedRolesExactMatch() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.reader,system:role.writer"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.admin")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + } + + @Test + public void testIsSystemAllowedRolesWildcardPrefix() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.*"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.admin")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.any-role-name")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:group.reader")); + } + + @Test + public void testIsSystemAllowedRolesWildcardSuffix() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("*.role.reader"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "weather:role.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:group.reader")); + } + + @Test + public void testIsSystemAllowedRolesWildcardMiddle() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:*.reader"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:group.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:service.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + } + + @Test + public void testIsSystemAllowedRolesMultipleWildcards() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("*:role.*"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.admin")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "weather:role.any")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:group.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:service.api")); + } + + @Test + public void testIsSystemAllowedRolesMixedExactAndWildcard() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.reader,system:role.writer,*:role.admin"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + // Exact matches + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + + // Wildcard matches + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.admin")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.admin")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "weather:role.admin")); + + // Non-matches + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.other")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + } + + @Test + public void testIsSystemAllowedRolesEmptyConfig() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv(""); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "any:role.any")); + } + + @Test + public void testIsSystemAllowedRolesWhitespaceHandling() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv(" system:role.reader , system:role.writer "); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + // DynamicConfigCsv trims whitespace, so these should match + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + } + + @Test + public void testIsSystemAllowedRolesSpecialCharacters() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.test-role,system:role.test_role,*:role.test.role"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.test-role")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.test_role")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.test.role")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.test.role")); + } + + @Test + public void testIsSystemAllowedRolesWildcardOnly() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("*"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + // Wildcard * should match everything + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.writer")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "any:any.anything")); + } + + @Test + public void testIsSystemAllowedRolesWildcardAtStart() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("*reader"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + } + + @Test + public void testIsSystemAllowedRolesWildcardAtEnd() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:*"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:group.writer")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:service.api")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:anything")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + } + + @Test + public void testIsSystemAllowedRolesMultipleWildcardPatterns() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.*,*:role.reader,*:*.admin"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + // Matches first pattern + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + + // Matches second pattern + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "weather:role.reader")); + + // Matches third pattern + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.admin")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:group.admin")); + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "weather:service.admin")); + + // No match + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.writer")); + } + + @Test + public void testIsSystemAllowedRolesExactMatchTakesPrecedence() { + // Even if there's a wildcard pattern, exact match should work + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.reader,system:role.*"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + // Exact match should be found first (via hasItem) + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + // Wildcard should also match + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); + } + + @Test + public void testIsSystemAllowedRolesCaseSensitive() { + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("system:role.Reader"); + OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); + + // Should be case-sensitive + assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.Reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); + } } From 962e8d2e9c3218b65a05b28a9b39665dd8c6c06a Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Mon, 22 Dec 2025 19:52:29 -0800 Subject: [PATCH 2/2] address review comment Signed-off-by: Henry Avetisyan --- .../main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java | 2 +- .../java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java b/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java index 807a2877349..0221709497b 100644 --- a/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java +++ b/servers/zts/src/main/java/com/yahoo/athenz/zts/token/OAuthTokenScope.java @@ -262,7 +262,7 @@ boolean isSystemAllowedRoles(DynamicConfigCsv systemAllowedRoles, String scopeIt // we have wildcard matches for the given item for (String allowedRolePattern : systemAllowedRoles.getStringsList()) { if (allowedRolePattern.contains("*")) { - final String regex = allowedRolePattern.replace("*", ".*"); + final String regex = allowedRolePattern.replace(".", "\\.").replace("*", ".*"); if (scopeItem.matches(regex)) { return true; } diff --git a/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java b/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java index b338293df23..54892198bbe 100644 --- a/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java +++ b/servers/zts/src/test/java/com/yahoo/athenz/zts/token/OAuthTokenScopeTest.java @@ -205,12 +205,13 @@ public void testIsSystemAllowedRolesWildcardPrefix() { assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.admin")); assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.any-role-name")); assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role-reader")); assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:group.reader")); } @Test public void testIsSystemAllowedRolesWildcardSuffix() { - DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("*.role.reader"); + DynamicConfigCsv systemAllowedRoles = new DynamicConfigCsv("*:role.reader"); OAuthTokenScope scope = new OAuthTokenScope("openid", 1, null, null); assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.reader")); @@ -230,6 +231,7 @@ public void testIsSystemAllowedRolesWildcardMiddle() { assertTrue(scope.isSystemAllowedRoles(systemAllowedRoles, "system:service.reader")); assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:role.writer")); assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "sports:role.reader")); + assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:test-reader")); } @Test