| /* |
| * 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.nifi.registry.security.authorization.shell; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext; |
| import org.apache.nifi.registry.security.authorization.Group; |
| import org.apache.nifi.registry.security.authorization.User; |
| import org.apache.nifi.registry.security.authorization.UserAndGroups; |
| import org.apache.nifi.registry.security.authorization.UserGroupProvider; |
| import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext; |
| import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException; |
| import org.apache.nifi.registry.security.exception.SecurityProviderCreationException; |
| import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException; |
| import org.apache.nifi.registry.util.FormatUtils; |
| import org.apache.nifi.registry.util.PropertyValue; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.IOException; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| /* |
| * ShellUserGroupProvider implements UserGroupProvider by way of shell commands. |
| */ |
| public class ShellUserGroupProvider implements UserGroupProvider { |
| private final static Logger logger = LoggerFactory.getLogger(ShellUserGroupProvider.class); |
| |
| private final static String OS_TYPE_ERROR = "Unsupported operating system."; |
| private final static String SYS_CHECK_ERROR = "System check failed - cannot provide users and groups."; |
| private final static Map<String, User> usersById = new HashMap<>(); // id == identifier |
| private final static Map<String, User> usersByName = new HashMap<>(); // name == identity |
| private final static Map<String, Group> groupsById = new HashMap<>(); |
| |
| public static final String REFRESH_DELAY_PROPERTY = "Refresh Delay"; |
| private static final long MINIMUM_SYNC_INTERVAL_MILLISECONDS = 10_000; |
| |
| public static final String EXCLUDE_USER_PROPERTY = "Exclude Users"; |
| public static final String EXCLUDE_GROUP_PROPERTY = "Exclude Groups"; |
| |
| public static final String COMMAND_TIMEOUT_PROPERTY = "Command Timeout"; |
| |
| private static final String DEFAULT_COMMAND_TIMEOUT = "60 seconds"; |
| |
| private long fixedDelay; |
| private Pattern excludeUsers; |
| private Pattern excludeGroups; |
| private int timeoutSeconds; |
| |
| // Our scheduler has one thread for users, one for groups: |
| private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); |
| |
| // Commands selected during initialization: |
| private ShellCommandsProvider selectedShellCommands; |
| |
| private ShellRunner shellRunner; |
| |
| // Start of the UserGroupProvider implementation. Javadoc strings |
| // copied from the interface definition for reference. |
| |
| /** |
| * Retrieves all users. Must be non null |
| * |
| * @return a list of users |
| * @throws AuthorizationAccessException if there was an unexpected error performing the operation |
| */ |
| @Override |
| public Set<User> getUsers() throws AuthorizationAccessException { |
| synchronized (usersById) { |
| logger.debug("getUsers has user set of size: " + usersById.size()); |
| return new HashSet<>(usersById.values()); |
| } |
| } |
| |
| /** |
| * Retrieves the user with the given identifier. |
| * |
| * @param identifier the id of the user to retrieve |
| * @return the user with the given id, or null if no matching user was found |
| * @throws AuthorizationAccessException if there was an unexpected error performing the operation |
| */ |
| @Override |
| public User getUser(String identifier) throws AuthorizationAccessException { |
| User user; |
| |
| synchronized (usersById) { |
| user = usersById.get(identifier); |
| } |
| |
| if (user == null) { |
| logger.debug("getUser (by id) user not found: " + identifier); |
| } else { |
| logger.debug("getUser (by id) found user: " + user + " for id: " + identifier); |
| } |
| return user; |
| } |
| |
| /** |
| * Retrieves the user with the given identity. |
| * |
| * @param identity the identity of the user to retrieve |
| * @return the user with the given identity, or null if no matching user was found |
| * @throws AuthorizationAccessException if there was an unexpected error performing the operation |
| */ |
| @Override |
| public User getUserByIdentity(String identity) throws AuthorizationAccessException { |
| User user; |
| |
| synchronized (usersByName) { |
| user = usersByName.get(identity); |
| } |
| |
| if (user == null) { |
| refreshOneUser(selectedShellCommands.getUserByName(identity), "Get Single User by Name"); |
| user = usersByName.get(identity); |
| } |
| |
| if (user == null) { |
| logger.debug("getUser (by name) user not found: " + identity); |
| } else { |
| logger.debug("getUser (by name) found user: " + user.getIdentity() + " for name: " + identity); |
| } |
| return user; |
| } |
| |
| /** |
| * Retrieves all groups. Must be non null |
| * |
| * @return a list of groups |
| * @throws AuthorizationAccessException if there was an unexpected error performing the operation |
| */ |
| @Override |
| public Set<Group> getGroups() throws AuthorizationAccessException { |
| synchronized (groupsById) { |
| logger.debug("getGroups has group set of size: " + groupsById.size()); |
| return new HashSet<>(groupsById.values()); |
| } |
| } |
| |
| /** |
| * Retrieves a Group by Id. |
| * |
| * @param identifier the identifier of the Group to retrieve |
| * @return the Group with the given identifier, or null if no matching group was found |
| * @throws AuthorizationAccessException if there was an unexpected error performing the operation |
| */ |
| @Override |
| public Group getGroup(String identifier) throws AuthorizationAccessException { |
| Group group; |
| |
| synchronized (groupsById) { |
| group = groupsById.get(identifier); |
| } |
| |
| if (group == null) { |
| refreshOneGroup(selectedShellCommands.getGroupById(identifier), "Get Single Group by Id"); |
| group = groupsById.get(identifier); |
| } |
| |
| if (group == null) { |
| logger.debug("getGroup (by id) group not found: " + identifier); |
| } else { |
| logger.debug("getGroup (by id) found group: " + group.getName() + " for id: " + identifier); |
| } |
| return group; |
| |
| } |
| |
| /** |
| * Gets a user and their groups. |
| * |
| * @return the UserAndGroups for the specified identity |
| * @throws AuthorizationAccessException if there was an unexpected error performing the operation |
| */ |
| @Override |
| public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { |
| User user = getUserByIdentity(identity); |
| logger.debug("Retrieved user {} for identity {}", new Object[]{user, identity}); |
| |
| Set<Group> groups = new HashSet<>(); |
| if (user != null) { |
| for (Group g : getGroups()) { |
| if (g.getUsers().contains(user.getIdentifier())) { |
| logger.debug("User {} belongs to group {}", new Object[]{user.getIdentity(), g.getName()}); |
| groups.add(g); |
| } |
| } |
| } |
| |
| if (groups.isEmpty()) { |
| logger.debug("User {} belongs to no groups", user); |
| } |
| |
| return new UserAndGroups() { |
| @Override |
| public User getUser() { |
| return user; |
| } |
| |
| @Override |
| public Set<Group> getGroups() { |
| return groups; |
| } |
| }; |
| } |
| |
| /** |
| * Called immediately after instance creation for implementers to perform additional setup |
| * |
| * @param initializationContext in which to initialize |
| */ |
| @Override |
| public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException { |
| } |
| |
| /** |
| * Called to configure the Authorizer. |
| * |
| * @param configurationContext at the time of configuration |
| * @throws SecurityProviderCreationException for any issues configuring the provider |
| */ |
| @Override |
| public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException { |
| logger.info("Configuring ShellUserGroupProvider"); |
| |
| fixedDelay = getDelayProperty(configurationContext, REFRESH_DELAY_PROPERTY, "5 mins"); |
| timeoutSeconds = getTimeoutProperty(configurationContext, COMMAND_TIMEOUT_PROPERTY, DEFAULT_COMMAND_TIMEOUT); |
| shellRunner = new ShellRunner(timeoutSeconds); |
| logger.debug("Configured ShellRunner with command timeout of '{}' seconds", new Object[]{timeoutSeconds}); |
| |
| |
| // Our next init step is to select the command set based on the operating system name: |
| ShellCommandsProvider commands = getCommandsProvider(); |
| |
| if (commands == null) { |
| commands = getCommandsProviderFromName(null); |
| setCommandsProvider(commands); |
| } |
| |
| // Our next init step is to run the system check from that command set to determine if the other commands |
| // will work on this host or not. |
| try { |
| shellRunner.runShell(commands.getSystemCheck()); |
| } catch (final Exception e) { |
| logger.error("initialize exception: " + e + " system check command: " + commands.getSystemCheck()); |
| throw new SecurityProviderCreationException(SYS_CHECK_ERROR, e); |
| } |
| |
| // The next step is to add the user and group exclude regexes: |
| try { |
| excludeGroups = Pattern.compile(getProperty(configurationContext, EXCLUDE_GROUP_PROPERTY, "")); |
| excludeUsers = Pattern.compile(getProperty(configurationContext, EXCLUDE_USER_PROPERTY, "")); |
| } catch (final PatternSyntaxException e) { |
| throw new SecurityProviderCreationException(e); |
| } |
| |
| // With our command set selected, and our system check passed, we can pull in the users and groups: |
| refreshUsersAndGroups(); |
| |
| // And finally, our last init step is to fire off the refresh thread: |
| scheduler.scheduleWithFixedDelay(() -> { |
| try { |
| refreshUsersAndGroups(); |
| }catch (final Throwable t) { |
| logger.error("", t); |
| } |
| }, fixedDelay, fixedDelay, TimeUnit.MILLISECONDS); |
| |
| logger.info("Completed configuration of ShellUserGroupProvider"); |
| } |
| |
| private static ShellCommandsProvider getCommandsProviderFromName(String osName) { |
| if (osName == null) { |
| osName = System.getProperty("os.name"); |
| } |
| |
| ShellCommandsProvider commands; |
| if (osName.startsWith("Linux")) { |
| logger.debug("Selected Linux command set."); |
| commands = new NssShellCommands(); |
| } else if (osName.startsWith("Mac OS X")) { |
| logger.debug("Selected OSX command set."); |
| commands = new OsxShellCommands(); |
| } else { |
| throw new SecurityProviderCreationException(OS_TYPE_ERROR); |
| } |
| return commands; |
| } |
| |
| private String getProperty(AuthorizerConfigurationContext authContext, String propertyName, String defaultValue) { |
| final PropertyValue property = authContext.getProperty(propertyName); |
| final String value; |
| |
| if (property != null && property.isSet()) { |
| value = property.getValue(); |
| } else { |
| value = defaultValue; |
| } |
| return value; |
| |
| } |
| |
| private long getDelayProperty(AuthorizerConfigurationContext authContext, String propertyName, String defaultValue) { |
| final PropertyValue intervalProperty = authContext.getProperty(propertyName); |
| final String propertyValue; |
| final long syncInterval; |
| |
| if (intervalProperty.isSet()) { |
| propertyValue = intervalProperty.getValue(); |
| } else { |
| propertyValue = defaultValue; |
| } |
| |
| try { |
| syncInterval = Math.round(FormatUtils.getPreciseTimeDuration(propertyValue, TimeUnit.MILLISECONDS)); |
| } catch (final IllegalArgumentException ignored) { |
| throw new SecurityProviderCreationException(String.format("The %s '%s' is not a valid time interval.", propertyName, propertyValue)); |
| } |
| |
| if (syncInterval < MINIMUM_SYNC_INTERVAL_MILLISECONDS) { |
| throw new SecurityProviderCreationException(String.format("The %s '%s' is below the minimum value of '%d ms'", propertyName, propertyValue, MINIMUM_SYNC_INTERVAL_MILLISECONDS)); |
| } |
| return syncInterval; |
| } |
| |
| private int getTimeoutProperty(AuthorizerConfigurationContext authContext, String propertyName, String defaultValue) { |
| final PropertyValue timeoutProperty = authContext.getProperty(propertyName); |
| |
| final String propertyValue; |
| if (timeoutProperty.isSet()) { |
| propertyValue = timeoutProperty.getValue(); |
| } else { |
| propertyValue = defaultValue; |
| } |
| |
| final long timeoutValue; |
| try { |
| timeoutValue = Math.round(FormatUtils.getPreciseTimeDuration(propertyValue, TimeUnit.SECONDS)); |
| } catch (final IllegalArgumentException ignored) { |
| throw new SecurityProviderCreationException(String.format("The %s '%s' is not a valid time interval.", propertyName, propertyValue)); |
| } |
| |
| return Math.toIntExact(timeoutValue); |
| } |
| |
| /** |
| * Called immediately before instance destruction for implementers to release resources. |
| * |
| * @throws SecurityProviderDestructionException If pre-destruction fails. |
| */ |
| @Override |
| public void preDestruction() throws SecurityProviderDestructionException { |
| try { |
| scheduler.shutdownNow(); |
| } catch (final Exception e) { |
| logger.warn("Error shutting down refresh scheduler: " + e.getMessage(), e); |
| } |
| try { |
| shellRunner.shutdown(); |
| } catch (final Exception e) { |
| logger.warn("Error shutting down ShellRunner: " + e.getMessage(), e); |
| } |
| } |
| |
| public ShellCommandsProvider getCommandsProvider() { |
| return selectedShellCommands; |
| } |
| |
| public void setCommandsProvider(ShellCommandsProvider commandsProvider) { |
| selectedShellCommands = commandsProvider; |
| } |
| |
| /** |
| * Refresh a single user. |
| * |
| * @param command Shell command to read a single user. Pre-formatted by caller. |
| * @param description Shell command description. |
| */ |
| private void refreshOneUser(String command, String description) { |
| if (command != null) { |
| Map<String, User> idToUser = new HashMap<>(); |
| Map<String, User> usernameToUser = new HashMap<>(); |
| Map<String, User> gidToUser = new HashMap<>(); |
| List<String> userLines; |
| |
| try { |
| userLines = shellRunner.runShell(command, description); |
| rebuildUsers(userLines, idToUser, usernameToUser, gidToUser); |
| } catch (final IOException ioexc) { |
| logger.error("refreshOneUser shell exception: " + ioexc); |
| } |
| |
| if (idToUser.size() > 0) { |
| synchronized (usersById) { |
| usersById.putAll(idToUser); |
| } |
| } |
| |
| if (usernameToUser.size() > 0) { |
| synchronized (usersByName) { |
| usersByName.putAll(usernameToUser); |
| } |
| } |
| } else { |
| logger.info("Get Single User not supported on this system."); |
| } |
| } |
| |
| /** |
| * Refresh a single group. |
| * |
| * @param command Shell command to read a single group. Pre-formatted by caller. |
| * @param description Shell command description. |
| */ |
| private void refreshOneGroup(String command, String description) { |
| if (command != null) { |
| Map<String, Group> gidToGroup = new HashMap<>(); |
| List<String> groupLines; |
| |
| try { |
| groupLines = shellRunner.runShell(command, description); |
| rebuildGroups(groupLines, gidToGroup); |
| } catch (final IOException ioexc) { |
| logger.error("refreshOneGroup shell exception: " + ioexc); |
| } |
| |
| if (gidToGroup.size() > 0) { |
| synchronized (groupsById) { |
| groupsById.putAll(gidToGroup); |
| } |
| } |
| } else { |
| logger.info("Get Single Group not supported on this system."); |
| } |
| } |
| |
| /** |
| * This is our entry point for user and group refresh. This method runs the top-level |
| * `getUserList()` and `getGroupsList()` shell commands, then passes those results to the |
| * other methods for record parse, extract, and object construction. |
| */ |
| private void refreshUsersAndGroups() { |
| final long startTime = System.currentTimeMillis(); |
| |
| Map<String, User> uidToUser = new HashMap<>(); |
| Map<String, User> usernameToUser = new HashMap<>(); |
| Map<String, User> gidToUser = new HashMap<>(); |
| Map<String, Group> gidToGroup = new HashMap<>(); |
| |
| List<String> userLines; |
| List<String> groupLines; |
| |
| try { |
| userLines = shellRunner.runShell(selectedShellCommands.getUsersList(), "Get Users List"); |
| groupLines = shellRunner.runShell(selectedShellCommands.getGroupsList(), "Get Groups List"); |
| } catch (final IOException ioexc) { |
| logger.error("refreshUsersAndGroups shell exception: " + ioexc); |
| return; |
| } |
| |
| rebuildUsers(userLines, uidToUser, usernameToUser, gidToUser); |
| rebuildGroups(groupLines, gidToGroup); |
| reconcilePrimaryGroups(gidToUser, gidToGroup); |
| |
| synchronized (usersById) { |
| usersById.clear(); |
| usersById.putAll(uidToUser); |
| |
| if (logger.isTraceEnabled()) { |
| logger.trace("=== Users by id..."); |
| Set<User> sortedUsers = new TreeSet<>(Comparator.comparing(User::getIdentity)); |
| sortedUsers.addAll(usersById.values()); |
| sortedUsers.forEach(u -> logger.trace("=== " + u.toString())); |
| } |
| } |
| |
| synchronized (usersByName) { |
| usersByName.clear(); |
| usersByName.putAll(usernameToUser); |
| logger.debug("users now size: " + usersByName.size()); |
| } |
| |
| synchronized (groupsById) { |
| groupsById.clear(); |
| groupsById.putAll(gidToGroup); |
| logger.debug("groups now size: " + groupsById.size()); |
| |
| if (logger.isTraceEnabled()) { |
| logger.trace("=== Groups by id..."); |
| Set<Group> sortedGroups = new TreeSet<>(Comparator.comparing(Group::getName)); |
| sortedGroups.addAll(groupsById.values()); |
| sortedGroups.forEach(g -> logger.trace("=== " + g.toString())); |
| } |
| } |
| |
| final long endTime = System.currentTimeMillis(); |
| logger.info("Refreshed users and groups, took {} seconds", (endTime - startTime) / 1000); |
| } |
| |
| /** |
| * This method parses the output of the `getUsersList()` shell command, where we expect the output |
| * to look like `user-name:user-id:primary-group-id`. |
| * <p> |
| * This method splits each output line on the ":" and attempts to build a User object |
| * from the resulting name, uid, and primary gid. Unusable records are logged. |
| */ |
| private void rebuildUsers(List<String> userLines, Map<String, User> idToUser, Map<String, User> usernameToUser, Map<String, User> gidToUser) { |
| userLines.forEach(line -> { |
| logger.trace("Processing user: {}", new Object[]{line}); |
| |
| String[] record = line.split(":"); |
| if (record.length > 2) { |
| String userIdentity = record[0], userIdentifier = record[1], primaryGroupIdentifier = record[2]; |
| |
| if (!StringUtils.isBlank(userIdentifier) && !StringUtils.isBlank(userIdentity) && !excludeUsers.matcher(userIdentity).matches()) { |
| User user = new User.Builder() |
| .identity(userIdentity) |
| .identifierGenerateFromSeed(getUserIdentifierSeed(userIdentity)) |
| .build(); |
| idToUser.put(user.getIdentifier(), user); |
| usernameToUser.put(userIdentity, user); |
| logger.debug("Refreshed user {}", new Object[]{user}); |
| |
| if (!StringUtils.isBlank(primaryGroupIdentifier)) { |
| // create a temporary group to deterministically generate the group id and associate this user |
| Group group = new Group.Builder() |
| .name(primaryGroupIdentifier) |
| .identifierGenerateFromSeed(getGroupIdentifierSeed(primaryGroupIdentifier)) |
| .build(); |
| gidToUser.put(group.getIdentifier(), user); |
| logger.debug("Associated primary group {} with user {}", new Object[]{group.getIdentifier(), userIdentity}); |
| } else { |
| logger.warn("Null or empty primary group id for: " + userIdentity); |
| } |
| |
| } else { |
| logger.warn("Null, empty, or skipped user name: " + userIdentity + " or id: " + userIdentifier); |
| } |
| } else { |
| logger.warn("Unexpected record format. Expected 3 or more colon separated values per line."); |
| } |
| }); |
| } |
| |
| /** |
| * This method parses the output of the `getGroupsList()` shell command, where we expect the output |
| * to look like `group-name:group-id`. |
| * <p> |
| * This method splits each output line on the ":" and attempts to build a Group object |
| * from the resulting name and gid. Unusable records are logged. |
| * <p> |
| * This command also runs the `getGroupMembers(username)` command once per group. The expected output |
| * of that command should look like `group-name-1,group-name-2`. |
| */ |
| private void rebuildGroups(List<String> groupLines, Map<String, Group> groupsById) { |
| groupLines.forEach(line -> { |
| logger.trace("Processing group: {}", new Object[]{line}); |
| |
| String[] record = line.split(":"); |
| if (record.length > 1) { |
| Set<String> users = new HashSet<>(); |
| String groupName = record[0], groupIdentifier = record[1]; |
| |
| try { |
| String groupMembersCommand = selectedShellCommands.getGroupMembers(groupName); |
| List<String> memberLines = shellRunner.runShell(groupMembersCommand); |
| // Use the first line only, and log if the line count isn't exactly one: |
| if (!memberLines.isEmpty()) { |
| String memberLine = memberLines.get(0); |
| if (!StringUtils.isBlank(memberLine)) { |
| String[] members = memberLine.split(","); |
| for (String userIdentity : members) { |
| if (!StringUtils.isBlank(userIdentity)) { |
| User tempUser = new User.Builder() |
| .identity(userIdentity) |
| .identifierGenerateFromSeed(getUserIdentifierSeed(userIdentity)) |
| .build(); |
| users.add(tempUser.getIdentifier()); |
| logger.debug("Added temp user {} for group {}", new Object[]{tempUser, groupName}); |
| } |
| } |
| } else { |
| logger.debug("list membership returned no members"); |
| } |
| } else { |
| logger.debug("list membership returned zero lines."); |
| } |
| if (memberLines.size() > 1) { |
| logger.error("list membership returned too many lines, only used the first."); |
| } |
| |
| } catch (final IOException ioexc) { |
| logger.error("list membership shell exception: " + ioexc); |
| } |
| |
| if (!StringUtils.isBlank(groupIdentifier) && !StringUtils.isBlank(groupName) && !excludeGroups.matcher(groupName).matches()) { |
| Group group = new Group.Builder() |
| .name(groupName) |
| .identifierGenerateFromSeed(getGroupIdentifierSeed(groupIdentifier)) |
| .addUsers(users) |
| .build(); |
| groupsById.put(group.getIdentifier(), group); |
| logger.debug("Refreshed group {}", new Object[] {group}); |
| } else { |
| logger.warn("Null, empty, or skipped group name: " + groupName + " or id: " + groupIdentifier); |
| } |
| } else { |
| logger.warn("Unexpected record format. Expected 1 or more comma separated values."); |
| } |
| }); |
| } |
| |
| /** |
| * This method parses the output of the `getGroupsList()` shell command, where we expect the output |
| * to look like `group-name:group-id`. |
| * <p> |
| * This method splits each output line on the ":" and attempts to build a Group object |
| * from the resulting name and gid. |
| */ |
| private void reconcilePrimaryGroups(Map<String, User> uidToUser, Map<String, Group> gidToGroup) { |
| uidToUser.forEach((primaryGid, primaryUser) -> { |
| Group primaryGroup = gidToGroup.get(primaryGid); |
| |
| if (primaryGroup == null) { |
| logger.warn("Primary group {} not found for {}", new Object[]{primaryGid, primaryUser.getIdentity()}); |
| } else if (!excludeGroups.matcher(primaryGroup.getName()).matches()) { |
| Set<String> groupUsers = primaryGroup.getUsers(); |
| if (!groupUsers.contains(primaryUser.getIdentifier())) { |
| Set<String> updatedUserIdentifiers = new HashSet<>(groupUsers); |
| updatedUserIdentifiers.add(primaryUser.getIdentifier()); |
| |
| Group updatedGroup = new Group.Builder() |
| .identifier(primaryGroup.getIdentifier()) |
| .name(primaryGroup.getName()) |
| .addUsers(updatedUserIdentifiers) |
| .build(); |
| gidToGroup.put(updatedGroup.getIdentifier(), updatedGroup); |
| logger.debug("Added user {} to primary group {}", new Object[]{primaryUser, updatedGroup}); |
| } else { |
| logger.debug("Primary group {} already contains user {}", new Object[]{primaryGroup, primaryUser}); |
| } |
| } else { |
| logger.debug("Primary group {} excluded from matcher for {}", new Object[]{primaryGroup.getName(), primaryUser.getIdentity()}); |
| } |
| }); |
| } |
| |
| private String getUserIdentifierSeed(final String userIdentifier) { |
| return userIdentifier + "-user"; |
| } |
| |
| private String getGroupIdentifierSeed(final String groupIdentifier) { |
| return groupIdentifier + "-group"; |
| } |
| |
| |
| /** |
| * @return The fixed refresh delay. |
| */ |
| public long getRefreshDelay() { |
| return fixedDelay; |
| } |
| |
| /** |
| * Testing concession for clearing the internal caches. |
| */ |
| void clearCaches() { |
| synchronized (usersById) { |
| usersById.clear(); |
| } |
| |
| synchronized (usersByName) { |
| usersByName.clear(); |
| } |
| |
| synchronized (groupsById) { |
| groupsById.clear(); |
| } |
| } |
| |
| /** |
| * @return The size of the internal user cache. |
| */ |
| public int userCacheSize() { |
| return usersById.size(); |
| } |
| |
| /** |
| * @return The size of the internal group cache. |
| */ |
| public int groupCacheSize() { |
| return groupsById.size(); |
| } |
| } |