/*
* eXist Open Source Native XML Database
* Copyright (C) 2001 Wolfgang M. Meier
* meier@ifs.tu-darmstadt.de
* http://exist.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.security;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.CollectionConfiguration;
import org.exist.collections.IndexInfo;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.DocumentImpl;
import org.exist.security.xacml.ExistPDP;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.sync.Sync;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.LockException;
import org.exist.util.MimeType;
import org.exist.util.hashtable.Int2ObjectHashMap;
import org.exist.xmldb.XmldbURI;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* SecurityManager is responsible for managing users and groups.
*
* There's only one SecurityManager for each database instance, which
* may be obtained by {@link BrokerPool#getSecurityManager()}.
*
* Users and groups are stored in the system collection, in document
* users.xml. While it is possible to edit this file by hand, it
* may lead to unexpected results, since SecurityManager reads
* users.xml only during database startup and shutdown.
*/
public class XMLSecurityManager implements SecurityManager {
public static final String CONFIGURATION_ELEMENT_NAME = "default-permissions";
public static final String COLLECTION_ATTRIBUTE = "collection";
public static final String RESOURCE_ATTRIBUTE = "resource";
public static final String PROPERTY_PERMISSIONS_COLLECTIONS = "indexer.permissions.collection";
public static final String PROPERTY_PERMISSIONS_RESOURCES = "indexer.permissions.resource";
public final static String DBA_GROUP = "dba";
public final static String DBA_USER = "admin";
public final static String GUEST_GROUP = "guest";
public final static String GUEST_USER = "guest";
public final static User SYSTEM_USER = new User(DBA_USER, null, DBA_GROUP);
private final static Logger LOG =
Logger.getLogger(SecurityManager.class);
private BrokerPool pool;
private Int2ObjectHashMap groups = new Int2ObjectHashMap(65);
private Int2ObjectHashMap users = new Int2ObjectHashMap(65);
private int nextUserId = 0;
private int nextGroupId = 0;
private int defCollectionPermissions = Permission.DEFAULT_PERM;
private int defResourcePermissions = Permission.DEFAULT_PERM;
private ExistPDP pdp;
public XMLSecurityManager() {
}
/**
* Initialize the security manager.
*
* Checks if the file users.xml exists in the system collection of the database.
* If not, it is created with two default users: admin and guest.
*
* @param pool
* @param sysBroker
*/
public void attach(BrokerPool pool, DBBroker sysBroker) {
this.pool = pool;
TransactionManager transact = pool.getTransactionManager();
Txn txn = null;
DBBroker broker = sysBroker;
try {
Collection sysCollection = broker.getCollection(XmldbURI.SYSTEM_COLLECTION_URI);
if (sysCollection == null) {
txn = transact.beginTransaction();
sysCollection = broker.getOrCreateCollection(txn, XmldbURI.SYSTEM_COLLECTION_URI);
if (sysCollection == null)
return;
sysCollection.setPermissions(0770);
broker.saveCollection(txn, sysCollection);
transact.commit(txn);
}
Document acl = sysCollection.getDocument(broker, ACL_FILE_URI);
Element docElement = null;
if (acl != null)
docElement = acl.getDocumentElement();
if (docElement == null) {
LOG.debug("creating system users");
User user = new User(DBA_USER, null);
user.addGroup(DBA_GROUP);
user.setUID(++nextUserId);
users.put(user.getUID(), user);
user = new User(GUEST_USER, GUEST_USER, GUEST_GROUP);
user.setUID(++nextUserId);
users.put(user.getUID(), user);
newGroup(DBA_GROUP);
newGroup(GUEST_GROUP);
txn = transact.beginTransaction();
save(broker, txn);
transact.commit(txn);
} else {
LOG.debug("loading acl");
Element root = acl.getDocumentElement();
Attr version = root.getAttributeNode("version");
int major = 0;
int minor = 0;
if (version!=null) {
String [] numbers = version.getValue().split("\\.");
major = Integer.parseInt(numbers[0]);
minor = Integer.parseInt(numbers[1]);
}
NodeList nl = root.getChildNodes();
Node node;
Element next;
User user;
NodeList ul;
String lastId;
Group group;
for (int i = 0; i < nl.getLength(); i++) {
if(nl.item(i).getNodeType() != Node.ELEMENT_NODE)
continue;
next = (Element) nl.item(i);
if (next.getTagName().equals("users")) {
lastId = next.getAttribute("last-id");
try {
nextUserId = Integer.parseInt(lastId);
} catch (NumberFormatException e) {
}
ul = next.getChildNodes();
for (int j = 0; j < ul.getLength(); j++) {
node = ul.item(j);
if(node.getNodeType() == Node.ELEMENT_NODE &&
node.getLocalName().equals("user")) {
user = new User(major,minor,(Element)node);
users.put(user.getUID(), user);
}
}
} else if (next.getTagName().equals("groups")) {
lastId = next.getAttribute("last-id");
try {
nextGroupId = Integer.parseInt(lastId);
} catch (NumberFormatException e) {
}
ul = next.getChildNodes();
for (int j = 0; j < ul.getLength(); j++) {
node = ul.item(j);
if(node.getNodeType() == Node.ELEMENT_NODE &&
node.getLocalName().equals("group")) {
group = new Group((Element)node);
groups.put(group.getId(), group);
}
}
}
}
}
} catch (Exception e) {
transact.abort(txn);
e.printStackTrace();
LOG.debug("loading acl failed: " + e.getMessage());
}
// read default collection and resource permissions
Integer defOpt = (Integer)
broker.getConfiguration().getProperty(PROPERTY_PERMISSIONS_COLLECTIONS);
if (defOpt != null)
defCollectionPermissions = defOpt.intValue();
defOpt = (Integer)
broker.getConfiguration().getProperty(PROPERTY_PERMISSIONS_RESOURCES);
if (defOpt != null)
defResourcePermissions = defOpt.intValue();
Boolean enableXACML = (Boolean)broker.getConfiguration().getProperty("xacml.enable");
if(enableXACML != null && enableXACML.booleanValue()) {
pdp = new ExistPDP(pool);
LOG.debug("XACML enabled");
}
}
public boolean isXACMLEnabled() {
return pdp != null;
}
public ExistPDP getPDP() {
return pdp;
}
public synchronized void deleteUser(String name) throws PermissionDeniedException {
deleteUser(getUser(name));
}
public synchronized void deleteUser(User user) throws PermissionDeniedException {
if(user == null)
return;
user = (User)users.remove(user.getUID());
if(user != null)
LOG.debug("user " + user.getName() + " removed");
else
LOG.debug("user not found");
DBBroker broker = null;
TransactionManager transact = pool.getTransactionManager();
Txn txn = transact.beginTransaction();
try {
broker = pool.get(SYSTEM_USER);
save(broker, txn);
transact.commit(txn);
} catch (EXistException e) {
transact.abort(txn);
e.printStackTrace();
} finally {
pool.release(broker);
}
}
public synchronized User getUser(String name) {
User user;
for (Iterator i = users.valueIterator(); i.hasNext();) {
user = (User) i.next();
if (user.getName().equals(name))
return user;
}
LOG.debug("user " + name + " not found");
return null;
}
public synchronized User getUser(int uid) {
final User user = (User)users.get(uid);
if(user == null) {
// LOG.debug("user with uid " + uid + " not found");
}
return user;
}
public synchronized User[] getUsers() {
User u[] = new User[users.size()];
int j = 0;
for (Iterator i = users.valueIterator(); i.hasNext(); j++)
u[j] = (User) i.next();
return u;
}
protected void newGroup(String name) {
Group group = new Group(name, ++nextGroupId);
groups.put(group.getId(), group);
}
public synchronized void addGroup(String name) {
newGroup(name);
DBBroker broker = null;
TransactionManager transact = pool.getTransactionManager();
Txn txn = transact.beginTransaction();
try {
broker = pool.get(SYSTEM_USER);
save(broker, txn);
transact.commit(txn);
} catch (EXistException e) {
transact.abort(txn);
e.printStackTrace();
} finally {
pool.release(broker);
}
}
public synchronized boolean hasGroup(String name) {
Group group;
for (Iterator i = groups.valueIterator(); i.hasNext();) {
group = (Group) i.next();
if (group.getName().equals(name))
return true;
}
return false;
}
public synchronized Group getGroup(String name) {
Group group;
for (Iterator i = groups.valueIterator(); i.hasNext();) {
group = (Group) i.next();
if (group.getName().equals(name))
return group;
}
return null;
}
public synchronized Group getGroup(int gid) {
return (Group)groups.get(gid);
}
public synchronized String[] getGroups() {
ArrayList list = new ArrayList(groups.size());
Group group;
for(Iterator i = groups.valueIterator(); i.hasNext(); ) {
group = (Group) i.next();
list.add(group.getName());
}
String[] gl = new String[list.size()];
list.toArray(gl);
return gl;
}
public synchronized boolean hasAdminPrivileges(User user) {
return user.hasDbaRole();
}
public synchronized boolean hasUser(String name) {
User user;
for (Iterator i = users.valueIterator(); i.hasNext();) {
user = (User) i.next();
if (user.getName().equals(name))
return true;
}
return false;
}
private synchronized void save(DBBroker broker, Txn transaction) throws EXistException {
LOG.debug("storing acl file");
StringBuffer buf = new StringBuffer();
buf.append("<!-- Central user configuration. Editing this document will cause the security " +
"to reload and update its internal database. Please handle with care! -->");
buf.append("<auth version='1.0'>");
// save groups
buf.append("<!-- Please do not remove the guest and admin groups -->");
buf.append("<groups last-id=\"");
buf.append(Integer.toString(nextGroupId));
buf.append("\">");
for (Iterator i = groups.valueIterator(); i.hasNext();)
buf.append(((Group) i.next()).toString());
buf.append("</groups>");
//save users
buf.append("<!-- Please do not remove the admin user. -->");
buf.append("<users last-id=\"");
buf.append(Integer.toString(nextUserId));
buf.append("\">");
for (Iterator i = users.valueIterator(); i.hasNext();)
buf.append(((User) i.next()).toString());
buf.append("</users>");
buf.append("</auth>");
// store users.xml
broker.flush();
broker.sync(Sync.MAJOR_SYNC);
try {
broker.setUser(getUser(DBA_USER));
Collection sysCollection = broker.getCollection(XmldbURI.SYSTEM_COLLECTION_URI);
String data = buf.toString();
IndexInfo info = sysCollection.validateXMLResource(transaction, broker, ACL_FILE_URI, data);
//TODO : unlock the collection here ?
DocumentImpl doc = info.getDocument();
doc.getMetadata().setMimeType(MimeType.XML_TYPE.getName());
sysCollection.store(transaction, broker, info, data, false);
doc.setPermissions(0770);
broker.saveCollection(transaction, doc.getCollection());
} catch (IOException e) {
throw new EXistException(e.getMessage());
} catch (TriggerException e) {
throw new EXistException(e.getMessage());
} catch (SAXException e) {
throw new EXistException(e.getMessage());
} catch (PermissionDeniedException e) {
throw new EXistException(e.getMessage());
} catch (LockException e) {
throw new EXistException(e.getMessage());
}
broker.flush();
broker.sync(Sync.MAJOR_SYNC);
}
public synchronized void setUser(User user) {
if (user.getUID() < 0)
user.setUID(++nextUserId);
users.put(user.getUID(), user);
String[] groups = user.getGroups();
// if no group is specified, we automatically fall back to the guest group
if (groups.length == 0)
user.addGroup(GUEST_GROUP);
for (int i = 0; i < groups.length; i++) {
if (!hasGroup(groups[i]))
newGroup(groups[i]);
}
TransactionManager transact = pool.getTransactionManager();
Txn txn = transact.beginTransaction();
DBBroker broker = null;
try {
broker = pool.get(SYSTEM_USER);
save(broker, txn);
createUserHome(broker, txn, user);
transact.commit(txn);
} catch (EXistException e) {
transact.abort(txn);
LOG.debug("error while creating user", e);
} catch (IOException e) {
transact.abort(txn);
LOG.debug("error while creating home collection", e);
} catch (PermissionDeniedException e) {
transact.abort(txn);
LOG.debug("error while creating home collection", e);
} finally {
pool.release(broker);
}
}
public int getResourceDefaultPerms() {
return defResourcePermissions;
}
public int getCollectionDefaultPerms() {
return defCollectionPermissions;
}
private void createUserHome(DBBroker broker, Txn transaction, User user)
throws EXistException, PermissionDeniedException, IOException {
if(user.getHome() == null)
return;
broker.setUser(getUser(DBA_USER));
Collection home = broker.getOrCreateCollection(transaction, user.getHome());
home.getPermissions().setOwner(user.getName());
CollectionConfiguration config = home.getConfiguration(broker);
String group = (config!=null) ? config.getDefCollGroup(user) : user.getPrimaryGroup();
home.getPermissions().setGroup(group);
broker.saveCollection(transaction, home);
}
}