1 /*
  2  * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 import java.nio.file.Files;
 25 import java.nio.file.Path;
 26 import java.nio.file.Paths;
 27 import java.nio.file.StandardCopyOption;
 28 import java.util.Collections;
 29 import java.util.LinkedList;
 30 import java.util.List;
 31 import java.util.Arrays;
 32 import java.io.File;
 33 import java.io.OutputStream;
 34 import java.lang.module.ModuleDescriptor;
 35 import java.lang.module.ModuleDescriptor.Builder;
 36 import java.util.stream.Stream;
 37 import jdk.test.lib.process.ProcessTools;
 38 import jdk.test.lib.process.OutputAnalyzer;
 39 import jdk.test.lib.util.JarUtils;
 40 import jdk.test.lib.util.ModuleInfoWriter;
 41 
 42 /*
 43  * @test
 44  * @bug 8078813 8183310
 45  * @summary Test custom JAAS login module with all possible modular option.
 46  * @modules java.base/jdk.internal.module
 47  * @library /test/lib
 48  * @build jdk.test.lib.util.JarUtils jdk.test.lib.util.ModuleInfoWriter
 49  * @build TestLoginModule JaasClient
 50  * @run main JaasModularClientTest false
 51  * @run main JaasModularClientTest true
 52  */
 53 public class JaasModularClientTest {
 54 
 55     private static final Path SRC = Paths.get(System.getProperty("test.src"));
 56     private static final Path TEST_CLASSES
 57             = Paths.get(System.getProperty("test.classes"));
 58     private static final Path ARTIFACT_DIR = Paths.get("jars");
 59     private static final String PS = File.pathSeparator;
 60     private static final String L_TYPE = "login.TestLoginModule";
 61     private static final String C_TYPE = "client.JaasClient";
 62 
 63     /**
 64      * Here is the naming convention followed.
 65      * l.jar    - Unnamed login module jar.
 66      * ml.jar   - Modular login module jar.
 67      * msl.jar  - Modular login module jar provides login module service
 68      *            through module-info
 69      * c.jar    - Unnamed client jar.
 70      * mc.jar   - Modular client jar.
 71      * mcs.jar  - Modular client jar uses login module service through
 72      *            module-info.
 73      * amc.jar  - Modular client used for automatic login module jar.
 74      * amcs.jar - Modular client used for automatic login module jar and uses
 75      *            login module service through module-info.
 76      */
 77     private static final Path L_JAR = artifact("l.jar");
 78     private static final Path ML_JAR = artifact("ml.jar");
 79     private static final Path MSL_JAR = artifact("msl.jar");
 80     private static final Path C_JAR = artifact("c.jar");
 81     private static final Path MC_JAR = artifact("mc.jar");
 82     private static final Path MCS_JAR = artifact("mcs.jar");
 83     private static final Path AMC_JAR = artifact("amc.jar");
 84     private static final Path AMCS_JAR = artifact("amcs.jar");
 85 
 86     private final String unnL;
 87     private final String modL;
 88     private final String unnC;
 89     private final String modC;
 90     private final String autoMC;
 91     // Common set of VM arguments used in all test cases
 92     private final List<String> commonArgs;
 93 
 94     public JaasModularClientTest(boolean service) {
 95 
 96         System.out.printf("%n*** Login Module defined as service in "
 97                 + "module-info: %s ***%n%n", service);
 98         List<String> argList = new LinkedList<>();
 99         argList.add("-Djava.security.auth.login.config="
100                 + toAbsPath(SRC.resolve("jaas.conf")));
101         commonArgs = Collections.unmodifiableList(argList);
102 
103         // Based on Testcase, select unnamed/modular jar files to use.
104         unnL = toAbsPath(L_JAR);
105         modL = toAbsPath(service ? MSL_JAR : ML_JAR);
106         unnC = toAbsPath(C_JAR);
107         modC = toAbsPath(service ? MCS_JAR : MC_JAR);
108         autoMC = toAbsPath(service ? AMCS_JAR : AMC_JAR);
109     }
110 
111     /*
112      * Test cases are based on the following logic,
113      * for (definedAs : {"Service in module-info", "Class Type"}) {
114      *     for (clientType : {"NAMED", "AUTOMATIC", "UNNAMED"}) {
115      *         for (loginModuleType : {"NAMED", "AUTOMATIC", "UNNAMED"}) {
116      *             Create and run java command for each possible case
117      *         }
118      *     }
119      * }
120      */
121     public static void main(String[] args) throws Exception {
122 
123         // Generates unnamed and modular jars.
124         setUp();
125         boolean service = Boolean.valueOf(args[0]);
126         JaasModularClientTest test = new JaasModularClientTest(service);
127         test.process();
128     }
129 
130     private void process() throws Exception {
131 
132         // Case: NAMED-NAMED, NAMED-AUTOMATIC, NAMED-UNNAMED
133         System.out.println("Case: Modular Client and Modular Login module.");
134         execute(String.format("--module-path %s%s%s -m mc/%s",
135                 modC, PS, modL, C_TYPE));
136         System.out.println("Case: Modular Client and automatic Login module.");
137         execute(String.format("--module-path %s%s%s --add-modules=l -m mc/%s",
138                 autoMC, PS, unnL, C_TYPE));
139         System.out.println("Case: Modular Client and unnamed Login module.");
140         execute(String.format("--module-path %s -cp %s -m mc/%s", autoMC,
141                 unnL, C_TYPE));
142 
143         // Case: AUTOMATIC-NAMED, AUTOMATIC-AUTOMATIC, AUTOMATIC-UNNAMED
144         System.out.println("Case: Automatic Client and modular Login module.");
145         execute(String.format("--module-path %s%s%s --add-modules=ml -m c/%s",
146                 unnC, PS, modL, C_TYPE));
147         System.out.println("Case: Automatic Client and automatic Login module");
148         execute(String.format("--module-path %s%s%s --add-modules=l -m c/%s",
149                 unnC, PS, unnL, C_TYPE));
150         System.out.println("Case: Automatic Client and unnamed Login module.");
151         execute(String.format("--module-path %s -cp %s -m c/%s", unnC,
152                 unnL, C_TYPE));
153 
154         // Case: UNNAMED-NAMED, UNNAMED-AUTOMATIC, UNNAMED-UNNAMED
155         System.out.println("Case: Unnamed Client and modular Login module.");
156         execute(String.format("-cp %s --module-path %s --add-modules=ml %s",
157                 unnC, modL, C_TYPE));
158         System.out.println("Case: Unnamed Client and automatic Login module.");
159         execute(String.format("-cp %s --module-path %s --add-modules=l %s",
160                 unnC, unnL, C_TYPE));
161         System.out.println("Case: Unnamed Client and unnamed Login module.");
162         execute(String.format("-cp %s%s%s %s", unnC, PS, unnL, C_TYPE));
163 
164         // Case: unnamed jars in --module-path and modular jars in -cp.
165         System.out.println(
166                 "Case: Unnamed Client and Login module from modulepath.");
167         execute(String.format("--module-path %s%s%s --add-modules=l -m c/%s",
168                 unnC, PS, unnL, C_TYPE));
169         System.out.println(
170                 "Case: Modular Client and Login module in classpath.");
171         execute(String.format("-cp %s%s%s %s", modC, PS, modL, C_TYPE));
172     }
173 
174     /**
175      * Execute with command arguments and process the result.
176      */
177     private void execute(String args) throws Exception {
178 
179         String[] safeArgs = Stream.concat(commonArgs.stream(),
180                 Stream.of(args.split("\\s+"))).filter(s -> {
181             if (s.contains(" ")) {
182                 throw new RuntimeException("No spaces in args");
183             }
184             return !s.isEmpty();
185         }).toArray(String[]::new);
186         OutputAnalyzer out = ProcessTools.executeTestJava(safeArgs);
187         // Handle response.
188         if (out.getExitValue() != 0) {
189             System.out.printf("OUTPUT: %s", out.getOutput());
190             throw new RuntimeException("FAIL: Unknown failure occured.");
191         } else {
192             System.out.println("Passed.");
193         }
194     }
195 
196     /**
197      * Creates Unnamed/modular jar files for TestClient and TestClassLoader.
198      */
199     private static void setUp() throws Exception {
200 
201         if (ARTIFACT_DIR.toFile().exists()) {
202             System.out.println("Skipping setup: Artifacts already exists.");
203             return;
204         }
205         // Generate unnamed login module jar file.
206         JarUtils.createJarFile(L_JAR, TEST_CLASSES,
207                 "login/TestLoginModule.class");
208         // Generate unnamed client jar.
209         JarUtils.createJarFile(C_JAR, TEST_CLASSES, "client/JaasClient.class",
210                 "client/JaasClient$MyCallbackHandler.class");
211 
212         Builder mBuilder = ModuleDescriptor.newModule("ml")
213                 .requires("jdk.security.auth");
214         // Modular jar exports package to let the login module type accessible.
215         generateJar(L_JAR, ML_JAR, mBuilder.exports("login").build());
216 
217         mBuilder = ModuleDescriptor.newModule("ml")
218                 .requires("jdk.security.auth")
219                 .provides("javax.security.auth.spi.LoginModule",
220                         Arrays.asList(L_TYPE));
221         // Modular login module as Service in module-info does not need to
222         // export service package.
223         generateJar(L_JAR, MSL_JAR, mBuilder.build());
224 
225         mBuilder = ModuleDescriptor.newModule("mc").exports("client")
226                 .requires("jdk.security.auth");
227         // Generate modular client jar to use automatic login module jar.
228         generateJar(C_JAR, AMC_JAR, mBuilder.build());
229         // Generate modular client jar to use modular login module jar.
230         generateJar(C_JAR, MC_JAR, mBuilder.requires("ml").build());
231 
232         mBuilder = ModuleDescriptor.newModule("mc").exports("client")
233                 .requires("jdk.security.auth")
234                 .uses("javax.security.auth.spi.LoginModule");
235         // Generate modular client jar to use automatic login module service.
236         generateJar(C_JAR, AMCS_JAR, mBuilder.build());
237         // Generate modular client jar using modular login module service.
238         generateJar(C_JAR, MCS_JAR, mBuilder.requires("ml").build());
239     }
240 
241     /**
242      * Update Unnamed jars and include module descriptor files.
243      */
244     private static void generateJar(Path sjar, Path djar,
245             ModuleDescriptor mDesc) throws Exception {
246 
247         Files.copy(sjar, djar, StandardCopyOption.REPLACE_EXISTING);
248         Path dir = Files.createTempDirectory("tmp");
249         if (mDesc != null) {
250             Path mi = dir.resolve("module-info.class");
251             try (OutputStream out = Files.newOutputStream(mi)) {
252                 ModuleInfoWriter.write(mDesc, out);
253             }
254             System.out.format("Added 'module-info.class' in '%s'%n", djar);
255         }
256         JarUtils.updateJarFile(djar, dir);
257     }
258 
259     /**
260      * Look for file path in generated jars.
261      */
262     private static Path artifact(String file) {
263         return ARTIFACT_DIR.resolve(file);
264     }
265 
266     /**
267      * Convert to absolute file path.
268      */
269     private static String toAbsPath(Path path) {
270         return path.toFile().getAbsolutePath();
271     }
272 }