public class RestClientHelper { private static final PwmLogger LOGGER = PwmLogger.forClass(RestClientHelper.class); public static String makeOutboundRestWSCall( final PwmApplication pwmApplication, final Locale locale, final String url, final String jsonRequestBody) throws PwmOperationalException, PwmUnrecoverableException { final HttpPost httpPost = new HttpPost(url); httpPost.setHeader("Accept", PwmConstants.AcceptValue.json.getHeaderValue()); if (locale != null) { httpPost.setHeader("Accept-Locale", locale.toString()); } httpPost.setHeader("Content-Type", PwmConstants.ContentTypeValue.json.getHeaderValue()); final HttpResponse httpResponse; try { final StringEntity stringEntity = new StringEntity(jsonRequestBody); stringEntity.setContentType(PwmConstants.AcceptValue.json.getHeaderValue()); httpPost.setEntity(stringEntity); LOGGER.debug( "beginning external rest call to: " + httpPost.toString() + ", body: " + jsonRequestBody); httpResponse = PwmHttpClient.getHttpClient(pwmApplication.getConfig()).execute(httpPost); final String responseBody = EntityUtils.toString(httpResponse.getEntity()); LOGGER.trace( "external rest call returned: " + httpResponse.getStatusLine().toString() + ", body: " + responseBody); if (httpResponse.getStatusLine().getStatusCode() != 200) { final String errorMsg = "received non-200 response code (" + httpResponse.getStatusLine().getStatusCode() + ") when executing web-service"; LOGGER.error(errorMsg); throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg)); } return responseBody; } catch (IOException e) { final String errorMsg = "http response error while executing external rest call, error: " + e.getMessage(); LOGGER.error(errorMsg); throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg), e); } } }
public enum Statistic { AUTHENTICATIONS(Type.INCREMENTOR, "Authentications", null), AUTHENTICATION_FAILURES(Type.INCREMENTOR, "AuthenticationFailures", null), AUTHENTICATION_EXPIRED(Type.INCREMENTOR, "Authentications_Expired", null), AUTHENTICATION_PRE_EXPIRED(Type.INCREMENTOR, "Authentications_PreExpired", null), AUTHENTICATION_EXPIRED_WARNING(Type.INCREMENTOR, "Authentications_ExpiredWarning", null), PWM_STARTUPS(Type.INCREMENTOR, "PWM_Startups", null), PWM_UNKNOWN_ERRORS(Type.INCREMENTOR, "PWM_UnknownErrors", null), PASSWORD_CHANGES(Type.INCREMENTOR, "PasswordChanges", null), FORGOTTEN_USERNAME_FAILURES(Type.INCREMENTOR, "ForgottenUsernameFailures", null), FORGOTTEN_USERNAME_SUCCESSES(Type.INCREMENTOR, "ForgottenUsernameSuccesses", null), EMAIL_SEND_SUCCESSES(Type.INCREMENTOR, "EmailSendSuccesses", null), EMAIL_SEND_FAILURES(Type.INCREMENTOR, "EmailSendFailures", null), EMAIL_SEND_DISCARDS(Type.INCREMENTOR, "EmailSendDiscards", null), SMS_SEND_SUCCESSES(Type.INCREMENTOR, "SmsSendSuccesses", null), SMS_SEND_FAILURES(Type.INCREMENTOR, "SmsSendFailures", null), SMS_SEND_DISCARDS(Type.INCREMENTOR, "SmsSendDiscards", null), PASSWORD_RULE_CHECKS(Type.INCREMENTOR, "PasswordRuleChecks", null), HTTP_REQUESTS(Type.INCREMENTOR, "HttpRequests", null), HTTP_RESOURCE_REQUESTS(Type.INCREMENTOR, "HttpResourceRequests", null), HTTP_SESSIONS(Type.INCREMENTOR, "HttpSessions", null), ACTIVATED_USERS(Type.INCREMENTOR, "ActivatedUsers", null), NEW_USERS(Type.INCREMENTOR, "NewUsers", new ConfigSettingDetail(PwmSetting.NEWUSER_ENABLE)), GUESTS(Type.INCREMENTOR, "Guests", new ConfigSettingDetail(PwmSetting.GUEST_ENABLE)), UPDATED_GUESTS( Type.INCREMENTOR, "UpdatedGuests", new ConfigSettingDetail(PwmSetting.GUEST_ENABLE)), LOCKED_USERS(Type.INCREMENTOR, "LockedUsers", null), LOCKED_ADDRESSES(Type.INCREMENTOR, "LockedAddresses", null), LOCKED_USERIDS(Type.INCREMENTOR, "LockedUserDNs", null), LOCKED_ATTRIBUTES(Type.INCREMENTOR, "LockedAttributes", null), LOCKED_TOKENDESTS(Type.INCREMENTOR, "LockedTokenDests", null), CAPTCHA_SUCCESSES(Type.INCREMENTOR, "CaptchaSuccessess", null), CAPTCHA_FAILURES(Type.INCREMENTOR, "CaptchaFailures", null), CAPTCHA_PRESENTATIONS(Type.INCREMENTOR, "CaptchaPresentations", null), LDAP_UNAVAILABLE_COUNT(Type.INCREMENTOR, "LdapUnavailableCount", null), DB_UNAVAILABLE_COUNT(Type.INCREMENTOR, "DatabaseUnavailableCount", null), SETUP_RESPONSES(Type.INCREMENTOR, "SetupResponses", null), SETUP_OTP_SECRET( Type.INCREMENTOR, "SetupOtpSecret", new ConfigSettingDetail(PwmSetting.OTP_ENABLED)), UPDATE_ATTRIBUTES( Type.INCREMENTOR, "UpdateAttributes", new ConfigSettingDetail(PwmSetting.UPDATE_PROFILE_ENABLE)), SHORTCUTS_SELECTED( Type.INCREMENTOR, "ShortcutsSelected", new ConfigSettingDetail(PwmSetting.SHORTCUT_ENABLE)), GENERATED_PASSWORDS(Type.INCREMENTOR, "GeneratedPasswords", null), RECOVERY_SUCCESSES(Type.INCREMENTOR, "RecoverySuccesses", null), RECOVERY_FAILURES(Type.INCREMENTOR, "RecoveryFailures", null), TOKENS_SENT(Type.INCREMENTOR, "TokensSent", null), TOKENS_PASSSED(Type.INCREMENTOR, "TokensPassed", null), RECOVERY_TOKENS_SENT(Type.INCREMENTOR, "RecoveryTokensSent", null), RECOVERY_TOKENS_PASSED(Type.INCREMENTOR, "RecoveryTokensPassed", null), RECOVERY_TOKENS_FAILED(Type.INCREMENTOR, "RecoveryTokensFailed", null), RECOVERY_OTP_PASSED( Type.INCREMENTOR, "RecoveryOTPPassed", new ConfigSettingDetail(PwmSetting.OTP_ENABLED)), RECOVERY_OTP_FAILED( Type.INCREMENTOR, "RecoveryOTPFailed", new ConfigSettingDetail(PwmSetting.OTP_ENABLED)), PEOPLESEARCH_CACHE_HITS( Type.INCREMENTOR, "PeopleSearchCacheHits", new ConfigSettingDetail(PwmSetting.PEOPLE_SEARCH_ENABLE)), PEOPLESEARCH_CACHE_MISSES( Type.INCREMENTOR, "PeopleSearchCacheMisses", new ConfigSettingDetail(PwmSetting.PEOPLE_SEARCH_ENABLE)), PEOPLESEARCH_SEARCHES( Type.INCREMENTOR, "PeopleSearchSearches", new ConfigSettingDetail(PwmSetting.PEOPLE_SEARCH_ENABLE)), PEOPLESEARCH_DETAILS( Type.INCREMENTOR, "PeopleSearchDetails", new ConfigSettingDetail(PwmSetting.PEOPLE_SEARCH_ENABLE)), PEOPLESEARCH_ORGCHART( Type.INCREMENTOR, "PeopleSearchOrgChart", new ConfigSettingDetail(PwmSetting.PEOPLE_SEARCH_ENABLE)), HELPDESK_PASSWORD_SET(Type.INCREMENTOR, "HelpdeskPasswordSet", null), HELPDESK_USER_LOOKUP(Type.INCREMENTOR, "HelpdeskUserLookup", null), HELPDESK_TOKENS_SENT(Type.INCREMENTOR, "HelpdeskTokenSent", null), HELPDESK_UNLOCK(Type.INCREMENTOR, "HelpdeskUnlock", null), HELPDESK_VERIFY_OTP(Type.INCREMENTOR, "HelpdeskVerifyOTP", null), REST_STATUS(Type.INCREMENTOR, "RestStatus", null), REST_CHECKPASSWORD(Type.INCREMENTOR, "RestCheckPassword", null), REST_SETPASSWORD(Type.INCREMENTOR, "RestSetPassword", null), REST_RANDOMPASSWORD(Type.INCREMENTOR, "RestRandomPassword", null), REST_CHALLENGES(Type.INCREMENTOR, "RestChallenges", null), REST_HEALTH(Type.INCREMENTOR, "RestHealth", null), REST_STATISTICS(Type.INCREMENTOR, "RestStatistics", null), REST_VERIFYCHALLENGES(Type.INCREMENTOR, "RestVerifyChallenges", null), INTRUDER_ATTEMPTS(Type.INCREMENTOR, "IntruderAttempts", null), FOREIGN_SESSIONS_ACCEPTED(Type.INCREMENTOR, "ForeignSessionsAccepted", null), AVG_PASSWORD_SYNC_TIME(Type.AVERAGE, "AvgPasswordSyncTime", null), AVG_AUTHENTICATION_TIME(Type.AVERAGE, "AvgAuthenticationTime", null), AVG_PASSWORD_STRENGTH(Type.AVERAGE, "AvgPasswordStrength", null), AVG_LDAP_SEARCH_TIME(Type.AVERAGE, "AvgLdapSearchTime", null), ; private static final PwmLogger LOGGER = PwmLogger.forClass(Statistic.class); private final Type type; private final String key; private final StatDetail statDetail; Statistic(final Type type, final String key, final StatDetail statDetail) { this.type = type; this.key = key; this.statDetail = statDetail; } public String getKey() { return key; } public Type getType() { return type; } public boolean isActive(final PwmApplication pwmApplication) { if (statDetail == null) { return true; } return statDetail.isActive(pwmApplication); } public static SortedSet<Statistic> sortedValues(final Locale locale) { final Comparator<Statistic> comparator = new Comparator<Statistic>() { public int compare(final Statistic o1, final Statistic o2) { return o1.getLabel(locale).compareTo(o2.getLabel(locale)); } }; final TreeSet<Statistic> set = new TreeSet<>(comparator); set.addAll(Arrays.asList(values())); return set; } public enum Type { INCREMENTOR, AVERAGE, } public String getLabel(final Locale locale) { try { final String keyName = "Statistic_Label." + this.getKey(); return LocaleHelper.getLocalizedMessage(locale, keyName, null, Admin.class); } catch (MissingResourceException e) { return "MISSING STATISTIC LABEL for " + this.getKey(); } } public String getDescription(final Locale locale) { final String keyName = "Statistic_Description." + this.getKey(); try { return LocaleHelper.getLocalizedMessage(locale, keyName, null, Admin.class); } catch (Exception e) { LOGGER.error("unable to load localization for " + keyName + ", error: " + e.getMessage()); return "missing localization for " + keyName; } } public enum EpsType { PASSWORD_CHANGES(Statistic.PASSWORD_CHANGES), AUTHENTICATION(Statistic.AUTHENTICATIONS), INTRUDER_ATTEMPTS(Statistic.INTRUDER_ATTEMPTS), PWMDB_WRITES(null), PWMDB_READS(null), DB_WRITES(null), DB_READS(null), ; private Statistic relatedStatistic; EpsType(final Statistic relatedStatistic) { this.relatedStatistic = relatedStatistic; } public Statistic getRelatedStatistic() { return relatedStatistic; } public String getLabel(final Locale locale) { final String keyName = "Statistic_Label." + EpsType.class.getSimpleName() + "_" + this.name(); return LocaleHelper.getLocalizedMessage(locale, keyName, null, Admin.class); } } public enum EpsDuration { MINUTE(TimeDuration.MINUTE), HOUR(TimeDuration.HOUR), DAY(TimeDuration.DAY), ; private final TimeDuration timeDuration; EpsDuration(final TimeDuration timeDuration) { this.timeDuration = timeDuration; } public TimeDuration getTimeDuration() { return timeDuration; } } interface StatDetail { boolean isActive(PwmApplication pwmApplication); } static class ConfigSettingDetail implements StatDetail { private final PwmSetting pwmSetting; ConfigSettingDetail(final PwmSetting pwmSetting) { this.pwmSetting = pwmSetting; } public boolean isActive(final PwmApplication pwmApplication) { return pwmApplication.getConfig().readSettingAsBoolean(pwmSetting); } } }
public class StatisticsManager implements PwmService { private static final PwmLogger LOGGER = PwmLogger.forClass(StatisticsManager.class); private static final int DB_WRITE_FREQUENCY_MS = 60 * 1000; // 1 minutes private static final String DB_KEY_VERSION = "STATS_VERSION"; private static final String DB_KEY_CUMULATIVE = "CUMULATIVE"; private static final String DB_KEY_INITIAL_DAILY_KEY = "INITIAL_DAILY_KEY"; private static final String DB_KEY_PREFIX_DAILY = "DAILY_"; private static final String DB_KEY_TEMP = "TEMP_KEY"; private static final String DB_VALUE_VERSION = "1"; public static final String KEY_CURRENT = "CURRENT"; public static final String KEY_CUMULATIVE = "CUMULATIVE"; public static final String KEY_CLOUD_PUBLISH_TIMESTAMP = "CLOUD_PUB_TIMESTAMP"; private LocalDB localDB; private DailyKey currentDailyKey = new DailyKey(new Date()); private DailyKey initialDailyKey = new DailyKey(new Date()); private Timer daemonTimer; private final StatisticsBundle statsCurrent = new StatisticsBundle(); private StatisticsBundle statsDaily = new StatisticsBundle(); private StatisticsBundle statsCummulative = new StatisticsBundle(); private Map<String, EventRateMeter> epsMeterMap = new HashMap<>(); private PwmApplication pwmApplication; private STATUS status = STATUS.NEW; private final Map<String, StatisticsBundle> cachedStoredStats = new LinkedHashMap<String, StatisticsBundle>() { @Override protected boolean removeEldestEntry(final Map.Entry<String, StatisticsBundle> eldest) { return this.size() > 50; } }; public StatisticsManager() {} public synchronized void incrementValue(final Statistic statistic) { statsCurrent.incrementValue(statistic); statsDaily.incrementValue(statistic); statsCummulative.incrementValue(statistic); } public synchronized void updateAverageValue(final Statistic statistic, final long value) { statsCurrent.updateAverageValue(statistic, value); statsDaily.updateAverageValue(statistic, value); statsCummulative.updateAverageValue(statistic, value); } public Map<String, String> getStatHistory(final Statistic statistic, final int days) { final Map<String, String> returnMap = new LinkedHashMap<>(); DailyKey loopKey = currentDailyKey; int counter = days; while (counter > 0) { final StatisticsBundle bundle = getStatBundleForKey(loopKey.toString()); if (bundle != null) { final String key = (new SimpleDateFormat("MMM dd")).format(loopKey.calendar().getTime()); final String value = bundle.getStatistic(statistic); returnMap.put(key, value); } loopKey = loopKey.previous(); counter--; } return returnMap; } public StatisticsBundle getStatBundleForKey(final String key) { if (key == null || key.length() < 1 || KEY_CUMULATIVE.equals(key)) { return statsCummulative; } if (KEY_CURRENT.equals(key)) { return statsCurrent; } if (currentDailyKey.toString().equals(key)) { return statsDaily; } if (cachedStoredStats.containsKey(key)) { return cachedStoredStats.get(key); } if (localDB == null) { return null; } try { final String storedStat = localDB.get(LocalDB.DB.PWM_STATS, key); final StatisticsBundle returnBundle; if (storedStat != null && storedStat.length() > 0) { returnBundle = StatisticsBundle.input(storedStat); } else { returnBundle = new StatisticsBundle(); } cachedStoredStats.put(key, returnBundle); return returnBundle; } catch (LocalDBException e) { LOGGER.error("error retrieving stored stat for " + key + ": " + e.getMessage()); } return null; } public Map<DailyKey, String> getAvailableKeys(final Locale locale) { final DateFormat dateFormatter = SimpleDateFormat.getDateInstance(SimpleDateFormat.DEFAULT, locale); final Map<DailyKey, String> returnMap = new LinkedHashMap<DailyKey, String>(); // add current time; returnMap.put(currentDailyKey, dateFormatter.format(new Date())); // if now historical data then we're done if (currentDailyKey.equals(initialDailyKey)) { return returnMap; } DailyKey loopKey = currentDailyKey; int safetyCounter = 0; while (!loopKey.equals(initialDailyKey) && safetyCounter < 5000) { final Calendar c = loopKey.calendar(); final String display = dateFormatter.format(c.getTime()); returnMap.put(loopKey, display); loopKey = loopKey.previous(); safetyCounter++; } return returnMap; } public String toString() { final StringBuilder sb = new StringBuilder(); for (final Statistic m : Statistic.values()) { sb.append(m.toString()); sb.append("="); sb.append(statsCurrent.getStatistic(m)); sb.append(", "); } if (sb.length() > 2) { sb.delete(sb.length() - 2, sb.length()); } return sb.toString(); } public void init(PwmApplication pwmApplication) throws PwmException { for (final Statistic.EpsType type : Statistic.EpsType.values()) { for (final Statistic.EpsDuration duration : Statistic.EpsDuration.values()) { epsMeterMap.put( type.toString() + duration.toString(), new EventRateMeter(duration.getTimeDuration())); } } status = STATUS.OPENING; this.localDB = pwmApplication.getLocalDB(); this.pwmApplication = pwmApplication; if (localDB == null) { LOGGER.error("LocalDB is not available, will remain closed"); status = STATUS.CLOSED; return; } { final String storedCummulativeBundleStr = localDB.get(LocalDB.DB.PWM_STATS, DB_KEY_CUMULATIVE); if (storedCummulativeBundleStr != null && storedCummulativeBundleStr.length() > 0) { statsCummulative = StatisticsBundle.input(storedCummulativeBundleStr); } } { for (final Statistic.EpsType loopEpsType : Statistic.EpsType.values()) { for (final Statistic.EpsType loopEpsDuration : Statistic.EpsType.values()) { final String key = "EPS-" + loopEpsType.toString() + loopEpsDuration.toString(); final String storedValue = localDB.get(LocalDB.DB.PWM_STATS, key); if (storedValue != null && storedValue.length() > 0) { try { final EventRateMeter eventRateMeter = JsonUtil.deserialize(storedValue, EventRateMeter.class); epsMeterMap.put(loopEpsType.toString() + loopEpsDuration.toString(), eventRateMeter); } catch (Exception e) { LOGGER.error( "unexpected error reading last EPS rate for " + loopEpsType + " from LocalDB: " + e.getMessage()); } } } } } { final String storedInitialString = localDB.get(LocalDB.DB.PWM_STATS, DB_KEY_INITIAL_DAILY_KEY); if (storedInitialString != null && storedInitialString.length() > 0) { initialDailyKey = new DailyKey(storedInitialString); } } { currentDailyKey = new DailyKey(new Date()); final String storedDailyStr = localDB.get(LocalDB.DB.PWM_STATS, currentDailyKey.toString()); if (storedDailyStr != null && storedDailyStr.length() > 0) { statsDaily = StatisticsBundle.input(storedDailyStr); } } try { localDB.put( LocalDB.DB.PWM_STATS, DB_KEY_TEMP, PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date())); } catch (IllegalStateException e) { LOGGER.error("unable to write to localDB, will remain closed, error: " + e.getMessage()); status = STATUS.CLOSED; return; } localDB.put(LocalDB.DB.PWM_STATS, DB_KEY_VERSION, DB_VALUE_VERSION); localDB.put(LocalDB.DB.PWM_STATS, DB_KEY_INITIAL_DAILY_KEY, initialDailyKey.toString()); { // setup a timer to roll over at 0 Zula and one to write current stats every 10 seconds final String threadName = Helper.makeThreadName(pwmApplication, this.getClass()) + " timer"; daemonTimer = new Timer(threadName, true); daemonTimer.schedule(new FlushTask(), 10 * 1000, DB_WRITE_FREQUENCY_MS); daemonTimer.schedule(new NightlyTask(), Helper.nextZuluZeroTime()); } if (pwmApplication.getApplicationMode() == PwmApplication.MODE.RUNNING) { if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PUBLISH_STATS_ENABLE)) { long lastPublishTimestamp = pwmApplication.getInstallTime().getTime(); { final String lastPublishDateStr = localDB.get(LocalDB.DB.PWM_STATS, KEY_CLOUD_PUBLISH_TIMESTAMP); if (lastPublishDateStr != null && lastPublishDateStr.length() > 0) { try { lastPublishTimestamp = Long.parseLong(lastPublishDateStr); } catch (Exception e) { LOGGER.error( "unexpected error reading last publish timestamp from PwmDB: " + e.getMessage()); } } } final Date nextPublishTime = new Date( lastPublishTimestamp + PwmConstants.STATISTICS_PUBLISH_FREQUENCY_MS + (long) PwmRandom.getInstance().nextInt(3600 * 1000)); daemonTimer.schedule( new PublishTask(), nextPublishTime, PwmConstants.STATISTICS_PUBLISH_FREQUENCY_MS); } } status = STATUS.OPEN; } private void writeDbValues() { if (localDB != null) { try { localDB.put(LocalDB.DB.PWM_STATS, DB_KEY_CUMULATIVE, statsCummulative.output()); localDB.put(LocalDB.DB.PWM_STATS, currentDailyKey.toString(), statsDaily.output()); for (final Statistic.EpsType loopEpsType : Statistic.EpsType.values()) { for (final Statistic.EpsDuration loopEpsDuration : Statistic.EpsDuration.values()) { final String key = "EPS-" + loopEpsType.toString(); final String mapKey = loopEpsType.toString() + loopEpsDuration.toString(); final String value = JsonUtil.serialize(this.epsMeterMap.get(mapKey)); localDB.put(LocalDB.DB.PWM_STATS, key, value); } } } catch (LocalDBException e) { LOGGER.error("error outputting pwm statistics: " + e.getMessage()); } } } private void resetDailyStats() { try { final Map<String, String> emailValues = new LinkedHashMap<>(); for (final Statistic statistic : Statistic.values()) { final String key = statistic.getLabel(PwmConstants.DEFAULT_LOCALE); final String value = statsDaily.getStatistic(statistic); emailValues.put(key, value); } AlertHandler.alertDailyStats(pwmApplication, emailValues); } catch (Exception e) { LOGGER.error("error while generating daily alert statistics: " + e.getMessage()); } currentDailyKey = new DailyKey(new Date()); statsDaily = new StatisticsBundle(); LOGGER.debug("reset daily statistics"); } public STATUS status() { return status; } public void close() { try { writeDbValues(); } catch (Exception e) { LOGGER.error("unexpected error closing: " + e.getMessage()); } if (daemonTimer != null) { daemonTimer.cancel(); } status = STATUS.CLOSED; } public List<HealthRecord> healthCheck() { return Collections.emptyList(); } private class NightlyTask extends TimerTask { public void run() { writeDbValues(); resetDailyStats(); daemonTimer.schedule(new NightlyTask(), Helper.nextZuluZeroTime()); } } private class FlushTask extends TimerTask { public void run() { writeDbValues(); } } private class PublishTask extends TimerTask { public void run() { try { publishStatisticsToCloud(); } catch (Exception e) { LOGGER.error("error publishing statistics to cloud: " + e.getMessage()); } } } public static class DailyKey { int year; int day; public DailyKey(final Date date) { final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Zulu")); calendar.setTime(date); year = calendar.get(Calendar.YEAR); day = calendar.get(Calendar.DAY_OF_YEAR); } public DailyKey(final String value) { final String strippedValue = value.substring(DB_KEY_PREFIX_DAILY.length(), value.length()); final String[] splitValue = strippedValue.split("_"); year = Integer.valueOf(splitValue[0]); day = Integer.valueOf(splitValue[1]); } private DailyKey() {} @Override public String toString() { return DB_KEY_PREFIX_DAILY + String.valueOf(year) + "_" + String.valueOf(day); } public DailyKey previous() { final Calendar calendar = calendar(); calendar.add(Calendar.HOUR, -24); final DailyKey newKey = new DailyKey(); newKey.year = calendar.get(Calendar.YEAR); newKey.day = calendar.get(Calendar.DAY_OF_YEAR); return newKey; } public Calendar calendar() { final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Zulu")); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.DAY_OF_YEAR, day); return calendar; } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final DailyKey key = (DailyKey) o; if (day != key.day) return false; if (year != key.year) return false; return true; } @Override public int hashCode() { int result = year; result = 31 * result + day; return result; } } public void updateEps(final Statistic.EpsType type, final int itemCount) { for (final Statistic.EpsDuration duration : Statistic.EpsDuration.values()) { epsMeterMap.get(type.toString() + duration.toString()).markEvents(itemCount); } } public BigDecimal readEps(final Statistic.EpsType type, final Statistic.EpsDuration duration) { return epsMeterMap.get(type.toString() + duration.toString()).readEventRate(); } private void publishStatisticsToCloud() throws URISyntaxException, IOException, PwmUnrecoverableException { final StatsPublishBean statsPublishData; { final StatisticsBundle bundle = getStatBundleForKey(KEY_CUMULATIVE); final Map<String, String> statData = new HashMap<>(); for (final Statistic loopStat : Statistic.values()) { statData.put(loopStat.getKey(), bundle.getStatistic(loopStat)); } final Configuration config = pwmApplication.getConfig(); final List<String> configuredSettings = new ArrayList<>(); for (final PwmSetting pwmSetting : config.nonDefaultSettings()) { if (!pwmSetting.getCategory().hasProfiles() && !config.isDefaultValue(pwmSetting)) { configuredSettings.add(pwmSetting.getKey()); } } final Map<String, String> otherData = new HashMap<>(); otherData.put( StatsPublishBean.KEYS.SITE_URL.toString(), config.readSettingAsString(PwmSetting.PWM_SITE_URL)); otherData.put( StatsPublishBean.KEYS.SITE_DESCRIPTION.toString(), config.readSettingAsString(PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION)); otherData.put( StatsPublishBean.KEYS.INSTALL_DATE.toString(), PwmConstants.DEFAULT_DATETIME_FORMAT.format(pwmApplication.getInstallTime())); try { otherData.put( StatsPublishBean.KEYS.LDAP_VENDOR.toString(), pwmApplication .getProxyChaiProvider(config.getDefaultLdapProfile().getIdentifier()) .getDirectoryVendor() .toString()); } catch (Exception e) { LOGGER.trace("unable to read ldap vendor type for stats publication: " + e.getMessage()); } statsPublishData = new StatsPublishBean( pwmApplication.getInstanceID(), new Date(), statData, configuredSettings, PwmConstants.BUILD_NUMBER, PwmConstants.BUILD_VERSION, otherData); } final URI requestURI = new URI(PwmConstants.PWM_URL_CLOUD + "/rest/pwm/statistics"); final HttpPost httpPost = new HttpPost(requestURI.toString()); final String jsonDataString = JsonUtil.serialize(statsPublishData); httpPost.setEntity(new StringEntity(jsonDataString)); httpPost.setHeader("Accept", PwmConstants.AcceptValue.json.getHeaderValue()); httpPost.setHeader("Content-Type", PwmConstants.ContentTypeValue.json.getHeaderValue()); LOGGER.debug( "preparing to send anonymous statistics to " + requestURI.toString() + ", data to send: " + jsonDataString); final HttpResponse httpResponse = PwmHttpClient.getHttpClient(pwmApplication.getConfig()).execute(httpPost); if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new IOException( "http response error code: " + httpResponse.getStatusLine().getStatusCode()); } LOGGER.info("published anonymous statistics to " + requestURI.toString()); try { localDB.put( LocalDB.DB.PWM_STATS, KEY_CLOUD_PUBLISH_TIMESTAMP, String.valueOf(System.currentTimeMillis())); } catch (LocalDBException e) { LOGGER.error( "unexpected error trying to save last statistics published time to LocalDB: " + e.getMessage()); } } public int outputStatsToCsv( final OutputStream outputStream, final Locale locale, final boolean includeHeader) throws IOException { LOGGER.trace("beginning output stats to csv process"); final Date startTime = new Date(); final StatisticsManager statsManger = pwmApplication.getStatisticsManager(); final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream); if (includeHeader) { final List<String> headers = new ArrayList<>(); headers.add("KEY"); headers.add("YEAR"); headers.add("DAY"); for (Statistic stat : Statistic.values()) { headers.add(stat.getLabel(locale)); } csvPrinter.printRecord(headers); } int counter = 0; final Map<StatisticsManager.DailyKey, String> keys = statsManger.getAvailableKeys(PwmConstants.DEFAULT_LOCALE); for (final StatisticsManager.DailyKey loopKey : keys.keySet()) { counter++; final StatisticsBundle bundle = statsManger.getStatBundleForKey(loopKey.toString()); final List<String> lineOutput = new ArrayList<>(); lineOutput.add(loopKey.toString()); lineOutput.add(String.valueOf(loopKey.year)); lineOutput.add(String.valueOf(loopKey.day)); for (final Statistic stat : Statistic.values()) { lineOutput.add(bundle.getStatistic(stat)); } csvPrinter.printRecord(lineOutput); } csvPrinter.flush(); LOGGER.trace( "completed output stats to csv process; output " + counter + " records in " + TimeDuration.fromCurrent(startTime).asCompactString()); return counter; } public ServiceInfo serviceInfo() { if (status() == STATUS.OPEN) { return new ServiceInfo(Collections.singletonList(DataStorageMethod.LOCALDB)); } else { return new ServiceInfo(Collections.<DataStorageMethod>emptyList()); } } public static void incrementStat(final PwmRequest pwmRequest, final Statistic statistic) { incrementStat(pwmRequest.getPwmApplication(), statistic); } public static void incrementStat(final PwmApplication pwmApplication, final Statistic statistic) { if (pwmApplication == null) { LOGGER.error( "skipping requested statistic increment of " + statistic + " due to null pwmApplication"); return; } final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager(); if (statisticsManager == null) { LOGGER.error( "skipping requested statistic increment of " + statistic + " due to null statisticsManager"); return; } if (statisticsManager.status() != STATUS.OPEN) { LOGGER.trace( "skipping requested statistic increment of " + statistic + " due to StatisticsManager being closed"); return; } statisticsManager.incrementValue(statistic); } }
public class SetupOtpBean extends PwmSessionBean { private static final PwmLogger LOGGER = PwmLogger.forClass(SetupOtpBean.class); private OTPUserRecord otpUserRecord; private boolean confirmed; private boolean codeSeen; private boolean written; private List<String> recoveryCodes; private Long challenge; // for HOTP only private boolean hasPreExistingOtp; public SetupOtpBean() {} public OTPUserRecord getOtpUserRecord() { return otpUserRecord; } public boolean isHasPreExistingOtp() { return hasPreExistingOtp; } public void setHasPreExistingOtp(final boolean hasPreExistingOtp) { this.hasPreExistingOtp = hasPreExistingOtp; } public void setOtpUserRecord(final OTPUserRecord otp) { this.otpUserRecord = otp; } public boolean isConfirmed() { return confirmed; } public void setConfirmed(final boolean confirmed) { this.confirmed = confirmed; } public Long getChallenge() { if (challenge == null) { SecureRandom random; try { random = SecureRandom.getInstance("SHA1PRNG", "SUN"); } catch (NoSuchAlgorithmException ex) { random = new SecureRandom(); LOGGER.error(ex.getMessage(), ex); } catch (NoSuchProviderException ex) { random = new SecureRandom(); LOGGER.error(ex.getMessage(), ex); } random.setSeed((new Date()).getTime()); challenge = random.nextLong() % (10 ^ 6); } return challenge; } public void setChallenge(final Long challenge) { this.challenge = challenge; } public List<String> getRecoveryCodes() { return recoveryCodes; } public void setRecoveryCodes(final List<String> recoveryCodes) { this.recoveryCodes = recoveryCodes; } public boolean isCodeSeen() { return codeSeen; } public void setCodeSeen(final boolean codeSeen) { this.codeSeen = codeSeen; } public boolean isWritten() { return written; } public void setWritten(final boolean written) { this.written = written; } public Type getType() { return Type.AUTHENTICATED; } @Override public Set<SessionBeanMode> supportedModes() { return Collections.unmodifiableSet( new HashSet<>(Arrays.asList(SessionBeanMode.LOCAL, SessionBeanMode.CRYPTCOOKIE))); } }
/** @author mpieters */ public class DbOtpOperator extends AbstractOtpOperator { private static final PwmLogger LOGGER = PwmLogger.forClass(DbOtpOperator.class); public DbOtpOperator(PwmApplication pwmApplication) { super.setPwmApplication(pwmApplication); } @Override public OTPUserRecord readOtpUserConfiguration(UserIdentity theUser, String userGUID) throws PwmUnrecoverableException { LOGGER.trace(String.format("Enter: readOtpUserConfiguration(%s, %s)", theUser, userGUID)); if (userGUID == null || userGUID.length() < 1) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot save otp to db, user does not have a GUID")); } OTPUserRecord otpConfig = null; try { final DatabaseAccessorImpl databaseAccessor = pwmApplication.getDatabaseAccessor(); String value = databaseAccessor.get(DatabaseTable.OTP, userGUID); if (value != null && value.length() > 0) { if (getPwmApplication().getConfig().readSettingAsBoolean(PwmSetting.OTP_SECRET_ENCRYPT)) { value = decryptAttributeValue(value); } if (value != null) { otpConfig = decomposeOtpAttribute(value); } if (otpConfig != null) { LOGGER.debug("found user OTP secret in db: " + otpConfig.toString()); } } } catch (LocalDBException e) { final String errorMsg = "unexpected LocalDB error reading responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); throw new PwmUnrecoverableException(errorInformation); } catch (PwmOperationalException e) { final String errorMsg = "unexpected error reading responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); throw new PwmUnrecoverableException(errorInformation); } return otpConfig; } @Override public void writeOtpUserConfiguration( final PwmSession pwmSession, final UserIdentity theUser, final String userGUID, final OTPUserRecord otpConfig) throws PwmUnrecoverableException { if (userGUID == null || userGUID.length() < 1) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot save OTP secret to remote database, user " + theUser + " does not have a guid")); } LOGGER.trace( "attempting to save OTP secret for " + theUser + " in remote database (key=" + userGUID + ")"); try { String value = composeOtpAttribute(otpConfig); if (getPwmApplication().getConfig().readSettingAsBoolean(PwmSetting.OTP_SECRET_ENCRYPT)) { LOGGER.debug("Encrypting OTP secret for storage"); value = encryptAttributeValue(value); } final DatabaseAccessorImpl databaseAccessor = pwmApplication.getDatabaseAccessor(); databaseAccessor.put(DatabaseTable.OTP, userGUID, value); LOGGER.info("saved OTP secret for " + theUser + " in remote database (key=" + userGUID + ")"); } catch (PwmOperationalException ex) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, "unexpected error saving otp to db: " + ex.getMessage()); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo); pwmOE.initCause(ex); throw pwmOE; } } @Override public void clearOtpUserConfiguration( final PwmSession pwmSession, final UserIdentity theUser, final String userGUID) throws PwmUnrecoverableException { if (userGUID == null || userGUID.length() < 1) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot save OTP secret to remote database, user " + theUser + " does not have a guid")); } LOGGER.trace( "attempting to clear OTP secret for " + theUser + " in remote database (key=" + userGUID + ")"); try { final DatabaseAccessorImpl databaseAccessor = pwmApplication.getDatabaseAccessor(); databaseAccessor.remove(DatabaseTable.OTP, userGUID); LOGGER.info( "cleared OTP secret for " + theUser + " in remote database (key=" + userGUID + ")"); } catch (DatabaseException ex) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, "unexpected error saving otp to db: " + ex.getMessage()); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo); pwmOE.initCause(ex); throw pwmOE; } } @Override public void close() {} }
public class H2_LocalDB extends AbstractJDBC_LocalDB { private static final PwmLogger LOGGER = PwmLogger.forClass(H2_LocalDB.class, true); private static final String H2_CLASSPATH = "org.h2.Driver"; private static final Map<String, String> DEFAULT_INIT_PARAMS; static { final Map<String, String> defaultInitParams = new HashMap<>(); defaultInitParams.put("DB_CLOSE_ON_EXIT", "FALSE"); defaultInitParams.put("COMPRESS", "TRUE"); // defaultInitParams.put("TRACE_LEVEL_FILE","2"); DEFAULT_INIT_PARAMS = Collections.unmodifiableMap(defaultInitParams); } private Driver driver; H2_LocalDB() throws Exception { super(); } @Override String getDriverClasspath() { return H2_CLASSPATH; } @Override void closeConnection(final Connection connection) throws SQLException { if (aggressiveCompact) { CallableStatement statement = null; try { LOCK.writeLock().lock(); final java.util.Date start = new java.util.Date(); LOGGER.trace("beginning shutdown compact"); statement = dbConnection.prepareCall("SHUTDOWN COMPACT"); statement.execute(); LOGGER.trace( "completed shutdown compact in " + TimeDuration.fromCurrent(start).asCompactString()); } catch (SQLException ex) { LOGGER.error("error during shutdown compact: " + ex.getMessage()); } finally { close(statement); LOCK.writeLock().unlock(); } } try { connection.close(); if (driver != null) { DriverManager.deregisterDriver(driver); driver = null; } } catch (Exception e) { LOGGER.error("error during H2 shutdown: " + e.getMessage()); } } @Override Connection openConnection( final File databaseDirectory, final String driverClasspath, final Map<String, String> initParams) throws LocalDBException { final String filePath = databaseDirectory.getAbsolutePath() + File.separator + "localdb-h2"; final String connectionString = "jdbc:h2:split:" + filePath + ";" + makeInitStringParams(initParams); try { driver = (Driver) Class.forName(H2_CLASSPATH).newInstance(); final Properties connectionProps = new Properties(); final Connection connection = driver.connect(connectionString, connectionProps); connection.setAutoCommit(true); return connection; } catch (Throwable e) { final String errorMsg = "error opening DB: " + e.getMessage(); LOGGER.error(errorMsg, e); throw new LocalDBException( new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE, errorMsg)); } } private static String makeInitStringParams(final Map<String, String> initParams) { final Map<String, String> params = new HashMap<>(); params.putAll(DEFAULT_INIT_PARAMS); params.putAll(initParams); return StringUtil.mapToString(params, "=", ";"); } }
public class UserCacheService implements PwmService { private static final PwmLogger LOGGER = PwmLogger.forClass(UserCacheService.class); private CacheStoreWrapper cacheStore; private STATUS status; public STATUS status() { return status; } public UserCacheRecord updateUserCache(final UserInfoBean userInfoBean) throws PwmUnrecoverableException { final StorageKey storageKey = StorageKey.fromUserInfoBean(userInfoBean); 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 { final UserCacheRecord userCacheRecord = cacheStore.read(storageKey); return userCacheRecord; } public boolean removeStorageKey(final StorageKey storageKey) throws LocalDBException { return cacheStore.remove(storageKey); } public void store(UserCacheRecord userCacheRecord) throws LocalDBException, PwmUnrecoverableException { final StorageKey storageKey = StorageKey.fromUserGUID(userCacheRecord.getUserGUID()); cacheStore.write(storageKey, userCacheRecord); } public void clear() throws LocalDBException { cacheStore.clear(); } public ClosableIterator<StorageKey> iterator() { try { return new UserStatusCacheBeanIterator(); } catch (LocalDBException e) { LOGGER.error("unexpected error generating user status iterator: " + e.getMessage()); return null; } } private class UserStatusCacheBeanIterator 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.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() throws LocalDBException { return cacheStore.size(); } public static class StorageKey { private String key; private StorageKey(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) throws PwmUnrecoverableException { final String userGUID = userInfoBean.getUserGuid(); return fromUserGUID(userGUID); } 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); } private static StorageKey fromUserGUID(final String userGUID) throws PwmUnrecoverableException { return new StorageKey(SecureHelper.md5sum(userGUID)); } } private static class CacheStoreWrapper { private static final LocalDB.DB DB = LocalDB.DB.USER_CACHE; private final LocalDB localDB; private CacheStoreWrapper(LocalDB localDB) { this.localDB = localDB; } private void write(StorageKey key, UserCacheRecord cacheBean) throws LocalDBException { final String jsonValue = JsonUtil.serialize(cacheBean); localDB.put(DB, key.getKey(), jsonValue); } private UserCacheRecord read(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(StorageKey key) throws LocalDBException { return localDB.remove(DB, key.getKey()); } private void clear() throws LocalDBException { localDB.truncate(DB); } private int size() throws LocalDBException { return localDB.size(DB); } } }
/** * A repository for objects common to the servlet context. A singleton of this object is stored in * the servlet context. * * @author Jason D. Rivard */ public class PwmApplication { // ------------------------------ FIELDS ------------------------------ // ----------------------------- CONSTANTS ---------------------------- private static final PwmLogger LOGGER = PwmLogger.forClass(PwmApplication.class); private static final String DEFAULT_INSTANCE_ID = "-1"; public enum AppAttribute { INSTANCE_ID("context_instanceID"), INSTALL_DATE("DB_KEY_INSTALL_DATE"), CONFIG_HASH("configurationSettingHash"), LAST_LDAP_ERROR("lastLdapError"), TOKEN_COUNTER("tokenCounter"), REPORT_STATUS("reporting.status"), REPORT_CLEAN_FLAG("reporting.cleanFlag"), SMS_ITEM_COUNTER("smsQueue.itemCount"), EMAIL_ITEM_COUNTER("itemQueue.itemCount"), LOCALDB_IMPORT_STATUS("localDB.import.status"), ; private String key; AppAttribute(String key) { this.key = key; } public String getKey() { return key; } } private String instanceID = DEFAULT_INSTANCE_ID; private final Configuration configuration; private LocalDB localDB; private LocalDBLogger localDBLogger; private final Map<Class<? extends PwmService>, PwmService> pwmServices = new LinkedHashMap<>(); private final Date startupTime = new Date(); private Date installTime = new Date(); private ErrorInformation lastLocalDBFailure = null; private final File applicationPath; private final File webInfPath; private final File configurationFile; private MODE applicationMode; private static final List<Class<? extends PwmService>> PWM_SERVICE_CLASSES = Collections.unmodifiableList( Arrays.asList( SecureService.class, LdapConnectionService.class, DatabaseAccessorImpl.class, SharedHistoryManager.class, HealthMonitor.class, AuditManager.class, StatisticsManager.class, WordlistManager.class, SeedlistManager.class, EmailQueueManager.class, SmsQueueManager.class, UrlShortenerService.class, TokenService.class, VersionChecker.class, IntruderManager.class, ReportService.class, CrService.class, OtpService.class, CacheService.class)); private PwmApplication(final PwmEnvironment pwmEnvironment) throws PwmUnrecoverableException { verifyIfApplicationPathIsSetProperly(pwmEnvironment); this.configuration = pwmEnvironment.config; this.applicationMode = pwmEnvironment.applicationMode; this.applicationPath = pwmEnvironment.applicationPath; this.configurationFile = pwmEnvironment.configurationFile; this.webInfPath = pwmEnvironment.webInfPath; try { initialize(pwmEnvironment.initLogging); } catch (PwmUnrecoverableException e) { LOGGER.fatal(e.getMessage()); throw e; } } private void initialize(final boolean initLogging) throws PwmUnrecoverableException { final Date startTime = new Date(); // initialize log4j if (initLogging) { final String log4jFileName = configuration.readSettingAsString(PwmSetting.EVENTS_JAVA_LOG4JCONFIG_FILE); final File log4jFile = Helper.figureFilepath(log4jFileName, applicationPath); final String consoleLevel, fileLevel; switch (getApplicationMode()) { case ERROR: case NEW: consoleLevel = PwmLogLevel.TRACE.toString(); fileLevel = PwmLogLevel.TRACE.toString(); break; default: consoleLevel = configuration.readSettingAsString(PwmSetting.EVENTS_JAVA_STDOUT_LEVEL); fileLevel = configuration.readSettingAsString(PwmSetting.EVENTS_FILE_LEVEL); break; } PwmLogManager.initializeLogger( this, configuration, log4jFile, consoleLevel, applicationPath, fileLevel); switch (getApplicationMode()) { case RUNNING: break; case ERROR: LOGGER.fatal( "starting up in ERROR mode! Check log or health check information for cause"); break; default: LOGGER.trace( "setting log level to TRACE because application mode is " + getApplicationMode()); break; } } LOGGER.info( "initializing, application mode=" + getApplicationMode() + ", applicationPath=" + (applicationPath == null ? "null" : applicationPath.getAbsolutePath()) + ", configurationFile=" + (configurationFile == null ? "null" : configurationFile.getAbsolutePath())); this.localDB = Initializer.initializeLocalDB(this); this.localDBLogger = PwmLogManager.initializeLocalDBLogger(this); // log the loaded configuration LOGGER.info("configuration load completed"); // read the pwm servlet instance id instanceID = fetchInstanceID(localDB, this); LOGGER.info("using '" + getInstanceID() + "' for instance's ID (instanceID)"); // read the pwm installation date installTime = fetchInstallDate(startupTime); LOGGER.debug( "this application instance first installed on " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(installTime)); initServices(); final TimeDuration totalTime = TimeDuration.fromCurrent(startTime); LOGGER.info( PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " open for bidness! (" + totalTime.asCompactString() + ")"); StatisticsManager.incrementStat(this, Statistic.PWM_STARTUPS); LOGGER.debug( "buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE); final Thread postInitThread = new Thread() { @Override public void run() { postInitTasks(); } }; postInitThread.setDaemon(true); postInitThread.setName(Helper.makeThreadName(this, PwmApplication.class)); postInitThread.start(); } private void initServices() throws PwmUnrecoverableException { for (final Class<? extends PwmService> serviceClass : PWM_SERVICE_CLASSES) { final Date startTime = new Date(); final PwmService newServiceInstance; try { final Object newInstance = serviceClass.newInstance(); newServiceInstance = (PwmService) newInstance; } catch (Exception e) { final String errorMsg = "unexpected error instantiating service class '" + serviceClass.getName() + "', error: " + e.toString(); LOGGER.fatal(errorMsg, e); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg)); } try { LOGGER.debug("initializing service " + serviceClass.getName()); newServiceInstance.init(this); LOGGER.debug( "completed initialization of service " + serviceClass.getName() + " in " + TimeDuration.fromCurrent(startTime).asCompactString() + ", status=" + newServiceInstance.status()); } catch (PwmException e) { LOGGER.warn( "error instantiating service class '" + serviceClass.getName() + "', service will remain unavailable, error: " + e.getMessage()); } catch (Exception e) { String errorMsg = "unexpected error instantiating service class '" + serviceClass.getName() + "', cannot load, error: " + e.getMessage(); if (e.getCause() != null) { errorMsg += ", cause: " + e.getCause(); } LOGGER.fatal(errorMsg); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg)); } pwmServices.put(serviceClass, newServiceInstance); } } private void postInitTasks() { final Date startTime = new Date(); LOGGER.debug("loaded configuration: \n" + configuration.toDebugString()); // detect if config has been modified since previous startup try { final String previousHash = readAppAttribute(AppAttribute.CONFIG_HASH); final String currentHash = configuration.configurationHash(); if (previousHash == null || !previousHash.equals(currentHash)) { writeAppAttribute(AppAttribute.CONFIG_HASH, currentHash); LOGGER.warn( "configuration checksum does not match previously seen checksum, configuration has been modified since last startup"); if (this.getAuditManager() != null) { final String modifyMessage = "configuration was modified directly (not using ConfigEditor UI)"; this.getAuditManager() .submit( SystemAuditRecord.create( AuditEvent.MODIFY_CONFIGURATION, modifyMessage, this.getInstanceID())); } } } catch (Exception e) { LOGGER.debug( "unable to detect if configuration has been modified since previous startup: " + e.getMessage()); } if (this.getConfig() != null) { final Map<AppProperty, String> nonDefaultProperties = getConfig().readAllNonDefaultAppProperties(); if (nonDefaultProperties != null && !nonDefaultProperties.isEmpty()) { final Map<String, String> tempMap = new LinkedHashMap<>(); for (final AppProperty loopProperty : nonDefaultProperties.keySet()) { tempMap.put(loopProperty.getKey(), nonDefaultProperties.get(loopProperty)); } LOGGER.trace( "non-default app properties read from configuration: " + JsonUtil.serializeMap(tempMap)); } else { LOGGER.trace("no non-default app properties in configuration"); } } // send system audit event final SystemAuditRecord auditRecord = SystemAuditRecord.create(AuditEvent.STARTUP, null, getInstanceID()); try { getAuditManager().submit(auditRecord); } catch (PwmException e) { LOGGER.warn("unable to submit alert event " + JsonUtil.serialize(auditRecord)); } try { Map<PwmAboutProperty, String> infoMap = Helper.makeInfoBean(this); LOGGER.trace("application info: " + JsonUtil.serializeMap(infoMap)); } catch (Exception e) { LOGGER.error("error generating about application bean: " + e.getMessage()); } try { this.getIntruderManager() .clear(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME); } catch (Exception e) { LOGGER.debug( "error while clearing configmanager-intruder-username from intruder table: " + e.getMessage()); } LOGGER.trace( "completed post init tasks in " + TimeDuration.fromCurrent(startTime).asCompactString()); } public String getInstanceID() { return instanceID; } public SharedHistoryManager getSharedHistoryManager() { return (SharedHistoryManager) pwmServices.get(SharedHistoryManager.class); } public IntruderManager getIntruderManager() { return (IntruderManager) pwmServices.get(IntruderManager.class); } public ChaiUser getProxiedChaiUser(final UserIdentity userIdentity) throws PwmUnrecoverableException { try { final ChaiProvider proxiedProvider = getProxyChaiProvider(userIdentity.getLdapProfileID()); return ChaiFactory.createChaiUser(userIdentity.getUserDN(), proxiedProvider); } catch (ChaiUnavailableException e) { throw PwmUnrecoverableException.fromChaiException(e); } } public ChaiProvider getProxyChaiProvider(final String identifier) throws PwmUnrecoverableException { return getLdapConnectionService().getProxyChaiProvider(identifier); } public LocalDBLogger getLocalDBLogger() { return localDBLogger; } public HealthMonitor getHealthMonitor() { return (HealthMonitor) pwmServices.get(HealthMonitor.class); } public List<PwmService> getPwmServices() { final List<PwmService> pwmServices = new ArrayList<>(); pwmServices.add(this.localDBLogger); pwmServices.addAll(this.pwmServices.values()); pwmServices.remove(null); return Collections.unmodifiableList(pwmServices); } public WordlistManager getWordlistManager() { return (WordlistManager) pwmServices.get(WordlistManager.class); } public SeedlistManager getSeedlistManager() { return (SeedlistManager) pwmServices.get(SeedlistManager.class); } public ReportService getUserReportService() { return (ReportService) pwmServices.get(ReportService.class); } public EmailQueueManager getEmailQueue() { return (EmailQueueManager) pwmServices.get(EmailQueueManager.class); } public AuditManager getAuditManager() { return (AuditManager) pwmServices.get(AuditManager.class); } public SmsQueueManager getSmsQueue() { return (SmsQueueManager) pwmServices.get(SmsQueueManager.class); } public UrlShortenerService getUrlShortener() { return (UrlShortenerService) pwmServices.get(UrlShortenerService.class); } public VersionChecker getVersionChecker() { return (VersionChecker) pwmServices.get(VersionChecker.class); } public ErrorInformation getLastLocalDBFailure() { return lastLocalDBFailure; } public TokenService getTokenService() { return (TokenService) pwmServices.get(TokenService.class); } public LdapConnectionService getLdapConnectionService() { return (LdapConnectionService) pwmServices.get(LdapConnectionService.class); } public Configuration getConfig() { if (configuration == null) { return null; } return configuration; } public MODE getApplicationMode() { return applicationMode; } public synchronized DatabaseAccessorImpl getDatabaseAccessor() { return (DatabaseAccessorImpl) pwmServices.get(DatabaseAccessorImpl.class); } private Date fetchInstallDate(final Date startupTime) { if (localDB != null) { try { final String storedDateStr = readAppAttribute(AppAttribute.INSTALL_DATE); if (storedDateStr == null || storedDateStr.length() < 1) { writeAppAttribute(AppAttribute.INSTALL_DATE, String.valueOf(startupTime.getTime())); } else { return new Date(Long.parseLong(storedDateStr)); } } catch (Exception e) { LOGGER.error("error retrieving installation date from localDB: " + e.getMessage()); } } return new Date(); } private String fetchInstanceID(final LocalDB localDB, final PwmApplication pwmApplication) { String newInstanceID = pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_INSTANCE_NAME); if (newInstanceID != null && newInstanceID.trim().length() > 0) { return newInstanceID; } newInstanceID = readAppAttribute(AppAttribute.INSTANCE_ID); if (newInstanceID == null || newInstanceID.length() < 1) { newInstanceID = Long.toHexString(PwmRandom.getInstance().nextLong()).toUpperCase(); LOGGER.info("generated new random instanceID " + newInstanceID); if (localDB != null) { writeAppAttribute(AppAttribute.INSTANCE_ID, newInstanceID); } } else { LOGGER.trace("retrieved instanceID " + newInstanceID + "" + " from localDB"); } if (newInstanceID.length() < 1) { newInstanceID = DEFAULT_INSTANCE_ID; } return newInstanceID; } public StatisticsManager getStatisticsManager() { return (StatisticsManager) pwmServices.get(StatisticsManager.class); } public OtpService getOtpService() { return (OtpService) pwmServices.get(OtpService.class); } public CrService getCrService() { return (CrService) pwmServices.get(CrService.class); } public CacheService getCacheService() { return (CacheService) pwmServices.get(CacheService.class); } public SecureService getSecureService() { return (SecureService) pwmServices.get(SecureService.class); } public void sendSmsUsingQueue(final SmsItemBean smsItem, final MacroMachine macroMachine) { final SmsQueueManager smsQueue = getSmsQueue(); if (smsQueue == null) { LOGGER.error("SMS queue is unavailable, unable to send SMS: " + smsItem.toString()); return; } final SmsItemBean rewrittenSmsItem = new SmsItemBean( macroMachine.expandMacros(smsItem.getTo()), macroMachine.expandMacros(smsItem.getMessage())); try { smsQueue.addSmsToQueue(rewrittenSmsItem); } catch (PwmUnrecoverableException e) { LOGGER.warn("unable to add sms to queue: " + e.getMessage()); } } public void shutdown() { LOGGER.warn("shutting down"); { // send system audit event final SystemAuditRecord auditRecord = SystemAuditRecord.create(AuditEvent.SHUTDOWN, null, getInstanceID()); try { getAuditManager().submit(auditRecord); } catch (PwmException e) { LOGGER.warn("unable to submit alert event " + JsonUtil.serialize(auditRecord)); } } { final List<Class> reverseServiceList = new ArrayList<Class>(PWM_SERVICE_CLASSES); Collections.reverse(reverseServiceList); for (final Class serviceClass : reverseServiceList) { if (pwmServices.containsKey(serviceClass)) { LOGGER.trace("closing service " + serviceClass.getName()); final PwmService loopService = pwmServices.get(serviceClass); LOGGER.trace("successfully closed service " + serviceClass.getName()); try { loopService.close(); } catch (Exception e) { LOGGER.error( "error closing " + loopService.getClass().getSimpleName() + ": " + e.getMessage(), e); } } } } if (localDBLogger != null) { try { localDBLogger.close(); } catch (Exception e) { LOGGER.error("error closing localDBLogger: " + e.getMessage(), e); } localDBLogger = null; } if (localDB != null) { try { localDB.close(); } catch (Exception e) { LOGGER.fatal("error closing localDB: " + e, e); } localDB = null; } LOGGER.info( PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " closed for bidness, cya!"); } public Date getStartupTime() { return startupTime; } public Date getInstallTime() { return installTime; } public LocalDB getLocalDB() { return localDB; } // -------------------------- INNER CLASSES -------------------------- private static class Initializer { public static LocalDB initializeLocalDB(final PwmApplication pwmApplication) { if (pwmApplication.getApplicationMode() == MODE.ERROR || pwmApplication.getApplicationMode() == MODE.NEW) { LOGGER.warn( "skipping LocalDB open due to application mode " + pwmApplication.getApplicationMode()); return null; } final File databaseDirectory; // see if META-INF isn't already there, then use WEB-INF. try { final String localDBLocationSetting = pwmApplication.getConfig().readSettingAsString(PwmSetting.PWMDB_LOCATION); databaseDirectory = Helper.figureFilepath(localDBLocationSetting, pwmApplication.applicationPath); } catch (Exception e) { pwmApplication.lastLocalDBFailure = new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "error locating configured LocalDB directory: " + e.getMessage()); LOGGER.warn(pwmApplication.lastLocalDBFailure.toDebugStr()); return null; } LOGGER.debug("using localDB path " + databaseDirectory); // initialize the localDB try { final boolean readOnly = pwmApplication.getApplicationMode() == MODE.READ_ONLY; return LocalDBFactory.getInstance( databaseDirectory, readOnly, pwmApplication, pwmApplication.getConfig()); } catch (Exception e) { pwmApplication.lastLocalDBFailure = new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "unable to initialize LocalDB: " + e.getMessage()); LOGGER.warn(pwmApplication.lastLocalDBFailure.toDebugStr()); } return null; } } public File getApplicationPath() { return applicationPath; } public enum MODE { NEW, CONFIGURATION, RUNNING, READ_ONLY, ERROR } public String getInstanceNonce() { return Long.toString(getStartupTime().getTime(), 36); } public String readAppAttribute(final AppAttribute appAttribute) { if (localDB == null || localDB.status() != LocalDB.Status.OPEN) { LOGGER.error("error retrieving key '" + appAttribute.getKey() + "', localDB unavailable: "); return null; } if (appAttribute == null) { return null; } try { return localDB.get(LocalDB.DB.PWM_META, appAttribute.getKey()); } catch (Exception e) { LOGGER.error( "error retrieving key '" + appAttribute.getKey() + "' installation date from localDB: " + e.getMessage()); } return null; } public void writeAppAttribute(final AppAttribute appAttribute, final String value) { if (localDB == null || localDB.status() != LocalDB.Status.OPEN) { LOGGER.error("error writing key '" + appAttribute.getKey() + "', localDB unavailable: "); return; } if (appAttribute == null) { return; } try { if (value == null) { localDB.remove(LocalDB.DB.PWM_META, appAttribute.getKey()); } else { localDB.put(LocalDB.DB.PWM_META, appAttribute.getKey(), value); } } catch (Exception e) { LOGGER.error( "error retrieving key '" + appAttribute.getKey() + "' installation date from localDB: " + e.getMessage()); } } public File getWebInfPath() { return webInfPath; } public static void verifyApplicationPath(final File applicationPath) throws PwmUnrecoverableException { if (applicationPath == null) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_STARTUP_ERROR, "unable to determine valid applicationPath")); } LOGGER.trace("examining applicationPath of " + applicationPath.getAbsolutePath() + ""); if (!applicationPath.exists()) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_STARTUP_ERROR, "applicationPath " + applicationPath.getAbsolutePath() + " does not exist")); } if (!applicationPath.canRead()) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_STARTUP_ERROR, "unable to read from applicationPath " + applicationPath.getAbsolutePath() + "")); } if (!applicationPath.canWrite()) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_STARTUP_ERROR, "unable to write to applicationPath " + applicationPath.getAbsolutePath() + "")); } final File infoFile = new File( applicationPath.getAbsolutePath() + File.separator + PwmConstants.APPLICATION_PATH_INFO_FILE); LOGGER.trace( "checking " + infoFile.getAbsolutePath() + " status, (applicationPathType=" + PwmEnvironment.ApplicationPathType.derived + ")"); if (infoFile.exists()) { final String errorMsg = "The file " + infoFile.getAbsolutePath() + " exists, and an applicationPath was not explicitly specified." + " This happens when an applicationPath was previously configured, but is not now being specified." + " An explicit applicationPath parameter must be specified, or the file can be removed if the applicationPath should be changed to the default /WEB-INF directory."; throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg)); } else { LOGGER.trace("marker file " + infoFile.getAbsolutePath() + " does not exist"); } } private void verifyIfApplicationPathIsSetProperly(final PwmEnvironment pwmEnvironment) throws PwmUnrecoverableException { final File applicationPath = pwmEnvironment.applicationPath; File webInfPath = pwmEnvironment.webInfPath; verifyApplicationPath(applicationPath); boolean applicationPathIsWebInfPath = false; if (applicationPath.equals(webInfPath)) { applicationPathIsWebInfPath = true; } else if (applicationPath.getAbsolutePath().endsWith("/WEB-INF")) { final File webXmlFile = new File(applicationPath.getAbsolutePath() + File.separator + "web.xml"); if (webXmlFile.exists()) { applicationPathIsWebInfPath = true; } } if (applicationPathIsWebInfPath) { if (webInfPath == null) { webInfPath = applicationPath; pwmEnvironment.webInfPath = applicationPath; } LOGGER.trace("applicationPath appears to be servlet /WEB-INF directory"); } if (webInfPath != null) { final File infoFile = new File( webInfPath.getAbsolutePath() + File.separator + PwmConstants.APPLICATION_PATH_INFO_FILE); if (applicationPathIsWebInfPath) { if (pwmEnvironment.applicationPathType == PwmEnvironment.ApplicationPathType.derived) { LOGGER.trace( "checking " + infoFile.getAbsolutePath() + " status, (applicationPathType=" + PwmEnvironment.ApplicationPathType.derived + ")"); if (infoFile.exists()) { final String errorMsg = "The file " + infoFile.getAbsolutePath() + " exists, and an applicationPath was not explicitly specified." + " This happens when an applicationPath was previously configured, but is not now being specified." + " An explicit applicationPath parameter must be specified, or the file can be removed if the applicationPath should be changed to the default /WEB-INF directory."; throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg)); } else { LOGGER.trace("marker file " + infoFile.getAbsolutePath() + " does not exist"); } } } else { if (pwmEnvironment.applicationPathType == PwmEnvironment.ApplicationPathType.specified) { try { final FileOutputStream fos = new FileOutputStream(infoFile); final Properties outputProperties = new Properties(); outputProperties.setProperty("lastApplicationPath", applicationPath.getAbsolutePath()); outputProperties.store( fos, "Marker file to record a previously configured applicationPath"); } catch (IOException e) { LOGGER.warn( "unable to write applicationPath marker properties file " + infoFile.getAbsolutePath() + ""); } } } } } public static class PwmEnvironment { private MODE applicationMode = MODE.ERROR; private Configuration config; private File applicationPath; private boolean initLogging; private File configurationFile; private File webInfPath; private ApplicationPathType applicationPathType = ApplicationPathType.derived; public enum ApplicationPathType { derived, specified, } public PwmEnvironment(Configuration config, File applicationPath) { this.config = config; this.applicationPath = applicationPath; } public PwmEnvironment setApplicationMode(MODE applicationMode) { this.applicationMode = applicationMode; return this; } public PwmEnvironment setInitLogging(boolean initLogging) { this.initLogging = initLogging; return this; } public PwmEnvironment setConfigurationFile(File configurationFile) { this.configurationFile = configurationFile; return this; } public PwmEnvironment setWebInfPath(File webInfPath) { this.webInfPath = webInfPath; return this; } public PwmEnvironment setApplicationPathType(ApplicationPathType applicationPathType) { this.applicationPathType = applicationPathType; return this; } public PwmApplication createPwmApplication() throws PwmUnrecoverableException { return new PwmApplication(this); } } }
/** @author Jason D. Rivard */ public class PasswordUtility { private static final PwmLogger LOGGER = PwmLogger.forClass(PasswordUtility.class); private static final String NEGATIVE_CACHE_HIT = "NEGATIVE_CACHE_HIT"; public static String sendNewPassword( final UserInfoBean userInfoBean, final PwmApplication pwmApplication, final MacroMachine macroMachine, final PasswordData newPassword, final Locale userLocale, final MessageSendMethod messageSendMethod) throws PwmOperationalException, PwmUnrecoverableException { final String emailAddress = userInfoBean.getUserEmailAddress(); final String smsNumber = userInfoBean.getUserSmsNumber(); String returnToAddress = emailAddress; ErrorInformation error = null; switch (messageSendMethod) { case BOTH: // Send both email and SMS, success if one of both succeeds final ErrorInformation err1 = sendNewPasswordEmail( userInfoBean, pwmApplication, macroMachine, newPassword, emailAddress, userLocale); final ErrorInformation err2 = sendNewPasswordSms( userInfoBean, pwmApplication, macroMachine, newPassword, smsNumber, userLocale); if (err1 != null) { error = err1; returnToAddress = smsNumber; } else if (err2 != null) { error = err2; } break; case EMAILFIRST: // Send email first, try SMS if email is not available error = sendNewPasswordEmail( userInfoBean, pwmApplication, macroMachine, newPassword, emailAddress, userLocale); if (error != null) { error = sendNewPasswordSms( userInfoBean, pwmApplication, macroMachine, newPassword, smsNumber, userLocale); returnToAddress = smsNumber; } break; case SMSFIRST: // Send SMS first, try email if SMS is not available error = sendNewPasswordSms( userInfoBean, pwmApplication, macroMachine, newPassword, smsNumber, userLocale); if (error != null) { error = sendNewPasswordEmail( userInfoBean, pwmApplication, macroMachine, newPassword, emailAddress, userLocale); } else { returnToAddress = smsNumber; } break; case SMSONLY: // Only try SMS error = sendNewPasswordSms( userInfoBean, pwmApplication, macroMachine, newPassword, smsNumber, userLocale); returnToAddress = smsNumber; break; case EMAILONLY: default: // Only try email error = sendNewPasswordEmail( userInfoBean, pwmApplication, macroMachine, newPassword, emailAddress, userLocale); break; } if (error != null) { throw new PwmOperationalException(error); } return returnToAddress; } private static ErrorInformation sendNewPasswordSms( final UserInfoBean userInfoBean, final PwmApplication pwmApplication, final MacroMachine macroMachine, final PasswordData newPassword, final String toNumber, final Locale userLocale) throws PwmOperationalException, PwmUnrecoverableException { final Configuration config = pwmApplication.getConfig(); String message = config.readSettingAsLocalizedString(PwmSetting.SMS_CHALLENGE_NEW_PASSWORD_TEXT, userLocale); if (toNumber == null || toNumber.length() < 1) { final String errorMsg = String.format( "unable to send new password email for '%s'; no SMS number available in ldap", userInfoBean.getUserIdentity()); return new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); } message = message.replace("%TOKEN%", newPassword.getStringValue()); pwmApplication.sendSmsUsingQueue(new SmsItemBean(toNumber, message), macroMachine); LOGGER.debug(String.format("password SMS added to send queue for %s", toNumber)); return null; } private static ErrorInformation sendNewPasswordEmail( final UserInfoBean userInfoBean, final PwmApplication pwmApplication, final MacroMachine macroMachine, final PasswordData newPassword, final String toAddress, final Locale userLocale) throws PwmOperationalException, PwmUnrecoverableException { final Configuration config = pwmApplication.getConfig(); final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_SENDPASSWORD, userLocale); if (configuredEmailSetting == null) { final String errorMsg = "send password email contents are not configured"; return new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); } final EmailItemBean emailItemBean = new EmailItemBean( configuredEmailSetting.getTo(), configuredEmailSetting.getFrom(), configuredEmailSetting.getSubject(), configuredEmailSetting.getBodyPlain().replace("%TOKEN%", newPassword.getStringValue()), configuredEmailSetting.getBodyHtml().replace("%TOKEN%", newPassword.getStringValue())); pwmApplication.getEmailQueue().submitEmail(emailItemBean, userInfoBean, macroMachine); LOGGER.debug( "new password email to " + userInfoBean.getUserIdentity() + " added to send queue for " + toAddress); return null; } enum PasswordPolicySource { MERGE, LDAP, PWM, } private PasswordUtility() {} /** * This is the entry point under which all password changes are managed. The following is the * general procedure when this method is invoked. * * <ul> * <li>password is checked against PWM password requirement * <li>ldap password set is attempted<br> * <br> * if successful: * <ul> * <li>uiBean is updated with old and new passwords * <li>uiBean's password expire flag is set to false * <li>any configured external methods are invoked * <li>user email notification is sent * <li>return true * </ul> * <br> * if unsuccessful * <ul> * <li>ssBean is updated with appropriate error * <li>return false * </ul> * </ul> * * @param newPassword the new password that is being set. * @param pwmSession beanmanager for config and user info lookup * @throws com.novell.ldapchai.exception.ChaiUnavailableException if the ldap directory is not * unavailable * @throws password.pwm.error.PwmUnrecoverableException if user is not authenticated */ public static void setActorPassword( final PwmSession pwmSession, final PwmApplication pwmApplication, final PasswordData newPassword) throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException { final UserInfoBean uiBean = pwmSession.getUserInfoBean(); if (!pwmSession .getSessionManager() .checkPermission(pwmApplication, Permission.CHANGE_PASSWORD)) { final String errorMsg = "attempt to setActorPassword, but user does not have password change permission"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg); throw new PwmOperationalException(errorInformation); } // double check to make sure password meets PWM rule requirements. This should // have been done before setActorPassword() is invoked, so it should be redundant // but we do it just in case. try { final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator(pwmApplication, uiBean.getPasswordPolicy()); pwmPasswordRuleValidator.testPassword( newPassword, null, uiBean, pwmSession.getSessionManager().getActor(pwmApplication)); } catch (PwmDataValidationException e) { final String errorMsg = "attempt to setActorPassword, but password does not pass local policy validator"; final ErrorInformation errorInformation = new ErrorInformation(e.getErrorInformation().getError(), errorMsg); throw new PwmOperationalException(errorInformation); } // retrieve the user's old password from the userInfoBean in the session final PasswordData oldPassword = pwmSession.getLoginInfoBean().getUserCurrentPassword(); boolean setPasswordWithoutOld = false; if (oldPassword == null) { if (pwmSession .getSessionManager() .getActor(pwmApplication) .getChaiProvider() .getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) { setPasswordWithoutOld = true; } } if (!setPasswordWithoutOld) { // Check to make sure we actually have an old password if (oldPassword == null) { final String errorMsg = "cannot set password for user, old password is not available"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_WRONGPASSWORD, errorMsg); throw new PwmOperationalException(errorInformation); } } try { final ChaiProvider provider = pwmSession.getSessionManager().getChaiProvider(); final ChaiUser theUser = ChaiFactory.createChaiUser( pwmSession.getUserInfoBean().getUserIdentity().getUserDN(), provider); final boolean boundAsSelf = theUser .getEntryDN() .equals(provider.getChaiConfiguration().getSetting(ChaiSetting.BIND_DN)); LOGGER.trace( pwmSession, "preparing to setActorPassword for '" + theUser.getEntryDN() + "', bindAsSelf=" + boundAsSelf + ", authType=" + pwmSession.getLoginInfoBean().getType()); if (setPasswordWithoutOld) { theUser.setPassword(newPassword.getStringValue(), true); } else { theUser.changePassword(oldPassword.getStringValue(), newPassword.getStringValue()); } } catch (ChaiPasswordPolicyException e) { final String errorMsg = "error setting password for user '" + uiBean.getUserIdentity() + "'' " + e.toString(); final PwmError pwmError = PwmError.forChaiError(e.getErrorCode()); final ErrorInformation error = new ErrorInformation( pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError, errorMsg); throw new PwmOperationalException(error); } catch (ChaiOperationException e) { final String errorMsg = "error setting password for user '" + uiBean.getUserIdentity() + "'' " + e.getMessage(); final PwmError pwmError = PwmError.forChaiError(e.getErrorCode()) == null ? PwmError.ERROR_UNKNOWN : PwmError.forChaiError(e.getErrorCode()); final ErrorInformation error = new ErrorInformation(pwmError, errorMsg); throw new PwmOperationalException(error); } // at this point the password has been changed, so log it. LOGGER.info( pwmSession, "user '" + uiBean.getUserIdentity() + "' successfully changed password"); // update the session state bean's password modified flag pwmSession.getSessionStateBean().setPasswordModified(true); // update the login info bean with the user's new password pwmSession.getLoginInfoBean().setUserCurrentPassword(newPassword); // close any outstanding ldap connections (since they cache the old password) pwmSession .getSessionManager() .updateUserPassword(pwmApplication, uiBean.getUserIdentity(), newPassword); // clear the "requires new password flag" uiBean.setRequiresNewPassword(false); // mark the auth type as authenticatePd now that we have the user's natural password. pwmSession.getLoginInfoBean().setType(AuthenticationType.AUTHENTICATED); // update the uibean's "password expired flag". final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, pwmSession.getLabel()); uiBean.setPasswordState( userStatusReader.readPasswordStatus( pwmSession.getSessionManager().getActor(pwmApplication), uiBean.getPasswordPolicy(), uiBean, newPassword)); // create a proxy user object for pwm to update/read the user. final ChaiUser proxiedUser = pwmSession.getSessionManager().getActor(pwmApplication); // update statistics { pwmApplication.getStatisticsManager().incrementValue(Statistic.PASSWORD_CHANGES); pwmApplication.getStatisticsManager().updateEps(Statistic.EpsType.PASSWORD_CHANGES, 1); final int passwordStrength = PasswordUtility.judgePasswordStrength(newPassword.getStringValue()); pwmApplication .getStatisticsManager() .updateAverageValue(Statistic.AVG_PASSWORD_STRENGTH, passwordStrength); } // add the old password to the global history list (if the old password is known) if (oldPassword != null && pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.PASSWORD_SHAREDHISTORY_ENABLE)) { pwmApplication.getSharedHistoryManager().addWord(pwmSession, oldPassword.getStringValue()); } // invoke post password change actions invokePostChangePasswordActions(pwmSession, newPassword.getStringValue()); { // execute configured actions LOGGER.debug(pwmSession, "executing configured actions to user " + proxiedUser.getEntryDN()); final List<ActionConfiguration> configValues = pwmApplication .getConfig() .readSettingAsAction(PwmSetting.CHANGE_PASSWORD_WRITE_ATTRIBUTES); if (configValues != null && !configValues.isEmpty()) { final LoginInfoBean clonedLoginInfoBean = JsonUtil.cloneUsingJson(pwmSession.getLoginInfoBean(), LoginInfoBean.class); clonedLoginInfoBean.setUserCurrentPassword(newPassword); final MacroMachine macroMachine = new MacroMachine( pwmApplication, pwmSession.getLabel(), pwmSession.getUserInfoBean(), clonedLoginInfoBean, pwmSession.getSessionManager().getUserDataReader(pwmApplication)); final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, uiBean.getUserIdentity()) .setMacroMachine(macroMachine) .setExpandPwmMacros(true) .createActionExecutor(); actionExecutor.executeActions(configValues, pwmSession); } } // update the current last password update field in ldap LdapOperationsHelper.updateLastPasswordUpdateAttribute( pwmApplication, pwmSession.getLabel(), uiBean.getUserIdentity()); } public static void helpdeskSetUserPassword( final PwmSession pwmSession, final ChaiUser chaiUser, final UserIdentity userIdentity, final PwmApplication pwmApplication, final PasswordData newPassword) throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException { final SessionLabel sessionLabel = pwmSession.getLabel(); if (!pwmSession.isAuthenticated()) { final String errorMsg = "attempt to helpdeskSetUserPassword, but user is not authenticated"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg); throw new PwmOperationalException(errorInformation); } final HelpdeskProfile helpdeskProfile = pwmSession.getSessionManager().getHelpdeskProfile(pwmApplication); if (helpdeskProfile == null) { final String errorMsg = "attempt to helpdeskSetUserPassword, but user does not have helpdesk permission"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg); throw new PwmOperationalException(errorInformation); } try { chaiUser.setPassword(newPassword.getStringValue()); } catch (ChaiPasswordPolicyException e) { final String errorMsg = "error setting password for user '" + chaiUser.getEntryDN() + "'' " + e.toString(); final PwmError pwmError = PwmError.forChaiError(e.getErrorCode()); final ErrorInformation error = new ErrorInformation( pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError, errorMsg); throw new PwmOperationalException(error); } catch (ChaiOperationException e) { final String errorMsg = "error setting password for user '" + chaiUser.getEntryDN() + "'' " + e.getMessage(); final PwmError pwmError = PwmError.forChaiError(e.getErrorCode()) == null ? PwmError.ERROR_UNKNOWN : PwmError.forChaiError(e.getErrorCode()); final ErrorInformation error = new ErrorInformation(pwmError, errorMsg); throw new PwmOperationalException(error); } // at this point the password has been changed, so log it. LOGGER.info( sessionLabel, "user '" + pwmSession.getUserInfoBean().getUserIdentity() + "' successfully changed password for " + chaiUser.getEntryDN()); // create a proxy user object for pwm to update/read the user. final ChaiUser proxiedUser = pwmApplication.getProxiedChaiUser(userIdentity); // mark the event log { final HelpdeskAuditRecord auditRecord = pwmApplication .getAuditManager() .createHelpdeskAuditRecord( AuditEvent.HELPDESK_SET_PASSWORD, pwmSession.getUserInfoBean().getUserIdentity(), null, userIdentity, pwmSession.getSessionStateBean().getSrcAddress(), pwmSession.getSessionStateBean().getSrcHostname()); pwmApplication.getAuditManager().submit(auditRecord); } // update statistics pwmApplication.getStatisticsManager().updateEps(Statistic.EpsType.PASSWORD_CHANGES, 1); pwmApplication.getStatisticsManager().incrementValue(Statistic.HELPDESK_PASSWORD_SET); // create a uib for end user final UserInfoBean userInfoBean = new UserInfoBean(); final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, pwmSession.getLabel()); userStatusReader.populateUserInfoBean( userInfoBean, pwmSession.getSessionStateBean().getLocale(), userIdentity, proxiedUser.getChaiProvider()); { // execute configured actions LOGGER.debug( sessionLabel, "executing changepassword and helpdesk post password change writeAttributes to user " + userIdentity); final List<ActionConfiguration> actions = new ArrayList<>(); actions.addAll( pwmApplication .getConfig() .readSettingAsAction(PwmSetting.CHANGE_PASSWORD_WRITE_ATTRIBUTES)); actions.addAll( helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_POST_SET_PASSWORD_WRITE_ATTRIBUTES)); if (!actions.isEmpty()) { final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity) .setMacroMachine( MacroMachine.forUser( pwmApplication, pwmSession.getSessionStateBean().getLocale(), sessionLabel, userIdentity)) .setExpandPwmMacros(true) .createActionExecutor(); actionExecutor.executeActions(actions, pwmSession); } } final HelpdeskClearResponseMode settingClearResponses = HelpdeskClearResponseMode.valueOf( helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_CLEAR_RESPONSES)); if (settingClearResponses == HelpdeskClearResponseMode.yes) { final String userGUID = LdapOperationsHelper.readLdapGuidValue(pwmApplication, sessionLabel, userIdentity, false); pwmApplication.getCrService().clearResponses(pwmSession, proxiedUser, userGUID); // mark the event log final HelpdeskAuditRecord auditRecord = pwmApplication .getAuditManager() .createHelpdeskAuditRecord( AuditEvent.HELPDESK_CLEAR_RESPONSES, pwmSession.getUserInfoBean().getUserIdentity(), null, userIdentity, pwmSession.getSessionStateBean().getSrcAddress(), pwmSession.getSessionStateBean().getSrcHostname()); pwmApplication.getAuditManager().submit(auditRecord); } // send email notification sendChangePasswordHelpdeskEmailNotice(pwmSession, pwmApplication, userInfoBean); // expire if so configured if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_FORCE_PW_EXPIRATION)) { LOGGER.trace( pwmSession, "preparing to expire password for user " + userIdentity.toDisplayString()); try { proxiedUser.expirePassword(); } catch (ChaiOperationException e) { LOGGER.warn( pwmSession, "error while forcing password expiration for user " + userIdentity.toDisplayString() + ", error: " + e.getMessage()); e.printStackTrace(); } } // send password final boolean sendPassword = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_SEND_PASSWORD); if (sendPassword) { final MessageSendMethod messageSendMethod; { final String profileID = ProfileUtility.discoverProfileIDforUser( pwmApplication, sessionLabel, userIdentity, ProfileType.ForgottenPassword); final ForgottenPasswordProfile forgottenPasswordProfile = pwmApplication.getConfig().getForgottenPasswordProfiles().get(profileID); messageSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_SENDNEWPW_METHOD, MessageSendMethod.class); } final UserDataReader userDataReader = new LdapUserDataReader(userIdentity, chaiUser); final LoginInfoBean loginInfoBean = new LoginInfoBean(); loginInfoBean.setUserCurrentPassword(newPassword); final MacroMachine macroMachine = new MacroMachine( pwmApplication, pwmSession.getLabel(), userInfoBean, loginInfoBean, userDataReader); PasswordUtility.sendNewPassword( userInfoBean, pwmApplication, macroMachine, newPassword, pwmSession.getSessionStateBean().getLocale(), messageSendMethod); } } public static Map<String, Date> readIndividualReplicaLastPasswordTimes( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity) throws PwmUnrecoverableException { final Map<String, Date> returnValue = new LinkedHashMap<>(); final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()); final Collection<ChaiConfiguration> perReplicaConfigs = ChaiUtility.splitConfigurationPerReplica( chaiProvider.getChaiConfiguration(), Collections.singletonMap(ChaiSetting.FAILOVER_CONNECT_RETRIES, "1")); for (final ChaiConfiguration loopConfiguration : perReplicaConfigs) { final String loopReplicaUrl = loopConfiguration.getSetting(ChaiSetting.BIND_DN); ChaiProvider loopProvider = null; try { loopProvider = ChaiProviderFactory.createProvider(loopConfiguration); final Date lastModifiedDate = determinePwdLastModified(pwmApplication, sessionLabel, userIdentity); returnValue.put(loopReplicaUrl, lastModifiedDate); } catch (ChaiUnavailableException e) { LOGGER.error(sessionLabel, "unreachable server during replica password sync check"); e.printStackTrace(); } finally { if (loopProvider != null) { try { loopProvider.close(); } catch (Exception e) { final String errorMsg = "error closing loopProvider to " + loopReplicaUrl + " while checking individual password sync status"; LOGGER.error(sessionLabel, errorMsg); } } } } return returnValue; } private static void invokePostChangePasswordActions( final PwmSession pwmSession, final String newPassword) throws PwmUnrecoverableException { final List<PostChangePasswordAction> postChangePasswordActions = pwmSession.getUserSessionDataCacheBean().removePostChangePasswordActions(); if (postChangePasswordActions == null || postChangePasswordActions.isEmpty()) { LOGGER.trace(pwmSession, "no post change password actions pending from previous operations"); return; } for (final PostChangePasswordAction postChangePasswordAction : postChangePasswordActions) { try { postChangePasswordAction.doAction(pwmSession, newPassword); } catch (PwmUnrecoverableException e) { LOGGER.error( pwmSession, "error during post change password action '" + postChangePasswordAction.getLabel() + "' " + e.getMessage()); throw e; } catch (Exception e) { LOGGER.error( pwmSession, "unexpected error during post change password action '" + postChangePasswordAction.getLabel() + "' " + e.getMessage(), e); final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage()); throw new PwmUnrecoverableException(errorInfo); } } } /* static Map<String, ReplicationStatus> checkIfPasswordIsReplicated(final ChaiUser user, final PwmSession pwmSession) throws ChaiUnavailableException { final Map<String, ReplicationStatus> repStatusMap = new HashMap<String, ReplicationStatus>(); { final ReplicationStatus repStatus = checkDirectoryReplicationStatus(user, pwmSession); repStatusMap.put("ReplicationSync", repStatus); } if (ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY == user.getChaiProvider().getDirectoryVendor()) { } return repStatusMap; } public static Map<String, ReplicationStatus> checkNovellIDMReplicationStatus(final ChaiUser chaiUser) throws ChaiUnavailableException, ChaiOperationException { final Map<String,ReplicationStatus> repStatuses = new HashMap<String,ReplicationStatus>(); final Set<String> values = chaiUser.readMultiStringAttribute("DirXML-PasswordSyncStatus"); if (values != null) { for (final String value : values) { if (value != null && value.length() >= 62 ) { final String guid = value.substring(0,32); final String timestamp = value.substring(32,46); final String status = value.substring(46,62); final String descr = value.substring(61, value.length()); final Date dateValue = EdirEntries.convertZuluToDate(timestamp + "Z"); System.out.println("guid=" + guid + ", timestamp=" + dateValue.toString() + ", status=" + status + ", descr=" + descr); } } } return repStatuses; } private static ReplicationStatus checkDirectoryReplicationStatus(final ChaiUser user, final PwmSession pwmSession) throws ChaiUnavailableException { boolean isReplicated = false; try { isReplicated = ChaiUtility.testAttributeReplication(user, pwmSession.getConfig().readSettingAsString(PwmSetting.PASSWORD_LAST_UPDATE_ATTRIBUTE), null); Helper.pause(PwmConstants.PASSWORD_UPDATE_CYCLE_DELAY_MS); } catch (ChaiOperationException e) { //oh well, give up. LOGGER.trace(pwmSession, "error during password sync check: " + e.getMessage()); } return isReplicated ? ReplicationStatus.COMPLETE : ReplicationStatus.IN_PROGRESS; } enum ReplicationStatus { IN_PROGRESS, COMPLETE } */ public static int judgePasswordStrength(final String password) throws PwmUnrecoverableException { if (password == null || password.length() < 1) { return 0; } int score = 0; final PasswordCharCounter charCounter = new PasswordCharCounter(password); // -- Additions -- // amount of unique chars if (charCounter.getUniqueChars() > 7) { score = score + 10; } score = score + ((charCounter.getUniqueChars()) * 3); // Numbers if (charCounter.getNumericCharCount() > 0) { score = score + 8; score = score + (charCounter.getNumericCharCount()) * 4; } // specials if (charCounter.getSpecialCharsCount() > 0) { score = score + 14; score = score + (charCounter.getSpecialCharsCount()) * 5; } // mixed case if ((charCounter.getAlphaChars() != charCounter.getUpperChars()) && (charCounter.getAlphaChars() != charCounter.getLowerChars())) { score = score + 10; } // -- Deductions -- // sequential numbers if (charCounter.getSequentialNumericChars() > 2) { score = score - (charCounter.getSequentialNumericChars() - 1) * 4; } // sequential chars if (charCounter.getSequentialRepeatedChars() > 1) { score = score - (charCounter.getSequentialRepeatedChars()) * 5; } return score > 100 ? 100 : score < 0 ? 0 : score; } public static PwmPasswordPolicy readPasswordPolicyForUser( final PwmApplication pwmApplication, final SessionLabel pwmSession, final UserIdentity userIdentity, final ChaiUser theUser, final Locale locale) throws PwmUnrecoverableException { final long startTime = System.currentTimeMillis(); final PasswordPolicySource ppSource = PasswordPolicySource.valueOf( pwmApplication.getConfig().readSettingAsString(PwmSetting.PASSWORD_POLICY_SOURCE)); final PwmPasswordPolicy returnPolicy; switch (ppSource) { case MERGE: final PwmPasswordPolicy pwmPolicy = determineConfiguredPolicyProfileForUser( pwmApplication, pwmSession, userIdentity, locale); final PwmPasswordPolicy userPolicy = readLdapPasswordPolicy(pwmApplication, theUser); LOGGER.trace( pwmSession, "read user policy for '" + theUser.getEntryDN() + "', policy: " + userPolicy.toString()); returnPolicy = pwmPolicy.merge(userPolicy); LOGGER.debug( pwmSession, "merged user password policy of '" + theUser.getEntryDN() + "' with PWM configured policy: " + returnPolicy.toString()); break; case LDAP: returnPolicy = readLdapPasswordPolicy(pwmApplication, theUser); LOGGER.debug( pwmSession, "discovered assigned password policy for " + theUser.getEntryDN() + " " + returnPolicy.toString()); break; case PWM: returnPolicy = determineConfiguredPolicyProfileForUser( pwmApplication, pwmSession, userIdentity, locale); break; default: throw new IllegalStateException("unknown policy source defined: " + ppSource.name()); } LOGGER.trace( pwmSession, "readPasswordPolicyForUser completed in " + TimeDuration.fromCurrent(startTime).asCompactString()); return returnPolicy; } protected static PwmPasswordPolicy determineConfiguredPolicyProfileForUser( final PwmApplication pwmApplication, final SessionLabel pwmSession, final UserIdentity userIdentity, final Locale locale) throws PwmUnrecoverableException { final List<String> profiles = pwmApplication.getConfig().getPasswordProfileIDs(); if (profiles.isEmpty()) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_NO_PROFILE_ASSIGNED, "no password profiles are configured")); } for (final String profile : profiles) { final PwmPasswordPolicy loopPolicy = pwmApplication.getConfig().getPasswordPolicy(profile, locale); final List<UserPermission> userPermissions = loopPolicy.getUserPermissions(); LOGGER.debug(pwmSession, "testing password policy profile '" + profile + "'"); try { boolean match = LdapPermissionTester.testUserPermissions( pwmApplication, pwmSession, userIdentity, userPermissions); if (match) { return loopPolicy; } } catch (PwmUnrecoverableException e) { LOGGER.error( pwmSession, "unexpected error while testing password policy profile '" + profile + "', error: " + e.getMessage()); } } throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_NO_PROFILE_ASSIGNED, "no challenge profile is configured")); } public static PwmPasswordPolicy readLdapPasswordPolicy( final PwmApplication pwmApplication, final ChaiUser theUser) throws PwmUnrecoverableException { try { final Map<String, String> ruleMap = new HashMap<>(); final ChaiPasswordPolicy chaiPolicy; try { chaiPolicy = theUser.getPasswordPolicy(); } catch (ChaiUnavailableException e) { throw new PwmUnrecoverableException(PwmError.forChaiError(e.getErrorCode())); } if (chaiPolicy != null) { for (final String key : chaiPolicy.getKeys()) { ruleMap.put(key, chaiPolicy.getValue(key)); } if (!"read" .equals( pwmApplication .getConfig() .readSettingAsString(PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY))) { ruleMap.put( PwmPasswordRule.CaseSensitive.getKey(), pwmApplication .getConfig() .readSettingAsString(PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY)); } return PwmPasswordPolicy.createPwmPasswordPolicy(ruleMap, chaiPolicy); } } catch (ChaiOperationException e) { LOGGER.warn( "error reading password policy for user " + theUser.getEntryDN() + ", error: " + e.getMessage()); } return PwmPasswordPolicy.defaultPolicy(); } public static PasswordCheckInfo checkEnteredPassword( final PwmApplication pwmApplication, final Locale locale, final ChaiUser user, final UserInfoBean userInfoBean, final LoginInfoBean loginInfoBean, final PasswordData password, final PasswordData confirmPassword) throws PwmUnrecoverableException, ChaiUnavailableException { if (userInfoBean == null) { throw new NullPointerException("userInfoBean cannot be null"); } boolean pass = false; String userMessage = ""; int errorCode = 0; final boolean passwordIsCaseSensitive = userInfoBean.getPasswordPolicy() == null || userInfoBean .getPasswordPolicy() .getRuleHelper() .readBooleanValue(PwmPasswordRule.CaseSensitive); final CachePolicy cachePolicy; { final long cacheLifetimeMS = Long.parseLong( pwmApplication .getConfig() .readAppProperty(AppProperty.CACHE_PWRULECHECK_LIFETIME_MS)); cachePolicy = CachePolicy.makePolicyWithExpirationMS(cacheLifetimeMS); } if (password == null) { userMessage = new ErrorInformation(PwmError.PASSWORD_MISSING) .toUserStr(locale, pwmApplication.getConfig()); } else { final CacheService cacheService = pwmApplication.getCacheService(); final CacheKey cacheKey = user != null && userInfoBean.getUserIdentity() != null ? CacheKey.makeCacheKey( PasswordUtility.class, userInfoBean.getUserIdentity(), user.getEntryDN() + ":" + password.hash()) : null; if (pwmApplication.getConfig().isDevDebugMode()) { LOGGER.trace("generated cacheKey for password check request: " + cacheKey); } try { if (cacheService != null && cacheKey != null) { final String cachedValue = cacheService.get(cacheKey); if (cachedValue != null) { if (NEGATIVE_CACHE_HIT.equals(cachedValue)) { pass = true; } else { LOGGER.trace("cache hit!"); final ErrorInformation errorInformation = JsonUtil.deserialize(cachedValue, ErrorInformation.class); throw new PwmDataValidationException(errorInformation); } } } if (!pass) { final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator( pwmApplication, userInfoBean.getPasswordPolicy(), locale); final PasswordData oldPassword = loginInfoBean == null ? null : loginInfoBean.getUserCurrentPassword(); pwmPasswordRuleValidator.testPassword(password, oldPassword, userInfoBean, user); pass = true; if (cacheService != null && cacheKey != null) { cacheService.put(cacheKey, cachePolicy, NEGATIVE_CACHE_HIT); } } } catch (PwmDataValidationException e) { errorCode = e.getError().getErrorCode(); userMessage = e.getErrorInformation().toUserStr(locale, pwmApplication.getConfig()); pass = false; if (cacheService != null && cacheKey != null) { final String jsonPayload = JsonUtil.serialize(e.getErrorInformation()); cacheService.put(cacheKey, cachePolicy, jsonPayload); } } } final PasswordCheckInfo.MATCH_STATUS matchStatus = figureMatchStatus(passwordIsCaseSensitive, password, confirmPassword); if (pass) { switch (matchStatus) { case EMPTY: userMessage = new ErrorInformation(PwmError.PASSWORD_MISSING_CONFIRM) .toUserStr(locale, pwmApplication.getConfig()); break; case MATCH: userMessage = new ErrorInformation(PwmError.PASSWORD_MEETS_RULES) .toUserStr(locale, pwmApplication.getConfig()); break; case NO_MATCH: userMessage = new ErrorInformation(PwmError.PASSWORD_DOESNOTMATCH) .toUserStr(locale, pwmApplication.getConfig()); break; default: userMessage = ""; } } final int strength = judgePasswordStrength(password == null ? null : password.getStringValue()); return new PasswordCheckInfo(userMessage, pass, strength, matchStatus, errorCode); } public static PasswordCheckInfo.MATCH_STATUS figureMatchStatus( final boolean caseSensitive, final PasswordData password1, final PasswordData password2) { final PasswordCheckInfo.MATCH_STATUS matchStatus; if (password2 == null) { matchStatus = PasswordCheckInfo.MATCH_STATUS.EMPTY; } else if (password1 == null) { matchStatus = PasswordCheckInfo.MATCH_STATUS.NO_MATCH; } else { if (caseSensitive) { matchStatus = password1.equals(password2) ? PasswordCheckInfo.MATCH_STATUS.MATCH : PasswordCheckInfo.MATCH_STATUS.NO_MATCH; } else { matchStatus = password1.equalsIgnoreCase(password2) ? PasswordCheckInfo.MATCH_STATUS.MATCH : PasswordCheckInfo.MATCH_STATUS.NO_MATCH; } } return matchStatus; } public static class PasswordCheckInfo implements Serializable { private final String message; private final boolean passed; private final int strength; private final MATCH_STATUS match; private final int errorCode; public enum MATCH_STATUS { MATCH, NO_MATCH, EMPTY } public PasswordCheckInfo( String message, boolean passed, int strength, MATCH_STATUS match, int errorCode) { this.message = message; this.passed = passed; this.strength = strength; this.match = match; this.errorCode = errorCode; } public String getMessage() { return message; } public boolean isPassed() { return passed; } public int getStrength() { return strength; } public MATCH_STATUS getMatch() { return match; } public int getErrorCode() { return errorCode; } } private static void sendChangePasswordHelpdeskEmailNotice( final PwmSession pwmSession, final PwmApplication pwmApplication, final UserInfoBean userInfoBean) throws PwmUnrecoverableException { final Configuration config = pwmApplication.getConfig(); final Locale locale = pwmSession.getSessionStateBean().getLocale(); final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_CHANGEPASSWORD_HELPDESK, locale); if (configuredEmailSetting == null) { LOGGER.debug( pwmSession, "skipping send change password email for '" + pwmSession.getUserInfoBean().getUserIdentity() + "' no email configured"); return; } final MacroMachine macroMachine = userInfoBean == null ? null : new MacroMachine( pwmApplication, pwmSession.getLabel(), userInfoBean, null, LdapUserDataReader.appProxiedReader( pwmApplication, userInfoBean.getUserIdentity())); pwmApplication.getEmailQueue().submitEmail(configuredEmailSetting, userInfoBean, macroMachine); } public static Date determinePwdLastModified( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity) throws ChaiUnavailableException, PwmUnrecoverableException { final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity); return determinePwdLastModified(pwmApplication, sessionLabel, theUser, userIdentity); } private static Date determinePwdLastModified( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final ChaiUser theUser, final UserIdentity userIdentity) throws ChaiUnavailableException, PwmUnrecoverableException { // fetch last password modification time from pwm last update attribute operation try { final Date chaiReadDate = theUser.readPasswordModificationDate(); if (chaiReadDate != null) { LOGGER.trace( sessionLabel, "read last user password change timestamp (via chai) as: " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(chaiReadDate)); return chaiReadDate; } } catch (ChaiOperationException e) { LOGGER.error( sessionLabel, "unexpected error reading password last modified timestamp: " + e.getMessage()); } final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID()); final String pwmLastSetAttr = ldapProfile.readSettingAsString(PwmSetting.PASSWORD_LAST_UPDATE_ATTRIBUTE); if (pwmLastSetAttr != null && pwmLastSetAttr.length() > 0) { try { final Date pwmPwdLastModified = theUser.readDateAttribute(pwmLastSetAttr); LOGGER.trace( sessionLabel, "read pwmPasswordChangeTime as: " + (pwmPwdLastModified == null ? "n/a" : PwmConstants.DEFAULT_DATETIME_FORMAT.format(pwmPwdLastModified))); return pwmPwdLastModified; } catch (ChaiOperationException e) { LOGGER.error( sessionLabel, "error parsing password last modified PWM password value for user " + theUser.getEntryDN() + "; error: " + e.getMessage()); } } LOGGER.debug(sessionLabel, "unable to determine time of user's last password modification"); return null; } }
public class ConfigAccessFilter extends AbstractPwmFilter { private static final PwmLogger LOGGER = PwmLogger.forClass(ConfigAccessFilter.class); @Override void processFilter(PwmRequest pwmRequest, PwmFilterChain filterChain) throws PwmException, IOException, ServletException { final PwmApplication.MODE appMode = pwmRequest.getPwmApplication().getApplicationMode(); if (appMode == PwmApplication.MODE.NEW) { filterChain.doFilter(); return; } final ConfigManagerBean configManagerBean = pwmRequest.getPwmSession().getConfigManagerBean(); if (!checkAuthentication(pwmRequest, configManagerBean)) { filterChain.doFilter(); } } static boolean checkAuthentication( final PwmRequest pwmRequest, final ConfigManagerBean configManagerBean) throws IOException, PwmUnrecoverableException, ServletException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final ConfigurationReader runningConfigReader = ContextManager.getContextManager(pwmRequest.getHttpServletRequest().getSession()) .getConfigReader(); final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration(); boolean authRequired = false; if (storedConfig.hasPassword()) { authRequired = true; } if (PwmApplication.MODE.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) { if (!pwmSession.getSessionStateBean().isAuthenticated()) { throw new PwmUnrecoverableException(PwmError.ERROR_AUTHENTICATION_REQUIRED); } if (!pwmRequest .getPwmSession() .getSessionManager() .checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN)) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED); pwmRequest.respondWithError(errorInformation); return true; } if (pwmSession.getLoginInfoBean().getAuthenticationType() != AuthenticationType.AUTHENTICATED) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED, "Username/Password authentication is required to edit configuration. This session has not been authenticated using a user password (SSO or other method used).")); } } if (PwmApplication.MODE.CONFIGURATION != pwmRequest.getPwmApplication().getApplicationMode()) { authRequired = true; } if (!authRequired) { return false; } if (!storedConfig.hasPassword()) { final String errorMsg = "config file does not have a configuration password"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[] {errorMsg}); pwmRequest.respondWithError(errorInformation, true); return true; } if (configManagerBean.isPasswordVerified()) { return false; } String persistentLoginValue = null; boolean persistentLoginAccepted = false; boolean persistentLoginEnabled = false; if (pwmRequest.getConfig().isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) { LOGGER.debug(pwmRequest, "security not available, persistent login not possible."); } else { persistentLoginEnabled = true; final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey(); if (PwmApplication.MODE.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) { persistentLoginValue = SecureEngine.hash( storedConfig.readConfigProperty(ConfigurationProperty.PASSWORD_HASH) + pwmSession.getUserInfoBean().getUserIdentity().toDelimitedKey(), PwmHashAlgorithm.SHA512); } else { persistentLoginValue = SecureEngine.hash( storedConfig.readConfigProperty(ConfigurationProperty.PASSWORD_HASH), PwmHashAlgorithm.SHA512); } { final String cookieStr = ServletHelper.readCookie( pwmRequest.getHttpServletRequest(), PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN); if (securityKey != null && cookieStr != null && !cookieStr.isEmpty()) { try { final String jsonStr = pwmApplication.getSecureService().decryptStringValue(cookieStr); final PersistentLoginInfo persistentLoginInfo = JsonUtil.deserialize(jsonStr, PersistentLoginInfo.class); if (persistentLoginInfo != null && persistentLoginValue != null) { if (persistentLoginInfo.getExpireDate().after(new Date())) { if (persistentLoginValue.equals(persistentLoginInfo.getPassword())) { persistentLoginAccepted = true; LOGGER.debug( pwmRequest, "accepting persistent config login from cookie (expires " + PwmConstants.DEFAULT_DATETIME_FORMAT.format( persistentLoginInfo.getExpireDate()) + ")"); } } } } catch (Exception e) { LOGGER.error( pwmRequest, "error examining persistent config login cookie: " + e.getMessage()); } if (!persistentLoginAccepted) { Cookie removalCookie = new Cookie(PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN, null); removalCookie.setMaxAge(0); pwmRequest.getPwmResponse().addCookie(removalCookie); LOGGER.debug(pwmRequest, "removing non-working persistent config login cookie"); } } } } final String password = pwmRequest.readParameterAsString("password"); boolean passwordAccepted = false; if (!persistentLoginAccepted) { if (password != null && password.length() > 0) { if (storedConfig.verifyPassword(password)) { passwordAccepted = true; LOGGER.trace(pwmRequest, "valid configuration password accepted"); updateLoginHistory(pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), true); } else { LOGGER.trace(pwmRequest, "configuration password is not correct"); pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession); pwmApplication .getIntruderManager() .mark( RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel()); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_WRONGPASSWORD); pwmRequest.setResponseError(errorInformation); updateLoginHistory(pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), false); } } } if ((persistentLoginAccepted || passwordAccepted)) { configManagerBean.setPasswordVerified(true); pwmApplication.getIntruderManager().convenience().clearAddressAndSession(pwmSession); pwmApplication .getIntruderManager() .clear(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME); if (persistentLoginEnabled && !persistentLoginAccepted && "on".equals(pwmRequest.readParameterAsString("remember"))) { final int persistentSeconds = figureMaxLoginSeconds(pwmRequest); if (persistentSeconds > 0) { final Date expirationDate = new Date(System.currentTimeMillis() + (persistentSeconds * 1000)); final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo(expirationDate, persistentLoginValue); final String jsonPersistentLoginInfo = JsonUtil.serialize(persistentLoginInfo); final String cookieValue = pwmApplication.getSecureService().encryptToString(jsonPersistentLoginInfo); pwmRequest .getPwmResponse() .writeCookie( PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN, cookieValue, persistentSeconds); LOGGER.debug( pwmRequest, "set persistent config login cookie (expires " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(expirationDate) + ")"); } } if (configManagerBean.getPrePasswordEntryUrl() != null) { final String originalUrl = configManagerBean.getPrePasswordEntryUrl(); configManagerBean.setPrePasswordEntryUrl(null); pwmRequest.getPwmResponse().sendRedirect(originalUrl); return true; } return false; } if (configManagerBean.getPrePasswordEntryUrl() == null) { configManagerBean.setPrePasswordEntryUrl( pwmRequest.getHttpServletRequest().getRequestURL().toString()); } forwardToJsp(pwmRequest); return true; } private static void forwardToJsp(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException { final int persistentSeconds = figureMaxLoginSeconds(pwmRequest); final String time = new TimeDuration(persistentSeconds * 1000).asLongString(pwmRequest.getLocale()); final ConfigLoginHistory configLoginHistory = readConfigLoginHistory(pwmRequest); pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigLoginHistory, configLoginHistory); pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigPasswordRememberTime, time); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_LOGIN); } private static ConfigLoginHistory readConfigLoginHistory(final PwmRequest pwmRequest) { ConfigLoginHistory configLoginHistory = pwmRequest .getPwmApplication() .readAppAttribute( PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, ConfigLoginHistory.class); return configLoginHistory == null ? new ConfigLoginHistory() : configLoginHistory; } private static void updateLoginHistory( final PwmRequest pwmRequest, final UserIdentity userIdentity, boolean successful) { final ConfigLoginHistory configLoginHistory = readConfigLoginHistory(pwmRequest); final ConfigLoginEvent event = new ConfigLoginEvent( userIdentity == null ? "n/a" : userIdentity.toDisplayString(), new Date(), pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress()); final int maxEvents = Integer.parseInt( pwmRequest .getPwmApplication() .getConfig() .readAppProperty(AppProperty.CONFIG_HISTORY_MAX_ITEMS)); configLoginHistory.addEvent(event, maxEvents, successful); pwmRequest .getPwmApplication() .writeAppAttribute(PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, configLoginHistory); } private static class PersistentLoginInfo implements Serializable { private Date expireDate; private String password; private PersistentLoginInfo(Date expireDate, String password) { this.expireDate = expireDate; this.password = password; } public Date getExpireDate() { return expireDate; } public String getPassword() { return password; } } public static class ConfigLoginHistory implements Serializable { private final List<ConfigLoginEvent> successEvents = new ArrayList<>(); private final List<ConfigLoginEvent> failedEvents = new ArrayList<>(); void addEvent(ConfigLoginEvent event, int maxEvents, boolean successful) { final List<ConfigLoginEvent> events = successful ? successEvents : failedEvents; events.add(event); if (maxEvents > 0) { while (events.size() > maxEvents) { events.remove(0); } } } public List<ConfigLoginEvent> successEvents() { return Collections.unmodifiableList(successEvents); } public List<ConfigLoginEvent> failedEvents() { return Collections.unmodifiableList(failedEvents); } } public static class ConfigLoginEvent implements Serializable { private final String userIdentity; private final Date date; private final String networkAddress; public ConfigLoginEvent(String userIdentity, Date date, String networkAddress) { this.userIdentity = userIdentity; this.date = date; this.networkAddress = networkAddress; } public String getUserIdentity() { return userIdentity; } public Date getDate() { return date; } public String getNetworkAddress() { return networkAddress; } } static int figureMaxLoginSeconds(final PwmRequest pwmRequest) { return Integer.parseInt( pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS)); } }
public class HealthMonitor implements PwmService { private static final PwmLogger LOGGER = PwmLogger.forClass(HealthMonitor.class); private static final int MIN_INTERVAL_SECONDS = 30; private static final int MAX_INTERVAL_SECONDS = 60 * 60 * 24; private PwmApplication pwmApplication; private Set<HealthRecord> healthRecords = Collections.emptySet(); private final List<HealthChecker> healthCheckers = new ArrayList<>(); private Date lastHealthCheckDate = null; private int intervalSeconds = 0; private Map<HealthProperty, Serializable> healthProperties = new HashMap<>(); private STATUS status = STATUS.NEW; public enum HealthProperty { LdapVendorSameCheck, AdPasswordPolicyApiCheck, } public HealthMonitor() {} public Date getLastHealthCheckDate() { return lastHealthCheckDate; } public HealthStatus getMostSevereHealthStatus() { return getMostSevereHealthStatus(getHealthRecords()); } public static HealthStatus getMostSevereHealthStatus( final Collection<HealthRecord> healthRecords) { HealthStatus returnStatus = HealthStatus.GOOD; if (healthRecords != null) { for (HealthRecord record : healthRecords) { if (record.getStatus().getSeverityLevel() > returnStatus.getSeverityLevel()) { returnStatus = record.getStatus(); } } } return returnStatus; } public void registerHealthCheck(final HealthChecker healthChecker) { healthCheckers.add(healthChecker); } public Set<HealthRecord> getHealthRecords() { return getHealthRecords(false); } public synchronized Set<HealthRecord> getHealthRecords(final boolean refreshImmediate) { if (lastHealthCheckDate == null || refreshImmediate) { doHealthChecks(); } else { final long lastHealthCheckMs = lastHealthCheckDate.getTime(); final long lastValidHealthCheckMs = System.currentTimeMillis() - (intervalSeconds * 1000); if (lastHealthCheckMs < lastValidHealthCheckMs) { doHealthChecks(); } } return healthRecords; } public STATUS status() { return status; } public void init(PwmApplication pwmApplication) throws PwmException { status = STATUS.OPENING; this.pwmApplication = pwmApplication; this.intervalSeconds = Integer.parseInt( pwmApplication .getConfig() .readAppProperty(AppProperty.HEALTH_MIN_CHECK_INTERVAL_SECONDS)); if (intervalSeconds < MIN_INTERVAL_SECONDS) { intervalSeconds = MIN_INTERVAL_SECONDS; } else if (intervalSeconds > MAX_INTERVAL_SECONDS) { intervalSeconds = MAX_INTERVAL_SECONDS; } registerHealthCheck(new LDAPStatusChecker()); registerHealthCheck(new JavaChecker()); registerHealthCheck(new ConfigurationChecker()); registerHealthCheck(new LocalDBHealthChecker()); registerHealthCheck(new CertificateChecker()); final Set<HealthRecord> newHealthRecords = new HashSet<>(); newHealthRecords.add( new HealthRecord( HealthStatus.CAUTION, HealthTopic.Application, "Health Check operation has not been performed since PWM has started.")); healthRecords = Collections.unmodifiableSet(newHealthRecords); status = STATUS.OPEN; } public void close() { healthRecords = Collections.emptySet(); status = STATUS.CLOSED; } public List<HealthRecord> healthCheck() { return Collections.emptyList(); } private void doHealthChecks() { if (status != STATUS.OPEN) { return; } LOGGER.trace("beginning health check process"); final List<HealthRecord> newResults = new ArrayList<>(); for (final HealthChecker loopChecker : healthCheckers) { try { final List<HealthRecord> loopResults = loopChecker.doHealthCheck(pwmApplication); if (loopResults != null) { newResults.addAll(loopResults); } } catch (Exception e) { LOGGER.warn("unexpected error during healthCheck: " + e.getMessage(), e); } } for (final PwmService service : pwmApplication.getPwmServices()) { try { final List<HealthRecord> loopResults = service.healthCheck(); if (loopResults != null) { newResults.addAll(loopResults); } } catch (Exception e) { LOGGER.warn("unexpected error during healthCheck: " + e.getMessage(), e); } } final Set<HealthRecord> sortedRecordList = new TreeSet<>(); sortedRecordList.addAll(newResults); healthRecords = Collections.unmodifiableSet(sortedRecordList); lastHealthCheckDate = new Date(); LOGGER.trace("health check process completed"); } public ServiceInfo serviceInfo() { return new ServiceInfo(Collections.<DataStorageMethod>emptyList()); } public Map<HealthProperty, Serializable> getHealthProperties() { return healthProperties; } }
class LDAPAuthenticationRequest implements AuthenticationRequest { private static final PwmLogger LOGGER = PwmLogger.forClass(LDAPAuthenticationRequest.class); private static final String ORACLE_ATTR_PW_ALLOW_CHG_TIME = "passwordAllowChangeTime"; private final PwmApplication pwmApplication; private final SessionLabel sessionLabel; private final UserIdentity userIdentity; private final AuthenticationType requestedAuthType; private final PwmAuthenticationSource authenticationSource; private ChaiProvider userProvider; private AuthenticationStrategy strategy = AuthenticationStrategy.BIND; private Date startTime; private static int counter = 0; private int operationNumber = 0; LDAPAuthenticationRequest( PwmApplication pwmApplication, SessionLabel sessionLabel, UserIdentity userIdentity, AuthenticationType requestedAuthType, PwmAuthenticationSource authenticationSource) { this.pwmApplication = pwmApplication; this.sessionLabel = sessionLabel; this.userIdentity = userIdentity; this.requestedAuthType = requestedAuthType; this.authenticationSource = authenticationSource; this.operationNumber = counter++; } static AuthenticationRequest createLDAPAuthenticationRequest( PwmApplication pwmApplication, SessionLabel sessionLabel, UserIdentity userIdentity, AuthenticationType requestedAuthType, PwmAuthenticationSource authenticationSource) { return new LDAPAuthenticationRequest( pwmApplication, sessionLabel, userIdentity, requestedAuthType, authenticationSource); } @Override public AuthenticationResult authUsingUnknownPw() throws ChaiUnavailableException, PwmUnrecoverableException { initialize(); log(PwmLogLevel.TRACE, "beginning authentication using unknown password procedure"); PasswordData userPassword = null; final boolean configAlwaysUseProxy = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_USE_PROXY_FOR_FORGOTTEN); if (configAlwaysUseProxy) { strategy = AuthenticationStrategy.ADMIN_PROXY; } else { userPassword = learnUserPassword(); if (userPassword != null) { strategy = AuthenticationStrategy.READ_THEN_BIND; } else { userPassword = setTempUserPassword(); if (userPassword != null) { strategy = AuthenticationStrategy.WRITE_THEN_BIND; } } if (userPassword == null) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_UNKNOWN, "no available unknown-pw authentication method")); } } try { return authenticateUserImpl(userPassword); } catch (PwmOperationalException e) { if (strategy == AuthenticationStrategy.READ_THEN_BIND) { final String errorStr = "unable to authenticate with password read from directory, check proxy rights, ldap logs; error: " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_BAD_SESSION_PASSWORD, errorStr)); } else if (strategy == AuthenticationStrategy.WRITE_THEN_BIND) { final String errorStr = "unable to authenticate with temporary password, check proxy rights, ldap logs; error: " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_BAD_SESSION_PASSWORD, errorStr)); } throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_UNKNOWN, "unable to authenticate via authWithUnknownPw method: " + e.getMessage())); } } @Override public AuthenticationResult authenticateUser(final PasswordData password) throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException { initialize(); return authenticateUserImpl(password); } private AuthenticationResult authenticateUserImpl(final PasswordData password) throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException { if (startTime == null) { startTime = new Date(); } log( PwmLogLevel.DEBUG, "preparing to authenticate user using authenticationType=" + this.requestedAuthType + " using strategy " + this.strategy); final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager(); final IntruderManager intruderManager = pwmApplication.getIntruderManager(); intruderManager.convenience().checkUserIdentity(userIdentity); intruderManager.check(RecordType.ADDRESS, sessionLabel.getSrcAddress()); boolean allowBindAsUser = true; if (strategy == AuthenticationStrategy.ADMIN_PROXY) { allowBindAsUser = false; } if (allowBindAsUser) { try { testCredentials(userIdentity, password); } catch (PwmOperationalException e) { boolean permitAuthDespiteError = false; final ChaiProvider.DIRECTORY_VENDOR vendor = pwmApplication .getProxyChaiProvider(userIdentity.getLdapProfileID()) .getDirectoryVendor(); if (PwmError.PASSWORD_NEW_PASSWORD_REQUIRED == e.getError()) { if (vendor == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) { if (pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.AD_ALLOW_AUTH_REQUIRE_NEW_PWD)) { log( PwmLogLevel.INFO, "auth bind failed, but will allow login due to 'must change password on next login AD error', error: " + e.getErrorInformation().toDebugStr()); allowBindAsUser = false; permitAuthDespiteError = true; } } else if (vendor == ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS) { if (pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.ORACLE_DS_ALLOW_AUTH_REQUIRE_NEW_PWD)) { log( PwmLogLevel.INFO, "auth bind failed, but will allow login due to 'pwdReset' user attribute, error: " + e.getErrorInformation().toDebugStr()); allowBindAsUser = false; permitAuthDespiteError = true; } } } else if (PwmError.PASSWORD_EXPIRED == e.getError()) { // handle ad case where password is expired if (vendor == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) { if (pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.AD_ALLOW_AUTH_REQUIRE_NEW_PWD)) { if (!pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.AD_ALLOW_AUTH_EXPIRED)) { throw e; } log( PwmLogLevel.INFO, "auth bind failed, but will allow login due to 'password expired AD error', error: " + e.getErrorInformation().toDebugStr()); allowBindAsUser = false; permitAuthDespiteError = true; } } } if (!permitAuthDespiteError) { // auth failed, presumably due to wrong password. statisticsManager.incrementValue(Statistic.AUTHENTICATION_FAILURES); throw e; } } } else { // verify user is not account disabled AuthenticationUtility.checkIfUserEligibleToAuthentication(pwmApplication, userIdentity); } statisticsManager.incrementValue(Statistic.AUTHENTICATIONS); statisticsManager.updateEps(Statistic.EpsType.AUTHENTICATION, 1); statisticsManager.updateAverageValue( Statistic.AVG_AUTHENTICATION_TIME, TimeDuration.fromCurrent(startTime).getTotalMilliseconds()); final AuthenticationType returnAuthType; if (!allowBindAsUser) { returnAuthType = AuthenticationType.AUTH_BIND_INHIBIT; } else { if (requestedAuthType == null) { returnAuthType = AuthenticationType.AUTHENTICATED; } else { if (requestedAuthType == AuthenticationType.AUTH_WITHOUT_PASSWORD) { returnAuthType = AuthenticationType.AUTHENTICATED; } else if (requestedAuthType == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) { returnAuthType = AuthenticationType.AUTH_FROM_PUBLIC_MODULE; } else { returnAuthType = requestedAuthType; } } } final boolean useProxy = determineIfLdapProxyNeeded(returnAuthType, password); final ChaiProvider returnProvider = useProxy ? makeProxyProvider() : userProvider; final AuthenticationResult authenticationResult = new AuthenticationResult(returnProvider, returnAuthType, password); final StringBuilder debugMsg = new StringBuilder(); debugMsg.append("successful ldap authentication for ").append(userIdentity); debugMsg.append(" (").append(TimeDuration.fromCurrent(startTime).asCompactString()).append(")"); debugMsg.append(" type: ").append(returnAuthType).append(", using strategy ").append(strategy); debugMsg.append(", using proxy connection: ").append(useProxy); debugMsg .append(", returning bind dn: ") .append( returnProvider == null ? "none" : returnProvider.getChaiConfiguration().getSetting(ChaiSetting.BIND_DN)); log(PwmLogLevel.INFO, debugMsg); pwmApplication .getAuditManager() .submit( pwmApplication .getAuditManager() .createUserAuditRecord( AuditEvent.AUTHENTICATE, this.userIdentity, makeAuditLogMessage(returnAuthType), sessionLabel.getSrcAddress(), sessionLabel.getSrcHostname())); return authenticationResult; } private void initialize() { if (startTime != null) { throw new IllegalStateException("AuthenticationRequest can not be used more than once"); } startTime = new Date(); } private void testCredentials(final UserIdentity userIdentity, final PasswordData password) throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException { log(PwmLogLevel.TRACE, "beginning testCredentials process"); if (userIdentity == null || userIdentity.getUserDN() == null || userIdentity.getUserDN().length() < 1) { final String errorMsg = "attempt to authenticate with null userDN"; log(PwmLogLevel.DEBUG, errorMsg); throw new PwmOperationalException( new ErrorInformation(PwmError.ERROR_WRONGPASSWORD, errorMsg)); } if (password == null) { final String errorMsg = "attempt to authenticate with null password"; log(PwmLogLevel.DEBUG, errorMsg); throw new PwmOperationalException( new ErrorInformation(PwmError.ERROR_WRONGPASSWORD, errorMsg)); } // try authenticating the user using a normal ldap BIND operation. log(PwmLogLevel.TRACE, "attempting authentication using ldap BIND"); boolean bindSucceeded = false; try { // read a provider using the user's DN and password. userProvider = LdapOperationsHelper.createChaiProvider( sessionLabel, userIdentity.getLdapProfile(pwmApplication.getConfig()), pwmApplication.getConfig(), userIdentity.getUserDN(), password); // issue a read operation to trigger a bind. userProvider.readStringAttribute( userIdentity.getUserDN(), ChaiConstant.ATTR_LDAP_OBJECTCLASS); bindSucceeded = true; } catch (ChaiException e) { if (e.getErrorCode() != null && e.getErrorCode() == ChaiError.INTRUDER_LOCKOUT) { final String errorMsg = "intruder lockout detected for user " + userIdentity + " marking session as locked out: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INTRUDER_LDAP, errorMsg); log(PwmLogLevel.WARN, errorInformation.toDebugStr()); throw new PwmUnrecoverableException(errorInformation); } final PwmError pwmError = PwmError.forChaiError(e.getErrorCode()); final ErrorInformation errorInformation; if (pwmError != null && PwmError.ERROR_UNKNOWN != pwmError) { errorInformation = new ErrorInformation(pwmError, e.getMessage()); } else { errorInformation = new ErrorInformation( PwmError.ERROR_WRONGPASSWORD, "ldap error during password check: " + e.getMessage()); } log(PwmLogLevel.DEBUG, errorInformation.toDebugStr()); throw new PwmOperationalException(errorInformation); } finally { if (!bindSucceeded && userProvider != null) { try { userProvider.close(); userProvider = null; } catch (Throwable e) { log( PwmLogLevel.ERROR, "unexpected error closing invalid ldap connection after failed login attempt: " + e.getMessage()); } } } } private PasswordData learnUserPassword() throws ChaiUnavailableException, PwmUnrecoverableException { log(PwmLogLevel.TRACE, "beginning auth processes for user with unknown password"); if (userIdentity == null || userIdentity.getUserDN() == null || userIdentity.getUserDN().length() < 1) { throw new NullPointerException("invalid user (null)"); } final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()); final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider); // use chai (nmas) to retrieve user password if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.EDIRECTORY_READ_USER_PWD)) { String currentPass = null; try { final String readPassword = chaiUser.readPassword(); if (readPassword != null && readPassword.length() > 0) { currentPass = readPassword; log( PwmLogLevel.DEBUG, "successfully retrieved user's current password from ldap, now conducting standard authentication"); } } catch (Exception e) { log(PwmLogLevel.ERROR, "unable to retrieve user password from ldap: " + e.getMessage()); } // actually do the authentication since we have user pw. if (currentPass != null && currentPass.length() > 0) { return new PasswordData(currentPass); } } else { log(PwmLogLevel.TRACE, "skipping attempt to read user password, option disabled"); } return null; } private PasswordData setTempUserPassword() throws ChaiUnavailableException, ImpossiblePasswordPolicyException, PwmUnrecoverableException { final boolean configAlwaysUseProxy = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_USE_PROXY_FOR_FORGOTTEN); final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()); final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider); // try setting a random password on the account to authenticate. if (!configAlwaysUseProxy && requestedAuthType == AuthenticationType.AUTH_FROM_PUBLIC_MODULE) { log(PwmLogLevel.DEBUG, "attempting to set temporary random password"); PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( pwmApplication, sessionLabel, userIdentity, chaiUser, PwmConstants.DEFAULT_LOCALE); // create random password for user RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = new RandomPasswordGenerator.RandomGeneratorConfig(); randomGeneratorConfig.setSeedlistPhrases(RandomPasswordGenerator.DEFAULT_SEED_PHRASES); randomGeneratorConfig.setPasswordPolicy(passwordPolicy); final PasswordData currentPass = RandomPasswordGenerator.createRandomPassword( sessionLabel, randomGeneratorConfig, pwmApplication); try { final String oracleDS_PrePasswordAllowChangeTime = oraclePreTemporaryPwHandler(chaiProvider, chaiUser); // write the random password for the user. chaiUser.setPassword(currentPass.getStringValue()); oraclePostTemporaryPwHandler(chaiProvider, chaiUser, oracleDS_PrePasswordAllowChangeTime); log( PwmLogLevel.INFO, "user " + userIdentity + " password has been set to random value to use for user authentication"); } catch (ChaiOperationException e) { final String errorStr = "error setting random password for user " + userIdentity + " " + e.getMessage(); log(PwmLogLevel.ERROR, errorStr); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.ERROR_BAD_SESSION_PASSWORD, errorStr)); } return currentPass; } return null; } private String oraclePreTemporaryPwHandler( final ChaiProvider chaiProvider, final ChaiUser chaiUser) throws PwmUnrecoverableException, ChaiUnavailableException, ChaiOperationException { if (!pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.ORACLE_DS_ENABLE_MANIP_ALLOWCHANGETIME)) { return null; } if (ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS != chaiUser.getChaiProvider().getDirectoryVendor()) { return null; } // oracle DS special case: passwordAllowChangeTime handler final String oracleDS_PrePasswordAllowChangeTime = chaiProvider.readStringAttribute(chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME); log( PwmLogLevel.TRACE, "read OracleDS value of passwordAllowChangeTime value=" + oracleDS_PrePasswordAllowChangeTime); if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) { final Date date = OracleDSEntries.convertZuluToDate(oracleDS_PrePasswordAllowChangeTime); if (new Date().before(date)) { final String errorMsg = "change not permitted until " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(date); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.PASSWORD_TOO_SOON, errorMsg)); } } return oracleDS_PrePasswordAllowChangeTime; } private void oraclePostTemporaryPwHandler( final ChaiProvider chaiProvider, final ChaiUser chaiUser, final String oracleDS_PrePasswordAllowChangeTime) throws ChaiUnavailableException, ChaiOperationException { if (!pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.ORACLE_DS_ENABLE_MANIP_ALLOWCHANGETIME)) { return; } // oracle DS special case: passwordAllowChangeTime handler if (ChaiProvider.DIRECTORY_VENDOR.ORACLE_DS != chaiUser.getChaiProvider().getDirectoryVendor()) { return; } if (oracleDS_PrePasswordAllowChangeTime != null && !oracleDS_PrePasswordAllowChangeTime.isEmpty()) { // write back the original pre-password allow change time. final Set<String> values = new HashSet<>(Collections.singletonList(oracleDS_PrePasswordAllowChangeTime)); chaiProvider.writeStringAttribute( chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME, values, true); log( PwmLogLevel.TRACE, "re-wrote passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + ", value=" + oracleDS_PrePasswordAllowChangeTime); } else { final String oracleDS_PostPasswordAllowChangeTime = chaiProvider.readStringAttribute(chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME); if (oracleDS_PostPasswordAllowChangeTime != null && !oracleDS_PostPasswordAllowChangeTime.isEmpty()) { // password allow change time has appeared, but wasn't present previously, so delete it. log( PwmLogLevel.TRACE, "a new value for passwordAllowChangeTime attribute to user " + chaiUser.getEntryDN() + " has appeared, will remove"); chaiProvider.deleteStringAttributeValue( chaiUser.getEntryDN(), ORACLE_ATTR_PW_ALLOW_CHG_TIME, oracleDS_PostPasswordAllowChangeTime); log( PwmLogLevel.TRACE, "deleted attribute value for passwordAllowChangeTime attribute on user " + chaiUser.getEntryDN()); } } } private boolean determineIfLdapProxyNeeded( final AuthenticationType authenticationType, final PasswordData userPassword) throws ChaiUnavailableException, PwmUnrecoverableException { if (userProvider != null) { return false; } final boolean authIsBindInhibit = authenticationType == AuthenticationType.AUTH_BIND_INHIBIT; final boolean authIsFromForgottenPw = authenticationType == AuthenticationType.AUTH_FROM_PUBLIC_MODULE; final boolean alwaysUseProxyIsEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_USE_PROXY_FOR_FORGOTTEN); final boolean passwordNotPresent = userPassword == null; return authIsBindInhibit || authIsFromForgottenPw && (alwaysUseProxyIsEnabled || passwordNotPresent); } private ChaiProvider makeProxyProvider() throws ChaiUnavailableException, PwmUnrecoverableException { final LdapProfile profile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID()); final String proxyDN = profile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN); final PasswordData proxyPassword = profile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD); return LdapOperationsHelper.createChaiProvider( sessionLabel, profile, pwmApplication.getConfig(), proxyDN, proxyPassword); } private void log(final PwmLogLevel level, final CharSequence message) { LOGGER.log(level, sessionLabel, "authID=" + operationNumber + ", " + message); } private String makeAuditLogMessage(AuthenticationType authenticationType) { return "type=" + authenticationType.toString() + ", " + "source=" + (authenticationSource == null ? "null" : authenticationSource.toString()); } }
public class NAAFEndPoint { private static final PwmLogger LOGGER = PwmLogger.forClass(NAAFEndPoint.class); private final String id; private final String salt; private final String secret; private final String endpointURL; private String endpoint_session_id; private PwmHttpClient pwmHttpClient; private Locale locale; public NAAFEndPoint(final PwmApplication pwmApplication, final String url, final Locale locale) throws PwmUnrecoverableException { this.locale = locale; final Configuration config = pwmApplication.getConfig(); this.endpointURL = url; this.id = config.readAppProperty(AppProperty.NAAF_ID); this.secret = config.readAppProperty(AppProperty.NAAF_SECRET); final int saltLength = Integer.parseInt(config.readAppProperty(AppProperty.NAAF_SALT_LENGTH)); this.salt = PwmRandom.getInstance().alphaNumericString(saltLength); final X509Certificate[] naafWsCerts = config.readSettingAsCertificate(PwmSetting.NAAF_WS_CERTIFICATE); final PwmHttpClientConfiguration pwmHttpClientConfiguration = new PwmHttpClientConfiguration.Builder().setCertificate(naafWsCerts).create(); this.pwmHttpClient = new PwmHttpClient(pwmApplication, null, pwmHttpClientConfiguration); establishEndpointSession(); } public void establishEndpointSession() throws PwmUnrecoverableException { LOGGER.debug("establishing endpoint connection to " + endpointURL); final String m1 = id + salt; final String m1Hash = SecureEngine.hash(m1, PwmHashAlgorithm.SHA256).toLowerCase(); final String m2 = secret + m1Hash; final String m2Hash = SecureEngine.hash(m2, PwmHashAlgorithm.SHA256).toLowerCase(); final HashMap<String, Object> initConnectMap = new HashMap<>(); initConnectMap.put("salt", salt); initConnectMap.put("endpoint_secret_hash", m2Hash); initConnectMap.put("session_data", new HashMap<String, String>()); final PwmHttpClientResponse response = makeApiRequest(HttpMethod.POST, "/endpoints/" + id + "/sessions", initConnectMap); final String body = response.getBody(); final Map<String, String> responseValues = JsonUtil.deserializeStringMap(body); endpoint_session_id = responseValues.get("endpoint_session_id"); LOGGER.debug( "endpoint connection established to " + endpointURL + ", endpoint_session_id=" + endpoint_session_id); } String getEndpoint_session_id() { return endpoint_session_id; } String getEndpointURL() { return endpointURL; } PwmHttpClient getPwmHttpClient() { return pwmHttpClient; } public List<NAAFChainBean> readChains(final String username) throws PwmUnrecoverableException { final Map<String, String> urlParams = new LinkedHashMap<>(); urlParams.put("username", username); urlParams.put("application", "NAM"); urlParams.put("is_trusted", "true"); urlParams.put("endpoint_session_id", this.getEndpoint_session_id()); final String url = PwmURL.appendAndEncodeUrlParameters("/logon/chains", urlParams); final PwmHttpClientResponse response = makeApiRequest(HttpMethod.POST, url, null); final NAAFChainInformationResponseBean naafChainInformationResponseBean = JsonUtil.deserialize(response.getBody(), NAAFChainInformationResponseBean.class); return naafChainInformationResponseBean.getChains(); } PwmHttpClientResponse makeApiRequest( final HttpMethod method, final String urlPart, final Serializable body) throws PwmUnrecoverableException { final Map<String, String> headers = new HashMap<>(); headers.put(PwmConstants.HttpHeader.Content_Type.getHttpName(), "application/json"); if (locale != null) { headers.put(PwmConstants.HttpHeader.Accept_Language.getHttpName(), locale.toLanguageTag()); } final PwmHttpClientRequest pwmHttpClientRequest = new PwmHttpClientRequest( method, getEndpointURL() + urlPart, JsonUtil.serialize(body), headers); return pwmHttpClient.makeRequest(pwmHttpClientRequest); } }
public class NMASUAWSOperator implements CrOperator { private static final PwmLogger LOGGER = PwmLogger.forClass(NMASUAWSOperator.class); final PwmApplication pwmApplication; public NMASUAWSOperator(PwmApplication pwmApplication) { this.pwmApplication = pwmApplication; } public void close() {} @Override public ResponseSet readResponseSet( final ChaiUser theUser, final UserIdentity userIdentity, final String userGUID) throws PwmUnrecoverableException { return readResponsesFromNovellUA(pwmApplication, theUser); } @Override public ResponseInfoBean readResponseInfo( final ChaiUser theUser, final UserIdentity userIdentity, final String userGUID) throws PwmUnrecoverableException { final ResponseSet responseSet = readResponsesFromNovellUA(pwmApplication, theUser); if (responseSet == null) { return null; } final Map<Challenge, String> crMap = new LinkedHashMap<>(); final Map<Challenge, String> helpdeskCrMap = new LinkedHashMap<>(); try { for (final Challenge loopChallenge : responseSet.getChallengeSet().getChallenges()) { crMap.put(loopChallenge, ""); } for (final Challenge loopChallenge : responseSet.getHelpdeskResponses().keySet()) { helpdeskCrMap.put(loopChallenge, ""); } return new ResponseInfoBean( crMap, helpdeskCrMap, PwmConstants.DEFAULT_LOCALE, responseSet.getChallengeSet().getMinRandomRequired(), responseSet.getChallengeSet().getIdentifier(), DataStorageMethod.NMASUAWS, null); } catch (ChaiValidationException e) { LOGGER.error( "unexpected error converting NMASUserAppWebService ResponseSet to ResponseInfoBean: " + e.getMessage()); } return null; // To change body of implemented methods use File | Settings | File Templates. } @Override public void clearResponses(ChaiUser theUser, String userGUID) throws PwmUnrecoverableException { throw new UnsupportedOperationException( "NMASUserAppWebService C/R implementation does not support clearing responses"); } @Override public void writeResponses(ChaiUser theUser, String userGuid, ResponseInfoBean responseInfoBean) throws PwmUnrecoverableException { throw new UnsupportedOperationException( "NMASUserAppWebService C/R implementation does not support writing responses"); } private static ResponseSet readResponsesFromNovellUA( final PwmApplication pwmApplication, final ChaiUser theUser) throws PwmUnrecoverableException { final String novellUserAppWebServiceURL = pwmApplication .getConfig() .readSettingAsString(PwmSetting.EDIRECTORY_PWD_MGT_WEBSERVICE_URL); try { LOGGER.trace("establishing connection to web service at " + novellUserAppWebServiceURL); final PasswordManagementServiceLocator locater = new PasswordManagementServiceLocator(); final PasswordManagement service = locater.getPasswordManagementPort(new URL(novellUserAppWebServiceURL)); ((Stub) service)._setProperty(javax.xml.rpc.Stub.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); final ProcessUserRequest userRequest = new ProcessUserRequest(theUser.getEntryDN()); final ForgotPasswordWSBean processUserResponse = service.processUser(userRequest); if (processUserResponse.isTimeout() || processUserResponse.isError()) { throw new Exception( "novell web service reports " + (processUserResponse.isTimeout() ? "timeout" : "error") + ": " + processUserResponse.getMessage()); } if (processUserResponse.getChallengeQuestions() != null) { return new NovellWSResponseSet(service, processUserResponse); } } catch (Throwable e) { final String errorMsg = "error retrieving novell user responses from web service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_UNREACHABLE, errorMsg); throw new PwmUnrecoverableException(errorInformation); } return null; } private static class NovellWSResponseSet implements ResponseSet, Serializable { private final transient PasswordManagement service; private final String userDN; private final ChallengeSet challengeSet; private final Locale locale; private final String localIdentifier; private static int lastLocalIdentifier; public NovellWSResponseSet(final PasswordManagement service, final ForgotPasswordWSBean wsBean) throws ChaiValidationException { this.userDN = wsBean.getUserDN(); this.service = service; this.localIdentifier = "NovellWSResponseSet #" + String.valueOf(lastLocalIdentifier++); LOGGER.debug("initialized " + localIdentifier); final List<Challenge> challenges = new ArrayList<>(); for (final String loopQuestion : wsBean.getChallengeQuestions()) { final Challenge loopChallenge = new ChaiChallenge(true, loopQuestion, 1, 255, true, 0, false); challenges.add(loopChallenge); } locale = PwmConstants.DEFAULT_LOCALE; challengeSet = new ChaiChallengeSet(challenges, 0, locale, "NovellWSResponseSet derived ChallengeSet"); } public ChallengeSet getChallengeSet() { return challengeSet; } public ChallengeSet getPresentableChallengeSet() throws ChaiValidationException { return challengeSet; } public boolean meetsChallengeSetRequirements(final ChallengeSet challengeSet) { if (challengeSet.getRequiredChallenges().size() > this.getChallengeSet().getRequiredChallenges().size()) { LOGGER.debug( localIdentifier + "failed meetsChallengeSetRequirements, not enough required challenge"); return false; } for (final Challenge loopChallenge : challengeSet.getRequiredChallenges()) { if (loopChallenge.isAdminDefined()) { if (!this.getChallengeSet() .getChallengeTexts() .contains(loopChallenge.getChallengeText())) { LOGGER.debug( localIdentifier + "failed meetsChallengeSetRequirements, missing required challenge text: '" + loopChallenge.getChallengeText() + "'"); return false; } } } if (challengeSet.getMinRandomRequired() > 0) { if (this.getChallengeSet().getChallenges().size() < challengeSet.getMinRandomRequired()) { LOGGER.debug( localIdentifier + "failed meetsChallengeSetRequirements, not enough questions to meet minrandom; minRandomRequired=" + challengeSet.getMinRandomRequired() + ", ChallengesInSet=" + this.getChallengeSet().getChallenges().size()); return false; } } return true; } public String stringValue() throws UnsupportedOperationException { return "NovellWSResponseSet derived ResponseSet"; } public boolean test(final Map<Challenge, String> responseTest) throws ChaiUnavailableException { if (service == null) { LOGGER.error( localIdentifier + "beginning web service 'processChaRes' response test, however service bean is not in session memory, aborting response test..."); return false; } LOGGER.trace(localIdentifier + "beginning web service 'processChaRes' response test "); final String[] responseArray = new String[challengeSet.getAdminDefinedChallenges().size()]; { int i = 0; for (final Challenge loopChallenge : challengeSet.getAdminDefinedChallenges()) { final String loopResponse = responseTest.get(loopChallenge); responseArray[i] = loopResponse; i++; } } final ProcessChaResRequest request = new ProcessChaResRequest(); request.setChaAnswers(responseArray); request.setUserDN(userDN); try { final ForgotPasswordWSBean response = service.processChaRes(request); if (response.isTimeout()) { LOGGER.error(localIdentifier + "web service reports timeout: " + response.getMessage()); return false; } if (response.isError()) { if ("Account restrictions prevent you from logging in. See your administrator for more details." .equals(response.getMessage())) { // throw PwmUnrecoverableException.createPwmException(PwmError.ERROR_INTRUDER_USER); } LOGGER.error(localIdentifier + "web service reports error: " + response.getMessage()); return false; } LOGGER.debug(localIdentifier + "web service has validated the users responses"); return true; } catch (RemoteException e) { LOGGER.error(localIdentifier + "error processing web service response: " + e.getMessage()); } return false; // To change body of implemented methods use File | Settings | File Templates. } public Locale getLocale() throws ChaiUnavailableException, IllegalStateException, ChaiOperationException { return locale; } public Date getTimestamp() throws ChaiUnavailableException, IllegalStateException, ChaiOperationException { return new Date(); } @Override public Map<Challenge, String> getHelpdeskResponses() { return Collections.emptyMap(); } @Override public String toString() { return "NovellWSResponseSet holding {" + challengeSet.toString() + "}"; } @Override public List<ChallengeBean> asChallengeBeans(boolean includeAnswers) { return Collections.emptyList(); } @Override public List<ChallengeBean> asHelpdeskChallengeBeans(boolean includeAnswers) { return Collections.emptyList(); } } }
@Path("/app-data") public class RestAppDataServer extends AbstractRestServer { private static final PwmLogger LOGGER = PwmLogger.forClass(RestAppDataServer.class); public static class AppData implements Serializable { public Map<String, Object> PWM_GLOBAL; } @GET @Produces(MediaType.TEXT_HTML) public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException { return RestServerHelper.doHtmlRedirect(); } @GET @Path("/audit") @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") public Response doGetAppAuditData(@QueryParam("maximum") int maximum) throws ChaiUnavailableException, PwmUnrecoverableException { maximum = maximum > 0 ? maximum : 10 * 1000; final RestRequestBean restRequestBean; try { final ServicePermissions servicePermissions = new ServicePermissions(); servicePermissions.setAdminOnly(true); servicePermissions.setAuthRequired(true); servicePermissions.setBlockExternal(true); restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, null); } catch (PwmUnrecoverableException e) { return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse(); } final ArrayList<UserAuditRecord> userRecords = new ArrayList<>(); final ArrayList<HelpdeskAuditRecord> helpdeskRecords = new ArrayList<>(); final ArrayList<SystemAuditRecord> systemRecords = new ArrayList<>(); final Iterator<AuditRecord> iterator = restRequestBean.getPwmApplication().getAuditManager().readVault(); int counter = 0; while (iterator.hasNext() && counter <= maximum) { final AuditRecord loopRecord = iterator.next(); counter++; if (loopRecord instanceof SystemAuditRecord) { systemRecords.add((SystemAuditRecord) loopRecord); } else if (loopRecord instanceof HelpdeskAuditRecord) { helpdeskRecords.add((HelpdeskAuditRecord) loopRecord); } else if (loopRecord instanceof UserAuditRecord) { userRecords.add((UserAuditRecord) loopRecord); } } final HashMap<String, List> outputMap = new HashMap<>(); outputMap.put("user", userRecords); outputMap.put("helpdesk", helpdeskRecords); outputMap.put("system", systemRecords); final RestResultBean restResultBean = new RestResultBean(); restResultBean.setData(outputMap); LOGGER.debug(restRequestBean.getPwmSession(), "output " + counter + " audit records."); return restResultBean.asJsonResponse(); } @GET @Path("/session") @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") public Response doGetAppSessionData(@QueryParam("maximum") int maximum) throws ChaiUnavailableException, PwmUnrecoverableException { maximum = maximum > 0 ? maximum : 10 * 1000; final RestRequestBean restRequestBean; try { final ServicePermissions servicePermissions = new ServicePermissions(); servicePermissions.setAdminOnly(true); servicePermissions.setAuthRequired(true); servicePermissions.setBlockExternal(true); restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, null); } catch (PwmUnrecoverableException e) { return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse(); } if (!restRequestBean .getPwmSession() .getSessionManager() .checkPermission(restRequestBean.getPwmApplication(), Permission.PWMADMIN)) { final ErrorInformation errorInfo = PwmError.ERROR_UNAUTHORIZED.toInfo(); return RestResultBean.fromError(errorInfo, restRequestBean).asJsonResponse(); } final ArrayList<SessionStateInfoBean> gridData = new ArrayList<>(); int counter = 0; final Iterator<SessionStateInfoBean> infos = restRequestBean.getPwmApplication().getSessionTrackService().getSessionInfoIterator(); while (counter < maximum && infos.hasNext()) { gridData.add(infos.next()); counter++; } final RestResultBean restResultBean = new RestResultBean(); restResultBean.setData(gridData); return restResultBean.asJsonResponse(); } @GET @Path("/intruder") @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") public Response doGetAppIntruderData(@QueryParam("maximum") int maximum) throws ChaiUnavailableException, PwmUnrecoverableException { maximum = maximum > 0 ? maximum : 10 * 1000; final RestRequestBean restRequestBean; try { final ServicePermissions servicePermissions = new ServicePermissions(); servicePermissions.setAdminOnly(true); servicePermissions.setAuthRequired(true); servicePermissions.setBlockExternal(true); restRequestBean = RestServerHelper.initializeRestRequest(request, response, servicePermissions, null); } catch (PwmUnrecoverableException e) { return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse(); } if (!restRequestBean .getPwmSession() .getSessionManager() .checkPermission(restRequestBean.getPwmApplication(), Permission.PWMADMIN)) { final ErrorInformation errorInfo = PwmError.ERROR_UNAUTHORIZED.toInfo(); return RestResultBean.fromError(errorInfo, restRequestBean).asJsonResponse(); } final TreeMap<String, Object> returnData = new TreeMap<>(); try { for (final RecordType recordType : RecordType.values()) { returnData.put( recordType.toString(), restRequestBean .getPwmApplication() .getIntruderManager() .getRecords(recordType, maximum)); } } catch (PwmOperationalException e) { final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage()); return RestResultBean.fromError(errorInfo, restRequestBean).asJsonResponse(); } final RestResultBean restResultBean = new RestResultBean(); restResultBean.setData(returnData); return restResultBean.asJsonResponse(); } @GET @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") @Path("/client") public Response doGetAppClientData( @QueryParam("pageUrl") String pageUrl, @PathParam(value = "eTagUri") final String eTagUri, @Context HttpServletRequest request, @Context HttpServletResponse response) throws PwmUnrecoverableException, IOException, ChaiUnavailableException { final int maxCacheAgeSeconds = 60 * 5; final RestRequestBean restRequestBean; try { restRequestBean = RestServerHelper.initializeRestRequest( request, response, ServicePermissions.PUBLIC, null); } catch (PwmUnrecoverableException e) { return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse(); } final String eTagValue = makeClientEtag( restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), request); // check the incoming header; final String ifNoneMatchValue = request.getHeader("If-None-Match"); if (ifNoneMatchValue != null && ifNoneMatchValue.equals(eTagValue) && eTagValue.equals(eTagUri)) { return Response.notModified().build(); } response.setHeader("ETag", eTagValue); response.setDateHeader("Expires", System.currentTimeMillis() + (maxCacheAgeSeconds * 1000)); response.setHeader("Cache-Control", "public, max-age=" + maxCacheAgeSeconds); final AppData appData = makeAppData( restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), request, response, pageUrl); final RestResultBean restResultBean = new RestResultBean(); restResultBean.setData(appData); return restResultBean.asJsonResponse(); } @GET @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") @Path("/strings/{bundle}") public Response doGetStringData(@PathParam(value = "bundle") final String bundleName) throws PwmUnrecoverableException, IOException, ChaiUnavailableException { final int maxCacheAgeSeconds = 60 * 5; final RestRequestBean restRequestBean; try { restRequestBean = RestServerHelper.initializeRestRequest( request, response, ServicePermissions.PUBLIC, null); } catch (PwmUnrecoverableException e) { return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse(); } final String eTagValue = makeClientEtag( restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), request); response.setHeader("ETag", eTagValue); response.setDateHeader("Expires", System.currentTimeMillis() + (maxCacheAgeSeconds * 1000)); response.setHeader("Cache-Control", "public, max-age=" + maxCacheAgeSeconds); try { final LinkedHashMap<String, String> displayData = new LinkedHashMap<>( makeDisplayData( restRequestBean.getPwmApplication(), restRequestBean.getPwmSession(), bundleName)); final RestResultBean restResultBean = new RestResultBean(); restResultBean.setData(displayData); return restResultBean.asJsonResponse(); } catch (Exception e) { final String errorMSg = "error during rest /strings call for bundle " + bundleName + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMSg); return RestResultBean.fromError(errorInformation).asJsonResponse(); } } private AppData makeAppData( final PwmApplication pwmApplication, final PwmSession pwmSession, final HttpServletRequest request, final HttpServletResponse response, final String pageUrl) throws ChaiUnavailableException, PwmUnrecoverableException { final AppData appData = new AppData(); appData.PWM_GLOBAL = makeClientData(pwmApplication, pwmSession, request, response, pageUrl); return appData; } private Map<String, String> makeDisplayData( final PwmApplication pwmApplication, final PwmSession pwmSession, final String bundleName) { Class displayClass = LocaleHelper.classForShortName(bundleName); displayClass = displayClass == null ? Display.class : displayClass; final Locale userLocale = pwmSession.getSessionStateBean().getLocale(); final Configuration config = pwmApplication.getConfig(); final TreeMap<String, String> displayStrings = new TreeMap<>(); final ResourceBundle bundle = ResourceBundle.getBundle(displayClass.getName()); try { final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication); for (final String key : new TreeSet<>(Collections.list(bundle.getKeys()))) { String displayValue = LocaleHelper.getLocalizedMessage(userLocale, key, config, displayClass); displayValue = macroMachine.expandMacros(displayValue); displayStrings.put(key, displayValue); } } catch (Exception e) { LOGGER.error(pwmSession, "error expanding macro display value: " + e.getMessage()); } return displayStrings; } private static Map<String, Object> makeClientData( final PwmApplication pwmApplication, final PwmSession pwmSession, final HttpServletRequest request, final HttpServletResponse response, final String pageUrl) throws ChaiUnavailableException, PwmUnrecoverableException { final Configuration config = pwmApplication.getConfig(); final TreeMap<String, Object> settingMap = new TreeMap<>(); settingMap.put( "client.ajaxTypingTimeout", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_AJAX_TYPING_TIMEOUT))); settingMap.put( "client.ajaxTypingWait", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_AJAX_TYPING_WAIT))); settingMap.put( "client.activityMaxEpsRate", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_ACTIVITY_MAX_EPS_RATE))); settingMap.put( "client.js.enableHtml5Dialog", Boolean.parseBoolean(config.readAppProperty(AppProperty.CLIENT_JS_ENABLE_HTML5DIALOG))); settingMap.put( "client.pwShowRevertTimeout", Integer.parseInt(config.readAppProperty(AppProperty.CLIENT_PW_SHOW_REVERT_TIMEOUT))); settingMap.put( "enableIdleTimeout", config.readSettingAsBoolean(PwmSetting.DISPLAY_IDLE_TIMEOUT)); settingMap.put( "pageLeaveNotice", config.readSettingAsLong(PwmSetting.SECURITY_PAGE_LEAVE_NOTICE_TIMEOUT)); settingMap.put( "setting-showHidePasswordFields", pwmApplication .getConfig() .readSettingAsBoolean( password.pwm.config.PwmSetting.DISPLAY_SHOW_HIDE_PASSWORD_FIELDS)); settingMap.put("setting-displayEula", PwmConstants.ENABLE_EULA_DISPLAY); settingMap.put( "setting-showStrengthMeter", config.readSettingAsBoolean(PwmSetting.PASSWORD_SHOW_STRENGTH_METER)); { long idleSeconds = config.readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS); if (pageUrl == null || pageUrl.isEmpty()) { LOGGER.warn(pwmSession, "request to /client data did not incliude pageUrl"); } else { try { final PwmURL pwmURL = new PwmURL(new URI(pageUrl), request.getContextPath()); final TimeDuration maxIdleTime = IdleTimeoutCalculator.idleTimeoutForRequest(pwmURL, pwmApplication, pwmSession); idleSeconds = maxIdleTime.getTotalSeconds(); } catch (Exception e) { LOGGER.error( pwmSession, "error determining idle timeout time for request: " + e.getMessage()); } } settingMap.put("MaxInactiveInterval", idleSeconds); } settingMap.put("paramName.locale", config.readAppProperty(AppProperty.HTTP_PARAM_NAME_LOCALE)); settingMap.put("startupTime", pwmApplication.getStartupTime()); settingMap.put("applicationMode", pwmApplication.getApplicationMode()); final String contextPath = request.getContextPath(); settingMap.put("url-context", contextPath); settingMap.put( "url-logout", contextPath + PwmServletDefinition.Logout.servletUrl() + "?idle=true"); settingMap.put("url-command", contextPath + PwmServletDefinition.Command.servletUrl()); settingMap.put( "url-resources", contextPath + "/public/resources" + pwmApplication.getResourceServletService().getResourceNonce()); settingMap.put("url-restservice", contextPath + "/public/rest"); { String passwordGuideText = pwmApplication .getConfig() .readSettingAsLocalizedString( PwmSetting.DISPLAY_PASSWORD_GUIDE_TEXT, pwmSession.getSessionStateBean().getLocale()); final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication); passwordGuideText = macroMachine.expandMacros(passwordGuideText); settingMap.put("passwordGuideText", passwordGuideText); } { final List<String> formTypeOptions = new ArrayList<>(); for (final FormConfiguration.Type type : FormConfiguration.Type.values()) { formTypeOptions.add(type.toString()); } settingMap.put("formTypeOptions", formTypeOptions); } { final List<String> actionTypeOptions = new ArrayList<>(); for (final ActionConfiguration.Type type : ActionConfiguration.Type.values()) { actionTypeOptions.add(type.toString()); } settingMap.put("actionTypeOptions", actionTypeOptions); } { final List<String> epsTypes = new ArrayList<>(); for (final Statistic.EpsType loopEpsType : Statistic.EpsType.values()) { epsTypes.add(loopEpsType.toString()); } settingMap.put("epsTypes", epsTypes); } { final List<String> epsDurations = new ArrayList<>(); for (final Statistic.EpsDuration loopEpsDuration : Statistic.EpsDuration.values()) { epsDurations.add(loopEpsDuration.toString()); } settingMap.put("epsDurations", epsDurations); } { final Map<String, String> localeInfo = new TreeMap<>(); final Map<String, String> localeDisplayNames = new TreeMap<>(); final Map<String, String> localeFlags = new TreeMap<>(); for (final Locale locale : pwmApplication.getConfig().getKnownLocales()) { final String flagCode = pwmApplication.getConfig().getKnownLocaleFlagMap().get(locale); localeFlags.put(locale.toString(), flagCode); localeInfo.put( locale.toString(), locale.getDisplayName() + " - " + locale.getDisplayLanguage(locale)); localeDisplayNames.put(locale.toString(), locale.getDisplayLanguage()); } settingMap.put("localeInfo", localeInfo); settingMap.put("localeDisplayNames", localeDisplayNames); settingMap.put("localeFlags", localeFlags); settingMap.put("defaultLocale", PwmConstants.DEFAULT_LOCALE.toString()); } if (pwmApplication .getConfig() .readSettingAsEnum(PwmSetting.LDAP_SELECTABLE_CONTEXT_MODE, SelectableContextMode.class) != SelectableContextMode.NONE) { final Map<String, Map<String, String>> ldapProfiles = new LinkedHashMap<>(); for (final String ldapProfile : pwmApplication.getConfig().getLdapProfiles().keySet()) { final Map<String, String> contexts = pwmApplication.getConfig().getLdapProfiles().get(ldapProfile).getLoginContexts(); ldapProfiles.put(ldapProfile, contexts); } settingMap.put("ldapProfiles", ldapProfiles); } return settingMap; } public static String makeClientEtag(final PwmRequest pwmRequest) throws PwmUnrecoverableException { return makeClientEtag( pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest()); } public static String makeClientEtag( final PwmApplication pwmApplication, final PwmSession pwmSession, final HttpServletRequest httpServletRequest) throws PwmUnrecoverableException { final StringBuilder inputString = new StringBuilder(); inputString.append(PwmConstants.BUILD_NUMBER); inputString.append(pwmApplication.getStartupTime().getTime()); inputString.append(httpServletRequest.getSession().getMaxInactiveInterval()); inputString.append(pwmApplication.getInstanceNonce()); if (pwmSession.getSessionStateBean().getLocale() != null) { inputString.append(pwmSession.getSessionStateBean().getLocale()); } inputString.append(pwmSession.getSessionStateBean().getSessionID()); if (pwmSession.isAuthenticated()) { inputString.append(pwmSession.getUserInfoBean().getUserGuid()); inputString.append(pwmSession.getLoginInfoBean().getAuthTime()); } return SecureEngine.hash(inputString.toString(), PwmHashAlgorithm.SHA1).toLowerCase(); } }
/** @author Jason D. Rivard */ public class DatabaseAccessorImpl implements PwmService, DatabaseAccessor { // ------------------------------ FIELDS ------------------------------ private static final PwmLogger LOGGER = PwmLogger.forClass(DatabaseAccessorImpl.class, true); private static final String KEY_COLUMN = "id"; private static final String VALUE_COLUMN = "value"; private static final int KEY_COLUMN_LENGTH = PwmConstants.DATABASE_ACCESSOR_KEY_LENGTH; private static final String KEY_TEST = "write-test-key"; private static final String KEY_ENGINE_START_PREFIX = "engine-start-"; private DBConfiguration dbConfiguration; private Driver driver; private String instanceID; private boolean traceLogging; private volatile Connection connection; private volatile PwmService.STATUS status = PwmService.STATUS.NEW; private ErrorInformation lastError; private PwmApplication pwmApplication; // --------------------------- CONSTRUCTORS --------------------------- public DatabaseAccessorImpl() {} // ------------------------ INTERFACE METHODS ------------------------ // --------------------- Interface PwmService --------------------- public STATUS status() { return status; } public void init(final PwmApplication pwmApplication) throws PwmException { this.pwmApplication = pwmApplication; final Configuration config = pwmApplication.getConfig(); init(config); } public void init(final Configuration config) throws PwmException { final Map<FileValue.FileInformation, FileValue.FileContent> fileValue = config.readSettingAsFile(PwmSetting.DATABASE_JDBC_DRIVER); final byte[] jdbcDriverBytes; if (fileValue != null && !fileValue.isEmpty()) { final FileValue.FileInformation fileInformation1 = fileValue.keySet().iterator().next(); final FileValue.FileContent fileContent = fileValue.get(fileInformation1); jdbcDriverBytes = fileContent.getContents(); } else { jdbcDriverBytes = null; } this.dbConfiguration = new DBConfiguration( config.readSettingAsString(PwmSetting.DATABASE_CLASS), config.readSettingAsString(PwmSetting.DATABASE_URL), config.readSettingAsString(PwmSetting.DATABASE_USERNAME), config.readSettingAsPassword(PwmSetting.DATABASE_PASSWORD), config.readSettingAsString(PwmSetting.DATABASE_COLUMN_TYPE_KEY), config.readSettingAsString(PwmSetting.DATABASE_COLUMN_TYPE_VALUE), jdbcDriverBytes); this.instanceID = pwmApplication == null ? null : pwmApplication.getInstanceID(); this.traceLogging = config.readSettingAsBoolean(PwmSetting.DATABASE_DEBUG_TRACE); if (this.dbConfiguration.isEmpty()) { status = PwmService.STATUS.CLOSED; LOGGER.debug("skipping database connection open, no connection parameters configured"); } } public void close() { status = PwmService.STATUS.CLOSED; if (connection != null) { try { connection.close(); } catch (Exception e) { LOGGER.debug("error while closing DB: " + e.getMessage()); } } try { driver = null; } catch (Exception e) { LOGGER.debug("error while de-registering driver: " + e.getMessage()); } connection = null; } public List<HealthRecord> healthCheck() { if (status == PwmService.STATUS.CLOSED) { return Collections.emptyList(); } final List<HealthRecord> returnRecords = new ArrayList<>(); try { preOperationCheck(); } catch (DatabaseException e) { lastError = e.getErrorInformation(); returnRecords.add( new HealthRecord( HealthStatus.WARN, HealthTopic.Database, "Database server is not available: " + e.getErrorInformation().toDebugStr())); return returnRecords; } try { final Map<String, String> tempMap = new HashMap<>(); tempMap.put("instance", instanceID); tempMap.put("date", (new java.util.Date()).toString()); this.put( DatabaseTable.PWM_META, DatabaseAccessorImpl.KEY_TEST, JsonUtil.serializeMap(tempMap)); } catch (PwmException e) { returnRecords.add( new HealthRecord( HealthStatus.WARN, HealthTopic.Database, "Error writing to database: " + e.getErrorInformation().toDebugStr())); return returnRecords; } if (lastError != null) { final TimeDuration errorAge = TimeDuration.fromCurrent(lastError.getDate().getTime()); if (errorAge.isShorterThan(TimeDuration.HOUR)) { returnRecords.add( new HealthRecord( HealthStatus.CAUTION, HealthTopic.Database, "Database server was recently unavailable (" + errorAge.asLongString(PwmConstants.DEFAULT_LOCALE) + " ago at " + lastError.getDate().toString() + "): " + lastError.toDebugStr())); } } if (returnRecords.isEmpty()) { returnRecords.add( new HealthRecord( HealthStatus.GOOD, HealthTopic.Database, "Database connection to " + this.dbConfiguration.getConnectionString() + " okay")); } return returnRecords; } // -------------------------- OTHER METHODS -------------------------- private synchronized void init() throws DatabaseException { status = PwmService.STATUS.OPENING; LOGGER.debug("opening connection to database " + this.dbConfiguration.getConnectionString()); connection = openDB(dbConfiguration); for (final DatabaseTable table : DatabaseTable.values()) { initTable(connection, table, dbConfiguration); } status = PwmService.STATUS.OPEN; try { put( DatabaseTable.PWM_META, KEY_ENGINE_START_PREFIX + instanceID, PwmConstants.DEFAULT_DATETIME_FORMAT.format(new java.util.Date())); } catch (DatabaseException e) { final String errorMsg = "error writing engine start time value: " + e.getMessage(); throw new DatabaseException(new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg)); } } private Connection openDB(final DBConfiguration dbConfiguration) throws DatabaseException { final String connectionURL = dbConfiguration.getConnectionString(); final String jdbcClassName = dbConfiguration.getDriverClassname(); try { final byte[] jdbcDriverBytes = dbConfiguration.getJdbcDriver(); if (jdbcDriverBytes != null) { LOGGER.debug("loading JDBC database driver stored in configuration"); final JarClassLoader jarClassLoader = new JarClassLoader(); jarClassLoader.add(new ByteArrayInputStream(jdbcDriverBytes)); final JclObjectFactory jclObjectFactory = JclObjectFactory.getInstance(); // Create object of loaded class driver = (Driver) jclObjectFactory.create(jarClassLoader, jdbcClassName); LOGGER.debug( "successfully loaded JDBC database driver '" + jdbcClassName + "' from application configuration"); } } catch (Throwable e) { final String errorMsg = "error registering JDBC database driver stored in configuration: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg); LOGGER.error(errorMsg, e); throw new DatabaseException(errorInformation); } if (driver == null) { try { LOGGER.debug("loading JDBC database driver from classpath: " + jdbcClassName); driver = (Driver) Class.forName(jdbcClassName).newInstance(); LOGGER.debug("successfully loaded JDBC database driver from classpath: " + jdbcClassName); } catch (Throwable e) { final String errorMsg = e.getClass().getName() + " error loading JDBC database driver from classpath: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg); throw new DatabaseException(errorInformation); } } try { LOGGER.debug("opening connection to database " + connectionURL); final Properties connectionProperties = new Properties(); if (dbConfiguration.getUsername() != null && !dbConfiguration.getUsername().isEmpty()) { connectionProperties.setProperty("user", dbConfiguration.getUsername()); } if (dbConfiguration.getPassword() != null) { connectionProperties.setProperty( "password", dbConfiguration.getPassword().getStringValue()); } final Connection connection = driver.connect(connectionURL, connectionProperties); final Map<PwmAboutProperty, String> debugProps = getConnectionDebugProperties(connection); ; LOGGER.debug( "successfully opened connection to database " + connectionURL + ", properties: " + JsonUtil.serializeMap(debugProps)); connection.setAutoCommit(true); return connection; } catch (Throwable e) { final String errorMsg = "error connecting to database: " + Helper.readHostileExceptionMessage(e); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg); if (e instanceof IOException) { LOGGER.error(errorInformation); } else { LOGGER.error(errorMsg, e); } throw new DatabaseException(errorInformation); } } private static void initTable( final Connection connection, final DatabaseTable table, final DBConfiguration dbConfiguration) throws DatabaseException { try { checkIfTableExists(connection, table); LOGGER.trace("table " + table + " appears to exist"); } catch (SQLException e) { // assume error was due to table missing; { final StringBuilder sqlString = new StringBuilder(); sqlString.append("CREATE table ").append(table.toString()).append(" (").append("\n"); sqlString .append(" " + KEY_COLUMN + " ") .append(dbConfiguration.getColumnTypeKey()) .append("(") .append(KEY_COLUMN_LENGTH) .append(") NOT NULL PRIMARY KEY,") .append("\n"); sqlString .append(" " + VALUE_COLUMN + " ") .append(dbConfiguration.getColumnTypeValue()) .append(" "); sqlString.append("\n"); sqlString.append(")").append("\n"); LOGGER.trace( "attempting to execute the following sql statement:\n " + sqlString.toString()); Statement statement = null; try { statement = connection.createStatement(); statement.execute(sqlString.toString()); LOGGER.debug("created table " + table.toString()); } catch (SQLException ex) { LOGGER.error("error creating new table " + table.toString() + ": " + ex.getMessage()); } finally { close(statement); } } { final String indexName = table.toString() + "_IDX"; final StringBuilder sqlString = new StringBuilder(); sqlString.append("CREATE index ").append(indexName); sqlString.append(" ON ").append(table.toString()); sqlString.append(" (").append(KEY_COLUMN).append(")"); Statement statement = null; LOGGER.trace( "attempting to execute the following sql statement:\n " + sqlString.toString()); try { statement = connection.createStatement(); statement.execute(sqlString.toString()); LOGGER.debug("created index " + indexName); } catch (SQLException ex) { LOGGER.error("error creating new index " + indexName + ": " + ex.getMessage()); } finally { close(statement); } } } } private static void checkIfTableExists(final Connection connection, final DatabaseTable table) throws SQLException { final StringBuilder sb = new StringBuilder(); sb.append("SELECT * FROM ").append(table.toString()).append(" WHERE " + KEY_COLUMN + " = '0'"); Statement statement = null; ResultSet resultSet = null; try { statement = connection.createStatement(); resultSet = statement.executeQuery(sb.toString()); } finally { close(statement); close(resultSet); } } @Override public boolean put(final DatabaseTable table, final String key, final String value) throws DatabaseException { preOperationCheck(); if (traceLogging) { LOGGER.trace("attempting put operation for table=" + table + ", key=" + key); } if (!contains(table, key)) { final String sqlText = "INSERT INTO " + table.toString() + "(" + KEY_COLUMN + ", " + VALUE_COLUMN + ") VALUES(?,?)"; PreparedStatement statement = null; try { statement = connection.prepareStatement(sqlText); statement.setString(1, key); statement.setString(2, value); statement.executeUpdate(); } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "put operation failed: " + e.getMessage()); lastError = errorInformation; throw new DatabaseException(errorInformation); } finally { close(statement); } return false; } final String sqlText = "UPDATE " + table.toString() + " SET " + VALUE_COLUMN + "=? WHERE " + KEY_COLUMN + "=?"; PreparedStatement statement = null; try { statement = connection.prepareStatement(sqlText); statement.setString(1, value); statement.setString(2, key); statement.executeUpdate(); } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "put operation failed: " + e.getMessage()); lastError = errorInformation; throw new DatabaseException(errorInformation); } finally { close(statement); } if (traceLogging) { final Map<String, Object> debugOutput = new LinkedHashMap<>(); debugOutput.put("table", table); debugOutput.put("key", key); debugOutput.put("value", value); LOGGER.trace( "put operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint)); } updateStats(false, true); return true; } private synchronized void preOperationCheck() throws DatabaseException { if (status == PwmService.STATUS.CLOSED) { throw new DatabaseException( new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, "database connection is not open")); } if (status == PwmService.STATUS.NEW) { init(); } if (!isValid(connection)) { init(); } } private boolean isValid(final Connection connection) { if (connection == null) { return false; } if (status != PwmService.STATUS.OPEN) { return false; } try { final Method getFreeSpaceMethod = File.class.getMethod("isValid"); final Object rawResult = getFreeSpaceMethod.invoke(connection, 10); return (Boolean) rawResult; } catch (NoSuchMethodException e) { /* no error, pre java 1.6 doesn't have this method */ } catch (Exception e) { LOGGER.debug( "error checking for isValid for " + connection.toString() + ",: " + e.getMessage()); } final StringBuilder sb = new StringBuilder(); sb.append("SELECT * FROM ") .append(DatabaseTable.PWM_META.toString()) .append(" WHERE " + KEY_COLUMN + " = ?"); PreparedStatement statement = null; ResultSet resultSet = null; try { statement = connection.prepareStatement(sb.toString()); statement.setString(1, KEY_ENGINE_START_PREFIX + instanceID); statement.setMaxRows(1); resultSet = statement.executeQuery(); if (resultSet.next()) { resultSet.getString(VALUE_COLUMN); } } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "isValid operation failed: " + e.getMessage()); lastError = errorInformation; LOGGER.error(errorInformation.toDebugStr()); return false; } finally { close(statement); close(resultSet); } return true; } private static void close(final Statement statement) { if (statement != null) { try { statement.close(); } catch (SQLException e) { LOGGER.error("unexpected error during close statement object " + e.getMessage(), e); } } } private static void close(final ResultSet resultSet) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { LOGGER.error("unexpected error during close resultSet object " + e.getMessage(), e); } } } @Override public boolean contains(final DatabaseTable table, final String key) throws DatabaseException { final boolean result = get(table, key) != null; if (traceLogging) { final Map<String, Object> debugOutput = new LinkedHashMap<>(); debugOutput.put("table", table); debugOutput.put("key", key); debugOutput.put("result", result); LOGGER.trace( "contains operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint)); } updateStats(true, false); return result; } @Override public String get(final DatabaseTable table, final String key) throws DatabaseException { if (traceLogging) { LOGGER.trace("attempting get operation for table=" + table + ", key=" + key); } preOperationCheck(); final StringBuilder sb = new StringBuilder(); sb.append("SELECT * FROM ").append(table.toString()).append(" WHERE " + KEY_COLUMN + " = ?"); PreparedStatement statement = null; ResultSet resultSet = null; String returnValue = null; try { statement = connection.prepareStatement(sb.toString()); statement.setString(1, key); statement.setMaxRows(1); resultSet = statement.executeQuery(); if (resultSet.next()) { returnValue = resultSet.getString(VALUE_COLUMN); } } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "get operation failed: " + e.getMessage()); lastError = errorInformation; throw new DatabaseException(errorInformation); } finally { close(statement); close(resultSet); } if (traceLogging) { final LinkedHashMap<String, Object> debugOutput = new LinkedHashMap<>(); debugOutput.put("table", table); debugOutput.put("key", key); debugOutput.put("result", returnValue); LOGGER.trace( "get operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint)); } updateStats(true, false); return returnValue; } @Override public ClosableIterator<String> iterator(final DatabaseTable table) throws DatabaseException { preOperationCheck(); return new DBIterator(table); } @Override public boolean remove(final DatabaseTable table, final String key) throws DatabaseException { if (traceLogging) { LOGGER.trace("attempting remove operation for table=" + table + ", key=" + key); } boolean result = contains(table, key); if (result) { final StringBuilder sqlText = new StringBuilder(); sqlText.append("DELETE FROM ").append(table.toString()).append(" WHERE " + KEY_COLUMN + "=?"); PreparedStatement statement = null; try { statement = connection.prepareStatement(sqlText.toString()); statement.setString(1, key); statement.executeUpdate(); LOGGER.trace("remove operation succeeded for table=" + table + ", key=" + key); } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "remove operation failed: " + e.getMessage()); lastError = errorInformation; throw new DatabaseException(errorInformation); } finally { close(statement); } } if (traceLogging) { final Map<String, Object> debugOutput = new LinkedHashMap<>(); debugOutput.put("table", table); debugOutput.put("key", key); debugOutput.put("result", result); LOGGER.trace( "remove operation result: " + JsonUtil.serializeMap(debugOutput, JsonUtil.Flag.PrettyPrint)); } updateStats(true, false); return result; } @Override public int size(final DatabaseTable table) throws DatabaseException { preOperationCheck(); final StringBuilder sb = new StringBuilder(); sb.append("SELECT COUNT(" + KEY_COLUMN + ") FROM ").append(table.toString()); PreparedStatement statement = null; ResultSet resultSet = null; try { statement = connection.prepareStatement(sb.toString()); resultSet = statement.executeQuery(); if (resultSet.next()) { return resultSet.getInt(1); } } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "size operation failed: " + e.getMessage()); lastError = errorInformation; throw new DatabaseException(errorInformation); } finally { close(statement); close(resultSet); } updateStats(true, false); return 0; } // -------------------------- ENUMERATIONS -------------------------- // -------------------------- INNER CLASSES -------------------------- public class DBIterator implements ClosableIterator<String> { private final DatabaseTable table; private final ResultSet resultSet; private java.lang.String nextValue; private boolean finished; public DBIterator(final DatabaseTable table) throws DatabaseException { this.table = table; this.resultSet = init(); getNextItem(); } private ResultSet init() throws DatabaseException { final StringBuilder sb = new StringBuilder(); sb.append("SELECT " + KEY_COLUMN + " FROM ").append(table.toString()); try { final PreparedStatement statement = connection.prepareStatement(sb.toString()); return statement.executeQuery(); } catch (SQLException e) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "get iterator failed: " + e.getMessage()); lastError = errorInformation; throw new DatabaseException(errorInformation); } } public boolean hasNext() { return !finished; } public java.lang.String next() { if (finished) { throw new IllegalStateException("iterator completed"); } final String returnValue = nextValue; getNextItem(); return returnValue; } public void remove() { throw new UnsupportedOperationException("remove not supported"); } private void getNextItem() { try { if (resultSet.next()) { nextValue = resultSet.getString(KEY_COLUMN); } else { close(); } } catch (SQLException e) { finished = true; LOGGER.warn("unexpected error during result set iteration: " + e.getMessage()); } updateStats(true, false); } public void close() { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { LOGGER.error("error closing inner resultset in iterator: " + e.getMessage()); } } finished = true; } } public static class DBConfiguration implements Serializable { private final String driverClassname; private final String connectionString; private final String username; private final PasswordData password; private final String columnTypeKey; private final String columnTypeValue; private final byte[] jdbcDriver; public DBConfiguration( final String driverClassname, final String connectionString, final String username, final PasswordData password, final String columnTypeKey, final String columnTypeValue, final byte[] jdbcDriver) { this.driverClassname = driverClassname; this.connectionString = connectionString; this.username = username; this.password = password; this.columnTypeKey = columnTypeKey; this.columnTypeValue = columnTypeValue; this.jdbcDriver = jdbcDriver; } public String getDriverClassname() { return driverClassname; } public String getConnectionString() { return connectionString; } public String getUsername() { return username; } public PasswordData getPassword() { return password; } public String getColumnTypeKey() { return columnTypeKey; } public String getColumnTypeValue() { return columnTypeValue; } public byte[] getJdbcDriver() { return jdbcDriver; } public boolean isEmpty() { if (driverClassname == null || driverClassname.length() < 1) { if (connectionString == null || connectionString.length() < 1) { if (username == null || username.length() < 1) { if (password == null) { return true; } } } } return false; } } public ServiceInfo serviceInfo() { if (status() == STATUS.OPEN) { return new ServiceInfo(Collections.singletonList(DataStorageMethod.DB)); } else { return new ServiceInfo(Collections.<DataStorageMethod>emptyList()); } } private void updateStats(boolean readOperation, boolean writeOperation) { if (pwmApplication != null && pwmApplication.getApplicationMode() == PwmApplication.MODE.RUNNING) { final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager(); if (statisticsManager != null && statisticsManager.status() == STATUS.OPEN) { if (readOperation) { statisticsManager.updateEps(Statistic.EpsType.DB_READS, 1); } if (writeOperation) { statisticsManager.updateEps(Statistic.EpsType.DB_WRITES, 1); } } } } @Override public Map<PwmAboutProperty, String> getConnectionDebugProperties() { return getConnectionDebugProperties(connection); } private static Map<PwmAboutProperty, String> getConnectionDebugProperties( final Connection connection) { if (connection != null) { try { final Map<PwmAboutProperty, String> returnObj = new LinkedHashMap<>(); final DatabaseMetaData databaseMetaData = connection.getMetaData(); returnObj.put(PwmAboutProperty.database_driverName, databaseMetaData.getDriverName()); returnObj.put(PwmAboutProperty.database_driverVersion, databaseMetaData.getDriverVersion()); returnObj.put( PwmAboutProperty.database_databaseProductName, databaseMetaData.getDatabaseProductName()); returnObj.put( PwmAboutProperty.database_databaseProductVersion, databaseMetaData.getDatabaseProductVersion()); return Collections.unmodifiableMap(returnObj); } catch (SQLException e) { LOGGER.error("error rading jdbc meta data: " + e.getMessage()); } } return Collections.emptyMap(); } }
/** * GZip Filter Wrapper. This filter must be invoked _before_ a PwmRequest object is instantiated, * else it will cache a reference to the original response and break the application. */ public class GZIPFilter implements Filter { private static final PwmLogger LOGGER = PwmLogger.forClass(GZIPFilter.class); public void init(FilterConfig filterConfig) throws ServletException {} public void destroy() {} @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { final String acceptEncoding = ((HttpServletRequest) servletRequest) .getHeader(PwmConstants.HttpHeader.Accept_Encoding.getHttpName()); if (acceptEncoding != null && acceptEncoding.contains("gzip") && isEnabled(servletRequest)) { GZIPHttpServletResponseWrapper gzipResponse = new GZIPHttpServletResponseWrapper((HttpServletResponse) servletResponse); gzipResponse.addHeader("Content-Encoding", "gzip"); filterChain.doFilter(servletRequest, gzipResponse); gzipResponse.finish(); } else { filterChain.doFilter(servletRequest, servletResponse); } } private boolean isEnabled(final ServletRequest servletRequest) { try { final PwmURL pwmURL = new PwmURL((HttpServletRequest) servletRequest); if (pwmURL.isResourceURL() || pwmURL.isWebServiceURL()) { return false; } } catch (Exception e) { LOGGER.error("unable to parse request url, defaulting to non-gzip: " + e.getMessage()); } final PwmApplication pwmApplication; try { pwmApplication = ContextManager.getPwmApplication((HttpServletRequest) servletRequest); return Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_ENABLE_GZIP)); } catch (PwmUnrecoverableException e) { LOGGER.trace( "unable to read http-gzip app-property, defaulting to non-gzip: " + e.getMessage()); } return false; } public static class GZIPHttpServletResponseWrapper extends HttpServletResponseWrapper { private ServletResponseGZIPOutputStream gzipStream; private ServletOutputStream outputStream; private PrintWriter printWriter; public GZIPHttpServletResponseWrapper(HttpServletResponse response) throws IOException { super(response); } public void finish() throws IOException { if (printWriter != null) { printWriter.close(); } if (outputStream != null) { outputStream.close(); } if (gzipStream != null) { gzipStream.close(); } } @Override public void flushBuffer() throws IOException { if (printWriter != null) { printWriter.flush(); } if (outputStream != null) { outputStream.flush(); } super.flushBuffer(); } @Override public ServletOutputStream getOutputStream() throws IOException { if (printWriter != null) { throw new IllegalStateException( "getWriter() has previously been invoked, can not call getOutputStream()"); } if (outputStream == null) { initGzip(); outputStream = gzipStream; } return outputStream; } @Override public PrintWriter getWriter() throws IOException { if (outputStream != null) { throw new IllegalStateException( "getOutputStream() has previously been invoked, can not call getWriter()"); } if (printWriter == null) { initGzip(); printWriter = new PrintWriter( new OutputStreamWriter(gzipStream, getResponse().getCharacterEncoding())); } return printWriter; } @Override public void setContentLength(int len) {} private void initGzip() throws IOException { gzipStream = new ServletResponseGZIPOutputStream(getResponse().getOutputStream()); } } public static class ServletResponseGZIPOutputStream extends ServletOutputStream { private final AtomicBoolean open = new AtomicBoolean(true); private GZIPOutputStream gzipStream; public ServletResponseGZIPOutputStream(OutputStream output) throws IOException { gzipStream = new GZIPOutputStream(output); } @Override public void close() throws IOException { if (open.compareAndSet(true, false)) { gzipStream.close(); } } @Override public void flush() throws IOException { gzipStream.flush(); } @Override public void write(byte b[]) throws IOException { write(b, 0, b.length); } @Override public void write(byte b[], int off, int len) throws IOException { if (!open.get()) { throw new IOException("Stream closed!"); } gzipStream.write(b, off, len); } @Override public void write(int b) throws IOException { if (!open.get()) { throw new IOException("Stream closed!"); } gzipStream.write(b); } } }
public abstract class InternalMacros { private static final PwmLogger LOGGER = PwmLogger.forClass(InternalMacros.class); public static final List<Class<? extends MacroImplementation>> INTERNAL_MACROS; static { final List<Class<? extends MacroImplementation>> defaultMacros = new ArrayList<>(); defaultMacros.add(OtpSetupTimeMacro.class); defaultMacros.add(ResponseSetupTimeMacro.class); defaultMacros.add(PwmSettingReference.class); defaultMacros.add(PwmAppName.class); INTERNAL_MACROS = Collections.unmodifiableList(defaultMacros); } public static class OtpSetupTimeMacro extends AbstractMacro { private static final Pattern PATTERN = Pattern.compile("@OtpSetupTime@"); public Pattern getRegExPattern() { return PATTERN; } public String replaceValue(String matchValue, MacroRequestInfo macroRequestInfo) { final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean(); if (userInfoBean != null && userInfoBean.getOtpUserRecord() != null && userInfoBean.getOtpUserRecord().getTimestamp() != null) { return PwmConstants.DEFAULT_DATETIME_FORMAT.format( userInfoBean.getOtpUserRecord().getTimestamp()); } return null; } } public static class ResponseSetupTimeMacro extends AbstractMacro { private static final Pattern PATTERN = Pattern.compile("@ResponseSetupTime@"); public Pattern getRegExPattern() { return PATTERN; } public String replaceValue(String matchValue, MacroRequestInfo macroRequestInfo) { final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean(); if (userInfoBean != null && userInfoBean.getResponseInfoBean() != null && userInfoBean.getResponseInfoBean().getTimestamp() != null) { return PwmConstants.DEFAULT_DATETIME_FORMAT.format( userInfoBean.getResponseInfoBean().getTimestamp()); } return null; } } public static class PwmSettingReference extends AbstractMacro { private static final Pattern PATTERN = Pattern.compile("@PwmSettingReference" + PATTERN_OPTIONAL_PARAMETER_MATCH + "@"); public Pattern getRegExPattern() { return PATTERN; } public String replaceValue(String matchValue, MacroRequestInfo macroRequestInfo) throws MacroParseException { final String settingKeyStr = matchValue.substring(21, matchValue.length() - 1); if (settingKeyStr.isEmpty()) { throw new MacroParseException("PwmSettingReference macro requires a setting key value"); } final PwmSetting setting = PwmSetting.forKey(settingKeyStr); if (setting == null) { throw new MacroParseException( "PwmSettingReference macro has unknown key value '" + settingKeyStr + "'"); } return setting.toMenuLocationDebug(null, PwmConstants.DEFAULT_LOCALE); } } public static class PwmAppName extends AbstractMacro { private static final Pattern PATTERN = Pattern.compile("@PwmAppName@"); public Pattern getRegExPattern() { return PATTERN; } public String replaceValue(String matchValue, MacroRequestInfo macroRequestInfo) throws MacroParseException { return PwmConstants.PWM_APP_NAME; } } }
public class ReportService implements PwmService { private static final PwmLogger LOGGER = PwmLogger.forClass(ReportService.class); private final AvgTracker avgTracker = new AvgTracker(100); private PwmApplication pwmApplication; private STATUS status = STATUS.NEW; private boolean cancelFlag = false; private ReportStatusInfo reportStatus = new ReportStatusInfo(""); private ReportSummaryData summaryData = ReportSummaryData.newSummaryData(null); private ScheduledExecutorService executorService; private UserCacheService userCacheService; private ReportSettings settings = new ReportSettings(); public ReportService() {} public STATUS status() { return status; } public void clear() throws LocalDBException, PwmUnrecoverableException { final Date startTime = new Date(); LOGGER.info(PwmConstants.REPORTING_SESSION_LABEL, "clearing cached report data"); if (userCacheService != null) { userCacheService.clear(); } summaryData = ReportSummaryData.newSummaryData(settings.getTrackDays()); reportStatus = new ReportStatusInfo(settings.getSettingsHash()); saveTempData(); LOGGER.info( PwmConstants.REPORTING_SESSION_LABEL, "finished clearing report " + TimeDuration.fromCurrent(startTime).asCompactString()); } @Override public void init(PwmApplication pwmApplication) throws PwmException { status = STATUS.OPENING; this.pwmApplication = pwmApplication; if (pwmApplication.getApplicationMode() == PwmApplication.MODE.READ_ONLY) { LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "application mode is read-only, will remain closed"); status = STATUS.CLOSED; return; } if (pwmApplication.getLocalDB() == null || LocalDB.Status.OPEN != pwmApplication.getLocalDB().status()) { LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL, "LocalDB is not open, will remain closed"); status = STATUS.CLOSED; return; } if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.REPORTING_ENABLE)) { LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "reporting module is not enabled, will remain closed"); status = STATUS.CLOSED; clear(); return; } try { userCacheService = new UserCacheService(); userCacheService.init(pwmApplication); } catch (Exception e) { LOGGER.error(PwmConstants.REPORTING_SESSION_LABEL, "unable to init cache service"); status = STATUS.CLOSED; return; } settings = ReportSettings.readSettingsFromConfig(pwmApplication.getConfig()); summaryData = ReportSummaryData.newSummaryData(settings.getTrackDays()); executorService = Executors.newSingleThreadScheduledExecutor( Helper.makePwmThreadFactory( Helper.makeThreadName(pwmApplication, this.getClass()) + "-", true)); String startupMsg = "report service started"; LOGGER.debug(startupMsg); executorService.submit(new InitializationTask()); status = STATUS.OPEN; } @Override public void close() { status = STATUS.CLOSED; saveTempData(); pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.REPORT_CLEAN_FLAG, "true"); if (userCacheService != null) { userCacheService.close(); } if (executorService != null) { executorService.shutdown(); } executorService = null; } private void saveTempData() { try { final String jsonInfo = JsonUtil.serialize(reportStatus); pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.REPORT_STATUS, jsonInfo); } catch (Exception e) { LOGGER.error( PwmConstants.REPORTING_SESSION_LABEL, "error writing cached report dredge info into memory: " + e.getMessage()); } } private void initTempData() throws LocalDBException, PwmUnrecoverableException { final String cleanFlag = pwmApplication.readAppAttribute(PwmApplication.AppAttribute.REPORT_CLEAN_FLAG); if (!"true".equals(cleanFlag)) { LOGGER.error(PwmConstants.REPORTING_SESSION_LABEL, "did not shut down cleanly"); reportStatus = new ReportStatusInfo(settings.getSettingsHash()); reportStatus.setTotal(userCacheService.size()); } else { try { final String jsonInfo = pwmApplication.readAppAttribute(PwmApplication.AppAttribute.REPORT_STATUS); if (jsonInfo != null && !jsonInfo.isEmpty()) { reportStatus = JsonUtil.deserialize(jsonInfo, ReportStatusInfo.class); } } catch (Exception e) { LOGGER.error( PwmConstants.REPORTING_SESSION_LABEL, "error loading cached report status info into memory: " + e.getMessage()); } } reportStatus = reportStatus == null ? new ReportStatusInfo(settings.getSettingsHash()) : reportStatus; // safety final String currentSettingCache = settings.getSettingsHash(); if (reportStatus.getSettingsHash() != null && !reportStatus.getSettingsHash().equals(currentSettingCache)) { LOGGER.error( PwmConstants.REPORTING_SESSION_LABEL, "configuration has changed, will clear cached report data"); clear(); } reportStatus.setInProgress(false); pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.REPORT_CLEAN_FLAG, "false"); } @Override public List<HealthRecord> healthCheck() { return null; } @Override public ServiceInfo serviceInfo() { return new ServiceInfo(Collections.singletonList(DataStorageMethod.LDAP)); } public void scheduleImmediateUpdate() { if (!reportStatus.isInProgress()) { executorService.submit(new DredgeTask()); LOGGER.trace( PwmConstants.REPORTING_SESSION_LABEL, "submitted new ldap dredge task to executorService"); } } public void cancelUpdate() { cancelFlag = true; } private void updateCacheFromLdap() throws ChaiUnavailableException, ChaiOperationException, PwmOperationalException, PwmUnrecoverableException { LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "beginning process to updating user cache records from ldap"); if (status != STATUS.OPEN) { return; } cancelFlag = false; reportStatus = new ReportStatusInfo(settings.getSettingsHash()); reportStatus.setInProgress(true); reportStatus.setStartDate(new Date()); try { final Queue<UserIdentity> allUsers = new LinkedList<>(getListOfUsers()); reportStatus.setTotal(allUsers.size()); while (status == STATUS.OPEN && !allUsers.isEmpty() && !cancelFlag) { final Date startUpdateTime = new Date(); final UserIdentity userIdentity = allUsers.poll(); try { if (updateCachedRecordFromLdap(userIdentity)) { reportStatus.setUpdated(reportStatus.getUpdated() + 1); } } catch (Exception e) { String errorMsg = "error while updating report cache for " + userIdentity.toString() + ", cause: "; errorMsg += e instanceof PwmException ? ((PwmException) e).getErrorInformation().toDebugStr() : e.getMessage(); final ErrorInformation errorInformation; errorInformation = new ErrorInformation(PwmError.ERROR_REPORTING_ERROR, errorMsg); LOGGER.error(PwmConstants.REPORTING_SESSION_LABEL, errorInformation.toDebugStr()); reportStatus.setLastError(errorInformation); reportStatus.setErrors(reportStatus.getErrors() + 1); } reportStatus.setCount(reportStatus.getCount() + 1); reportStatus.getEventRateMeter().markEvents(1); final TimeDuration totalUpdateTime = TimeDuration.fromCurrent(startUpdateTime); if (settings.isAutoCalcRest()) { avgTracker.addSample(totalUpdateTime.getTotalMilliseconds()); Helper.pause(avgTracker.avgAsLong()); } else { Helper.pause(settings.getRestTime().getTotalMilliseconds()); } } if (cancelFlag) { reportStatus.setLastError( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "report cancelled by operator")); } } finally { reportStatus.setFinishDate(new Date()); reportStatus.setInProgress(false); } LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "update user cache process completed: " + JsonUtil.serialize(reportStatus)); } private void updateRestingCacheData() { final long startTime = System.currentTimeMillis(); int examinedRecords = 0; ClosableIterator<UserCacheRecord> iterator = null; try { LOGGER.trace(PwmConstants.REPORTING_SESSION_LABEL, "checking size of stored cache records"); final int totalRecords = userCacheService.size(); LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "beginning cache review process of " + totalRecords + " records"); iterator = iterator(); Date lastLogOutputTime = new Date(); while (iterator.hasNext() && status == STATUS.OPEN) { final UserCacheRecord record = iterator.next(); // (purge routine is embedded in next(); if (summaryData != null && record != null) { summaryData.update(record); } examinedRecords++; if (TimeDuration.fromCurrent(lastLogOutputTime).isLongerThan(30, TimeUnit.SECONDS)) { final TimeDuration progressDuration = TimeDuration.fromCurrent(startTime); LOGGER.trace( PwmConstants.REPORTING_SESSION_LABEL, "cache review process in progress, examined " + examinedRecords + " records in " + progressDuration.asCompactString()); lastLogOutputTime = new Date(); } } final TimeDuration totalTime = TimeDuration.fromCurrent(startTime); LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "completed cache review process of " + examinedRecords + " cached report records in " + totalTime.asCompactString()); } finally { if (iterator != null) { iterator.close(); } } } public boolean updateCachedRecordFromLdap(final UserInfoBean uiBean) throws LocalDBException, PwmUnrecoverableException, ChaiUnavailableException { if (status != STATUS.OPEN) { return false; } final UserCacheService.StorageKey storageKey = UserCacheService.StorageKey.fromUserInfoBean(uiBean); return updateCachedRecordFromLdap(uiBean.getUserIdentity(), uiBean, storageKey); } private boolean updateCachedRecordFromLdap(final UserIdentity userIdentity) throws ChaiUnavailableException, PwmUnrecoverableException, LocalDBException { if (status != STATUS.OPEN) { return false; } final UserCacheService.StorageKey storageKey = UserCacheService.StorageKey.fromUserIdentity(pwmApplication, userIdentity); return updateCachedRecordFromLdap(userIdentity, null, storageKey); } private boolean updateCachedRecordFromLdap( final UserIdentity userIdentity, final UserInfoBean userInfoBean, final UserCacheService.StorageKey storageKey) throws ChaiUnavailableException, PwmUnrecoverableException, LocalDBException { final UserCacheRecord userCacheRecord = userCacheService.readStorageKey(storageKey); TimeDuration cacheAge = null; if (userCacheRecord != null && userCacheRecord.getCacheTimestamp() != null) { cacheAge = TimeDuration.fromCurrent(userCacheRecord.getCacheTimestamp()); } boolean updateCache = false; if (userInfoBean != null) { updateCache = true; } else { if (cacheAge == null) { LOGGER.trace( PwmConstants.REPORTING_SESSION_LABEL, "stored cache for " + userIdentity + " is missing cache storage timestamp, will update"); updateCache = true; } else if (cacheAge.isLongerThan(settings.getMinCacheAge())) { LOGGER.trace( PwmConstants.REPORTING_SESSION_LABEL, "stored cache for " + userIdentity + " is " + cacheAge.asCompactString() + " old, will update"); updateCache = true; } } if (updateCache) { if (userCacheRecord != null) { if (summaryData != null && summaryData.getEpoch() != null && summaryData.getEpoch().equals(userCacheRecord.getSummaryEpoch())) { summaryData.remove(userCacheRecord); } } final UserInfoBean newUserBean; if (userInfoBean != null) { newUserBean = userInfoBean; } else { newUserBean = new UserInfoBean(); final UserStatusReader.Settings readerSettings = new UserStatusReader.Settings(); readerSettings.setSkipReportUpdate(true); final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()); final UserStatusReader userStatusReader = new UserStatusReader( pwmApplication, PwmConstants.REPORTING_SESSION_LABEL, readerSettings); userStatusReader.populateUserInfoBean( newUserBean, PwmConstants.DEFAULT_LOCALE, userIdentity, chaiProvider); } final UserCacheRecord newUserCacheRecord = userCacheService.updateUserCache(newUserBean); if (summaryData != null && summaryData.getEpoch() != null && newUserCacheRecord != null) { if (!summaryData.getEpoch().equals(newUserCacheRecord.getSummaryEpoch())) { newUserCacheRecord.setSummaryEpoch(summaryData.getEpoch()); userCacheService.store(newUserCacheRecord); } summaryData.update(newUserCacheRecord); } } return updateCache; } public ReportStatusInfo getReportStatusInfo() { return reportStatus; } private List<UserIdentity> getListOfUsers() throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException { return readAllUsersFromLdap( pwmApplication, settings.getSearchFilter(), settings.getMaxSearchSize()); } private static List<UserIdentity> readAllUsersFromLdap( final PwmApplication pwmApplication, final String searchFilter, final int maxResults) throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException { final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, null); final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration(); searchConfiguration.setEnableValueEscaping(false); searchConfiguration.setSearchTimeout( Long.parseLong( pwmApplication.getConfig().readAppProperty(AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT))); if (searchFilter == null) { searchConfiguration.setUsername("*"); } else { searchConfiguration.setFilter(searchFilter); } LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "beginning UserReportService user search using parameters: " + (JsonUtil.serialize(searchConfiguration))); final Map<UserIdentity, Map<String, String>> searchResults = userSearchEngine.performMultiUserSearch( searchConfiguration, maxResults, Collections.<String>emptyList()); LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "user search found " + searchResults.size() + " users for reporting"); final List<UserIdentity> returnList = new ArrayList<>(searchResults.keySet()); Collections.shuffle(returnList); return returnList; } public RecordIterator<UserCacheRecord> iterator() { return new RecordIterator<>(userCacheService.<UserCacheService.StorageKey>iterator()); } public class RecordIterator<K> implements ClosableIterator<UserCacheRecord> { private UserCacheService.UserStatusCacheBeanIterator<UserCacheService.StorageKey> storageKeyIterator; public RecordIterator( UserCacheService.UserStatusCacheBeanIterator<UserCacheService.StorageKey> storageKeyIterator) { this.storageKeyIterator = storageKeyIterator; } public boolean hasNext() { return this.storageKeyIterator.hasNext(); } public UserCacheRecord next() { try { UserCacheRecord returnBean = null; while (returnBean == null && this.storageKeyIterator.hasNext()) { UserCacheService.StorageKey key = this.storageKeyIterator.next(); returnBean = userCacheService.readStorageKey(key); if (returnBean != null) { if (returnBean.getCacheTimestamp() == null) { LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "purging record due to missing cache timestamp: " + JsonUtil.serialize(returnBean)); userCacheService.removeStorageKey(key); } else if (TimeDuration.fromCurrent(returnBean.getCacheTimestamp()) .isLongerThan(settings.getMaxCacheAge())) { LOGGER.debug( PwmConstants.REPORTING_SESSION_LABEL, "purging record due to old age timestamp: " + JsonUtil.serialize(returnBean)); userCacheService.removeStorageKey(key); } else { return returnBean; } } } } catch (LocalDBException e) { throw new IllegalStateException( "unexpected iterator traversal error while reading LocalDB: " + e.getMessage()); } return null; } public void remove() {} public void close() { storageKeyIterator.close(); } } public void outputSummaryToCsv(final OutputStream outputStream, final Locale locale) throws IOException { final List<ReportSummaryData.PresentationRow> outputList = summaryData.asPresentableCollection(pwmApplication.getConfig(), locale); final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream); for (final ReportSummaryData.PresentationRow presentationRow : outputList) { final List<String> headerRow = new ArrayList<>(); headerRow.add(presentationRow.getLabel()); headerRow.add(presentationRow.getCount()); headerRow.add(presentationRow.getPct()); csvPrinter.printRecord(headerRow); } csvPrinter.close(); } public void outputToCsv( final OutputStream outputStream, final boolean includeHeader, final Locale locale) throws IOException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException { final CSVPrinter csvPrinter = Helper.makeCsvPrinter(outputStream); final Configuration config = pwmApplication.getConfig(); final Class localeClass = password.pwm.i18n.Admin.class; if (includeHeader) { final List<String> headerRow = new ArrayList<>(); headerRow.add( LocaleHelper.getLocalizedMessage(locale, "Field_Report_UserDN", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_LDAP_Profile", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage(locale, "Field_Report_Username", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage(locale, "Field_Report_Email", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage(locale, "Field_Report_UserGuid", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage(locale, "Field_Report_LastLogin", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_PwdExpireTime", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_PwdChangeTime", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_ResponseSaveTime", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_HasResponses", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_HasHelpdeskResponses", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_ResponseStorageMethod", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage(locale, "Field_Report_PwdExpired", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_PwdPreExpired", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_PwdViolatesPolicy", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_PwdWarnPeriod", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_RequiresPasswordUpdate", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_RequiresResponseUpdate", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_RequiresProfileUpdate", config, localeClass)); headerRow.add( LocaleHelper.getLocalizedMessage( locale, "Field_Report_RecordCacheTime", config, localeClass)); csvPrinter.printRecord(headerRow); } ClosableIterator<UserCacheRecord> cacheBeanIterator = null; try { cacheBeanIterator = this.iterator(); while (cacheBeanIterator.hasNext()) { final UserCacheRecord userCacheRecord = cacheBeanIterator.next(); outputRecordRow(config, locale, userCacheRecord, csvPrinter); } } finally { if (cacheBeanIterator != null) { cacheBeanIterator.close(); } } csvPrinter.flush(); } private void outputRecordRow( final Configuration config, final Locale locale, final UserCacheRecord userCacheRecord, final CSVPrinter csvPrinter) throws IOException { final String trueField = Display.getLocalizedMessage(locale, Display.Value_True, config); final String falseField = Display.getLocalizedMessage(locale, Display.Value_False, config); final String naField = Display.getLocalizedMessage(locale, Display.Value_NotApplicable, config); final List<String> csvRow = new ArrayList<>(); csvRow.add(userCacheRecord.getUserDN()); csvRow.add(userCacheRecord.getLdapProfile()); csvRow.add(userCacheRecord.getUsername()); csvRow.add(userCacheRecord.getEmail()); csvRow.add(userCacheRecord.getUserGUID()); csvRow.add( userCacheRecord.getLastLoginTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getLastLoginTime())); csvRow.add( userCacheRecord.getPasswordExpirationTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format( userCacheRecord.getPasswordExpirationTime())); csvRow.add( userCacheRecord.getPasswordChangeTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getPasswordChangeTime())); csvRow.add( userCacheRecord.getResponseSetTime() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getResponseSetTime())); csvRow.add(userCacheRecord.isHasResponses() ? trueField : falseField); csvRow.add(userCacheRecord.isHasHelpdeskResponses() ? trueField : falseField); csvRow.add( userCacheRecord.getResponseStorageMethod() == null ? naField : userCacheRecord.getResponseStorageMethod().toString()); csvRow.add(userCacheRecord.getPasswordStatus().isExpired() ? trueField : falseField); csvRow.add(userCacheRecord.getPasswordStatus().isPreExpired() ? trueField : falseField); csvRow.add(userCacheRecord.getPasswordStatus().isViolatesPolicy() ? trueField : falseField); csvRow.add(userCacheRecord.getPasswordStatus().isWarnPeriod() ? trueField : falseField); csvRow.add(userCacheRecord.isRequiresPasswordUpdate() ? trueField : falseField); csvRow.add(userCacheRecord.isRequiresResponseUpdate() ? trueField : falseField); csvRow.add(userCacheRecord.isRequiresProfileUpdate() ? trueField : falseField); csvRow.add( userCacheRecord.getCacheTimestamp() == null ? naField : PwmConstants.DEFAULT_DATETIME_FORMAT.format(userCacheRecord.getCacheTimestamp())); csvPrinter.printRecord(csvRow); } public ReportSummaryData getSummaryData() { return summaryData; } public static class AvgTracker { private final int maxSamples; private final Queue<BigInteger> samples = new LinkedList<>(); public AvgTracker(int maxSamples) { this.maxSamples = maxSamples; } public void addSample(final long input) { samples.add(new BigInteger(Long.toString(input))); while (samples.size() > maxSamples) { samples.remove(); } } public BigDecimal avg() { if (samples.isEmpty()) { throw new IllegalStateException("unable to compute avg without samples"); } BigInteger total = BigInteger.ZERO; for (final BigInteger sample : samples) { total = total.add(sample); } final BigDecimal maxAsBD = new BigDecimal(Integer.toString(maxSamples)); return new BigDecimal(total).divide(maxAsBD, MathContext.DECIMAL32); } public long avgAsLong() { return avg().longValue(); } } private class DredgeTask implements Runnable { @Override public void run() { reportStatus.setCurrentProcess(ReportStatusInfo.ReportEngineProcess.DredgeTask); try { updateCacheFromLdap(); } catch (Exception e) { if (e instanceof PwmException) { if (((PwmException) e).getErrorInformation().getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE) { if (executorService != null) { LOGGER.error( PwmConstants.REPORTING_SESSION_LABEL, "directory unavailable error during background DredgeTask, will retry; error: " + e.getMessage()); executorService.schedule(new DredgeTask(), 10, TimeUnit.MINUTES); } } else { LOGGER.error( PwmConstants.REPORTING_SESSION_LABEL, "error during background DredgeTask: " + e.getMessage()); } } } finally { reportStatus.setCurrentProcess(ReportStatusInfo.ReportEngineProcess.None); } } } private class RolloverTask implements Runnable { @Override public void run() { reportStatus.setCurrentProcess(ReportStatusInfo.ReportEngineProcess.RollOver); try { summaryData = ReportSummaryData.newSummaryData(settings.getTrackDays()); updateRestingCacheData(); } finally { reportStatus.setCurrentProcess(ReportStatusInfo.ReportEngineProcess.None); } } } private class InitializationTask implements Runnable { @Override public void run() { try { initTempData(); } catch (LocalDBException | PwmUnrecoverableException e) { LOGGER.error( PwmConstants.REPORTING_SESSION_LABEL, "error during initialization: " + e.getMessage()); status = STATUS.CLOSED; return; } final long secondsUntilNextDredge = settings.getJobOffsetSeconds() + TimeDuration.fromCurrent(Helper.nextZuluZeroTime()).getTotalSeconds(); executorService.scheduleAtFixedRate( new DredgeTask(), secondsUntilNextDredge, TimeDuration.DAY.getTotalSeconds(), TimeUnit.SECONDS); executorService.scheduleAtFixedRate( new RolloverTask(), secondsUntilNextDredge + 1, TimeDuration.DAY.getTotalSeconds(), TimeUnit.SECONDS); executorService.submit(new RolloverTask()); } } }
public class StatisticsBundle { private static final PwmLogger LOGGER = PwmLogger.forClass(StatisticsBundle.class); static final SimpleDateFormat STORED_DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); static { STORED_DATETIME_FORMATTER.setTimeZone(TimeZone.getTimeZone("Zulu")); } private final Map<Statistic, String> valueMap = new HashMap<>(); public StatisticsBundle() {} public String output() { return JsonUtil.serializeMap(valueMap); } public static StatisticsBundle input(final String inputString) { final Map<Statistic, String> srcMap = new HashMap<>(); final Map<String, String> loadedMap = JsonUtil.deserializeStringMap(inputString); for (final String key : loadedMap.keySet()) { try { srcMap.put(Statistic.valueOf(key), loadedMap.get(key)); } catch (IllegalArgumentException e) { LOGGER.error("error parsing statistic key '" + key + "', reason: " + e.getMessage()); } } final StatisticsBundle bundle = new StatisticsBundle(); for (final Statistic loopStat : Statistic.values()) { final String value = srcMap.get(loopStat); if (value != null && !value.equals("")) { bundle.valueMap.put(loopStat, value); } } return bundle; } public synchronized void incrementValue(final Statistic statistic) { if (Statistic.Type.INCREMENTOR != statistic.getType()) { LOGGER.error("attempt to increment non-counter/incremental stat " + statistic); return; } BigInteger currentValue = BigInteger.ZERO; try { if (valueMap.containsKey(statistic)) { currentValue = new BigInteger(valueMap.get(statistic)); } else { currentValue = BigInteger.ZERO; } } catch (NumberFormatException e) { LOGGER.error("error reading counter/incremental stat " + statistic); } final BigInteger newValue = currentValue.add(BigInteger.ONE); valueMap.put(statistic, newValue.toString()); } public synchronized void updateAverageValue(final Statistic statistic, final long timeDuration) { if (Statistic.Type.AVERAGE != statistic.getType()) { LOGGER.error("attempt to update average value of non-average stat " + statistic); return; } final String avgStrValue = valueMap.get(statistic); AverageBean avgBean = new AverageBean(); if (avgStrValue != null && avgStrValue.length() > 0) { try { avgBean = JsonUtil.deserialize(avgStrValue, AverageBean.class); } catch (Exception e) { LOGGER.trace( "unable to parse statistics value for stat " + statistic.toString() + ", value=" + avgStrValue); } } avgBean.appendValue(timeDuration); valueMap.put(statistic, JsonUtil.serialize(avgBean)); } public String getStatistic(final Statistic statistic) { switch (statistic.getType()) { case INCREMENTOR: return valueMap.containsKey(statistic) ? valueMap.get(statistic) : "0"; case AVERAGE: final String avgStrValue = valueMap.get(statistic); AverageBean avgBean = new AverageBean(); if (avgStrValue != null && avgStrValue.length() > 0) { try { avgBean = JsonUtil.deserialize(avgStrValue, AverageBean.class); } catch (Exception e) { LOGGER.trace( "unable to parse statistics value for stat " + statistic.toString() + ", value=" + avgStrValue); } } return avgBean.getAverage().toString(); default: return ""; } } private static class AverageBean implements Serializable { BigInteger total = BigInteger.ZERO; BigInteger count = BigInteger.ZERO; AverageBean() {} BigInteger getAverage() { if (BigInteger.ZERO.equals(count)) { return BigInteger.ZERO; } return total.divide(count); } void appendValue(final long value) { count = count.add(BigInteger.ONE); total = total.add(BigInteger.valueOf(value)); } } }
/** * User interaction servlet for updating user attributes * * @author Jason D. Rivard */ @WebServlet( name = "UpdateProfileServlet", urlPatterns = { PwmConstants.URL_PREFIX_PRIVATE + "/updateprofile", PwmConstants.URL_PREFIX_PRIVATE + "/UpdateProfile" }) public class UpdateProfileServlet extends AbstractPwmServlet { private static final PwmLogger LOGGER = PwmLogger.forClass(UpdateProfileServlet.class); public enum UpdateProfileAction implements AbstractPwmServlet.ProcessAction { updateProfile(HttpMethod.POST), agree(HttpMethod.POST), confirm(HttpMethod.POST), unConfirm(HttpMethod.POST), validate(HttpMethod.POST), ; private final HttpMethod method; UpdateProfileAction(HttpMethod method) { this.method = method; } public Collection<HttpMethod> permittedMethods() { return Collections.singletonList(method); } } protected UpdateProfileAction readProcessAction(final PwmRequest request) throws PwmUnrecoverableException { try { return UpdateProfileAction.valueOf( request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST)); } catch (IllegalArgumentException e) { return null; } } protected void processAction(final PwmRequest pwmRequest) throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException { final PwmSession pwmSession = pwmRequest.getPwmSession(); final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final UpdateProfileBean updateProfileBean = pwmSession.getUpdateProfileBean(); if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_ENABLE)) { pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE)); return; } if (!pwmSession .getSessionManager() .checkPermission(pwmApplication, Permission.PROFILE_UPDATE)) { pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED)); return; } final UpdateProfileAction action = readProcessAction(pwmRequest); if (action != null) { pwmRequest.validatePwmFormID(); switch (action) { case updateProfile: handleUpdateRequest(pwmRequest, updateProfileBean); break; case agree: handleAgreeRequest(pwmRequest, updateProfileBean); break; case confirm: updateProfileBean.setConfirmationPassed(true); break; case unConfirm: handleUnconfirm(updateProfileBean); break; case validate: restValidateForm(pwmRequest, updateProfileBean); return; } } advanceToNextStep(pwmRequest, updateProfileBean); } protected static void restValidateForm( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException { boolean success = true; String userMessage = Message.getLocalizedMessage( pwmRequest.getLocale(), Message.Success_UpdateForm, pwmRequest.getConfig()); final Map<FormConfiguration, String> formValues = updateProfileBean.getFormData(); try { // read in the responses from the request readFromJsonRequest(pwmRequest, updateProfileBean); // verify form meets the form requirements verifyFormAttributes(pwmRequest, formValues, true); } catch (PwmOperationalException e) { success = false; userMessage = e.getErrorInformation() .toUserStr(pwmRequest.getPwmSession(), pwmRequest.getPwmApplication()); } final LinkedHashMap<String, String> outputMap = new LinkedHashMap<>(); outputMap.put("version", "1"); outputMap.put("message", userMessage); outputMap.put("success", String.valueOf(success)); pwmRequest.outputJsonResult(new RestResultBean(outputMap)); } private void handleUnconfirm(final UpdateProfileBean updateProfileBean) { updateProfileBean.setFormSubmitted(false); updateProfileBean.setConfirmationPassed(false); } private void advanceToNextStep( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final String updateProfileAgreementText = pwmApplication .getConfig() .readSettingAsLocalizedString( PwmSetting.UPDATE_PROFILE_AGREEMENT_MESSAGE, pwmSession.getSessionStateBean().getLocale()); if (updateProfileAgreementText != null && updateProfileAgreementText.length() > 0) { if (!updateProfileBean.isAgreementPassed()) { final MacroMachine macroMachine = pwmRequest .getPwmSession() .getSessionManager() .getMacroMachine(pwmRequest.getPwmApplication()); final String expandedText = macroMachine.expandMacros(updateProfileAgreementText); pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.AgreementText, expandedText); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES_AGREEMENT); return; } } final Map<FormConfiguration, String> formValues = updateProfileBean.getFormData(); if (!updateProfileBean.isFormSubmitted()) { final Map<FormConfiguration, String> formMap = updateProfileBean.getFormData(); final List<FormConfiguration> formFields = pwmApplication.getConfig().readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); FormUtility.populateFormMapFromLdap( formFields, pwmRequest.getSessionLabel(), formMap, pwmSession.getSessionManager().getUserDataReader(pwmApplication)); forwardToForm(pwmRequest); return; } // make sure there is form data in the bean. if (updateProfileBean.getFormData() == null) { forwardToForm(pwmRequest); return; } // validate the form data. try { // verify form meets the form requirements verifyFormAttributes(pwmRequest, formValues, true); } catch (PwmOperationalException e) { LOGGER.error(pwmSession, e.getMessage()); pwmRequest.setResponseError(e.getErrorInformation()); forwardToForm(pwmRequest); return; } final boolean requireConfirmation = pwmApplication .getConfig() .readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_SHOW_CONFIRMATION); if (requireConfirmation && !updateProfileBean.isConfirmationPassed()) { forwardToConfirmForm(pwmRequest); return; } try { // write the form values final ChaiUser theUser = pwmSession.getSessionManager().getActor(pwmApplication); doProfileUpdate(pwmRequest, formValues, theUser); pwmRequest.forwardToSuccessPage(Message.Success_UpdateProfile); return; } catch (PwmException e) { LOGGER.error(pwmSession, e.getMessage()); pwmRequest.setResponseError(e.getErrorInformation()); } catch (ChaiException e) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UPDATE_ATTRS_FAILURE, e.toString()); LOGGER.error(pwmSession, errorInformation.toDebugStr()); pwmRequest.setResponseError(errorInformation); } forwardToForm(pwmRequest); } private void handleAgreeRequest( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException { LOGGER.debug(pwmRequest, "user accepted agreement"); if (!updateProfileBean.isAgreementPassed()) { updateProfileBean.setAgreementPassed(true); AuditRecord auditRecord = pwmRequest .getPwmApplication() .getAuditManager() .createUserAuditRecord( AuditEvent.AGREEMENT_PASSED, pwmRequest.getUserInfoIfLoggedIn(), pwmRequest.getSessionLabel(), "UpdateProfile"); pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord); } } private void readFormParametersFromRequest( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws PwmUnrecoverableException, PwmDataValidationException, ChaiUnavailableException { final List<FormConfiguration> formFields = pwmRequest.getConfig().readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> existingForm = updateProfileBean.getFormData(); // read the values from the request existingForm.putAll( FormUtility.readFormValuesFromRequest(pwmRequest, formFields, pwmRequest.getLocale())); } private static void readFromJsonRequest( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws PwmDataValidationException, PwmUnrecoverableException, IOException { final List<FormConfiguration> formFields = pwmRequest.getConfig().readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> existingForm = updateProfileBean.getFormData(); final Map<String, String> clientValues = pwmRequest.readBodyAsJsonStringMap(); if (clientValues != null) { existingForm.putAll( FormUtility.readFormValuesFromMap(clientValues, formFields, pwmRequest.getLocale())); } } private void handleUpdateRequest( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException { try { readFormParametersFromRequest(pwmRequest, updateProfileBean); } catch (PwmOperationalException e) { LOGGER.error(pwmRequest, e.getMessage()); pwmRequest.setResponseError(e.getErrorInformation()); } updateProfileBean.setFormSubmitted(true); } public static void doProfileUpdate( final PwmRequest pwmRequest, final Map<FormConfiguration, String> formValues, final ChaiUser theUser) throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final UserInfoBean uiBean = pwmRequest.getPwmSession().getUserInfoBean(); // verify form meets the form requirements (may be redundant, but shouldn't hurt) verifyFormAttributes(pwmRequest, formValues, false); // write values. LOGGER.info( "updating profile for " + pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity()); pwmRequest.getPwmSession().getSessionManager().getChaiProvider(); Helper.writeFormValuesToLdap( pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), theUser, formValues, false); final UserIdentity userIdentity = uiBean.getUserIdentity(); // re-populate the uiBean because we have changed some values. final UserStatusReader userStatusReader = new UserStatusReader(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel()); userStatusReader.populateActorUserInfoBean(pwmRequest.getPwmSession(), userIdentity); // clear cached read attributes. pwmRequest.getPwmSession().getSessionManager().clearUserDataReader(); { // execute configured actions final List<ActionConfiguration> actions = pwmApplication .getConfig() .readSettingAsAction(PwmSetting.UPDATE_PROFILE_WRITE_ATTRIBUTES); if (actions != null && !actions.isEmpty()) { LOGGER.debug(pwmRequest, "executing configured actions to user " + userIdentity); final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity) .setExpandPwmMacros(true) .createActionExecutor(); actionExecutor.executeActions(actions, pwmSession); } } sendProfileUpdateEmailNotice(pwmSession, pwmApplication); // mark the event log pwmApplication .getAuditManager() .submit(AuditEvent.UPDATE_PROFILE, pwmSession.getUserInfoBean(), pwmSession); // mark the uiBean so we user isn't recycled to the update profile page by the CommandServlet uiBean.setRequiresUpdateProfile(false); // clear out the updateProfileBean pwmSession.clearUpdateProfileBean(); // success, so forward to success page pwmApplication.getStatisticsManager().incrementValue(Statistic.UPDATE_ATTRIBUTES); } private static void verifyFormAttributes( final PwmRequest pwmRequest, final Map<FormConfiguration, String> formValues, final boolean allowResultCaching) throws PwmOperationalException, PwmUnrecoverableException { final Locale userLocale = pwmRequest.getLocale(); // see if the values meet form requirements. FormUtility.validateFormValues(pwmRequest.getConfig(), formValues, userLocale); // check unique fields against ldap FormUtility.validateFormValueUniqueness( pwmRequest.getPwmApplication(), formValues, userLocale, Collections.singletonList(pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity()), allowResultCaching); } private static void sendProfileUpdateEmailNotice( final PwmSession pwmSession, final PwmApplication pwmApplication) throws PwmUnrecoverableException, ChaiUnavailableException { final Configuration config = pwmApplication.getConfig(); final Locale locale = pwmSession.getSessionStateBean().getLocale(); final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_UPDATEPROFILE, locale); if (configuredEmailSetting == null) { LOGGER.debug( pwmSession, "skipping send profile update email for '" + pwmSession.getUserInfoBean().getUserIdentity() + "' no email configured"); return; } pwmApplication .getEmailQueue() .submitEmail( configuredEmailSetting, pwmSession.getUserInfoBean(), pwmSession.getSessionManager().getMacroMachine(pwmApplication)); } protected void forwardToForm(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException { final List<FormConfiguration> form = pwmRequest.getConfig().readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formData = pwmRequest.getPwmSession().getUpdateProfileBean().getFormData(); pwmRequest.addFormInfoToRequestAttr(form, formData, false, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES); } protected void forwardToConfirmForm(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException { final List<FormConfiguration> form = pwmRequest.getConfig().readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formData = pwmRequest.getPwmSession().getUpdateProfileBean().getFormData(); pwmRequest.addFormInfoToRequestAttr(form, formData, true, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES_CONFIRM); } }
public class SharedHistoryManager implements Wordlist { // ------------------------------ FIELDS ------------------------------ private static final PwmLogger LOGGER = PwmLogger.forClass(SharedHistoryManager.class); private static final String KEY_OLDEST_ENTRY = "oldest_entry"; private static final String KEY_VERSION = "version"; private static final String KEY_SALT = "salt"; private static final int MIN_CLEANER_FREQUENCY = 1000 * 60 * 60; // 1 hour private static final int MAX_CLEANER_FREQUENCY = 1000 * 60 * 60 * 24; // 1 day private static final LocalDB.DB META_DB = LocalDB.DB.SHAREDHISTORY_META; private static final LocalDB.DB WORDS_DB = LocalDB.DB.SHAREDHISTORY_WORDS; private volatile PwmService.STATUS status = STATUS.NEW; private volatile Timer cleanerTimer = null; private LocalDB localDB; private String salt; private long oldestEntry; private final Settings settings = new Settings(); // --------------------------- CONSTRUCTORS --------------------------- public SharedHistoryManager() throws LocalDBException {} // -------------------------- OTHER METHODS -------------------------- public void close() { status = STATUS.CLOSED; LOGGER.debug("closed"); if (cleanerTimer != null) { cleanerTimer.cancel(); } localDB = null; } public boolean containsWord(final String word) { if (status != STATUS.OPEN) { return false; } final String testWord = normalizeWord(word); if (testWord == null) { return false; } // final long startTime = System.currentTimeMillis(); boolean result = false; try { final String hashedWord = hashWord(testWord); final boolean inDB = localDB.contains(WORDS_DB, hashedWord); if (inDB) { final long timeStamp = Long.parseLong(localDB.get(WORDS_DB, hashedWord)); final long entryAge = System.currentTimeMillis() - timeStamp; if (entryAge < settings.maxAgeMs) { result = true; } } } catch (Exception e) { LOGGER.warn("error checking global history list: " + e.getMessage()); } // LOGGER.trace(pwmSession, "successfully checked word, result=" + result + ", duration=" + new // TimeDuration(System.currentTimeMillis(), startTime).asCompactString()); return result; } public PwmService.STATUS status() { return status; } public Date getOldestEntryTime() { if (size() > 0) { return new Date(oldestEntry); } return null; } public int size() { if (localDB != null) { try { return localDB.size(WORDS_DB); } catch (Exception e) { LOGGER.error("error checking wordlist size: " + e.getMessage()); return 0; } } else { return 0; } } private boolean checkDbVersion() throws Exception { LOGGER.trace("checking version number stored in LocalDB"); final Object versionInDB = localDB.get(META_DB, KEY_VERSION); final String currentVersion = "version=" + settings.version; final boolean result = currentVersion.equals(versionInDB); if (!result) { LOGGER.info( "existing db version does not match current db version db=(" + versionInDB + ") current=(" + currentVersion + "), clearing db"); localDB.truncate(WORDS_DB); localDB.put(META_DB, KEY_VERSION, currentVersion); localDB.remove(META_DB, KEY_OLDEST_ENTRY); } else { LOGGER.trace( "existing db version matches current db version db=(" + versionInDB + ") current=(" + currentVersion + ")"); } return result; } private void init(final PwmApplication pwmApplication, final long maxAgeMs) { status = STATUS.OPENING; final long startTime = System.currentTimeMillis(); try { checkDbVersion(); } catch (Exception e) { LOGGER.error("error checking db version", e); status = STATUS.CLOSED; return; } try { final String oldestEntryStr = localDB.get(META_DB, KEY_OLDEST_ENTRY); if (oldestEntryStr == null || oldestEntryStr.length() < 1) { oldestEntry = 0; LOGGER.trace("no oldestEntry timestamp stored, will rescan"); } else { oldestEntry = Long.parseLong(oldestEntryStr); LOGGER.trace( "oldest timestamp loaded from localDB, age is " + TimeDuration.fromCurrent(oldestEntry).asCompactString()); } } catch (LocalDBException e) { LOGGER.error( "unexpected error loading oldest-entry meta record, will remain closed: " + e.getMessage(), e); status = STATUS.CLOSED; return; } try { final int size = localDB.size(WORDS_DB); final StringBuilder sb = new StringBuilder(); sb.append("open with ").append(size).append(" words ("); sb.append(new TimeDuration(System.currentTimeMillis(), startTime).asCompactString()) .append(")"); sb.append(", maxAgeMs=").append(new TimeDuration(maxAgeMs).asCompactString()); sb.append(", oldestEntry=") .append(new TimeDuration(System.currentTimeMillis(), oldestEntry).asCompactString()); LOGGER.info(sb.toString()); } catch (LocalDBException e) { LOGGER.error( "unexpected error examining size of DB, will remain closed: " + e.getMessage(), e); status = STATUS.CLOSED; return; } status = STATUS.OPEN; // populateFromWordlist(); //only used for debugging!!! if (pwmApplication.getApplicationMode() == PwmApplication.MODE.RUNNING || pwmApplication.getApplicationMode() == PwmApplication.MODE.CONFIGURATION) { long frequencyMs = maxAgeMs > MAX_CLEANER_FREQUENCY ? MAX_CLEANER_FREQUENCY : maxAgeMs; frequencyMs = frequencyMs < MIN_CLEANER_FREQUENCY ? MIN_CLEANER_FREQUENCY : frequencyMs; LOGGER.debug( "scheduling cleaner task to run once every " + new TimeDuration(frequencyMs).asCompactString()); final String threadName = Helper.makeThreadName(pwmApplication, this.getClass()) + " timer"; cleanerTimer = new Timer(threadName, true); cleanerTimer.schedule(new CleanerTask(), 1000, frequencyMs); } } private String normalizeWord(final String input) { if (input == null) { return null; } String word = input.trim(); if (settings.caseInsensitive) { word = word.toLowerCase(); } return word.length() > 0 ? word : null; } public synchronized void addWord(final PwmSession pwmSession, final String word) { if (status != STATUS.OPEN) { return; } final String addWord = normalizeWord(word); if (addWord == null) { return; } final long startTime = System.currentTimeMillis(); try { final String hashedWord = hashWord(addWord); final boolean preExisting = localDB.contains(WORDS_DB, hashedWord); localDB.put(WORDS_DB, hashedWord, Long.toString(System.currentTimeMillis())); { final StringBuilder logOutput = new StringBuilder(); logOutput.append(preExisting ? "updated" : "added").append(" word"); logOutput .append(" (") .append(new TimeDuration(System.currentTimeMillis(), startTime).asCompactString()) .append(")"); logOutput.append(" (").append(this.size()).append(" total words)"); LOGGER.trace(logOutput.toString()); } } catch (Exception e) { LOGGER.warn(pwmSession, "error adding word to global history list: " + e.getMessage()); } } private String hashWord(final String word) throws NoSuchAlgorithmException { final MessageDigest md = MessageDigest.getInstance(settings.hashName); final String wordWithSalt = salt + word; final int hashLoopCount = settings.hashIterations; byte[] hashedAnswer = md.digest((wordWithSalt).getBytes()); for (int i = 0; i < hashLoopCount; i++) { hashedAnswer = md.digest(hashedAnswer); } return Helper.binaryArrayToHex(hashedAnswer); } // -------------------------- INNER CLASSES -------------------------- private class CleanerTask extends TimerTask { final Sleeper sleeper = new Sleeper(10); private CleanerTask() {} public void run() { try { reduceWordDB(); } catch (LocalDBException e) { LOGGER.error("error during old record purge: " + e.getMessage()); } } private void reduceWordDB() throws LocalDBException { if (localDB == null || localDB.status() != LocalDB.Status.OPEN) { return; } final long oldestEntryAge = System.currentTimeMillis() - oldestEntry; if (oldestEntryAge < settings.maxAgeMs) { LOGGER.debug( "skipping wordDB reduce operation, eldestEntry=" + TimeDuration.asCompactString(oldestEntryAge) + ", maxAge=" + TimeDuration.asCompactString(settings.maxAgeMs)); return; } final long startTime = System.currentTimeMillis(); final int initialSize = size(); int removeCount = 0; long localOldestEntry = System.currentTimeMillis(); LOGGER.debug( "beginning wordDB reduce operation, examining " + initialSize + " words for entries older than " + TimeDuration.asCompactString(settings.maxAgeMs)); LocalDB.LocalDBIterator<String> keyIterator = null; try { keyIterator = localDB.iterator(WORDS_DB); while (status == STATUS.OPEN && keyIterator.hasNext()) { final String key = keyIterator.next(); final String value = localDB.get(WORDS_DB, key); final long timeStamp = Long.parseLong(value); final long entryAge = System.currentTimeMillis() - timeStamp; if (entryAge > settings.maxAgeMs) { localDB.remove(WORDS_DB, key); removeCount++; if (removeCount % 1000 == 0) { LOGGER.trace( "wordDB reduce operation in progress, removed=" + removeCount + ", total=" + (initialSize - removeCount)); } } else { localOldestEntry = timeStamp < localOldestEntry ? timeStamp : localOldestEntry; } sleeper.sleep(); } } finally { try { if (keyIterator != null) { keyIterator.close(); } } catch (Exception e) { LOGGER.warn("error returning LocalDB iterator: " + e.getMessage()); } } // update the oldest entry if (status == STATUS.OPEN) { oldestEntry = localOldestEntry; localDB.put(META_DB, KEY_OLDEST_ENTRY, Long.toString(oldestEntry)); } final StringBuilder sb = new StringBuilder(); sb.append("completed wordDB reduce operation"); sb.append(", removed=").append(removeCount); sb.append(", totalRemaining=").append(size()); sb.append(", oldestEntry=").append(TimeDuration.asCompactString(oldestEntry)); sb.append(" in ") .append(TimeDuration.asCompactString(System.currentTimeMillis() - startTime)); LOGGER.debug(sb.toString()); } } public List<HealthRecord> healthCheck() { return null; } public void init(final PwmApplication pwmApplication) throws PwmException { settings.maxAgeMs = 1000 * pwmApplication .getConfig() .readSettingAsLong(PwmSetting.PASSWORD_SHAREDHISTORY_MAX_AGE); // convert to MS; settings.caseInsensitive = Boolean.parseBoolean( pwmApplication .getConfig() .readAppProperty(AppProperty.SECURITY_SHAREDHISTORY_CASE_INSENSITIVE)); settings.hashName = pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_SHAREDHISTORY_HASH_NAME); settings.hashIterations = Integer.parseInt( pwmApplication .getConfig() .readAppProperty(AppProperty.SECURITY_SHAREDHISTORY_HASH_ITERATIONS)); settings.version = "2" + "_" + settings.hashName + "_" + settings.hashIterations + "_" + settings.caseInsensitive; final int SALT_LENGTH = Integer.parseInt( pwmApplication .getConfig() .readAppProperty(AppProperty.SECURITY_SHAREDHISTORY_SALT_LENGTH)); this.localDB = pwmApplication.getLocalDB(); boolean needsClearing = false; if (localDB == null) { LOGGER.info("LocalDB is not available, will remain closed"); status = STATUS.CLOSED; return; } if (settings.maxAgeMs < 1) { LOGGER.debug("max age=" + settings.maxAgeMs + ", will remain closed"); needsClearing = true; } { this.salt = localDB.get(META_DB, KEY_SALT); if (salt == null || salt.length() < SALT_LENGTH) { LOGGER.warn("stored global salt value is not present, creating new salt"); this.salt = PwmRandom.getInstance().alphaNumericString(SALT_LENGTH); localDB.put(META_DB, KEY_SALT, this.salt); needsClearing = true; } } if (needsClearing) { LOGGER.trace("clearing wordlist"); try { localDB.truncate(WORDS_DB); } catch (Exception e) { LOGGER.error("error during wordlist truncate", e); } } new Thread( new Runnable() { public void run() { LOGGER.debug("starting up in background thread"); init(pwmApplication, settings.maxAgeMs); } }, Helper.makeThreadName(pwmApplication, this.getClass()) + " initializer") .start(); } private static class Settings { private String version; private String hashName; private int hashIterations; private long maxAgeMs; private boolean caseInsensitive; } public ServiceInfo serviceInfo() { if (status == STATUS.OPEN) { return new ServiceInfo( Collections.<DataStorageMethod>singletonList(DataStorageMethod.LOCALDB)); } else { return new ServiceInfo(Collections.<DataStorageMethod>emptyList()); } } }
/** * Servlet for creating new guest users (helpdesk/admin registration) * * @author Jason D. Rivard, Menno Pieters */ @WebServlet( name = "GuestRegistrationServlet", urlPatterns = { PwmConstants.URL_PREFIX_PRIVATE + "/guest-registration", PwmConstants.URL_PREFIX_PRIVATE + "/GuestRegistration", }) public class GuestRegistrationServlet extends AbstractPwmServlet { private static final PwmLogger LOGGER = PwmLogger.forClass(GuestRegistrationServlet.class); public static final String HTTP_PARAM_EXPIRATION_DATE = "_expirationDateFormInput"; public enum Page { create, search } public enum GuestRegistrationAction implements AbstractPwmServlet.ProcessAction { create, search, update, selectPage, ; public Collection<HttpMethod> permittedMethods() { return Collections.singletonList(HttpMethod.POST); } } protected GuestRegistrationAction readProcessAction(final PwmRequest request) throws PwmUnrecoverableException { try { return GuestRegistrationAction.valueOf( request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST)); } catch (IllegalArgumentException e) { return null; } } protected void processAction(final PwmRequest pwmRequest) throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException { // Fetch the session state bean. final PwmSession pwmSession = pwmRequest.getPwmSession(); final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final GuestRegistrationBean guestRegistrationBean = pwmApplication.getSessionStateService().getBean(pwmRequest, GuestRegistrationBean.class); final Configuration config = pwmApplication.getConfig(); if (!config.readSettingAsBoolean(PwmSetting.GUEST_ENABLE)) { pwmRequest.respondWithError(PwmError.ERROR_SERVICE_NOT_AVAILABLE.toInfo()); return; } if (!pwmSession .getSessionManager() .checkPermission(pwmApplication, Permission.GUEST_REGISTRATION)) { pwmRequest.respondWithError(PwmError.ERROR_UNAUTHORIZED.toInfo()); return; } checkConfiguration(config); final GuestRegistrationAction action = readProcessAction(pwmRequest); if (action != null) { pwmRequest.validatePwmFormID(); switch (action) { case create: handleCreateRequest(pwmRequest, guestRegistrationBean); return; case search: handleSearchRequest(pwmRequest, guestRegistrationBean); return; case update: handleUpdateRequest(pwmRequest, guestRegistrationBean); return; case selectPage: handleSelectPageRequest(pwmRequest, guestRegistrationBean); return; } } this.forwardToJSP(pwmRequest, guestRegistrationBean); } protected void handleSelectPageRequest( final PwmRequest pwmRequest, final GuestRegistrationBean guestRegistrationBean) throws PwmUnrecoverableException, IOException, ServletException { final String requestedPage = pwmRequest.readParameterAsString("page"); try { guestRegistrationBean.setCurrentPage(Page.valueOf(requestedPage)); } catch (IllegalArgumentException e) { LOGGER.error(pwmRequest, "unknown page select request: " + requestedPage); } this.forwardToJSP(pwmRequest, guestRegistrationBean); } protected void handleUpdateRequest( final PwmRequest pwmRequest, final GuestRegistrationBean guestRegistrationBean) throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException { // Fetch the session state bean. final PwmSession pwmSession = pwmRequest.getPwmSession(); final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean(); final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final Configuration config = pwmApplication.getConfig(); final List<FormConfiguration> formItems = pwmApplication.getConfig().readSettingAsForm(PwmSetting.GUEST_UPDATE_FORM); final String expirationAttribute = config.readSettingAsString(PwmSetting.GUEST_EXPIRATION_ATTRIBUTE); try { // read the values from the request final Map<FormConfiguration, String> formValues = FormUtility.readFormValuesFromRequest(pwmRequest, formItems, pwmRequest.getLocale()); // see if the values meet form requirements. FormUtility.validateFormValues(config, formValues, ssBean.getLocale()); // read current values from user. final ChaiUser theGuest = pwmSession .getSessionManager() .getActor(pwmApplication, guestRegistrationBean.getUpdateUserIdentity()); // check unique fields against ldap FormUtility.validateFormValueUniqueness( pwmApplication, formValues, ssBean.getLocale(), Collections.singletonList(guestRegistrationBean.getUpdateUserIdentity()), false); final Date expirationDate = readExpirationFromRequest(pwmRequest); // Update user attributes Helper.writeFormValuesToLdap(pwmApplication, pwmSession, theGuest, formValues, false); // Write expirationDate if (expirationDate != null) { theGuest.writeDateAttribute(expirationAttribute, expirationDate); } // send email. final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, pwmSession.getLabel()); final UserInfoBean guestUserInfoBean = new UserInfoBean(); userStatusReader.populateUserInfoBean( guestUserInfoBean, pwmSession.getSessionStateBean().getLocale(), guestRegistrationBean.getUpdateUserIdentity(), theGuest.getChaiProvider()); this.sendUpdateGuestEmailConfirmation(pwmRequest, guestUserInfoBean); pwmApplication.getStatisticsManager().incrementValue(Statistic.UPDATED_GUESTS); // everything good so forward to confirmation page. pwmRequest.getPwmResponse().forwardToSuccessPage(Message.Success_UpdateGuest); return; } catch (PwmOperationalException e) { LOGGER.error(pwmSession, e.getErrorInformation().toDebugStr()); pwmRequest.setResponseError(e.getErrorInformation()); } catch (ChaiOperationException e) { final ErrorInformation info = new ErrorInformation( PwmError.ERROR_UNKNOWN, "unexpected error writing to ldap: " + e.getMessage()); LOGGER.error(pwmSession, info); pwmRequest.setResponseError(info); } this.forwardToUpdateJSP(pwmRequest, guestRegistrationBean); } private void sendUpdateGuestEmailConfirmation( final PwmRequest pwmRequest, final UserInfoBean guestUserInfoBean) throws PwmUnrecoverableException { final Configuration config = pwmRequest.getConfig(); final Locale locale = pwmRequest.getLocale(); final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_UPDATEGUEST, locale); if (configuredEmailSetting == null) { LOGGER.debug(pwmRequest, "unable to send updated guest user email: no email configured"); return; } pwmRequest .getPwmApplication() .getEmailQueue() .submitEmail(configuredEmailSetting, guestUserInfoBean, null); } protected void handleSearchRequest( final PwmRequest pwmRequest, final GuestRegistrationBean guestRegistrationBean) throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException { LOGGER.trace(pwmRequest, "Enter: handleSearchRequest(...)"); final PwmSession pwmSession = pwmRequest.getPwmSession(); final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final ChaiProvider chaiProvider = pwmSession.getSessionManager().getChaiProvider(); final Configuration config = pwmApplication.getConfig(); final String adminDnAttribute = config.readSettingAsString(PwmSetting.GUEST_ADMIN_ATTRIBUTE); final Boolean origAdminOnly = config.readSettingAsBoolean(PwmSetting.GUEST_EDIT_ORIG_ADMIN_ONLY); final String usernameParam = pwmRequest.readParameterAsString("username"); final GuestRegistrationBean guBean = pwmApplication.getSessionStateService().getBean(pwmRequest, GuestRegistrationBean.class); final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration(); searchConfiguration.setChaiProvider(chaiProvider); searchConfiguration.setContexts( Collections.singletonList(config.readSettingAsString(PwmSetting.GUEST_CONTEXT))); searchConfiguration.setEnableContextValidation(false); searchConfiguration.setUsername(usernameParam); final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, pwmSession.getLabel()); try { final UserIdentity theGuest = userSearchEngine.performSingleUserSearch(searchConfiguration); final FormMap formProps = guBean.getFormValues(); try { final List<FormConfiguration> guestUpdateForm = config.readSettingAsForm(PwmSetting.GUEST_UPDATE_FORM); final Set<String> involvedAttrs = new HashSet<>(); for (final FormConfiguration formItem : guestUpdateForm) { if (!formItem.getName().equalsIgnoreCase(HTTP_PARAM_EXPIRATION_DATE)) { involvedAttrs.add(formItem.getName()); } } final UserDataReader userDataReader = LdapUserDataReader.selfProxiedReader(pwmApplication, pwmSession, theGuest); final Map<String, String> userAttrValues = userDataReader.readStringAttributes(involvedAttrs); if (origAdminOnly && adminDnAttribute != null && adminDnAttribute.length() > 0) { final String origAdminDn = userAttrValues.get(adminDnAttribute); if (origAdminDn != null && origAdminDn.length() > 0) { if (!pwmSession .getUserInfoBean() .getUserIdentity() .getUserDN() .equalsIgnoreCase(origAdminDn)) { final ErrorInformation info = new ErrorInformation(PwmError.ERROR_ORIG_ADMIN_ONLY); pwmRequest.setResponseError(info); LOGGER.warn(pwmSession, info); this.forwardToJSP(pwmRequest, guestRegistrationBean); } } } final String expirationAttribute = config.readSettingAsString(PwmSetting.GUEST_EXPIRATION_ATTRIBUTE); if (expirationAttribute != null && expirationAttribute.length() > 0) { final Date expiration = userDataReader.readDateAttribute(expirationAttribute); if (expiration != null) { guBean.setUpdateUserExpirationDate(expiration); } } for (final FormConfiguration formItem : guestUpdateForm) { final String key = formItem.getName(); final String value = userAttrValues.get(key); if (value != null) { formProps.put(key, value); } } guBean.setUpdateUserIdentity(theGuest); this.forwardToUpdateJSP(pwmRequest, guestRegistrationBean); return; } catch (ChaiOperationException e) { LOGGER.warn(pwmSession, "error reading current attributes for user: "******"Attribute from form: " + formItem.getName() + " = " + formValues.get(formItem)); final String n = formItem.getName(); final String v = formValues.get(formItem); if (n != null && n.length() > 0 && v != null && v.length() > 0) { createAttributes.put(n, v); } } // Write creator DN createAttributes.put( config.readSettingAsString(PwmSetting.GUEST_ADMIN_ATTRIBUTE), pwmSession.getUserInfoBean().getUserIdentity().getUserDN()); // read the creation object classes. final Set<String> createObjectClasses = new HashSet<>(config.readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES)); provider.createEntry(guestUserDN, createObjectClasses, createAttributes); LOGGER.info(pwmSession, "created user object: " + guestUserDN); final ChaiUser theUser = ChaiFactory.createChaiUser(guestUserDN, provider); final UserIdentity userIdentity = new UserIdentity( guestUserDN, pwmSession.getUserInfoBean().getUserIdentity().getLdapProfileID()); // write the expiration date: if (expirationDate != null) { final String expirationAttr = config.readSettingAsString(PwmSetting.GUEST_EXPIRATION_ATTRIBUTE); theUser.writeDateAttribute(expirationAttr, expirationDate); } final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( pwmApplication, pwmSession.getLabel(), userIdentity, theUser, locale); final PasswordData newPassword = RandomPasswordGenerator.createRandomPassword( pwmSession.getLabel(), passwordPolicy, pwmApplication); theUser.setPassword(newPassword.getStringValue()); /* final UserInfoBean guestUserInfoBean = new UserInfoBean(); final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication); userStatusReader.populateUserInfoBean( pwmSession.getLabel(), guestUserInfoBean, pwmSession.getSessionStateBean().getLocale(), userIdentity, theUser.getChaiProvider() ); */ { // execute configured actions LOGGER.debug(pwmSession, "executing configured actions to user " + theUser.getEntryDN()); final List<ActionConfiguration> actions = pwmApplication.getConfig().readSettingAsAction(PwmSetting.GUEST_WRITE_ATTRIBUTES); if (actions != null && !actions.isEmpty()) { final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity); final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, theUser) .setExpandPwmMacros(true) .setMacroMachine(macroMachine) .createActionExecutor(); actionExecutor.executeActions(actions, pwmSession); } } // everything good so forward to success page. this.sendGuestUserEmailConfirmation(pwmRequest, userIdentity); pwmApplication.getStatisticsManager().incrementValue(Statistic.NEW_USERS); pwmRequest.getPwmResponse().forwardToSuccessPage(Message.Success_CreateGuest); } catch (ChaiOperationException e) { final ErrorInformation info = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, "error creating user: "******"yyyy-MM-dd").parse(expirationDateStr); } catch (ParseException e) { final String errorMsg = "unable to read expiration date value: " + e.getMessage(); throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_FIELD_REQUIRED, errorMsg, new String[] {"expiration date"})); } if (expirationDate.before(new Date())) { final String errorMsg = "expiration date must be in the future"; throw new PwmOperationalException( new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, errorMsg)); } final long durationValueMs = durationValueDays * 24 * 60 * 60 * 1000; final long futureDateMs = System.currentTimeMillis() + durationValueMs; final Date futureDate = new Date(futureDateMs); if (expirationDate.after(futureDate)) { final String errorMsg = "expiration date must be sooner than " + futureDate.toString(); throw new PwmOperationalException( new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, errorMsg)); } LOGGER.trace(pwmRequest, "read expiration date as " + expirationDate.toString()); return expirationDate; } private static String determineUserDN( final Map<FormConfiguration, String> formValues, final Configuration config) throws PwmUnrecoverableException { final String namingAttribute = config.getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE); for (final FormConfiguration formItem : formValues.keySet()) { if (namingAttribute.equals(formItem.getName())) { final String namingValue = formValues.get(formItem); final String gestUserContextDN = config.readSettingAsString(PwmSetting.GUEST_CONTEXT); return namingAttribute + "=" + namingValue + "," + gestUserContextDN; } } final String errorMsg = "unable to determine new user DN due to missing form value for naming attribute '" + namingAttribute + '"'; throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg)); } private void sendGuestUserEmailConfirmation( final PwmRequest pwmRequest, final UserIdentity userIdentity) throws PwmUnrecoverableException { final PwmSession pwmSession = pwmRequest.getPwmSession(); final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final Configuration config = pwmApplication.getConfig(); final Locale locale = pwmSession.getSessionStateBean().getLocale(); final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_GUEST, locale); if (configuredEmailSetting == null) { LOGGER.debug( pwmSession, "unable to send guest registration email for '" + userIdentity + "' no email configured"); return; } final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity); pwmApplication.getEmailQueue().submitEmail(configuredEmailSetting, null, macroMachine); } private void forwardToJSP( final PwmRequest pwmRequest, final GuestRegistrationBean guestRegistrationBean) throws IOException, ServletException, PwmUnrecoverableException { calculateFutureDateFlags(pwmRequest, guestRegistrationBean); if (Page.search == guestRegistrationBean.getCurrentPage()) { pwmRequest.addFormInfoToRequestAttr(PwmSetting.GUEST_UPDATE_FORM, false, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.GUEST_UPDATE_SEARCH); } else { pwmRequest.addFormInfoToRequestAttr(PwmSetting.GUEST_FORM, false, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.GUEST_REGISTRATION); } } private void forwardToUpdateJSP( final PwmRequest pwmRequest, final GuestRegistrationBean guestRegistrationBean) throws IOException, ServletException, PwmUnrecoverableException { calculateFutureDateFlags(pwmRequest, guestRegistrationBean); final List<FormConfiguration> guestUpdateForm = pwmRequest.getConfig().readSettingAsForm(PwmSetting.GUEST_UPDATE_FORM); final Map<FormConfiguration, String> formValueMap = new LinkedHashMap<>(); for (final FormConfiguration formConfiguration : guestUpdateForm) { final String value = guestRegistrationBean.getFormValues().get(formConfiguration.getName()); formValueMap.put(formConfiguration, value); } pwmRequest.addFormInfoToRequestAttr(guestUpdateForm, formValueMap, false, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.GUEST_UPDATE); } private static void checkConfiguration(final Configuration configuration) throws PwmUnrecoverableException { final String namingAttribute = configuration.getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE); final List<FormConfiguration> formItems = configuration.readSettingAsForm(PwmSetting.GUEST_FORM); { boolean namingIsInForm = false; for (final FormConfiguration formItem : formItems) { if (namingAttribute.equalsIgnoreCase(formItem.getName())) { namingIsInForm = true; } } if (!namingIsInForm) { final String errorMsg = "ldap naming attribute '" + namingAttribute + "' is not in form configuration, but is required"; final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorMsg, new String[] {namingAttribute}); throw new PwmUnrecoverableException(errorInformation); } } } private void calculateFutureDateFlags( final PwmRequest pwmRequest, GuestRegistrationBean guestRegistrationBean) { final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); final long maxValidDays = pwmRequest.getConfig().readSettingAsLong(PwmSetting.GUEST_MAX_VALID_DAYS); pwmRequest.setAttribute( PwmRequest.Attribute.GuestMaximumValidDays, String.valueOf(maxValidDays)); final String maxExpirationDate; { if (maxValidDays > 0) { long futureMS = maxValidDays * 24 * 60 * 60 * 1000; Date maxValidDate = new Date(new Date().getTime() + (futureMS)); maxExpirationDate = DATE_FORMAT.format(maxValidDate); } else { maxExpirationDate = ""; } } final String currentExpirationDate; { String selectedDate = guestRegistrationBean.getFormValues().get(HTTP_PARAM_EXPIRATION_DATE); if (selectedDate == null || selectedDate.isEmpty()) { Date currentDate = guestRegistrationBean.getUpdateUserExpirationDate(); if (currentDate == null) { currentExpirationDate = maxExpirationDate; } else { currentExpirationDate = DATE_FORMAT.format(currentDate); } } else { currentExpirationDate = DATE_FORMAT.format(new Date()); } } pwmRequest.setAttribute(PwmRequest.Attribute.GuestCurrentExpirationDate, currentExpirationDate); pwmRequest.setAttribute(PwmRequest.Attribute.GuestMaximumExpirationDate, maxExpirationDate); } }
/** * User interaction servlet for updating user attributes * * @author Jason D. Rivard */ @WebServlet( name = "UpdateProfileServlet", urlPatterns = { PwmConstants.URL_PREFIX_PRIVATE + "/updateprofile", PwmConstants.URL_PREFIX_PRIVATE + "/UpdateProfile" }) public class UpdateProfileServlet extends AbstractPwmServlet { private static final PwmLogger LOGGER = PwmLogger.forClass(UpdateProfileServlet.class); public enum UpdateProfileAction implements AbstractPwmServlet.ProcessAction { updateProfile(HttpMethod.POST), agree(HttpMethod.POST), confirm(HttpMethod.POST), unConfirm(HttpMethod.POST), validate(HttpMethod.POST), enterCode(HttpMethod.POST), ; private final HttpMethod method; UpdateProfileAction(HttpMethod method) { this.method = method; } public Collection<HttpMethod> permittedMethods() { return Collections.singletonList(method); } } protected UpdateProfileAction readProcessAction(final PwmRequest request) throws PwmUnrecoverableException { try { return UpdateProfileAction.valueOf( request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST)); } catch (IllegalArgumentException e) { return null; } } protected void processAction(final PwmRequest pwmRequest) throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final UpdateAttributesProfile updateAttributesProfile = pwmRequest.getPwmSession().getSessionManager().getUpdateAttributeProfile(pwmApplication); final UpdateProfileBean updateProfileBean = pwmApplication.getSessionStateService().getBean(pwmRequest, UpdateProfileBean.class); if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_ENABLE)) { pwmRequest.respondWithError( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.UPDATE_PROFILE_ENABLE.toMenuLocationDebug(null, null) + " is not enabled.")); return; } if (updateAttributesProfile == null) { pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_NO_PROFILE_ASSIGNED)); return; } final UpdateProfileAction action = readProcessAction(pwmRequest); if (action != null) { pwmRequest.validatePwmFormID(); switch (action) { case updateProfile: handleUpdateRequest(pwmRequest, updateAttributesProfile, updateProfileBean); break; case agree: handleAgreeRequest(pwmRequest, updateProfileBean); break; case confirm: updateProfileBean.setConfirmationPassed(true); break; case unConfirm: handleUnconfirm(updateProfileBean); break; case validate: restValidateForm(pwmRequest, updateAttributesProfile, updateProfileBean); return; case enterCode: handleEnterCodeRequest(pwmRequest, updateProfileBean); break; } } advanceToNextStep(pwmRequest, updateAttributesProfile, updateProfileBean); } protected static void restValidateForm( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException { boolean success = true; String userMessage = Message.getLocalizedMessage( pwmRequest.getLocale(), Message.Success_UpdateForm, pwmRequest.getConfig()); try { // read in the responses from the request final Map<FormConfiguration, String> formValues = readFromJsonRequest(pwmRequest, updateAttributesProfile, updateProfileBean); // verify form meets the form requirements verifyFormAttributes(pwmRequest, formValues, true); updateProfileBean.getFormData().putAll(FormUtility.asStringMap(formValues)); } catch (PwmOperationalException e) { success = false; userMessage = e.getErrorInformation() .toUserStr(pwmRequest.getPwmSession(), pwmRequest.getPwmApplication()); } final LinkedHashMap<String, String> outputMap = new LinkedHashMap<>(); outputMap.put("version", "1"); outputMap.put("message", userMessage); outputMap.put("success", String.valueOf(success)); pwmRequest.outputJsonResult(new RestResultBean(outputMap)); } private void handleUnconfirm(final UpdateProfileBean updateProfileBean) { updateProfileBean.setFormSubmitted(false); updateProfileBean.setConfirmationPassed(false); } private void advanceToNextStep( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final String updateProfileAgreementText = updateAttributesProfile.readSettingAsLocalizedString( PwmSetting.UPDATE_PROFILE_AGREEMENT_MESSAGE, pwmSession.getSessionStateBean().getLocale()); if (updateProfileAgreementText != null && updateProfileAgreementText.length() > 0) { if (!updateProfileBean.isAgreementPassed()) { final MacroMachine macroMachine = pwmRequest .getPwmSession() .getSessionManager() .getMacroMachine(pwmRequest.getPwmApplication()); final String expandedText = macroMachine.expandMacros(updateProfileAgreementText); pwmRequest.setAttribute(PwmRequest.Attribute.AgreementText, expandedText); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES_AGREEMENT); return; } } // make sure there is form data in the bean. if (updateProfileBean.getFormData() == null) { updateProfileBean.setFormData(formDataFromLdap(pwmRequest, updateAttributesProfile)); forwardToForm(pwmRequest, updateAttributesProfile, updateProfileBean); return; } if (!updateProfileBean.isFormSubmitted()) { forwardToForm(pwmRequest, updateAttributesProfile, updateProfileBean); return; } // validate the form data. try { // verify form meets the form requirements final List<FormConfiguration> formFields = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formValues = FormUtility.readFormValuesFromMap( updateProfileBean.getFormData(), formFields, pwmRequest.getLocale()); verifyFormAttributes(pwmRequest, formValues, true); } catch (PwmException e) { LOGGER.error(pwmSession, e.getMessage()); pwmRequest.setResponseError(e.getErrorInformation()); forwardToForm(pwmRequest, updateAttributesProfile, updateProfileBean); return; } final boolean requireConfirmation = updateAttributesProfile.readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_SHOW_CONFIRMATION); if (requireConfirmation && !updateProfileBean.isConfirmationPassed()) { forwardToConfirmForm(pwmRequest, updateAttributesProfile, updateProfileBean); return; } final Set<TokenVerificationProgress.TokenChannel> requiredVerifications = determineTokenPhaseRequired(pwmRequest, updateProfileBean, updateAttributesProfile); if (requiredVerifications != null) { for (final TokenVerificationProgress.TokenChannel tokenChannel : requiredVerifications) { if (requiredVerifications.contains(tokenChannel)) { if (!updateProfileBean .getTokenVerificationProgress() .getIssuedTokens() .contains(tokenChannel)) { initializeToken(pwmRequest, updateProfileBean, tokenChannel); } if (!updateProfileBean .getTokenVerificationProgress() .getPassedTokens() .contains(tokenChannel)) { updateProfileBean.getTokenVerificationProgress().setPhase(tokenChannel); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES_ENTER_CODE); return; } } } } try { // write the form values final ChaiUser theUser = pwmSession.getSessionManager().getActor(pwmApplication); doProfileUpdate(pwmRequest, updateProfileBean.getFormData(), theUser); pwmRequest.getPwmResponse().forwardToSuccessPage(Message.Success_UpdateProfile); return; } catch (PwmException e) { LOGGER.error(pwmSession, e.getMessage()); pwmRequest.setResponseError(e.getErrorInformation()); } catch (ChaiException e) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UPDATE_ATTRS_FAILURE, e.toString()); LOGGER.error(pwmSession, errorInformation.toDebugStr()); pwmRequest.setResponseError(errorInformation); } forwardToForm(pwmRequest, updateAttributesProfile, updateProfileBean); } private void handleAgreeRequest( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException { LOGGER.debug(pwmRequest, "user accepted agreement"); if (!updateProfileBean.isAgreementPassed()) { updateProfileBean.setAgreementPassed(true); AuditRecord auditRecord = pwmRequest .getPwmApplication() .getAuditManager() .createUserAuditRecord( AuditEvent.AGREEMENT_PASSED, pwmRequest.getUserInfoIfLoggedIn(), pwmRequest.getSessionLabel(), "UpdateProfile"); pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord); } } final Map<FormConfiguration, String> readFormParametersFromRequest( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws PwmUnrecoverableException, PwmDataValidationException, ChaiUnavailableException { final List<FormConfiguration> formFields = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); // read the values from the request final Map<FormConfiguration, String> formValueMap = FormUtility.readFormValuesFromRequest(pwmRequest, formFields, pwmRequest.getLocale()); updateProfileBean.getFormData().clear(); updateProfileBean.getFormData().putAll(FormUtility.asStringMap(formValueMap)); return formValueMap; } static Map<FormConfiguration, String> readFromJsonRequest( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws PwmDataValidationException, PwmUnrecoverableException, IOException { final List<FormConfiguration> formFields = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formValueMap = FormUtility.readFormValuesFromMap( pwmRequest.readBodyAsJsonStringMap(), formFields, pwmRequest.getLocale()); updateProfileBean.getFormData().clear(); updateProfileBean.getFormData().putAll(FormUtility.asStringMap(formValueMap)); return formValueMap; } private void handleUpdateRequest( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException { try { readFormParametersFromRequest(pwmRequest, updateAttributesProfile, updateProfileBean); } catch (PwmOperationalException e) { LOGGER.error(pwmRequest, e.getMessage()); pwmRequest.setResponseError(e.getErrorInformation()); } updateProfileBean.setFormSubmitted(true); } public static void doProfileUpdate( final PwmRequest pwmRequest, final Map<String, String> formValues, final ChaiUser theUser) throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final UserInfoBean uiBean = pwmRequest.getPwmSession().getUserInfoBean(); final UpdateAttributesProfile updateAttributesProfile = pwmRequest.getPwmSession().getSessionManager().getUpdateAttributeProfile(pwmApplication); final List<FormConfiguration> formFields = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formMap = FormUtility.readFormValuesFromMap(formValues, formFields, pwmRequest.getLocale()); // verify form meets the form requirements (may be redundant, but shouldn't hurt) verifyFormAttributes(pwmRequest, formMap, false); // write values. LOGGER.info( "updating profile for " + pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity()); pwmRequest.getPwmSession().getSessionManager().getChaiProvider(); Helper.writeFormValuesToLdap( pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), theUser, formMap, false); final UserIdentity userIdentity = uiBean.getUserIdentity(); // re-populate the uiBean because we have changed some values. final UserStatusReader userStatusReader = new UserStatusReader(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel()); userStatusReader.populateActorUserInfoBean(pwmRequest.getPwmSession(), userIdentity); // clear cached read attributes. pwmRequest.getPwmSession().getSessionManager().clearUserDataReader(); { // execute configured actions final List<ActionConfiguration> actions = updateAttributesProfile.readSettingAsAction(PwmSetting.UPDATE_PROFILE_WRITE_ATTRIBUTES); if (actions != null && !actions.isEmpty()) { LOGGER.debug(pwmRequest, "executing configured actions to user " + userIdentity); final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity) .setExpandPwmMacros(true) .setMacroMachine(pwmSession.getSessionManager().getMacroMachine(pwmApplication)) .createActionExecutor(); actionExecutor.executeActions(actions, pwmSession); } } sendProfileUpdateEmailNotice(pwmSession, pwmApplication); // mark the event log pwmApplication .getAuditManager() .submit(AuditEvent.UPDATE_PROFILE, pwmSession.getUserInfoBean(), pwmSession); // mark the uiBean so we user isn't recycled to the update profile page by the CommandServlet uiBean.setRequiresUpdateProfile(false); // clear out the updateProfileBean pwmApplication.getSessionStateService().clearBean(pwmRequest, UpdateProfileBean.class); // success, so forward to success page pwmApplication.getStatisticsManager().incrementValue(Statistic.UPDATE_ATTRIBUTES); } private static void verifyFormAttributes( final PwmRequest pwmRequest, final Map<FormConfiguration, String> formValues, final boolean allowResultCaching) throws PwmOperationalException, PwmUnrecoverableException { final Locale userLocale = pwmRequest.getLocale(); // see if the values meet form requirements. FormUtility.validateFormValues(pwmRequest.getConfig(), formValues, userLocale); // check unique fields against ldap FormUtility.validateFormValueUniqueness( pwmRequest.getPwmApplication(), formValues, userLocale, Collections.singletonList(pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity()), allowResultCaching); } private static void sendProfileUpdateEmailNotice( final PwmSession pwmSession, final PwmApplication pwmApplication) throws PwmUnrecoverableException, ChaiUnavailableException { final Configuration config = pwmApplication.getConfig(); final Locale locale = pwmSession.getSessionStateBean().getLocale(); final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_UPDATEPROFILE, locale); if (configuredEmailSetting == null) { LOGGER.debug( pwmSession, "skipping send profile update email for '" + pwmSession.getUserInfoBean().getUserIdentity() + "' no email configured"); return; } pwmApplication .getEmailQueue() .submitEmail( configuredEmailSetting, pwmSession.getUserInfoBean(), pwmSession.getSessionManager().getMacroMachine(pwmApplication)); } static void forwardToForm( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws ServletException, PwmUnrecoverableException, IOException { final List<FormConfiguration> form = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formValueMap = formMapFromBean(updateAttributesProfile, updateProfileBean); pwmRequest.addFormInfoToRequestAttr(form, formValueMap, false, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES); } static void forwardToConfirmForm( final PwmRequest pwmRequest, final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws ServletException, PwmUnrecoverableException, IOException { final List<FormConfiguration> form = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formValueMap = formMapFromBean(updateAttributesProfile, updateProfileBean); pwmRequest.addFormInfoToRequestAttr(form, formValueMap, true, false); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.UPDATE_ATTRIBUTES_CONFIRM); } static Map<FormConfiguration, String> formMapFromBean( final UpdateAttributesProfile updateAttributesProfile, final UpdateProfileBean updateProfileBean) throws PwmUnrecoverableException { final List<FormConfiguration> form = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formValueMap = new HashMap<>(); for (final FormConfiguration formConfiguration : form) { formValueMap.put( formConfiguration, updateProfileBean.getFormData().keySet().contains(formConfiguration.getName()) ? updateProfileBean.getFormData().get(formConfiguration.getName()) : ""); } return formValueMap; } static Map<String, String> formDataFromLdap( final PwmRequest pwmRequest, UpdateAttributesProfile updateAttributesProfile) throws PwmUnrecoverableException { final UserDataReader userDataReader = pwmRequest .getPwmSession() .getSessionManager() .getUserDataReader(pwmRequest.getPwmApplication()); final List<FormConfiguration> formFields = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM); final Map<FormConfiguration, String> formMap = new HashMap<>(); FormUtility.populateFormMapFromLdap( formFields, pwmRequest.getSessionLabel(), formMap, userDataReader); return FormUtility.asStringMap(formMap); } static Set<TokenVerificationProgress.TokenChannel> determineTokenPhaseRequired( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean, final UpdateAttributesProfile updateAttributesProfile) throws PwmUnrecoverableException { final Set<TokenVerificationProgress.TokenChannel> returnObj = new HashSet<>(); final Map<String, String> userFormData = updateProfileBean.getFormData(); Map<String, String> ldapData = null; if (updateAttributesProfile.readSettingAsBoolean( PwmSetting.UPDATE_PROFILE_EMAIL_VERIFICATION)) { final String emailAddressAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE); if (userFormData.containsKey(emailAddressAttribute)) { ldapData = formDataFromLdap(pwmRequest, updateAttributesProfile); if (userFormData.get(emailAddressAttribute) != null && !userFormData .get(emailAddressAttribute) .equalsIgnoreCase(ldapData.get(emailAddressAttribute))) { returnObj.add(TokenVerificationProgress.TokenChannel.EMAIL); } } else { LOGGER.warn( pwmRequest, "email verification enabled, but email attribute '" + emailAddressAttribute + "' is not in update form"); } } if (updateAttributesProfile.readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_SMS_VERIFICATION)) { final String phoneNumberAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.SMS_USER_PHONE_ATTRIBUTE); if (userFormData.containsKey(phoneNumberAttribute)) { if (ldapData == null) { ldapData = formDataFromLdap(pwmRequest, updateAttributesProfile); } if (userFormData.get(phoneNumberAttribute) != null && !userFormData .get(phoneNumberAttribute) .equalsIgnoreCase(ldapData.get(phoneNumberAttribute))) { returnObj.add(TokenVerificationProgress.TokenChannel.SMS); } } else { LOGGER.warn( pwmRequest, "sms verification enabled, but phone attribute '" + phoneNumberAttribute + "' is not in update form"); } } return returnObj; } public void initializeToken( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean, final TokenVerificationProgress.TokenChannel tokenType) throws PwmUnrecoverableException { final PwmSession pwmSession = pwmRequest.getPwmSession(); final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); if (pwmApplication.getConfig().getTokenStorageMethod() == TokenStorageMethod.STORE_LDAP) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "cannot generate new user tokens when storage type is configured as STORE_LDAP." })); } final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmApplication); final Configuration config = pwmApplication.getConfig(); switch (tokenType) { case SMS: { final String telephoneNumberAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.SMS_USER_PHONE_ATTRIBUTE); final String toNum = updateProfileBean.getFormData().get(telephoneNumberAttribute); final String tokenKey; try { final TokenPayload tokenPayload = pwmApplication .getTokenService() .createTokenPayload( TokenType.UPDATE_SMS, Collections.<String, String>emptyMap(), pwmRequest.getUserInfoIfLoggedIn(), Collections.singleton(toNum)); tokenKey = pwmApplication .getTokenService() .generateNewToken(tokenPayload, pwmRequest.getSessionLabel()); } catch (PwmOperationalException e) { throw new PwmUnrecoverableException(e.getErrorInformation()); } final String message = config.readSettingAsLocalizedString( PwmSetting.SMS_UPDATE_PROFILE_TOKEN_TEXT, pwmSession.getSessionStateBean().getLocale()); try { TokenService.TokenSender.sendSmsToken( pwmApplication, null, macroMachine, toNum, message, tokenKey); } catch (Exception e) { throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN)); } updateProfileBean .getTokenVerificationProgress() .getIssuedTokens() .add(TokenVerificationProgress.TokenChannel.SMS); updateProfileBean.getTokenVerificationProgress().setTokenDisplayText(toNum); updateProfileBean .getTokenVerificationProgress() .setPhase(TokenVerificationProgress.TokenChannel.SMS); } break; case EMAIL: { final EmailItemBean configuredEmailSetting = config.readSettingAsEmail( PwmSetting.EMAIL_UPDATEPROFILE_VERIFICATION, pwmRequest.getLocale()); final String emailAddressAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE); final String toAddress = updateProfileBean.getFormData().get(emailAddressAttribute); final String tokenKey; try { final TokenPayload tokenPayload = pwmApplication .getTokenService() .createTokenPayload( TokenType.UPDATE_EMAIL, Collections.<String, String>emptyMap(), pwmRequest.getUserInfoIfLoggedIn(), Collections.singleton(toAddress)); tokenKey = pwmApplication .getTokenService() .generateNewToken(tokenPayload, pwmRequest.getSessionLabel()); } catch (PwmOperationalException e) { throw new PwmUnrecoverableException(e.getErrorInformation()); } updateProfileBean .getTokenVerificationProgress() .getIssuedTokens() .add(TokenVerificationProgress.TokenChannel.EMAIL); updateProfileBean .getTokenVerificationProgress() .setPhase(TokenVerificationProgress.TokenChannel.EMAIL); updateProfileBean.getTokenVerificationProgress().setTokenDisplayText(toAddress); final EmailItemBean emailItemBean = new EmailItemBean( toAddress, configuredEmailSetting.getFrom(), configuredEmailSetting.getSubject(), configuredEmailSetting.getBodyPlain().replace("%TOKEN%", tokenKey), configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenKey)); try { TokenService.TokenSender.sendEmailToken( pwmApplication, null, macroMachine, emailItemBean, toAddress, tokenKey); } catch (Exception e) { throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN)); } } break; default: LOGGER.error("Unimplemented token purpose: " + tokenType); updateProfileBean.getTokenVerificationProgress().setPhase(null); } } private void handleEnterCodeRequest( final PwmRequest pwmRequest, final UpdateProfileBean updateProfileBean) throws PwmUnrecoverableException, IOException, ServletException, ChaiUnavailableException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); final String userEnteredCode = pwmRequest.readParameterAsString(PwmConstants.PARAM_TOKEN); boolean tokenPassed = false; ErrorInformation errorInformation = null; try { final TokenPayload tokenPayload = pwmApplication .getTokenService() .processUserEnteredCode( pwmSession, pwmRequest.getUserInfoIfLoggedIn(), null, userEnteredCode); if (tokenPayload != null) { if (TokenType.UPDATE_EMAIL.matchesName(tokenPayload.getName())) { LOGGER.debug(pwmRequest, "email token passed"); updateProfileBean .getTokenVerificationProgress() .getPassedTokens() .add(TokenVerificationProgress.TokenChannel.EMAIL); updateProfileBean .getTokenVerificationProgress() .getIssuedTokens() .add(TokenVerificationProgress.TokenChannel.EMAIL); updateProfileBean.getTokenVerificationProgress().setPhase(null); tokenPassed = true; } else if (TokenType.UPDATE_SMS.matchesName(tokenPayload.getName())) { LOGGER.debug(pwmRequest, "SMS token passed"); updateProfileBean .getTokenVerificationProgress() .getPassedTokens() .add(TokenVerificationProgress.TokenChannel.SMS); updateProfileBean .getTokenVerificationProgress() .getIssuedTokens() .add(TokenVerificationProgress.TokenChannel.SMS); updateProfileBean.getTokenVerificationProgress().setPhase(null); tokenPassed = true; } else { final String errorMsg = "token name/type is not recognized: " + tokenPayload.getName(); errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT, errorMsg); } } } catch (PwmOperationalException e) { final String errorMsg = "token incorrect: " + e.getMessage(); errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT, errorMsg); } if (!tokenPassed) { if (errorInformation == null) { errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT); } LOGGER.debug(pwmSession, errorInformation.toDebugStr()); pwmRequest.setResponseError(errorInformation); } } }
public class DbCrOperator implements CrOperator { private static final PwmLogger LOGGER = PwmLogger.forClass(DbCrOperator.class); final PwmApplication pwmApplication; public DbCrOperator(PwmApplication pwmApplication) { this.pwmApplication = pwmApplication; } public void close() {} public ResponseSet readResponseSet( final ChaiUser theUser, final UserIdentity userIdentity, final String userGUID) throws PwmUnrecoverableException { if (userGUID == null || userGUID.length() < 1) { final String errorMsg = "user " + theUser.getEntryDN() + " does not have a guid, unable to search for responses in remote database"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_GUID, errorMsg); throw new PwmUnrecoverableException(errorInformation); } try { final DatabaseAccessorImpl databaseAccessor = pwmApplication.getDatabaseAccessor(); final String responseStringBlob = databaseAccessor.get(DatabaseTable.PWM_RESPONSES, userGUID); if (responseStringBlob != null && responseStringBlob.length() > 0) { final ResponseSet userResponseSet = ChaiResponseSet.parseChaiResponseSetXML(responseStringBlob, theUser); LOGGER.debug( "found responses for " + theUser.getEntryDN() + " in remote database: " + userResponseSet.toString()); return userResponseSet; } else { LOGGER.trace( "user guid for " + theUser.getEntryDN() + " not found in remote database (key=" + userGUID + ")"); } } catch (ChaiValidationException e) { final String errorMsg = "unexpected error reading responses for " + theUser.getEntryDN() + " from remote database: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); throw new PwmUnrecoverableException(errorInformation); } catch (PwmOperationalException e) { final String errorMsg = "unexpected error reading responses for " + theUser.getEntryDN() + " from remote database: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(e.getErrorInformation().getError(), errorMsg); throw new PwmUnrecoverableException(errorInformation); } return null; } public ResponseInfoBean readResponseInfo( ChaiUser theUser, final UserIdentity userIdentity, String userGUID) throws PwmUnrecoverableException { try { final ResponseSet responseSet = readResponseSet(theUser, userIdentity, userGUID); return responseSet == null ? null : CrOperators.convertToNoAnswerInfoBean(responseSet, DataStorageMethod.DB); } catch (ChaiException e) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, "unexpected error reading response info for " + theUser.getEntryDN() + ", error: " + e.getMessage())); } } public void clearResponses(final ChaiUser theUser, final String userGUID) throws PwmUnrecoverableException { if (userGUID == null || userGUID.length() < 1) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot clear responses to remote database, user " + theUser.getEntryDN() + " does not have a guid")); } try { final DatabaseAccessorImpl databaseAccessor = pwmApplication.getDatabaseAccessor(); databaseAccessor.remove(DatabaseTable.PWM_RESPONSES, userGUID); LOGGER.info("cleared responses for user " + theUser.getEntryDN() + " in remote database"); } catch (DatabaseException e) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CLEARING_RESPONSES, "unexpected error clearing responses for " + theUser.getEntryDN() + " in remote database, error: " + e.getMessage()); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo); pwmOE.initCause(e); throw pwmOE; } } @Override public void writeResponses(ChaiUser theUser, String userGUID, ResponseInfoBean responseInfoBean) throws PwmUnrecoverableException { if (userGUID == null || userGUID.length() < 1) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_GUID, "cannot save responses to remote database, user " + theUser.getEntryDN() + " does not have a guid")); } LOGGER.trace( "attempting to save responses for " + theUser.getEntryDN() + " in remote database (key=" + userGUID + ")"); try { final ChaiResponseSet responseSet = ChaiCrFactory.newChaiResponseSet( responseInfoBean.getCrMap(), responseInfoBean.getHelpdeskCrMap(), responseInfoBean.getLocale(), responseInfoBean.getMinRandoms(), theUser.getChaiProvider().getChaiConfiguration(), responseInfoBean.getCsIdentifier()); final DatabaseAccessorImpl databaseAccessor = pwmApplication.getDatabaseAccessor(); databaseAccessor.put(DatabaseTable.PWM_RESPONSES, userGUID, responseSet.stringValue()); LOGGER.info( "saved responses for " + theUser.getEntryDN() + " in remote database (key=" + userGUID + ")"); } catch (ChaiException e) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, "unexpected error saving responses for " + theUser.getEntryDN() + " in remote database: " + e.getMessage()); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo); LOGGER.error(errorInfo.toDebugStr()); pwmOE.initCause(e); throw pwmOE; } catch (DatabaseException e) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, "unexpected error saving responses for " + theUser.getEntryDN() + " in remote database: " + e.getMessage()); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo); LOGGER.error(errorInfo.toDebugStr()); pwmOE.initCause(e); throw pwmOE; } } }
@WebServlet( name = "ConfigManagerServlet", urlPatterns = { PwmConstants.URL_PREFIX_PRIVATE + "/config/manager", PwmConstants.URL_PREFIX_PRIVATE + "/config/ConfigManager" }) public class ConfigManagerServlet extends AbstractPwmServlet { private static final PwmLogger LOGGER = PwmLogger.forClass(ConfigManagerServlet.class); public enum ConfigManagerAction implements ProcessAction { lockConfiguration(HttpMethod.POST), startEditing(HttpMethod.POST), downloadConfig(HttpMethod.GET), generateSupportZip(HttpMethod.GET), uploadConfig(HttpMethod.POST), uploadWordlist(HttpMethod.POST), summary(HttpMethod.GET), permissions(HttpMethod.GET), viewLog(HttpMethod.GET), ; private final HttpMethod method; ConfigManagerAction(HttpMethod method) { this.method = method; } public Collection<HttpMethod> permittedMethods() { return Collections.singletonList(method); } } protected ConfigManagerAction readProcessAction(final PwmRequest request) throws PwmUnrecoverableException { try { return ConfigManagerAction.valueOf( request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST)); } catch (IllegalArgumentException e) { return null; } } protected void processAction(final PwmRequest pwmRequest) throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException { final PwmSession pwmSession = pwmRequest.getPwmSession(); final ConfigManagerBean configManagerBean = pwmRequest .getPwmApplication() .getSessionStateService() .getBean(pwmRequest, ConfigManagerBean.class); final ConfigManagerAction processAction = readProcessAction(pwmRequest); if (processAction != null) { switch (processAction) { case lockConfiguration: restLockConfiguration(pwmRequest); break; case startEditing: doStartEditing(pwmRequest); break; case downloadConfig: doDownloadConfig(pwmRequest); break; case generateSupportZip: doGenerateSupportZip(pwmRequest); break; case uploadConfig: ConfigGuideServlet.restUploadConfig(pwmRequest); return; case uploadWordlist: restUploadWordlist(pwmRequest); return; case summary: showSummary(pwmRequest); return; case permissions: showPermissions(pwmRequest); return; } return; } initRequestAttributes(pwmRequest); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_MODE_CONFIGURATION); } void initRequestAttributes(final PwmRequest pwmRequest) throws PwmUnrecoverableException { final ConfigurationReader configurationReader = pwmRequest.getContextManager().getConfigReader(); pwmRequest.setAttribute( PwmRequest.Attribute.PageTitle, LocaleHelper.getLocalizedMessage(Config.Title_ConfigManager, pwmRequest)); pwmRequest.setAttribute( PwmRequest.Attribute.ApplicationPath, pwmRequest.getPwmApplication().getPwmEnvironment().getApplicationPath().getAbsolutePath()); pwmRequest.setAttribute( PwmRequest.Attribute.ConfigFilename, configurationReader.getConfigFile().getAbsolutePath()); { final Date lastModifyTime = configurationReader.getStoredConfiguration().modifyTime(); final String output = lastModifyTime == null ? LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable, pwmRequest) : PwmConstants.DEFAULT_DATETIME_FORMAT.format(lastModifyTime); pwmRequest.setAttribute(PwmRequest.Attribute.ConfigLastModified, output); } pwmRequest.setAttribute( PwmRequest.Attribute.ConfigHasPassword, LocaleHelper.booleanString( configurationReader.getStoredConfiguration().hasPassword(), pwmRequest.getLocale(), pwmRequest.getConfig())); } void restUploadWordlist(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final HttpServletRequest req = pwmRequest.getHttpServletRequest(); if (!ServletFileUpload.isMultipartContent(req)) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, "no file found in upload"); pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest)); LOGGER.error(pwmRequest, "error during import: " + errorInformation.toDebugStr()); return; } final InputStream inputStream = ServletHelper.readFileUpload(pwmRequest.getHttpServletRequest(), "uploadFile"); try { pwmApplication.getWordlistManager().populate(inputStream); } catch (PwmUnrecoverableException e) { final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage()); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmRequest, errorInfo.toDebugStr()); pwmRequest.outputJsonResult(restResultBean); return; } pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown)); } private void doStartEditing(final PwmRequest pwmRequest) throws IOException, PwmUnrecoverableException, ServletException { forwardToEditor(pwmRequest); } private void restLockConfiguration(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); if (PwmConstants.TRIAL_MODE) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_TRIAL_VIOLATION, "configuration lock not available in trial"); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmSession, errorInfo); pwmRequest.outputJsonResult(restResultBean); return; } if (!pwmSession.isAuthenticated()) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED, "You must be authenticated before restricting the configuration"); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmSession, errorInfo); pwmRequest.outputJsonResult(restResultBean); return; } if (!pwmSession.getSessionManager().checkPermission(pwmApplication, Permission.PWMADMIN)) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "You must be authenticated with admin privileges before restricting the configuration"); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmSession, errorInfo); pwmRequest.outputJsonResult(restResultBean); return; } try { final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest); if (!storedConfiguration.hasPassword()) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "Please set a configuration password before restricting the configuration" }); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmSession, errorInfo); pwmRequest.outputJsonResult(restResultBean); return; } storedConfiguration.writeConfigProperty(ConfigurationProperty.CONFIG_IS_EDITABLE, "false"); saveConfiguration(pwmRequest, storedConfiguration); final ConfigManagerBean configManagerBean = pwmRequest .getPwmApplication() .getSessionStateService() .getBean(pwmRequest, ConfigManagerBean.class); configManagerBean.setConfiguration(null); } catch (PwmException e) { final ErrorInformation errorInfo = e.getErrorInformation(); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmSession, errorInfo.toDebugStr()); pwmRequest.outputJsonResult(restResultBean); return; } catch (Exception e) { final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage()); final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest); LOGGER.debug(pwmSession, errorInfo.toDebugStr()); pwmRequest.outputJsonResult(restResultBean); return; } final HashMap<String, String> resultData = new HashMap<>(); LOGGER.info(pwmSession, "Configuration Locked"); pwmRequest.outputJsonResult(new RestResultBean(resultData)); } public static void saveConfiguration( final PwmRequest pwmRequest, final StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException { { final List<String> errorStrings = storedConfiguration.validateValues(); if (errorStrings != null && !errorStrings.isEmpty()) { final String errorString = errorStrings.get(0); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, null, new String[] {errorString})); } } try { ContextManager contextManager = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession().getServletContext()); contextManager .getConfigReader() .saveConfiguration( storedConfiguration, contextManager.getPwmApplication(), pwmRequest.getSessionLabel()); contextManager.requestPwmApplicationRestart(); } catch (Exception e) { final String errorString = "error saving file: " + e.getMessage(); LOGGER.error(pwmRequest, errorString); throw new PwmUnrecoverableException( new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, null, new String[] {errorString})); } } static void forwardToEditor(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException { final String url = pwmRequest.getHttpServletRequest().getContextPath() + "/private/config/ConfigEditor"; pwmRequest.sendRedirect(url); } private void doDownloadConfig(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException { final PwmSession pwmSession = pwmRequest.getPwmSession(); final PwmResponse resp = pwmRequest.getPwmResponse(); try { final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest); final OutputStream responseWriter = resp.getOutputStream(); resp.setHeader( PwmConstants.HttpHeader.ContentDisposition, "attachment;filename=" + PwmConstants.DEFAULT_CONFIG_FILE_FILENAME); resp.setContentType(PwmConstants.ContentTypeValue.xml); storedConfiguration.toXml(responseWriter); responseWriter.close(); } catch (Exception e) { LOGGER.error(pwmSession, "unable to download configuration: " + e.getMessage()); } } private void doGenerateSupportZip(final PwmRequest pwmRequest) throws IOException, ServletException { final PwmResponse resp = pwmRequest.getPwmResponse(); resp.setHeader( PwmConstants.HttpHeader.ContentDisposition, "attachment;filename=" + PwmConstants.PWM_APP_NAME + "-Support.zip"); resp.setContentType(PwmConstants.ContentTypeValue.zip); final String pathPrefix = PwmConstants.PWM_APP_NAME + "-Support" + "/"; ZipOutputStream zipOutput = null; try { zipOutput = new ZipOutputStream(resp.getOutputStream(), PwmConstants.DEFAULT_CHARSET); DebugItemGenerator.outputZipDebugFile(pwmRequest, zipOutput, pathPrefix); } catch (Exception e) { LOGGER.error(pwmRequest, "error during zip debug building: " + e.getMessage()); } finally { if (zipOutput != null) { try { zipOutput.close(); } catch (Exception e) { LOGGER.error(pwmRequest, "error during zip debug closing: " + e.getMessage()); } } } } public static StoredConfigurationImpl readCurrentConfiguration(final PwmRequest pwmRequest) throws PwmUnrecoverableException { final ContextManager contextManager = ContextManager.getContextManager(pwmRequest.getHttpServletRequest().getSession()); final ConfigurationReader runningConfigReader = contextManager.getConfigReader(); final StoredConfigurationImpl runningConfig = runningConfigReader.getStoredConfiguration(); return StoredConfigurationImpl.copy(runningConfig); } private void showSummary(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException { final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest); final LinkedHashMap<String, Object> outputMap = new LinkedHashMap<>(storedConfiguration.toOutputMap(pwmRequest.getLocale())); pwmRequest.setAttribute(PwmRequest.Attribute.ConfigurationSummaryOutput, outputMap); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_EDITOR_SUMMARY); } private void showPermissions(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException { final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest); LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator(storedConfiguration); pwmRequest.setAttribute( PwmRequest.Attribute.ConfigurationSummaryOutput, ldapPermissionCalculator); pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_PERMISSIONS); } public enum Page { manager(PwmConstants.JSP_URL.ADMIN_DASHBOARD, "/manager"), wordlists(PwmConstants.JSP_URL.ADMIN_ANALYSIS, "/wordlists"), ; private final PwmConstants.JSP_URL jspURL; private final String urlSuffix; Page(PwmConstants.JSP_URL jspURL, String urlSuffix) { this.jspURL = jspURL; this.urlSuffix = urlSuffix; } public PwmConstants.JSP_URL getJspURL() { return jspURL; } public String getUrlSuffix() { return urlSuffix; } public static Page forUrl(final PwmURL pwmURL) { final String url = pwmURL.toString(); for (final Page page : Page.values()) { if (url.endsWith(page.urlSuffix)) { return page; } } return null; } } }