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.io.ASN1Writer;
040import org.forgerock.opendj.ldap.ByteString;
041import org.forgerock.opendj.ldap.ByteStringBuilder;
042import org.forgerock.opendj.ldap.DN;
043import org.forgerock.opendj.ldap.DecodeException;
044import org.forgerock.opendj.ldap.DecodeOptions;
045import org.forgerock.opendj.ldap.schema.Schema;
046import org.forgerock.util.Reject;
047
048/**
049 * The proxy authorization v1 request control as defined in
050 * draft-weltman-ldapv3-proxy-04. This control allows a user to request that an
051 * operation be performed using the authorization of another user. The target
052 * user is specified as a DN in the control value, which distinguishes it from
053 * later versions of the control (which used a different OID) in which the
054 * target user was specified using an authorization ID.
055 * <p>
056 * This control implementation is based on version 1 of the proxied
057 * authorization control as defined in early versions of
058 * draft-weltman-ldapv3-proxy (this implementation is based on the "-04"
059 * revision) and is intended for use in legacy applications. New applications
060 * should use the v2 version of this control in preference.
061 *
062 * @see <a href="http://tools.ietf.org/html/draft-weltman-ldapv3-proxy-04">
063 *      draft-weltman-ldapv3-proxy-04 - LDAP Proxied Authorization Control </a>
064 */
065public final class ProxiedAuthV1RequestControl implements Control {
066
067    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
068    /**
069     * The OID for the proxied authorization v1 control.
070     */
071    public static final String OID = "2.16.840.1.113730.3.4.12";
072
073    /**
074     * A decoder which can be used for decoding the proxied authorization v1
075     * request control.
076     */
077    public static final ControlDecoder<ProxiedAuthV1RequestControl> DECODER =
078            new ControlDecoder<ProxiedAuthV1RequestControl>() {
079
080                public ProxiedAuthV1RequestControl decodeControl(final Control control,
081                        final DecodeOptions options) throws DecodeException {
082                    Reject.ifNull(control);
083
084                    if (control instanceof ProxiedAuthV1RequestControl) {
085                        return (ProxiedAuthV1RequestControl) control;
086                    }
087
088                    if (!control.getOID().equals(OID)) {
089                        final LocalizableMessage message =
090                                ERR_PROXYAUTH1_CONTROL_BAD_OID.get(control.getOID(), OID);
091                        throw DecodeException.error(message);
092                    }
093
094                    if (!control.isCritical()) {
095                        final LocalizableMessage message =
096                                ERR_PROXYAUTH1_CONTROL_NOT_CRITICAL.get();
097                        throw DecodeException.error(message);
098                    }
099
100                    if (!control.hasValue()) {
101                        // The response control must always have a value.
102                        final LocalizableMessage message = ERR_PROXYAUTH1_NO_CONTROL_VALUE.get();
103                        throw DecodeException.error(message);
104                    }
105
106                    final ASN1Reader reader = ASN1.getReader(control.getValue());
107                    String authorizationDNString;
108                    try {
109                        reader.readStartSequence();
110                        authorizationDNString = reader.readOctetStringAsString();
111                        reader.readEndSequence();
112                    } catch (final IOException e) {
113                        logger.debug(LocalizableMessage.raw("Unable to read sequence", e));
114
115                        final LocalizableMessage message =
116                                ERR_PROXYAUTH1_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
117                        throw DecodeException.error(message, e);
118                    }
119
120                    final Schema schema =
121                            options.getSchemaResolver().resolveSchema(authorizationDNString);
122                    DN authorizationDN;
123                    try {
124                        authorizationDN = DN.valueOf(authorizationDNString, schema);
125                    } catch (final LocalizedIllegalArgumentException e) {
126                        final LocalizableMessage message =
127                                ERR_PROXYAUTH1_INVALID_AUTHZIDDN.get(getExceptionMessage(e));
128                        throw DecodeException.error(message, e);
129                    }
130
131                    return new ProxiedAuthV1RequestControl(authorizationDN);
132                }
133
134                public String getOID() {
135                    return OID;
136                }
137            };
138
139    /**
140     * Creates a new proxy authorization v1 request control with the provided
141     * authorization name.
142     *
143     * @param authorizationName
144     *            The distinguished name of the user whose authorization is to
145     *            be used when performing the operation.
146     * @return The new control.
147     * @throws NullPointerException
148     *             If {@code authorizationName} was {@code null}.
149     */
150    public static ProxiedAuthV1RequestControl newControl(final DN authorizationName) {
151        Reject.ifNull(authorizationName);
152        return new ProxiedAuthV1RequestControl(authorizationName);
153    }
154
155    /**
156     * Creates a new proxy authorization v1 request control with the provided
157     * authorization name decoded using the default schema.
158     *
159     * @param authorizationName
160     *            The distinguished name of the user whose authorization is to
161     *            be used when performing the operation.
162     * @return The new control.
163     * @throws LocalizedIllegalArgumentException
164     *             If {@code authorizationName} is not a valid LDAP string
165     *             representation of a DN.
166     * @throws NullPointerException
167     *             If {@code authorizationName} was {@code null}.
168     */
169    public static ProxiedAuthV1RequestControl newControl(final String authorizationName) {
170        Reject.ifNull(authorizationName);
171        return new ProxiedAuthV1RequestControl(DN.valueOf(authorizationName));
172    }
173
174    private final DN authorizationName;
175
176    private ProxiedAuthV1RequestControl(final DN authorizationName) {
177        this.authorizationName = authorizationName;
178    }
179
180    /**
181     * Returns the distinguished name of the user whose authorization is to be
182     * used when performing the operation.
183     *
184     * @return The distinguished name of the user whose authorization is to be
185     *         used when performing the operation.
186     */
187    public DN getAuthorizationDNName() {
188        return authorizationName;
189    }
190
191    /** {@inheritDoc} */
192    public String getOID() {
193        return OID;
194    }
195
196    /** {@inheritDoc} */
197    public ByteString getValue() {
198        final ByteStringBuilder buffer = new ByteStringBuilder();
199        final ASN1Writer writer = ASN1.getWriter(buffer);
200        try {
201            writer.writeStartSequence();
202            writer.writeOctetString(authorizationName.toString());
203            writer.writeEndSequence();
204            return buffer.toByteString();
205        } catch (final IOException ioe) {
206            // This should never happen unless there is a bug somewhere.
207            throw new RuntimeException(ioe);
208        }
209    }
210
211    /** {@inheritDoc} */
212    public boolean hasValue() {
213        return true;
214    }
215
216    /** {@inheritDoc} */
217    public boolean isCritical() {
218        return true;
219    }
220
221    /** {@inheritDoc} */
222    @Override
223    public String toString() {
224        final StringBuilder buffer = new StringBuilder();
225        buffer.append("ProxiedAuthorizationV1Control(oid=");
226        buffer.append(getOID());
227        buffer.append(", criticality=");
228        buffer.append(isCritical());
229        buffer.append(", proxyDN=\"");
230        buffer.append(authorizationName);
231        buffer.append("\")");
232        return buffer.toString();
233    }
234}