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