1 /*
2  * Copyright (c) 2015, 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 com.sun.security.auth.UnixPrincipal;
25 
26 import javax.security.auth.Subject;
27 import javax.security.auth.callback.*;
28 import javax.security.auth.login.FailedLoginException;
29 import javax.security.auth.login.LoginContext;
30 import javax.security.auth.login.LoginException;
31 import javax.security.auth.spi.LoginModule;
32 import java.io.IOException;
33 import java.security.Principal;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Map;
37 
38 /*
39  * @test
40  * @bug 8050460
41  * @summary Test checks that proper methods associated with login/logout process
42  * of LoginContext are called for different configurations and circumstances.
43  * @modules jdk.security.auth
44  *
45  * @run main/othervm  LCTest EmptyModuleConfig false
46  * @run main/othervm  LCTest IncorrectName false
47  * @run main/othervm  LCTest AbortRequisite false abort
48  * @run main/othervm  LCTest AbortSufficient false abort
49  * @run main/othervm  LCTest AbortRequired false abort
50  * @run main/othervm  LCTest LogoutRequisite false logout
51  * @run main/othervm  LCTest LogoutSufficient true logout
52  * @run main/othervm  LCTest LogoutRequired false logout
53  * @run main/othervm  LCTest LoginRequisite false login
54  * @run main/othervm  LCTest LoginSufficient true login
55  * @run main/othervm  LCTest LoginRequired false login
56  */
57 
58 public class LCTest {
59 
60     private static final String USER_NAME = "testUser";
61     private static final String PASSWORD = "testPassword";
62     private static final List<String> loggedActions = new ArrayList<>();
63 
64     static {
65         System.setProperty("java.security.auth.login.config",
66                 System.getProperty("test.src")
67                         + System.getProperty("file.separator")
68                         + "LCTest.jaas.config");
69     }
70 
main(String[] args)71     public static void main(String[] args) {
72         if (args.length < 2) {
73             throw new RuntimeException("Incorrect test params");
74         }
75         String nameOfContext = args[0];
76         boolean isPositive = Boolean.parseBoolean(args[1]);
77         String actionName = null;
78         if (args.length == 3) {
79             actionName = args[2];
80         }
81         try {
82             LoginContext lc = new LoginContext(nameOfContext,
83                     new MyCallbackHandler());
84             lc.login();
85             checkPrincipal(lc, true);
86             lc.logout();
87             checkPrincipal(lc, false);
88             if (!isPositive) {
89                 throw new RuntimeException("Test failed. Exception expected.");
90             }
91         } catch (LoginException le) {
92             if (isPositive) {
93                 throw new RuntimeException("Test failed. Unexpected " +
94                         "exception", le);
95             }
96             System.out.println("Expected exception: "
97                     + le.getMessage());
98         }
99         checkActions(actionName);
100         System.out.println("Test passed.");
101     }
102 
103     /*
104      * Log action from login modules
105      */
logAction(String actionName)106     private static void logAction(String actionName) {
107         loggedActions.add(actionName);
108     }
109 
110     /*
111      * Check if logged actions are as expected. We always expected 3 actions
112      * if any.
113      */
checkActions(String actionName)114     private static void checkActions(String actionName) {
115         if (actionName == null) {
116             if (loggedActions.size() != 0) {
117                 throw new RuntimeException("No logged actions expected");
118             }
119         } else {
120             int loggedActionsFound = 0;
121             System.out.println("Logged actions : " + loggedActions);
122             for (String s : loggedActions) {
123                 if (s.equals(actionName)) {
124                     loggedActionsFound++;
125                 }
126             }
127             if (loggedActionsFound != 3) {
128                 throw new RuntimeException("Incorrect number of actions " +
129                         actionName + " : " + loggedActionsFound);
130             }
131         }
132     }
133 
134     /*
135      * Check context for principal of the test user.
136      */
checkPrincipal(LoginContext loginContext, boolean principalShouldExist)137     private static void checkPrincipal(LoginContext loginContext, boolean
138             principalShouldExist) {
139         if (!principalShouldExist) {
140             if (loginContext.getSubject().getPrincipals().size() != 0) {
141                 throw new RuntimeException("Test failed. Principal was not " +
142                         "cleared.");
143             }
144         } else {
145             for (Principal p : loginContext.getSubject().getPrincipals()) {
146                 if (p instanceof UnixPrincipal &&
147                         USER_NAME.equals(p.getName())) {
148                     //Proper principal was found, return.
149                     return;
150                 }
151             }
152             throw new RuntimeException("Test failed. UnixPrincipal "
153                     + USER_NAME + " expected.");
154         }
155     }
156 
157     private static class MyCallbackHandler implements CallbackHandler {
158 
159         @Override
handle(Callback[] callbacks)160         public void handle(Callback[] callbacks) throws IOException,
161                 UnsupportedCallbackException {
162             for (Callback callback : callbacks) {
163                 if (callback instanceof NameCallback) {
164                     ((NameCallback) callback).setName(USER_NAME);
165                 } else if (callback instanceof PasswordCallback) {
166                     ((PasswordCallback) callback).setPassword(
167                             PASSWORD.toCharArray());
168                 } else {
169                     throw new UnsupportedCallbackException(callback);
170                 }
171             }
172         }
173     }
174 
175     /* -------------------------------------------------------------------------
176      * Test login modules
177      * -------------------------------------------------------------------------
178      */
179 
180     /*
181      * Login module that should pass through all phases.
182      */
183     public static class LoginModuleAllPass extends LoginModuleBase {
184 
185     }
186 
187     /*
188      * Login module that throws Exception in abort method.
189      */
190     public static class LoginModuleWithAbortException extends LoginModuleBase {
191 
192         @Override
abort()193         public boolean abort() throws LoginException {
194             super.abort();
195             throw new LoginException("Abort failed!");
196         }
197     }
198 
199     /*
200      * Login module that throws Exception in login method.
201      */
202     public static class LoginModuleWithLoginException extends LoginModuleBase {
203 
204         @Override
login()205         public boolean login() throws LoginException {
206             super.login();
207             throw new FailedLoginException("Login failed!");
208         }
209     }
210 
211     /*
212      * Login module that throws Exception in logout method.
213      */
214     public static class LoginModuleWithLogoutException extends LoginModuleBase {
215 
216         @Override
logout()217         public boolean logout() throws LoginException {
218             super.logout();
219             throw new FailedLoginException("Logout failed!");
220         }
221     }
222 
223     /*
224      * Base class for login modules
225      */
226     public static abstract class LoginModuleBase implements LoginModule {
227         // initial state
228         private Subject subject;
229         private CallbackHandler callbackHandler;
230         private Map sharedState;
231         private Map options;
232         private UnixPrincipal userPrincipal;
233 
234         // username and password
235         private String username;
236         private String password;
237 
238         // the authentication status
239         private boolean succeeded = false;
240         private boolean commitSucceeded = false;
241 
242         @Override
initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options)243         public void initialize(Subject subject, CallbackHandler callbackHandler,
244                                Map<String, ?> sharedState, Map<String, ?> options) {
245 
246             this.subject = subject;
247             this.callbackHandler = callbackHandler;
248             this.sharedState = sharedState;
249             this.options = options;
250             System.out.println("Login module initialized.");
251         }
252 
253         /*
254          * Authenticate the user by prompting for a username and password.
255          */
256         @Override
login()257         public boolean login() throws LoginException {
258             LCTest.logAction("login");
259             if (callbackHandler == null) {
260                 throw new LoginException("No CallbackHandler available");
261             }
262 
263             Callback[] callbacks = new Callback[2];
264             callbacks[0] = new NameCallback("Username: ");
265             callbacks[1] = new PasswordCallback("Password: ", false);
266 
267             try {
268                 callbackHandler.handle(callbacks);
269                 username = ((NameCallback) callbacks[0]).getName();
270                 password = new String(((PasswordCallback) callbacks[1])
271                         .getPassword());
272                 if (username.equals(LCTest.USER_NAME) &&
273                         password.equals(LCTest.PASSWORD)) {
274                     succeeded = true;
275                     return true;
276                 }
277                 throw new FailedLoginException("Incorrect username/password!");
278             } catch (IOException | UnsupportedCallbackException e) {
279                 throw new LoginException("Login failed: " + e.getMessage());
280             }
281         }
282 
283         @Override
commit()284         public boolean commit() throws LoginException {
285             LCTest.logAction("commit");
286             if (succeeded == false) {
287                 return false;
288             }
289             userPrincipal = new UnixPrincipal(username);
290             final Subject s = subject;
291             final UnixPrincipal up = userPrincipal;
292             java.security.AccessController.doPrivileged
293                     ((java.security.PrivilegedAction) () -> {
294                         if (!s.getPrincipals().contains(up)) {
295                             s.getPrincipals().add(up);
296                         }
297                         return null;
298                     });
299             password = null;
300             commitSucceeded = true;
301             return true;
302         }
303 
304         @Override
abort()305         public boolean abort() throws LoginException {
306             LCTest.logAction("abort");
307             if (succeeded == false) {
308                 return false;
309             }
310             clearState();
311             return true;
312         }
313 
314         @Override
logout()315         public boolean logout() throws LoginException {
316             LCTest.logAction("logout");
317             clearState();
318             return true;
319         }
320 
clearState()321         private void clearState() {
322             if (commitSucceeded) {
323                 final Subject s = subject;
324                 final UnixPrincipal up = userPrincipal;
325                 java.security.AccessController.doPrivileged
326                         ((java.security.PrivilegedAction) () -> {
327                             s.getPrincipals().remove(up);
328                             return null;
329                         });
330             }
331             username = null;
332             password = null;
333             userPrincipal = null;
334         }
335     }
336 
337 }
338