/*
* eXist Open Source Native XML Database
* Copyright (C) 2010-2011 The eXist Project
* http://exist-db.org
*
* 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.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.config.Configuration;
import org.exist.config.ConfigurationException;
import org.exist.config.Reference;
import org.exist.config.ReferenceImpl;
import org.exist.security.AXSchemaType;
import org.exist.security.AbstractAccount;
import org.exist.security.AbstractPrincipal;
import org.exist.security.AbstractRealm;
import org.exist.security.Account;
import org.exist.security.AuthenticationException;
import org.exist.security.EXistSchemaType;
import org.exist.security.Group;
import org.exist.security.PermissionDeniedException;
import org.exist.security.SecurityManager;
import org.exist.security.Subject;
import org.exist.security.UUIDGenerator;
import org.exist.security.internal.aider.UserAider;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.xmldb.XmldbURI;
/**
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
*
*/
public class RealmImpl extends AbstractRealm {
public static String ID = "exist"; //TODO: final "eXist-db";
private final static Logger LOG = Logger.getLogger(RealmImpl.class);
static public void setPasswordRealm(String value) {
ID = value;
}
public final static int SYSTEM_ACCOUNT_ID = 1048575;
public final static int ADMIN_ACCOUNT_ID = 1048574;
public final static int GUEST_ACCOUNT_ID = 1048573;
public final static int UNKNOWN_ACCOUNT_ID = 1048572;
public final static int INITIAL_LAST_ACCOUNT_ID = 10;
public final static int DBA_GROUP_ID = 1048575;
public final static int GUEST_GROUP_ID = 1048574;
public final static int UNKNOWN_GROUP_ID = 1048573;
public final static int INITIAL_LAST_GROUP_ID = 10;
protected final AccountImpl ACCOUNT_SYSTEM;
protected final AccountImpl ACCOUNT_UNKNOWN;
protected final GroupImpl GROUP_DBA;
protected final GroupImpl GROUP_GUEST;
protected final GroupImpl GROUP_UNKNOWN;
private final static String DEFAULT_ADMIN_PASSWORD = "";
private final static String DEFAULT_GUEST_PASSWORD = "guest";
//@ConfigurationFieldAsElement("allow-guest-authentication")
public boolean allowGuestAuthentication = true;
protected RealmImpl(final SecurityManagerImpl sm, final Configuration config) throws ConfigurationException { //, Configuration conf
super(sm, config);
sm.lastUserId = INITIAL_LAST_ACCOUNT_ID; //TODO this is horrible!
sm.lastGroupId = INITIAL_LAST_GROUP_ID; //TODO this is horrible!
//DBA group
GROUP_DBA = new GroupImpl(this, DBA_GROUP_ID, SecurityManager.DBA_GROUP);
GROUP_DBA.setManagers(new ArrayList<Reference<SecurityManager, Account>>(){
{ add(new ReferenceImpl<SecurityManager, Account>(sm, "getAccount", SecurityManager.DBA_USER)); }
});
GROUP_DBA.setMetadataValue(EXistSchemaType.DESCRIPTION, "Database Administrators");
sm.addGroup(GROUP_DBA.getId(), GROUP_DBA);
registerGroup(GROUP_DBA);
//sm.groupsById.put(GROUP_DBA.getId(), GROUP_DBA);
//groupsByName.put(GROUP_DBA.getName(), GROUP_DBA);
//System account
ACCOUNT_SYSTEM = new AccountImpl(this, SYSTEM_ACCOUNT_ID, SecurityManager.SYSTEM, "", GROUP_DBA, true);
ACCOUNT_SYSTEM.setMetadataValue(AXSchemaType.FULLNAME, SecurityManager.SYSTEM);
ACCOUNT_SYSTEM.setMetadataValue(EXistSchemaType.DESCRIPTION, "System Internals");
sm.addUser(ACCOUNT_SYSTEM.getId(), ACCOUNT_SYSTEM);
registerAccount(ACCOUNT_SYSTEM);
//sm.usersById.put(ACCOUNT_SYSTEM.getId(), ACCOUNT_SYSTEM);
//usersByName.put(ACCOUNT_SYSTEM.getName(), ACCOUNT_SYSTEM);
//guest group
GROUP_GUEST = new GroupImpl(this, GUEST_GROUP_ID, SecurityManager.GUEST_GROUP);
GROUP_GUEST.setManagers(new ArrayList<Reference<SecurityManager, Account>>(){
{ add(new ReferenceImpl<SecurityManager, Account>(sm, "getAccount", SecurityManager.DBA_USER)); }
});
GROUP_GUEST.setMetadataValue(EXistSchemaType.DESCRIPTION, "Anonymous Users");
sm.addGroup(GROUP_GUEST.getId(), GROUP_GUEST);
registerGroup(GROUP_GUEST);
//sm.groupsById.put(GROUP_GUEST.getId(), GROUP_GUEST);
//groupsByName.put(GROUP_GUEST.getName(), GROUP_GUEST);
//unknown account and group
GROUP_UNKNOWN = new GroupImpl(this, UNKNOWN_GROUP_ID, "");
ACCOUNT_UNKNOWN = new AccountImpl(this, UNKNOWN_ACCOUNT_ID, "", (String)null, GROUP_UNKNOWN);
//XXX: GROUP_DBA._addManager(ACCOUNT_ADMIN);
//XXX: GROUP_GUEST._addManager(ACCOUNT_ADMIN);
}
@Override
public void start(final DBBroker broker) throws EXistException {
super.start(broker);
try {
createAdminAndGuestIfNotExist(broker);
} catch(final PermissionDeniedException pde) {
final boolean exportOnly = (Boolean) broker.getConfiguration().getProperty(BrokerPool.PROPERTY_EXPORT_ONLY, false);
if(!exportOnly) {
throw new EXistException(pde.getMessage(), pde);
}
}
}
private void createAdminAndGuestIfNotExist(final DBBroker broker) throws EXistException, PermissionDeniedException {
//Admin account
if(getSecurityManager().getAccount(ADMIN_ACCOUNT_ID) == null) {
//AccountImpl actAdmin = new AccountImpl(broker, this, ADMIN_ACCOUNT_ID, SecurityManager.DBA_USER, "", GROUP_DBA, true);
final UserAider actAdmin = new UserAider(ADMIN_ACCOUNT_ID, getId(), SecurityManager.DBA_USER);
actAdmin.setPassword(DEFAULT_ADMIN_PASSWORD);
actAdmin.setMetadataValue(AXSchemaType.FULLNAME, SecurityManager.DBA_USER);
actAdmin.setMetadataValue(EXistSchemaType.DESCRIPTION, "System Administrator");
actAdmin.addGroup(SecurityManager.DBA_GROUP);
getSecurityManager().addAccount(broker, actAdmin);
}
//Guest account
if(getSecurityManager().getAccount(GUEST_ACCOUNT_ID) == null) {
//AccountImpl actGuest = new AccountImpl(broker, this, GUEST_ACCOUNT_ID, SecurityManager.GUEST_USER, SecurityManager.GUEST_USER, GROUP_GUEST, false);
final UserAider actGuest = new UserAider(GUEST_ACCOUNT_ID, getId(), SecurityManager.GUEST_USER);
actGuest.setMetadataValue(AXSchemaType.FULLNAME, SecurityManager.GUEST_USER);
actGuest.setMetadataValue(EXistSchemaType.DESCRIPTION, "Anonymous User");
actGuest.setPassword(DEFAULT_GUEST_PASSWORD);
actGuest.addGroup(SecurityManager.GUEST_GROUP);
getSecurityManager().addAccount(broker, actGuest);
}
}
@Override
public String getId() {
return ID;
}
@Override
public boolean deleteAccount(final Account account) throws PermissionDeniedException, EXistException {
if(account == null) {
return false;
}
usersByName.modify2E(new PrincipalDbModify2E<Account, PermissionDeniedException, EXistException>(){
@Override
public void execute(Map<String, Account> principalDb) throws PermissionDeniedException, EXistException {
final AbstractAccount remove_account = (AbstractAccount)principalDb.get(account.getName());
if(remove_account == null){
throw new IllegalArgumentException("No such account exists!");
}
DBBroker broker = null;
try {
broker = getDatabase().get(null);
final Account user = broker.getSubject();
if(!(account.getName().equals(user.getName()) || user.hasDbaRole()) ) {
throw new PermissionDeniedException("You are not allowed to delete '" +account.getName() + "' user");
}
remove_account.setRemoved(true);
remove_account.setCollection(broker, collectionRemovedAccounts, XmldbURI.create(UUIDGenerator.getUUID()+".xml"));
final TransactionManager transaction = getDatabase().getTransactionManager();
Txn txn = null;
try {
txn = transaction.beginTransaction();
collectionAccounts.removeXMLResource(txn, broker, XmldbURI.create( remove_account.getName() + ".xml"));
transaction.commit(txn);
} catch(final Exception e) {
transaction.abort(txn);
e.printStackTrace();
LOG.debug("loading configuration failed: " + e.getMessage());
} finally {
transaction.close(txn);
}
getSecurityManager().addUser(remove_account.getId(), remove_account);
principalDb.remove(remove_account.getName());
} finally {
getDatabase().release(broker);
}
}
});
return true;
}
@Override
public boolean deleteGroup(final Group group) throws PermissionDeniedException, EXistException {
if(group == null)
{return false;}
groupsByName.modify2E(new PrincipalDbModify2E<Group, PermissionDeniedException, EXistException>(){
@Override
public void execute(Map<String, Group> principalDb) throws PermissionDeniedException, EXistException {
final AbstractPrincipal remove_group = (AbstractPrincipal)principalDb.get(group.getName());
if(remove_group == null)
{throw new IllegalArgumentException("Group does '"+group.getName()+"' not exist!");}
final DBBroker broker = getDatabase().getActiveBroker();
final Subject subject = broker.getSubject();
((Group)remove_group).assertCanModifyGroup(subject);
remove_group.setRemoved(true);
remove_group.setCollection(broker, collectionRemovedGroups, XmldbURI.create(UUIDGenerator.getUUID() + ".xml"));
final TransactionManager transaction = getDatabase().getTransactionManager();
Txn txn = null;
try {
txn = transaction.beginTransaction();
collectionGroups.removeXMLResource(txn, broker, XmldbURI.create(remove_group.getName() + ".xml" ));
transaction.commit(txn);
} catch (final Exception e) {
transaction.abort(txn);
LOG.debug(e);
} finally {
transaction.close(txn);
}
getSecurityManager().addGroup(remove_group.getId(), (Group)remove_group);
principalDb.remove(remove_group.getName());
}
});
return true;
}
@Override
public Subject authenticate(final String accountName, Object credentials) throws AuthenticationException {
final Account account = getAccount(accountName);
if(account == null) {
throw new AuthenticationException(AuthenticationException.ACCOUNT_NOT_FOUND, "Account '" + accountName + "' not found.");
}
if("SYSTEM".equals(accountName) || (!allowGuestAuthentication && "guest".equals(accountName))) {
throw new AuthenticationException(AuthenticationException.ACCOUNT_NOT_FOUND, "Account '" + accountName + "' can not be used.");
}
if(!account.isEnabled()) {
throw new AuthenticationException(AuthenticationException.ACCOUNT_LOCKED, "Account '" + accountName + "' is disabled.");
}
final Subject subject = new SubjectImpl((AccountImpl) account, credentials);
if(!subject.isAuthenticated()) {
throw new AuthenticationException(AuthenticationException.WRONG_PASSWORD, "Wrong password for user [" + accountName + "] ");
}
return subject;
}
@Override
public List<String> findUsernamesWhereUsernameStarts(final String startsWith) {
return usersByName.read(new PrincipalDbRead<Account, List<String>>(){
@Override
public List<String> execute(Map<String, Account> principalDb) {
final List<String> userNames = new ArrayList<String>();
for(final String userName : principalDb.keySet()) {
if(userName.startsWith(startsWith)) {
userNames.add(userName);
}
}
return userNames;
}
});
}
@Override
public List<String> findGroupnamesWhereGroupnameStarts(final String startsWith) {
return groupsByName.read(new PrincipalDbRead<Group, List<String>>(){
@Override
public List<String> execute(Map<String, Group> principalDb) {
final List<String> groupNames = new ArrayList<String>();
for(final String groupName : principalDb.keySet()) {
if(groupName.startsWith(startsWith)) {
groupNames.add(groupName);
}
}
return groupNames;
}
});
}
@Override
public Collection<? extends String> findGroupnamesWhereGroupnameContains(final String fragment) {
return groupsByName.read(new PrincipalDbRead<Group, List<String>>(){
@Override
public List<String> execute(Map<String, Group> principalDb) {
final List<String> groupNames = new ArrayList<String>();
for(final String groupName : principalDb.keySet()) {
if(groupName.indexOf(fragment) > -1) {
groupNames.add(groupName);
}
}
return groupNames;
}
});
}
@Override
public List<String> findAllGroupNames() {
return groupsByName.read(new PrincipalDbRead<Group, List<String>>(){
@Override
public List<String> execute(Map<String, Group> principalDb) {
return new ArrayList<String>(principalDb.keySet());
}
});
}
@Override
public List<String> findAllUserNames() {
return usersByName.read(new PrincipalDbRead<Account, List<String>>(){
@Override
public List<String> execute(Map<String, Account> principalDb) {
return new ArrayList<String>(principalDb.keySet());
}
});
}
@Override
public List<String> findAllGroupMembers(final String groupName) {
return usersByName.read(new PrincipalDbRead<Account, List<String>>(){
@Override
public List<String> execute(Map<String, Account> principalDb) {
final List<String> groupMembers = new ArrayList<String>();
for(final Account account : principalDb.values()) {
if(account.hasGroup(groupName)) {
groupMembers.add(account.getName());
}
}
return groupMembers;
}
});
}
@Override
public List<String> findUsernamesWhereNameStarts(String startsWith) {
return new ArrayList<String>(); //TODO at present exist users cannot have personal name details
}
@Override
public List<String> findUsernamesWhereNamePartStarts(String startsWith) {
return new ArrayList<String>(); //TODO at present exist users cannot have personal name details
}
}