/*
* 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.novell.ldapchai.cr.Answer;
import password.pwm.config.Configuration;
import password.pwm.config.option.DataStorageMethod;
import password.pwm.i18n.Admin;
import password.pwm.util.LocaleHelper;
import password.pwm.util.java.Percent;
import password.pwm.util.java.TimeDuration;
import java.math.BigInteger;
import java.text.NumberFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
public class ReportSummaryData {
private static final long MS_DAY = TimeDuration.DAY.getTotalMilliseconds();
private static BigInteger TWO = new BigInteger("2");
private Instant meanCacheTime;
private int totalUsers;
private int hasResponses;
private int hasResponseSetTime;
private int hasHelpdeskResponses;
private int hasPasswordExpirationTime;
private int hasAccountExpirationTime;
private int hasLoginTime;
private int hasChangePwTime;
private int hasOtpSecret;
private int hasOtpSecretSetTime;
private Map<DataStorageMethod, Integer> responseStorage = new HashMap<>();
private Map<Answer.FormatType, Integer> responseFormatType = new HashMap<>();
private Map<String, Integer> ldapProfile = new HashMap<>();
private int pwExpired;
private int pwPreExpired;
private int pwWarnPeriod;
private Map<Integer,Integer> pwExpireDays = new TreeMap<>();
private Map<Integer,Integer> accountExpireDays = new TreeMap<>();
private Map<Integer,Integer> changePwDays = new TreeMap<>();
private Map<Integer,Integer> responseSetDays = new TreeMap<>();
private Map<Integer,Integer> otpSetDays = new TreeMap<>();
private Map<Integer,Integer> loginDays = new TreeMap<>();
private ReportSummaryData() {
}
public static ReportSummaryData newSummaryData(final List<Integer> trackedDays) {
final ReportSummaryData reportSummaryData = new ReportSummaryData();
if (trackedDays != null) {
for (final int day : trackedDays) {
reportSummaryData.pwExpireDays.put(day, 0);
reportSummaryData.accountExpireDays.put(day, 0);
reportSummaryData.changePwDays.put(day, 0);
reportSummaryData.responseSetDays.put(day, 0);
reportSummaryData.otpSetDays.put(day, 0);
reportSummaryData.loginDays.put(day, 0);
}
}
return reportSummaryData;
}
public int getTotalUsers()
{
return totalUsers;
}
public int getHasResponses()
{
return hasResponses;
}
public int getHasPasswordExpirationTime()
{
return hasPasswordExpirationTime;
}
public Map<DataStorageMethod, Integer> getResponseStorage()
{
return Collections.unmodifiableMap(responseStorage);
}
public Map<Answer.FormatType, Integer> getResponseFormatType()
{
return responseFormatType;
}
public Instant getMeanCacheTime()
{
return meanCacheTime;
}
void update(final UserCacheRecord userCacheRecord) {
update(userCacheRecord, true);
}
void remove(final UserCacheRecord userCacheRecord) {
update(userCacheRecord,false);
}
private synchronized void update(final UserCacheRecord userCacheRecord, final boolean adding) {
final int modifier = adding ? 1 : -1;
totalUsers += modifier;
updateMeanTime(userCacheRecord.cacheTimestamp,adding);
if (userCacheRecord.hasResponses) {
hasResponses += modifier;
}
if (userCacheRecord.hasHelpdeskResponses) {
hasHelpdeskResponses += modifier;
}
if (userCacheRecord.responseSetTime != null) {
hasResponseSetTime += modifier;
for (final int day : responseSetDays.keySet()) {
responseSetDays.put(day, responseSetDays.get(day) + calcTimeWindow(userCacheRecord.responseSetTime, MS_DAY * day, adding));
}
}
if (userCacheRecord.passwordExpirationTime != null) {
hasPasswordExpirationTime += modifier;
for (final int day : pwExpireDays.keySet()) {
pwExpireDays.put(day, pwExpireDays.get(day) + calcTimeWindow(userCacheRecord.passwordExpirationTime, MS_DAY * day, adding));
}
}
if (userCacheRecord.accountExpirationTime != null) {
hasAccountExpirationTime += modifier;
for (final int day : accountExpireDays.keySet()) {
accountExpireDays.put(day, accountExpireDays.get(day) + calcTimeWindow(userCacheRecord.accountExpirationTime, MS_DAY * day, adding));
}
}
if (userCacheRecord.lastLoginTime != null) {
hasLoginTime += modifier;
for (final int day : loginDays.keySet()) {
loginDays.put(day, loginDays.get(day) + calcTimeWindow(userCacheRecord.lastLoginTime, MS_DAY * day, adding));
}
}
if (userCacheRecord.passwordChangeTime != null) {
hasChangePwTime += modifier;
for (final int day : changePwDays.keySet()) {
changePwDays.put(day, changePwDays.get(day) + calcTimeWindow(userCacheRecord.passwordChangeTime, MS_DAY * day, adding));
}
}
if (userCacheRecord.passwordStatus != null) {
if (adding) {
if (userCacheRecord.passwordStatus.isExpired()) {
pwExpired++;
}
if (userCacheRecord.passwordStatus.isPreExpired()) {
pwPreExpired++;
}
if (userCacheRecord.passwordStatus.isWarnPeriod()) {
pwWarnPeriod++;
}
} else {
if (userCacheRecord.passwordStatus.isExpired()) {
pwExpired--;
}
if (userCacheRecord.passwordStatus.isPreExpired()) {
pwPreExpired--;
}
if (userCacheRecord.passwordStatus.isWarnPeriod()) {
pwWarnPeriod--;
}
}
}
if (userCacheRecord.responseStorageMethod != null) {
final DataStorageMethod method = userCacheRecord.responseStorageMethod;
if (!responseStorage.containsKey(method)) {
responseStorage.put(method,0);
}
if (adding) {
responseStorage.put(method, responseStorage.get(method) + 1);
} else {
responseStorage.put(method, responseStorage.get(method) - 1);
}
}
if (userCacheRecord.getLdapProfile() != null) {
final String userProfile = userCacheRecord.getLdapProfile();
if (!ldapProfile.containsKey(userProfile)) {
ldapProfile.put(userProfile,0);
}
if (adding) {
ldapProfile.put(userProfile, ldapProfile.get(userProfile) + 1);
} else {
ldapProfile.put(userProfile, ldapProfile.get(userProfile) - 1);
}
}
if (userCacheRecord.responseFormatType != null) {
final Answer.FormatType type = userCacheRecord.responseFormatType;
if (!responseFormatType.containsKey(type)) {
responseFormatType.put(type,0);
}
if (adding) {
responseFormatType.put(type, responseFormatType.get(type) + 1);
} else {
responseFormatType.put(type, responseFormatType.get(type) + 1);
}
}
if (userCacheRecord.isHasOtpSecret()) {
hasOtpSecret += modifier;
}
if (userCacheRecord.getOtpSecretSetTime() != null) {
hasOtpSecretSetTime += modifier;
for (final int day : otpSetDays.keySet()) {
otpSetDays.put(day, otpSetDays.get(day) + calcTimeWindow(userCacheRecord.getOtpSecretSetTime(), MS_DAY * day, adding));
}
}
}
private void updateMeanTime(final Instant newTime, final boolean adding) {
if (meanCacheTime == null) {
if (adding) {
meanCacheTime = newTime;
}
return;
}
final BigInteger currentMillis = BigInteger.valueOf(meanCacheTime.toEpochMilli());
final BigInteger newMillis = BigInteger.valueOf(newTime.toEpochMilli());
final BigInteger combinedMillis = currentMillis.add(newMillis);
final BigInteger halvedMillis = combinedMillis.divide(TWO);
meanCacheTime = Instant.ofEpochMilli(halvedMillis.longValue());
}
private int calcTimeWindow(final Instant eventDate, final long timeWindow, final boolean adding) {
if (eventDate == null) {
return 0;
}
final TimeDuration timeBoundary = new TimeDuration(0,timeWindow);
final TimeDuration eventDifference = TimeDuration.fromCurrent(eventDate);
if (timeWindow >= 0 && eventDate.isAfter(Instant.now()) && eventDifference.isShorterThan(timeBoundary)) {
return adding ? 1 : -1;
}
if (timeWindow < 0 && eventDate.isBefore(Instant.now()) && eventDifference.isShorterThan(timeBoundary)) {
return adding ? 1 : -1;
}
return 0;
}
public List<PresentationRow> asPresentableCollection(final Configuration config, final Locale locale) {
final ArrayList<PresentationRow> returnCollection = new ArrayList<>();
final PresentationRowBuilder builder = new PresentationRowBuilder(config,this.totalUsers,locale);
returnCollection.add(builder.makeNoPctRow("Field_Report_Sum_Total", this.totalUsers, null));
if (totalUsers == 0) {
return returnCollection;
}
if (config.getLdapProfiles().keySet().size() > 1) {
for (final String userProfile : ldapProfile.keySet()) {
final int count = this.ldapProfile.get(userProfile);
final String displayName = config.getLdapProfiles().containsKey(userProfile)
? config.getLdapProfiles().get(userProfile).getDisplayName(locale)
: userProfile;
returnCollection.add(
builder.makeRow("Field_Report_Sum_LdapProfile", count, displayName));
}
}
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveLoginTime", this.hasLoginTime));
for (final Integer day : loginDays.keySet()) {
if (day < 0) {
returnCollection.add(builder.makeRow("Field_Report_Sum_LoginTimePrevious", this.loginDays.get(day), String.valueOf(Math.abs(day))));
}
}
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveAccountExpirationTime", this.hasAccountExpirationTime));
for (final Integer day : accountExpireDays.keySet()) {
final String key = day < 0 ? "Field_Report_Sum_AccountExpirationPrevious" : "Field_Report_Sum_AccountExpirationNext";
returnCollection.add(builder.makeRow(key, this.accountExpireDays.get(day), String.valueOf(Math.abs(day))));
}
returnCollection.add(builder.makeRow("Field_Report_Sum_HavePwExpirationTime", this.hasPasswordExpirationTime));
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveExpiredPw",this.pwExpired));
returnCollection.add(builder.makeRow("Field_Report_Sum_HavePreExpiredPw",this.pwPreExpired));
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveExpiredPwWarn",this.pwWarnPeriod));
for (final Integer day : pwExpireDays.keySet()) {
final String key = day < 0 ? "Field_Report_Sum_PwExpirationPrevious" : "Field_Report_Sum_PwExpirationNext";
returnCollection.add(builder.makeRow(key, this.pwExpireDays.get(day), String.valueOf(Math.abs(day))));
}
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveChgPw", this.hasChangePwTime));
for (final Integer day : changePwDays.keySet()) {
if (day < 0) {
returnCollection.add(builder.makeRow("Field_Report_Sum_ChgPwPrevious", this.changePwDays.get(day), String.valueOf(Math.abs(day))));
}
}
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveResponses", this.hasResponses));
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveHelpdeskResponses", this.hasHelpdeskResponses));
for (final DataStorageMethod storageMethod : this.getResponseStorage().keySet()) {
final int count = this.getResponseStorage().get(storageMethod);
returnCollection.add(builder.makeRow("Field_Report_Sum_StorageMethod", count, storageMethod.toString()));
}
for (final Answer.FormatType formatType : this.getResponseFormatType().keySet()) {
final int count = this.getResponseFormatType().get(formatType);
returnCollection.add(builder.makeRow("Field_Report_Sum_ResponseFormatType", count, formatType.toString()));
}
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveResponseTime", this.hasResponseSetTime));
for (final Integer day : responseSetDays.keySet()) {
if (day < 0) {
returnCollection.add(builder.makeRow("Field_Report_Sum_ResponseTimePrevious", this.responseSetDays.get(day), String.valueOf(Math.abs(day))));
}
}
if (this.hasOtpSecret > 0) {
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveOtpSecret", this.hasOtpSecret));
returnCollection.add(builder.makeRow("Field_Report_Sum_HaveOtpSecretSetTime", this.hasOtpSecretSetTime));
for (final Integer day : otpSetDays.keySet()) {
if (day < 0) {
returnCollection.add(builder.makeRow("Field_Report_Sum_OtpSecretTimePrevious", this.otpSetDays.get(day), String.valueOf(Math.abs(day))));
}
}
}
return returnCollection;
}
public static class PresentationRow {
private String label;
private String count;
private String pct;
public PresentationRow(
final String label,
final String count,
final String pct
)
{
this.label = label;
this.count = count;
this.pct = pct;
}
public String getLabel()
{
return label;
}
public String getCount()
{
return count;
}
public String getPct()
{
return pct;
}
}
public static class PresentationRowBuilder {
private final Configuration config;
private final int totalUsers;
private final Locale locale;
public PresentationRowBuilder(
final Configuration config,
final int totalUsers,
final Locale locale
)
{
this.config = config;
this.totalUsers = totalUsers;
this.locale = locale;
}
public PresentationRow makeRow(final String labelKey, final int valueCount) {
return makeRow(labelKey, valueCount, null);
}
public PresentationRow makeRow(final String labelKey, final int valueCount, final String replacement)
{
final String display = replacement == null
? LocaleHelper.getLocalizedMessage(locale, labelKey, config, Admin.class)
: LocaleHelper.getLocalizedMessage(locale, labelKey, config, Admin.class, new String[]{replacement});
final String pct = valueCount > 0 ? new Percent(valueCount,totalUsers).pretty(2) : "";
final NumberFormat numberFormat = NumberFormat.getInstance(locale);
final String formattedCount = numberFormat.format(valueCount);
return new PresentationRow(display, formattedCount, pct);
}
public PresentationRow makeNoPctRow(final String labelKey, final int valueCount, final String replacement)
{
final String display = replacement == null
? LocaleHelper.getLocalizedMessage(locale, labelKey, config, Admin.class)
: LocaleHelper.getLocalizedMessage(locale, labelKey, config, Admin.class, new String[]{replacement});
final NumberFormat numberFormat = NumberFormat.getInstance(locale);
final String formattedCount = numberFormat.format(valueCount);
return new PresentationRow(display, formattedCount, null);
}
}
}