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 @@ -20,6 +20,7 @@
import com.yahoo.rdl.Timestamp;

import java.io.Closeable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -56,6 +57,9 @@ public interface ObjectStoreConnection extends Closeable {
AthenzDomain getAthenzDomain(String domainName) throws ServerResourceException;
DomainMetaList listModifiedDomains(long modifiedSince) throws ServerResourceException;
void setDomainOptions(DomainOptions domainOptions) throws ServerResourceException;
default Map<String, String> listDomainsWithExternalMemberValidator() throws ServerResourceException {
return Collections.emptyMap();
}

// Domain tags

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public class JDBCConnection implements ObjectStoreConnection {
private static final String SQL_GET_DOMAIN_WITH_YPM_ID = "SELECT name FROM domain WHERE ypm_id=?;";
private static final String SQL_GET_DOMAIN_WITH_PRODUCT_ID = "SELECT name FROM domain WHERE product_id=?;";
private static final String SQL_LIST_DOMAIN_WITH_BUSINESS_SERVICE = "SELECT name FROM domain WHERE business_service=?;";
private static final String SQL_LIST_DOMAINS_WITH_EXTERNAL_MEMBER_VALIDATOR = "SELECT name, external_member_validator FROM domain WHERE external_member_validator!='';";
private static final String SQL_INSERT_DOMAIN = "INSERT INTO domain "
+ "(name, description, org, uuid, enabled, audit_enabled, account, ypm_id, application_id, cert_dns_domain,"
+ " member_expiry_days, token_expiry_mins, service_cert_expiry_mins, role_cert_expiry_mins, sign_algorithm,"
Expand Down Expand Up @@ -1490,6 +1491,24 @@ public Map<String, String> listDomainsByCloudProvider(String provider) throws Se
return domains;
}

@Override
public Map<String, String> listDomainsWithExternalMemberValidator() throws ServerResourceException {

final String caller = "listDomainsWithExternalMemberValidator";
Map<String, String> domains = new HashMap<>();
try (PreparedStatement ps = con.prepareStatement(SQL_LIST_DOMAINS_WITH_EXTERNAL_MEMBER_VALIDATOR)) {
try (ResultSet rs = executeQuery(ps, caller)) {
while (rs.next()) {
domains.put(rs.getString(JDBCConsts.DB_COLUMN_NAME),
rs.getString(JDBCConsts.DB_COLUMN_EXTERNAL_MEMBER_VALIDATOR));
}
}
} catch (SQLException ex) {
throw sqlError(ex, caller);
}
return domains;
}

@Override
public List<String> listDomains(String prefix, long modifiedSince) throws ServerResourceException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18002,4 +18002,58 @@ public void testEnforceGroupAuditLogLimitCleanUpException() throws SQLException
Mockito.verify(mockPrepStmt, times(1)).executeUpdate();
jdbcConn.close();
}

@Test
public void testListDomainsWithExternalMemberValidator() throws Exception {

JDBCConnection jdbcConn = new JDBCConnection(mockConn, true);

Mockito.when(mockResultSet.next())
.thenReturn(true)
.thenReturn(true)
.thenReturn(false);
Mockito.when(mockResultSet.getString(JDBCConsts.DB_COLUMN_NAME))
.thenReturn("domain1")
.thenReturn("domain2");
Mockito.when(mockResultSet.getString(JDBCConsts.DB_COLUMN_EXTERNAL_MEMBER_VALIDATOR))
.thenReturn("com.yahoo.athenz.ValidatorA")
.thenReturn("com.yahoo.athenz.ValidatorB");

Map<String, String> domains = jdbcConn.listDomainsWithExternalMemberValidator();
assertEquals(domains.size(), 2);
assertEquals(domains.get("domain1"), "com.yahoo.athenz.ValidatorA");
assertEquals(domains.get("domain2"), "com.yahoo.athenz.ValidatorB");

jdbcConn.close();
}

@Test
public void testListDomainsWithExternalMemberValidatorEmpty() throws Exception {

JDBCConnection jdbcConn = new JDBCConnection(mockConn, true);

Mockito.when(mockResultSet.next()).thenReturn(false);

Map<String, String> domains = jdbcConn.listDomainsWithExternalMemberValidator();
assertTrue(domains.isEmpty());

jdbcConn.close();
}

