001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2020-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.controls;
037
038
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.Collection;
043import java.util.Collections;
044import java.util.List;
045import java.util.SortedSet;
046import java.util.TreeSet;
047
048import com.unboundid.ldap.sdk.LDAPException;
049import com.unboundid.ldap.sdk.ResultCode;
050import com.unboundid.util.Debug;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.Nullable;
054import com.unboundid.util.StaticUtils;
055import com.unboundid.util.ThreadSafety;
056import com.unboundid.util.ThreadSafetyLevel;
057import com.unboundid.util.Validator;
058import com.unboundid.util.json.JSONArray;
059import com.unboundid.util.json.JSONField;
060import com.unboundid.util.json.JSONObject;
061import com.unboundid.util.json.JSONValue;
062
063import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
064
065
066
067/**
068 * This class provides a data structure with information about recent successful
069 * and failed login attempts for a user.
070 * <BR>
071 * <BLOCKQUOTE>
072 *   <B>NOTE:</B>  This class, and other classes within the
073 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
074 *   supported for use against Ping Identity, UnboundID, and
075 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
076 *   for proprietary functionality or for external specifications that are not
077 *   considered stable or mature enough to be guaranteed to work in an
078 *   interoperable way with other types of LDAP servers.
079 * </BLOCKQUOTE>
080 *
081 * @see  GetRecentLoginHistoryRequestControl
082 * @see  GetRecentLoginHistoryResponseControl
083 */
084@NotMutable()
085@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
086public final class RecentLoginHistory
087       implements Serializable
088{
089  /**
090   * The name of the JSON field used to hold the set of failed attempts.
091   */
092  @NotNull private static final String JSON_FIELD_FAILED_ATTEMPTS =
093       "failed-attempts";
094
095
096
097  /**
098   * The name of the JSON field used to hold the set of successful attempts.
099   */
100  @NotNull private static final String JSON_FIELD_SUCCESSFUL_ATTEMPTS =
101       "successful-attempts";
102
103
104
105  /**
106   * The serial version UID for this serializable class.
107   */
108  private static final long serialVersionUID = -5692706886656940486L;
109
110
111
112  // The JSON object providing an encoded representation of the recent login
113  // history.
114  @NotNull private final JSONObject jsonObject;
115
116  // A set of the recent failed authentication attempts.
117  @NotNull private final SortedSet<RecentLoginHistoryAttempt> failedAttempts;
118
119  // A set of the recent successful authentication attempts.
120  @NotNull private final SortedSet<RecentLoginHistoryAttempt>
121       successfulAttempts;
122
123
124
125  /**
126   * Creates a new recent login history with the provided sets of successful and
127   * failed attempts.
128   *
129   * @param  successfulAttempts  A list of recent successful authentication
130   *                             attempts.  It may be {@code null} or empty if
131   *                             there were no recent successful attempts.
132   * @param  failedAttempts      A list of recent failed authentication
133   *                             attempts.  It may be {@code null} or empty if
134   *                             there were no recent failed attempts.
135   */
136  public RecentLoginHistory(
137       @Nullable final Collection<RecentLoginHistoryAttempt> successfulAttempts,
138       @Nullable final Collection<RecentLoginHistoryAttempt> failedAttempts)
139  {
140    final List<JSONValue> successValues = new ArrayList<>();
141    final SortedSet<RecentLoginHistoryAttempt> successes = new TreeSet<>();
142    if (successfulAttempts != null)
143    {
144      for (final RecentLoginHistoryAttempt a : successfulAttempts)
145      {
146        successes.add(a);
147        successValues.add(a.asJSONObject());
148      }
149    }
150    this.successfulAttempts = Collections.unmodifiableSortedSet(successes);
151
152
153    final List<JSONValue> failureValues = new ArrayList<>();
154    final SortedSet<RecentLoginHistoryAttempt> failures = new TreeSet<>();
155    if (failedAttempts != null)
156    {
157      for (final RecentLoginHistoryAttempt a : failedAttempts)
158      {
159        failures.add(a);
160        failureValues.add(a.asJSONObject());
161      }
162    }
163    this.failedAttempts = Collections.unmodifiableSortedSet(failures);
164
165
166    jsonObject = new JSONObject(
167         new JSONField(JSON_FIELD_SUCCESSFUL_ATTEMPTS,
168              new JSONArray(successValues)),
169         new JSONField(JSON_FIELD_FAILED_ATTEMPTS,
170              new JSONArray(failureValues)));
171  }
172
173
174
175  /**
176   * Creates a new recent login history that is decoded from the provided JSON
177   * object.
178   *
179   * @param  jsonObject  A JSON object containing an encoded representation of
180   *                     the recent login history.  It must not be
181   * {@code null}.
182   *
183   * @throws  LDAPException  If a problem occurs while attempting to decode the
184   *                         provided JSON object as a recent login history.
185   */
186  public RecentLoginHistory(@NotNull final JSONObject jsonObject)
187         throws LDAPException
188  {
189    Validator.ensureNotNull(jsonObject,
190         "RecentLoginHistory.<init>.jsonObject must not be null.");
191
192    this.jsonObject = jsonObject;
193
194    final SortedSet<RecentLoginHistoryAttempt> successes = new TreeSet<>();
195    final List<JSONValue> successValues =
196         jsonObject.getFieldAsArray(JSON_FIELD_SUCCESSFUL_ATTEMPTS);
197    if (successValues != null)
198    {
199      for (final JSONValue v : successValues)
200      {
201        try
202        {
203          successes.add(new RecentLoginHistoryAttempt((JSONObject) v));
204        }
205        catch (final LDAPException e)
206        {
207          Debug.debugException(e);
208          throw new LDAPException(ResultCode.DECODING_ERROR,
209               ERR_RECENT_LOGIN_HISTORY_CANNOT_PARSE_SUCCESS.get(
210                    jsonObject.toSingleLineString(), e.getMessage()),
211               e);
212        }
213        catch (final Exception e)
214        {
215          Debug.debugException(e);
216          throw new LDAPException(ResultCode.DECODING_ERROR,
217               ERR_RECENT_LOGIN_HISTORY_CANNOT_PARSE_SUCCESS.get(
218                    jsonObject.toSingleLineString(),
219                    StaticUtils.getExceptionMessage(e)),
220               e);
221        }
222      }
223    }
224
225    final SortedSet<RecentLoginHistoryAttempt> failures = new TreeSet<>();
226    final List<JSONValue> failureValues =
227         jsonObject.getFieldAsArray(JSON_FIELD_FAILED_ATTEMPTS);
228    if (failureValues != null)
229    {
230      for (final JSONValue v : failureValues)
231      {
232        try
233        {
234          failures.add(new RecentLoginHistoryAttempt((JSONObject) v));
235        }
236        catch (final LDAPException e)
237        {
238          Debug.debugException(e);
239          throw new LDAPException(ResultCode.DECODING_ERROR,
240               ERR_RECENT_LOGIN_HISTORY_CANNOT_PARSE_FAILURE.get(
241                    jsonObject.toSingleLineString(), e.getMessage()),
242               e);
243        }
244        catch (final Exception e)
245        {
246          Debug.debugException(e);
247          throw new LDAPException(ResultCode.DECODING_ERROR,
248               ERR_RECENT_LOGIN_HISTORY_CANNOT_PARSE_FAILURE.get(
249                    jsonObject.toSingleLineString(),
250                    StaticUtils.getExceptionMessage(e)),
251               e);
252        }
253      }
254    }
255
256    successfulAttempts = Collections.unmodifiableSortedSet(successes);
257    failedAttempts = Collections.unmodifiableSortedSet(failures);
258  }
259
260
261
262  /**
263   * Retrieves the set of recent successful login attempts.
264   *
265   * @return  The set of recent successful login attempts.
266   */
267  @NotNull()
268  public SortedSet<RecentLoginHistoryAttempt> getSuccessfulAttempts()
269  {
270    return successfulAttempts;
271  }
272
273
274
275  /**
276   * Retrieves the set of recent failed login attempts.
277   *
278   * @return  The set of recent failed login attempts.
279   */
280  @NotNull()
281  public SortedSet<RecentLoginHistoryAttempt> getFailedAttempts()
282  {
283    return failedAttempts;
284  }
285
286
287
288  /**
289   * Retrieves a JSON object with an encoded representation of this recent
290   * login history.
291   *
292   * @return  A JSON object with an encoded representation of this recent long
293   *          history.
294   */
295  @NotNull()
296  public JSONObject asJSONObject()
297  {
298    return jsonObject;
299  }
300
301
302
303  /**
304   * Retrieves a string representation of this recent login history.
305   *
306   * @return  A string representation of this recent login history.
307   */
308  @Override()
309  @NotNull()
310  public String toString()
311  {
312    return jsonObject.toSingleLineString();
313  }
314}