blob: dc6db5b202ff286a8875ae1cae426d22a82ae29c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.druid.security.basic.authorization.db.updater;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import org.apache.druid.common.config.ConfigManager;
import org.apache.druid.concurrent.LifecycleLock;
import org.apache.druid.guice.ManageLifecycle;
import org.apache.druid.guice.annotations.Smile;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.concurrent.Execs;
import org.apache.druid.java.util.common.concurrent.ScheduledExecutors;
import org.apache.druid.java.util.common.lifecycle.LifecycleStart;
import org.apache.druid.java.util.common.lifecycle.LifecycleStop;
import org.apache.druid.java.util.emitter.EmittingLogger;
import org.apache.druid.metadata.MetadataCASUpdate;
import org.apache.druid.metadata.MetadataStorageConnector;
import org.apache.druid.metadata.MetadataStorageTablesConfig;
import org.apache.druid.security.basic.BasicAuthCommonCacheConfig;
import org.apache.druid.security.basic.BasicAuthDBConfig;
import org.apache.druid.security.basic.BasicAuthUtils;
import org.apache.druid.security.basic.BasicSecurityDBResourceException;
import org.apache.druid.security.basic.authorization.BasicRoleBasedAuthorizer;
import org.apache.druid.security.basic.authorization.db.cache.BasicAuthorizerCacheNotifier;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMapping;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerGroupMappingMapBundle;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleMapBundle;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser;
import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserMapBundle;
import org.apache.druid.security.basic.authorization.entity.GroupMappingAndRoleMap;
import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap;
import org.apache.druid.server.security.AuthorizationUtils;
import org.apache.druid.server.security.Authorizer;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.ResourceAction;
import org.joda.time.Duration;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@ManageLifecycle
public class CoordinatorBasicAuthorizerMetadataStorageUpdater implements BasicAuthorizerMetadataStorageUpdater
{
private static final EmittingLogger LOG =
new EmittingLogger(CoordinatorBasicAuthorizerMetadataStorageUpdater.class);
private static final long UPDATE_RETRY_DELAY = 1000;
private static final String USERS = "users";
private static final String GROUP_MAPPINGS = "groupMappings";
private static final String ROLES = "roles";
public static final List<ResourceAction> SUPERUSER_PERMISSIONS = AuthorizationUtils.makeSuperUserPermissions();
private final AuthorizerMapper authorizerMapper;
private final MetadataStorageConnector connector;
private final MetadataStorageTablesConfig connectorConfig;
private final BasicAuthorizerCacheNotifier cacheNotifier;
private final BasicAuthCommonCacheConfig commonCacheConfig;
private final ObjectMapper objectMapper;
private final int numRetries = 5;
private final Map<String, BasicAuthorizerUserMapBundle> cachedUserMaps;
private final Map<String, BasicAuthorizerGroupMappingMapBundle> cachedGroupMappingMaps;
private final Map<String, BasicAuthorizerRoleMapBundle> cachedRoleMaps;
private final Set<String> authorizerNames;
private final LifecycleLock lifecycleLock = new LifecycleLock();
private final ScheduledExecutorService exec;
private volatile boolean stopped = false;
@Inject
public CoordinatorBasicAuthorizerMetadataStorageUpdater(
AuthorizerMapper authorizerMapper,
MetadataStorageConnector connector,
MetadataStorageTablesConfig connectorConfig,
BasicAuthCommonCacheConfig commonCacheConfig,
@Smile ObjectMapper objectMapper,
BasicAuthorizerCacheNotifier cacheNotifier,
ConfigManager configManager // -V6022: ConfigManager creates the db table we need, set a dependency here
)
{
this.exec = Execs.scheduledSingleThreaded("CoordinatorBasicAuthorizerMetadataStorageUpdater-Exec--%d");
this.authorizerMapper = authorizerMapper;
this.connector = connector;
this.connectorConfig = connectorConfig;
this.commonCacheConfig = commonCacheConfig;
this.objectMapper = objectMapper;
this.cacheNotifier = cacheNotifier;
this.cachedUserMaps = new ConcurrentHashMap<>();
this.cachedGroupMappingMaps = new ConcurrentHashMap<>();
this.cachedRoleMaps = new ConcurrentHashMap<>();
this.authorizerNames = new HashSet<>();
}
@LifecycleStart
public void start()
{
if (!lifecycleLock.canStart()) {
throw new ISE("can't start.");
}
if (authorizerMapper == null || authorizerMapper.getAuthorizerMap() == null) {
return;
}
try {
LOG.info("Starting CoordinatorBasicAuthorizerMetadataStorageUpdater");
BasicAuthUtils.maybeInitialize(
() -> {
for (Map.Entry<String, Authorizer> entry : authorizerMapper.getAuthorizerMap().entrySet()) {
Authorizer authorizer = entry.getValue();
if (authorizer instanceof BasicRoleBasedAuthorizer) {
BasicRoleBasedAuthorizer basicRoleBasedAuthorizer = (BasicRoleBasedAuthorizer) authorizer;
BasicAuthDBConfig dbConfig = basicRoleBasedAuthorizer.getDbConfig();
String authorizerName = entry.getKey();
authorizerNames.add(authorizerName);
byte[] userMapBytes = getCurrentUserMapBytes(authorizerName);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
objectMapper,
userMapBytes
);
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
byte[] groupMappingMapBytes = getCurrentGroupMappingMapBytes(authorizerName);
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
objectMapper,
groupMappingMapBytes
);
cachedGroupMappingMaps.put(
authorizerName,
new BasicAuthorizerGroupMappingMapBundle(
groupMappingMap,
groupMappingMapBytes
)
);
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
roleMapBytes
);
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
initSuperUsersAndGroupMapping(authorizerName, userMap, roleMap, groupMappingMap,
dbConfig.getInitialAdminUser(),
dbConfig.getInitialAdminRole(),
dbConfig.getInitialAdminGroupMapping()
);
}
}
return true;
});
ScheduledExecutors.scheduleWithFixedDelay(
exec,
new Duration(commonCacheConfig.getPollingPeriod()),
new Duration(commonCacheConfig.getPollingPeriod()),
() -> {
if (stopped) {
return ScheduledExecutors.Signal.STOP;
}
try {
LOG.debug("Scheduled db poll is running");
for (String authorizerName : authorizerNames) {
byte[] userMapBytes = getCurrentUserMapBytes(authorizerName);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
objectMapper,
userMapBytes
);
if (userMapBytes != null) {
synchronized (cachedUserMaps) {
cachedUserMaps.put(authorizerName, new BasicAuthorizerUserMapBundle(userMap, userMapBytes));
}
}
byte[] groupMappingMapBytes = getCurrentGroupMappingMapBytes(authorizerName);
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
objectMapper,
groupMappingMapBytes
);
if (groupMappingMapBytes != null) {
synchronized (cachedGroupMappingMaps) {
cachedGroupMappingMaps.put(authorizerName, new BasicAuthorizerGroupMappingMapBundle(groupMappingMap, groupMappingMapBytes));
}
}
byte[] roleMapBytes = getCurrentRoleMapBytes(authorizerName);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
roleMapBytes
);
if (roleMapBytes != null) {
synchronized (cachedRoleMaps) {
cachedRoleMaps.put(authorizerName, new BasicAuthorizerRoleMapBundle(roleMap, roleMapBytes));
}
}
}
LOG.debug("Scheduled db poll is done");
}
catch (Throwable t) {
LOG.makeAlert(t, "Error occured while polling for cachedUserMaps, cachedGroupMappingMaps, cachedRoleMaps.").emit();
}
return ScheduledExecutors.Signal.REPEAT;
}
);
lifecycleLock.started();
}
finally {
lifecycleLock.exitStart();
}
}
@LifecycleStop
public void stop()
{
if (!lifecycleLock.canStop()) {
throw new ISE("can't stop.");
}
LOG.info("CoordinatorBasicAuthorizerMetadataStorageUpdater is stopping.");
stopped = true;
LOG.info("CoordinatorBasicAuthorizerMetadataStorageUpdater is stopped.");
}
private static String getPrefixedKeyColumn(String keyPrefix, String keyName)
{
return StringUtils.format("basic_authorization_%s_%s", keyPrefix, keyName);
}
private boolean tryUpdateUserMap(
String prefix,
Map<String, BasicAuthorizerUser> userMap,
byte[] oldUserMapValue,
byte[] newUserMapValue
)
{
try {
List<MetadataCASUpdate> updates = new ArrayList<>();
if (userMap != null) {
updates.add(
createMetadataCASUpdate(prefix, oldUserMapValue, newUserMapValue, USERS)
);
boolean succeeded = connector.compareAndSwap(updates);
if (succeeded) {
cachedUserMaps.put(prefix, new BasicAuthorizerUserMapBundle(userMap, newUserMapValue));
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
cacheNotifier.addUpdateUser(prefix, serializedUserAndRoleMap);
return true;
} else {
return false;
}
}
return false;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean tryUpdateGroupMappingMap(
String prefix,
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
byte[] oldGroupMappingMapValue,
byte[] newGroupMappingMapValue
)
{
try {
List<MetadataCASUpdate> updates = new ArrayList<>();
if (groupMappingMap != null) {
updates.add(
createMetadataCASUpdate(prefix, oldGroupMappingMapValue, newGroupMappingMapValue, GROUP_MAPPINGS)
);
boolean succeeded = connector.compareAndSwap(updates);
if (succeeded) {
cachedGroupMappingMaps.put(prefix,
new BasicAuthorizerGroupMappingMapBundle(
groupMappingMap,
newGroupMappingMapValue
)
);
byte[] serializedGroupMappingAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(prefix);
cacheNotifier.addUpdateGroupMapping(prefix, serializedGroupMappingAndRoleMap);
return true;
} else {
return false;
}
}
return false;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean tryUpdateRoleMap(
String prefix,
Map<String, BasicAuthorizerRole> roleMap,
byte[] oldRoleMapValue,
byte[] newRoleMapValue
)
{
try {
List<MetadataCASUpdate> updates = new ArrayList<>();
if (roleMap != null) {
updates.add(
createMetadataCASUpdate(prefix, oldRoleMapValue, newRoleMapValue, ROLES)
);
boolean succeeded = connector.compareAndSwap(updates);
if (succeeded) {
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
cacheNotifier.addUpdateUser(prefix, serializedUserAndRoleMap);
byte[] serializedGroupMappingAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(prefix);
cacheNotifier.addUpdateGroupMapping(prefix, serializedGroupMappingAndRoleMap);
return true;
} else {
return false;
}
}
return false;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean tryUpdateUserAndRoleMap(
String prefix,
Map<String, BasicAuthorizerUser> userMap,
byte[] oldUserMapValue,
byte[] newUserMapValue,
Map<String, BasicAuthorizerRole> roleMap,
byte[] oldRoleMapValue,
byte[] newRoleMapValue
)
{
try {
List<MetadataCASUpdate> updates = new ArrayList<>();
if (userMap != null && roleMap != null) {
updates.add(
createMetadataCASUpdate(prefix, oldUserMapValue, newUserMapValue, USERS)
);
updates.add(
createMetadataCASUpdate(prefix, oldRoleMapValue, newRoleMapValue, ROLES)
);
boolean succeeded = connector.compareAndSwap(updates);
if (succeeded) {
cachedUserMaps.put(prefix, new BasicAuthorizerUserMapBundle(userMap, newUserMapValue));
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(prefix);
cacheNotifier.addUpdateUser(prefix, serializedUserAndRoleMap);
return true;
} else {
return false;
}
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
private boolean tryUpdateGroupMappingAndRoleMap(
String prefix,
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
byte[] oldGroupMappingMapValue,
byte[] newGroupMappingMapValue,
Map<String, BasicAuthorizerRole> roleMap,
byte[] oldRoleMapValue,
byte[] newRoleMapValue
)
{
try {
List<MetadataCASUpdate> updates = new ArrayList<>();
if (groupMappingMap != null && roleMap != null) {
updates.add(
createMetadataCASUpdate(prefix, oldGroupMappingMapValue, newGroupMappingMapValue, GROUP_MAPPINGS)
);
updates.add(
createMetadataCASUpdate(prefix, oldRoleMapValue, newRoleMapValue, ROLES)
);
}
boolean succeeded = connector.compareAndSwap(updates);
if (succeeded) {
cachedGroupMappingMaps.put(prefix, new BasicAuthorizerGroupMappingMapBundle(groupMappingMap, newGroupMappingMapValue));
cachedRoleMaps.put(prefix, new BasicAuthorizerRoleMapBundle(roleMap, newRoleMapValue));
byte[] serializedGroupMappingAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(prefix);
cacheNotifier.addUpdateGroupMapping(prefix, serializedGroupMappingAndRoleMap);
return true;
} else {
return false;
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Nonnull
private MetadataCASUpdate createMetadataCASUpdate(
String prefix,
byte[] oldValue,
byte[] newValue,
String columnName
)
{
return new MetadataCASUpdate(
connectorConfig.getConfigTable(),
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
getPrefixedKeyColumn(prefix, columnName),
oldValue,
newValue
);
}
@Override
public void createUser(String prefix, String userName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
createUserInternal(prefix, userName);
}
@Override
public void deleteUser(String prefix, String userName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
deleteUserInternal(prefix, userName);
}
@Override
public void createGroupMapping(String prefix, BasicAuthorizerGroupMapping groupMapping)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
createGroupMappingInternal(prefix, groupMapping);
}
@Override
public void deleteGroupMapping(String prefix, String groupMappingName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
deleteGroupMappingInternal(prefix, groupMappingName);
}
@Override
public void createRole(String prefix, String roleName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
createRoleInternal(prefix, roleName);
}
@Override
public void deleteRole(String prefix, String roleName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
deleteRoleInternal(prefix, roleName);
}
@Override
public void assignUserRole(String prefix, String userName, String roleName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
assignUserRoleInternal(prefix, userName, roleName);
}
@Override
public void unassignUserRole(String prefix, String userName, String roleName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
unassignUserRoleInternal(prefix, userName, roleName);
}
@Override
public void assignGroupMappingRole(String prefix, String groupMappingName, String roleName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
assignGroupMappingRoleInternal(prefix, groupMappingName, roleName);
}
@Override
public void unassignGroupMappingRole(String prefix, String groupMappingName, String roleName)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
unassignGroupMappingRoleInternal(prefix, groupMappingName, roleName);
}
@Override
public void setPermissions(String prefix, String roleName, List<ResourceAction> permissions)
{
Preconditions.checkState(lifecycleLock.awaitStarted(1, TimeUnit.MILLISECONDS));
setPermissionsInternal(prefix, roleName, permissions);
}
@Override
@Nullable
public Map<String, BasicAuthorizerUser> getCachedUserMap(String prefix)
{
BasicAuthorizerUserMapBundle userMapBundle = cachedUserMaps.get(prefix);
return userMapBundle == null ? null : userMapBundle.getUserMap();
}
@Override
public Map<String, BasicAuthorizerGroupMapping> getCachedGroupMappingMap(String prefix)
{
BasicAuthorizerGroupMappingMapBundle groupMapBundle = cachedGroupMappingMaps.get(prefix);
return groupMapBundle == null ? null : groupMapBundle.getGroupMappingMap();
}
@Override
@Nullable
public Map<String, BasicAuthorizerRole> getCachedRoleMap(String prefix)
{
BasicAuthorizerRoleMapBundle roleMapBundle = cachedRoleMaps.get(prefix);
return roleMapBundle == null ? null : roleMapBundle.getRoleMap();
}
@Override
public byte[] getCurrentUserMapBytes(String prefix)
{
return connector.lookup(
connectorConfig.getConfigTable(),
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
getPrefixedKeyColumn(prefix, USERS)
);
}
@Override
public byte[] getCurrentGroupMappingMapBytes(String prefix)
{
return connector.lookup(
connectorConfig.getConfigTable(),
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
getPrefixedKeyColumn(prefix, GROUP_MAPPINGS)
);
}
@Override
public byte[] getCurrentRoleMapBytes(String prefix)
{
return connector.lookup(
connectorConfig.getConfigTable(),
MetadataStorageConnector.CONFIG_TABLE_KEY_COLUMN,
MetadataStorageConnector.CONFIG_TABLE_VALUE_COLUMN,
getPrefixedKeyColumn(prefix, ROLES)
);
}
@Override
public void refreshAllNotification()
{
authorizerNames.forEach(
(authorizerName) -> {
try {
byte[] serializedUserAndRoleMap = getCurrentUserAndRoleMapSerialized(authorizerName);
cacheNotifier.addUpdateUser(authorizerName, serializedUserAndRoleMap);
byte[] serializeGroupAndRoleMap = getCurrentGroupMappingAndRoleMapSerialized(authorizerName);
cacheNotifier.addUpdateGroupMapping(authorizerName, serializeGroupAndRoleMap);
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
);
}
private byte[] getCurrentUserAndRoleMapSerialized(String prefix) throws IOException
{
BasicAuthorizerUserMapBundle userMapBundle = cachedUserMaps.get(prefix);
BasicAuthorizerRoleMapBundle roleMapBundle = cachedRoleMaps.get(prefix);
UserAndRoleMap userAndRoleMap = new UserAndRoleMap(
userMapBundle == null ? null : userMapBundle.getUserMap(),
roleMapBundle == null ? null : roleMapBundle.getRoleMap()
);
return objectMapper.writeValueAsBytes(userAndRoleMap);
}
private byte[] getCurrentGroupMappingAndRoleMapSerialized(String prefix) throws IOException
{
BasicAuthorizerGroupMappingMapBundle groupMappingMapBundle = cachedGroupMappingMaps.get(prefix);
BasicAuthorizerRoleMapBundle roleMapBundle = cachedRoleMaps.get(prefix);
GroupMappingAndRoleMap groupMappingAndRoleMap = new GroupMappingAndRoleMap(
groupMappingMapBundle == null ? null : groupMappingMapBundle.getGroupMappingMap(),
roleMapBundle == null ? null : roleMapBundle.getRoleMap()
);
return objectMapper.writeValueAsBytes(groupMappingAndRoleMap);
}
private void createUserInternal(String prefix, String userName)
{
int attempts = 0;
while (attempts < numRetries) {
if (createUserOnce(prefix, userName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not create user [%s] due to concurrent update contention.", userName);
}
private void deleteUserInternal(String prefix, String userName)
{
int attempts = 0;
while (attempts < numRetries) {
if (deleteUserOnce(prefix, userName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not delete user [%s] due to concurrent update contention.", userName);
}
private void createGroupMappingInternal(String prefix, BasicAuthorizerGroupMapping groupMapping)
{
int attempts = 0;
while (attempts < numRetries) {
if (createGroupMappingOnce(prefix, groupMapping)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not create group mapping [%s] due to concurrent update contention.", groupMapping);
}
private void deleteGroupMappingInternal(String prefix, String groupMappingName)
{
int attempts = 0;
while (attempts < numRetries) {
if (deleteGroupMappingOnce(prefix, groupMappingName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not delete group mapping [%s] due to concurrent update contention.", groupMappingName);
}
private void createRoleInternal(String prefix, String roleName)
{
int attempts = 0;
while (attempts < numRetries) {
if (createRoleOnce(prefix, roleName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not create role [%s] due to concurrent update contention.", roleName);
}
private void deleteRoleInternal(String prefix, String roleName)
{
int attempts = 0;
while (attempts < numRetries) {
if (deleteRoleOnce(prefix, roleName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not delete role [%s] due to concurrent update contention.", roleName);
}
private void assignUserRoleInternal(String prefix, String userName, String roleName)
{
int attempts = 0;
while (attempts < numRetries) {
if (assignUserRoleOnce(prefix, userName, roleName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not assign role [%s] to user [%s] due to concurrent update contention.", roleName, userName);
}
private void unassignUserRoleInternal(String prefix, String userName, String roleName)
{
int attempts = 0;
while (attempts < numRetries) {
if (unassignUserRoleOnce(prefix, userName, roleName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not unassign role [%s] from user [%s] due to concurrent update contention.", roleName, userName);
}
private void assignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)
{
int attempts = 0;
while (attempts < numRetries) {
if (assignGroupMappingRoleOnce(prefix, groupMappingName, roleName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not assign role [%s] to group mapping [%s] due to concurrent update contention.",
roleName,
groupMappingName
);
}
private void unassignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)
{
int attempts = 0;
while (attempts < numRetries) {
if (unassignGroupMappingRoleOnce(prefix, groupMappingName, roleName)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not unassign role [%s] from group mapping [%s] due to concurrent update contention.", roleName,
groupMappingName
);
}
private void setPermissionsInternal(String prefix, String roleName, List<ResourceAction> permissions)
{
int attempts = 0;
while (attempts < numRetries) {
if (setPermissionsOnce(prefix, roleName, permissions)) {
return;
} else {
attempts++;
}
try {
Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
throw new ISE("Could not set permissions for role [%s] due to concurrent update contention.", roleName);
}
private boolean deleteUserOnce(String prefix, String userName)
{
byte[] oldValue = getCurrentUserMapBytes(prefix);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(objectMapper, oldValue);
if (userMap.get(userName) == null) {
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
} else {
userMap.remove(userName);
}
byte[] newValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
}
private boolean createUserOnce(String prefix, String userName)
{
byte[] oldValue = getCurrentUserMapBytes(prefix);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(objectMapper, oldValue);
if (userMap.get(userName) != null) {
throw new BasicSecurityDBResourceException("User [%s] already exists.", userName);
} else {
userMap.put(userName, new BasicAuthorizerUser(userName, null));
}
byte[] newValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
return tryUpdateUserMap(prefix, userMap, oldValue, newValue);
}
private boolean deleteGroupMappingOnce(String prefix, String groupMappingName)
{
byte[] oldValue = getCurrentGroupMappingMapBytes(prefix);
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(objectMapper, oldValue);
if (groupMappingMap.get(groupMappingName) == null) {
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
} else {
groupMappingMap.remove(groupMappingName);
}
byte[] newValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
return tryUpdateGroupMappingMap(prefix, groupMappingMap, oldValue, newValue);
}
private boolean createGroupMappingOnce(String prefix, BasicAuthorizerGroupMapping groupMapping)
{
byte[] oldValue = getCurrentGroupMappingMapBytes(prefix);
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(objectMapper, oldValue);
if (groupMappingMap.get(groupMapping.getName()) != null) {
throw new BasicSecurityDBResourceException("Group mapping [%s] already exists.", groupMapping.getName());
} else {
groupMappingMap.put(groupMapping.getName(), groupMapping);
}
byte[] newValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
return tryUpdateGroupMappingMap(prefix, groupMappingMap, oldValue, newValue);
}
private boolean createRoleOnce(String prefix, String roleName)
{
byte[] oldValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(objectMapper, oldValue);
if (roleMap.get(roleName) != null) {
throw new BasicSecurityDBResourceException("Role [%s] already exists.", roleName);
} else {
roleMap.put(roleName, new BasicAuthorizerRole(roleName, null));
}
byte[] newValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
return tryUpdateRoleMap(prefix, roleMap, oldValue, newValue);
}
private boolean deleteRoleOnce(String prefix, String roleName)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
oldRoleMapValue
);
if (roleMap.get(roleName) == null) {
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
} else {
roleMap.remove(roleName);
}
byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
objectMapper,
oldUserMapValue
);
for (BasicAuthorizerUser user : userMap.values()) {
user.getRoles().remove(roleName);
}
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
byte[] oldGroupMapValue = getCurrentGroupMappingMapBytes(prefix);
Map<String, BasicAuthorizerGroupMapping> groupMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
objectMapper,
oldGroupMapValue
);
for (BasicAuthorizerGroupMapping group : groupMap.values()) {
group.getRoles().remove(roleName);
}
byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMap);
byte[] newRoleMapValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
return tryUpdateUserAndRoleMap(
prefix,
userMap, oldUserMapValue, newUserMapValue,
roleMap, oldRoleMapValue, newRoleMapValue
) && tryUpdateGroupMappingAndRoleMap(
prefix,
groupMap, oldGroupMapValue, newGroupMapValue,
roleMap, newRoleMapValue, newRoleMapValue
);
}
private boolean assignUserRoleOnce(String prefix, String userName, String roleName)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
oldRoleMapValue
);
if (roleMap.get(roleName) == null) {
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
}
byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
objectMapper,
oldUserMapValue
);
BasicAuthorizerUser user = userMap.get(userName);
if (userMap.get(userName) == null) {
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
}
if (user.getRoles().contains(roleName)) {
throw new BasicSecurityDBResourceException("User [%s] already has role [%s].", userName, roleName);
}
user.getRoles().add(roleName);
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
return tryUpdateUserAndRoleMap(
prefix,
userMap, oldUserMapValue, newUserMapValue,
roleMap, oldRoleMapValue, oldRoleMapValue
);
}
private boolean unassignUserRoleOnce(String prefix, String userName, String roleName)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
oldRoleMapValue
);
if (roleMap.get(roleName) == null) {
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
}
byte[] oldUserMapValue = getCurrentUserMapBytes(prefix);
Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(
objectMapper,
oldUserMapValue
);
BasicAuthorizerUser user = userMap.get(userName);
if (userMap.get(userName) == null) {
throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName);
}
if (!user.getRoles().contains(roleName)) {
throw new BasicSecurityDBResourceException("User [%s] does not have role [%s].", userName, roleName);
}
user.getRoles().remove(roleName);
byte[] newUserMapValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
return tryUpdateUserAndRoleMap(
prefix,
userMap, oldUserMapValue, newUserMapValue,
roleMap, oldRoleMapValue, oldRoleMapValue
);
}
private boolean assignGroupMappingRoleOnce(String prefix, String groupMappingName, String roleName)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
oldRoleMapValue
);
if (roleMap.get(roleName) == null) {
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
}
byte[] oldGroupMappingMapValue = getCurrentGroupMappingMapBytes(prefix);
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
objectMapper,
oldGroupMappingMapValue
);
BasicAuthorizerGroupMapping groupMapping = groupMappingMap.get(groupMappingName);
if (groupMappingMap.get(groupMappingName) == null) {
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
}
if (groupMapping.getRoles().contains(roleName)) {
throw new BasicSecurityDBResourceException("Group mapping [%s] already has role [%s].", groupMappingName, roleName);
}
groupMapping.getRoles().add(roleName);
byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
return tryUpdateGroupMappingAndRoleMap(
prefix,
groupMappingMap, oldGroupMappingMapValue, newGroupMapValue,
roleMap, oldRoleMapValue, oldRoleMapValue
);
}
private boolean unassignGroupMappingRoleOnce(String prefix, String groupMappingName, String roleName)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
oldRoleMapValue
);
if (roleMap.get(roleName) == null) {
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
}
byte[] oldGroupMappingMapValue = getCurrentGroupMappingMapBytes(prefix);
Map<String, BasicAuthorizerGroupMapping> groupMappingMap = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(
objectMapper,
oldGroupMappingMapValue
);
BasicAuthorizerGroupMapping groupMapping = groupMappingMap.get(groupMappingName);
if (groupMappingMap.get(groupMappingName) == null) {
throw new BasicSecurityDBResourceException("Group mapping [%s] does not exist.", groupMappingName);
}
if (!groupMapping.getRoles().contains(roleName)) {
throw new BasicSecurityDBResourceException("Group mapping [%s] does not have role [%s].", groupMappingName, roleName);
}
groupMapping.getRoles().remove(roleName);
byte[] newGroupMapValue = BasicAuthUtils.serializeAuthorizerGroupMappingMap(objectMapper, groupMappingMap);
// Role map is unchanged, but submit as an update to ensure that the table didn't change (e.g., role deleted)
return tryUpdateGroupMappingAndRoleMap(
prefix,
groupMappingMap, oldGroupMappingMapValue, newGroupMapValue,
roleMap, oldRoleMapValue, oldRoleMapValue
);
}
private boolean setPermissionsOnce(String prefix, String roleName, List<ResourceAction> permissions)
{
byte[] oldRoleMapValue = getCurrentRoleMapBytes(prefix);
Map<String, BasicAuthorizerRole> roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap(
objectMapper,
oldRoleMapValue
);
if (roleMap.get(roleName) == null) {
throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName);
}
roleMap.put(
roleName,
new BasicAuthorizerRole(roleName, BasicAuthorizerPermission.makePermissionList(permissions))
);
byte[] newRoleMapValue = BasicAuthUtils.serializeAuthorizerRoleMap(objectMapper, roleMap);
return tryUpdateRoleMap(prefix, roleMap, oldRoleMapValue, newRoleMapValue);
}
private void initSuperUsersAndGroupMapping(
String authorizerName,
Map<String, BasicAuthorizerUser> userMap,
Map<String, BasicAuthorizerRole> roleMap,
Map<String, BasicAuthorizerGroupMapping> groupMappingMap,
String initialAdminUser,
String initialAdminRole,
String initialAdminGroupMapping
)
{
if (!roleMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
createRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
setPermissionsInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, SUPERUSER_PERMISSIONS);
}
if (!roleMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
createRoleInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME);
setPermissionsInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, SUPERUSER_PERMISSIONS);
}
if (!userMap.containsKey(BasicAuthUtils.ADMIN_NAME)) {
createUserInternal(authorizerName, BasicAuthUtils.ADMIN_NAME);
assignUserRoleInternal(authorizerName, BasicAuthUtils.ADMIN_NAME, BasicAuthUtils.ADMIN_NAME);
}
if (!userMap.containsKey(BasicAuthUtils.INTERNAL_USER_NAME)) {
createUserInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME);
assignUserRoleInternal(authorizerName, BasicAuthUtils.INTERNAL_USER_NAME, BasicAuthUtils.INTERNAL_USER_NAME);
}
if (initialAdminRole != null
&& !(initialAdminRole.equals(BasicAuthUtils.ADMIN_NAME) || initialAdminRole.equals(BasicAuthUtils.INTERNAL_USER_NAME))
&& !roleMap.containsKey(initialAdminRole)) {
createRoleInternal(authorizerName, initialAdminRole);
setPermissionsInternal(authorizerName, initialAdminRole, SUPERUSER_PERMISSIONS);
}
if (initialAdminUser != null
&& !(initialAdminUser.equals(BasicAuthUtils.ADMIN_NAME) || initialAdminUser.equals(BasicAuthUtils.INTERNAL_USER_NAME))
&& !userMap.containsKey(initialAdminUser)) {
createUserInternal(authorizerName, initialAdminUser);
assignUserRoleInternal(authorizerName, initialAdminUser, initialAdminRole == null ? BasicAuthUtils.ADMIN_NAME : initialAdminRole);
}
if (initialAdminGroupMapping != null && !groupMappingMap.containsKey(BasicAuthUtils.ADMIN_GROUP_MAPPING_NAME)) {
BasicAuthorizerGroupMapping groupMapping =
new BasicAuthorizerGroupMapping(
BasicAuthUtils.ADMIN_GROUP_MAPPING_NAME,
initialAdminGroupMapping,
new HashSet<>(Collections.singletonList(initialAdminRole == null ? BasicAuthUtils.ADMIN_NAME : initialAdminRole))
);
createGroupMappingInternal(authorizerName, groupMapping);
}
}
}