Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public OAuthTokenScope(final String scope, int maxDomains, DynamicConfigCsv syst
// openid <domainName>:group.<groupName>
// openid [<domainName>:domain]+

String systemAllowedRole = null;
Set<String> userSystemAllowedRoles = null;
Map<String, Set<String>> scopeRoleNames = new HashMap<>();
Map<String, Set<String>> scopeGroupNames = new HashMap<>();
for (String scopeItem : scopeList) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(".", "\\.").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,
Expand Down
66 changes: 66 additions & 0 deletions servers/zts/src/test/java/com/yahoo/athenz/zts/ZTSImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> 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() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,200 @@ 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, "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"));
assertFalse(scope.isSystemAllowedRoles(systemAllowedRoles, "system:test-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"));
}
}
Loading