/*
* 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.svc.report;
import com.google.gson.JsonSyntaxException;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import password.pwm.PwmApplication;
import password.pwm.bean.UserIdentity;
import password.pwm.bean.UserInfoBean;
import password.pwm.config.option.DataStorageMethod;
import password.pwm.error.PwmException;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.health.HealthRecord;
import password.pwm.ldap.LdapOperationsHelper;
import password.pwm.svc.PwmService;
import password.pwm.util.java.ClosableIterator;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.localdb.LocalDB;
import password.pwm.util.localdb.LocalDBException;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.secure.SecureService;
import java.util.Collections;
import java.util.List;
public class UserCacheService implements PwmService {
private static final PwmLogger LOGGER = PwmLogger.forClass(UserCacheService.class);
private CacheStoreWrapper cacheStore;
private STATUS status;
private PwmApplication pwmApplication;
public STATUS status() {
return status;
}
public UserCacheRecord updateUserCache(final UserInfoBean userInfoBean)
throws PwmUnrecoverableException
{
final StorageKey storageKey = StorageKey.fromUserInfoBean(userInfoBean, pwmApplication);
boolean preExisting = false;
try {
UserCacheRecord userCacheRecord = readStorageKey(storageKey);
if (userCacheRecord == null) {
userCacheRecord = new UserCacheRecord();
} else {
preExisting = true;
}
userCacheRecord.addUiBeanData(userInfoBean);
store(userCacheRecord);
return userCacheRecord;
} catch (LocalDBException e) {
LOGGER.error("unable to store user status cache to localdb: " + e.getMessage());
}
LOGGER.trace("updateCache: " + (preExisting?"updated existing":"created new") + " user cache for " + userInfoBean.getUserIdentity() + " user key " + storageKey.getKey());
return null;
}
public UserCacheRecord readStorageKey(final StorageKey storageKey) throws LocalDBException {
return cacheStore.read(storageKey);
}
public boolean removeStorageKey(final StorageKey storageKey)
throws LocalDBException
{
return cacheStore.remove(storageKey);
}
public void store(final UserCacheRecord userCacheRecord)
throws LocalDBException, PwmUnrecoverableException
{
final StorageKey storageKey = StorageKey.fromUserGUID(userCacheRecord.getUserGUID(), pwmApplication);
cacheStore.write(storageKey, userCacheRecord);
}
public void clear()
throws LocalDBException
{
cacheStore.clear();
}
public UserStatusCacheBeanIterator<StorageKey> iterator() {
try {
return new UserStatusCacheBeanIterator<>();
} catch (LocalDBException e) {
LOGGER.error("unexpected error generating user status iterator: " + e.getMessage());
return null;
}
}
public class UserStatusCacheBeanIterator<K extends StorageKey> implements ClosableIterator {
private LocalDB.LocalDBIterator<String> innerIterator;
private UserStatusCacheBeanIterator() throws LocalDBException {
innerIterator = cacheStore.localDB.iterator(CacheStoreWrapper.DB);
}
public boolean hasNext() {
return innerIterator.hasNext();
}
public StorageKey next() {
final String nextKey = innerIterator.next();
return new StorageKey(nextKey);
}
public void remove() {
throw new UnsupportedOperationException();
}
public void close() {
innerIterator.close();
}
}
public void init(final PwmApplication pwmApplication) throws PwmException {
status = STATUS.OPENING;
this.pwmApplication = pwmApplication;
this.cacheStore = new CacheStoreWrapper(pwmApplication.getLocalDB());
status = STATUS.OPEN;
}
public void close() {
status = STATUS.CLOSED;
}
public List<HealthRecord> healthCheck() {
return Collections.emptyList();
}
public ServiceInfo serviceInfo()
{
return new ServiceInfo(Collections.singletonList(DataStorageMethod.LOCALDB));
}
public int size()
{
return cacheStore.size();
}
public static class StorageKey {
private String key;
private StorageKey(final String key)
{
if (key == null || key.isEmpty()) {
throw new IllegalArgumentException("storage key must have a value");
}
this.key = key;
}
public String getKey()
{
return key;
}
public static StorageKey fromUserInfoBean(final UserInfoBean userInfoBean, final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
final String userGUID = userInfoBean.getUserGuid();
return fromUserGUID(userGUID, pwmApplication);
}
public static StorageKey fromUserIdentity(final PwmApplication pwmApplication, final UserIdentity userIdentity)
throws ChaiUnavailableException, PwmUnrecoverableException
{
final String userGUID = LdapOperationsHelper.readLdapGuidValue(pwmApplication, null, userIdentity, true);
return fromUserGUID(userGUID, pwmApplication);
}
private static StorageKey fromUserGUID(final String userGUID, final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
final SecureService secureService = pwmApplication.getSecureService();
return new StorageKey(secureService.hash(userGUID));
}
}
private static class CacheStoreWrapper {
private static final LocalDB.DB DB = LocalDB.DB.USER_CACHE;
private final LocalDB localDB;
private CacheStoreWrapper(final LocalDB localDB)
{
this.localDB = localDB;
}
private void write(final StorageKey key, final UserCacheRecord cacheBean)
throws LocalDBException
{
final String jsonValue = JsonUtil.serialize(cacheBean);
localDB.put(DB,key.getKey(),jsonValue);
}
private UserCacheRecord read(final StorageKey key)
throws LocalDBException
{
final String jsonValue = localDB.get(DB,key.getKey());
if (jsonValue != null && !jsonValue.isEmpty()) {
try {
return JsonUtil.deserialize(jsonValue,UserCacheRecord.class);
} catch (JsonSyntaxException e) {
LOGGER.error("error reading record from cache store for key=" + key.getKey() + ", error: " + e.getMessage());
localDB.remove(DB,key.getKey());
}
}
return null;
}
private boolean remove(final StorageKey key)
throws LocalDBException
{
return localDB.remove(DB,key.getKey());
}
private void clear()
throws LocalDBException
{
localDB.truncate(DB);
}
private int size()
{
try {
return localDB.size(DB);
} catch (Exception e) {
return 0;
}
}
}
}