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