Index: pom.xml
===================================================================
--- pom.xml (revision 97895)
+++ pom.xml (working copy)
@@ -48,8 +48,18 @@
jboss.web
servlet-api
+
+
+ junit
+ junit
+ test
+
+
+ org.easymock
+ easymock
+ 2.4
+ test
-
Index: src/main/java/org/jboss/security/negotiation/AuthorizationHeader.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/AuthorizationHeader.java (revision 0)
+++ src/main/java/org/jboss/security/negotiation/AuthorizationHeader.java (revision 0)
@@ -0,0 +1,129 @@
+/*
+ * Created on Dec 14, 2009
+ */
+package org.jboss.security.negotiation;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.catalina.connector.Request;
+import org.apache.log4j.Logger;
+import org.jboss.util.Base64;
+
+/**
+ * Parses the Authorization header provided in the request and provides details
+ * about the header for determining how to authenticate the user, if possible.
+ *
+ * @author jacob@solutionsfit.com
+ * @version 1.0
+ */
+public class AuthorizationHeader
+{
+ private static final Logger log = Logger.getLogger(AuthorizationHeader.class);
+
+ private static final Pattern AUTH_SCHEME_PATTERN = Pattern.compile("(\\w+)(.*)");
+
+ private static final String AUTH_HEADER_KEY = "Authorization";
+ private static final transient byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ private boolean emptyAuthHeader = true;
+ private String authScheme;
+ private String authToken;
+
+ /**
+ * Creates an AuthorizationHeader instance from the provided request based
+ * on the Authorization header.
+ *
+ * @param request
+ * @return an AuthorizationHeader instance for the request
+ * @throws IOException
+ */
+ public static AuthorizationHeader createFromRequest(final Request request) throws IOException
+ {
+ String authHeader = request.getHeader(AUTH_HEADER_KEY);
+
+ log.debug("Header - " + authHeader);
+
+ return new AuthorizationHeader(authHeader);
+ }
+
+ protected AuthorizationHeader(final String authHeader) throws IOException
+ {
+ if(authHeader != null && authHeader.length() > 0)
+ {
+ boolean invalidHeader = false;
+
+ authScheme = determineAuthScheme(authHeader);
+
+ if(authScheme == null)
+ {
+ throw new IOException("Invalid 'Authorization' header.");
+ }
+
+ authToken = authHeader.length() > authScheme.length() ?
+ authHeader.substring(authScheme.length() + 1) : null;
+
+ if(authToken == null)
+ {
+ throw new IOException("Invalid 'Authorization' header.");
+ }
+
+ emptyAuthHeader = false;
+ }
+ }
+
+ protected String determineAuthScheme(String authHeader)
+ {
+ Matcher authSchemeMatcher = AUTH_SCHEME_PATTERN.matcher(authHeader);
+
+ if(authSchemeMatcher.matches())
+ {
+ return authSchemeMatcher.group(1);
+ }
+
+ return null;
+ }
+
+ /**
+ * @return a boolean indicating whether the authorization header was empty
+ */
+ public boolean isEmpty()
+ {
+ return emptyAuthHeader;
+ }
+
+ /**
+ * @param authScheme
+ * @return a boolean indicating whether this authorization header is attempting
+ * to authenticate with the provided authScheme
+ */
+ public boolean isScheme(String authScheme)
+ {
+ return this.authScheme.equals(authScheme);
+ }
+
+ protected String getScheme()
+ {
+ return authScheme;
+ }
+
+ /**
+ * @return the authToken sent with the Authorization header in the base64
+ * encoded format that it was sent
+ */
+ public String getAuthTokenBase64()
+ {
+ return authToken;
+ }
+
+ /**
+ * @return the authToken sent with the Authorization header as a byte array
+ * decoded from the base64 format that it was sent
+ */
+ public byte[] getAuthToken()
+ {
+ return authToken != null ? Base64.decode(authToken) : null;
+ }
+}
+
Index: src/main/java/org/jboss/security/negotiation/common/AuthenticationContext.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/common/AuthenticationContext.java (revision 0)
+++ src/main/java/org/jboss/security/negotiation/common/AuthenticationContext.java (revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Created on Dec 15, 2009
+ */
+package org.jboss.security.negotiation.common;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.log4j.Logger;
+import org.jboss.security.negotiation.AuthorizationHeader;
+
+
+/**
+ * Maintains the context for negotiating authentication with the user. Holds
+ * the components necessary to perform authentication and holds the status
+ * of that authentication during the course of a request.
+ *
+ * @author jacob@solutionsfit.com
+ * @version 1.0
+ */
+public class AuthenticationContext
+{
+ private static final Logger log = Logger.getLogger(AuthenticationContext.class);
+
+ private Request request;
+
+ private Response response;
+ private LoginConfig loginConfig;
+ private Realm realm;
+
+ private AuthorizationHeader authHeader;
+ private String authenticationMethod;
+ private Principal principal;
+ private boolean registerPrincipal = false;
+
+ public AuthenticationContext(final Request request, final Response response,
+ final LoginConfig loginConfig, final Realm realm) throws IOException
+ {
+ this.request = request;
+ this.response = response;
+ this.loginConfig = loginConfig;
+ this.realm = realm;
+
+ this.principal = request.getUserPrincipal();
+ this.authHeader = AuthorizationHeader.createFromRequest(request);
+
+ if(isAuthenticated() && log.isTraceEnabled())
+ {
+ log.trace("Already authenticated '" + this.principal.getName() + "'");
+ }
+ }
+
+ /**
+ * Returns a boolean indicating if the user has been authenticated.
+ *
+ * @return boolean
+ */
+ public boolean isAuthenticated()
+ {
+ return principal != null;
+ }
+
+ /**
+ * Authenticates the provided username with the provided credentials
+ * against the realm associated with this context.
+ *
+ * @param username
+ * @param credentials
+ */
+ public void authenticate(String username, String credentials)
+ {
+ principal = realm.authenticate(username, credentials);
+
+ if(principal != null)
+ {
+ registerPrincipal = true;
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("authenticated principal = " + principal);
+ }
+ }
+ }
+
+ /**
+ * Determines whether the principal should be registered by the Authenticator.
+ *
+ * @return Returns the registerPrincipal.
+ */
+ public boolean shouldRegisterPrincipal()
+ {
+ return registerPrincipal;
+ }
+
+ /**
+ * Get the request
+ * @return Returns the request.
+ */
+ public Request getRequest()
+ {
+ return request;
+ }
+
+ /**
+ * Get the response
+ * @return Returns the response.
+ */
+ public Response getResponse()
+ {
+ return response;
+ }
+
+ public Principal getPrincipal()
+ {
+ return principal;
+ }
+
+ public AuthorizationHeader getAuthorizationHeader()
+ {
+ return this.authHeader;
+ }
+
+ public void setAuthenticationMethod(String authenticationMethod)
+ {
+ this.authenticationMethod = authenticationMethod;
+ }
+
+ public String getAuthenticationMethod()
+ {
+ return this.authenticationMethod;
+ }
+
+ /**
+ * Get the username
+ *
+ * @return Returns the username.
+ */
+ public String getUsername()
+ {
+ return request.getSessionInternal().getId();
+ }
+
+ /**
+ * Get the loginConfig
+ *
+ * @return Returns the loginConfig.
+ */
+ public LoginConfig getLoginConfig()
+ {
+ return loginConfig;
+ }
+}
Index: src/main/java/org/jboss/security/negotiation/common/NegotiationContext.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/common/NegotiationContext.java (revision 98000)
+++ src/main/java/org/jboss/security/negotiation/common/NegotiationContext.java (working copy)
@@ -50,6 +50,11 @@
private Object schemeContext = null;
+ public static boolean isDefined()
+ {
+ return getCurrentNegotiationContext() != null;
+ }
+
public static NegotiationContext getCurrentNegotiationContext()
{
return negotiationContext.get();
Index: src/main/java/org/jboss/security/negotiation/DelegatingNegotiationAuthenticator.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/DelegatingNegotiationAuthenticator.java (revision 0)
+++ src/main/java/org/jboss/security/negotiation/DelegatingNegotiationAuthenticator.java (revision 0)
@@ -0,0 +1,223 @@
+/*
+ * Created on Dec 17, 2009
+ */
+package org.jboss.security.negotiation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.log4j.Logger;
+import org.jboss.security.negotiation.common.AuthenticationContext;
+import org.jboss.security.negotiation.common.MessageTrace;
+import org.jboss.security.negotiation.common.NegotiationContext;
+
+/**
+ * Authenticator that attempts to authenticate the user through HTTP
+ * challenge-response authentication. It first attempts to Negotiate
+ * authentication. If Negotiate fails, it attempts to fall back to a lesser
+ * authentication scheme (if available). Lesser authentication schemes can
+ * be provided by implementing the {@link SchemeAuthenticator} interface
+ * and providing the implementation when constructing an instance.
+ *
+ * @author jacob.orshalick@archongroup.com
+ * @version 1.0
+ */
+public class DelegatingNegotiationAuthenticator
+{
+ private static final Logger log = Logger.getLogger(NegotiationAuthenticator.class);
+
+ public static final String AUTH_HEADER_KEY = "WWW-Authenticate";
+
+ private static final String NEGOTIATE_SCHEME = "Negotiate";
+ private static final String NEGOTIATION_CONTEXT = "NEGOTIATION_CONTEXT";
+
+ private final List fallbackSchemeAuthenticators;
+
+ /**
+ * Constructs an instance with the provided list of fallbackSchemeAuthenticators.
+ *
+ * @param fallbackSchemeAuthenticators
+ */
+ public DelegatingNegotiationAuthenticator(List fallbackSchemeAuthenticators)
+ {
+ List schemeAuthenticators = new ArrayList(fallbackSchemeAuthenticators);
+ this.fallbackSchemeAuthenticators = Collections.unmodifiableList(schemeAuthenticators);
+ }
+
+ /**
+ * Attempts to negotiate authentication with the user based on the provided
+ * authenticationContext. If that negotiation fails, attempts to fallback to
+ * any provided fallbackAuthenticators.
+ *
+ * @param authenticationContext
+ * @return a boolean representing whether authentication was successful
+ * @throws IOException
+ */
+ public boolean authenticate(AuthenticationContext authenticationContext)
+ throws IOException
+ {
+ if (authenticationContext.isAuthenticated())
+ {
+ return true;
+ }
+
+ AuthorizationHeader authHeader = authenticationContext.getAuthorizationHeader();
+ LoginConfig config = authenticationContext.getLoginConfig();
+ Request request = authenticationContext.getRequest();
+ Response response = authenticationContext.getResponse();
+
+ boolean authenticated = false;
+
+ if(authHeader.isEmpty())
+ {
+ sendErrorWithSupportedSchemes(response, config);
+ }
+ else if(authHeader.isScheme(NEGOTIATE_SCHEME))
+ {
+ authenticated = negotiateAuthentication(authenticationContext);
+ }
+ else
+ {
+ SchemeAuthenticator schemeAuthenticator = findFallbackSchemeAuthenticator(authHeader);
+ authenticated = schemeAuthenticator.authenticate(request, response, authenticationContext);
+ }
+
+ return authenticated;
+ }
+
+ protected SchemeAuthenticator findFallbackSchemeAuthenticator(AuthorizationHeader authHeader) throws IOException
+ {
+ for(SchemeAuthenticator schemeAuthenticator : fallbackSchemeAuthenticators)
+ {
+ if(authHeader.isScheme(schemeAuthenticator.getScheme()))
+ {
+ return schemeAuthenticator;
+ }
+ }
+
+ throw new IOException("Unsupported authorization scheme");
+ }
+
+ protected void sendErrorWithSupportedSchemes(Response response, LoginConfig loginConfig) throws IOException
+ {
+ log.debug("No Authorization Header, sending 401");
+
+ log.debug(AUTH_HEADER_KEY + ": " + NEGOTIATE_SCHEME);
+ response.setHeader(AUTH_HEADER_KEY, NEGOTIATE_SCHEME);
+
+ sendSupportedFallbackSchemes(response, loginConfig);
+
+ response.sendError(401);
+ }
+
+ protected void sendSupportedFallbackSchemes(Response response, LoginConfig loginConfig)
+ {
+ for(SchemeAuthenticator schemeAuthenticator : fallbackSchemeAuthenticators)
+ {
+ String supportedSchemeHeader = schemeAuthenticator.getAuthenticateHeader(loginConfig);
+
+ log.debug(AUTH_HEADER_KEY + ": " + supportedSchemeHeader);
+ response.addHeader(AUTH_HEADER_KEY, supportedSchemeHeader);
+ }
+ }
+
+ /**
+ * Attempts to negotiate authentication with the user based on the
+ * private authenticationContext.
+ *
+ * @param authenticationContext
+ * @return a boolean indicating if authentication was negotiated successfully
+ * @throws IOException
+ */
+ protected boolean negotiateAuthentication(final AuthenticationContext authenticationContext) throws IOException
+ {
+ log.trace("Authenticating user");
+
+ Request request = authenticationContext.getRequest();
+ Response response = authenticationContext.getResponse();
+ AuthorizationHeader authHeader = authenticationContext.getAuthorizationHeader();
+
+ byte[] authToken = authHeader.getAuthToken();
+ ByteArrayInputStream authTokenIS = new ByteArrayInputStream(authToken);
+ MessageTrace.logRequestBase64(authHeader.getAuthTokenBase64());
+ MessageTrace.logRequestHex(authToken);
+
+ Session session = request.getSessionInternal();
+ NegotiationContext negotiationContext = (NegotiationContext) session.getNote(NEGOTIATION_CONTEXT);
+ if (negotiationContext == null)
+ {
+ log.debug("Creating new NegotiationContext");
+ {
+ negotiationContext = new NegotiationContext();
+ session.setNote(NEGOTIATION_CONTEXT, negotiationContext);
+ }
+ }
+
+ try
+ {
+ // Set the ThreadLocal association.
+ negotiationContext.associate();
+
+ MessageFactory mf = MessageFactory.newInstance();
+ if (mf.accepts(authTokenIS) == false)
+ {
+ throw new IOException("Unsupported negotiation mechanism.");
+ }
+
+ NegotiationMessage requestMessage = mf.createMessage(authTokenIS);
+ negotiationContext.setRequestMessage(requestMessage);
+
+ authenticationContext.authenticate(authenticationContext.getUsername(), (String) null);
+ authenticationContext.setAuthenticationMethod(negotiationContext.getAuthenticationMethod());
+
+ NegotiationMessage responseMessage = negotiationContext.getResponseMessage();
+
+ if (responseMessage != null)
+ {
+ ByteArrayOutputStream responseMessageOS = new ByteArrayOutputStream();
+ responseMessage.writeTo(responseMessageOS, true);
+ String responseHeader = responseMessageOS.toString();
+
+ MessageTrace.logResponseBase64(responseHeader);
+
+ response.setHeader(AUTH_HEADER_KEY,
+ NEGOTIATE_SCHEME + " " + responseHeader);
+ }
+ else if(!authenticationContext.isAuthenticated())
+ {
+ // negotiation authentication failed, so attempt fallback
+ log.info("Negotiation failed due to unsupported negotiate message or Kerberos failure. " +
+ "Attempting to fallback to lesser authentication mechanism.");
+
+ sendSupportedFallbackSchemes(response, authenticationContext.getLoginConfig());
+ }
+ }
+ catch (NegotiationException e)
+ {
+ IOException ioe = new IOException("Error processing " + NEGOTIATE_SCHEME + " header.");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ finally
+ {
+ // Clear the headers and remove the ThreadLocal association.
+ negotiationContext.clear();
+ }
+
+ if (!authenticationContext.isAuthenticated())
+ {
+ response.sendError(Response.SC_UNAUTHORIZED);
+ }
+
+ return authenticationContext.isAuthenticated();
+ }
+}
Index: src/main/java/org/jboss/security/negotiation/NegotiationAuthenticator.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/NegotiationAuthenticator.java (revision 98000)
+++ src/main/java/org/jboss/security/negotiation/NegotiationAuthenticator.java (working copy)
@@ -22,21 +22,14 @@
*/
package org.jboss.security.negotiation;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.security.Principal;
-
-import org.apache.catalina.Realm;
-import org.apache.catalina.Session;
+import java.util.Collections;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.log4j.Logger;
-import org.jboss.security.negotiation.common.MessageTrace;
-import org.jboss.security.negotiation.common.NegotiationContext;
-import org.jboss.util.Base64;
+import org.jboss.security.negotiation.common.AuthenticationContext;
/**
* An authenticator to manage Negotiation based authentication in connection with the
@@ -47,124 +40,33 @@
*/
public class NegotiationAuthenticator extends AuthenticatorBase
{
-
private static final Logger log = Logger.getLogger(NegotiationAuthenticator.class);
- private static final String NEGOTIATE = "Negotiate";
-
- private static final String NEGOTIATION_CONTEXT = "NEGOTIATION_CONTEXT";
-
- protected String getNegotiateScheme()
+ private final DelegatingNegotiationAuthenticator delegatingNegotiationAuthenticator;
+
+ public NegotiationAuthenticator()
{
- return NEGOTIATE;
+ super();
+
+ delegatingNegotiationAuthenticator = new DelegatingNegotiationAuthenticator(Collections.EMPTY_LIST);
}
-
+
@Override
protected boolean authenticate(final Request request, final Response response, final LoginConfig config)
throws IOException
{
- log.trace("Authenticating user");
-
- Principal principal = request.getUserPrincipal();
- if (principal != null)
- {
- if (log.isTraceEnabled())
- log.trace("Already authenticated '" + principal.getName() + "'");
- return true;
- }
-
- String negotiateScheme = getNegotiateScheme();
-
- log.debug("Header - " + request.getHeader("Authorization"));
- String authHeader = request.getHeader("Authorization");
- if (authHeader == null)
- {
- log.debug("No Authorization Header, sending 401");
- response.setHeader("WWW-Authenticate", negotiateScheme);
- response.sendError(401);
-
- return false;
- }
- else if (authHeader.startsWith(negotiateScheme + " ") == false)
- {
- throw new IOException("Invalid 'Authorization' header.");
- }
-
- String authTokenBase64 = authHeader.substring(negotiateScheme.length() + 1);
- byte[] authToken = Base64.decode(authTokenBase64);
- ByteArrayInputStream authTokenIS = new ByteArrayInputStream(authToken);
- MessageTrace.logRequestBase64(authTokenBase64);
- MessageTrace.logRequestHex(authToken);
-
- Session session = request.getSessionInternal();
- NegotiationContext negotiationContext = (NegotiationContext) session.getNote(NEGOTIATION_CONTEXT);
- if (negotiationContext == null)
- {
- log.debug("Creating new NegotiationContext");
- {
- negotiationContext = new NegotiationContext();
- session.setNote(NEGOTIATION_CONTEXT, negotiationContext);
- }
- }
-
- String username = session.getId();
- String authenticationMethod = "";
- try
- {
- // Set the ThreadLocal association.
- negotiationContext.associate();
-
- MessageFactory mf = MessageFactory.newInstance();
- if (mf.accepts(authTokenIS) == false)
- {
- throw new IOException("Unsupported negotiation mechanism.");
- }
-
- NegotiationMessage requestMessage = mf.createMessage(authTokenIS);
- negotiationContext.setRequestMessage(requestMessage);
-
- Realm realm = context.getRealm();
- principal = realm.authenticate(username, (String) null);
-
- authenticationMethod = negotiationContext.getAuthenticationMethod();
-
- if (log.isDebugEnabled() && principal != null)
- log.debug("authenticated principal = " + principal);
-
- NegotiationMessage responseMessage = negotiationContext.getResponseMessage();
- if (responseMessage != null)
- {
- ByteArrayOutputStream responseMessageOS = new ByteArrayOutputStream();
- responseMessage.writeTo(responseMessageOS, true);
- String responseHeader = responseMessageOS.toString();
-
- MessageTrace.logResponseBase64(responseHeader);
-
- response.setHeader("WWW-Authenticate", negotiateScheme + " " + responseHeader);
- }
-
- }
- catch (NegotiationException e)
- {
- IOException ioe = new IOException("Error processing " + negotiateScheme + " header.");
- ioe.initCause(e);
- throw ioe;
- }
- finally
- {
- // Clear the headers and remove the ThreadLocal association.
- negotiationContext.clear();
- }
-
- if (principal == null)
- {
- response.sendError(Response.SC_UNAUTHORIZED);
- }
- else
- {
- register(request, response, principal, authenticationMethod, username, null);
- }
-
- return (principal != null);
+ AuthenticationContext authenticationContext =
+ new AuthenticationContext(request, response, config, context.getRealm());
+
+ boolean authenticated = delegatingNegotiationAuthenticator.authenticate(authenticationContext);
+
+ if(authenticationContext.shouldRegisterPrincipal())
+ {
+ register(request, response, authenticationContext.getPrincipal(),
+ authenticationContext.getAuthenticationMethod(),
+ authenticationContext.getUsername(), null);
+ }
+
+ return authenticated;
}
}
Index: src/main/java/org/jboss/security/negotiation/NegotiationWithBasicFallbackAuthenticator.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/NegotiationWithBasicFallbackAuthenticator.java (revision 0)
+++ src/main/java/org/jboss/security/negotiation/NegotiationWithBasicFallbackAuthenticator.java (revision 0)
@@ -0,0 +1,91 @@
+/*
+ * Created on Dec 15, 2009
+ */
+package org.jboss.security.negotiation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.catalina.authenticator.BasicAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.log4j.Logger;
+import org.jboss.security.negotiation.common.AuthenticationContext;
+
+/**
+ * Attempts SPNEGO authentication and if that authentication fails, falls back
+ * to Basic authentication.
+ *
+ * @author jacob@solutionsfit.com
+ * @version 1.0
+ */
+public class NegotiationWithBasicFallbackAuthenticator extends BasicAuthenticator implements SchemeAuthenticator
+{
+ private static final Logger log = Logger.getLogger(NegotiationAuthenticator.class);
+
+ private final DelegatingNegotiationAuthenticator delegatingNegotiationAuthenticator;
+
+ public NegotiationWithBasicFallbackAuthenticator()
+ {
+ super();
+
+ List fallbackAuthenticators = new ArrayList();
+ fallbackAuthenticators.add(this);
+
+ delegatingNegotiationAuthenticator = new DelegatingNegotiationAuthenticator(fallbackAuthenticators);
+ }
+
+ @Override
+ public boolean authenticate(final Request request, final Response response, final LoginConfig config)
+ throws IOException
+ {
+ AuthenticationContext authenticationContext =
+ new AuthenticationContext(request, response, config, context.getRealm());
+
+ boolean authenticated = delegatingNegotiationAuthenticator.authenticate(authenticationContext);
+
+ if(authenticationContext.shouldRegisterPrincipal())
+ {
+ register(request, response, authenticationContext.getPrincipal(),
+ authenticationContext.getAuthenticationMethod(),
+ authenticationContext.getUsername(), null);
+ }
+
+ return authenticated;
+ }
+
+ /**
+ * @see org.jboss.security.negotiation.SchemeAuthenticator#authenticate(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response, org.jboss.security.negotiation.common.AuthenticationContext)
+ * @param request
+ * @param response
+ * @param authenticationContext
+ * @return
+ * @throws IOException
+ */
+ public boolean authenticate(Request request, Response response,
+ AuthenticationContext authenticationContext) throws IOException
+ {
+ return super.authenticate(request, response, authenticationContext.getLoginConfig());
+ }
+
+ /**
+ * @see org.jboss.security.negotiation.SchemeAuthenticator#getAuthenticateHeader(org.apache.catalina.deploy.LoginConfig)
+ * @param loginConfig
+ * @return
+ */
+ public String getAuthenticateHeader(LoginConfig loginConfig)
+ {
+ return getScheme() + " " + loginConfig.getRealmName();
+ }
+
+ /**
+ * @see org.jboss.security.negotiation.SchemeAuthenticator#getScheme()
+ * @return
+ */
+ public String getScheme()
+ {
+ return "Basic";
+ }
+}
Index: src/main/java/org/jboss/security/negotiation/SchemeAuthenticator.java
===================================================================
--- src/main/java/org/jboss/security/negotiation/SchemeAuthenticator.java (revision 0)
+++ src/main/java/org/jboss/security/negotiation/SchemeAuthenticator.java (revision 0)
@@ -0,0 +1,31 @@
+/*
+ * Created on Dec 15, 2009
+ */
+package org.jboss.security.negotiation;
+
+import java.io.IOException;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.jboss.security.negotiation.common.AuthenticationContext;
+
+/**
+ * Represents an authenticator for a particular scheme (e.g. Digest, Basic) that
+ * can be added as a fallback authenticator for the
+ * {@link DelegatingNegotiationAuthenticator}. Fallback authenticators
+ * can be provided by implementing this interface and delegating authentication
+ * from an Authenticator to the {@link DelegatingNegotiationAuthenticator}.
+ *
+ * @author jacob@solutionsfit.com
+ * @version 1.0
+ */
+public interface SchemeAuthenticator
+{
+ public String getScheme();
+
+ public String getAuthenticateHeader(LoginConfig loginConfig);
+
+ public boolean authenticate(final Request request, final Response response, final AuthenticationContext authenticationContext)
+ throws IOException;
+}
Index: src/tests/java/AuthenticationContextTest.java
===================================================================
--- src/tests/java/AuthenticationContextTest.java (revision 0)
+++ src/tests/java/AuthenticationContextTest.java (revision 0)
@@ -0,0 +1,98 @@
+import static org.easymock.EasyMock.*;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.connector.Request;
+import org.jboss.security.negotiation.common.AuthenticationContext;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author jacob.orshalick@archongroup.com
+ * @version 1.0
+ */
+public class AuthenticationContextTest extends TestCase
+{
+ private AuthenticationContext authenticationContext;
+
+ private Request mockRequest;
+ private Realm mockRealm;
+ private Principal mockPrincipal;
+
+ @Override
+ public void setUp() throws IOException
+ {
+ mockPrincipal = createMock(Principal.class);
+ mockRealm = createMock(Realm.class);
+ mockRequest = new Request()
+ {
+ @Override
+ public Principal getUserPrincipal()
+ {
+ return mockPrincipal;
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ return "Basic asdfjjsadhafsj";
+ }
+ };
+ }
+
+ public void testIsAlreadyAuthenticated() throws IOException
+ {
+ AuthenticationContext authenticationContext =
+ new AuthenticationContext(mockRequest, null, null, null);
+
+ assertTrue(authenticationContext.isAuthenticated());
+ }
+
+ public void testIsAlreadyAuthenticated_NotAuthenticatedYet() throws IOException
+ {
+ mockPrincipal = null;
+
+ AuthenticationContext authenticationContext =
+ new AuthenticationContext(mockRequest, null, null, null);
+
+ assertFalse(authenticationContext.isAuthenticated());
+ }
+
+ public void testAuthenticate_Success() throws IOException
+ {
+ AuthenticationContext authenticationContext =
+ new AuthenticationContext(mockRequest, null, null, mockRealm);
+
+ expect(mockRealm.authenticate("testUser", "testCredentials")).andReturn(mockPrincipal);
+
+ replay(mockRealm);
+
+ authenticationContext.authenticate("testUser", "testCredentials");
+
+ verify(mockRealm);
+
+ assertTrue(authenticationContext.shouldRegisterPrincipal());
+ assertTrue(authenticationContext.isAuthenticated());
+ }
+
+
+ public void testAuthenticate_Failure() throws IOException
+ {
+ AuthenticationContext authenticationContext =
+ new AuthenticationContext(mockRequest, null, null, mockRealm);
+
+ expect(mockRealm.authenticate("testUser", "testCredentials")).andReturn(null);
+
+ replay(mockRealm);
+
+ authenticationContext.authenticate("testUser", "testCredentials");
+
+ verify(mockRealm);
+
+ assertFalse(authenticationContext.shouldRegisterPrincipal());
+ assertFalse(authenticationContext.isAuthenticated());
+ }
+}
Index: src/tests/java/org/jboss/security/negotiation/AuthorizationHeaderTest.java
===================================================================
--- src/tests/java/org/jboss/security/negotiation/AuthorizationHeaderTest.java (revision 0)
+++ src/tests/java/org/jboss/security/negotiation/AuthorizationHeaderTest.java (revision 0)
@@ -0,0 +1,72 @@
+/*
+ * Created on Dec 14, 2009
+ */
+package org.jboss.security.negotiation;
+
+import java.io.IOException;
+
+import org.jboss.security.negotiation.AuthorizationHeader;
+import org.jboss.util.Base64;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author jacob@solutionsfit.com
+ * @version 1.0
+ */
+public class AuthorizationHeaderTest extends TestCase
+{
+ private AuthorizationHeader header;
+ private String headerToken;
+ private String encodedHeaderToken;
+ private String headerString;
+
+ @Override
+ public void setUp() throws IOException
+ {
+ headerToken = "hello world";
+ encodedHeaderToken = Base64.encodeBytes(headerToken.getBytes());
+ headerString = "Negotiate " + encodedHeaderToken;
+ header = new AuthorizationHeader(headerString);
+ }
+
+ public void testGetAuthToken()
+ {
+ assertEquals(headerToken, new String(header.getAuthToken()));
+ }
+
+ public void testConstruct_NullHeader() throws IOException
+ {
+ header = new AuthorizationHeader(null);
+
+ assertTrue(header.isEmpty());
+ }
+
+ public void testConstruct_EmptyHeader() throws IOException
+ {
+ header = new AuthorizationHeader("");
+
+ assertTrue(header.isEmpty());
+ }
+
+ public void testConstruct_NoToken() throws IOException
+ {
+ try
+ {
+ header = new AuthorizationHeader("Negotiate");
+ fail("Should not allow an auth scheme with no token");
+ }
+ catch(IOException e)
+ {
+ // do nothing, expected
+ }
+ }
+
+ public void testDetermineAuthScheme()
+ {
+ String authScheme = header.determineAuthScheme(headerString);
+
+ assertEquals("Negotiate", authScheme);
+ }
+}
Index: src/tests/java/org/jboss/security/negotiation/DelegatingNegotiationAuthenticatorTest.java
===================================================================
--- src/tests/java/org/jboss/security/negotiation/DelegatingNegotiationAuthenticatorTest.java (revision 0)
+++ src/tests/java/org/jboss/security/negotiation/DelegatingNegotiationAuthenticatorTest.java (revision 0)
@@ -0,0 +1,89 @@
+/*
+ * Created on Dec 18, 2009
+ */
+package org.jboss.security.negotiation;
+
+import static org.easymock.EasyMock.*;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.catalina.connector.Request;
+import org.jboss.util.Base64;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author jacob.orshalick@archongroup.com
+ * @version 1.0
+ */
+public class DelegatingNegotiationAuthenticatorTest extends TestCase
+{
+ private DelegatingNegotiationAuthenticator authenticator;
+ private AuthorizationHeader authHeader;
+ private List fallbackAuthenticators;
+
+ private SchemeAuthenticator mockFallbackSchemeAuthenticator;
+
+ private String headerToken;
+ private String encodedHeaderToken;
+ private String headerString;
+
+ @Override
+ public void setUp() throws IOException
+ {
+ mockFallbackSchemeAuthenticator = createMock(SchemeAuthenticator.class);
+
+ headerToken = "hello:world";
+ encodedHeaderToken = Base64.encodeBytes(headerToken.getBytes());
+ authHeader = new AuthorizationHeader("Basic " + encodedHeaderToken);
+
+ fallbackAuthenticators = new ArrayList();
+ fallbackAuthenticators.add(mockFallbackSchemeAuthenticator);
+
+ authenticator = new DelegatingNegotiationAuthenticator(fallbackAuthenticators);
+ }
+
+ public void testFindFallbackSchemeAuthenticator_NoFallbacks()
+ {
+ fallbackAuthenticators.clear();
+
+ try
+ {
+ authenticator.findFallbackSchemeAuthenticator(authHeader);
+ }
+ catch(IOException e)
+ {
+ // do nothing, expected result
+ }
+ }
+
+ public void testFindFallbackSchemeAuthenticator_NoneFound() throws IOException
+ {
+ authHeader = new AuthorizationHeader("Digest " + encodedHeaderToken);
+
+ try
+ {
+ authenticator.findFallbackSchemeAuthenticator(authHeader);
+ }
+ catch(IOException e)
+ {
+ // do nothing, expected result
+ }
+ }
+
+ public void testFindFallbackSchemeAuthenticator_Basic() throws IOException
+ {
+ expect(mockFallbackSchemeAuthenticator.getScheme()).andReturn("Basic");
+
+ replay(mockFallbackSchemeAuthenticator);
+
+ assertSame(mockFallbackSchemeAuthenticator, authenticator.findFallbackSchemeAuthenticator(authHeader));
+
+ verify(mockFallbackSchemeAuthenticator);
+ }
+}