/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2017 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.bean;
import com.novell.ldapchai.ChaiUser;
import com.novell.ldapchai.exception.ChaiException;
import password.pwm.PwmApplication;
import password.pwm.config.Configuration;
import password.pwm.config.profile.LdapProfile;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.svc.cache.CacheKey;
import password.pwm.svc.cache.CachePolicy;
import password.pwm.svc.cache.CacheService;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.java.StringUtil;
import password.pwm.util.java.TimeDuration;
import java.io.Serializable;
import java.util.StringTokenizer;
public class UserIdentity implements Serializable, Comparable {
private static final String CRYPO_HEADER = "ui_C-";
private static final String DELIM_SEPARATOR = "|";
private transient String obfuscatedValue;
private transient boolean canonicalized;
private String userDN;
private String ldapProfile;
public UserIdentity(final String userDN, final String ldapProfile) {
if (userDN == null || userDN.length() < 1) {
throw new IllegalArgumentException("UserIdentity: userDN value cannot be empty");
}
this.userDN = userDN;
this.ldapProfile = ldapProfile == null ? "" : ldapProfile;
}
public String getUserDN() {
return userDN;
}
public String getLdapProfileID() {
return ldapProfile;
}
public LdapProfile getLdapProfile(final Configuration configuration) {
if (configuration == null) {
return null;
}
if (configuration.getLdapProfiles().containsKey(this.getLdapProfileID())) {
return configuration.getLdapProfiles().get(this.getLdapProfileID());
} else {
return null;
}
}
public String toString() {
return "UserIdentity" + JsonUtil.serialize(this);
}
public String toObfuscatedKey(final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
// use local cache first.
if (!StringUtil.isEmpty(obfuscatedValue)) {
return obfuscatedValue;
}
// check app cache. This is used primarily so that keys are static over some meaningful lifetime, allowing browser caching based on keys.
final CacheService cacheService = pwmApplication.getCacheService();
final CacheKey cacheKey = CacheKey.makeCacheKey(this.getClass(), null, "userKey" + "|" + this.toDelimitedKey());
final String cachedValue = cacheService.get(cacheKey);
if (!StringUtil.isEmpty(cachedValue)) {
obfuscatedValue = cachedValue;
return cachedValue;
}
// generate key
try {
final String jsonValue = JsonUtil.serialize(this);
final String localValue = CRYPO_HEADER + pwmApplication.getSecureService().encryptToString(jsonValue);
this.obfuscatedValue = localValue;
cacheService.put(cacheKey, CachePolicy.makePolicyWithExpiration(TimeDuration.DAY), localValue);
return localValue;
} catch (Exception e) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unexpected error making obfuscated user key: " + e.getMessage()));
}
}
public String toDelimitedKey() {
return this.getLdapProfileID() + DELIM_SEPARATOR + this.getUserDN();
}
public String toDisplayString() {
return this.getUserDN() + ((this.getLdapProfileID() != null && !this.getLdapProfileID().isEmpty()) ? " (" + this.getLdapProfileID() + ")" : "");
}
public static UserIdentity fromObfuscatedKey(final String key, final PwmApplication pwmApplication) throws PwmUnrecoverableException {
if (key == null || key.length() < 1) {
return null;
}
if (!key.startsWith(CRYPO_HEADER)) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"cannot reverse obfuscated user key: missing header; value=" + key));
}
try {
final String input = key.substring(CRYPO_HEADER.length(),key.length());
final String jsonValue = pwmApplication.getSecureService().decryptStringValue(input);
return JsonUtil.deserialize(jsonValue,UserIdentity.class);
} catch (Exception e) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unexpected error reversing obfuscated user key: " + e.getMessage()));
}
}
public static UserIdentity fromDelimitedKey(final String key) throws PwmUnrecoverableException {
if (key == null || key.length() < 1) {
return null;
}
final StringTokenizer st = new StringTokenizer(key, DELIM_SEPARATOR);
if (st.countTokens() < 2) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"not enough tokens while parsing delimited identity key"));
} else if (st.countTokens() > 2) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"too many string tokens while parsing delimited identity key"));
}
final String profileID = st.nextToken();
final String userDN = st.nextToken();
return new UserIdentity(userDN,profileID);
}
public static UserIdentity fromKey(final String key, final PwmApplication pwmApplication) throws PwmUnrecoverableException {
if (key == null || key.length() < 1) {
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"userKey parameter is missing");
throw new PwmUnrecoverableException(errorInformation);
}
if (key.startsWith(CRYPO_HEADER)) {
return fromObfuscatedKey(key, pwmApplication);
}
return fromDelimitedKey(key);
}
public boolean canonicalEquals(final UserIdentity otherIdentity, final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
if (otherIdentity == null) {
return false;
}
final UserIdentity thisCanonicalIdentity = this.canonicalized(pwmApplication);
final UserIdentity otherCanonicalIdentity = otherIdentity.canonicalized(pwmApplication);
return thisCanonicalIdentity.equals(otherCanonicalIdentity);
}
@Override
public boolean equals(final Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final UserIdentity that = (UserIdentity) o;
if (!ldapProfile.equals(that.ldapProfile)) {
return false;
}
if (!userDN.equals(that.userDN)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
int result = userDN.hashCode();
result = 31 * result + ldapProfile.hashCode();
return result;
}
@Override
public int compareTo(final Object o) {
final String thisStr = (ldapProfile == null ? "_" : ldapProfile) + userDN;
final UserIdentity otherIdentity = (UserIdentity)o;
final String otherStr = (otherIdentity.ldapProfile == null ? "_" : otherIdentity.ldapProfile) + otherIdentity.userDN;
return thisStr.compareTo(otherStr);
}
public UserIdentity canonicalized(final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
if (this.canonicalized) {
return this;
}
final ChaiUser chaiUser = pwmApplication.getProxiedChaiUser(this);
final String userDN;
try {
userDN = chaiUser.readCanonicalDN();
} catch (ChaiException e) {
throw PwmUnrecoverableException.fromChaiException(e);
}
final UserIdentity canonicalziedIdentity = new UserIdentity(userDN, this.getLdapProfileID());
canonicalziedIdentity.canonicalized = true;
return canonicalziedIdentity;
}
}