Java tutorial
/* * JBoss, Home of Professional Open Source. * Copyright 2017, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.additional.testsuite.jdkall.present.elytron.sasl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.Base64.Encoder; import java.util.List; import java.util.Properties; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.xml.bind.DatatypeConverter; import org.apache.commons.io.FileUtils; import org.apache.directory.server.annotations.CreateLdapServer; import org.apache.directory.server.annotations.CreateTransport; import org.apache.directory.server.core.annotations.AnnotationUtils; import org.apache.directory.server.core.annotations.CreateDS; import org.apache.directory.server.core.annotations.CreatePartition; import org.apache.directory.server.core.api.DirectoryService; import org.apache.directory.server.core.factory.DSAnnotationProcessor; import org.apache.directory.server.factory.ServerAnnotationProcessor; import org.apache.directory.server.ldap.LdapServer; import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.junit.Arquillian; import org.jboss.as.arquillian.api.ServerSetup; import org.jboss.as.network.NetworkUtils; import org.jboss.as.test.integration.ldap.InMemoryDirectoryServiceFactory; import org.jboss.as.test.integration.management.util.CLIWrapper; import org.jboss.as.test.integration.security.common.ManagedCreateLdapServer; import org.jboss.as.test.integration.security.common.Utils; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.wildfly.security.auth.client.AuthenticationConfiguration; import org.wildfly.security.auth.client.AuthenticationContext; import org.wildfly.security.auth.client.MatchRule; import org.wildfly.security.auth.permission.LoginPermission; import org.wildfly.security.sasl.SaslMechanismSelector; import org.jboss.additional.testsuite.jdkall.present.elytron.sasl.AbstractSaslTestBase.JmsSetup; import org.wildfly.test.security.common.AbstractElytronSetupTask; import org.wildfly.test.security.common.elytron.ConfigurableElement; import org.wildfly.test.security.common.elytron.ConstantPermissionMapper; import org.wildfly.test.security.common.elytron.ConstantRoleMapper; import org.wildfly.test.security.common.elytron.MechanismConfiguration; import org.wildfly.test.security.common.elytron.PermissionRef; import org.wildfly.test.security.common.elytron.SaslFilter; import org.wildfly.test.security.common.elytron.SimpleConfigurableSaslServerFactory; import org.wildfly.test.security.common.elytron.SimpleSaslAuthenticationFactory; import org.wildfly.test.security.common.elytron.SimpleSecurityDomain; import org.wildfly.test.security.common.elytron.SimpleSecurityDomain.SecurityDomainRealm; import org.wildfly.test.security.common.other.SimpleRemotingConnector; import org.wildfly.test.security.common.other.SimpleSocketBinding; import org.jboss.eap.additional.testsuite.annotations.EapAdditionalTestsuite; /** * Elytron OTP SASL mechanism tests which use Naming + JMS client. The server setup adds for each tested SASL configuration * a new native remoting port and client tests functionality against it. * * @author Josef Cacek */ @EapAdditionalTestsuite({ "modules/testcases/jdkAll/WildflyRelease-13.0.0.Final/elytron/src/main/java", "modules/testcases/jdkAll/Wildfly/elytron/src/main/java", "modules/testcases/jdkAll/Eap72x/elytron/src/main/java", "modules/testcases/jdkAll/Eap72x-Proposed/elytron/src/main/java", "modules/testcases/jdkAll/Eap7/elytron/src/main/java", "modules/testcases/jdkAll/Eap71x-Proposed/elytron/src/main/java", "modules/testcases/jdkAll/Eap71x/elytron/src/main/java" }) @RunWith(Arquillian.class) @RunAsClient @ServerSetup({ JmsSetup.class, OtpSaslTestCase.ServerSetup.class }) @Ignore("WFLY-8667") public class OtpSaslTestCase extends AbstractSaslTestBase { private static final String OTP = "OTP"; private static final String OTP_ALGORITHM = "otp-sha1"; private static final String OTP_PASSPHRASE = "This is a test."; private static final String OTP_SEED = "TeSt"; // https://www.ocf.berkeley.edu/~jjlin/jsotp/ // http://tomeko.net/online_tools/hex_to_base64.php?lang=en private static final byte[] OTP_HASH_99 = DatatypeConverter.parseHexBinary("87FEC7768B73CCF9"); private static final byte[] OTP_HASH_98 = DatatypeConverter.parseHexBinary("33D865A2BF9E5E76"); private static final int PORT_OTP = 10569; private static final int LDAP_PORT = 10389; private static final String HOST = Utils.getDefaultHost(false); private static final String HOST_FMT = NetworkUtils.formatPossibleIpv6Address(HOST); private static final String LDAP_URL = "ldap://" + HOST_FMT + ":" + LDAP_PORT; /** * Tests that client is able to use OTP SASL mechanism when server allows it. */ @Test public void testOtpAccess() throws Exception { assertSequenceAndHash(99, OTP_HASH_99); Runnable runAndExpectFail = () -> sendAndReceiveMsg(PORT_OTP, true); AuthenticationContext.empty() .with(MatchRule.ALL, AuthenticationConfiguration.empty().useDefaultProviders() .setSaslMechanismSelector(SaslMechanismSelector.fromString(OTP))) .run(runAndExpectFail); assertSequenceAndHash(99, OTP_HASH_99); AuthenticationContext.empty() .with(MatchRule.ALL, AuthenticationConfiguration.empty().useDefaultProviders() .setSaslMechanismSelector(SaslMechanismSelector.fromString(OTP)).useName("jduke") .usePassword("TeSt")) .run(runAndExpectFail); assertSequenceAndHash(99, OTP_HASH_99); AuthenticationContext.empty() .with(MatchRule.ALL, AuthenticationConfiguration.empty().useDefaultProviders() .setSaslMechanismSelector(SaslMechanismSelector.fromString(OTP)).useName("jduke") .usePassword(OTP_PASSPHRASE)) .run(() -> sendAndReceiveMsg(PORT_OTP, false)); assertSequenceAndHash(98, OTP_HASH_98); } /** * Check correct user attribute values in the LDAP when using OTP algorithm. */ private void assertSequenceAndHash(Integer expectedSequence, byte[] expectedHash) throws NamingException { final Properties env = new Properties(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, LDAP_URL); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system"); env.put(Context.SECURITY_CREDENTIALS, "secret"); final LdapContext ctx = new InitialLdapContext(env, null); NamingEnumeration<?> namingEnum = ctx.search("dc=wildfly,dc=org", new BasicAttributes("cn", "jduke")); if (namingEnum.hasMore()) { SearchResult sr = (SearchResult) namingEnum.next(); Attributes attrs = sr.getAttributes(); assertEquals("Unexpected sequence number in LDAP attribute", expectedSequence, new Integer(attrs.get("telephoneNumber").get().toString())); assertEquals("Unexpected hash value in LDAP attribute", Base64.getEncoder().encodeToString(expectedHash), attrs.get("title").get().toString()); } else { fail("User not found in LDAP"); } namingEnum.close(); ctx.close(); } /** * Setup task which configures Elytron security domains and remoting connectors for this test. */ public static class ServerSetup extends AbstractElytronSetupTask { @Override protected ConfigurableElement[] getConfigurableElements() { List<ConfigurableElement> elements = new ArrayList<>(); elements.add(ConstantPermissionMapper.builder().withName(NAME) .withPermissions(PermissionRef.fromPermission(new LoginPermission())).build()); elements.add(ConstantRoleMapper.builder().withName(NAME).withRoles("guest").build()); elements.add(SimpleSecurityDomain.builder().withName(NAME).withDefaultRealm("ApplicationRealm") .withRoleMapper(NAME).withPermissionMapper(NAME) .withRealms(SecurityDomainRealm.builder().withRealm("ApplicationRealm").build()).build()); elements.add(new OtpLdapConf()); elements.add(SimpleSecurityDomain.builder().withName(OTP).withDefaultRealm(OTP) .withPermissionMapper(NAME) .withRealms( SecurityDomainRealm.builder().withRealm(OTP).withRoleDecoder("groups-to-roles").build()) .build()); elements.add( SimpleConfigurableSaslServerFactory.builder().withName(OTP).withSaslServerFactory("elytron") .addFilter(SaslFilter.builder().withPatternFilter(OTP).build()).build()); elements.add(SimpleSaslAuthenticationFactory.builder().withName(OTP).withSaslServerFactory(OTP) .withSecurityDomain(OTP) .addMechanismConfiguration(MechanismConfiguration.builder().withMechanismName(OTP).build()) .build()); elements.add(SimpleSocketBinding.builder().withName(OTP).withPort(PORT_OTP).build()); elements.add(SimpleRemotingConnector.builder().withName(OTP).withSocketBinding(OTP) .withSaslAuthenticationFactory(OTP).build()); return elements.toArray(new ConfigurableElement[elements.size()]); } /** * LDAP configurable element - set's and starts LDAP instance and configures ldap-realm in the Elytron. * The LDAP realm is used as modifiable realm for testing OTP SASL mechanism. */ //@formatter:off @CreateDS(name = "WildFlyDS", factory = InMemoryDirectoryServiceFactory.class, partitions = @CreatePartition(name = "wildfly", suffix = "dc=wildfly,dc=org"), allowAnonAccess = true) @CreateLdapServer(transports = @CreateTransport(protocol = "LDAP", address = "localhost", port = LDAP_PORT), allowAnonymousAccess = true) //@formatter:on private static class OtpLdapConf implements ConfigurableElement { private static DirectoryService directoryService; private static LdapServer ldapServer; @Override public void create(CLIWrapper cli) throws Exception { Encoder b64e = Base64.getEncoder(); directoryService = DSAnnotationProcessor.getDirectoryService(); DSAnnotationProcessor.injectEntries(directoryService, "dn: dc=wildfly,dc=org\n" // + "dc: jboss\n" // + "objectClass: top\n" // + "objectClass: domain\n" // + "\n" // + "dn: cn=jduke,dc=wildfly,dc=org\n" // + "objectclass: top\n" // + "objectclass: person\n" // + "objectclass: organizationalPerson\n" // + "cn: jduke\n" // + "street: guest\n" // role ;) + "sn: " + OTP_ALGORITHM + "\n" // algorithm + "title: " + b64e.encodeToString(OTP_HASH_99) + "\n" // stored hash + "description: " + b64e.encodeToString(OTP_SEED.getBytes(StandardCharsets.US_ASCII)) + "\n" // seed + "telephoneNumber: 99\n" // sequence ); final ManagedCreateLdapServer createLdapServer = new ManagedCreateLdapServer( (CreateLdapServer) AnnotationUtils.getInstance(CreateLdapServer.class)); Utils.fixApacheDSTransportAddress(createLdapServer, Utils.getSecondaryTestAddress(null, false)); ldapServer = ServerAnnotationProcessor.instantiateLdapServer(createLdapServer, directoryService); ldapServer.start(); cli.sendLine(String.format( "/subsystem=elytron/dir-context=%s:add(url=\"%s\",principal=\"uid=admin,ou=system\",credential-reference={clear-text=secret})", OTP, LDAP_URL)); cli.sendLine(String.format( "/subsystem=elytron/ldap-realm=%s:add(dir-context=%s,identity-mapping={rdn-identifier=cn,search-base-dn=\"dc=wildfly,dc=org\"," + "otp-credential-mapper={algorithm-from=sn, hash-from=title, seed-from=description, sequence-from=telephoneNumber}," + "attribute-mapping=[{from=street,to=groups}]})", OTP, OTP)); } @Override public void remove(CLIWrapper cli) throws Exception { // cli.sendLine(String.format("/subsystem=elytron/security-domain=%s:remove()", OTP)); cli.sendLine(String.format("/subsystem=elytron/ldap-realm=%s:remove()", OTP)); cli.sendLine(String.format("/subsystem=elytron/dir-context=%s:remove()", OTP)); ldapServer.stop(); directoryService.shutdown(); FileUtils.deleteDirectory(directoryService.getInstanceLayout().getInstanceDirectory()); } @Override public String getName() { return "ldap-configuration"; } } } }