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}