X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;ds=sidebyside;f=src%2Fmain%2Fjava%2Forg%2Fonap%2Fdmaap%2FkafkaAuthorize%2FPlainSaslServer1.java;h=7a9bedefb1fdb46127e21c093e1569dc7150246b;hb=05eba8cb421bb948f5f72b8adec7cd34429391f4;hp=f28671bcfd41816fe55bdeadfacdeb77f03bfebb;hpb=b01a7330883cbd5bce618ea44ea0f86ce6332729;p=dmaap%2Fkafka11aaf.git diff --git a/src/main/java/org/onap/dmaap/kafkaAuthorize/PlainSaslServer1.java b/src/main/java/org/onap/dmaap/kafkaAuthorize/PlainSaslServer1.java index f28671b..7a9bede 100644 --- a/src/main/java/org/onap/dmaap/kafkaAuthorize/PlainSaslServer1.java +++ b/src/main/java/org/onap/dmaap/kafkaAuthorize/PlainSaslServer1.java @@ -1,8 +1,9 @@ -/******************************************************************************* +/****************************************************************************** * ============LICENSE_START======================================================= * org.onap.dmaap * ================================================================================ * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Modification copyright (C) 2021 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,19 +21,17 @@ *******************************************************************************/ package org.onap.dmaap.kafkaAuthorize; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; - import javax.security.auth.callback.CallbackHandler; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServerFactory; - -import org.apache.kafka.common.security.JaasContext; -import org.apache.kafka.common.security.authenticator.SaslServerCallbackHandler; - +import org.apache.kafka.common.errors.SaslAuthenticationException; import org.onap.dmaap.commonauth.kafka.base.authorization.AuthorizationProviderFactory; /** @@ -51,135 +50,154 @@ import org.onap.dmaap.commonauth.kafka.base.authorization.AuthorizationProviderF */ public class PlainSaslServer1 implements SaslServer { - public static final String PLAIN_MECHANISM = "PLAIN"; - - private final JaasContext jaasContext; - - private boolean complete; - private String authorizationID; - - public PlainSaslServer1(JaasContext jaasContext) { - this.jaasContext = jaasContext; - } - - @Override - public byte[] evaluateResponse(byte[] response) throws SaslException { - /* - * Message format (from https://tools.ietf.org/html/rfc4616): - * - * message = [authzid] UTF8NUL authcid UTF8NUL passwd authcid = 1*SAFE ; - * MUST accept up to 255 octets authzid = 1*SAFE ; MUST accept up to 255 - * octets passwd = 1*SAFE ; MUST accept up to 255 octets UTF8NUL = %x00 - * ; UTF-8 encoded NUL character - * - * SAFE = UTF1 / UTF2 / UTF3 / UTF4 ;; any UTF-8 encoded Unicode - * character except NUL - */ - - String[] tokens; - try { - tokens = new String(response, "UTF-8").split("\u0000"); - } catch (UnsupportedEncodingException e) { - throw new SaslException("UTF-8 encoding not supported", e); - } - if (tokens.length != 3) - throw new SaslException("Invalid SASL/PLAIN response: expected 3 tokens, got " + tokens.length); - authorizationID = tokens[0]; - String username = tokens[1]; - String password = tokens[2]; - - if (username.isEmpty()) { - throw new SaslException("Authentication failed: username not specified"); - } - if (password.isEmpty()) { - throw new SaslException("Authentication failed: password not specified"); - } - if (authorizationID.isEmpty()) - authorizationID = username; - - String aafResponse = "Not Verified"; + public static final String PLAIN_MECHANISM = "PLAIN"; + + private boolean complete; + private String authorizationId; + private static final String AUTH_EXC_NOT_COMPLETE = "Authentication exchange has not completed"; + + + /** + * @throws SaslAuthenticationException if username/password combination is invalid or if the requested + * authorization id is not the same as username. + *

+ * Note: This method may throw {@link SaslAuthenticationException} to provide custom error messages + * to clients. But care should be taken to avoid including any information in the exception message that + * should not be leaked to unauthenticated clients. It may be safer to throw {@link SaslException} in + * some cases so that a standard error message is returned to clients. + *

+ */ + @Override + public byte[] evaluateResponse(byte[] responseBytes) throws SaslAuthenticationException { + /* + * Message format (from https://tools.ietf.org/html/rfc4616): + * + * message = [authzid] UTF8NUL authcid UTF8NUL passwd + * authcid = 1*SAFE ; MUST accept up to 255 octets + * authzid = 1*SAFE ; MUST accept up to 255 octets + * passwd = 1*SAFE ; MUST accept up to 255 octets + * UTF8NUL = %x00 ; UTF-8 encoded NUL character + * + * SAFE = UTF1 / UTF2 / UTF3 / UTF4 + * ;; any UTF-8 encoded Unicode character except NUL + */ + String response = new String(responseBytes, StandardCharsets.UTF_8); + List tokens = extractTokens(response); + String authorizationIdFromClient = tokens.get(0); + String username = tokens.get(1); + String password = tokens.get(2); + + if (username.isEmpty()) { + throw new SaslAuthenticationException("Authentication failed: username not specified"); + } + if (password.isEmpty()) { + throw new SaslAuthenticationException("Authentication failed: password not specified"); + } + + String aafResponse = "Not Verified"; try { aafResponse = AuthorizationProviderFactory.getProviderFactory().getProvider().authenticate(username, password); - } catch (Exception e) { + } catch (Exception ignored) { + throw new SaslAuthenticationException("Authentication failed: " + aafResponse + " User " + username); } - if (null != aafResponse) { - throw new SaslException("Authentication failed: " + aafResponse + " User " + username); + throw new SaslAuthenticationException("Authentication failed: " + aafResponse + " User " + username); } - complete = true; - return new byte[0]; - } - - @Override - public String getAuthorizationID() { - if (!complete) - throw new IllegalStateException("Authentication exchange has not completed"); - return authorizationID; - } - - @Override - public String getMechanismName() { - return PLAIN_MECHANISM; - } - - @Override - public Object getNegotiatedProperty(String propName) { - if (!complete) - throw new IllegalStateException("Authentication exchange has not completed"); - return null; - } - - @Override - public boolean isComplete() { - return complete; - } - - @Override - public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { - if (!complete) - throw new IllegalStateException("Authentication exchange has not completed"); - return Arrays.copyOfRange(incoming, offset, offset + len); - } - - @Override - public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { - if (!complete) - throw new IllegalStateException("Authentication exchange has not completed"); - return Arrays.copyOfRange(outgoing, offset, offset + len); - } - - @Override - public void dispose() throws SaslException { - } - - public static class PlainSaslServerFactory1 implements SaslServerFactory { - - @Override - public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, - CallbackHandler cbh) throws SaslException { - - if (!PLAIN_MECHANISM.equals(mechanism)) - throw new SaslException( - String.format("Mechanism \'%s\' is not supported. Only PLAIN is supported.", mechanism)); - - if (!(cbh instanceof SaslServerCallbackHandler)) - throw new SaslException( - "CallbackHandler must be of type SaslServerCallbackHandler, but it is: " + cbh.getClass()); - - return new PlainSaslServer1(((SaslServerCallbackHandler) cbh).jaasContext()); - } - - @Override - public String[] getMechanismNames(Map props) { - if (props == null) - return new String[] { PLAIN_MECHANISM }; - String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT); - if ("true".equals(noPlainText)) - return new String[] {}; - else - return new String[] { PLAIN_MECHANISM }; - } - } + if (!authorizationIdFromClient.isEmpty() && !authorizationIdFromClient.equals(username)) + throw new SaslAuthenticationException("Authentication failed: Client requested an authorization id that is different from username"); + + this.authorizationId = username; + + complete = true; + return new byte[0]; + } + + private List extractTokens(String string) { + List tokens = new ArrayList<>(); + int startIndex = 0; + for (int i = 0; i < 4; ++i) { + int endIndex = string.indexOf("\u0000", startIndex); + if (endIndex == -1) { + tokens.add(string.substring(startIndex)); + break; + } + tokens.add(string.substring(startIndex, endIndex)); + startIndex = endIndex + 1; + } + + if (tokens.size() != 3) + throw new SaslAuthenticationException("Invalid SASL/PLAIN response: expected 3 tokens, got " + + tokens.size()); + + return tokens; + } + + @Override + public String getAuthorizationID() { + if (!complete) + throw new IllegalStateException(AUTH_EXC_NOT_COMPLETE); + return authorizationId; + } + + @Override + public String getMechanismName() { + return PLAIN_MECHANISM; + } + + @Override + public Object getNegotiatedProperty(String propName) { + if (!complete) + throw new IllegalStateException(AUTH_EXC_NOT_COMPLETE); + return null; + } + + @Override + public boolean isComplete() { + return complete; + } + + @Override + public byte[] unwrap(byte[] incoming, int offset, int len) { + if (!complete) + throw new IllegalStateException(AUTH_EXC_NOT_COMPLETE); + return Arrays.copyOfRange(incoming, offset, offset + len); + } + + @Override + public byte[] wrap(byte[] outgoing, int offset, int len) { + if (!complete) + throw new IllegalStateException(AUTH_EXC_NOT_COMPLETE); + return Arrays.copyOfRange(outgoing, offset, offset + len); + } + + @Override + public void dispose() { + // TODO Auto-generate method stub + } + + public static class PlainSaslServerFactory1 implements SaslServerFactory { + + @Override + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, CallbackHandler cbh) + throws SaslException { + + if (!PLAIN_MECHANISM.equals(mechanism)) + throw new SaslException(String.format("Mechanism '%s' is not supported. Only PLAIN is supported.", mechanism)); + + return new PlainSaslServer1(); + } + + @Override + public String[] getMechanismNames(Map props) { + if (props == null) return new String[]{PLAIN_MECHANISM}; + String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT); + if ("true".equals(noPlainText)) + return new String[]{}; + else + return new String[]{PLAIN_MECHANISM}; + } + } } +