@Test
public void testListDomainsWithExternalMemberValidatorException() throws Exception {

JDBCConnection jdbcConn = new JDBCConnection(mockConn, true);

Mockito.when(mockPrepStmt.executeQuery()).thenThrow(new SQLException("sql error"));

try {
jdbcConn.listDomainsWithExternalMemberValidator();
fail();
} catch (ServerResourceException ex) {
assertTrue(ex.getMessage().contains("sql error"));
}

jdbcConn.close();
}
}
8 changes: 8 additions & 0 deletions servers/zms/src/main/java/com/yahoo/athenz/zms/DBService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3586,6 +3586,14 @@ DomainList lookupDomainByRole(String roleMember, String roleName) {
return domList;
}

Map<String, String> getDomainsWithExternalMemberValidator() {
try (ObjectStoreConnection con = store.getConnection(true, true)) {
return con.listDomainsWithExternalMemberValidator();
} catch (ServerResourceException ex) {
throw ZMSUtils.error(ex);
}
}

List<String> listRoles(String domainName) {
return listRoles(domainName, false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright The Athenz Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yahoo.athenz.zms;

import com.yahoo.athenz.auth.ExternalMemberValidator;
import com.yahoo.athenz.zms.utils.ZMSUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.yahoo.athenz.zms.ZMSConsts.*;

public class ExternalMemberValidatorManager {

private static final Logger LOGGER = LoggerFactory.getLogger(ExternalMemberValidatorManager.class);

private ScheduledExecutorService scheduledExecutor;
private final DBService dbService;
private final ConcurrentHashMap<String, ExternalMemberValidator> validators = new ConcurrentHashMap<>();
private volatile Map<String, String> domainValidatorClasses = Collections.emptyMap();

public ExternalMemberValidatorManager(final DBService dbService) {
LOGGER.info("Initializing ExternalMemberValidatorManager...");
this.dbService = dbService;
refreshValidators();
init();
}

private void init() {
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
long frequencyHours = Long.parseLong(
System.getProperty(ZMS_PROP_EXTERNAL_MEMBER_VALIDATOR_FREQUENCY_HOURS,
ZMS_PROP_EXTERNAL_MEMBER_VALIDATOR_FREQUENCY_HOURS_DEFAULT));
scheduledExecutor.scheduleAtFixedRate(this::refreshValidators, frequencyHours,
frequencyHours, TimeUnit.HOURS);
}

public void shutdown() {
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
}
}

void refreshValidators() {

LOGGER.info("Refreshing external member validators from datastore");

Map<String, String> currentDomainValidators;
try {
currentDomainValidators = dbService.getDomainsWithExternalMemberValidator();
} catch (Exception ex) {
LOGGER.error("Unable to fetch domains with external member validators: {}", ex.getMessage());
return;
}

for (String domainName : validators.keySet()) {
if (!currentDomainValidators.containsKey(domainName)) {
LOGGER.info("Removing external member validator for domain: {}", domainName);
validators.remove(domainName);
}
}

for (Map.Entry<String, String> entry : currentDomainValidators.entrySet()) {
final String domainName = entry.getKey();
final String validatorClass = entry.getValue();

final String existingClass = domainValidatorClasses.get(domainName);
if (validatorClass.equals(existingClass) && validators.containsKey(domainName)) {
continue;
}

ExternalMemberValidator validator = newValidatorInstance(validatorClass);
if (validator != null) {
LOGGER.info("Loaded external member validator {} for domain: {}", validatorClass, domainName);
validators.put(domainName, validator);
} else {
LOGGER.error("Failed to instantiate external member validator {} for domain: {}",
validatorClass, domainName);
validators.remove(domainName);
}
}

domainValidatorClasses = currentDomainValidators;
LOGGER.info("External member validator refresh complete. Active validators for {} domains", validators.size());
}

ExternalMemberValidator newValidatorInstance(final String className) {
try {
Class<?> clazz = Class.forName(className);
return (ExternalMemberValidator) clazz.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
LOGGER.error("Unable to instantiate external member validator class {}: {}",
className, ex.getMessage());
return null;
}
}

/**
* Validate a member for the given domain. If the domain does not have
* an external member validator available, or the member is not valid,
* a bad request ResourceException is thrown.
* @param domainName the domain to validate in
* @param memberName the member name to validate
* @param caller the caller method name for error reporting
*/
public void validateMember(final String domainName, final String memberName, final String caller) {
ExternalMemberValidator validator = validators.get(domainName);
if (validator == null) {
throw ZMSUtils.requestError("External member validator for domain "
+ domainName + " is not available", caller);
}
if (!validator.validateMember(domainName, memberName)) {
throw ZMSUtils.requestError("Member " + memberName
+ " is not valid according to the external member validator for domain "
+ domainName, caller);
}
}

/**
* Returns an unmodifiable set of domain names that have an external
* member validator configured.
* @return unmodifiable set of domain names with active validators
*/
public Set<String> getDomainNamesWithValidator() {
return Collections.unmodifiableSet(validators.keySet());
}

Map<String, ExternalMemberValidator> getValidators() {
return validators;
}

Map<String, String> getDomainValidatorClasses() {
return domainValidatorClasses;
}
}
3 changes: 3 additions & 0 deletions servers/zms/src/main/java/com/yahoo/athenz/zms/ZMSConsts.java
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ public final class ZMSConsts {
public static final String ZMS_PROP_SERVICE_PROVIDER_MANAGER_ROLE = "athenz.zms.service_provider_manager_role";
public static final String ZMS_PROP_SERVICE_PROVIDER_MANAGER_ROLE_DEFAULT = "service_providers";

public static final String ZMS_PROP_EXTERNAL_MEMBER_VALIDATOR_FREQUENCY_HOURS = "athenz.zms.external_member_validator_frequency_hours";
public static final String ZMS_PROP_EXTERNAL_MEMBER_VALIDATOR_FREQUENCY_HOURS_DEFAULT = "1";

public static final String ZMS_PROP_QUOTA_ASSERTION_CONDITIONS = "athenz.zms.quota_assertion_conditions";

public static final String ZMS_PROP_MAX_POLICY_VERSIONS_DEFAULT = "3";
Expand Down
48 changes: 48 additions & 0 deletions servers/zms/src/test/java/com/yahoo/athenz/zms/DBServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14666,4 +14666,52 @@ public void testUpdateTemplateGroup() {
Group templateGroup = zms.dbService.updateTemplateGroup(group, "my-domain", List.of(param));
assertThat(templateGroup.getGroupMembers(), hasItems(groupMember1, groupMember));
}

@Test
public void testGetDomainsWithExternalMemberValidator() {

Map<String, String> result = zms.dbService.getDomainsWithExternalMemberValidator();
assertNotNull(result);
assertTrue(result.isEmpty());
}

@Test
public void testGetDomainsWithExternalMemberValidatorException() throws ServerResourceException {

ObjectStore saveStore = zms.dbService.store;
zms.dbService.store = mockObjStore;

Mockito.when(mockJdbcConn.listDomainsWithExternalMemberValidator())
.thenThrow(new ServerResourceException(500, "DB Error"));

try {
zms.dbService.getDomainsWithExternalMemberValidator();
fail();
} catch (ResourceException ex) {
assertEquals(ex.getCode(), 500);
}

zms.dbService.store = saveStore;
}

@Test
public void testGetDomainsWithExternalMemberValidatorWithData() throws ServerResourceException {

ObjectStore saveStore = zms.dbService.store;
zms.dbService.store = mockObjStore;

Map<String, String> mockResult = new HashMap<>();
mockResult.put("domain1", "com.yahoo.athenz.Validator1");
mockResult.put("domain2", "com.yahoo.athenz.Validator2");

Mockito.when(mockJdbcConn.listDomainsWithExternalMemberValidator()).thenReturn(mockResult);

Map<String, String> result = zms.dbService.getDomainsWithExternalMemberValidator();
assertNotNull(result);
assertEquals(result.size(), 2);
assertEquals(result.get("domain1"), "com.yahoo.athenz.Validator1");
assertEquals(result.get("domain2"), "com.yahoo.athenz.Validator2");

zms.dbService.store = saveStore;
}
}
Loading
Loading