001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2010 Sun Microsystems, Inc. 025 * Portions copyright 2012-2015 ForgeRock AS. 026 */ 027package org.forgerock.opendj.ldap.controls; 028 029import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; 030import static com.forgerock.opendj.ldap.CoreMessages.*; 031 032import java.io.IOException; 033import java.util.ArrayList; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.LinkedList; 037import java.util.List; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.forgerock.i18n.LocalizedIllegalArgumentException; 041import org.forgerock.opendj.io.ASN1; 042import org.forgerock.opendj.io.ASN1Reader; 043import org.forgerock.opendj.io.ASN1Writer; 044import org.forgerock.opendj.ldap.ByteString; 045import org.forgerock.opendj.ldap.ByteStringBuilder; 046import org.forgerock.opendj.ldap.DN; 047import org.forgerock.opendj.ldap.DecodeException; 048import org.forgerock.opendj.ldap.DecodeOptions; 049import org.forgerock.opendj.ldap.schema.AttributeType; 050import org.forgerock.opendj.ldap.schema.Schema; 051import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException; 052 053import org.forgerock.util.Reject; 054 055/** 056 * A partial implementation of the get effective rights request control as 057 * defined in draft-ietf-ldapext-acl-model. The main differences are: 058 * <ul> 059 * <li>The response control is not supported. Instead the OpenDJ implementation 060 * creates attributes containing effective rights information with the entry 061 * being returned. 062 * <li>The attribute type names are dynamically created. 063 * <li>The set of attributes for which effective rights information is to be 064 * requested can be included in the control. 065 * </ul> 066 * The get effective rights request control value has the following BER 067 * encoding: 068 * 069 * <pre> 070 * GetRightsControl ::= SEQUENCE { 071 * authzId authzId -- Only the "dn:DN" form is supported. 072 * attributes SEQUENCE OF AttributeType 073 * } 074 * </pre> 075 * 076 * You can use the control to retrieve effective rights during a search: 077 * 078 * <pre> 079 * String authDN = ...; 080 * 081 * SearchRequest request = 082 * Requests.newSearchRequest( 083 * "dc=example,dc=com", SearchScope.WHOLE_SUBTREE, 084 * "(uid=bjensen)", "cn", "aclRights", "aclRightsInfo") 085 * .addControl(GetEffectiveRightsRequestControl.newControl( 086 * true, authDN, "cn")); 087 * 088 * ConnectionEntryReader reader = connection.search(request); 089 * while (reader.hasNext()) { 090 * if (!reader.isReference()) { 091 * SearchResultEntry entry = reader.readEntry(); 092 * // Interpret aclRights and aclRightsInfo 093 * } 094 * } 095 * </pre> 096 * 097 * The entries returned by the search hold the {@code aclRights} and 098 * {@code aclRightsInfo} attributes with the effective rights information. You 099 * must parse the attribute options and values to interpret the information. 100 * 101 * @see <a 102 * href="http://tools.ietf.org/html/draft-ietf-ldapext-acl-model">draft-ietf-ldapext-acl-model 103 * - Access Control Model for LDAPv3 </a> 104 **/ 105public final class GetEffectiveRightsRequestControl implements Control { 106 /** 107 * The OID for the get effective rights request control. 108 */ 109 public static final String OID = "1.3.6.1.4.1.42.2.27.9.5.2"; 110 111 /** 112 * A decoder which can be used for decoding the get effective rights request 113 * control. 114 */ 115 public static final ControlDecoder<GetEffectiveRightsRequestControl> DECODER = 116 new ControlDecoder<GetEffectiveRightsRequestControl>() { 117 118 public GetEffectiveRightsRequestControl decodeControl(final Control control, 119 final DecodeOptions options) throws DecodeException { 120 Reject.ifNull(control); 121 122 if (control instanceof GetEffectiveRightsRequestControl) { 123 return (GetEffectiveRightsRequestControl) control; 124 } 125 126 if (!control.getOID().equals(OID)) { 127 final LocalizableMessage message = 128 ERR_GETEFFECTIVERIGHTS_CONTROL_BAD_OID.get(control.getOID(), OID); 129 throw DecodeException.error(message); 130 } 131 132 DN authorizationDN = null; 133 List<AttributeType> attributes = Collections.emptyList(); 134 135 if (control.hasValue()) { 136 final ASN1Reader reader = ASN1.getReader(control.getValue()); 137 try { 138 reader.readStartSequence(); 139 final String authzIDString = reader.readOctetStringAsString(); 140 final String lowerAuthzIDString = authzIDString.toLowerCase(); 141 Schema schema; 142 143 // Make sure authzId starts with "dn:" and is a 144 // valid DN. 145 if (lowerAuthzIDString.startsWith("dn:")) { 146 final String authorizationDNString = authzIDString.substring(3); 147 schema = 148 options.getSchemaResolver().resolveSchema( 149 authorizationDNString); 150 try { 151 authorizationDN = DN.valueOf(authorizationDNString, schema); 152 } catch (final LocalizedIllegalArgumentException e) { 153 final LocalizableMessage message = 154 ERR_GETEFFECTIVERIGHTS_INVALID_AUTHZIDDN 155 .get(getExceptionMessage(e)); 156 throw DecodeException.error(message, e); 157 } 158 } else { 159 final LocalizableMessage message = 160 INFO_GETEFFECTIVERIGHTS_INVALID_AUTHZID 161 .get(lowerAuthzIDString); 162 throw DecodeException.error(message); 163 } 164 165 // There is an sequence containing an attribute list, try to decode it. 166 if (reader.hasNextElement()) { 167 attributes = new LinkedList<>(); 168 reader.readStartSequence(); 169 while (reader.hasNextElement()) { 170 // Decode as an attribute type. 171 final String attributeName = reader.readOctetStringAsString(); 172 AttributeType attributeType; 173 try { 174 // FIXME: we're using the schema 175 // associated with the authzid 176 // which is not really correct. We 177 // should really use the schema 178 // associated with the entry. 179 attributeType = schema.getAttributeType(attributeName); 180 } catch (final UnknownSchemaElementException e) { 181 final LocalizableMessage message = 182 ERR_GETEFFECTIVERIGHTS_UNKNOWN_ATTRIBUTE 183 .get(attributeName); 184 throw DecodeException.error(message, e); 185 } 186 attributes.add(attributeType); 187 } 188 reader.readEndSequence(); 189 attributes = Collections.unmodifiableList(attributes); 190 } 191 reader.readEndSequence(); 192 } catch (final IOException e) { 193 final LocalizableMessage message = 194 INFO_GETEFFECTIVERIGHTS_DECODE_ERROR.get(e.getMessage()); 195 throw DecodeException.error(message); 196 } 197 } 198 199 return new GetEffectiveRightsRequestControl(control.isCritical(), 200 authorizationDN, attributes); 201 202 } 203 204 public String getOID() { 205 return OID; 206 } 207 }; 208 209 /** 210 * Creates a new get effective rights request control with the provided 211 * criticality, optional authorization name and attribute list. 212 * 213 * @param isCritical 214 * {@code true} if it is unacceptable to perform the operation 215 * without applying the semantics of this control, or 216 * {@code false} if it can be ignored. 217 * @param authorizationName 218 * The distinguished name of the user for which effective rights 219 * are to be returned, or {@code null} if the client's 220 * authentication ID is to be used. 221 * @param attributes 222 * The list of attributes for which effective rights are to be 223 * returned, which may be empty indicating that no attribute 224 * rights are to be returned. 225 * @return The new control. 226 * @throws NullPointerException 227 * If {@code attributes} was {@code null}. 228 */ 229 public static GetEffectiveRightsRequestControl newControl(final boolean isCritical, 230 final DN authorizationName, final Collection<AttributeType> attributes) { 231 Reject.ifNull(attributes); 232 233 final Collection<AttributeType> copyOfAttributes = 234 Collections.unmodifiableList(new ArrayList<AttributeType>(attributes)); 235 return new GetEffectiveRightsRequestControl(isCritical, authorizationName, copyOfAttributes); 236 } 237 238 /** 239 * Creates a new get effective rights request control with the provided 240 * criticality, optional authorization name and attribute list. The 241 * authorization name and attributes, if provided, will be decoded using the 242 * default schema. 243 * 244 * @param isCritical 245 * {@code true} if it is unacceptable to perform the operation 246 * without applying the semantics of this control, or 247 * {@code false} if it can be ignored. 248 * @param authorizationName 249 * The distinguished name of the user for which effective rights 250 * are to be returned, or {@code null} if the client's 251 * authentication ID is to be used. 252 * @param attributes 253 * The list of attributes for which effective rights are to be 254 * returned, which may be empty indicating that no attribute 255 * rights are to be returned. 256 * @return The new control. 257 * @throws UnknownSchemaElementException 258 * If the default schema is a strict schema and one or more of 259 * the requested attribute types were not recognized. 260 * @throws LocalizedIllegalArgumentException 261 * If {@code authorizationName} is not a valid LDAP string 262 * representation of a DN. 263 * @throws NullPointerException 264 * If {@code attributes} was {@code null}. 265 */ 266 public static GetEffectiveRightsRequestControl newControl(final boolean isCritical, 267 final String authorizationName, final String... attributes) { 268 Reject.ifNull((Object) attributes); 269 270 final DN dn = authorizationName == null ? null : DN.valueOf(authorizationName); 271 272 List<AttributeType> copyOfAttributes; 273 if (attributes != null && attributes.length > 0) { 274 copyOfAttributes = new ArrayList<>(attributes.length); 275 for (final String attribute : attributes) { 276 copyOfAttributes.add(Schema.getDefaultSchema().getAttributeType(attribute)); 277 } 278 copyOfAttributes = Collections.unmodifiableList(copyOfAttributes); 279 } else { 280 copyOfAttributes = Collections.emptyList(); 281 } 282 283 return new GetEffectiveRightsRequestControl(isCritical, dn, copyOfAttributes); 284 } 285 286 /** The DN representing the authzId (may be null meaning use the client's DN). */ 287 private final DN authorizationName; 288 289 /** The unmodifiable list of attributes to be queried (may be empty). */ 290 private final Collection<AttributeType> attributes; 291 292 private final boolean isCritical; 293 294 private GetEffectiveRightsRequestControl(final boolean isCritical, final DN authorizationName, 295 final Collection<AttributeType> attributes) { 296 this.isCritical = isCritical; 297 this.authorizationName = authorizationName; 298 this.attributes = attributes; 299 } 300 301 /** 302 * Returns an unmodifiable list of attributes for which effective rights are 303 * to be returned, which may be empty indicating that no attribute rights 304 * are to be returned. 305 * 306 * @return The unmodifiable list of attributes for which effective rights 307 * are to be returned. 308 */ 309 public Collection<AttributeType> getAttributes() { 310 return attributes; 311 } 312 313 /** 314 * Returns the distinguished name of the user for which effective rights are 315 * to be returned, or {@code null} if the client's authentication ID is to 316 * be used. 317 * 318 * @return The distinguished name of the user for which effective rights are 319 * to be returned. 320 */ 321 public DN getAuthorizationName() { 322 return authorizationName; 323 } 324 325 /** {@inheritDoc} */ 326 public String getOID() { 327 return OID; 328 } 329 330 /** {@inheritDoc} */ 331 public ByteString getValue() { 332 final ByteStringBuilder buffer = new ByteStringBuilder(); 333 final ASN1Writer writer = ASN1.getWriter(buffer); 334 try { 335 writer.writeStartSequence(); 336 if (authorizationName != null) { 337 writer.writeOctetString("dn:" + authorizationName); 338 } 339 340 if (!attributes.isEmpty()) { 341 writer.writeStartSequence(); 342 for (final AttributeType attribute : attributes) { 343 writer.writeOctetString(attribute.getNameOrOID()); 344 } 345 writer.writeEndSequence(); 346 } 347 writer.writeEndSequence(); 348 return buffer.toByteString(); 349 } catch (final IOException ioe) { 350 // This should never happen unless there is a bug somewhere. 351 throw new RuntimeException(ioe); 352 } 353 } 354 355 /** {@inheritDoc} */ 356 public boolean hasValue() { 357 return authorizationName != null || !attributes.isEmpty(); 358 } 359 360 /** {@inheritDoc} */ 361 public boolean isCritical() { 362 return isCritical; 363 } 364 365 /** {@inheritDoc} */ 366 @Override 367 public String toString() { 368 final StringBuilder builder = new StringBuilder(); 369 builder.append("GetEffectiveRightsRequestControl(oid="); 370 builder.append(getOID()); 371 builder.append(", criticality="); 372 builder.append(isCritical()); 373 builder.append(", authorizationDN=\""); 374 builder.append(authorizationName); 375 builder.append("\""); 376 builder.append(", attributes=("); 377 builder.append(attributes); 378 builder.append("))"); 379 return builder.toString(); 380 } 381}