This is an automated email from the ASF dual-hosted git repository. vavrtom pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git
The following commit(s) were added to refs/heads/main by this push: new 58b1b3e QPID-8566: [Broker-J] Implement composite authentication provider (#118) 58b1b3e is described below commit 58b1b3e961c4259956d084bc130ba4bc23fdb468 Author: Daniil Kirilyuk <daniel.kiril...@gmail.com> AuthorDate: Wed Mar 9 07:04:16 2022 +0100 QPID-8566: [Broker-J] Implement composite authentication provider (#118) Co-authored-by: aw924 <daniil.kiril...@deutsche-boerse.com> --- ...ositeUsernamePasswordAuthenticationManager.java | 35 + ...eUsernamePasswordAuthenticationManagerImpl.java | 528 +++++++++++ ...eUsernamePasswordAuthenticationManagerTest.java | 963 +++++++++++++++++++++ .../authenticationprovider/composite/add.html | 28 + .../authenticationprovider/composite/show.html | 26 + .../authenticationprovider/composite/add.js | 159 ++++ .../authenticationprovider/composite/show.js | 38 + ...Security-Authentication-Providers-Composite.xml | 76 ++ ...va-Broker-Security-Authentication-Providers.xml | 1 + 9 files changed, 1854 insertions(+) diff --git a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManager.java b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManager.java new file mode 100644 index 0000000..a9e1131 --- /dev/null +++ b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManager.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth.manager; + +import java.util.List; + +import org.apache.qpid.server.model.ManagedAttribute; +import org.apache.qpid.server.model.ManagedObject; + +@ManagedObject( category = false, type = "Composite" ) +public interface CompositeUsernamePasswordAuthenticationManager<T extends CompositeUsernamePasswordAuthenticationManager<T>> + extends CachingAuthenticationProvider<T>, UsernamePasswordAuthenticationProvider<T> +{ + String PROVIDER_TYPE = "Composite"; + + @ManagedAttribute(description = "delegate authentication providers", mandatory = true) + List<String> getDelegates(); + +} diff --git a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManagerImpl.java b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManagerImpl.java new file mode 100644 index 0000000..3674ef5 --- /dev/null +++ b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManagerImpl.java @@ -0,0 +1,528 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth.manager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Container; +import org.apache.qpid.server.model.ManagedAttributeField; +import org.apache.qpid.server.model.ManagedObjectFactoryConstructor; +import org.apache.qpid.server.model.NamedAddressSpace; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.sasl.PasswordSource; +import org.apache.qpid.server.security.auth.sasl.SaslNegotiator; +import org.apache.qpid.server.security.auth.sasl.SaslSettings; +import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5Base64HashedNegotiator; +import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5Base64HexNegotiator; +import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5Negotiator; +import org.apache.qpid.server.security.auth.sasl.plain.PlainNegotiator; +import org.apache.qpid.server.security.auth.sasl.scram.ScramNegotiator; +import org.apache.qpid.server.security.auth.sasl.scram.ScramSaslServerSource; +import org.apache.qpid.server.security.auth.sasl.scram.ScramSaslServerSourceAdapter; +import org.apache.qpid.server.util.ConnectionScopedRuntimeException; + +/** + * Composite username / password authentication provider. + * + * Contains list of delegate authentication providers, which are assessed one by one during authentication process + * until first successful authentication or until all authentication attempts fail. + * + * When two delegates share same SASL mechanism (e.g. PlainAuthenticationProvider and ScramSHA256AuthenticationManager + * have in common SCRAM-SHA-256), implementation is resolved in runtime choosing the delegate containing username requested. + * If user with same username is present in both delegates, authentication will be performed only against the first + * delegate in the list. + * + */ +public class CompositeUsernamePasswordAuthenticationManagerImpl + extends AbstractAuthenticationManager<CompositeUsernamePasswordAuthenticationManagerImpl> + implements CompositeUsernamePasswordAuthenticationManager<CompositeUsernamePasswordAuthenticationManagerImpl> +{ + + /** + * Mechanism name + */ + @SuppressWarnings("unused") + public static final String MECHANISM_NAME = "COMPOSITE"; + + /** + * Logger + */ + private static final Logger LOGGER = LoggerFactory.getLogger(CompositeUsernamePasswordAuthenticationManagerImpl.class); + + /** + * List of delegate authentication provider names + */ + @SuppressWarnings("unused") + @ManagedAttributeField + private List<String> _delegates; + + /** + * Authentication provider delegates + */ + private final Set<UsernamePasswordAuthenticationProvider<?>> _authenticationProviders = new LinkedHashSet<>(); + + /** + * Available SASL negotiators + */ + private final Map<String, Function<SaslSettings, SaslNegotiator>> _saslNegotiators = new HashMap<>(); + + /** + * Available scram adapters + */ + private final Map<String, ScramSaslServerSourceAdapter> _scramAdapters = new HashMap<>(); + + /** + * HMAC names + */ + private final Map<String, String> _hmacNames = new HashMap<>(); + + /** + * Digest names + */ + private final Map<String, String> _digestNames = new HashMap<>(); + + /** + * List of supported SASL mechanisms + */ + private List<String> _mechanisms = new ArrayList<>(); + + /** + * List of supported SASL mechanisms + */ + private List<String> _secureOnlyMechanisms = new ArrayList<>(); + + /** + * List of supported SASL mechanisms + */ + private List<String> _disabledMechanisms = new ArrayList<>(); + + /** + * Scram iterations count + */ + final int scramIterationCount = getContextValue( + Integer.class, + AbstractScramAuthenticationManager.QPID_AUTHMANAGER_SCRAM_ITERATION_COUNT + ); + + /** + * Constructor creates configured object + * + * @param attributes Attributes + * @param container Parent container + */ + @ManagedObjectFactoryConstructor() + public CompositeUsernamePasswordAuthenticationManagerImpl(final Map<String, Object> attributes, final Container<?> container) + { + super(attributes, container); + } + + /** + * Initiates SCRAM adapters, delegates + */ + @Override + protected void postResolveChildren() + { + super.postResolveChildren(); + + final PasswordSource passwordSource = getPasswordSource(); + + _scramAdapters.put( + ScramSHA1AuthenticationManager.MECHANISM, + new ScramSaslServerSourceAdapter( + scramIterationCount, + ScramSHA1AuthenticationManager.HMAC_NAME, + ScramSHA1AuthenticationManager.DIGEST_NAME, + passwordSource + ) + ); + + _scramAdapters.put( + ScramSHA256AuthenticationManager.MECHANISM, + new ScramSaslServerSourceAdapter( + scramIterationCount, + ScramSHA256AuthenticationManager.HMAC_NAME, + ScramSHA256AuthenticationManager.DIGEST_NAME, + passwordSource + ) + ); + + // check for duplicates + if (new HashSet<>(_delegates).size() != _delegates.size()) + { + throw new IllegalConfigurationException("Composite authentication manager shouldn't contain duplicate names"); + } + + // prepare delegate authentication providers + for (final String delegate: _delegates) + { + final AuthenticationProvider<?> authProvider = resolveDelegate(delegate); + _authenticationProviders.add((UsernamePasswordAuthenticationProvider<?>) authProvider); + } + + if (_authenticationProviders.isEmpty()) + { + throw new IllegalConfigurationException("Composite authentication manager should contain at least one delegate"); + } + + // supported SASL mechanisms are prepared as intersection of delegates mechanisms + _mechanisms = new ArrayList<>(_authenticationProviders.stream().findFirst().get().getMechanisms()); + _authenticationProviders.forEach(authProvider -> _mechanisms.retainAll(authProvider.getMechanisms())); + _authenticationProviders.stream() + .filter(authProvider -> authProvider.getDisabledMechanisms() != null) + .forEach(authProvider -> _mechanisms.removeAll(authProvider.getDisabledMechanisms())); + + // secure only SASL mechanisms are prepared as union of delegates secure only mechanisms + _secureOnlyMechanisms = Stream.concat( + Optional.ofNullable(super.getSecureOnlyMechanisms()).orElse(Collections.emptyList()).stream(), + _authenticationProviders.stream() + .filter(authProvider -> authProvider.getSecureOnlyMechanisms() != null) + .flatMap(authProvider -> authProvider.getSecureOnlyMechanisms().stream())) + .distinct().collect(Collectors.toList()); + + // disabled only SASL mechanisms are prepared as union of delegates disabled mechanisms + _disabledMechanisms = Stream.concat( + Optional.ofNullable(super.getDisabledMechanisms()).orElse(Collections.emptyList()).stream(), + _authenticationProviders.stream() + .filter(authProvider -> authProvider.getDisabledMechanisms() != null) + .flatMap(authProvider -> authProvider.getDisabledMechanisms().stream())) + .distinct().collect(Collectors.toList()); + } + + /** + * Initializes SASL negotiators + */ + @Override + protected void onOpen() + { + super.onOpen(); + + // initialize hmac names + _hmacNames.put(ScramSHA1AuthenticationManager.MECHANISM, ScramSHA1AuthenticationManager.HMAC_NAME); + _hmacNames.put(ScramSHA256AuthenticationManager.MECHANISM, ScramSHA256AuthenticationManager.HMAC_NAME); + + // initialize digest names + _digestNames.put(ScramSHA1AuthenticationManager.MECHANISM, ScramSHA1AuthenticationManager.DIGEST_NAME); + _digestNames.put(ScramSHA256AuthenticationManager.MECHANISM, ScramSHA256AuthenticationManager.DIGEST_NAME); + + // initialize available SASL negotiators + _saslNegotiators.put(CramMd5Negotiator.MECHANISM, (saslSettings) -> new CramMd5Negotiator(getAuthenticationProviderStub(), saslSettings.getLocalFQDN(), getPasswordSource())); + _saslNegotiators.put(CramMd5Base64HashedNegotiator.MECHANISM, (saslSettings) -> new CramMd5Base64HashedNegotiator(getAuthenticationProviderStub(), saslSettings.getLocalFQDN(), getPasswordSource())); + _saslNegotiators.put(CramMd5Base64HexNegotiator.MECHANISM, (saslSettings) -> new CramMd5Base64HexNegotiator(getAuthenticationProviderStub(), saslSettings.getLocalFQDN(), getPasswordSource())); + _saslNegotiators.put(PlainNegotiator.MECHANISM, (saslSettings) -> new PlainNegotiator(this)); + _saslNegotiators.put(ScramSHA1AuthenticationManager.MECHANISM, (saslSettings) -> new ScramNegotiator(this, getScramSaslServerSource(ScramSHA1AuthenticationManager.MECHANISM), ScramSHA1AuthenticationManager.MECHANISM)); + _saslNegotiators.put(ScramSHA256AuthenticationManager.MECHANISM, (saslSettings) -> new ScramNegotiator(this, getScramSaslServerSource(ScramSHA256AuthenticationManager.MECHANISM), ScramSHA256AuthenticationManager.MECHANISM)); + + } + + /** + * Validate changes + * + * @param proxyForValidation ConfiguredObject + * @param changedAttributes Attribute names + */ + @Override + public void validateChange(final ConfiguredObject<?> proxyForValidation, final Set<String> changedAttributes) + { + super.validateChange(proxyForValidation, changedAttributes); + final Collection<String> delegates = (Collection<String>) proxyForValidation.getAttribute("delegates"); + if (delegates.isEmpty()) + { + throw new IllegalConfigurationException("Composite authentication manager should contain at least one delegate"); + } + delegates.forEach(this::resolveDelegate); + } + + /** + * MD5 => ["PLAIN", "CRAM-MD5-HASHED", "CRAM-MD5-HEX"] + * Plain => ["PLAIN", "CRAM-MD5", "SCRAM-SHA-1", "SCRAM-SHA-256"] + * SCRAM-SHA-1 => ["PLAIN", "SCRAM-SHA-1"] + * SCRAM-SHA-256 => ["PLAIN", "SCRAM-SHA-256"] + * SimpleLDAP => ["PLAIN"] + * + * @return List of mechanism names + */ + @Override + public List<String> getMechanisms() + { + return Collections.unmodifiableList(_mechanisms); + } + + /** + * Returns list of available SASL mechanism names + * + * @param secure Secure flag + * + * @return List of mechanism names + */ + @Override + public List<String> getAvailableMechanisms(boolean secure) + { + final List<String> result = _mechanisms.stream() + .filter(mechanism -> secure || !_secureOnlyMechanisms.contains(mechanism)) + .filter(mechanism -> !_disabledMechanisms.contains(mechanism)) + .collect(Collectors.toList()); + return Collections.unmodifiableList(result); + } + + /** + * Creates SASL negotiator based on available options + * + * @param mechanism Mechanism name + * @param saslSettings SaslSettings + * @param addressSpace NamedAddressSpace + * + * @return SaslNegotiator + */ + @Override + public SaslNegotiator createSaslNegotiator( + final String mechanism, + final SaslSettings saslSettings, + final NamedAddressSpace addressSpace + ) + { + return _saslNegotiators.getOrDefault(mechanism, (settings) -> null).apply(saslSettings); + } + + /** + * Iterates over authentication provider delegates attempting authentication for each one. + * + * @param username username + * @param password password + * + * @return AuthenticationResult + */ + @Override + public AuthenticationResult authenticate(String username, String password) + { + for (final UsernamePasswordAuthenticationProvider<?> authenticationProvider : _authenticationProviders) + { + final AuthenticationResult authResult = authenticationProvider.authenticate(username, password); + if (AuthenticationResult.AuthenticationStatus.ERROR.equals(authResult.getStatus())) + { + LOGGER.debug( + "Authentication of user '{}' against '{}' failed", + username, + authenticationProvider.getClass().getSimpleName() + ); + continue; + } + LOGGER.debug( + "Authentication of user '{}' against '{}' succeeded", + username, + authenticationProvider.getClass().getSimpleName() + ); + return authResult; + } + LOGGER.debug("All authentication attempts failed"); + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + } + + /** + * Creates ScramSaslServerSource instance which will retrieve SaltAndPasswordKeys from the authentication + * provider delegate containing requested username. + * + * @param mechanism SASL mechanism + * + * @return ScramSaslServerSource + */ + private ScramSaslServerSource getScramSaslServerSource(String mechanism) + { + return new ScramSaslServerSource() + { + + @Override + public int getIterationCount() + { + return scramIterationCount; + } + + @Override + public String getDigestName() + { + return Optional.ofNullable(_digestNames.get(mechanism)) + .orElseThrow(() -> new ConnectionScopedRuntimeException("Mechanism '" + mechanism + "' not supported")); + } + + @Override + public String getHmacName() + { + return Optional.ofNullable(_hmacNames.get(mechanism)) + .orElseThrow(() -> new ConnectionScopedRuntimeException("Mechanism '" + mechanism + "' not supported")); + } + + @Override + public SaltAndPasswordKeys getSaltAndPasswordKeys(final String username) + { + return _authenticationProviders.stream() + .filter(authProvider -> authProvider instanceof ConfigModelPasswordManagingAuthenticationProvider<?>) + .filter(authProvider -> ((ConfigModelPasswordManagingAuthenticationProvider<?>)authProvider).getUser(username) != null) + .findFirst().map(authProvider -> + { + if (authProvider instanceof AbstractScramAuthenticationManager<?>) + { + return ((AbstractScramAuthenticationManager<?>) authProvider).getSaltAndPasswordKeys(username); + } + return _scramAdapters.get(mechanism).getSaltAndPasswordKeys(username); + }).orElse(_scramAdapters.get(mechanism).getSaltAndPasswordKeys(username)); + } + }; + } + + /** + * Resolves delegate authentication provider by name + * + * @param delegate Delegate name + * + * @return Delegate AuthenticationProvider + */ + private AuthenticationProvider<?> resolveDelegate(final String delegate) + { + final Broker<?> broker = (Broker<?>) getParent(); + + final Optional<AuthenticationProvider<?>> optAuthProvider = broker.getAuthenticationProviders().stream() + .filter(provider -> provider.getName().equals(delegate)).findFirst(); + + if (!optAuthProvider.isPresent()) + { + throw new IllegalConfigurationException("Authentication provider '" + delegate + "' not found"); + } + + final AuthenticationProvider<?> authProvider = optAuthProvider.get(); + + if (!(authProvider instanceof UsernamePasswordAuthenticationProvider<?>)) + { + throw new IllegalConfigurationException("Authentication provider '" + delegate + "' is not UsernamePasswordAuthenticationProvider"); + } + + if (authProvider instanceof CompositeUsernamePasswordAuthenticationManager<?>) + { + throw new IllegalConfigurationException("Composite authentication providers shouldn't be nested"); + } + + return authProvider; + } + + /** + * Retrieves username from the first authentication provider containing user with matching username + * + * @return PasswordSource + */ + private PasswordSource getPasswordSource() + { + return username -> _authenticationProviders.stream() + .filter(authProvider -> authProvider instanceof ConfigModelPasswordManagingAuthenticationProvider) + .filter(authProvider -> ((ConfigModelPasswordManagingAuthenticationProvider<?>) authProvider).getUser(username) != null) + .findFirst() + .map(authProvider -> (((ConfigModelPasswordManagingAuthenticationProvider<?>) authProvider) + .getPasswordSource().getPassword(username))) + .orElse(null); + } + + /** + * Creates authentication provider stub used by some SASL negotiators + * + * @return ConfigModelPasswordManagingAuthenticationProvider stub + */ + private <X extends ConfigModelPasswordManagingAuthenticationProvider<X>> ConfigModelPasswordManagingAuthenticationProvider<X> getAuthenticationProviderStub() + { + final Map<String, Object> attributes = new HashMap<>(); + attributes.put(AuthenticationProvider.NAME, "AuthenticationProviderStub"); + attributes.put(AuthenticationProvider.ID, UUID.randomUUID()); + + final CompositeUsernamePasswordAuthenticationManagerImpl parent = this; + final PasswordSource passwordSource = getPasswordSource(); + + return new ConfigModelPasswordManagingAuthenticationProvider<X>(attributes, (Container<?>) getParent()) + { + + @Override + public AuthenticationResult authenticate(final String username, final String password) + { + return parent.authenticate(username, password); + } + + @Override + public PasswordSource getPasswordSource() + { + return passwordSource; + } + + @Override + protected String createStoredPassword(final String password) + { + throw new ConnectionScopedRuntimeException("SaslNegotiator isn't supposed to call createStoredPassword()"); + } + + @Override + void validateUser(final ManagedUser managedUser) + { + throw new ConnectionScopedRuntimeException("SaslNegotiator isn't supposed to call validateUser()"); + } + + @Override + public List<String> getMechanisms() + { + throw new ConnectionScopedRuntimeException("SaslNegotiator isn't supposed to call getMechanisms()"); + } + + @Override + public SaslNegotiator createSaslNegotiator( + final String mechanism, + final SaslSettings saslSettings, + final NamedAddressSpace addressSpace + ) + { + throw new ConnectionScopedRuntimeException("SaslNegotiator isn't supposed to call createSaslNegotiator()"); + } + }; + } + + @Override + public String toString() + { + return "CompositeAuthenticationManagerImpl {" + + "_authenticationProviders=" + _authenticationProviders + '}'; + } + + @Override + public List<String> getDelegates() + { + return _delegates; + } +} diff --git a/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManagerTest.java b/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManagerTest.java new file mode 100644 index 0000000..2e5a081 --- /dev/null +++ b/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/CompositeUsernamePasswordAuthenticationManagerTest.java @@ -0,0 +1,963 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth.manager; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus.SUCCESS; +import static org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus.ERROR; +import static org.apache.qpid.server.security.auth.manager.CachingAuthenticationProvider.AUTHENTICATION_CACHE_MAX_SIZE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms; +import org.apache.directory.server.annotations.CreateLdapServer; +import org.apache.directory.server.annotations.CreateTransport; +import org.apache.directory.server.annotations.SaslMechanism; +import org.apache.directory.server.core.annotations.ApplyLdifFiles; +import org.apache.directory.server.core.annotations.CreateDS; +import org.apache.directory.server.core.annotations.CreatePartition; +import org.apache.directory.server.core.integ.CreateLdapServerRule; +import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor; +import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler; +import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.CurrentThreadTaskExecutor; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.BrokerTestHelper; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.sasl.SaslNegotiator; +import org.apache.qpid.server.security.auth.sasl.SaslSettings; +import org.apache.qpid.server.security.auth.sasl.SaslUtil; +import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5Base64HashedNegotiator; +import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5Base64HexNegotiator; +import org.apache.qpid.server.security.auth.sasl.crammd5.CramMd5Negotiator; +import org.apache.qpid.server.util.Strings; +import org.apache.qpid.test.utils.UnitTestBase; + +@CreateDS( + name = "testDS", + partitions = + { + @CreatePartition(name = "test", suffix = "dc=qpid,dc=org") + }, + additionalInterceptors = + { + KeyDerivationInterceptor.class + } +) +@CreateLdapServer( + transports = + { + @CreateTransport(protocol = "LDAP") + }, + allowAnonymousAccess = true, + saslHost = "localhost", + saslPrincipal = "ldap/localh...@qpid.org", + saslMechanisms = + { + @SaslMechanism(name = SupportedSaslMechanisms.PLAIN, implClass = PlainMechanismHandler.class), + @SaslMechanism(name = SupportedSaslMechanisms.GSSAPI, implClass = GssapiMechanismHandler.class) + } +) +@ApplyLdifFiles("users.ldif") +public class CompositeUsernamePasswordAuthenticationManagerTest extends UnitTestBase +{ + @ClassRule + public static CreateLdapServerRule LDAP = new CreateLdapServerRule(); + private final List<AuthenticationProvider<?>> _authenticationProviders = new ArrayList<>(); + private Broker<?> _broker; + private TaskExecutor _executor; + + private static final String USERNAME = "user1"; + private static final String PASSWORD = "password1"; + + @Before + public void setUp() throws Exception + { + _executor = new CurrentThreadTaskExecutor(); + _executor.start(); + _broker = BrokerTestHelper.createBrokerMock(); + when(_broker.getTaskExecutor()).thenReturn(_executor); + when(_broker.getChildExecutor()).thenReturn(_executor); + when(_broker.getAuthenticationProviders()).thenReturn(_authenticationProviders); + SaslHelper._clientNonce = UUID.randomUUID().toString(); + } + + @After + public void tearDown() throws Exception + { + _executor.stop(); + _authenticationProviders.clear(); + } + + @SuppressWarnings("unchecked") + private CompositeUsernamePasswordAuthenticationManager<?> createCompositeAuthenticationManager( + UsernamePasswordAuthenticationProvider<?>... authenticationProviders + ) + { + final Map<String, Object> attributesMap = new HashMap<>(); + attributesMap.put(AuthenticationProvider.TYPE, CompositeUsernamePasswordAuthenticationManager.PROVIDER_TYPE); + attributesMap.put(AuthenticationProvider.NAME, "CompositeAuthenticationProvider"); + attributesMap.put(AuthenticationProvider.ID, UUID.randomUUID()); + if (authenticationProviders.length > 0) + { + attributesMap.put( + "delegates", + Arrays.stream(authenticationProviders).map(ConfiguredObject::getName).collect(Collectors.toList()) + ); + } + + AuthenticationProvider<?> authProvider = + _broker.getObjectFactory().create(AuthenticationProvider.class, attributesMap, _broker); + _authenticationProviders.add(authProvider); + return (CompositeUsernamePasswordAuthenticationManager<?>) authProvider; + } + + @SuppressWarnings("unchecked") + private MD5AuthenticationProvider createMD5AuthenticationProvider() + { + final Map<String, Object> attributesMap = new HashMap<>(); + attributesMap.put(AuthenticationProvider.NAME, "MD5AuthenticationProvider"); + attributesMap.put(AuthenticationProvider.TYPE, "MD5"); + attributesMap.put(AuthenticationProvider.ID, UUID.randomUUID()); + AuthenticationProvider<?> authProvider = + _broker.getObjectFactory().create(AuthenticationProvider.class, attributesMap, _broker); + _authenticationProviders.add(authProvider); + return (MD5AuthenticationProvider) authProvider; + } + + @SuppressWarnings("unchecked") + private PlainAuthenticationProvider createPlainAuthenticationProvider(String... names) + { + final Map<String, Object> attributesMap = new HashMap<>(); + attributesMap.put(AuthenticationProvider.NAME, names.length == 0 ? "PlainAuthenticationProvider" : names[0]); + attributesMap.put(AuthenticationProvider.TYPE, "Plain"); + attributesMap.put(AuthenticationProvider.ID, UUID.randomUUID()); + PlainAuthenticationProvider authProvider = (PlainAuthenticationProvider) _broker.getObjectFactory() + .create(AuthenticationProvider.class, attributesMap, _broker); + _authenticationProviders.add(authProvider); + return authProvider; + } + + @SuppressWarnings("unchecked") + private ScramSHA256AuthenticationManager createScramSHA256AuthenticationManager(String... names) + { + final Map<String, Object> attributesMap = new HashMap<>(); + attributesMap.put(AuthenticationProvider.NAME, + names.length == 0 ? "ScramSHA256AuthenticationManager" : names[0]); + attributesMap.put(AuthenticationProvider.TYPE, "SCRAM-SHA-256"); + attributesMap.put(AuthenticationProvider.ID, UUID.randomUUID()); + ScramSHA256AuthenticationManager authProvider = (ScramSHA256AuthenticationManager) _broker.getObjectFactory() + .create(AuthenticationProvider.class, attributesMap, _broker); + _authenticationProviders.add(authProvider); + return authProvider; + } + + @SuppressWarnings("unchecked") + private ScramSHA1AuthenticationManager createScramSHA1AuthenticationManager(String... names) + { + final Map<String, Object> attributesMap = new HashMap<>(); + attributesMap.put(AuthenticationProvider.NAME, + names.length == 0 ? "ScramSHA1AuthenticationManager" : names[0]); + attributesMap.put(AuthenticationProvider.TYPE, "SCRAM-SHA-1"); + attributesMap.put(AuthenticationProvider.ID, UUID.randomUUID()); + ScramSHA1AuthenticationManager authProvider = (ScramSHA1AuthenticationManager) _broker.getObjectFactory() + .create(AuthenticationProvider.class, attributesMap, _broker); + _authenticationProviders.add(authProvider); + return authProvider; + } + + @SuppressWarnings("unchecked") + private SimpleLDAPAuthenticationManager<?> createSimpleLDAPAuthenticationManager() + { + final String LDAP_URL_TEMPLATE = "ldap://localhost:%d"; + final String ROOT = "dc=qpid,dc=org"; + final String SEARCH_CONTEXT_VALUE = "ou=users," + ROOT; + final String SEARCH_FILTER_VALUE = "(uid={0})"; + + final Map<String, Object> attributesMap = new HashMap<>(); + attributesMap.put(SimpleLDAPAuthenticationManager.NAME, "SimpleLDAPAuthenticationManager"); + attributesMap.put(SimpleLDAPAuthenticationManager.ID, UUID.randomUUID()); + attributesMap.put(SimpleLDAPAuthenticationManager.TYPE, SimpleLDAPAuthenticationManager.PROVIDER_TYPE); + attributesMap.put(SimpleLDAPAuthenticationManager.SEARCH_CONTEXT, SEARCH_CONTEXT_VALUE); + attributesMap.put(SimpleLDAPAuthenticationManager.PROVIDER_URL, + String.format(LDAP_URL_TEMPLATE, LDAP.getLdapServer().getPort())); + attributesMap.put(SimpleLDAPAuthenticationManager.SEARCH_FILTER, SEARCH_FILTER_VALUE); + attributesMap.put(SimpleLDAPAuthenticationManager.CONTEXT, + Collections.singletonMap(AUTHENTICATION_CACHE_MAX_SIZE, "0")); + final SimpleLDAPAuthenticationManager<?> authProvider = + (SimpleLDAPAuthenticationManager<?>) _broker.getObjectFactory() + .create(AuthenticationProvider.class, attributesMap, _broker); + _authenticationProviders.add(authProvider); + return authProvider; + } + + @Test(expected = IllegalArgumentException.class) + public void failToCreateCompositeAuthenticationManager() + { + createCompositeAuthenticationManager(); + } + + @Test() + public void authenticateAgainstPlainAuthenticationProvider() throws Exception + { + + final PlainAuthenticationProvider plainAuthenticationProvider = createPlainAuthenticationProvider(); + plainAuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + plainAuthenticationProvider + ); + + final AuthenticationResult result = authManager.authenticate(USERNAME, PASSWORD); + assertEquals("Unexpected result status", SUCCESS, result.getStatus()); + assertEquals("Unexpected result principal", USERNAME, result.getMainPrincipal().getName()); + + // authenticate via SASL PLAIN + final String RESPONSE = String.format("\0%s\0%s", USERNAME, PASSWORD); + final SaslNegotiator plainSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult plainAuthResult = plainSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", + SUCCESS, + plainAuthResult.getStatus()); + + // authenticate via SASL CRAM-MD5 + saslCramMd( + CramMd5Negotiator.MECHANISM, + authManager.createSaslNegotiator( + CramMd5Negotiator.MECHANISM, + CRAM_MD_SASL_SETTINGS, + null + ), + USERNAME, + PASSWORD + ); + + // authenticate via SASL SCRAM-SHA-1 + saslScramSha( + ScramSHA1AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA1AuthenticationManager.MECHANISM, null, null), + USERNAME, + PASSWORD + ); + + // authenticate via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, + PASSWORD + ); + } + + @Test() + public void authenticateAgainstMD5AuthenticationProvider() throws Exception + { + + final PlainAuthenticationProvider plainAuthenticationProvider = createPlainAuthenticationProvider(); + final MD5AuthenticationProvider md5AuthenticationProvider = createMD5AuthenticationProvider(); + md5AuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + plainAuthenticationProvider, md5AuthenticationProvider + ); + AuthenticationResult result = authManager.authenticate(USERNAME, PASSWORD); + assertEquals("Unexpected result status", SUCCESS, result.getStatus()); + assertEquals("Unexpected result principal", USERNAME, result.getMainPrincipal().getName()); + + // authenticate via SASL PLAIN + final String RESPONSE = String.format("\0%s\0%s", USERNAME, PASSWORD); + final SaslNegotiator plainSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult plainAuthResult = plainSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, plainAuthResult.getStatus()); + + // authenticate via SASL CRAM-MD5-HASHED + saslCramMd( + CramMd5Base64HashedNegotiator.MECHANISM, + authManager.createSaslNegotiator( + CramMd5Base64HashedNegotiator.MECHANISM, + CRAM_MD_SASL_SETTINGS, + null + ), + USERNAME, + PASSWORD + ); + + // authenticate via SASL CRAM-MD5-HEX + saslCramMd( + CramMd5Base64HexNegotiator.MECHANISM, + authManager.createSaslNegotiator( + CramMd5Base64HexNegotiator.MECHANISM, + CRAM_MD_SASL_SETTINGS, + null + ), + USERNAME, + PASSWORD + ); + } + + @Test() + public void authenticateAgainstScramSHA1AuthenticationManager() throws Exception + { + + final ScramSHA1AuthenticationManager scramSHA1AuthenticationManager = + createScramSHA1AuthenticationManager(); + scramSHA1AuthenticationManager.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + scramSHA1AuthenticationManager + ); + AuthenticationResult result = authManager.authenticate(USERNAME, PASSWORD); + assertEquals("Unexpected result status", SUCCESS, result.getStatus()); + assertEquals("Unexpected result principal", USERNAME, result.getMainPrincipal().getName()); + + saslScramSha( + ScramSHA1AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA1AuthenticationManager.MECHANISM, null, null), + USERNAME, + PASSWORD + ); + } + + @Test() + public void authenticateAgainstScramSHA256AuthenticationManager() throws Exception + { + + final ScramSHA256AuthenticationManager scramSHA256AuthenticationManager = + createScramSHA256AuthenticationManager(); + scramSHA256AuthenticationManager.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + scramSHA256AuthenticationManager + ); + AuthenticationResult result = authManager.authenticate(USERNAME, PASSWORD); + assertEquals("Unexpected result status", SUCCESS, result.getStatus()); + assertEquals("Unexpected result principal", USERNAME, result.getMainPrincipal().getName()); + + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, + PASSWORD + ); + } + + @Test() + public void authenticateAgainstSimpleLDAPAuthenticationManager() + { + final String LDAP_USERNAME = "test1"; + + final SimpleLDAPAuthenticationManager<?> simpleLDAPAuthenticationManager = + createSimpleLDAPAuthenticationManager(); + + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + simpleLDAPAuthenticationManager + ); + + AuthenticationResult result = authManager.authenticate(LDAP_USERNAME, PASSWORD); + assertEquals("Unexpected result status", SUCCESS, result.getStatus()); + assertEquals("Unexpected result principal", "cn=integration-test1,ou=users,dc=qpid,dc=org", result.getMainPrincipal().getName()); + + // authenticate via SASL PLAIN + final String RESPONSE = String.format("\0%s\0%s", LDAP_USERNAME, PASSWORD); + final SaslNegotiator plainSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult plainAuthResult = plainSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, plainAuthResult.getStatus()); + } + + @Test() + public void authenticateAgainstPlainAndMd5AndSimpleLdap() throws Exception + { + final String MD5_USERNAME = "user2"; + final String MD5_PASSWORD = "password2"; + final String LDAP_USERNAME = "test1"; + final String LDAP_PASSWORD = "password1"; + + final PlainAuthenticationProvider plainAuthenticationProvider = createPlainAuthenticationProvider(); + plainAuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + + final MD5AuthenticationProvider md5AuthenticationProvider = createMD5AuthenticationProvider(); + md5AuthenticationProvider.createUser(MD5_USERNAME, MD5_PASSWORD, Collections.emptyMap()); + + final SimpleLDAPAuthenticationManager<?> simpleLDAPAuthenticationManager = + createSimpleLDAPAuthenticationManager(); + + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + plainAuthenticationProvider, md5AuthenticationProvider, simpleLDAPAuthenticationManager + ); + + // authenticate against PlainAuthenticationProvider via SASL PLAIN + String RESPONSE = String.format("\0%s\0%s", USERNAME, PASSWORD); + final SaslNegotiator plainSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult plainAuthResult = plainSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, plainAuthResult.getStatus()); + assertEquals( + "Unexpected result principal", + USERNAME, + plainAuthResult.getMainPrincipal().getName() + ); + + // authenticate against PlainAuthenticationProvider via SASL CRAM-MD5 + saslCramMd( + CramMd5Negotiator.MECHANISM, + authManager.createSaslNegotiator(CramMd5Negotiator.MECHANISM, CRAM_MD_SASL_SETTINGS, null), + USERNAME, + PASSWORD + ); + + // authenticate against PlainAuthenticationProvider via SASL SCRAM-SHA-1 + saslScramSha( + ScramSHA1AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA1AuthenticationManager.MECHANISM, null, null), + USERNAME, + PASSWORD + ); + + // authenticate against PlainAuthenticationProvider via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, + PASSWORD + ); + + // authenticate against MD5AuthenticationProvider via SASL PLAIN + RESPONSE = String.format("\0%s\0%s", MD5_USERNAME, MD5_PASSWORD); + final SaslNegotiator md5SaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult md5AuthResult = md5SaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, md5AuthResult.getStatus()); + assertEquals( + "Unexpected result principal", + MD5_USERNAME, + md5AuthResult.getMainPrincipal().getName() + ); + + // authenticate against MD5AuthenticationProvider via SASL CRAM-MD5-HASHED + saslCramMd( + CramMd5Base64HashedNegotiator.MECHANISM, + authManager.createSaslNegotiator(CramMd5Base64HashedNegotiator.MECHANISM, CRAM_MD_SASL_SETTINGS, null), + MD5_USERNAME, + MD5_PASSWORD + ); + + // authenticate against MD5AuthenticationProvider via SASL CRAM-MD5-HEX + saslCramMd( + CramMd5Base64HexNegotiator.MECHANISM, + authManager.createSaslNegotiator(CramMd5Base64HexNegotiator.MECHANISM, CRAM_MD_SASL_SETTINGS,null), + MD5_USERNAME, + MD5_PASSWORD + ); + + // authenticate against SimpleLdapAuthenticationProvider via SASL PLAIN + RESPONSE = String.format("\0%s\0%s", LDAP_USERNAME, LDAP_PASSWORD); + final SaslNegotiator ldapSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult ldapAuthResult = ldapSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, ldapAuthResult.getStatus()); + assertEquals( + "Unexpected result principal", + "cn=integration-test1,ou=users,dc=qpid,dc=org", + ldapAuthResult.getMainPrincipal().getName() + ); + } + + @Test() + public void authenticateAgainstPlainAndSha256AndSimpleLdap() throws Exception + { + final String SHA256_USERNAME = "user2"; + final String SHA256_PASSWORD = "password2"; + final String LDAP_USERNAME = "test1"; + final String LDAP_PASSWORD = "password1"; + + final PlainAuthenticationProvider plainAuthenticationProvider = createPlainAuthenticationProvider(); + plainAuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + + final ScramSHA256AuthenticationManager sha256AuthenticationProvider = createScramSHA256AuthenticationManager(); + sha256AuthenticationProvider.createUser(SHA256_USERNAME, SHA256_PASSWORD, Collections.emptyMap()); + + final SimpleLDAPAuthenticationManager<?> simpleLDAPAuthenticationManager = + createSimpleLDAPAuthenticationManager(); + + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + plainAuthenticationProvider, sha256AuthenticationProvider, simpleLDAPAuthenticationManager + ); + + // authenticate against PlainAuthenticationProvider via SASL PLAIN + String RESPONSE = String.format("\0%s\0%s", USERNAME, PASSWORD); + final SaslNegotiator plainSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult plainAuthResult = plainSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, plainAuthResult.getStatus()); + assertEquals("Unexpected result principal", USERNAME, plainAuthResult.getMainPrincipal().getName()); + + // authenticate against PlainAuthenticationProvider via SASL CRAM-MD5 + saslCramMd( + CramMd5Negotiator.MECHANISM, + authManager.createSaslNegotiator(CramMd5Negotiator.MECHANISM, CRAM_MD_SASL_SETTINGS, null), + USERNAME, PASSWORD); + + // authenticate against PlainAuthenticationProvider via SASL SCRAM-SHA-1 + saslScramSha( + ScramSHA1AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA1AuthenticationManager.MECHANISM, null, null), + USERNAME, PASSWORD); + + // authenticate against PlainAuthenticationProvider via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, PASSWORD); + + // authenticate against ScramSHA256AuthenticationManager via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + SHA256_USERNAME, SHA256_PASSWORD); + + // authenticate against SimpleLdapAuthenticationProvider via SASL PLAIN + RESPONSE = String.format("\0%s\0%s", LDAP_USERNAME, LDAP_PASSWORD); + final SaslNegotiator ldapSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult ldapAuthResult = ldapSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", SUCCESS, ldapAuthResult.getStatus()); + assertEquals( + "Unexpected result principal", + "cn=integration-test1,ou=users,dc=qpid,dc=org", + ldapAuthResult.getMainPrincipal().getName() + ); + } + + @Test() + public void usernameCollision() throws Exception + { + final String PLAIN_PASSWORD = "password1"; + final String SHA256_PASSWORD = "password2"; + + final PlainAuthenticationProvider plainAuthenticationProvider = createPlainAuthenticationProvider(); + plainAuthenticationProvider.createUser(USERNAME, PLAIN_PASSWORD, Collections.emptyMap()); + + final ScramSHA256AuthenticationManager sha256AuthenticationProvider = createScramSHA256AuthenticationManager(); + sha256AuthenticationProvider.createUser(USERNAME, SHA256_PASSWORD, Collections.emptyMap()); + + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + plainAuthenticationProvider, sha256AuthenticationProvider); + + // authenticate against PlainAuthenticationProvider via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, PLAIN_PASSWORD); + + // authenticate against ScramSHA256AuthenticationManager via SASL SCRAM-SHA-256 (fails due username collision) + saslScramShaInvalidCredentials( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, SHA256_PASSWORD); + } + + @Test() + public void differentUsersInScramSHA256AuthenticationManagers() throws Exception + { + final String SHA256_USERNAME2 = "user2"; + final String SHA256_PASSWORD2 = "password2"; + final String SHA256_USERNAME3 = "user3"; + final String SHA256_PASSWORD3 = "password4"; + + final ScramSHA256AuthenticationManager sha256AuthenticationProvider1 = createScramSHA256AuthenticationManager("ScramSHA256AuthenticationManager1"); + sha256AuthenticationProvider1.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + + final ScramSHA256AuthenticationManager sha256AuthenticationProvider2 = createScramSHA256AuthenticationManager("ScramSHA256AuthenticationManager2"); + sha256AuthenticationProvider2.createUser(SHA256_USERNAME2, SHA256_PASSWORD2, Collections.emptyMap()); + + final ScramSHA256AuthenticationManager sha256AuthenticationProvider3 = createScramSHA256AuthenticationManager("ScramSHA256AuthenticationManager3"); + sha256AuthenticationProvider3.createUser(SHA256_USERNAME3, SHA256_PASSWORD3, Collections.emptyMap()); + + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + sha256AuthenticationProvider1, sha256AuthenticationProvider2, sha256AuthenticationProvider3); + + // authenticate against first ScramSHA256AuthenticationManager via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + USERNAME, PASSWORD); + + // authenticate against second ScramSHA256AuthenticationManager via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + SHA256_USERNAME2, SHA256_PASSWORD2); + + // authenticate against third ScramSHA256AuthenticationManager via SASL SCRAM-SHA-256 + saslScramSha( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + SHA256_USERNAME3, SHA256_PASSWORD3); + + } + + @Test() + public void userNotFound() throws Exception + { + final String SHA1_USERNAME = "user2"; + final String SHA1_PASSWORD = "password2"; + final String NON_EXISTING_USERNAME = "test99"; + + final ScramSHA256AuthenticationManager sha256AuthenticationProvider = createScramSHA256AuthenticationManager(); + sha256AuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + + final ScramSHA1AuthenticationManager sha1AuthenticationProvider = createScramSHA1AuthenticationManager(); + sha1AuthenticationProvider.createUser(SHA1_USERNAME, SHA1_PASSWORD, Collections.emptyMap()); + + final SimpleLDAPAuthenticationManager<?> simpleLDAPAuthenticationManager = + createSimpleLDAPAuthenticationManager(); + + final CompositeUsernamePasswordAuthenticationManager<?> authManager = createCompositeAuthenticationManager( + sha256AuthenticationProvider, sha1AuthenticationProvider, simpleLDAPAuthenticationManager); + + // authenticate against ScramSHA256AuthenticationManager via SASL SCRAM-SHA-256 + saslScramShaInvalidCredentials( + ScramSHA256AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA256AuthenticationManager.MECHANISM, null, null), + NON_EXISTING_USERNAME, PASSWORD); + + // authenticate against ScramSHA256AuthenticationManager via SASL SCRAM-SHA-1 + saslScramShaInvalidCredentials( + ScramSHA1AuthenticationManager.MECHANISM, + authManager.createSaslNegotiator(ScramSHA1AuthenticationManager.MECHANISM, null, null), + NON_EXISTING_USERNAME, SHA1_PASSWORD); + + // authenticate against SimpleLdapAuthenticationProvider via SASL PLAIN + String RESPONSE = String.format("\0%s\0%s", NON_EXISTING_USERNAME, PASSWORD); + final SaslNegotiator ldapSaslNegotiator = authManager.createSaslNegotiator("PLAIN", null, null); + final AuthenticationResult ldapAuthResult = ldapSaslNegotiator.handleResponse(RESPONSE.getBytes(US_ASCII)); + assertEquals("Unexpected result status", ERROR, ldapAuthResult.getStatus()); + } + + @Test(expected = IllegalConfigurationException.class) + public void nestedComposteUsernamePasswordAuthenticationManager() + { + final ScramSHA256AuthenticationManager sha256AuthenticationProvider = createScramSHA256AuthenticationManager(); + sha256AuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + final CompositeUsernamePasswordAuthenticationManager<?> composite1 = createCompositeAuthenticationManager(sha256AuthenticationProvider); + createCompositeAuthenticationManager(composite1); + } + + @Test(expected = IllegalConfigurationException.class) + public void duplicateDelegates() + { + final ScramSHA256AuthenticationManager sha256AuthenticationProvider = createScramSHA256AuthenticationManager(); + sha256AuthenticationProvider.createUser(USERNAME, PASSWORD, Collections.emptyMap()); + createCompositeAuthenticationManager(sha256AuthenticationProvider, sha256AuthenticationProvider); + } + + private void saslCramMd( + String mechanism, + SaslNegotiator saslNegotiator, + String username, + String password + ) throws Exception + { + + final AuthenticationResult firstResult = saslNegotiator.handleResponse(new byte[0]); + assertEquals( + "Unexpected first result status", + AuthenticationResult.AuthenticationStatus.CONTINUE, + firstResult.getStatus() + ); + + byte[] responseBytes = SaslUtil.generateCramMD5ClientResponse( + mechanism, username, password, firstResult.getChallenge() + ); + + final AuthenticationResult secondResult = saslNegotiator.handleResponse(responseBytes); + + assertEquals("Unexpected second result status", + SUCCESS, + secondResult.getStatus()); + assertNull("Unexpected second result challenge", secondResult.getChallenge()); + assertEquals("Unexpected second result main principal", + username, + secondResult.getMainPrincipal().getName()); + + final AuthenticationResult thirdResult = saslNegotiator.handleResponse(new byte[0]); + assertEquals("Unexpected third result status", + AuthenticationResult.AuthenticationStatus.ERROR, + thirdResult.getStatus()); + } + + private void saslScramSha( + String mechanism, + SaslNegotiator saslNegotiator, + String username, + String password + ) throws Exception + { + + final byte[] initialResponse = SaslHelper.createInitialResponse(username); + + final AuthenticationResult firstResult = saslNegotiator.handleResponse(initialResponse); + assertEquals( + "Unexpected first result status", + AuthenticationResult.AuthenticationStatus.CONTINUE, + firstResult.getStatus() + ); + assertNotNull("Unexpected first result challenge", firstResult.getChallenge()); + + final byte[] response = SaslHelper.calculateClientProof( + firstResult.getChallenge(), + ScramSHA256AuthenticationManager.MECHANISM.equals(mechanism) + ? ScramSHA256AuthenticationManager.HMAC_NAME : ScramSHA1AuthenticationManager.HMAC_NAME, + ScramSHA256AuthenticationManager.MECHANISM.equals(mechanism) + ? ScramSHA256AuthenticationManager.DIGEST_NAME : ScramSHA1AuthenticationManager.DIGEST_NAME, + password + ); + + final AuthenticationResult secondResult = saslNegotiator.handleResponse(response); + assertEquals( + "Unexpected second result status", + SUCCESS, + secondResult.getStatus() + ); + assertNotNull("Unexpected second result challenge", secondResult.getChallenge()); + assertEquals( + "Unexpected second result principal", + username, + secondResult.getMainPrincipal().getName() + ); + + final String serverFinalMessage = new String(secondResult.getChallenge(), SaslHelper.ASCII); + final String[] parts = serverFinalMessage.split(","); + if (!parts[0].startsWith("v=")) + { + fail("Server final message did not contain verifier"); + } + final byte[] serverSignature = Strings.decodeBase64(parts[0].substring(2)); + if (!Arrays.equals(SaslHelper._serverSignature, serverSignature)) + { + fail("Server signature did not match"); + } + + final AuthenticationResult thirdResult = saslNegotiator.handleResponse(initialResponse); + assertEquals("Unexpected result status after completion of negotiation", + AuthenticationResult.AuthenticationStatus.ERROR, + thirdResult.getStatus()); + assertNull("Unexpected principal after completion of negotiation", thirdResult.getMainPrincipal()); + } + + private void saslScramShaInvalidCredentials( + String mechanism, + SaslNegotiator saslNegotiator, + String username, + String password) throws Exception + { + + final byte[] initialResponse = SaslHelper.createInitialResponse(username); + + final AuthenticationResult firstResult = saslNegotiator.handleResponse(initialResponse); + assertEquals( + "Unexpected first result status", + AuthenticationResult.AuthenticationStatus.CONTINUE, + firstResult.getStatus() + ); + assertNotNull("Unexpected first result challenge", firstResult.getChallenge()); + + final byte[] response = SaslHelper.calculateClientProof( + firstResult.getChallenge(), + ScramSHA256AuthenticationManager.MECHANISM.equals(mechanism) + ? ScramSHA256AuthenticationManager.HMAC_NAME : ScramSHA1AuthenticationManager.HMAC_NAME, + ScramSHA256AuthenticationManager.MECHANISM.equals(mechanism) + ? ScramSHA256AuthenticationManager.DIGEST_NAME : ScramSHA1AuthenticationManager.DIGEST_NAME, + password + ); + + final AuthenticationResult secondResult = saslNegotiator.handleResponse(response); + assertEquals( + "Unexpected second result status", + ERROR, + secondResult.getStatus()); + assertNull("Unexpected second result challenge", secondResult.getChallenge()); + + } + + private static class SaslHelper + { + + private static final String GS2_HEADER = "n,,"; + private static final Charset ASCII = US_ASCII; + private static String _clientFirstMessageBare; + private static String _clientNonce; + private static byte[] _serverSignature; + + private static byte[] calculateClientProof( + final byte[] challenge, + String hmacName, + String digestName, + String userPassword + ) throws Exception + { + + final String serverFirstMessage = new String(challenge, ASCII); + final String[] parts = serverFirstMessage.split(","); + if (parts.length < 3) + { + fail("Server challenge '" + serverFirstMessage + "' cannot be parsed"); + } + else if (parts[0].startsWith("m=")) + { + fail("Server requires mandatory extension which is not supported: " + parts[0]); + } + else if (!parts[0].startsWith("r=")) + { + fail("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find nonce"); + } + final String nonce = parts[0].substring(2); + if (!nonce.startsWith(_clientNonce)) + { + fail("Server challenge did not use correct client nonce"); + } + if (!parts[1].startsWith("s=")) + { + fail("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find salt"); + } + final byte[] salt = Strings.decodeBase64(parts[1].substring(2)); + if (!parts[2].startsWith("i=")) + { + fail("Server challenge '" + serverFirstMessage + "' cannot be parsed, cannot find iteration count"); + } + final int _iterationCount = Integer.parseInt(parts[2].substring(2)); + if (_iterationCount <= 0) + { + fail("Iteration count " + _iterationCount + " is not a positive integer"); + } + final byte[] passwordBytes = saslPrep(userPassword).getBytes(StandardCharsets.UTF_8); + final byte[] saltedPassword = generateSaltedPassword(passwordBytes, hmacName, _iterationCount, salt); + + final String clientFinalMessageWithoutProof = + "c=" + Base64.getEncoder().encodeToString(GS2_HEADER.getBytes(ASCII)) + + ",r=" + nonce; + + final String authMessage = + _clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof; + final byte[] clientKey = computeHmac(saltedPassword, "Client Key", hmacName); + final byte[] storedKey = MessageDigest.getInstance(digestName).digest(clientKey); + final byte[] clientSignature = computeHmac(storedKey, authMessage, hmacName); + final byte[] clientProof = clientKey.clone(); + for (int i = 0; i < clientProof.length; i++) + { + clientProof[i] ^= clientSignature[i]; + } + final byte[] serverKey = computeHmac(saltedPassword, "Server Key", hmacName); + _serverSignature = computeHmac(serverKey, authMessage, hmacName); + final String finalMessageWithProof = clientFinalMessageWithoutProof + + ",p=" + Base64.getEncoder().encodeToString(clientProof); + return finalMessageWithProof.getBytes(); + } + + private static byte[] computeHmac(final byte[] key, final String string, String hmacName) + throws Exception + { + final Mac mac = createHmac(key, hmacName); + mac.update(string.getBytes(ASCII)); + return mac.doFinal(); + } + + private static byte[] generateSaltedPassword( + final byte[] passwordBytes, + String hmacName, + final int iterationCount, + final byte[] salt + ) throws Exception + { + final Mac mac = createHmac(passwordBytes, hmacName); + mac.update(salt); + mac.update(new byte[]{0, 0, 0, 1}); + final byte[] result = mac.doFinal(); + + byte[] previous = null; + for (int i = 1; i < iterationCount; i++) + { + mac.update(previous != null ? previous : result); + previous = mac.doFinal(); + for (int x = 0; x < result.length; x++) + { + result[x] ^= previous[x]; + } + } + + return result; + } + + private static Mac createHmac(final byte[] keyBytes, String hmacName) throws Exception + { + final SecretKeySpec key = new SecretKeySpec(keyBytes, hmacName); + final Mac mac = Mac.getInstance(hmacName); + mac.init(key); + return mac; + } + + private static String saslPrep(String name) + { + name = name.replace("=", "=3D"); + name = name.replace(",", "=2C"); + return name; + } + + private static byte[] createInitialResponse(final String userName) + { + _clientFirstMessageBare = "n=" + saslPrep(userName) + ",r=" + _clientNonce; + return (GS2_HEADER + _clientFirstMessageBare).getBytes(ASCII); + } + } + + private static final SaslSettings CRAM_MD_SASL_SETTINGS = new SaslSettings() + { + @Override + public String getLocalFQDN() + { + return "example.com"; + } + + @Override + public Principal getExternalPrincipal() + { + return null; + } + }; +} diff --git a/broker-plugins/management-http/src/main/java/resources/authenticationprovider/composite/add.html b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/composite/add.html new file mode 100644 index 0000000..b20ead9 --- /dev/null +++ b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/composite/add.html @@ -0,0 +1,28 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> +<div class="clear"> + <div class="formLabel-labelCell tableContainer-labelCell">Delegates:</div> + <div class="formLabel-controlCell tableContainer-valueCell"> + <div id="composite.delegates.container"/> + <input type="hidden" id="composite.delegates"/> + </div> + <div data-dojo-type="dijit/Tooltip" + data-dojo-props="connectId: ['composite.delegates'], + label: 'List of authentication provider delegate names'"/> +</div> diff --git a/broker-plugins/management-http/src/main/java/resources/authenticationprovider/composite/show.html b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/composite/show.html new file mode 100644 index 0000000..1f40b1f --- /dev/null +++ b/broker-plugins/management-http/src/main/java/resources/authenticationprovider/composite/show.html @@ -0,0 +1,26 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> +<div> + <div class="clear"> + <div class="formLabel-labelCell">Delegates:</div> + <div ><span class="delegates" ></span></div> + </div> + <div class="clear"></div> +</div> + diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/composite/add.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/composite/add.js new file mode 100644 index 0000000..5490ec6 --- /dev/null +++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/composite/add.js @@ -0,0 +1,159 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define([ + 'dojo/dom', + 'dojo/query', + 'dijit/registry', + 'qpid/common/util' +], function ( + dom, + query, + registry, + util +) +{ + return { + show: function (data) + { + util.parseHtmlIntoDiv(data.containerNode, 'authenticationprovider/composite/add.html', function () + { + if (!!data.data) + { + util.applyToWidgets(data.containerNode, + 'AuthenticationProvider', + 'Composite', + data.data, + data.metadata); + } + this.data = data; + const that = this; + this.management.load({type: 'authenticationprovider'}, {excludeInheritedContext: true, depth: 1}) + .then(function (data) + { + const allowed = ['MD5', 'Plain', 'SCRAM-SHA-1', 'SCRAM-SHA-256', 'SimpleLDAP']; + const names = data + .filter(authProvider => allowed.includes(authProvider.type)) + .map(authProvider => authProvider.name); + + const authProviderNames = that.data?.data?.delegates + ? that.data?.data?.delegates.slice() : []; + for (const name of names) + { + if (!authProviderNames.includes(name)) + { + authProviderNames.push(name); + } + } + const authProvidersMultiSelect = dom.byId('composite.delegates.container'); + authProvidersMultiSelect.style = 'border: 1px solid lightgray; padding: .5em; width: 14em;'; + + for (const name of authProviderNames) + { + const row = document.createElement('div'); + row.style = 'display: flex; justify-content: space-between; width: 15em; margin-bottom: .5em;'; + + const checkboxContainer = document.createElement('span'); + checkboxContainer.style = 'display: flex;'; + + const checkbox = document.createElement('input'); + checkbox.setAttribute('type', 'checkbox'); + checkbox.setAttribute('data-dojo-type', 'dijit/form/CheckBox'); + checkbox.setAttribute('id', name + '-checkbox'); + checkbox.setAttribute('name', name + '-checkbox'); + checkbox.style = 'cursor: pointer; margin-right: .5em;'; + checkboxContainer.appendChild(checkbox); + + const label = document.createElement('label'); + label.setAttribute('for', name + '-checkbox'); + label.style = 'cursor: pointer;'; + label.innerHTML = name; + checkboxContainer.appendChild(label); + + const buttonContainer = document.createElement('span'); + buttonContainer.style = 'display: flex; padding-right: .5em;'; + + const up = document.createElement('button'); + up.innerHTML = '▲'; + up.setAttribute('data-dojo-type', 'dijit/form/Button'); + up.setAttribute('type', 'button'); + up.addEventListener('click', (el) => moveUp(el.target)); + up.style = 'width: 1.5em; height: 1.5em; margin-right: .2em; display: flex; ' + + 'align-content: center; align-items: center; justify-content: center;'; + buttonContainer.appendChild(up); + + const down = document.createElement('button'); + down.innerHTML = '▼'; + down.setAttribute('data-dojo-type', 'dijit/form/Button'); + down.setAttribute('type', 'button'); + down.addEventListener('click',(el) => moveDown(el.target)); + down.style = 'width: 1.5em; height: 1.5em; margin-right: .2em; display: flex; ' + + 'align-content: center; align-items: center; justify-content: center;'; + buttonContainer.appendChild(down); + + row.appendChild(checkboxContainer); + row.appendChild(buttonContainer); + + authProvidersMultiSelect.appendChild(row); + } + + if (that.data?.data?.delegates) + { + for (const delegate of that.data.data.delegates) + { + dom.byId(delegate + '-checkbox').setAttribute('checked', 'checked'); + } + } + + }, util.xhrErrorHandler); + + function moveUp(el) { + const row = el.parentNode.parentNode; + if (row.previousElementSibling) + { + row.parentNode.insertBefore(row, row.previousElementSibling); + } + } + + function moveDown(el) { + const row = el.parentNode.parentNode; + if (row.nextElementSibling) + { + row.parentNode.insertBefore(row.nextElementSibling, row); + } + } + }); + }, + _preSubmit: function(formData) + { + let result = []; + const rows = document.getElementById('composite.delegates.container').children; + for (let i = 0; i < rows.length; i ++) + { + if (rows.item(i).firstChild?.firstChild?.checked) + { + result.push(rows.item(i).firstChild?.innerText); + } + } + formData.delegates = result; + } + }; +}); + diff --git a/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/composite/show.js b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/composite/show.js new file mode 100644 index 0000000..0d98104 --- /dev/null +++ b/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/composite/show.js @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["qpid/common/util", "dojo/domReady!"], function (util) +{ + + function CompositeAuthenticationProvider(data) + { + util.buildUI(data.containerNode, data.parent, "authenticationprovider/composite/show.html", ["delegates"], this); + } + + CompositeAuthenticationProvider.prototype.update = function (data) + { + if (this['delegates']) + { + this['delegates'].innerHTML = data?.delegates?.join(', '); + } + } + + return CompositeAuthenticationProvider; +}); diff --git a/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-Composite.xml b/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-Composite.xml new file mode 100644 index 0000000..ce09492 --- /dev/null +++ b/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers-Composite.xml @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--> + +<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="Java-Broker-Security-Composite-Provider"> + <title>Composite Provider</title> + + <para>Composite Provider uses existing username / password authentication providers allowing to perform authentication + against them in order defined. It can contains following authentication providers: + <itemizedlist> + <listitem><para><emphasis><link linkend="Java-Broker-Security-MD5-Provider">MD5 Provider</link></emphasis></para></listitem> + <listitem><para><emphasis><link linkend="Java-Broker-Security-Plain-Provider">Plain Provider</link></emphasis></para></listitem> + <listitem><para><emphasis><link linkend="Java-Broker-Security-ScramSha-Providers">SCRAM SHA Providers</link></emphasis></para></listitem> + <listitem><para><emphasis><link linkend="Java-Broker-Security-LDAP-Provider">Simple LDAP Providers</link></emphasis></para></listitem> + </itemizedlist> + </para> + + <para>When performing authentication, composite provider checks presence of a user with a given username in the first + delegate provider and if found, performs authentication. It should be considered, that in case of name collision + (when delegate providers contains users with same username but different passwords), it's not guaranteed that + authentication will succeed even with the correct credentials. Therefore username collision should be avoided, i.e. + each delegate provider should contain users with unique usernames.</para> + + <table pgwide="1"> + <title>SASL Mechanisms</title> + <tgroup cols="2"> + <colspec colnum="1" colname="authentication_provider" colwidth="1*"/> + <colspec colnum="2" colname="sasl_mechanisms" colwidth="1*"/> + <thead> + <row> + <entry>Authentication provider</entry> + <entry>SASL mechanisms</entry> + </row> + </thead> + <tbody> + <row xml:id="Java-Broker-Security-Composite-Provider-Delegate-SASL-Mechanisms-MD5"> + <entry><link linkend="Java-Broker-Security-MD5-Provider">MD5 Provider</link></entry> + <entry>PLAIN, CRAM-MD5-HASHED, CRAM-MD5-HEX</entry> + </row> + <row xml:id="Java-Broker-Security-Composite-Provider-Delegate-SASL-Mechanisms-Plain"> + <entry><link linkend="Java-Broker-Security-Plain-Provider">Plain</link></entry> + <entry>PLAIN, CRAM-MD5, SCRAM-SHA-1, SCRAM-SHA-256</entry> + </row> + <row xml:id="Java-Broker-Security-Composite-Provider-Delegate-SASL-Mechanisms-ScramSha"> + <entry><link linkend="Java-Broker-Security-ScramSha-Providers">SCRAM SHA Providers</link></entry> + <entry>PLAIN, SCRAM-SHA-1, SCRAM-SHA-256</entry> + </row> + <row xml:id="Java-Broker-Security-Composite-Provider-Delegate-SASL-Mechanisms-Simple-LDAP"> + <entry><link linkend="Java-Broker-Security-LDAP-Provider">Simple LDAP Providers</link></entry> + <entry>PLAIN</entry> + </row> + </tbody> + </tgroup> + </table> + + <para>Composite provider exposes intersection of SASL mechanism provided by its delegates.</para> + +</section> diff --git a/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers.xml b/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers.xml index fa733fb..3ca0e2c 100644 --- a/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers.xml +++ b/doc/java-broker/src/docbkx/security/Java-Broker-Security-Authentication-Providers.xml @@ -63,4 +63,5 @@ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="Java-Broker-Security-Authentication-Providers-PlainPasswordFile.xml"/> <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="Java-Broker-Security-Authentication-Providers-MD5.xml"/> <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="Java-Broker-Security-Authentication-Providers-Base64MD5PasswordFile.xml"/> + <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="Java-Broker-Security-Authentication-Providers-Composite.xml"/> </section> --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org