From 3d571c7d9add23623a14436acd3d19156a145ca8 Mon Sep 17 00:00:00 2001
From: markt
Date: Fri, 1 Apr 2011 10:49:43 +0000
Subject: [PATCH] Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=12428
Add optional support for preemptive authentication on a per context basis
Based on a patch suggested by Werner Donn
git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1087643 13f79535-47bb-0310-9956-ffa450edef68
---
java/org/apache/catalina/Context.java | 13 +++
.../catalina/authenticator/AuthenticatorBase.java | 94 +++++++++++++---------
java/org/apache/catalina/core/StandardContext.java | 14 ++++
.../org/apache/catalina/startup/ContextConfig.java | 17 ++--
.../apache/catalina/core/TestStandardWrapper.java | 24 ++++++
test/webapp-3.0-servletsecurity2/WEB-INF/web.xml | 43 ++++++++++
test/webapp-3.0-servletsecurity2/protected.jsp | 23 ++++++
test/webapp-3.0-servletsecurity2/unprotected.jsp | 23 ++++++
webapps/docs/changelog.xml | 5 ++
webapps/docs/config/context.xml | 10 +++
10 files changed, 224 insertions(+), 42 deletions(-)
create mode 100644 test/webapp-3.0-servletsecurity2/WEB-INF/web.xml
create mode 100644 test/webapp-3.0-servletsecurity2/protected.jsp
create mode 100644 test/webapp-3.0-servletsecurity2/unprotected.jsp
diff --git a/java/org/apache/catalina/Context.java b/java/org/apache/catalina/Context.java
index 35db590dc..6adfed8b8 100644
--- a/java/org/apache/catalina/Context.java
+++ b/java/org/apache/catalina/Context.java
@@ -1349,5 +1349,18 @@ public interface Context extends Container {
*/
public boolean getFireRequestListenersOnForwards();
+ /**
+ * Configures if a user presents authentication credentials, whether the
+ * context will process them when the request is for a non-protected
+ * resource.
+ */
+ public void setPreemptiveAuthentication(boolean enable);
+
+ /**
+ * Determines if a user presents authentication credentials, will the
+ * context will process them when the request is for a non-protected
+ * resource.
+ */
+ public boolean getPreemptiveAuthentication();
}
diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index cf42e29cf..baf6e5a11 100644
--- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
@@ -21,6 +21,7 @@ package org.apache.catalina.authenticator;
import java.io.IOException;
import java.security.Principal;
+import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -32,6 +33,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Authenticator;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Manager;
import org.apache.catalina.Realm;
@@ -454,8 +456,7 @@ public abstract class AuthenticatorBase extends ValveBase
SecurityConstraint [] constraints
= realm.findSecurityConstraints(request, this.context);
- if ((constraints == null) /* &&
- (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
+ if (constraints == null && !context.getPreemptiveAuthentication()) {
if (log.isDebugEnabled())
log.debug(" Not subject to any constraint");
getNext().invoke(request, response);
@@ -464,7 +465,7 @@ public abstract class AuthenticatorBase extends ValveBase
// Make sure that constrained resources are not cached by web proxies
// or browsers as caching can provide a security hole
- if (disableProxyCaching &&
+ if (constraints != null && disableProxyCaching &&
// FIXME: Disabled for Mozilla FORM support over SSL
// (improper caching issue)
//!request.isSecure() &&
@@ -482,36 +483,55 @@ public abstract class AuthenticatorBase extends ValveBase
}
int i;
- // Enforce any user data constraint for this security constraint
- if (log.isDebugEnabled()) {
- log.debug(" Calling hasUserDataPermission()");
- }
- if (!realm.hasUserDataPermission(request, response,
- constraints)) {
+ if (constraints != null) {
+ // Enforce any user data constraint for this security constraint
if (log.isDebugEnabled()) {
- log.debug(" Failed hasUserDataPermission() test");
+ log.debug(" Calling hasUserDataPermission()");
+ }
+ if (!realm.hasUserDataPermission(request, response,
+ constraints)) {
+ if (log.isDebugEnabled()) {
+ log.debug(" Failed hasUserDataPermission() test");
+ }
+ /*
+ * ASSERT: Authenticator already set the appropriate
+ * HTTP status code, so we do not have to do anything special
+ */
+ return;
}
- /*
- * ASSERT: Authenticator already set the appropriate
- * HTTP status code, so we do not have to do anything special
- */
- return;
}
// Since authenticate modifies the response on failure,
// we have to check for allow-from-all first.
- boolean authRequired = true;
- for(i=0; i < constraints.length && authRequired; i++) {
- if(!constraints[i].getAuthConstraint()) {
- authRequired = false;
- } else if(!constraints[i].getAllRoles()) {
- String [] roles = constraints[i].findAuthRoles();
- if(roles == null || roles.length == 0) {
+ boolean authRequired;
+ if (constraints == null) {
+ authRequired = false;
+ } else {
+ authRequired = true;
+ for(i=0; i < constraints.length && authRequired; i++) {
+ if(!constraints[i].getAuthConstraint()) {
authRequired = false;
+ } else if(!constraints[i].getAllRoles()) {
+ String [] roles = constraints[i].findAuthRoles();
+ if(roles == null || roles.length == 0) {
+ authRequired = false;
+ }
}
}
}
-
+
+ if (!authRequired) {
+ authRequired =
+ request.getCoyoteRequest().getMimeHeaders().getValue(
+ "authorization") != null;
+ }
+
+ if (!authRequired) {
+ X509Certificate[] certs = (X509Certificate[]) request.getAttribute(
+ Globals.CERTIFICATES_ATTR);
+ authRequired = certs != null && certs.length > 0;
+ }
+
if(authRequired) {
if (log.isDebugEnabled()) {
log.debug(" Calling authenticate()");
@@ -530,21 +550,23 @@ public abstract class AuthenticatorBase extends ValveBase
}
- if (log.isDebugEnabled()) {
- log.debug(" Calling accessControl()");
- }
- if (!realm.hasResourcePermission(request, response,
- constraints,
- this.context)) {
+ if (constraints != null) {
if (log.isDebugEnabled()) {
- log.debug(" Failed accessControl() test");
+ log.debug(" Calling accessControl()");
+ }
+ if (!realm.hasResourcePermission(request, response,
+ constraints,
+ this.context)) {
+ if (log.isDebugEnabled()) {
+ log.debug(" Failed accessControl() test");
+ }
+ /*
+ * ASSERT: AccessControl method has already set the
+ * appropriate HTTP status code, so we do not have to do
+ * anything special
+ */
+ return;
}
- /*
- * ASSERT: AccessControl method has already set the
- * appropriate HTTP status code, so we do not have to do
- * anything special
- */
- return;
}
// Any and all specified constraints have been satisfied
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index 231ad3b5a..9ec970eab 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -849,10 +849,24 @@ public class StandardContext extends ContainerBase
*/
private Set createdServlets = new HashSet();
+ private boolean preemptiveAuthentication = false;
+
// ----------------------------------------------------- Context Properties
@Override
+ public boolean getPreemptiveAuthentication() {
+ return preemptiveAuthentication;
+ }
+
+
+ @Override
+ public void setPreemptiveAuthentication(boolean preemptiveAuthentication) {
+ this.preemptiveAuthentication = preemptiveAuthentication;
+ }
+
+
+ @Override
public void setFireRequestListenersOnForwards(boolean enable) {
fireRequestListenersOnForwards = enable;
}
diff --git a/java/org/apache/catalina/startup/ContextConfig.java b/java/org/apache/catalina/startup/ContextConfig.java
index 76fec6ca8..da468b92e 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -367,12 +367,17 @@ public class ContextConfig
protected synchronized void authenticatorConfig() {
LoginConfig loginConfig = context.getLoginConfig();
- if (loginConfig == null) {
- if (context.getIgnoreAnnotations()) {
- return;
- } else {
- // Not metadata-complete, need an authenticator to support
- // @ServletSecurity annotations
+
+ SecurityConstraint constraints[] = context.findConstraints();
+ if (context.getIgnoreAnnotations() &&
+ (constraints == null || constraints.length ==0) &&
+ !context.getPreemptiveAuthentication()) {
+ return;
+ } else {
+ if (loginConfig == null) {
+ // Not metadata-complete or security constraints present, need
+ // an authenticator to support @ServletSecurity annotations
+ // and/or constraints
loginConfig = DUMMY_LOGIN_CONFIG;
context.setLoginConfig(loginConfig);
}
diff --git a/test/org/apache/catalina/core/TestStandardWrapper.java b/test/org/apache/catalina/core/TestStandardWrapper.java
index 1fc79cf25..62c791513 100644
--- a/test/org/apache/catalina/core/TestStandardWrapper.java
+++ b/test/org/apache/catalina/core/TestStandardWrapper.java
@@ -143,6 +143,30 @@ public class TestStandardWrapper extends TomcatBaseTest {
assertEquals(403, rc);
}
+ public void testSecurityAnnotationsNoWebXmlLoginConfig() throws Exception {
+ // Setup Tomcat instance
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp-3.0-servletsecurity2");
+ tomcat.addWebapp(null, "", appDir.getAbsolutePath());
+
+ tomcat.start();
+
+ ByteChunk bc = new ByteChunk();
+ int rc;
+ rc = getUrl("http://localhost:" + getPort() + "/protected.jsp",
+ bc, null, null);
+
+ assertNull(bc.toString());
+ assertEquals(403, rc);
+
+ rc = getUrl("http://localhost:" + getPort() + "/unprotected.jsp",
+ bc, null, null);
+
+ assertEquals(200, rc);
+ assertTrue(bc.toString().contains("00-OK"));
+ }
+
private void doTestSecurityAnnotationsAddServlet(boolean useCreateServlet)
throws Exception {
diff --git a/test/webapp-3.0-servletsecurity2/WEB-INF/web.xml b/test/webapp-3.0-servletsecurity2/WEB-INF/web.xml
new file mode 100644
index 000000000..674f1a0f1
--- /dev/null
+++ b/test/webapp-3.0-servletsecurity2/WEB-INF/web.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ Tomcat Test Application
+
+ Used as part of the Tomcat unit tests when a full web application is
+ required.
+
+
+
+
+
+ /protected.jsp
+
+
+
\ No newline at end of file
diff --git a/test/webapp-3.0-servletsecurity2/protected.jsp b/test/webapp-3.0-servletsecurity2/protected.jsp
new file mode 100644
index 000000000..535c6119e
--- /dev/null
+++ b/test/webapp-3.0-servletsecurity2/protected.jsp
@@ -0,0 +1,23 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+
+ Protected page
+
+ 00-OK
+
+
+
diff --git a/test/webapp-3.0-servletsecurity2/unprotected.jsp b/test/webapp-3.0-servletsecurity2/unprotected.jsp
new file mode 100644
index 000000000..d1d95f2a0
--- /dev/null
+++ b/test/webapp-3.0-servletsecurity2/unprotected.jsp
@@ -0,0 +1,23 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--%>
+
+ Unprotected page
+
+ 00-OK
+
+
+
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 5576b9c35..52d61a0b9 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -73,6 +73,11 @@
When using parallel deployment, correctly handle the scenario when the
client sends multiple JSESSIONID cookies. (markt)
+
+ 12428: Add support (disabled by default) for preemptive
+ authentication. This can be configured per context. Based on a patch
+ suggested by Werner Donn. (markt)
+
50929: When wrapping an exception, include the root cause.
Patch provided by sebb. (markt)
diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
index abc727621..c4ccae27c 100644
--- a/webapps/docs/config/context.xml
+++ b/webapps/docs/config/context.xml
@@ -312,6 +312,16 @@
filenames used for either the .xml context file or the docBase.
+
+ When set to true and the user presents credentials for a
+ resource that is not protected by a security constraint, if the
+ authenticator supports preemptive authentication (the standard
+ authenticators provided with Tomcat do) then the user' credentials
+ will be processed. If not specified, the default of falseis
+ used.
+
+
+
Set to true to allow this context to use container
servlets, like the manager servlet. Use of the privileged
--
2.11.0