001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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.LinkedHashMap; 042import java.util.List; 043import java.util.Map; 044 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.asn1.ASN1Sequence; 048import com.unboundid.ldap.sdk.Control; 049import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.util.Debug; 053import com.unboundid.util.NotMutable; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059import com.unboundid.util.Validator; 060import com.unboundid.util.json.JSONArray; 061import com.unboundid.util.json.JSONField; 062import com.unboundid.util.json.JSONObject; 063import com.unboundid.util.json.JSONString; 064import com.unboundid.util.json.JSONValue; 065 066import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 067 068 069 070/** 071 * This class provides an implementation of the get effective rights request 072 * control, which may be included in a search request to indicate that matching 073 * entries should include information about the rights a given user may have 074 * when interacting with that entry. 075 * <BR> 076 * <BLOCKQUOTE> 077 * <B>NOTE:</B> This class, and other classes within the 078 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 079 * supported for use against Ping Identity, UnboundID, and 080 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 081 * for proprietary functionality or for external specifications that are not 082 * considered stable or mature enough to be guaranteed to work in an 083 * interoperable way with other types of LDAP servers. 084 * </BLOCKQUOTE> 085 * <BR> 086 * When the get effective rights control is included in a search request, then 087 * each entry returned may include information about the rights that the 088 * specified user has for that entry in the {@code aclRights} operational 089 * attribute. Note that because this is an operational attribute, it must be 090 * explicitly included in the set of attributes to return. 091 * <BR><BR> 092 * If the {@code aclRights} attribute is included in the entry, then it will be 093 * present with multiple sets of options. In one case, it will have an option 094 * of "entryLevel", which provides information about the rights that the user 095 * has for the entry in general (see the {@link EntryRight} enum for a list of 096 * the entry-level rights that can be held). In all other cases, it will have 097 * one option of "attributeLevel" and another option that is the name of the 098 * attribute for which the set of rights is granted (see the 099 * {@link AttributeRight} enum for a list of the attribute-level rights that can 100 * be held). In either case, the value will be a comma-delimited list of 101 * right strings, where each right string is the name of the right followed by 102 * a colon and a one to indicate that the right is granted or zero to indicate 103 * that it is not granted. The {@link EffectiveRightsEntry} class provides a 104 * simple means of accessing the information encoded in the values of the 105 * {@code aclRights} attribute. 106 * <BR><BR> 107 * This control was designed by Sun Microsystems, and it is not the same as the 108 * get effective rights control referenced in the draft-ietf-ldapext-acl-model 109 * Internet draft. The value for this control should be encoded as follows: 110 * <BR><BR> 111 * <PRE> 112 * GET_EFFECTIVE_RIGHTS := SEQUENCE { 113 * authzID authzID, 114 * attributes SEQUENCE OF AttributeType OPTIONAL } 115 * </PRE> 116 * <H2>Example</H2> 117 * The following example demonstrates the use of the get effective rights 118 * control to determine whether user "uid=admin,dc=example,dc=com" has the 119 * ability to change the password for the user with uid "john.doe": 120 * <PRE> 121 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com", 122 * SearchScope.SUB, Filter.createEqualityFilter("uid", "john.doe"), 123 * "userPassword", "aclRights"); 124 * searchRequest.addControl(new GetEffectiveRightsRequestControl( 125 * "dn:uid=admin,dc=example,dc=com")); 126 * SearchResult searchResult = connection.search(searchRequest); 127 * 128 * for (SearchResultEntry entry : searchResult.getSearchEntries()) 129 * { 130 * EffectiveRightsEntry effectiveRightsEntry = 131 * new EffectiveRightsEntry(entry); 132 * if (effectiveRightsEntry.rightsInformationAvailable()) 133 * { 134 * if (effectiveRightsEntry.hasAttributeRight(AttributeRight.WRITE, 135 * "userPassword")) 136 * { 137 * // The admin user has permission to change the target user's password. 138 * } 139 * else 140 * { 141 * // The admin user does not have permission to change the target user's 142 * // password. 143 * } 144 * } 145 * else 146 * { 147 * // No effective rights information was returned. 148 * } 149 * } 150 * </PRE> 151 */ 152@NotMutable() 153@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 154public final class GetEffectiveRightsRequestControl 155 extends Control 156{ 157 /** 158 * The OID (1.3.6.1.4.1.42.2.27.9.5.2) for the get effective rights request 159 * control. 160 */ 161 @NotNull public static final String GET_EFFECTIVE_RIGHTS_REQUEST_OID = 162 "1.3.6.1.4.1.42.2.27.9.5.2"; 163 164 165 166 /** 167 * The name of the field used to specify the set of target attributes in the 168 * JSON representation of this control. 169 */ 170 @NotNull private static final String JSON_FIELD_ATTRIBUTES = "attributes"; 171 172 173 174 /** 175 * The name of the field used to specify the authorization ID in the JSON 176 * representation of this control. 177 */ 178 @NotNull private static final String JSON_FIELD_AUTHORIZATION_ID = 179 "authorization-id"; 180 181 182 183 /** 184 * The serial version UID for this serializable class. 185 */ 186 private static final long serialVersionUID = 354733122036206073L; 187 188 189 190 // The authorization ID of the user for which to calculate the effective 191 // rights. 192 @NotNull private final String authzID; 193 194 // The names of the attribute types for which to calculate the effective 195 // rights. 196 @NotNull private final String[] attributes; 197 198 199 200 /** 201 * Creates a new get effective rights request control with the provided 202 * information. It will not be marked critical. 203 * 204 * @param authzID The authorization ID of the user for whom the effective 205 * rights should be calculated. It must not be 206 * {@code null}. 207 * @param attributes The set of attributes for which to calculate the 208 * effective rights. 209 */ 210 public GetEffectiveRightsRequestControl(@NotNull final String authzID, 211 @NotNull final String... attributes) 212 { 213 this(false, authzID, attributes); 214 } 215 216 217 218 /** 219 * Creates a new get effective rights request control with the provided 220 * information. It will not be marked critical. 221 * 222 * @param isCritical Indicates whether this control should be marked 223 * critical. 224 * @param authzID The authorization ID of the user for whom the effective 225 * rights should be calculated. It must not be 226 * {@code null}. 227 * @param attributes The set of attributes for which to calculate the 228 * effective rights. 229 */ 230 public GetEffectiveRightsRequestControl(final boolean isCritical, 231 @NotNull final String authzID, 232 @NotNull final String... attributes) 233 { 234 super(GET_EFFECTIVE_RIGHTS_REQUEST_OID, isCritical, 235 encodeValue(authzID, attributes)); 236 237 this.authzID = authzID; 238 this.attributes = attributes; 239 } 240 241 242 243 /** 244 * Creates a new get effective rights request control which is decoded from 245 * the provided generic control. 246 * 247 * @param control The generic control to be decoded as a get effective 248 * rights request control. 249 * 250 * @throws LDAPException If the provided control cannot be decoded as a get 251 * effective rights request control. 252 */ 253 public GetEffectiveRightsRequestControl(@NotNull final Control control) 254 throws LDAPException 255 { 256 super(control); 257 258 final ASN1OctetString value = control.getValue(); 259 if (value == null) 260 { 261 throw new LDAPException(ResultCode.DECODING_ERROR, 262 ERR_GER_REQUEST_NO_VALUE.get()); 263 } 264 265 final ASN1Element[] elements; 266 try 267 { 268 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 269 elements = ASN1Sequence.decodeAsSequence(valueElement).elements(); 270 } 271 catch (final Exception e) 272 { 273 Debug.debugException(e); 274 throw new LDAPException(ResultCode.DECODING_ERROR, 275 ERR_GER_REQUEST_VALUE_NOT_SEQUENCE.get(e), e); 276 } 277 278 if ((elements.length < 1) || (elements.length > 2)) 279 { 280 throw new LDAPException(ResultCode.DECODING_ERROR, 281 ERR_GER_REQUEST_INVALID_ELEMENT_COUNT.get( 282 elements.length)); 283 } 284 285 authzID = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 286 287 if (elements.length == 2) 288 { 289 try 290 { 291 final ASN1Element[] attrElements = 292 ASN1Sequence.decodeAsSequence(elements[1]).elements(); 293 attributes = new String[attrElements.length]; 294 for (int i=0; i < attrElements.length; i++) 295 { 296 attributes[i] = ASN1OctetString.decodeAsOctetString( 297 attrElements[i]).stringValue(); 298 } 299 } 300 catch (final Exception e) 301 { 302 Debug.debugException(e); 303 throw new LDAPException(ResultCode.DECODING_ERROR, 304 ERR_GER_REQUEST_CANNOT_DECODE.get(e), e); 305 } 306 } 307 else 308 { 309 attributes = StaticUtils.NO_STRINGS; 310 } 311 } 312 313 314 315 /** 316 * Encodes the provided information into an ASN.1 octet string suitable for 317 * use as the value of this control. 318 * 319 * @param authzID The authorization ID of the user for whom the effective 320 * rights should be calculated. It must not be 321 * {@code null}. 322 * @param attributes The set of attributes for which to calculate the 323 * effective rights. 324 * 325 * @return An ASN.1 octet string containing the encoded control value. 326 */ 327 @NotNull() 328 private static ASN1OctetString encodeValue(@NotNull final String authzID, 329 @Nullable final String[] attributes) 330 { 331 Validator.ensureNotNull(authzID); 332 333 final ASN1Element[] elements; 334 if ((attributes == null) || (attributes.length == 0)) 335 { 336 elements = new ASN1Element[] 337 { 338 new ASN1OctetString(authzID), 339 new ASN1Sequence() 340 }; 341 } 342 else 343 { 344 final ASN1Element[] attrElements = new ASN1Element[attributes.length]; 345 for (int i=0; i < attributes.length; i++) 346 { 347 attrElements[i] = new ASN1OctetString(attributes[i]); 348 } 349 350 elements = new ASN1Element[] 351 { 352 new ASN1OctetString(authzID), 353 new ASN1Sequence(attrElements) 354 }; 355 } 356 357 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 358 } 359 360 361 362 /** 363 * Retrieves the authorization ID of the user for whom to calculate the 364 * effective rights. 365 * 366 * @return The authorization ID of the user for whom to calculate the 367 * effective rights. 368 */ 369 @NotNull() 370 public String getAuthzID() 371 { 372 return authzID; 373 } 374 375 376 377 /** 378 * Retrieves the names of the attributes for which to calculate the effective 379 * rights information. 380 * 381 * @return The names of the attributes for which to calculate the effective 382 * rights information, or an empty array if no attribute names were 383 * specified. 384 */ 385 @NotNull() 386 public String[] getAttributes() 387 { 388 return attributes; 389 } 390 391 392 393 /** 394 * {@inheritDoc} 395 */ 396 @Override() 397 @NotNull() 398 public String getControlName() 399 { 400 return INFO_CONTROL_NAME_GET_EFFECTIVE_RIGHTS_REQUEST.get(); 401 } 402 403 404 405 /** 406 * Retrieves a representation of this get effective rights request control as 407 * a JSON object. The JSON object uses the following fields: 408 * <UL> 409 * <LI> 410 * {@code oid} -- A mandatory string field whose value is the object 411 * identifier for this control. For the get effective rights request 412 * control, the OID is "1.3.6.1.4.1.42.2.27.9.5.2". 413 * </LI> 414 * <LI> 415 * {@code control-name} -- An optional string field whose value is a 416 * human-readable name for this control. This field is only intended for 417 * descriptive purposes, and when decoding a control, the {@code oid} 418 * field should be used to identify the type of control. 419 * </LI> 420 * <LI> 421 * {@code criticality} -- A mandatory Boolean field used to indicate 422 * whether this control is considered critical. 423 * </LI> 424 * <LI> 425 * {@code value-base64} -- An optional string field whose value is a 426 * base64-encoded representation of the raw value for this get effective 427 * rights request control. Exactly one of the {@code value-base64} and 428 * {@code value-json} fields must be present. 429 * </LI> 430 * <LI> 431 * {@code value-json} -- An optional JSON object field whose value is a 432 * user-friendly representation of the value for this get effective rights 433 * request control. Exactly one of the {@code value-base64} and 434 * {@code value-json} fields must be present, and if the 435 * {@code value-json} field is used, then it will use the following 436 * fields: 437 * <UL> 438 * <LI> 439 * {@code authorization-id} -- A mandatory string field whose value is 440 * the authorization identity of the user for whom to retrieve the 441 * effective rights. 442 * </LI> 443 * <LI> 444 * {@code attributes} -- An optional array field whose values are 445 * strings that represent the names of the attributes for which to 446 * make the effective rights determination. 447 * </LI> 448 * </UL> 449 * </LI> 450 * </UL> 451 * 452 * @return A JSON object that contains a representation of this control. 453 */ 454 @Override() 455 @NotNull() 456 public JSONObject toJSONControl() 457 { 458 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 459 valueFields.put(JSON_FIELD_AUTHORIZATION_ID, new JSONString(authzID)); 460 461 if (attributes.length > 0) 462 { 463 final List<JSONValue> attributeValues = new ArrayList<>(); 464 for (final String attribute : attributes) 465 { 466 attributeValues.add(new JSONString(attribute)); 467 } 468 469 valueFields.put(JSON_FIELD_ATTRIBUTES, new JSONArray(attributeValues)); 470 } 471 472 return new JSONObject( 473 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 474 GET_EFFECTIVE_RIGHTS_REQUEST_OID), 475 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 476 INFO_CONTROL_NAME_GET_EFFECTIVE_RIGHTS_REQUEST.get()), 477 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 478 isCritical()), 479 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 480 new JSONObject(valueFields))); 481 } 482 483 484 485 /** 486 * Attempts to decode the provided object as a JSON representation of a get 487 * effective rights request control. 488 * 489 * @param controlObject The JSON object to be decoded. It must not be 490 * {@code null}. 491 * @param strict Indicates whether to use strict mode when decoding 492 * the provided JSON object. If this is {@code true}, 493 * then this method will throw an exception if the 494 * provided JSON object contains any unrecognized 495 * fields. If this is {@code false}, then unrecognized 496 * fields will be ignored. 497 * 498 * @return The get effective rights request control that was decoded from 499 * the provided JSON object. 500 * 501 * @throws LDAPException If the provided JSON object cannot be parsed as a 502 * valid get effective rights request control. 503 */ 504 @NotNull() 505 public static GetEffectiveRightsRequestControl decodeJSONControl( 506 @NotNull final JSONObject controlObject, 507 final boolean strict) 508 throws LDAPException 509 { 510 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 511 controlObject, strict, true, true); 512 513 final ASN1OctetString rawValue = jsonControl.getRawValue(); 514 if (rawValue != null) 515 { 516 return new GetEffectiveRightsRequestControl(new Control( 517 jsonControl.getOID(), jsonControl.getCriticality(), rawValue)); 518 } 519 520 521 final JSONObject valueObject = jsonControl.getValueObject(); 522 523 final String authorizationID = 524 valueObject.getFieldAsString(JSON_FIELD_AUTHORIZATION_ID); 525 if (authorizationID == null) 526 { 527 throw new LDAPException(ResultCode.DECODING_ERROR, 528 ERR_GER_REQUEST_JSON_MISSING_AUTHZ_ID.get( 529 controlObject.toSingleLineString(), 530 JSON_FIELD_AUTHORIZATION_ID)); 531 } 532 533 final String[] attributes; 534 final List<JSONValue> attrValues = 535 valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTES); 536 if (attrValues == null) 537 { 538 attributes = StaticUtils.NO_STRINGS; 539 } 540 else 541 { 542 attributes = new String[attrValues.size()]; 543 for (int i=0; i < attributes.length; i++) 544 { 545 final JSONValue v = attrValues.get(i); 546 if (v instanceof JSONString) 547 { 548 attributes[i] = ((JSONString) v).stringValue(); 549 } 550 else 551 { 552 throw new LDAPException(ResultCode.DECODING_ERROR, 553 ERR_GER_REQUEST_JSON_ATTR_NOT_STRING.get( 554 controlObject.toSingleLineString(), 555 JSON_FIELD_ATTRIBUTES)); 556 } 557 } 558 } 559 560 561 if (strict) 562 { 563 final List<String> unrecognizedFields = 564 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 565 valueObject, JSON_FIELD_AUTHORIZATION_ID, 566 JSON_FIELD_ATTRIBUTES); 567 if (! unrecognizedFields.isEmpty()) 568 { 569 throw new LDAPException(ResultCode.DECODING_ERROR, 570 ERR_GER_REQUEST_JSON_CONTROL_UNRECOGNIZED_FIELD.get( 571 controlObject.toSingleLineString(), 572 unrecognizedFields.get(0))); 573 } 574 } 575 576 577 return new GetEffectiveRightsRequestControl(jsonControl.getCriticality(), 578 authorizationID, attributes); 579 } 580 581 582 583 /** 584 * {@inheritDoc} 585 */ 586 @Override() 587 public void toString(@NotNull final StringBuilder buffer) 588 { 589 buffer.append("GetEffectiveRightsRequestControl(authzId='"); 590 buffer.append(authzID); 591 buffer.append('\''); 592 593 if (attributes.length > 0) 594 { 595 buffer.append(", attributes={"); 596 for (int i=0; i < attributes.length; i++) 597 { 598 if (i > 0) 599 { 600 buffer.append(", "); 601 } 602 603 buffer.append(attributes[i]); 604 } 605 buffer.append('}'); 606 } 607 608 buffer.append(", isCritical="); 609 buffer.append(isCritical()); 610 buffer.append(')'); 611 } 612}