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 2016 ForgeRock AS.
015 */
016package org.forgerock.opendj.rest2ldap.authz;
017
018import static org.forgerock.util.Reject.checkNotNull;
019
020import org.forgerock.http.protocol.Headers;
021import org.forgerock.util.Function;
022import org.forgerock.util.Pair;
023import org.forgerock.util.encode.Base64;
024import org.forgerock.util.promise.NeverThrowsException;
025
026/**
027 * Factory method for function extracting credentials from HTTP request {@link Headers}.
028 */
029public final class CredentialExtractors {
030
031    /** HTTP Header sent by the client with HTTP basic authentication. */
032    public static final String HTTP_BASIC_AUTH_HEADER = "Authorization";
033
034    private CredentialExtractors() {
035    }
036
037    /**
038     * Creates a function which extracts the user's credentials from the standard HTTP Basic header.
039     *
040     * @return the basic extractor singleton
041     */
042    public static Function<Headers, Pair<String, String>, NeverThrowsException> httpBasicExtractor() {
043        return HttpBasicExtractor.INSTANCE;
044    }
045
046    /**
047     * Creates a function which extracts the user's credentials from custom HTTP header in addition of the standard HTTP
048     * Basic one.
049     *
050     * @param customHeaderUsername
051     *            Name of the additional header to check for the user's name
052     * @param customHeaderPassword
053     *            Name of the additional header to check for the user's password
054     * @return A new credentials extractors looking for custom header.
055     */
056    public static Function<Headers, Pair<String, String>, NeverThrowsException> newCustomHeaderExtractor(
057            String customHeaderUsername, String customHeaderPassword) {
058        return new CustomHeaderExtractor(customHeaderUsername, customHeaderPassword);
059    }
060
061    /** Extract the user's credentials from custom {@link Headers}. */
062    private static final class CustomHeaderExtractor
063            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
064
065        private final String customHeaderUsername;
066        private final String customHeaderPassword;
067
068        /**
069         * Create a new CustomHeaderExtractor.
070         *
071         * @param customHeaderUsername
072         *            Name of the header containing the username
073         * @param customHeaderPassword
074         *            Name of the header containing the password
075         * @throws NullPointerException
076         *             if a parameter is null.
077         */
078        public CustomHeaderExtractor(String customHeaderUsername, String customHeaderPassword) {
079            this.customHeaderUsername = checkNotNull(customHeaderUsername, "customHeaderUsername cannot be null");
080            this.customHeaderPassword = checkNotNull(customHeaderPassword, "customHeaderPassword cannot be null");
081        }
082
083        @Override
084        public Pair<String, String> apply(Headers headers) {
085            final String userName = headers.getFirst(customHeaderUsername);
086            final String password = headers.getFirst(customHeaderPassword);
087            if (userName != null && password != null) {
088                return Pair.of(userName, password);
089            }
090            return HttpBasicExtractor.INSTANCE.apply(headers);
091        }
092    }
093
094    /** Extract the user's credentials from the standard HTTP Basic {@link Headers}. */
095    private static final class HttpBasicExtractor
096            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
097
098        /** Reference to the HttpBasicExtractor Singleton. */
099        public static final HttpBasicExtractor INSTANCE = new HttpBasicExtractor();
100
101        private HttpBasicExtractor() { }
102
103        @Override
104        public Pair<String, String> apply(Headers headers) {
105            final String httpBasicAuthHeader = headers.getFirst(HTTP_BASIC_AUTH_HEADER);
106            if (httpBasicAuthHeader != null) {
107                final Pair<String, String> userCredentials = parseUsernamePassword(httpBasicAuthHeader);
108                if (userCredentials != null) {
109                    return userCredentials;
110                }
111            }
112            return null;
113        }
114
115        private Pair<String, String> parseUsernamePassword(String authHeader) {
116            if (authHeader != null && (authHeader.toLowerCase().startsWith("basic"))) {
117                // We received authentication info
118                // Example received header:
119                // "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
120                final String base64UserCredentials = authHeader.substring("basic".length() + 1);
121                // Example usage of base64:
122                // Base64("Aladdin:open sesame") = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
123                final String userCredentials = new String(Base64.decode(base64UserCredentials));
124                String[] split = userCredentials.split(":");
125                if (split.length == 2) {
126                    return Pair.of(split[0], split[1]);
127                }
128            }
129            return null;
130        }
131    }
132}