001/* 002 * Copyright 2012-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-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) 2012-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.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.EnumSet; 045import java.util.Iterator; 046import java.util.List; 047import java.util.Set; 048 049import com.unboundid.asn1.ASN1Element; 050import com.unboundid.asn1.ASN1Enumerated; 051import com.unboundid.asn1.ASN1OctetString; 052import com.unboundid.asn1.ASN1Sequence; 053import com.unboundid.ldap.sdk.Control; 054import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 055import com.unboundid.ldap.sdk.LDAPException; 056import com.unboundid.ldap.sdk.ResultCode; 057import com.unboundid.util.Debug; 058import com.unboundid.util.NotMutable; 059import com.unboundid.util.NotNull; 060import com.unboundid.util.StaticUtils; 061import com.unboundid.util.ThreadSafety; 062import com.unboundid.util.ThreadSafetyLevel; 063import com.unboundid.util.Validator; 064import com.unboundid.util.json.JSONArray; 065import com.unboundid.util.json.JSONField; 066import com.unboundid.util.json.JSONObject; 067import com.unboundid.util.json.JSONString; 068import com.unboundid.util.json.JSONValue; 069 070import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 071 072 073 074/** 075 * This class provides an implementation of a control that can be used to 076 * indicate that the server should suppress the update to one or more 077 * operational attributes for the associated request. 078 * <BR> 079 * <BLOCKQUOTE> 080 * <B>NOTE:</B> This class, and other classes within the 081 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 082 * supported for use against Ping Identity, UnboundID, and 083 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 084 * for proprietary functionality or for external specifications that are not 085 * considered stable or mature enough to be guaranteed to work in an 086 * interoperable way with other types of LDAP servers. 087 * </BLOCKQUOTE> 088 * <BR> 089 * The request control has an OID of 1.3.6.1.4.1.30221.2.5.27, and the 090 * criticality may be either {@code true} or {@code false}. The control must 091 * have a value with the following encoding: 092 * <PRE> 093 * SuppressOperationalAttributeUpdateRequestValue ::= SEQUENCE { 094 * suppressTypes [0] SEQUENCE OF ENUMERATED { 095 * last-access-time (0), 096 * last-login-time (1), 097 * last-login-ip (2), 098 * lastmod (3), 099 * ... }, 100 * ... } 101 * </PRE> 102 */ 103@NotMutable() 104@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 105public final class SuppressOperationalAttributeUpdateRequestControl 106 extends Control 107{ 108 /** 109 * The OID (1.3.6.1.4.1.30221.2.5.27) for the suppress operational attribute 110 * update request control. 111 */ 112 @NotNull public static final String SUPPRESS_OP_ATTR_UPDATE_REQUEST_OID = 113 "1.3.6.1.4.1.30221.2.5.27"; 114 115 116 117 /** 118 * The BER type to use for the set of suppress types. 119 */ 120 private static final byte TYPE_SUPPRESS_TYPES = (byte) 0x80; 121 122 123 124 /** 125 * The name of the field used to hold the suppress types in the JSON 126 * representation of this control. 127 */ 128 @NotNull private static final String JSON_FIELD_SUPPRESS_TYPES = 129 "suppress-types"; 130 131 132 133 /** 134 * The last-access-time suppress type value to use in the JSON representation 135 * of this control. 136 */ 137 @NotNull private static final String JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME = 138 "last-access-time"; 139 140 141 142 /** 143 * The last-login-ip-address suppress type value to use in the JSON 144 * representation of this control. 145 */ 146 @NotNull private static final String 147 JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS = "last-login-ip-address"; 148 149 150 151 /** 152 * The last-login-time suppress type value to use in the JSON representation 153 * of this control. 154 */ 155 @NotNull private static final String JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME = 156 "last-login-time"; 157 158 159 160 /** 161 * The lastmod suppress type value to use in the JSON representation of this 162 * control. 163 */ 164 @NotNull private static final String JSON_SUPPRESS_TYPE_LASTMOD = "lastmod"; 165 166 167 /** 168 * The serial version UID for this serializable class. 169 */ 170 private static final long serialVersionUID = 4603958484615351672L; 171 172 173 174 // The set of suppress types to include in the control. 175 @NotNull private final Set<SuppressType> suppressTypes; 176 177 178 179 /** 180 * Creates a new instance of this control that will suppress updates to the 181 * specified kinds of operational attributes. It will not be critical. 182 * 183 * @param suppressTypes The set of suppress types to include in the control. 184 * It must not be {@code null} or empty. 185 */ 186 public SuppressOperationalAttributeUpdateRequestControl( 187 @NotNull final SuppressType... suppressTypes) 188 { 189 this(false, suppressTypes); 190 } 191 192 193 194 /** 195 * Creates a new instance of this control that will suppress updates to the 196 * specified kinds of operational attributes. It will not be critical. 197 * 198 * @param suppressTypes The set of suppress types to include in the control. 199 * It must not be {@code null} or empty. 200 */ 201 public SuppressOperationalAttributeUpdateRequestControl( 202 @NotNull final Collection<SuppressType> suppressTypes) 203 { 204 this(false, suppressTypes); 205 } 206 207 208 209 /** 210 * Creates a new instance of this control that will suppress updates to the 211 * specified kinds of operational attributes. 212 * 213 * @param isCritical Indicates whether the control should be considered 214 * critical. 215 * @param suppressTypes The set of suppress types to include in the control. 216 * It must not be {@code null} or empty. 217 */ 218 public SuppressOperationalAttributeUpdateRequestControl( 219 final boolean isCritical, 220 @NotNull final SuppressType... suppressTypes) 221 { 222 this(isCritical, Arrays.asList(suppressTypes)); 223 } 224 225 226 227 /** 228 * Creates a new instance of this control that will suppress updates to the 229 * specified kinds of operational attributes. 230 * 231 * @param isCritical Indicates whether the control should be considered 232 * critical. 233 * @param suppressTypes The set of suppress types to include in the control. 234 * It must not be {@code null} or empty. 235 */ 236 public SuppressOperationalAttributeUpdateRequestControl( 237 final boolean isCritical, 238 @NotNull final Collection<SuppressType> suppressTypes) 239 { 240 super(SUPPRESS_OP_ATTR_UPDATE_REQUEST_OID, isCritical, 241 encodeValue(suppressTypes)); 242 243 Validator.ensureFalse(suppressTypes.isEmpty()); 244 245 final EnumSet<SuppressType> s = EnumSet.noneOf(SuppressType.class); 246 for (final SuppressType t : suppressTypes) 247 { 248 s.add(t); 249 } 250 251 this.suppressTypes = Collections.unmodifiableSet(s); 252 } 253 254 255 256 /** 257 * Decodes the provided generic control as a suppress operational attribute 258 * update request control. 259 * 260 * @param control The generic control to be decoded as a suppress 261 * operational attribute update request control. 262 * 263 * @throws LDAPException If a problem is encountered while attempting to 264 * decode the provided control. 265 */ 266 public SuppressOperationalAttributeUpdateRequestControl( 267 @NotNull final Control control) 268 throws LDAPException 269 { 270 super(control); 271 272 final ASN1OctetString value = control.getValue(); 273 if (value == null) 274 { 275 throw new LDAPException(ResultCode.DECODING_ERROR, 276 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_MISSING_VALUE.get()); 277 } 278 279 try 280 { 281 final ASN1Sequence valueSequence = 282 ASN1Sequence.decodeAsSequence(value.getValue()); 283 final ASN1Sequence suppressTypesSequence = 284 ASN1Sequence.decodeAsSequence(valueSequence.elements()[0]); 285 286 final EnumSet<SuppressType> s = EnumSet.noneOf(SuppressType.class); 287 for (final ASN1Element e : suppressTypesSequence.elements()) 288 { 289 final ASN1Enumerated ae = ASN1Enumerated.decodeAsEnumerated(e); 290 final SuppressType t = SuppressType.valueOf(ae.intValue()); 291 if (t == null) 292 { 293 throw new LDAPException(ResultCode.DECODING_ERROR, 294 ERR_SUPPRESS_OP_ATTR_UNRECOGNIZED_SUPPRESS_TYPE.get( 295 ae.intValue())); 296 } 297 else 298 { 299 s.add(t); 300 } 301 } 302 303 suppressTypes = Collections.unmodifiableSet(s); 304 } 305 catch (final LDAPException le) 306 { 307 Debug.debugException(le); 308 throw le; 309 } 310 catch (final Exception e) 311 { 312 Debug.debugException(e); 313 throw new LDAPException(ResultCode.DECODING_ERROR, 314 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_CANNOT_DECODE.get( 315 StaticUtils.getExceptionMessage(e)), 316 e); 317 } 318 } 319 320 321 322 /** 323 * Encodes the provided information into an octet string suitable for use as 324 * the value of this control. 325 * 326 * @param suppressTypes The set of suppress types to include in the control. 327 * It must not be {@code null} or empty. 328 * 329 * @return The ASN.1 octet string containing the encoded value. 330 */ 331 @NotNull() 332 private static ASN1OctetString encodeValue( 333 @NotNull final Collection<SuppressType> suppressTypes) 334 { 335 final ArrayList<ASN1Element> suppressTypeElements = 336 new ArrayList<>(suppressTypes.size()); 337 for (final SuppressType t : suppressTypes) 338 { 339 suppressTypeElements.add(new ASN1Enumerated(t.intValue())); 340 } 341 342 final ASN1Sequence valueSequence = new ASN1Sequence( 343 new ASN1Sequence(TYPE_SUPPRESS_TYPES, suppressTypeElements)); 344 return new ASN1OctetString(valueSequence.encode()); 345 } 346 347 348 349 /** 350 * Retrieves the set of suppress types for this control. 351 * 352 * @return The set of suppress types for this control. 353 */ 354 @NotNull() 355 public Set<SuppressType> getSuppressTypes() 356 { 357 return suppressTypes; 358 } 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override() 366 @NotNull() 367 public String getControlName() 368 { 369 return INFO_CONTROL_NAME_SUPPRESS_OP_ATTR_UPDATE_REQUEST.get(); 370 } 371 372 373 374 /** 375 * Retrieves a representation of this suppress operational attribute update 376 * request control as a JSON object. The JSON object uses the following 377 * fields: 378 * <UL> 379 * <LI> 380 * {@code oid} -- A mandatory string field whose value is the object 381 * identifier for this control. For the suppress operational attribute 382 * update request control, the OID is "1.3.6.1.4.1.30221.2.5.27". 383 * </LI> 384 * <LI> 385 * {@code control-name} -- An optional string field whose value is a 386 * human-readable name for this control. This field is only intended for 387 * descriptive purposes, and when decoding a control, the {@code oid} 388 * field should be used to identify the type of control. 389 * </LI> 390 * <LI> 391 * {@code criticality} -- A mandatory Boolean field used to indicate 392 * whether this control is considered critical. 393 * </LI> 394 * <LI> 395 * {@code value-base64} -- An optional string field whose value is a 396 * base64-encoded representation of the raw value for this suppress 397 * operational attribute update request control. Exactly one of the 398 * {@code value-base64} and {@code value-json} fields must be present. 399 * </LI> 400 * <LI> 401 * {@code value-json} -- An optional JSON object field whose value is a 402 * user-friendly representation of the value for this suppress operational 403 * attribute update control. Exactly one of the {@code value-base64} and 404 * {@code value-json} fields must be present, and if the 405 * {@code value-json} field is used, then it will use the following 406 * fields: 407 * <UL> 408 * <LI> 409 * {@code suppress-types} -- A mandatory array field whose values are 410 * the names of the types of updates that should be suppressed. 411 * Allowed values include "{@code last-access-time}", 412 * "{@code last-login-time}", "{@code last-login-ip-address}", 413 * and "{@code lastmod}". 414 * </LI> 415 * </UL> 416 * </LI> 417 * </UL> 418 * 419 * @return A JSON object that contains a representation of this control. 420 */ 421 @Override() 422 @NotNull() 423 public JSONObject toJSONControl() 424 { 425 final List<JSONValue> suppressTypeValues = 426 new ArrayList<>(suppressTypes.size()); 427 for (final SuppressType suppressType : suppressTypes) 428 { 429 switch (suppressType) 430 { 431 case LAST_ACCESS_TIME: 432 suppressTypeValues.add(new JSONString( 433 JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME)); 434 break; 435 case LAST_LOGIN_TIME: 436 suppressTypeValues.add(new JSONString( 437 JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME)); 438 break; 439 case LAST_LOGIN_IP: 440 suppressTypeValues.add(new JSONString( 441 JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS)); 442 break; 443 case LASTMOD: 444 suppressTypeValues.add(new JSONString(JSON_SUPPRESS_TYPE_LASTMOD)); 445 break; 446 } 447 } 448 449 return new JSONObject( 450 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 451 SUPPRESS_OP_ATTR_UPDATE_REQUEST_OID), 452 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 453 INFO_CONTROL_NAME_SUPPRESS_OP_ATTR_UPDATE_REQUEST.get()), 454 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 455 isCritical()), 456 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 457 new JSONObject( 458 new JSONField(JSON_FIELD_SUPPRESS_TYPES, 459 new JSONArray(suppressTypeValues))))); 460 } 461 462 463 464 /** 465 * Attempts to decode the provided object as a JSON representation of a 466 * suppress operational attribute update request control. 467 * 468 * @param controlObject The JSON object to be decoded. It must not be 469 * {@code null}. 470 * @param strict Indicates whether to use strict mode when decoding 471 * the provided JSON object. If this is {@code true}, 472 * then this method will throw an exception if the 473 * provided JSON object contains any unrecognized 474 * fields. If this is {@code false}, then unrecognized 475 * fields will be ignored. 476 * 477 * @return The suppress operational attribute update request control that was 478 * decoded from the provided JSON object. 479 * 480 * @throws LDAPException If the provided JSON object cannot be parsed as a 481 * valid suppress operational attribute update request 482 * control. 483 */ 484 @NotNull() 485 public static SuppressOperationalAttributeUpdateRequestControl 486 decodeJSONControl(@NotNull final JSONObject controlObject, 487 final boolean strict) 488 throws LDAPException 489 { 490 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 491 controlObject, strict, true, true); 492 493 final ASN1OctetString rawvalue = jsonControl.getRawValue(); 494 if (rawvalue != null) 495 { 496 return new SuppressOperationalAttributeUpdateRequestControl(new Control( 497 jsonControl.getOID(), jsonControl.getCriticality(), rawvalue)); 498 } 499 500 501 final JSONObject valueObject = jsonControl.getValueObject(); 502 503 final List<JSONValue> suppressTypeValues = 504 valueObject.getFieldAsArray(JSON_FIELD_SUPPRESS_TYPES); 505 if (suppressTypeValues == null) 506 { 507 throw new LDAPException(ResultCode.DECODING_ERROR, 508 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_MISSING_SUPPRESS_TYPES.get( 509 controlObject.toSingleLineString(), 510 JSON_FIELD_SUPPRESS_TYPES)); 511 } 512 513 if (suppressTypeValues.isEmpty()) 514 { 515 throw new LDAPException(ResultCode.DECODING_ERROR, 516 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_EMPTY_SUPPRESS_TYPES.get( 517 controlObject.toSingleLineString(), 518 JSON_FIELD_SUPPRESS_TYPES)); 519 } 520 521 final Set<SuppressType> suppressTypes = EnumSet.noneOf(SuppressType.class); 522 for (final JSONValue suppressTypeValue : suppressTypeValues) 523 { 524 if (suppressTypeValue instanceof JSONString) 525 { 526 final String suppressTypeString = 527 ((JSONString) suppressTypeValue).stringValue(); 528 switch (suppressTypeString) 529 { 530 case JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME: 531 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 532 break; 533 case JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME: 534 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 535 break; 536 case JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS: 537 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 538 break; 539 case JSON_SUPPRESS_TYPE_LASTMOD: 540 suppressTypes.add(SuppressType.LASTMOD); 541 break; 542 default: 543 throw new LDAPException(ResultCode.DECODING_ERROR, 544 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_UNKNOWN_SUPPRESS_TYPE. 545 get(controlObject.toSingleLineString(), 546 JSON_FIELD_SUPPRESS_TYPES, 547 JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME, 548 JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME, 549 JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS, 550 JSON_SUPPRESS_TYPE_LASTMOD)); 551 } 552 } 553 else 554 { 555 throw new LDAPException(ResultCode.DECODING_ERROR, 556 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_SUPPRESS_TYPE_NOT_STRING. 557 get(controlObject.toSingleLineString(), 558 JSON_FIELD_SUPPRESS_TYPES)); 559 } 560 } 561 562 563 if (strict) 564 { 565 final List<String> unrecognizedFields = 566 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 567 valueObject, JSON_FIELD_SUPPRESS_TYPES); 568 if (! unrecognizedFields.isEmpty()) 569 { 570 throw new LDAPException(ResultCode.DECODING_ERROR, 571 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_UNRECOGNIZED_FIELD.get( 572 controlObject.toSingleLineString(), 573 unrecognizedFields.get(0))); 574 } 575 } 576 577 578 return new SuppressOperationalAttributeUpdateRequestControl( 579 jsonControl.getCriticality(), suppressTypes); 580 } 581 582 583 584 /** 585 * {@inheritDoc} 586 */ 587 @Override() 588 public void toString(@NotNull final StringBuilder buffer) 589 { 590 buffer.append("SuppressOperationalAttributeUpdateRequestControl(" + 591 "isCritical="); 592 buffer.append(isCritical()); 593 buffer.append(", suppressTypes={"); 594 595 final Iterator<SuppressType> iterator = suppressTypes.iterator(); 596 while (iterator.hasNext()) 597 { 598 buffer.append(iterator.next().name()); 599 if (iterator.hasNext()) 600 { 601 buffer.append(','); 602 } 603 } 604 605 buffer.append("})"); 606 } 607}