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}