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
24import com.sun.security.auth.UnixPrincipal;
25
26import javax.security.auth.Subject;
27import javax.security.auth.callback.*;
28import javax.security.auth.login.FailedLoginException;
29import javax.security.auth.login.LoginContext;
30import javax.security.auth.login.LoginException;
31import javax.security.auth.spi.LoginModule;
32import java.io.IOException;
33import java.security.Principal;
34import java.util.ArrayList;
35import java.util.List;
36import 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
58public 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
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     */
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     */
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     */
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
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
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
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
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
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
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
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
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
315        public boolean logout() throws LoginException {
316            LCTest.logAction("logout");
317            clearState();
318            return true;
319        }
320
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