blob: 90efc5f449e733ee526d8b66b70d6f4eca026419 [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.qpid.server.security.group;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import com.google.common.base.Joiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.qpid.server.util.BaseAction;
import org.apache.qpid.server.util.FileHelper;
import org.apache.qpid.server.util.ServerScopedRuntimeException;
/**
* A group database that reads/writes the following file format:
*
* group1.users=user1,user2
* group2.users=user2,user3
*/
public class FileGroupDatabase implements GroupDatabase
{
private static final Logger LOGGER = LoggerFactory.getLogger(FileGroupDatabase.class);
private Map<String, Set<String>> _groupToUserMap = new ConcurrentHashMap<String, Set<String>>();
private Map<String, Set<String>> _userToGroupMap = new ConcurrentHashMap<String, Set<String>>();
private String _groupFile;
@Override
public Set<String> getAllGroups()
{
return Collections.unmodifiableSet(_groupToUserMap.keySet());
}
public synchronized void setGroupFile(String groupFile) throws IOException
{
File file = new File(groupFile);
if (!file.canRead())
{
throw new FileNotFoundException(groupFile
+ " cannot be found or is not readable");
}
readGroupFile(groupFile);
}
@Override
public Set<String> getUsersInGroup(String group)
{
if (group == null)
{
LOGGER.warn("Requested user set for null group. Returning empty set.");
return Collections.emptySet();
}
Set<String> set = _groupToUserMap.get(group);
if (set == null)
{
return Collections.emptySet();
}
else
{
return Collections.unmodifiableSet(set);
}
}
@Override
public synchronized void addUserToGroup(String user, String group)
{
Set<String> users = _groupToUserMap.get(group);
if (users == null)
{
throw new IllegalArgumentException("Group " + group + " does not exist so could not add " + user + " to it");
}
users.add(user);
Set<String> groups = _userToGroupMap.get(user);
if (groups == null)
{
groups = new ConcurrentSkipListSet<String>();
_userToGroupMap.put(user, groups);
}
groups.add(group);
update();
}
@Override
public synchronized void removeUserFromGroup(String user, String group)
{
Set<String> users = _groupToUserMap.get(group);
if (users == null)
{
throw new IllegalArgumentException("Group " + group + " does not exist so could not remove " + user + " from it");
}
users.remove(user);
Set<String> groups = _userToGroupMap.get(user);
if (groups != null)
{
groups.remove(group);
}
update();
}
@Override
public Set<String> getGroupsForUser(String user)
{
if(user == null)
{
LOGGER.warn("Requested group set for null user. Returning empty set.");
return Collections.emptySet();
}
Set<String> groups = _userToGroupMap.get(user);
if (groups == null)
{
return Collections.emptySet();
}
else
{
return Collections.unmodifiableSet(groups);
}
}
@Override
public synchronized void createGroup(String group)
{
Set<String> users = new ConcurrentSkipListSet<String>();
_groupToUserMap.put(group, users);
update();
}
@Override
public synchronized void removeGroup(String group)
{
_groupToUserMap.remove(group);
for (Set<String> groupsForUser : _userToGroupMap.values())
{
groupsForUser.remove(group);
}
update();
}
private synchronized void update()
{
if (_groupFile != null)
{
try
{
writeGroupFile(_groupFile);
}
catch (IOException e)
{
throw new ServerScopedRuntimeException("Unable to persist change to file " + _groupFile,e);
}
}
}
private synchronized void readGroupFile(String groupFile) throws IOException
{
_groupFile = groupFile;
_groupToUserMap.clear();
_userToGroupMap.clear();
Properties propertiesFile = new Properties();
try (FileInputStream fileInputStream = new FileInputStream(groupFile)) {
propertiesFile.load(fileInputStream);
}
for (String propertyName : propertiesFile.stringPropertyNames())
{
validatePropertyNameIsGroupName(propertyName);
String groupName = propertyName.replaceAll("\\.users$", "");
String userString = propertiesFile.getProperty(propertyName);
final Set<String> userSet = buildUserSetFromCommaSeparateValue(userString);
_groupToUserMap.put(groupName, userSet);
for (String userName : userSet)
{
Set<String> groupsForThisUser = _userToGroupMap.get(userName);
if (groupsForThisUser == null)
{
groupsForThisUser = new ConcurrentSkipListSet<String>();
_userToGroupMap.put(userName, groupsForThisUser);
}
groupsForThisUser.add(groupName);
}
}
}
private synchronized void writeGroupFile(final String groupFile) throws IOException
{
final Properties propertiesFile = new Properties();
for (String group : _groupToUserMap.keySet())
{
Set<String> users = _groupToUserMap.get(group);
final String userList = Joiner.on(",").useForNull("").join(users);
propertiesFile.setProperty(group + ".users", userList);
}
new FileHelper().writeFileSafely(new File(groupFile).toPath(), new BaseAction<File, IOException>()
{
@Override
public void performAction(File file) throws IOException
{
String comment = "Written " + new Date();
try(FileOutputStream fileOutputStream = new FileOutputStream(file))
{
propertiesFile.store(fileOutputStream, comment);
}
}
});
}
private void validatePropertyNameIsGroupName(String propertyName)
{
if (!propertyName.endsWith(".users"))
{
throw new IllegalArgumentException(
"Invalid definition with name '"
+ propertyName
+ "'. Group definitions must end with suffix '.users'");
}
}
private ConcurrentSkipListSet<String> buildUserSetFromCommaSeparateValue(String userString)
{
String[] users = userString.split(",");
final ConcurrentSkipListSet<String> userSet = new ConcurrentSkipListSet<String>();
for (String user : users)
{
final String trimmed = user.trim();
if (!trimmed.isEmpty())
{
userSet.add(trimmed);
}
}
return userSet;
}
}