/*
* 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.pwnotify;
import com.novell.ldapchai.ChaiUser;
import com.novell.ldapchai.exception.ChaiOperationException;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.bean.EmailItemBean;
import password.pwm.bean.SessionLabel;
import password.pwm.bean.UserIdentity;
import password.pwm.bean.UserInfoBean;
import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmOperationalException;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.ldap.LdapOperationsHelper;
import password.pwm.ldap.UserStatusReader;
import password.pwm.util.db.DatabaseException;
import password.pwm.util.db.DatabaseTable;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.java.StringUtil;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.macro.MacroMachine;
import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
public class PasswordExpireNotificationEngine {
private static final PwmLogger LOGGER = PwmLogger.forClass(PasswordExpireNotificationEngine.class);
private static final SessionLabel SESSION_LABEL = SessionLabel.PW_EXP_NOTICE_LABEL;
private final Settings settings;
private final PwmApplication pwmApplication;
public PasswordExpireNotificationEngine(final PwmApplication pwmApplication)
{
this.pwmApplication = pwmApplication;
this.settings = Settings.fromConfiguration(pwmApplication.getConfig());
}
public void executeJob()
throws ChaiUnavailableException, ChaiOperationException, PwmOperationalException, PwmUnrecoverableException
{
final Iterator<UserIdentity> workQueue = LdapOperationsHelper.readAllUsersFromLdap(
pwmApplication,
null,
null,
1_000_000
);
while (workQueue.hasNext()) {
final UserIdentity userIdentity = workQueue.next();
processUserIdentity(userIdentity);
}
}
private void processUserIdentity(
final UserIdentity userIdentity
)
throws PwmUnrecoverableException
{
final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity);
final Instant passwordExpirationTime = LdapOperationsHelper.readPasswordExpirationTime(theUser);
if (passwordExpirationTime == null || passwordExpirationTime.isBefore(Instant.now())) {
return;
}
final Instant previousNotice;
{
final DbStorage dbStorage = new DbStorage(pwmApplication);
final NotificationState storedState = dbStorage.readStoredState(userIdentity, SESSION_LABEL);
if (storedState == null || storedState.getExpireTime() == null || !storedState.getExpireTime().equals(passwordExpirationTime)) {
previousNotice = null;
} else {
previousNotice = storedState.getLastNotice();
}
}
final int currentDayInterval = daysUntilInstant(passwordExpirationTime);
final int previousDays = previousNotice == null
? Integer.MAX_VALUE
: daysUntilInstant(previousNotice);
int nextDayInterval = -1;
for (final int configuredDayInterval : settings.getDayIntervals()) {
if (currentDayInterval <= configuredDayInterval) {
if (configuredDayInterval != previousDays) {
nextDayInterval = configuredDayInterval;
}
}
}
if (nextDayInterval < 1) {
return;
}
System.out.println(userIdentity + " next=" +nextDayInterval);
{
final DbStorage dbStorage = new DbStorage(pwmApplication);
dbStorage.writeStoredState(userIdentity, SESSION_LABEL, new NotificationState(passwordExpirationTime, Instant.now()));
}
sendNoticeEmail(userIdentity);
}
void sendNoticeEmail(final UserIdentity userIdentity)
throws PwmUnrecoverableException
{
final Locale userLocale = PwmConstants.DEFAULT_LOCALE;
final EmailItemBean emailItemBean = pwmApplication.getConfig().readSettingAsEmail(
PwmSetting.EMAIL_PW_EXPIRATION_NOTICE,
userLocale
);
final MacroMachine macroMachine = MacroMachine.forUser(pwmApplication, userLocale, SESSION_LABEL, userIdentity);
final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, SESSION_LABEL);
final UserInfoBean userInfoBean = userStatusReader.populateUserInfoBean(userLocale, userIdentity);
pwmApplication.getEmailQueue().submitEmail(emailItemBean, userInfoBean, macroMachine);
}
static int daysUntilInstant(final Instant instant) {
final TimeDuration timeDuration = TimeDuration.fromCurrent(instant);
return (int)timeDuration.getTotalDays();
}
@Getter
static class Settings implements Serializable {
private List<Integer> dayIntervals = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(new Integer[]{8,5,3})));
static Settings fromConfiguration(final Configuration configuration)
{
final Settings settings = new Settings();
final List<Integer> tempList = new ArrayList<>(Arrays.asList(new Integer[]{8,5,3}));
Collections.sort(tempList);
Collections.reverse(tempList);
settings.dayIntervals = Collections.unmodifiableList(tempList);
return settings;
}
}
@Getter
@AllArgsConstructor
static class NotificationState implements Serializable {
private Instant expireTime;
private Instant lastNotice;
}
interface PwExpireStorageEngine {
NotificationState readStoredState(
UserIdentity userIdentity,
SessionLabel sessionLabel
)
throws PwmUnrecoverableException;
void writeStoredState(UserIdentity userIdentity, SessionLabel sessionLabel, NotificationState notificationState) throws PwmUnrecoverableException;
}
static class DbStorage implements PwExpireStorageEngine {
private static final DatabaseTable TABLE = DatabaseTable.PW_NOTIFY;
private final PwmApplication pwmApplication;
DbStorage(final PwmApplication pwmApplication)
{
this.pwmApplication = pwmApplication;
}
@Override
public NotificationState readStoredState(
final UserIdentity userIdentity,
final SessionLabel sessionLabel
)
throws PwmUnrecoverableException
{
final String guid;
try {
guid = LdapOperationsHelper.readLdapGuidValue(pwmApplication, sessionLabel, userIdentity, true);
} catch (ChaiUnavailableException e) {
throw new PwmUnrecoverableException(PwmUnrecoverableException.fromChaiException(e).getErrorInformation());
}
if (StringUtil.isEmpty(guid)) {
throw new PwmUnrecoverableException(PwmError.ERROR_MISSING_GUID);
}
final String rawDbValue;
try {
rawDbValue = pwmApplication.getDatabaseAccessor().get(TABLE, guid);
} catch (DatabaseException e) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,e.getMessage()));
}
return JsonUtil.deserialize(rawDbValue, NotificationState.class);
}
public void writeStoredState(
final UserIdentity userIdentity,
final SessionLabel sessionLabel,
final NotificationState notificationState
)
throws PwmUnrecoverableException
{
final String guid;
try {
guid = LdapOperationsHelper.readLdapGuidValue(pwmApplication, sessionLabel, userIdentity, true);
} catch (ChaiUnavailableException e) {
throw new PwmUnrecoverableException(PwmUnrecoverableException.fromChaiException(e).getErrorInformation());
}
if (StringUtil.isEmpty(guid)) {
throw new PwmUnrecoverableException(PwmError.ERROR_MISSING_GUID);
}
final String rawDbValue = JsonUtil.serialize(notificationState);
try {
pwmApplication.getDatabaseAccessor().put(TABLE, guid, rawDbValue);
} catch (DatabaseException e) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,e.getMessage()));
}
}
}
}