confluent based image
[dmaap/kafka11aaf.git] / src / main / java / org / onap / dmaap / kafkaAuthorize / PlainSaslServer1.java
1 /******************************************************************************
2  *  ============LICENSE_START=======================================================
3  *  org.onap.dmaap
4  *  ================================================================================
5  *  Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *        http://www.apache.org/licenses/LICENSE-2.0
11 *  
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *  ============LICENSE_END=========================================================
18  *  
19  *  
20  *******************************************************************************/
21 package org.onap.dmaap.kafkaAuthorize;
22
23 import java.io.UnsupportedEncodingException;
24 import java.nio.charset.StandardCharsets;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.security.auth.callback.Callback;
31 import javax.security.auth.callback.CallbackHandler;
32 import javax.security.auth.callback.NameCallback;
33 import javax.security.sasl.Sasl;
34 import javax.security.sasl.SaslException;
35 import javax.security.sasl.SaslServer;
36 import javax.security.sasl.SaslServerFactory;
37
38 import org.apache.kafka.common.errors.SaslAuthenticationException;
39 import org.apache.kafka.common.security.JaasContext;
40 import org.apache.kafka.common.security.authenticator.SaslServerCallbackHandler;
41 import org.apache.kafka.common.security.plain.PlainAuthenticateCallback;
42 import org.apache.kafka.common.security.plain.internals.PlainSaslServer;
43 import org.onap.dmaap.commonauth.kafka.base.authorization.AuthorizationProviderFactory;
44
45 /**
46  * Simple SaslServer implementation for SASL/PLAIN. In order to make this
47  * implementation fully pluggable, authentication of username/password is fully
48  * contained within the server implementation.
49  * <p>
50  * Valid users with passwords are specified in the Jaas configuration file. Each
51  * user is specified with user_<username> as key and <password> as value. This
52  * is consistent with Zookeeper Digest-MD5 implementation.
53  * <p>
54  * To avoid storing clear passwords on disk or to integrate with external
55  * authentication servers in production systems, this module can be replaced
56  * with a different implementation.
57  *
58  */
59 public class PlainSaslServer1 implements SaslServer {
60
61     public static final String PLAIN_MECHANISM = "PLAIN";
62
63     private boolean complete;
64     private String authorizationId;
65
66
67     /**
68      * @throws SaslAuthenticationException if username/password combination is invalid or if the requested
69      *         authorization id is not the same as username.
70      * <p>
71      * <b>Note:</b> This method may throw {@link SaslAuthenticationException} to provide custom error messages
72      * to clients. But care should be taken to avoid including any information in the exception message that
73      * should not be leaked to unauthenticated clients. It may be safer to throw {@link SaslException} in
74      * some cases so that a standard error message is returned to clients.
75      * </p>
76      */
77     @Override
78     public byte[] evaluateResponse(byte[] responseBytes) throws SaslAuthenticationException {
79         /*
80          * Message format (from https://tools.ietf.org/html/rfc4616):
81          *
82          * message   = [authzid] UTF8NUL authcid UTF8NUL passwd
83          * authcid   = 1*SAFE ; MUST accept up to 255 octets
84          * authzid   = 1*SAFE ; MUST accept up to 255 octets
85          * passwd    = 1*SAFE ; MUST accept up to 255 octets
86          * UTF8NUL   = %x00 ; UTF-8 encoded NUL character
87          *
88          * SAFE      = UTF1 / UTF2 / UTF3 / UTF4
89          *                ;; any UTF-8 encoded Unicode character except NUL
90          */
91         String response = new String(responseBytes, StandardCharsets.UTF_8);
92         List<String> tokens = extractTokens(response);
93         String authorizationIdFromClient = tokens.get(0);
94         String username = tokens.get(1);
95         String password = tokens.get(2);
96
97         if (username.isEmpty()) {
98             throw new SaslAuthenticationException("Authentication failed: username not specified");
99         }
100         if (password.isEmpty()) {
101             throw new SaslAuthenticationException("Authentication failed: password not specified");
102         }
103
104         String aafResponse = "Not Verified";
105                 try {
106                         aafResponse = AuthorizationProviderFactory.getProviderFactory().getProvider().authenticate(username,
107                                         password);
108                 } catch (Exception e) {
109                 }
110                 if (null != aafResponse) {
111                         throw new SaslAuthenticationException("Authentication failed: " + aafResponse + " User " + username);
112                 }
113
114
115         if (!authorizationIdFromClient.isEmpty() && !authorizationIdFromClient.equals(username))
116             throw new SaslAuthenticationException("Authentication failed: Client requested an authorization id that is different from username");
117
118         this.authorizationId = username;
119
120         complete = true;
121         return new byte[0];
122     }
123
124     private List<String> extractTokens(String string) {
125         List<String> tokens = new ArrayList<>();
126         int startIndex = 0;
127         for (int i = 0; i < 4; ++i) {
128             int endIndex = string.indexOf("\u0000", startIndex);
129             if (endIndex == -1) {
130                 tokens.add(string.substring(startIndex));
131                 break;
132             }
133             tokens.add(string.substring(startIndex, endIndex));
134             startIndex = endIndex + 1;
135         }
136
137         if (tokens.size() != 3)
138             throw new SaslAuthenticationException("Invalid SASL/PLAIN response: expected 3 tokens, got " +
139                 tokens.size());
140
141         return tokens;
142     }
143
144     @Override
145     public String getAuthorizationID() {
146         if (!complete)
147             throw new IllegalStateException("Authentication exchange has not completed");
148         return authorizationId;
149     }
150
151     @Override
152     public String getMechanismName() {
153         return PLAIN_MECHANISM;
154     }
155
156     @Override
157     public Object getNegotiatedProperty(String propName) {
158         if (!complete)
159             throw new IllegalStateException("Authentication exchange has not completed");
160         return null;
161     }
162
163     @Override
164     public boolean isComplete() {
165         return complete;
166     }
167
168     @Override
169     public byte[] unwrap(byte[] incoming, int offset, int len) {
170         if (!complete)
171             throw new IllegalStateException("Authentication exchange has not completed");
172         return Arrays.copyOfRange(incoming, offset, offset + len);
173     }
174
175     @Override
176     public byte[] wrap(byte[] outgoing, int offset, int len) {
177         if (!complete)
178             throw new IllegalStateException("Authentication exchange has not completed");
179         return Arrays.copyOfRange(outgoing, offset, offset + len);
180     }
181
182     @Override
183     public void dispose() {
184     }
185
186     public static class PlainSaslServerFactory1 implements SaslServerFactory {
187
188         @Override
189         public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh)
190             throws SaslException {
191
192             if (!PLAIN_MECHANISM.equals(mechanism))
193                 throw new SaslException(String.format("Mechanism \'%s\' is not supported. Only PLAIN is supported.", mechanism));
194
195             return new PlainSaslServer1();
196         }
197
198         @Override
199         public String[] getMechanismNames(Map<String, ?> props) {
200             if (props == null) return new String[]{PLAIN_MECHANISM};
201             String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT);
202             if ("true".equals(noPlainText))
203                 return new String[]{};
204             else
205                 return new String[]{PLAIN_MECHANISM};
206         }
207     }
208 }
209