/
RealmAdapter.java
1779 lines (1534 loc) · 70.5 KB
/
RealmAdapter.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
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2021, 2022 Contributors to the Eclipse Foundation.
* Copyright (c) 1997, 2020 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.web.security;
import com.sun.enterprise.deployment.RunAsIdentityDescriptor;
import com.sun.enterprise.deployment.WebBundleDescriptor;
import com.sun.enterprise.deployment.WebComponentDescriptor;
import com.sun.enterprise.deployment.web.LoginConfiguration;
import com.sun.enterprise.security.AppCNonceCacheMap;
import com.sun.enterprise.security.CNonceCacheFactory;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.security.WebSecurityDeployerProbeProvider;
import com.sun.enterprise.security.auth.digest.api.DigestAlgorithmParameter;
import com.sun.enterprise.security.auth.digest.api.Key;
import com.sun.enterprise.security.auth.digest.impl.DigestParameterGenerator;
import com.sun.enterprise.security.auth.digest.impl.HttpAlgorithmParameterImpl;
import com.sun.enterprise.security.auth.digest.impl.NestedDigestAlgoParamImpl;
import com.sun.enterprise.security.auth.login.DigestCredentials;
import com.sun.enterprise.security.auth.login.LoginContextDriver;
import com.sun.enterprise.security.authorize.PolicyContextHandlerImpl;
import com.sun.enterprise.security.integration.RealmInitializer;
import com.sun.enterprise.security.jmac.config.HttpServletConstants;
import com.sun.enterprise.security.jmac.config.HttpServletHelper;
import com.sun.enterprise.security.web.integration.WebPrincipal;
import com.sun.enterprise.security.web.integration.WebSecurityManager;
import com.sun.enterprise.security.web.integration.WebSecurityManagerFactory;
import com.sun.enterprise.util.net.NetUtils;
import com.sun.logging.LogDomains;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import jakarta.security.auth.message.AuthException;
import jakarta.security.auth.message.AuthStatus;
import jakarta.security.auth.message.MessageInfo;
import jakarta.security.auth.message.config.ServerAuthContext;
import jakarta.security.jacc.PolicyContext;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.AccessController;
import java.security.InvalidAlgorithmParameterException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import org.apache.catalina.Authenticator;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.HttpResponse;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.realm.Constants;
import org.apache.catalina.realm.RealmBase;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.grizzly.config.dom.NetworkConfig;
import org.glassfish.grizzly.config.dom.NetworkListener;
import org.glassfish.grizzly.config.dom.NetworkListeners;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.internal.api.ServerContext;
import org.glassfish.security.common.CNonceCache;
import org.glassfish.security.common.NonceInfo;
import org.jvnet.hk2.annotations.Service;
import static com.sun.enterprise.security.auth.digest.api.Constants.A1;
import static com.sun.enterprise.security.auth.digest.impl.DigestParameterGenerator.HTTP_DIGEST;
import static com.sun.enterprise.security.web.integration.WebSecurityManager.getContextID;
import static com.sun.enterprise.util.Utility.isAnyNull;
import static com.sun.enterprise.util.Utility.isEmpty;
import static com.sun.logging.LogDomains.WEB_LOGGER;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static org.apache.catalina.ContainerEvent.AFTER_AUTHENTICATION;
import static org.apache.catalina.ContainerEvent.AFTER_LOGOUT;
import static org.apache.catalina.ContainerEvent.AFTER_POST_AUTHENTICATION;
import static org.apache.catalina.ContainerEvent.BEFORE_AUTHENTICATION;
import static org.apache.catalina.ContainerEvent.BEFORE_LOGOUT;
import static org.apache.catalina.ContainerEvent.BEFORE_POST_AUTHENTICATION;
import static org.apache.catalina.Globals.WRAPPED_REQUEST;
import static org.apache.catalina.Globals.WRAPPED_RESPONSE;
/**
* This is the realm adapter used to authenticate users and authorize access to web resources. The authenticate method
* is called by Tomcat to authenticate users. The hasRole method is called by Tomcat during the authorization process.
*
* @author Harpreet Singh
* @author JeanFrancois Arcand
*/
@Service
@PerLookup
public class RealmAdapter extends RealmBase implements RealmInitializer, PostConstruct {
public static final String SECURITY_CONTEXT = "SecurityContext";
public static final String BASIC = "BASIC";
public static final String FORM = "FORM";
private static final Logger _logger = LogDomains.getLogger(RealmAdapter.class, WEB_LOGGER);
private static final ResourceBundle resourceBundle = _logger.getResourceBundle();
private static final String SERVER_AUTH_CONTEXT = "__jakarta.security.auth.message.ServerAuthContext";
private static final String MESSAGE_INFO = "__jakarta.security.auth.message.MessageInfo";
private static final WebSecurityDeployerProbeProvider websecurityProbeProvider = new WebSecurityDeployerProbeProvider();
// name of system property that can be used to define
// corresponding default provider for system apps.
private static final String SYSTEM_HTTPSERVLET_SECURITY_PROVIDER = "system_httpservlet_security_provider";
private WebBundleDescriptor webBundleDescriptor;
private HashMap<String, String> runAsPrincipals;
private String realmName; // required for realm-per-app login
/**
* Descriptive information about this Realm implementation.
*/
protected static final String name = "J2EE-RI-RealmAdapter";
/**
* The context Id value needed for Jakarta Authorization
*/
private String contextId;
private Container virtualServer;
/**
* A <code>WebSecurityManager</code> object associated with a CONTEXT_ID
*/
protected volatile WebSecurityManager webSecurityManager;
protected boolean isCurrentURIincluded = false;
/*
* the following fields are used to implement a bypass of FBL related targets
*/
protected final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private boolean contextEvaluated = false;
private String loginPage;
private String errorPage;
private final static SecurityConstraint[] emptyConstraints = new SecurityConstraint[] {};
/**
* the default provider id for system apps if one has been established. the default provider for system apps is
* established by defining a system property.
*/
private static String defaultSystemProviderID = getDefaultSystemProviderID();
private String moduleID;
private boolean isSystemApp;
private HttpServletHelper helper;
@Inject
private ServerContext serverContext;
@Inject
private Provider<AppCNonceCacheMap> appCNonceCacheMapProvider;
@Inject
private Provider<CNonceCacheFactory> cNonceCacheFactoryProvider;
@Inject
@Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
private NetworkConfig networkConfig;
/**
* The factory used for creating <code>WebSecurityManager</code> object.
*/
@Inject
protected WebSecurityManagerFactory webSecurityManagerFactory;
private CNonceCacheFactory cNonceCacheFactory;
private CNonceCache cnonces;
private AppCNonceCacheMap haCNonceCacheMap;
private NetworkListeners networkListeners;
/**
* ThreadLocal object to keep track of the reentrancy status of each thread. It contains a byte[] object whose single
* element is either 0 (initial value or no reentrancy), or 1 (current thread is reentrant). When a thread exits the
* implies method, byte[0] is always reset to 0.
*/
private static ThreadLocal<byte[]> reentrancyStatus =
ThreadLocal.withInitial(() -> new byte[] { 0 });
public RealmAdapter() {
// used during Injection in WebContainer (glue code)
}
/**
* Create for Web Services Enterprise Beans endpoint authentication.
*
* <p>
* Roles related data is not available here.
*/
public RealmAdapter(String realmName, String moduleID) {
this.realmName = realmName;
this.moduleID = moduleID;
}
@Override
public void initializeRealm(Object descriptor, boolean isSystemApp, String initialRealmName) {
this.isSystemApp = isSystemApp;
this.webBundleDescriptor = (WebBundleDescriptor) descriptor;
realmName = findRealmName(initialRealmName);
contextId = WebSecurityManager.getContextID(webBundleDescriptor);
moduleID = webBundleDescriptor.getModuleID();
collectRunAsPrincipals();
}
/**
* Return <tt>true</tt> if Jakarta Authentication is available.
*
* @return <tt>true</tt> if Jakarta Authentication is available. 1171
*/
@Override
public boolean isSecurityExtensionEnabled(final ServletContext context) {
if (helper == null) {
initConfigHelper(context);
}
try {
return (helper.getServerAuthConfig() != null);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns null 1. if there are no security constraints defined on any of the web resources within the context, or 2. if
* the target is a form login related page or target.
*
* otherwise return an empty array of SecurityConstraint.
*/
@Override
public SecurityConstraint[] findSecurityConstraints(HttpRequest request, Context context) {
return findSecurityConstraints(context);
}
/**
* Returns null 1. if there are no security constraints defined on any of the web resources within the context, or 2. if
* the target is a form login related page or target.
*
* otherwise return an empty array of SecurityConstraint.
*/
@Override
public SecurityConstraint[] findSecurityConstraints(String requestPathMB, String httpMethod, Context context) {
return findSecurityConstraints(context);
}
/**
* Enforce any user data constraint required by the security constraint guarding this request URI.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint being checked
*
* @exception IOException if an input/output error occurs
*
* @return <code>true</code> if this constraint was not violated and processing should continue, or <code>false</code>
* if we have created a response already
*/
@Override
public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints) throws IOException {
return hasUserDataPermission(request, response, constraints, null, null);
}
/**
* Checks if the given request URI and method are the target of any user-data-constraint with a transport-guarantee of
* CONFIDENTIAL, and whether any such constraint is already satisfied.
*
* If <tt>uri</tt> and <tt>method</tt> are null, then the URI and method of the given <tt>request</tt> are checked.
*
* If a user-data-constraint exists that is not satisfied, then the given <tt>request</tt> will be redirected to HTTPS.
*
* @param request the request that may be redirected
* @param response the response that may be redirected
* @param constraints the security constraints to check against
* @param uri the request URI (minus the context path) to check
* @param method the request method to check
*
* @return true if the request URI and method are not the target of any unsatisfied user-data-constraint with a
* transport-guarantee of CONFIDENTIAL, and false if they are (in which case the given request will have been redirected
* to HTTPS)
*/
@Override
public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, String uri, String method) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getServletPath() == null) {
request.setServletPath(getResourceName(httpServletRequest.getRequestURI(), httpServletRequest.getContextPath()));
}
_logger.fine(() ->
"[Web-Security][ hasUserDataPermission ]" +
" Principal: " + httpServletRequest.getUserPrincipal() +
" ContextPath: " + httpServletRequest.getContextPath());
if (request.getRequest().isSecure()) {
_logger.fine(() -> "[Web-Security] request.getRequest().isSecure(): " + request.getRequest().isSecure());
return true;
}
WebSecurityManager webSecurityManager = getWebSecurityManager(true);
if (webSecurityManager == null) {
return false;
}
int isGranted = 0;
try {
isGranted = webSecurityManager.hasUserDataPermission(httpServletRequest, uri, method);
} catch (IllegalArgumentException e) {
// end the request after getting IllegalArgumentException while checking
// user data permission
_logger.log(WARNING, e, () -> resourceBundle.getString("realmAdapter.badRequestWithId"));
((HttpServletResponse) response.getResponse()).sendError(SC_BAD_REQUEST, resourceBundle.getString("realmAdapter.badRequest"));
return false;
}
// Only redirect if we are sure the user will be granted.
// See bug 4947698
// This method will return:
// 1 - if granted
// 0 - if not granted
// -1 - if the current transport is not granted, but a redirection can occur
// so the grand will succeed.
if (isGranted == -1) {
_logger.fine(() -> "[Web-Security] redirecting using SSL");
return redirect(request, response);
}
if (isGranted == 0) {
((HttpServletResponse) response.getResponse()).sendError(SC_FORBIDDEN, resourceBundle.getString("realmBase.forbidden"));
return false;
}
return true;
}
/**
* Checks whether or not authentication is needed. When Jakarta Authentication (and by extension, Jakarta Security) is enabled,
* authentication is always needed.
*
* <p>
* Returns an int, one of AUTHENTICATE_NOT_NEEDED, AUTHENTICATE_NEEDED,
* or AUTHENTICATED_NOT_AUTHORIZED
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint we are enforcing
* @param disableProxyCaching whether or not to disable proxy caching for protected resources.
* @param securePagesWithPragma true if we add headers which are incompatible with downloading office documents in IE
* under SSL but which fix a caching problem in Mozilla.
* @param ssoEnabled true if sso is enabled
*
* @exception IOException if an input/output error occurs
*/
@Override
public int preAuthenticateCheck(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, boolean disableProxyCaching, boolean securePagesWithPragma, boolean ssoEnabled) throws IOException {
boolean isGranted = false;
try {
if (!isRequestAuthenticated(request)) {
SecurityContext.setUnauthenticatedContext();
}
if (isJakartaAuthenticationEnabled()) {
return AUTHENTICATE_NEEDED;
}
isGranted = invokeWebSecurityManager(request, response, constraints);
} catch (IOException iex) {
throw iex;
} catch (Throwable ex) {
_logger.log(SEVERE, ex, () -> "web_server.excep_authenticate_realmadapter");
((HttpServletResponse) response.getResponse()).sendError(SC_SERVICE_UNAVAILABLE);
response.setDetailMessage(resourceBundle.getString("realmBase.forbidden"));
return AUTHENTICATED_NOT_AUTHORIZED;
}
if (isGranted) {
if (isRequestAuthenticated(request)) {
disableProxyCaching(request, response, disableProxyCaching, securePagesWithPragma);
if (ssoEnabled) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest();
if (!getWebSecurityManager(true).permitAll(httpServletRequest)) {
// create a session for protected sso association
httpServletRequest.getSession(true);
}
}
}
return AUTHENTICATE_NOT_NEEDED;
}
if (isRequestAuthenticated(request)) {
((HttpServletResponse) response.getResponse()).sendError(SC_FORBIDDEN);
response.setDetailMessage(resourceBundle.getString("realmBase.forbidden"));
return AUTHENTICATED_NOT_AUTHORIZED;
}
disableProxyCaching(request, response, disableProxyCaching, securePagesWithPragma);
return AUTHENTICATE_NEEDED;
}
/**
* Authenticates the user making this request, based on the specified login configuration. Return <code>true</code> if
* any specified requirements have been satisfied, or <code>false</code> if we have created a response challenge
* already.
*
* @param request Request we are processing
* @param response Response we are creating
* @param context The Context to which client of this class is attached.
* @param authenticantion the current authenticator.
* @exception IOException if an input/output error occurs
*/
@Override
public boolean invokeAuthenticateDelegate(HttpRequest request, HttpResponse response, Context context, Authenticator authenticator, boolean calledFromAuthenticate) throws IOException {
LoginConfig config = context.getLoginConfig();
if (isJakartaAuthenticationEnabled()) {
// Jakarta Authentication is enabled for this application
try {
context.fireContainerEvent(BEFORE_AUTHENTICATION, null);
RequestFacade requestFacade = (RequestFacade) request.getRequest();
SecurityContext.getCurrent().setSessionPrincipal(requestFacade.getRequestPrincipal());
return validate(request, response, config, authenticator, calledFromAuthenticate);
} finally {
SecurityContext.getCurrent().setSessionPrincipal(null);
context.fireContainerEvent(AFTER_AUTHENTICATION, null);
}
}
// Jakarta Authentication is not enabled. Use the current authenticator.
return ((AuthenticatorBase) authenticator).authenticate(request, response, config);
}
/**
* Return a short name for this Realm Adapter implementation.
*/
@Override
protected String getName() {
return name;
}
/**
* Return the name of the realm this RealmAdapter uses.
*
* @return realm name
*
*/
@Override
public String getRealmName() {
return realmName;
}
/**
* Sets the virtual server on which the web module (with which this RealmAdapter is associated with) has been deployed.
*
* @param container The virtual server
*/
@Override
public void setVirtualServer(Object container) {
this.virtualServer = (Container) container;
}
@Override
public void updateWebSecurityManager() {
if (webSecurityManager == null) {
webSecurityManager = getWebSecurityManager(true);
}
if (webSecurityManager != null) {
try {
webSecurityManager.release();
webSecurityManager.destroy();
} catch (Exception ex) {
ex.printStackTrace();
}
webSecurityManager = webSecurityManagerFactory.createManager(webBundleDescriptor, true, serverContext);
_logger.fine(() -> "WebSecurityManager for " + contextId + " has been updated");
}
}
/**
* Authenticates and sets the SecurityContext in the TLS.
*
* @return the authenticated principal.
* @param the user name.
* @param the password.
*/
@Override
public Principal authenticate(String username, char[] password) {
_logger.fine(() -> "Tomcat callback for authenticate user/password");
_logger.fine(() -> "usename = " + username);
if (authenticate(username, password, null, null)) {
return new WebPrincipal(username, password, SecurityContext.getCurrent());
}
return null;
}
@Override
public Principal authenticate(HttpServletRequest httpServletRequest) {
DigestCredentials digestCredentials = generateDigestCredentials(httpServletRequest);
if (digestCredentials != null && authenticate(null, null, digestCredentials, null)) {
return new WebPrincipal(digestCredentials.getUserName(), (char[]) null, SecurityContext.getCurrent());
}
return null;
}
@Override
public Principal authenticate(X509Certificate certificates[]) {
if (authenticate(null, null, null, certificates)) {
return new WebPrincipal(certificates, SecurityContext.getCurrent());
}
return null;
}
/**
* Perform access control based on the specified authorization constraint. Return <code>true</code> if this constraint
* is satisfied and processing should continue, or <code>false</code> otherwise.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraint Security constraint we are enforcing
* @param The Context to which client of this class is attached.
*
* @exception IOException if an input/output error occurs
*/
@Override
public boolean hasResourcePermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, Context context) throws IOException {
boolean isGranted = false;
try {
isGranted = invokeWebSecurityManager(request, response, constraints);
} catch (IOException iex) {
throw iex;
} catch (Throwable ex) {
_logger.log(SEVERE, ex, () -> "web_server.excep_authenticate_realmadapter");
((HttpServletResponse) response.getResponse()).sendError(SC_SERVICE_UNAVAILABLE);
response.setDetailMessage(resourceBundle.getString("realmBase.forbidden"));
return isGranted;
}
if (isGranted) {
return isGranted;
}
((HttpServletResponse) response.getResponse()).sendError(SC_FORBIDDEN);
response.setDetailMessage(resourceBundle.getString("realmBase.forbidden"));
// invoking secureResponse
invokePostAuthenticateDelegate(request, response, context);
return isGranted;
}
/**
* Post authentication for given request and response.
*
* @param request Request we are processing
* @param response Response we are creating
* @param context The Context to which client of this class is attached.
* @exception IOException if an input/output error occurs
*/
@Override
public boolean invokePostAuthenticateDelegate(HttpRequest request, HttpResponse response, Context context) throws IOException {
boolean result = false;
ServerAuthContext serverAuthContext = null;
try {
if (helper != null) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest();
MessageInfo messageInfo = (MessageInfo) httpServletRequest.getAttribute(MESSAGE_INFO);
if (messageInfo != null) {
// Jakarta Authentication is enabled for this application
serverAuthContext = (ServerAuthContext) messageInfo.getMap().get(SERVER_AUTH_CONTEXT);
if (serverAuthContext != null) {
try {
context.fireContainerEvent(BEFORE_POST_AUTHENTICATION, null);
AuthStatus authStatus = serverAuthContext.secureResponse(messageInfo, null); // null serviceSubject
result = AuthStatus.SUCCESS.equals(authStatus);
} finally {
context.fireContainerEvent(AFTER_POST_AUTHENTICATION, null);
}
}
}
}
} catch (AuthException ex) {
throw new IOException(ex);
} finally {
if (helper != null && serverAuthContext != null) {
if (request instanceof HttpRequestWrapper) {
request.removeNote(WRAPPED_REQUEST);
}
if (response instanceof HttpResponseWrapper) {
request.removeNote(WRAPPED_RESPONSE);
}
}
}
return result;
}
/**
* Check if the given principal has the provided role. Returns true if the principal has the specified role, false
* otherwise.
*
* @return true if the principal has the specified role.
* @param request Request we are processing
* @param response Response we are creating
* @param the principal
* @param the role
*/
@Override
public boolean hasRole(HttpRequest request, HttpResponse response, Principal principal, String role) {
WebSecurityManager webSecurityManager = getWebSecurityManager(true);
if (webSecurityManager == null) {
return false;
}
// add HttpResponse and HttpResponse to the parameters, and remove
// instance variable currentRequest from this class. References to
// this.currentRequest are also removed from other methods.
// String servletName = getResourceName( currentRequest.getRequestURI(),
// currentRequest.getContextPath());
String servletName = getCanonicalName(request);
boolean isGranted = webSecurityManager.hasRoleRefPermission(servletName, role, principal);
_logger.fine(() -> "Checking if servlet " + servletName + " with principal " + principal + " has role " + role + " isGranted: "
+ isGranted);
return isGranted;
}
/**
* Create the realm adapter. Extracts the role to user/group mapping from the runtime deployment descriptor.
*
* @param the web bundle deployment descriptor.
* @param isSystemApp if the app is a system app.
*
* public RealmAdapter(WebBundleDescriptor descriptor, boolean isSystemApp) { this(descriptor, isSystemApp, null); }
*/
@Override
public void destroy() {
super.destroy();
if (helper != null) {
helper.disable();
}
}
public WebBundleDescriptor getWebDescriptor() {
return webBundleDescriptor;
}
// utility method to get web security anager.
// will log warning if the manager is not found in the factory, and
// logNull is true.
public WebSecurityManager getWebSecurityManager(boolean logNull) {
if (webSecurityManager == null) {
synchronized (this) {
webSecurityManager = webSecurityManagerFactory.getManager(contextId);
}
if (webSecurityManager == null && logNull) {
_logger.log(WARNING, "realmAdapter.noWebSecMgr", contextId);
}
}
return webSecurityManager;
}
public boolean hasRole(String servletName, Principal principal, String role) {
WebSecurityManager secMgr = getWebSecurityManager(true);
if (secMgr == null) {
return false;
}
return secMgr.hasRoleRefPermission(servletName, role, principal);
}
@Override
public void logout(HttpRequest httpRequest) {
boolean securityExtensionEnabled = isSecurityExtensionEnabled(httpRequest.getRequest().getServletContext());
byte[] alreadyCalled = reentrancyStatus.get();
if (securityExtensionEnabled && helper != null && alreadyCalled[0] == 0) {
alreadyCalled[0] = 1;
MessageInfo messageInfo = (MessageInfo) httpRequest.getRequest().getAttribute(MESSAGE_INFO);
if (messageInfo == null) {
messageInfo = new HttpMessageInfo((HttpServletRequest) httpRequest.getRequest(),
(HttpServletResponse) httpRequest.getResponse().getResponse());
}
messageInfo.getMap().put(HttpServletConstants.IS_MANDATORY, Boolean.TRUE.toString());
try {
ServerAuthContext serverAuthContext = helper.getServerAuthContext(messageInfo, null);
if (serverAuthContext != null) {
/*
* Check for the default/server-generated/unauthenticated security context.
*/
SecurityContext securityContext = SecurityContext.getCurrent();
Subject subject = securityContext.didServerGenerateCredentials() ? new Subject() : securityContext.getSubject();
if (subject == null) {
subject = new Subject();
}
if (subject.isReadOnly()) {
_logger.log(WARNING, "Read-only subject found during logout processing");
}
try {
httpRequest.getContext().fireContainerEvent(BEFORE_LOGOUT, null);
serverAuthContext.cleanSubject(messageInfo, subject);
} finally {
httpRequest.getContext().fireContainerEvent(AFTER_LOGOUT, null);
}
}
} catch (AuthException ex) {
throw new RuntimeException(ex);
} finally {
doLogout(httpRequest, true);
alreadyCalled[0] = 0;
}
} else {
doLogout(httpRequest, alreadyCalled[0] == 1);
}
}
private void doLogout(HttpRequest request, boolean extensionEnabled) {
Context context = request.getContext();
Authenticator authenticator = null;
if (context != null) {
authenticator = context.getAuthenticator();
}
if (authenticator == null) {
throw new RuntimeException("Context or Authenticator is null");
}
try {
if (extensionEnabled) {
AuthenticatorProxy proxy = new AuthenticatorProxy(authenticator, null, null);
proxy.logout(request);
} else {
authenticator.logout(request);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
logout();
}
@Override
public void logout() {
setSecurityContext(null);
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
resetPolicyContext();
return null;
}
});
}
/*
* IASRI 4688449 This method was only used by J2EEInstanceListener to set the security context prior to invocations by
* re-authenticating a previously set WebPrincipal. This is now cached so no need.
*/
public boolean authenticate(WebPrincipal principal) {
if (principal.isUsingCertificate()) {
return authenticate(null, null, null, principal.getCertificates());
}
return authenticate(principal.getName(), principal.getPassword(), null, null);
}
/**
* Authenticates and sets the SecurityContext in the TLS.
*
* @return true if authentication succeeded, false otherwise.
* @param the username.
* @param the authentication method.
* @param the authentication data.
*/
private boolean authenticate(String username, char[] password, DigestCredentials digestCredentials, X509Certificate[] certificates) {
try {
if (certificates != null) {
LoginContextDriver.doX500Login(generateX500Subject(certificates), moduleID);
} else if (digestCredentials != null) {
LoginContextDriver.login(digestCredentials);
} else {
LoginContextDriver.login(username, password, realmName);
}
_logger.log(FINE, () -> "Web login succeeded for: " + SecurityContext.getCurrent().getCallerPrincipal());
return true;
} catch (Exception le) {
_logger.log(WARNING, "WEB9102: Web Login Failed", le);
return false;
}
}
// BEGIN IASRI 4747594
/**
* Set the run-as principal into the SecurityContext when needed.
*
* <P>
* This method will attempt to obtain the name of the servlet from the ComponentInvocation. Note that there may not be
* one since this gets called also during internal processing (not clear..) not just part of servlet requests. However,
* if it is not a servlet request there is no need (or possibility) to have a run-as setting so no further action is
* taken.
*
* <P>
* If the servlet name is present the runAsPrincipals cache is checked to find the run-as principal to use (if any). If
* one is set, the SecurityContext is switched to this principal.
*
* @param componentInvocation The invocation object to process.
*
*/
public void preSetRunAsIdentity(ComponentInvocation componentInvocation) {
// Optimization to avoid the expensive call to getServletName
// for cases with no run-as descriptors
if (isEmpty(runAsPrincipals)) {
return;
}
String servletName = getServletName(componentInvocation);
if (servletName == null) {
return;
}
String runAs = runAsPrincipals.get(servletName);
if (runAs != null) {
// The existing SecurityContext is saved - however, this seems
// meaningless - see bug 4757733. For now, keep it unchanged
// in case there are some dependencies elsewhere in RI.
componentInvocation.setOldSecurityContext(getSecurityContext());
// Set the run-as principal into SecurityContext
loginForRunAs(runAs);
_logger.log(FINE, () -> "run-as principal for " + servletName + " set to: " + runAs);
}
}
/**
* Obtain servlet name from invocation.
*
* <P>
* In order to obtain the servlet name one of the following must be true: 1. The instanceName of the ComponentInvocation
* is not null 2. The ComponentInvocation contains a 'class' of type HttpServlet, which contains a valid ServletConfig
* object. This method returns the value returned by getServletName() on the ServletConfig.
*
* <P>
* If the above is not met, null is returned.
*
* @param componentInvocation The invocation object to process.
* @return Servlet name or null.
*
*/
private String getServletName(ComponentInvocation componentInvocation) {
String servletName = componentInvocation.getInstanceName();
if (servletName != null) {
return servletName;
}
Object invocationInstance = componentInvocation.getInstance();
if (invocationInstance instanceof HttpServlet) {
HttpServlet thisServlet = (HttpServlet) invocationInstance;
ServletConfig servletConfig = thisServlet.getServletConfig();
if (servletConfig != null) {
return thisServlet.getServletName();
}
}
return null;
}
/**
* Attempts to restore old SecurityContext (but fails).
*
* <P>
* In theory this method seems to attempt to check if a run-as principal was set by preSetRunAsIdentity() (based on the
* indirect assumption that if the servlet in the given invocation has a run-as this must've been the case). If so, it
* retrieves the oldSecurityContext from the invocation object and set it in the SecurityContext.
*
* <P>
* The problem is that the invocation object is not the same object as was passed in to preSetRunAsIdentity() so it will
* never contain the right info - see bug 4757733.
*
* <P>
* In practice it means this method only ever sets the SecurityContext to null (if run-as matched) or does nothing. In
* particular note the implication that it <i>will</i> be set to null after a run-as invocation completes. This behavior
* will be retained for the time being for consistency with RI. It must be fixed later.
*
* @param inv The invocation object to process.
*
*/
public void postSetRunAsIdentity(ComponentInvocation inv) {
// Optimization to avoid the expensivce call to getServletName
// for cases with no run-as descriptors
if (runAsPrincipals != null && runAsPrincipals.isEmpty()) {
return;
}
String servletName = this.getServletName(inv);
if (servletName == null) {
return;
}
String runAs = runAsPrincipals.get(servletName);
if (runAs != null) {
setSecurityContext((SecurityContext) inv.getOldSecurityContext()); // always null
}
}
// END IASRI 4747594
private void loginForRunAs(String principal) {
LoginContextDriver.loginPrincipal(principal, realmName);
}
private SecurityContext getSecurityContext() {
return SecurityContext.getCurrent();
}
private void setSecurityContext(SecurityContext sc) {
SecurityContext.setCurrent(sc);
}
/**
* Used to detect when the principals in the subject correspond to the default or "ANONYMOUS" principal, and therefore a
* null principal should be set in the HttpServletRequest.
*
* @param principalSet
* @return true whe a null principal is to be set.
*/