mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
2 days ago 81bc6cd81b347f178a1a2b85e33b7101d1241c41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.rest2ldap.authz;
 
import static org.forgerock.util.Reject.checkNotNull;
 
import org.forgerock.http.protocol.Headers;
import org.forgerock.util.Function;
import org.forgerock.util.Pair;
import org.forgerock.util.encode.Base64;
import org.forgerock.util.promise.NeverThrowsException;
 
/**
 * Factory method for function extracting credentials from HTTP request {@link Headers}.
 */
public final class CredentialExtractors {
 
    /** HTTP Header sent by the client with HTTP basic authentication. */
    public static final String HTTP_BASIC_AUTH_HEADER = "Authorization";
 
    private CredentialExtractors() {
    }
 
    /**
     * Creates a function which extracts the user's credentials from the standard HTTP Basic header.
     *
     * @return the basic extractor singleton
     */
    public static Function<Headers, Pair<String, String>, NeverThrowsException> httpBasicExtractor() {
        return HttpBasicExtractor.INSTANCE;
    }
 
    /**
     * Creates a function which extracts the user's credentials from custom HTTP header in addition of the standard HTTP
     * Basic one.
     *
     * @param customHeaderUsername
     *            Name of the additional header to check for the user's name
     * @param customHeaderPassword
     *            Name of the additional header to check for the user's password
     * @return A new credentials extractors looking for custom header.
     */
    public static Function<Headers, Pair<String, String>, NeverThrowsException> newCustomHeaderExtractor(
            String customHeaderUsername, String customHeaderPassword) {
        return new CustomHeaderExtractor(customHeaderUsername, customHeaderPassword);
    }
 
    /** Extract the user's credentials from custom {@link Headers}. */
    private static final class CustomHeaderExtractor
            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
 
        private final String customHeaderUsername;
        private final String customHeaderPassword;
 
        /**
         * Create a new CustomHeaderExtractor.
         *
         * @param customHeaderUsername
         *            Name of the header containing the username
         * @param customHeaderPassword
         *            Name of the header containing the password
         * @throws NullPointerException
         *             if a parameter is null.
         */
        public CustomHeaderExtractor(String customHeaderUsername, String customHeaderPassword) {
            this.customHeaderUsername = checkNotNull(customHeaderUsername, "customHeaderUsername cannot be null");
            this.customHeaderPassword = checkNotNull(customHeaderPassword, "customHeaderPassword cannot be null");
        }
 
        @Override
        public Pair<String, String> apply(Headers headers) {
            final String userName = headers.getFirst(customHeaderUsername);
            final String password = headers.getFirst(customHeaderPassword);
            if (userName != null && password != null) {
                return Pair.of(userName, password);
            }
            return HttpBasicExtractor.INSTANCE.apply(headers);
        }
    }
 
    /** Extract the user's credentials from the standard HTTP Basic {@link Headers}. */
    private static final class HttpBasicExtractor
            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
 
        /** Reference to the HttpBasicExtractor Singleton. */
        public static final HttpBasicExtractor INSTANCE = new HttpBasicExtractor();
 
        /** The authentication scheme and the space which separates it from the credentials. */
        private static final String BASIC_SCHEME = "basic ";
 
        private HttpBasicExtractor() { }
 
        @Override
        public Pair<String, String> apply(Headers headers) {
            final String httpBasicAuthHeader = headers.getFirst(HTTP_BASIC_AUTH_HEADER);
            if (httpBasicAuthHeader != null) {
                final Pair<String, String> userCredentials = parseUsernamePassword(httpBasicAuthHeader);
                if (userCredentials != null) {
                    return userCredentials;
                }
            }
            return null;
        }
 
        private Pair<String, String> parseUsernamePassword(String authHeader) {
            if (authHeader != null && authHeader.regionMatches(true, 0, BASIC_SCHEME, 0, BASIC_SCHEME.length())) {
                // We received authentication info
                // Example received header:
                // "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
                final String base64UserCredentials = authHeader.substring(BASIC_SCHEME.length());
                // Example usage of base64:
                // Base64("Aladdin:open sesame") = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
                final byte[] decoded = Base64.decode(base64UserCredentials);
                if (decoded == null) {
                    // Not a multiple of 4 characters long once the characters outside base64 are dropped.
                    return null;
                }
                final String userCredentials = new String(decoded);
                // RFC 7617 section 2: the user-id cannot contain a colon, the password can.
                final int colon = userCredentials.indexOf(':');
                if (colon >= 0) {
                    return Pair.of(userCredentials.substring(0, colon), userCredentials.substring(colon + 1));
                }
            }
            return null;
        }
    }
}