Example #1
0
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);
    }
  }
}
Example #2
0
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);
    }
  }
}
Example #3
0
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);
  }
}
Example #4
0
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)));
  }
}
Example #5
0
/** @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() {}
}
Example #6
0
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, "=", ";");
  }
}
Example #7
0
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);
    }
  }
}
Example #8
0
/**
 * 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);
    }
  }
}
Example #9
0
/** @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));
  }
}
Example #11
0
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());
  }
}
Example #13
0
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);
  }
}
Example #14
0
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();
    }
  }
}
Example #15
0
@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();
  }
}
Example #16
0
/** @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();
  }
}
Example #17
0
/**
 * 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);
    }
  }
}
Example #18
0
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;
    }
  }
}
Example #19
0
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());
    }
  }
}
Example #20
0
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));
    }
  }
}
Example #21
0
/**
 * 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);
  }
}
Example #22
0
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);
  }
}
Example #24
0
/**
 * 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);
    }
  }
}
Example #25
0
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;
    }
  }
}
Example #26
0
@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;
    }
  }
}