authorization check for more Kafka operations
[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.util.Arrays;
25 import java.util.Map;
26
27 import javax.security.auth.callback.CallbackHandler;
28 import javax.security.sasl.Sasl;
29 import javax.security.sasl.SaslException;
30 import javax.security.sasl.SaslServer;
31 import javax.security.sasl.SaslServerFactory;
32
33 import org.apache.kafka.common.security.JaasContext;
34 import org.apache.kafka.common.security.authenticator.SaslServerCallbackHandler;
35
36 import org.onap.dmaap.commonauth.kafka.base.authorization.AuthorizationProviderFactory;
37
38 /**
39  * Simple SaslServer implementation for SASL/PLAIN. In order to make this
40  * implementation fully pluggable, authentication of username/password is fully
41  * contained within the server implementation.
42  * <p>
43  * Valid users with passwords are specified in the Jaas configuration file. Each
44  * user is specified with user_<username> as key and <password> as value. This
45  * is consistent with Zookeeper Digest-MD5 implementation.
46  * <p>
47  * To avoid storing clear passwords on disk or to integrate with external
48  * authentication servers in production systems, this module can be replaced
49  * with a different implementation.
50  *
51  */
52 public class PlainSaslServer1 implements SaslServer {
53
54         public static final String PLAIN_MECHANISM = "PLAIN";
55
56         private boolean complete;
57         private String authorizationID;
58
59
60         @Override
61         public byte[] evaluateResponse(byte[] response) throws SaslException {
62                 /*
63                  * Message format (from https://tools.ietf.org/html/rfc4616):
64                  *
65                  * message = [authzid] UTF8NUL authcid UTF8NUL passwd authcid = 1*SAFE ;
66                  * MUST accept up to 255 octets authzid = 1*SAFE ; MUST accept up to 255
67                  * octets passwd = 1*SAFE ; MUST accept up to 255 octets UTF8NUL = %x00
68                  * ; UTF-8 encoded NUL character
69                  *
70                  * SAFE = UTF1 / UTF2 / UTF3 / UTF4 ;; any UTF-8 encoded Unicode
71                  * character except NUL
72                  */
73
74                 String[] tokens;
75                 try {
76                         tokens = new String(response, "UTF-8").split("\u0000");
77                 } catch (UnsupportedEncodingException e) {
78                         throw new SaslException("UTF-8 encoding not supported", e);
79                 }
80                 if (tokens.length != 3)
81                         throw new SaslException("Invalid SASL/PLAIN response: expected 3 tokens, got " + tokens.length);
82                 authorizationID = tokens[0];
83                 String username = tokens[1];
84                 String password = tokens[2];
85
86                 if (username.isEmpty()) {
87                         throw new SaslException("Authentication failed: username not specified");
88                 }
89                 if (password.isEmpty()) {
90                         throw new SaslException("Authentication failed: password not specified");
91                 }
92                 if (authorizationID.isEmpty())
93                         authorizationID = username;
94
95                 String aafResponse = "Not Verified";
96                 try {
97                         aafResponse = AuthorizationProviderFactory.getProviderFactory().getProvider().authenticate(username,
98                                         password);
99                 } catch (Exception e) {
100                 }
101
102                 if (null != aafResponse) {
103                         throw new SaslException("Authentication failed: " + aafResponse + " User " + username);
104                 }
105
106                 complete = true;
107                 return new byte[0];
108         }
109
110         @Override
111         public String getAuthorizationID() {
112                 if (!complete)
113                         throw new IllegalStateException("Authentication exchange has not completed");
114                 return authorizationID;
115         }
116
117         @Override
118         public String getMechanismName() {
119                 return PLAIN_MECHANISM;
120         }
121
122         @Override
123         public Object getNegotiatedProperty(String propName) {
124                 if (!complete)
125                         throw new IllegalStateException("Authentication exchange has not completed");
126                 return null;
127         }
128
129         @Override
130         public boolean isComplete() {
131                 return complete;
132         }
133
134         @Override
135         public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException {
136                 if (!complete)
137                         throw new IllegalStateException("Authentication exchange has not completed");
138                 return Arrays.copyOfRange(incoming, offset, offset + len);
139         }
140
141         @Override
142         public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
143                 if (!complete)
144                         throw new IllegalStateException("Authentication exchange has not completed");
145                 return Arrays.copyOfRange(outgoing, offset, offset + len);
146         }
147
148         @Override
149         public void dispose() throws SaslException {
150         }
151
152         public static class PlainSaslServerFactory1 implements SaslServerFactory {
153
154                 @Override
155                 public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props,
156                                 CallbackHandler cbh) throws SaslException {
157
158                         if (!PLAIN_MECHANISM.equals(mechanism))
159                                 throw new SaslException(
160                                                 String.format("Mechanism \'%s\' is not supported. Only PLAIN is supported.", mechanism));
161
162                         return new PlainSaslServer1();
163                 }
164
165                 @Override
166                 public String[] getMechanismNames(Map<String, ?> props) {
167                         if (props == null)
168                                 return new String[] { PLAIN_MECHANISM };
169                         String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT);
170                         if ("true".equals(noPlainText))
171                                 return new String[] {};
172                         else
173                                 return new String[] { PLAIN_MECHANISM };
174                 }
175         }
176 }