/*
* eXist Open Source Native XML Database
* Copyright (C) 2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.exist.security.internal;
import org.exist.security.AbstractRealm;
import org.exist.security.AbstractAccount;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.config.Configuration;
import org.exist.config.ConfigurationException;
import org.exist.config.Configurator;
import org.exist.config.annotation.ConfigurationClass;
import org.exist.config.annotation.ConfigurationFieldAsElement;
import org.exist.security.Group;
import org.exist.security.PermissionDeniedException;
import org.exist.security.SchemaType;
import org.exist.security.SecurityManager;
import org.exist.security.Account;
import org.exist.security.internal.aider.UserAider;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Properties;
import org.exist.security.Credential;
import org.exist.storage.DBBroker;
/**
* Represents a user within the database.
*
* @author Wolfgang Meier <wolfgang@exist-db.org>
* @author {Marco.Tampucci, Massimo.Martinelli} @isti.cnr.it
* @author Adam retter <adam@exist-db.org>
*/
@ConfigurationClass("account")
public class AccountImpl extends AbstractAccount {
private final static Logger LOG = LogManager.getLogger(AccountImpl.class);
public static boolean CHECK_PASSWORDS = true;
private final static SecurityProperties securityProperties = new SecurityProperties();
public static SecurityProperties getSecurityProperties() {
return securityProperties;
}
/*
static {
Properties props = new Properties();
try {
props.load(AccountImpl.class.getClassLoader().getResourceAsStream(
"org/exist/security/security.properties"));
} catch(IOException e) {
}
String option = props.getProperty("passwords.encoding", "md5");
setPasswordEncoding(option);
option = props.getProperty("passwords.check", "yes");
CHECK_PASSWORDS = option.equalsIgnoreCase("yes")
|| option.equalsIgnoreCase("true");
}
static public void enablePasswordChecks(boolean check) {
CHECK_PASSWORDS = check;
}
static public void setPasswordEncoding(String encoding) {
if(encoding != null) {
LOG.equals("Setting password encoding to " + encoding);
if(encoding.equalsIgnoreCase("plain")) {
PASSWORD_ENCODING = PLAIN_ENCODING;
} else if(encoding.equalsIgnoreCase("md5")) {
PASSWORD_ENCODING = MD5_ENCODING;
} else {
PASSWORD_ENCODING = SIMPLE_MD5_ENCODING;
}
}
}*/
@ConfigurationFieldAsElement("password")
private String password = null;
@ConfigurationFieldAsElement("digestPassword")
private String digestPassword = null;
/**
* Create a new user with name and password
*
* @param realm
* @param id
* @param name
* @param password
* @throws ConfigurationException
*/
public AccountImpl(final DBBroker broker, final AbstractRealm realm, final int id, final String name,final String password) throws ConfigurationException {
super(broker, realm, id, name);
setPassword(password);
}
public AccountImpl(final DBBroker broker, final AbstractRealm realm, final int id, final String name, final String password, final Group group, final boolean hasDbaRole) throws ConfigurationException {
super(broker, realm, id, name);
setPassword(password);
this.groups.add(group);
this.hasDbaRole = hasDbaRole;
}
public AccountImpl(final DBBroker broker, final AbstractRealm realm, final int id, final String name, final String password, final Group group) throws ConfigurationException {
super(broker, realm, id, name);
setPassword(password);
this.groups.add(group);
}
/**
* Create a new user with name
*
* @param realm
* @param name
* The account name
* @throws ConfigurationException
*/
public AccountImpl(final DBBroker broker, final AbstractRealm realm, final String name) throws ConfigurationException {
super(broker, realm, Account.UNDEFINED_ID, name);
}
// /**
// * Create a new user with name, password and primary group
// *
// * @param name
// * @param password
// * @param primaryGroup
// * @throws ConfigurationException
// * @throws PermissionDeniedException
// */
// public AccountImpl(AbstractRealm realm, int id, String name, String password, String primaryGroup) throws ConfigurationException {
// this(realm, id, name, password);
// addGroup(primaryGroup);
// }
public AccountImpl(final DBBroker broker, final AbstractRealm realm, final int id, final Account from_user) throws ConfigurationException, PermissionDeniedException {
super(broker, realm, id, from_user.getName());
instantiate(from_user);
}
private void instantiate(final Account from_user) throws PermissionDeniedException {
//copy metadata
for(final SchemaType metadataKey : from_user.getMetadataKeys()) {
final String metadataValue = from_user.getMetadataValue(metadataKey);
setMetadataValue(metadataKey, metadataValue);
}
//copy umask
setUserMask(from_user.getUserMask());
if(from_user instanceof AccountImpl) {
final AccountImpl user = (AccountImpl) from_user;
groups = new ArrayList<>(user.groups);
password = user.password;
digestPassword = user.digestPassword;
hasDbaRole = user.hasDbaRole;
_cred = user._cred;
} else if(from_user instanceof UserAider) {
final UserAider user = (UserAider) from_user;
final String[] groups = user.getGroups();
for (final String group : groups) {
addGroup(group);
}
setPassword(user.getPassword());
digestPassword = user.getDigestPassword();
} else {
addGroup(from_user.getDefaultGroup());
//TODO: groups
}
}
public AccountImpl(final DBBroker broker, final AbstractRealm realm, final AccountImpl from_user) throws ConfigurationException {
super(broker, realm, from_user.id, from_user.name);
//copy metadata
for(final SchemaType metadataKey : from_user.getMetadataKeys()) {
final String metadataValue = from_user.getMetadataValue(metadataKey);
setMetadataValue(metadataKey, metadataValue);
}
groups = from_user.groups;
password = from_user.password;
digestPassword = from_user.digestPassword;
hasDbaRole = from_user.hasDbaRole;
_cred = from_user._cred;
//this.realm = realm; //set via super()
}
public AccountImpl(final AbstractRealm realm, final Configuration configuration) throws ConfigurationException {
super(realm, configuration);
//this is required because the classes fields are initialized after the super constructor
if(this.configuration != null) {
this.configuration = Configurator.configure(this, this.configuration);
}
this.hasDbaRole = this.hasGroup(SecurityManager.DBA_GROUP);
}
public AccountImpl(final AbstractRealm realm, final Configuration configuration, final boolean removed) throws ConfigurationException {
this(realm, configuration);
this.removed = removed;
}
/**
* Get the user's password
*
* @return Description of the Return Value
* @deprecated
*/
@Override
public final String getPassword() {
return password;
}
@Override
public final String getDigestPassword() {
return digestPassword;
}
@Override
public final void setPassword(final String passwd) {
_cred = new Password(this, passwd);
if(passwd == null) {
this.password = null;
this.digestPassword = null;
} else {
this.password = _cred.toString();
this.digestPassword = _cred.getDigest();
}
}
@Override
public void setCredential(final Credential credential) {
this._cred = credential;
this.password = _cred.toString();
this.digestPassword = _cred.getDigest();
}
public final static class SecurityProperties {
private final static boolean DEFAULT_CHECK_PASSWORDS = true;
private final static String PROP_CHECK_PASSWORDS = "passwords.check";
private Properties loadedSecurityProperties = null;
private Boolean checkPasswords = null;
public synchronized boolean isCheckPasswords() {
if(checkPasswords == null) {
final String property = getProperty(PROP_CHECK_PASSWORDS);
if(property == null || property.length() == 0) {
checkPasswords = DEFAULT_CHECK_PASSWORDS;
} else {
checkPasswords = property.equalsIgnoreCase("yes") || property.equalsIgnoreCase("true");
}
}
return checkPasswords;
}
public synchronized void enableCheckPasswords(final boolean enable) {
this.checkPasswords = enable;
}
private synchronized String getProperty(final String propertyName) {
if(loadedSecurityProperties == null) {
loadedSecurityProperties = new Properties();
try(final InputStream is = AccountImpl.class.getResourceAsStream("security.properties")) {
if(is != null) {
loadedSecurityProperties.load(is);
}
} catch(final IOException ioe) {
LOG.error("Unable to load security.properties, using defaults. " + ioe.getMessage(), ioe);
}
}
return loadedSecurityProperties.getProperty(propertyName);
}
}
//this method is used by Configurator
public final Group insertGroup(final int index, final String name) throws PermissionDeniedException {
//if we cant find the group in our own realm, try other realms
final Group group = Optional.ofNullable(getRealm().getGroup(name))
.orElse(getRealm().getSecurityManager().getGroup(name));
return insertGroup(index, group);
}
private Group insertGroup(final int index, final Group group) throws PermissionDeniedException {
if(group == null){
return null;
}
final Account user = getDatabase().getActiveBroker().getCurrentSubject();
group.assertCanModifyGroup(user);
if(!groups.contains(group)) {
groups.add(index, group);
if(SecurityManager.DBA_GROUP.equals(group.getName())) {
hasDbaRole = true;
}
}
return group;
}
}