From 1fd3e4ad1b8903bc9033dd24c86a0207996097b1 Mon Sep 17 00:00:00 2001 From: Josh Elser Date: Tue, 1 Aug 2017 22:18:04 -0400 Subject: [PATCH] =?UTF-8?q?Sends=20the=20WWW-Authenticate=20header=20if=20?= =?UTF-8?q?a=20non-Negotiate=20authorization=20he=E2=80=A6=20(#1700)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sends the WWW-Authenticate header if a non-Negotiate authorization header was given Fixes #1698 Signed-off-by: Josh Elser * Dumb compilation error Signed-off-by: Josh Elser * Adds a test to show the challenge is sent. Signed-off-by: Josh Elser * Refactor the conditionals per Greg's suggestion Signed-off-by: Josh Elser * Add the expected license header Signed-off-by: Josh Elser --- .../authentication/SpnegoAuthenticator.java | 43 ++++--- .../SpnegoAuthenticatorTest.java | 113 ++++++++++++++++++ 2 files changed, 134 insertions(+), 22 deletions(-) create mode 100644 jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java index 93e0592eed2..aad1da02967 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java @@ -72,27 +72,8 @@ public class SpnegoAuthenticator extends LoginAuthenticator return new DeferredAuthentication(this); } - // check to see if we have authorization headers required to continue - if ( header == null ) - { - try - { - if (DeferredAuthentication.isDeferred(res)) - { - return Authentication.UNAUTHENTICATED; - } - - LOG.debug("SpengoAuthenticator: sending challenge"); - res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); - res.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return Authentication.SEND_CONTINUE; - } - catch (IOException ioe) - { - throw new ServerAuthException(ioe); - } - } - else if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString())) + // The client has responded to the challenge we sent previously + if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString().toLowerCase())) { String spnegoToken = header.substring(10); @@ -104,7 +85,25 @@ public class SpnegoAuthenticator extends LoginAuthenticator } } - return Authentication.UNAUTHENTICATED; + // A challenge should be sent if any of the following cases are true: + // 1. There was no Authorization header provided + // 2. There was an Authorization header for a type other than Negotiate + try + { + if (DeferredAuthentication.isDeferred(res)) + { + return Authentication.UNAUTHENTICATED; + } + + LOG.debug("SpengoAuthenticator: sending challenge"); + res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); + res.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return Authentication.SEND_CONTINUE; + } + catch (IOException ioe) + { + throw new ServerAuthException(ioe); + } } @Override diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java new file mode 100644 index 00000000000..8e968b8cb60 --- /dev/null +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticatorTest.java @@ -0,0 +1,113 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.security.authentication; + +import static org.junit.Assert.assertEquals; + +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.server.Authentication; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpOutput; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link SpnegoAuthenticator}. + */ +public class SpnegoAuthenticatorTest { + private SpnegoAuthenticator _authenticator; + + @Before + public void setup() throws Exception + { + _authenticator = new SpnegoAuthenticator(); + } + + @Test + public void testChallengeSentWithNoAuthorization() throws Exception + { + HttpChannel channel = new HttpChannel(null, new HttpConfiguration(), null, null) + { + @Override + public Server getServer() + { + return null; + } + }; + Request req = new Request(channel, null); + HttpOutput out = new HttpOutput(channel) + { + @Override + public void close() + { + return; + } + }; + Response res = new Response(channel, out); + MetaData.Request metadata = new MetaData.Request(new HttpFields()); + metadata.setURI(new HttpURI("http://localhost")); + req.setMetaData(metadata); + + assertEquals(Authentication.SEND_CONTINUE, _authenticator.validateRequest(req, res, true)); + assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString())); + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus()); + } + + @Test + public void testChallengeSentWithUnhandledAuthorization() throws Exception + { + HttpChannel channel = new HttpChannel(null, new HttpConfiguration(), null, null) + { + @Override + public Server getServer() + { + return null; + } + }; + Request req = new Request(channel, null); + HttpOutput out = new HttpOutput(channel) + { + @Override + public void close() + { + return; + } + }; + Response res = new Response(channel, out); + HttpFields http_fields = new HttpFields(); + // Create a bogus Authorization header. We don't care about the actual credentials. + http_fields.add(HttpHeader.AUTHORIZATION, "Basic asdf"); + MetaData.Request metadata = new MetaData.Request(http_fields); + metadata.setURI(new HttpURI("http://localhost")); + req.setMetaData(metadata); + + assertEquals(Authentication.SEND_CONTINUE, _authenticator.validateRequest(req, res, true)); + assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString())); + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus()); + } +}