-
Notifications
You must be signed in to change notification settings - Fork 138
/
BasePasswordLoginModule.java
428 lines (376 loc) · 14.2 KB
/
BasePasswordLoginModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
* Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.enterprise.security;
import com.sun.enterprise.security.auth.login.LoginCallbackHandler;
import com.sun.enterprise.security.auth.login.common.PasswordCredential;
import com.sun.enterprise.security.auth.realm.Realm;
import com.sun.enterprise.util.i18n.StringManager;
import java.security.Principal;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.glassfish.internal.api.Globals;
import org.glassfish.security.common.Group;
import org.glassfish.security.common.UserNameAndPassword;
import org.glassfish.security.common.UserPrincipal;
/**
* Abstract base class for password-based login modules.
*
* <P>
* Most login modules receive a username and password from the client (possibly through HTTP BASIC auth, or FORM, or other
* mechanism) and then make (or delegate) an authentication decision based on this data. This class provides common methods for
* such password-based login modules.
*
* <P>
* Subclasses need to implement the authenticateUser() method and later call commitUserAuthentication().
*/
public abstract class BasePasswordLoginModule implements LoginModule {
// The _subject, _sharedState and _options satisfy LoginModule and are
// shared across sub-classes
protected Subject _subject;
protected Map _sharedState;
protected Map _options;
protected String _username;
@Deprecated
protected String _password;
protected char[] _passwd;
protected Realm _currentRealm;
// the authentication status
protected boolean _succeeded = false;
protected boolean _commitSucceeded = false;
protected UserPrincipal _userPrincipal;
protected String[] _groupsList = null;
protected static final Logger _logger = SecurityLoggerInfo.getLogger();
protected final static StringManager sm = StringManager.getManager(LoginCallbackHandler.class);
private LoginModule userDefinedLoginModule;
/**
* Initialize this login module.
*
* @param subject - the Subject to be authenticated.
* @param callbackHandler - a CallbackHandler for obtaining the subject username and password.
* @param sharedState - state shared with other configured LoginModules.
* @param options - options specified in the login Configuration for this particular LoginModule.
*
*/
@Override
final public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
_subject = subject;
_sharedState = sharedState;
_options = options;
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "Login module initialized: " + this.getClass().toString());
}
}
/**
* Perform login.
*
* <P>
* The callback handler is used to obtain authentication info for the subject and a login is attempted. This PasswordLoginModule
* expects to find a PasswordCredential in the private credentials of the Subject. If not present the login fails. The callback
* handler is ignored as it is not really relevant on the server side. Finally, the authenticateUser() method is invoked.
*
* @returns true if login succeeds, otherwise an exception is thrown.
* @throws LoginException Thrown if login failed, or on other problems.
*
*/
@Override
final public boolean login() throws LoginException {
//Extract the username and password
extractCredentials();
// Delegate the actual authentication to subclass.
authenticateUser();
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JAAS login complete.");
}
return true;
}
/**
* Commit the authentication.
* <P>
* Commit is called after all necessary login modules have succeeded. It adds (if not present) a
* {@link UserNameAndPassword} principal and a LocalCredentials public credential to the Subject.
*
* @throws LoginException If commit fails.
*/
@Override
public boolean commit() throws LoginException {
if (_succeeded == false) {
return false;
}
// Add a Principal (authenticated identity) to the Subject
String realm_name = _currentRealm.getName();
PrincipalGroupFactory factory = Globals.getDefaultHabitat().getService(PrincipalGroupFactory.class);
if (factory == null) {
_userPrincipal = new UserNameAndPassword(getUsername());
} else {
_userPrincipal = factory.getPrincipalInstance(getUsername(), realm_name);
}
Set<Principal> principalSet = _subject.getPrincipals();
if (!principalSet.contains(_userPrincipal)) {
principalSet.add(_userPrincipal);
}
/* populate the group in the subject and clean out the slate at the same
* time
*/
for (int i = 0; i < _groupsList.length; i++) {
if (_groupsList[i] != null) {
Group g;
if (factory != null) {
g = factory.getGroupInstance(_groupsList[i], realm_name);
} else {
g = new Group(_groupsList[i]);
}
if (!principalSet.contains(g)) {
principalSet.add(g);
}
// cleaning the slate
_groupsList[i] = null;
}
}
// In any case, clean out state.
_groupsList = null;
setUsername(null);
setPassword(null);
setPasswordChar(null);
_commitSucceeded = true;
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JAAS authentication committed.");
}
return true;
}
/**
* Abort the authentication process.
*
*/
@Override
final public boolean abort() throws LoginException {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JAAS authentication aborted.");
}
if (_succeeded == false) {
return false;
} else if (_succeeded == true && _commitSucceeded == false) {
// login succeeded but overall authentication failed
_succeeded = false;
setUsername(null);
setPassword(null);
setPasswordChar(null);
_userPrincipal = null;
for (int i = 0; i < _groupsList.length; i++) {
_groupsList[i] = null;
}
_groupsList = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Log out the subject.
*
*/
@Override
final public boolean logout() throws LoginException {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "JAAS logout for: " + _subject.toString());
}
_subject.getPrincipals().clear();
_subject.getPublicCredentials().clear();
_subject.getPrivateCredentials().clear();
_succeeded = false;
_commitSucceeded = false;
setUsername(null);
setPassword(null);
_userPrincipal = null;
if (_groupsList != null) {
for (int i = 0; i < _groupsList.length; i++) {
_groupsList[i] = null;
}
_groupsList = null;
}
return true;
}
/**
*
* <P>
* This is a convenience method which can be used by subclasses
*
* <P>
* Note that this method is called after the authentication has succeeded. If authentication failed do not call this method.
*
* Global instance field succeeded is set to true by this method.
*
* @param groups String array of group memberships for user (could be empty).
*/
public final void commitUserAuthentication(final String[] groups) {
//Copy the groups into a new array before storing it in the instance
String[] groupsListCopy = (groups == null) ? null : Arrays.copyOf(groups, groups.length);
_groupsList = groupsListCopy;
_succeeded = true;
}
/**
* @return the subject being authenticated. use case: A custom login module could overwrite commit() method, and call
* getSubject() to get subject being authenticated inside its commit(). Custom principal then can be added to subject. By doing
* this,custom principal will be stored in calling thread's security context and participate in following Appserver's
* authorization.
*
*/
public Subject getSubject() {
return _subject;
}
/**
* Method to extract container-provided username and password
*
* @throws javax.security.auth.login.LoginException
*/
final public void extractCredentials() throws LoginException {
if (_subject == null) {
String msg = sm.getString("pwdlm.noinfo");
_logger.log(Level.SEVERE, msg);
throw new LoginException(msg);
}
PasswordCredential pwdCred = null;
try {
Iterator i = _subject.getPrivateCredentials().iterator();
while (i.hasNext() && pwdCred == null) {
Object privCred = i.next();
if (privCred instanceof PasswordCredential) {
pwdCred = (PasswordCredential) privCred;
}
}
} catch (Exception e) {
_logger.log(Level.WARNING, SecurityLoggerInfo.privateSubjectCredentialsError, e.toString());
}
if (pwdCred == null) {
_logger.log(Level.SEVERE, SecurityLoggerInfo.noPwdCredentialProvidedError);
String msg = sm.getString("pwdlm.nocreds");
throw new LoginException(msg);
}
// Need to obtain the requested realm to get parameters.
String realm = null;
try {
realm = pwdCred.getRealm();
_currentRealm = Realm.getInstance(realm);
} catch (Exception e) {
String msg = sm.getString("pwdlm.norealm", realm);
_logger.log(Level.SEVERE, msg);
throw new LoginException(msg);
}
// Get username and password data from credential (ignore callback)
setUsername(pwdCred.getUser());
setPasswordChar(pwdCred.getPassword());
setPassword(new String(pwdCred.getPassword()));
}
/**
* Perform authentication decision.
*
* Method returns silently on success and returns a LoginException on failure.
*
* @throws LoginException on authentication failure.
*
*/
protected abstract void authenticateUser() throws LoginException;
public void setLoginModuleForAuthentication(LoginModule userDefinedLoginModule) {
this.userDefinedLoginModule = userDefinedLoginModule;
}
/**
* @return the username sent by container - is made available to the custom login module using the protected _username field. Use
* Case: A custom login module could use the username to validate against a realm of users
*/
public String getUsername() {
return _username;
}
/**
* Used for setting the username obtained from the container internally, to be made available to the custom login module
* implementation
*
* @param username
*/
private void setUsername(String username) {
this._username = username;
}
/**
* Deprecated - password is preferred to be a char[]
*/
@Deprecated
public String getPassword() {
return _password;
}
/**
* Deprecated - password is preferred to be a char[]
*/
@Deprecated
private void setPassword(String password) {
this._password = password;
}
/**
* @return the password sent by container - is made available to the custom login module using the protected _password field. Use
* Case: A custom login module could use the password to validate against a custom realm of usernames and passwords Password is
* preferred to be a char[] instead of a string
*/
public char[] getPasswordChar() {
return Arrays.copyOf(_passwd, _passwd.length);
}
/**
* Used for setting the password obtained from the container internally, to be made available to the custom login module
* implementation Password is preferred to be a char[] instead of a string
*
* @param password
*/
private void setPasswordChar(char[] password) {
this._passwd = password;
}
/**
* @return the currentRealm - for backward compatability
*/
public Realm getCurrentRealm() {
return _currentRealm;
}
/**
* @return the succeeded state - for backward compatability
*/
public boolean isSucceeded() {
return _succeeded;
}
/**
* @return the commitsucceeded state - for backward compatability
*/
public boolean isCommitSucceeded() {
return _commitSucceeded;
}
/**
* @return the UserPrincipal - for backward compatability
*/
public UserPrincipal getUserPrincipal() {
return _userPrincipal;
}
/**
* @return the groupList - for backward compatability
*/
public String[] getGroupsList() {
return Arrays.copyOf(_groupsList, _groupsList.length);
}
}