blob: c784b20e8018269372d7b8fd5ec49777f6e66c72 [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.ace.resourceprocessor.useradmin.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.ace.resourceprocessor.useradmin.UserAdminConfigurator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.log.LogService;
import org.osgi.service.useradmin.Group;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdmin;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
public class UserAdminStore extends ResourceStore implements UserAdminConfigurator {
/**
* Value object for relaying user information between XML-processing methods and UserAdmin users. This indirection
* is necessary because we want to separate the parsing of the XML, and the actual installation.
*/
private class ProcessRole {
private final int m_type;
private final String m_name;
private final Map<String, Object> m_properties = new HashMap<>();
private final Map<String, Object> m_credentials = new HashMap<>();
private final List<String> m_memberOf = new ArrayList<>();
ProcessRole(String name, int type) {
m_name = name;
m_type = type;
}
public Map<String, Object> getCredentials() {
return m_credentials;
}
public List<String> getMemberOf() {
return m_memberOf;
}
public String getName() {
return m_name;
}
public Map<String, Object> getProperties() {
return m_properties;
}
public int getType() {
return m_type;
}
}
private final Object m_installListLock = new Object();
private final Object m_userAdminLock = new Object();
private volatile UserAdmin m_userAdmin;
private volatile LogService m_log;
private volatile List<String> m_installedUsers;
private List<ProcessRole> m_toInstall = new ArrayList<>();
private List<ProcessRole> m_toRemove = new ArrayList<>();
private boolean m_clear;
UserAdminStore(BundleContext context) {
super(context);
}
@Override
public void begin() {
m_installedUsers = new ArrayList<>();
}
@Override
public void end() {
checkTransactionInProgress();
m_installedUsers = null;
}
@Override
public void install(String resourceName) throws IOException {
begin();
installRoles(getDocument(getResource(resourceName)));
updateUserAdmin();
end();
}
public void setUsers(InputStream input) throws IOException {
setUsers(getDocument(input), true /* clearExistingUsers */);
}
@Override
public void uninstall(String resourceName) throws IOException {
begin();
removeRoles(getDocument(getResource(resourceName)));
updateUserAdmin();
end();
}
/**
* Called by the dependency manager when a user admin becomes available.
*/
public void userAdminAdded(UserAdmin admin) {
synchronized (m_userAdminLock) {
if (m_userAdmin != null) {
throw new IllegalStateException("UserAdminStore is intended to work with a single user admin.");
}
m_userAdmin = admin;
begin();
updateUserAdmin();
end();
}
}
/**
* Called by the dependency manager when a user admin goes away.
*/
public void userAdminRemoved(UserAdmin admin) {
synchronized (m_userAdminLock) {
if (m_userAdmin != admin) {
throw new IllegalStateException("UserAdminStore is intended to work with a single user admin.");
}
m_userAdmin = null;
}
}
@Override
public void validate(InputStream resource) throws Exception {
Document doc = getDocument(resource);
getRoles(doc);
}
private void checkTransactionInProgress() {
if (m_installedUsers == null) {
throw new IllegalStateException("No transaction in progress!");
}
}
private void clearDictionary(Dictionary<?, ?> dict) {
Enumeration<?> i = dict.keys();
while (i.hasMoreElements()) {
dict.remove(i.nextElement());
}
}
/**
* Helper method that checks the presence of an object in an array. Returns <code>true</code> if <code>t</code> is
* in <code>ts</code>, <code>false</code> otherwise.
*/
private <T> boolean contains(T t, T[] ts) {
for (T current : ts) {
if (current.equals(t)) {
return true;
}
}
return false;
}
/**
* Gets the DOM document contained in a stream.
*/
private Document getDocument(InputStream input) throws IOException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
builder = factory.newDocumentBuilder();
return builder.parse(input);
}
catch (ParserConfigurationException e) {
throw new IOException("Error instantiation XML parser:" + e.getMessage());
}
catch (SAXException e) {
throw new IOException("Error parsing user data:" + e.getMessage());
}
}
/**
* Helper method that takes a single XML node containing a 'user' or 'group', and return a ProcessRole for it.
*/
private ProcessRole getRole(Node node) {
ProcessRole result = new ProcessRole(node.getAttributes().getNamedItem("name").getTextContent(), (node.getNodeName().equals("group") ? Role.GROUP : Role.USER));
for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child.getNodeName().equals("properties")) {
for (Node property = child.getFirstChild(); property != null; property = property.getNextSibling()) {
if (!property.getNodeName().equals("#text")) {
String type = null;
Node typeNode = property.getAttributes().getNamedItem("type");
if (typeNode != null) {
type = typeNode.getTextContent();
}
result.getProperties().put(property.getNodeName(), "byte[]".equals(type) ? property.getTextContent().getBytes() : property.getTextContent());
}
}
}
else if (child.getNodeName().equals("credentials")) {
for (Node credential = child.getFirstChild(); credential != null; credential = credential.getNextSibling()) {
if (!credential.getNodeName().equals("#text")) {
String type = null;
Node typeNode = credential.getAttributes().getNamedItem("type");
if (typeNode != null) {
type = typeNode.getTextContent();
}
result.getCredentials().put(credential.getNodeName(), "byte[]".equals(type) ? credential.getTextContent().getBytes() : credential.getTextContent());
}
}
}
else if (child.getNodeName().equals("memberof")) {
if (!child.getNodeName().equals("#text")) {
result.getMemberOf().add(child.getTextContent());
}
}
}
return result;
}
/**
* Gets all roles that are present in a document.
*
* @param doc
* The document to use.
* @return A list of ProcessRoles.
*/
private List<ProcessRole> getRoles(Document doc) {
List<ProcessRole> result = new ArrayList<>();
for (Node node = doc.getFirstChild().getFirstChild(); node != null; node = node.getNextSibling()) {
if (!node.getNodeName().equals("#text")) {
result.add(getRole(node));
}
}
return result;
}
/**
* Installs the users and groups found in a document.
*/
private void installRoles(Document doc) {
synchronized (m_installListLock) {
m_toInstall.addAll(getRoles(doc));
}
}
/**
* Helper that finds all groups this role is a member of.
*/
private Group[] memberOf(Role r) {
List<Group> result = new ArrayList<>();
Role[] roles = null;
try {
roles = m_userAdmin.getRoles(null);
}
catch (InvalidSyntaxException e) {
// Will not happen, since we pass in a null filter.
}
if (roles == null) {
return new Group[0];
}
for (Role group : roles) {
if (group instanceof Group) {
Role[] members = ((Group) group).getMembers();
if (members != null) {
if (contains(r, members)) {
result.add((Group) group);
}
}
}
}
return result.toArray(new Group[result.size()]);
}
/**
* Removes the users and groups found in a document.
*/
private void removeRoles(Document doc) {
synchronized (m_installListLock) {
m_toRemove.addAll(getRoles(doc));
}
}
private void setUsers(Document doc, boolean clearExistingUsers) {
m_toInstall.clear();
m_toRemove.clear();
installRoles(doc);
m_clear = clearExistingUsers;
begin();
updateUserAdmin();
end();
}
/**
* Updates a role with new parameter, but reuses the UserAdmin's role object for this (if available).
*/
private void updateRole(ProcessRole role) {
m_installedUsers.add(role.getName());
Role r = m_userAdmin.getRole(role.getName());
if (r == null) {
r = m_userAdmin.createRole(role.getName(), role.getType());
}
clearDictionary(r.getProperties());
for (Entry<String, Object> entry : role.getProperties().entrySet()) {
r.getProperties().put(entry.getKey(), entry.getValue());
}
clearDictionary(((User) r).getCredentials());
if (role.getType() == Role.USER) {
for (Entry<String, Object> entry : role.getCredentials().entrySet()) {
((User) r).getCredentials().put(entry.getKey(), entry.getValue());
}
}
for (Group g : memberOf(r)) {
g.removeMember(r);
}
for (String groupName : role.getMemberOf()) {
Group g = (Group) m_userAdmin.getRole(groupName);
if (g == null) {
m_log.log(LogService.LOG_WARNING, "Cannot add user " + role.getName() + " to group " + groupName + ", because the group does not exist.");
continue;
}
g.addMember(r);
}
}
/**
* Updates the currently present UserAdmin with the data in m_toInstall and m_toRemove.
*/
private void updateUserAdmin() {
synchronized (m_installListLock) {
synchronized (m_userAdminLock) {
if (m_userAdmin == null) {
return;
}
// install or update all roles we have to update
while (!m_toInstall.isEmpty()) {
ProcessRole role = m_toInstall.remove(0);
updateRole(role);
}
// remove all roles that have not been updated if this install
// is a full install
if (m_clear) {
Role[] roles = null;
try {
roles = m_userAdmin.getRoles(null);
}
catch (InvalidSyntaxException e) {
// Will not happen, since we pass in a null filter.
}
for (Role r : roles) {
if (!m_installedUsers.contains(r.getName())) {
m_userAdmin.removeRole(r.getName());
}
}
}
// if this is not a full install, remove any roles that should be
// removed
if (!m_clear) {
while (!m_toRemove.isEmpty()) {
// do it tail to head
ProcessRole role = m_toRemove.remove(m_toRemove.size() - 1);
if (!m_installedUsers.contains(role.getName())) {
m_userAdmin.removeRole(role.getName());
}
}
}
m_clear = false;
}
}
}
}