/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security.password;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList;
import com.emc.storageos.db.client.constraint.impl.AlternateIdConstraintImpl;
import com.emc.storageos.db.client.impl.DataObjectType;
import com.emc.storageos.db.client.impl.TypeMap;
import com.emc.storageos.db.client.model.PasswordHistory;
import com.emc.storageos.db.client.model.UserPreferences;
import com.emc.storageos.security.audit.AuditLogManager;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.mail.MailHelper;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.services.util.AlertsLogger;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* class to schedule a background thread to scan all localuser's password
* expire time once per day to see if need to send notification mail to
* root about the to be expired password.
*/
public class NotificationManager {
private static final Logger _log = LoggerFactory.getLogger(NotificationManager.class);
private static final AlertsLogger _alertsLog = AlertsLogger.getAlertsLogger();
private final ScheduledExecutorService _scheduler = Executors.newScheduledThreadPool(1);
private static CoordinatorClient _coordinator;
public synchronized void setCoordinator(CoordinatorClient coordinator) {
_coordinator = coordinator;
}
private DbClient _dbClient;
public void setDbClient(DbClient dbClient) {
_dbClient = dbClient;
}
private Map<String, StorageOSUser> _localUsers;
public void setLocalUsers(Map<String, StorageOSUser> localUsers) {
_localUsers = localUsers;
}
private PasswordUtils _passwordUtils;
public void setPasswordUtils(PasswordUtils passwordUtils) {
_passwordUtils = passwordUtils;
}
private MailHelper mailHelper;
private AuditLogManager _auditLogManager;
public void setAuditLogManager(AuditLogManager auditLogManager) {
_auditLogManager = auditLogManager;
}
/**
* initialize a scheduler to run Notification Thread once per day at 3:00 am.
*/
public void init() {
int OneDayMinutes = 24 * 60;
_scheduler.scheduleAtFixedRate(
new PasswordExpireMailNotifier(),
getFirstDelayInMin(),
OneDayMinutes,
TimeUnit.MINUTES);
}
/**
* scan all local user's password expire time, send notification mail if a password is
* to be expired.
*/
private class PasswordExpireMailNotifier implements Runnable {
final static String PASSWORD_EXPIRE_MAIL_LOCK = "password_expire_notifier_lock"; // NOSONAR
// ("Suppressing: removing this hard-coded password since it's just the name of attribute")
@Override
public void run() {
InterProcessLock lock = null;
try {
_log.info("Starting password_expire_notifier ...");
lock = _coordinator.getLock(PASSWORD_EXPIRE_MAIL_LOCK);
lock.acquire();
_log.info("Got password_expire_notifier_lock ...");
// check if global expire rule enabled.
String configExpireDays = _passwordUtils.getConfigProperty(Constants.PASSWORD_EXPIRE_DAYS);
if (configExpireDays == null || Integer.parseInt(configExpireDays) == 0) {
return;
}
Calendar now = Calendar.getInstance();
Calendar gracePoint = Calendar.getInstance();
gracePoint.add(Calendar.DATE, Constants.GRACE_DAYS);
_log.info("now: " + now.getTime());
_log.info("grace point: " + gracePoint.getTime());
// loop through local users
for (String user : _localUsers.keySet()) {
_log.info("checking for user: " + user);
PasswordHistory ph = _passwordUtils.getPasswordHistory(user);
Calendar expireDate = ph.getExpireDate();
_log.info("expire time: " + expireDate.getTime());
if (expireDate == null || expireDate.before(now) || expireDate.after(gracePoint)) {
_log.info("password not in grace period, skip mail");
continue;
}
Calendar lastMailSendDate = ph.getLastNotificationMailSent();
if (lastMailSendDate != null &&
lastMailSendDate.get(Calendar.DATE) == now.get(Calendar.DATE)) {
_log.info("last mail send date: " + lastMailSendDate.getTime());
_log.info("already sent by other vipr nodes today, skip mail");
continue;
}
// check if the day is a NOTIFICATION_DAY, which defined in Constants class.
int daysToExpire = PasswordUtils.getDaysAfterEpoch(expireDate)
- PasswordUtils.getDaysAfterEpoch(now);
for (int day : Constants.NOTIFICATION_DAYS) {
if (day == daysToExpire) {
_log.info("send notification mail for " + user + " at day " + daysToExpire);
_alertsLog.warn(user + "'s password is about to expire in " + daysToExpire + " days");
Map parameters = Maps.newHashMap();
parameters.put("user", user);
parameters.put("daysToExpire", daysToExpire);
parameters.put("configExpireDays", configExpireDays);
if (sendPasswordToBeExpiredMail(parameters)) {
// update mail sent time in Cassandra
ph.setLastNotificationMailSent(now);
_dbClient.updateAndReindexObject(ph);
// audit the mail sent success
_auditLogManager.recordAuditLog(
null, null, "syssvc",
OperationTypeEnum.SEND_PASSWORD_TO_BE_EXPIRE_MAIL,
new Date().getTime(),
AuditLogManager.AUDITLOG_SUCCESS,
null, user);
} else {
// audit the mail sent fail
_auditLogManager.recordAuditLog(
null, null, "syssvc",
OperationTypeEnum.SEND_PASSWORD_TO_BE_EXPIRE_MAIL,
new Date().getTime(),
AuditLogManager.AUDITLOG_FAILURE,
null, user);
}
break;
}
}
}
} catch (Exception e) {
_log.warn("Unexpected exception during db maintenance", e);
} finally {
if (lock != null) {
try {
lock.release();
} catch (Exception e) {
_log.warn("Unexpected exception unlocking repair lock", e);
}
}
}
}
}
/**
* run the MailNotifier now
*/
public void runMailNotifierNow() {
new PasswordExpireMailNotifier().run();
}
private long getFirstDelayInMin() {
Date aDate = new Date();
Calendar with = Calendar.getInstance();
with.setTime(aDate);
int hour = with.get(Calendar.HOUR_OF_DAY);
int Minutes = with.get(Calendar.MINUTE);
int MinutesPassed12AM = hour * 60 + Minutes;
int MinutesAtMailSendHour = Constants.MAIL_SEND_HOUR * 60;
int OneDayMinutes = 24 * 60;
long DelayInMinutes = (MinutesPassed12AM <= MinutesAtMailSendHour) ?
MinutesAtMailSendHour - MinutesPassed12AM :
OneDayMinutes - (MinutesPassed12AM - MinutesAtMailSendHour);
return DelayInMinutes;
}
/**
* send notification mail to root about the to be expired local user.
*
* as for now, only 4 special local users: sysmonitor, proxyuser, root, svcuser.
* all mails will send to root.
*
* if vipr introduces manage other local users in the future, need change
* the logic to send mail to the user whose password to be expired.
*
* @param parameters
*/
public boolean sendPasswordToBeExpiredMail(Map parameters) {
String to = getMailAddressOfUser("root");
if (to == null || to.isEmpty()) {
_log.warn("root's mail address haven't configured, skip sending mail");
return false;
}
String title = String.format("ATTENTION - %s Password Is About To Expire",
parameters.get("user"));
String content = MailHelper.readTemplate("PasswordToBeExpireEmail.html");
content = MailHelper.parseTemplate(parameters, content);
getMailHelper().sendMailMessage(to, title, content);
return true;
}
private MailHelper getMailHelper() {
if (mailHelper == null) {
mailHelper = new MailHelper(_coordinator);
}
return mailHelper;
}
/**
* get user's mail address from UserPreference CF
*
* @param userName
* @return
*/
private String getMailAddressOfUser(String userName) {
DataObjectType doType = TypeMap.getDoType(UserPreferences.class);
AlternateIdConstraint constraint = new AlternateIdConstraintImpl(
doType.getColumnField(UserPreferences.USER_ID), userName);
NamedElementQueryResultList queryResults = new NamedElementQueryResultList();
_dbClient.queryByConstraint(constraint, queryResults);
List<URI> userPrefsIds = Lists.newArrayList();
for (NamedElementQueryResultList.NamedElement namedElement : queryResults) {
userPrefsIds.add(namedElement.getId());
}
final List<UserPreferences> userPrefs = Lists.newArrayList();
Iterator<UserPreferences> iter = _dbClient.queryIterativeObjects(UserPreferences.class, userPrefsIds);
while (iter.hasNext()) {
userPrefs.add(iter.next());
}
if (userPrefs.size() > 1) {
throw new IllegalStateException("There should only be 1 user preferences object for a user");
}
else if (userPrefs.isEmpty()) {
// if there isn't a user prefs object in the DB yet then we haven't saved one for this user yet.
return null;
}
return userPrefs.get(0).getEmail();
}
}