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; 023 024import org.forgerock.i18n.LocalizableMessage; 025import org.forgerock.i18n.LocalizedIllegalArgumentException; 026import org.forgerock.i18n.slf4j.LocalizedLogger; 027import org.forgerock.opendj.io.ASN1; 028import org.forgerock.opendj.io.ASN1Reader; 029import org.forgerock.opendj.ldap.ByteString; 030import org.forgerock.opendj.ldap.DecodeException; 031import org.forgerock.opendj.ldap.DecodeOptions; 032import org.forgerock.util.Reject; 033 034/** 035 * The proxy authorization v2 request control as defined in RFC 4370. This 036 * control allows a user to request that an operation be performed using the 037 * authorization of another user. 038 * <p> 039 * The target user is specified using an authorization ID, or {@code authzId}, 040 * as defined in RFC 4513 section 5.2.1.8. 041 * <p> 042 * This example shows an application replacing a description on a user entry on 043 * behalf of a directory administrator. 044 * 045 * <pre> 046 * Connection connection = ...; 047 * String bindDN = "cn=My App,ou=Apps,dc=example,dc=com"; // Client app 048 * char[] password = ...; 049 * String targetDn = "uid=bjensen,ou=People,dc=example,dc=com"; // Regular user 050 * String authzId = "dn:uid=kvaughan,ou=People,dc=example,dc=com"; // Admin user 051 * 052 * ModifyRequest request = 053 * Requests.newModifyRequest(targetDn) 054 * .addControl(ProxiedAuthV2RequestControl.newControl(authzId)) 055 * .addModification(ModificationType.REPLACE, "description", 056 * "Done with proxied authz"); 057 * 058 * connection.bind(bindDN, password); 059 * connection.modify(request); 060 * Entry entry = connection.readEntry(targetDn, "description"); 061 * </pre> 062 * 063 * @see <a href="http://tools.ietf.org/html/rfc4370">RFC 4370 - Lightweight 064 * Directory Access Protocol (LDAP) Proxied Authorization Control </a> 065 * @see <a href="http://tools.ietf.org/html/rfc4513#section-5.2.1.8">RFC 4513 - 066 * SASL Authorization Identities (authzId) </a> 067 */ 068public final class ProxiedAuthV2RequestControl implements Control { 069 070 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 071 /** The OID for the proxied authorization v2 control. */ 072 public static final String OID = "2.16.840.1.113730.3.4.18"; 073 074 private static final ProxiedAuthV2RequestControl ANONYMOUS = 075 new ProxiedAuthV2RequestControl(""); 076 077 /** A decoder which can be used for decoding the proxied authorization v2 request control. */ 078 public static final ControlDecoder<ProxiedAuthV2RequestControl> DECODER = 079 new ControlDecoder<ProxiedAuthV2RequestControl>() { 080 081 @Override 082 public ProxiedAuthV2RequestControl decodeControl(final Control control, 083 final DecodeOptions options) throws DecodeException { 084 Reject.ifNull(control); 085 086 if (control instanceof ProxiedAuthV2RequestControl) { 087 return (ProxiedAuthV2RequestControl) control; 088 } 089 090 if (!control.getOID().equals(OID)) { 091 final LocalizableMessage message = 092 ERR_PROXYAUTH2_CONTROL_BAD_OID.get(control.getOID(), OID); 093 throw DecodeException.error(message); 094 } 095 096 if (!control.isCritical()) { 097 final LocalizableMessage message = 098 ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL.get(); 099 throw DecodeException.error(message); 100 } 101 102 if (!control.hasValue()) { 103 // The response control must always have a value. 104 final LocalizableMessage message = ERR_PROXYAUTH2_NO_CONTROL_VALUE.get(); 105 throw DecodeException.error(message); 106 } 107 108 final ASN1Reader reader = ASN1.getReader(control.getValue()); 109 String authorizationID; 110 try { 111 if (reader.elementAvailable()) { 112 // Try the legacy encoding where the value is 113 // wrapped by an extra octet string 114 authorizationID = reader.readOctetStringAsString(); 115 } else { 116 authorizationID = control.getValue().toString(); 117 } 118 } catch (final IOException e) { 119 logger.debug(LocalizableMessage.raw("Unable to read exceptionID", e)); 120 121 final LocalizableMessage message = 122 ERR_PROXYAUTH2_CANNOT_DECODE_VALUE.get(getExceptionMessage(e)); 123 throw DecodeException.error(message, e); 124 } 125 126 if (authorizationID.length() == 0) { 127 // Anonymous. 128 return ANONYMOUS; 129 } 130 131 final int colonIndex = authorizationID.indexOf(':'); 132 if (colonIndex < 0) { 133 final LocalizableMessage message = 134 ERR_PROXYAUTH2_INVALID_AUTHZID_TYPE.get(authorizationID); 135 throw DecodeException.error(message); 136 } 137 138 return new ProxiedAuthV2RequestControl(authorizationID); 139 } 140 141 @Override 142 public String getOID() { 143 return OID; 144 } 145 }; 146 147 /** 148 * Creates a new proxy authorization v2 request control with the provided 149 * authorization ID. The authorization ID usually has the form "dn:" 150 * immediately followed by the distinguished name of the user, or "u:" 151 * followed by a user ID string, but other forms are permitted. 152 * 153 * @param authorizationID 154 * The authorization ID of the user whose authorization is to be 155 * used when performing the operation. 156 * @return The new control. 157 * @throws LocalizedIllegalArgumentException 158 * If {@code authorizationID} was non-empty and did not contain 159 * a valid authorization ID type. 160 * @throws NullPointerException 161 * If {@code authorizationName} was {@code null}. 162 */ 163 public static ProxiedAuthV2RequestControl newControl(final String authorizationID) { 164 if (authorizationID.length() == 0) { 165 // Anonymous. 166 return ANONYMOUS; 167 } 168 169 final int colonIndex = authorizationID.indexOf(':'); 170 if (colonIndex < 0) { 171 final LocalizableMessage message = 172 ERR_PROXYAUTH2_INVALID_AUTHZID_TYPE.get(authorizationID); 173 throw new LocalizedIllegalArgumentException(message); 174 } 175 176 return new ProxiedAuthV2RequestControl(authorizationID); 177 } 178 179 /** The authorization ID from the control value. */ 180 private final String authorizationID; 181 182 private ProxiedAuthV2RequestControl(final String authorizationID) { 183 this.authorizationID = authorizationID; 184 } 185 186 /** 187 * Returns the authorization ID of the user whose authorization is to be 188 * used when performing the operation. The authorization ID usually has the 189 * form "dn:" immediately followed by the distinguished name of the user, or 190 * "u:" followed by a user ID string, but other forms are permitted. 191 * 192 * @return The authorization ID of the user whose authorization is to be 193 * used when performing the operation. 194 */ 195 public String getAuthorizationID() { 196 return authorizationID; 197 } 198 199 @Override 200 public String getOID() { 201 return OID; 202 } 203 204 @Override 205 public ByteString getValue() { 206 return ByteString.valueOfUtf8(authorizationID); 207 } 208 209 @Override 210 public boolean hasValue() { 211 return true; 212 } 213 214 @Override 215 public boolean isCritical() { 216 return true; 217 } 218 219 @Override 220 public String toString() { 221 final StringBuilder builder = new StringBuilder(); 222 builder.append("ProxiedAuthorizationV2Control(oid="); 223 builder.append(getOID()); 224 builder.append(", criticality="); 225 builder.append(isCritical()); 226 builder.append(", authorizationID=\""); 227 builder.append(authorizationID); 228 builder.append("\")"); 229 return builder.toString(); 230 } 231}