/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* 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 ch.entwine.weblounge.common.impl.security;
import ch.entwine.weblounge.common.impl.language.LanguageUtils;
import ch.entwine.weblounge.common.impl.util.WebloungeDateFormat;
import ch.entwine.weblounge.common.impl.util.config.ConfigurationUtils;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.security.DigestType;
import ch.entwine.weblounge.common.security.Password;
import ch.entwine.weblounge.common.security.Role;
import ch.entwine.weblounge.common.security.WebloungeUser;
import ch.entwine.weblounge.common.site.Site;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
/**
* Default implementation of a weblounge user.
*/
public class WebloungeUserImpl extends UserImpl implements WebloungeUser {
/** Logging facility */
private static final Logger logger = LoggerFactory.getLogger(WebloungeUserImpl.class);
/** Enabled flag */
protected boolean enabled = true;
/** First name of the person */
protected String firstName = null;
/** Family name of the person */
protected String lastName = null;
/** E-mail address */
protected String email = null;
/** Preferred language */
protected Language language = null;
/** Cached initials */
private String initials = null;
/** Password challenge */
protected String challenge = null;
/** Password challenge response */
protected byte[] response = null;
/** Password hash type, either plain or md5 */
protected DigestType responseDigestType = DigestType.plain;
/** Additional properties of this user */
protected Map<String, Object> properties = new HashMap<String, Object>();
/** Date of the last login */
protected Date lastLogin = null;
/** Source of the last login */
protected String lastLoginSource = null;
/** Cached version of automatically generated initials */
private String cachedInitials = null;
/** Cached version of automatically generated name */
private String cachedName = null;
/**
* Creates a user with the given login and initializes it from the weblounge
* database.
*
* @param login
* the username
* @param realm
* the login domain
*/
public WebloungeUserImpl(String login, String realm) {
super(login, realm);
}
/**
* Creates a user with login <code>login</code> and the default realm
* {@link #DefaultRealm}.
*
* @param login
* the username
*/
public WebloungeUserImpl(String login) {
this(login, DefaultRealm);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#isEnabled()
*/
public boolean isEnabled() {
return enabled;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#canLogin()
*/
public boolean canLogin() {
Set<Object> passwords = getPrivateCredentials(Password.class);
return isEnabled() && passwords.size() > 0;
}
/**
* Sets the enabled flag. Set it to <code>true</code> to enable the login.
*
* @param enabled
* <code>true</code> to enable this login
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Sets this person's first name.
*
* @param firstname
* the first name
*/
public void setFirstName(String firstname) {
this.firstName = firstname;
cachedInitials = null;
cachedName = null;
}
/**
* Returns the first name of this person.
*
* @return the person's first name
*/
public String getFirstName() {
return firstName;
}
/**
* Sets this person's last name.
*
* @param lastname
* the last name
*/
public void setLastName(String lastname) {
this.lastName = lastname;
cachedInitials = null;
cachedName = null;
}
/**
* Returns the last name of this person.
*
* @return the person's last name
*/
public String getLastName() {
return lastName;
}
/**
* Returns the name of this user. If possible, the value returned consists of
* type <first name><last name>.
*
* @returns the full user name
*/
public String getName() {
if (name != null)
return name;
if (cachedName != null)
return cachedName;
String first = getFirstName();
String last = getLastName();
if (StringUtils.trimToNull(first) == null && StringUtils.trimToNull(last) == null)
return null;
// Create the name
StringBuffer name = new StringBuffer();
if (StringUtils.trimToNull(first) != null) {
name.append(first);
}
if (StringUtils.trimToNull(last) != null) {
if (name.length() > 0)
name.append(" ");
name.append(last);
}
// Cache for further reference
cachedName = name.toString();
return cachedName;
}
/**
* Sets the person's email.
*
* @param email
* the email address
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Returns the email address of this person.
*
* @return the person's email address
*/
public String getEmail() {
return email;
}
/**
* Sets the person's preferred language.
*
* @param language
* the preferred language
*/
public void setLanguage(Language language) {
this.language = language;
}
/**
* Returns the preferred language of this person.
*
* @return the person's preferred language
*/
public Language getLanguage() {
return language;
}
/**
* Returns the short version of the persons name, which are constructed from
* the first and the last name of the user.
*
* @return the persons initials
*/
public String getInitials() {
if (initials != null)
return initials;
if (cachedInitials != null)
return cachedInitials;
StringBuffer initials = new StringBuffer();
String first = getFirstName();
String last = getLastName();
if (!StringUtils.isBlank(first) && !StringUtils.isBlank(last)) {
initials.append(first.substring(0, 1));
initials.append(last.subSequence(0, 1));
cachedInitials = initials.toString().toLowerCase();
return cachedInitials;
}
return null;
}
/**
* Sets the person's initials.
*
* @param initials
* the person's initials
*/
public void setInitials(String initials) {
this.initials = initials;
}
/**
* Sets the last login date.
*
* @param date
* the login date
* @param src
* the login source
*/
public void setLastLogin(Date date, String src) {
lastLogin = date;
lastLoginSource = src;
}
/**
* Returns the data where the user logged in for the last time.
*
* @return the last login
*/
public Date getLastLogin() {
return lastLogin;
}
/**
* Returns the last login source. The source can be either an ip address or a
* host name.
*
* @return the source of the last login
*/
public String getLastLoginFrom() {
return lastLoginSource;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#getChallenge()
*/
@Override
public String getChallenge() {
return challenge;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#setChallenge(java.lang.String)
*/
@Override
public void setChallenge(String challenge) {
this.challenge = challenge;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#getResponse()
*/
@Override
public byte[] getResponse() {
return response;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#setResponse(java.lang.String)
*/
@Override
public void setResponse(byte[] response, DigestType digest) {
this.response = response;
this.responseDigestType = digest;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#getProperty(java.lang.String)
*/
public Object getProperty(String name) {
return properties.get(name);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#removeProperty(java.lang.String)
*/
public Object removeProperty(String name) {
return properties.remove(name);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.security.WebloungeUser#setProperty(java.lang.String,
* java.lang.Object)
*/
public void setProperty(String name, Object value) {
properties.put(name, value);
}
/**
* Initializes this user object by reading all information from the
* <code>XML</code> configuration node.
*
* @param userNode
* the <code>XML</code> node containing the user configuration
* @param site
* the associated site
*/
public static WebloungeUserImpl fromXml(Node userNode, Site site)
throws IllegalStateException {
XPath xpath = XPathFactory.newInstance().newXPath();
return fromXml(userNode, site, xpath);
}
/**
* Initializes this user object by reading all information from the
* <code>XML</code> configuration node.
*
* @param userNode
* the <code>XML</code> node containing the user configuration
* @param site
* the associated site
* @param xpath
* the {@link XPath} processor
*/
public static WebloungeUserImpl fromXml(Node userNode, Site site, XPath xpath)
throws IllegalStateException {
if (userNode == null)
return null;
String login = XPathHelper.valueOf(userNode, "@id", xpath);
WebloungeUserImpl user = new WebloungeUserImpl(login);
String realm = XPathHelper.valueOf(userNode, "@realm", xpath);
if (realm != null)
user.realm = realm;
user.enabled = ConfigurationUtils.isTrue(XPathHelper.valueOf(userNode, "@enabled", xpath));
String name = XPathHelper.valueOf(userNode, "profile/name", xpath);
if (name != null) {
user.name = XPathHelper.valueOf(userNode, "profile/name", xpath);
} else {
user.firstName = XPathHelper.valueOf(userNode, "profile/firstname", xpath);
user.lastName = XPathHelper.valueOf(userNode, "profile/lastname", xpath);
}
user.initials = XPathHelper.valueOf(userNode, "profile/initials", xpath);
user.email = XPathHelper.valueOf(userNode, "profile/email", xpath);
String language = XPathHelper.valueOf(userNode, "profile/language", xpath);
if (language != null) {
Language l = LanguageUtils.getLanguage(language);
user.language = (l != null) ? l : site.getDefaultLanguage();
}
// Password
String password = XPathHelper.valueOf(userNode, "security/password", xpath);
if (password != null) {
String digestType = null;
try {
digestType = XPathHelper.valueOf(userNode, "security/password/@type", xpath);
Password pw = new PasswordImpl(password, DigestType.valueOf(digestType));
user.addPrivateCredentials(pw);
} catch (Throwable t) {
throw new IllegalStateException("Unknown password digest found: " + digestType);
}
}
// Challenge / Response
user.challenge = XPathHelper.valueOf(userNode, "security/challenge", xpath);
String response = XPathHelper.valueOf(userNode, "security/response", xpath);
if (response != null) {
String digestType = null;
try {
digestType = XPathHelper.valueOf(userNode, "security/response/@type", xpath);
user.responseDigestType = DigestType.valueOf(digestType);
user.response = response.getBytes();
} catch (Throwable t) {
throw new IllegalStateException("Unknown response digest found: " + digestType);
}
}
// Last login
String lastLogin = XPathHelper.valueOf(userNode, "security/lastlogin/date", xpath);
try {
if (lastLogin != null)
user.lastLogin = WebloungeDateFormat.parseStatic(lastLogin);
user.lastLoginSource = XPathHelper.valueOf(userNode, "security/lastlogin/ip", xpath);
} catch (ParseException e) {
// It's not important. Let's log and then forget about it
logger.error("Unable to parse last login date '{}'", lastLogin, e);
}
// Properties
NodeList properties = XPathHelper.selectList(userNode, "properties/property", xpath);
if (properties != null) {
for (int i = 0; i < properties.getLength(); i++) {
String key = XPathHelper.valueOf(properties.item(i), "name", xpath);
String value = XPathHelper.valueOf(properties.item(i), "value", xpath);
// TODO: Check for serialized objects or xml nodes
if (key != null && value != null)
user.properties.put(key, value);
}
}
return user;
}
/**
* Returns an <code>XML</code> representation of this user.
*
* @return the user as an <code>XML</code> document fragment
*/
public String toXml() {
StringBuffer b = new StringBuffer();
// Add root node
b.append("<user id=\"" + login + "\"");
if (realm != null) {
b.append(" realm=\"");
b.append(realm);
b.append("\"");
}
b.append(" enabled=\"" + enabled + "\"");
b.append(">");
//
// Profile
//
b.append("<profile>");
// First name
if (firstName != null) {
b.append("<firstname><![CDATA[");
b.append(firstName);
b.append("]]></firstname>");
}
// Last name
if (lastName != null) {
b.append("<lastname><![CDATA[");
b.append(lastName);
b.append("]]></lastname>");
}
// Name, if first name and last name were not given
if (name != null && firstName == null && lastName == null) {
b.append("<name><![CDATA[");
b.append(name);
b.append("]]></name>");
}
// Initials
if (getInitials() != null) {
b.append("<initials><![CDATA[");
b.append(getInitials());
b.append("]]></initials>");
}
// Email
if (email != null) {
b.append("<email>");
b.append(email);
b.append("</email>");
}
// Language
if (language != null) {
b.append("<language>");
b.append(language.getIdentifier());
b.append("</language>");
}
b.append("</profile>");
//
// Security
//
b.append("<security>");
// Password
Set<Object> passwords = getPrivateCredentials(Password.class);
for (Object o : passwords) {
Password password = (Password) o;
b.append("<password type=\"");
b.append(password.getDigestType().toString());
b.append("\"><![CDATA[");
b.append(password.getPassword());
b.append("]]></password>");
}
// Roles
Set<Object> roles = getPublicCredentials(Role.class);
for (Object r : roles) {
Role role = (Role) r;
b.append("<role context=\"");
b.append(role.getContext());
b.append("\"><![CDATA[");
b.append(role.getName());
b.append("]]></password>");
}
// challenge - response
if (challenge != null) {
b.append("<challenge><![CDATA[");
b.append(challenge);
b.append("]]></challenge>");
}
if (response != null) {
b.append("<response type=\"");
b.append(responseDigestType.toString());
b.append("\"><![CDATA[");
b.append(new String(response));
b.append("]]></response>");
}
// Last login
if (lastLogin != null && lastLoginSource != null) {
b.append("<lastlogin>");
b.append("<date>");
b.append(WebloungeDateFormat.formatStatic(lastLogin));
b.append("</date>");
b.append("<ip>");
b.append(lastLoginSource);
b.append("</ip>");
b.append("</lastlogin>");
}
b.append("</security>");
// properties
if (properties != null && properties.size() > 0) {
b.append("<properties>");
for (Map.Entry<String, Object> entry : properties.entrySet()) {
b.append("<property>");
b.append("<name>");
b.append(entry.getKey());
b.append("</name>");
b.append("<value><![CDATA[");
// TODO: Examine object. If XML node or serializable, serialize with
// care
b.append(entry.getValue().toString());
b.append("]]></value>");
b.append("</property>");
}
b.append("</properties>");
}
b.append("</user>");
return b.toString();
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.impl.security.AuthenticatedUserImpl#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
// Overwritten to document that we are using the super impl
return super.equals(obj);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.impl.security.AuthenticatedUserImpl#hashCode()
*/
@Override
public int hashCode() {
// Overwritten to document that we are using the super impl
return super.hashCode();
}
}