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}