BasicAuth.java

/***************************************************************************
   Copyright 2013 Emily Estes

   Licensed 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.
***************************************************************************/
package net.metanotion.web.concrete;


import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.metanotion.authident.AuthPassword;
import net.metanotion.authident.AuthUtils;
import net.metanotion.authident.LogoutEvent;
import net.metanotion.authident.UserToken;
import net.metanotion.util.Base64;
import net.metanotion.util.Dispatcher;
import net.metanotion.util.Message;
import net.metanotion.util.Pair;
import net.metanotion.util.SecureString;
import net.metanotion.util.StateMachine;
import net.metanotion.util.Unknown;

import net.metanotion.web.HttpStatus;
import net.metanotion.web.HttpValues;
import net.metanotion.web.RequestObject;

/** A Service that implements HTTP Basic Authentication.
	Due to the nature of HTTP Basic authentication this service primarily functions by wrapping a dispatcher with a
	delayed invocation inside  a special message object. If the browser sends an Authorization header, this class will
	authenticate the user, but it will not send the <code>WWW-Authenticate</code> header unless the "authRequired"
	property on the Dispatcher is set to true. Which generally means that browsers will not attempt to authenticate the
	user at all. In general, authRequired should therefore be set to true, but it will force all interactions with your
	site to be authenticated, so it's best to only use this inside another dispatcher(e.g. for just relevant prefixes
	within the Prefix dispatcher, etc.). */
public final class BasicAuth {
	private static final Logger logger = LoggerFactory.getLogger(BasicAuth.class);
	/** Several HTTP responses need empty lists. */
	private static final List<Map.Entry<String,Object>> emptyList = Collections.emptyList();

	/** Password authentication provider used to authenticate users. */
	private final AuthPassword<? extends AuthPassword> pw;
	/** The string constant name of the realm to send with authentication requests. */
	private final String httpRealm;
	/** The cached HTTP response to send when a user needs to authenticate to access a resource. */
	private final HttpValues authHeader;

	/** Create a new BasicAuth service for a specific HTTP realm.
		@param pw The username/password authentication service to use for authentication.
		@param httpRealm The HTTP Realm to send to the browser when prompting for credentials.
	*/
	public BasicAuth(final AuthPassword<? extends AuthPassword> pw, final String httpRealm) {
		this.pw = pw;
		this.httpRealm = httpRealm;
		final List<Map.Entry<String,Object>> realmHeader =
			Arrays.asList(
				(Map.Entry<String,Object>) new Pair<String,Object>("WWW-Authenticate", "Basic realm=\"" + httpRealm + "\""));
		this.authHeader = new SimpleHttpValues(realmHeader, emptyList, "", HttpStatus.UNAUTHORIZED.codeNumber());
	}


	/** The HTTP "Authorization:" header value is a base 64 encoded value that begins with the unencoded
		string "Basic "(the word basic followed by a space). This is 6 characters long.
	*/
	private static final int AUTHORIZATION_HEADER_PREFIX_LENGTH = 6;

	/** This is the method called by the dispatcher for this service.
		@param ut The user token for the authenticated user or null if there is no authenticated user.
		@param sm The session state machine which will receive the authentication events.
		@param ro The request object for this HTTP Request so we can examine the appropriate headers.
		@return true if the user has been successfully authenticated, false otherwise.
	*/
	private boolean authorized(final UserToken ut, final StateMachine sm, final RequestObject ro) {
		try {
			final String base64Encoded = ro.getHeader("Authorization");
			if(base64Encoded == null) { return (ut != null); }
			if(base64Encoded.startsWith("Basic ")) {
				final String base64EncodedCredentials = base64Encoded.substring(AUTHORIZATION_HEADER_PREFIX_LENGTH);
				/* The credentials are a username password pair separated by a ":"(which implies that a ":" is not
				permitted in a username.) */
				final String[] credentials = (new String(Base64.decode(base64EncodedCredentials), "UTF-8")).split(":", 2);
				final SecureString password = new SecureString(credentials[1]);
 				credentials[1] = null;
				return AuthUtils.externalAuthenticate(pw, sm, credentials[0], password);
			}
		} catch (Exception e) {
			logger.error("Error", e);
		}
		sm.nextState(LogoutEvent.LOGOUT);
		return false;
	}

	/** The HTTP Response to send to indicate the user should be prompted for credentials.
		@return An HTTP 401 Unauthorized and the WWW-Authenticate header with the realm to provide credentials for.
	*/
	private HttpValues requireAuth() { return authHeader; }

	/** Protect a dispatcher with Basic Authentication.
		@param <O> The final receiver type of the dispatcher this dispatcher is wrapping.
		@param child The dispatcher to protect.
		@param authRequired If this is true, requests will only be dispatched to child if the user has successfully
			authenticated.
		@return A dispatcher to handle HTTP requests meant for child.
	*/
	public static <O extends Object> Dispatcher<Unknown,RequestObject> dispatcher(final Dispatcher<O, RequestObject> child,
			final boolean authRequired) {
		return new BasicAuthDispatcher<O>(child, authRequired);
	}

	/** A dispatcher to wrap calls to a child dispatcher with a "before" method that authenticates the user. */
	private static final class BasicAuthDispatcher<I> implements Dispatcher<Unknown,RequestObject> {
		/** The dispatcher used to delegate authenticated user requests to. */
		private final Dispatcher<I, RequestObject> child;
		/** True of authentication is required to access the resources protected by this dispatcher. */
		private final boolean authRequired;
		/** Create a dispatcher to protect a set of resources.
			@param child The dispatcher to delegated authenticated requests to.
			@param authRequired True if authentication is required to access the resources protected by this
				dispatcher. */
		public BasicAuthDispatcher(final Dispatcher<I, RequestObject> child, final boolean authRequired) {
			this.child = child;
			this.authRequired = authRequired;
		}

		/** Rather than decode the message to be sent to the child dispatcher, we make a message object that will,
			when invoked, decide whether the user is authenticated(we have to wait until the message invoked because
			we won't have access to the session/Service component until then).
			@param ro The request object to dispatch the request object.
			@return A message object that checks the authentication of the request before delegating to the child
				dispatcher.
		*/
		@Override public Message<Unknown> dispatch(final RequestObject ro) { return new Msg<I>(ro, child, authRequired); }

		/** A delegating/delayed message implementation. This message will look up the appropriate services and
			authenticate the user, and, if appropriate will delegate the request to the child dispatcher and invoke the
			resulting message on the service/component.
		*/
		private static final class Msg<I> implements Message<Unknown> {
			/** The request object to pass to the delegated dispatcher if authentication succeeds. */
			private final RequestObject ro;
			/** The child dispatcher we will delegate authorized requests to. */
			private final Dispatcher<I, RequestObject> child;
			/** True if authentication si required. */
			private final boolean authRequired;

			/** Create a message object that protects the delegated dispatcher.
				@param ro The request object to use if the authentication succeeds.
				@param child The dispatcher to use if the authentication succeeds.
				@param authRequired True if authentication is required.
			*/
			public Msg(final RequestObject ro, final Dispatcher<I, RequestObject> child, final boolean authRequired) {
				this.ro = ro;
				this.child = child;
				this.authRequired = authRequired;
			}

			@Override public Class<Unknown> receiverType() { return Unknown.class; }

			/** <p>This is where authentication actually happens. The session will provided the message as the
				parameter and the services required for authentication will be looked up: chiefly, we need an instance
				of BasicAuth so we can get the HTTP Realm and password authentication service. A session state machine
				so we can change the session state between authenticated or not, and a WhoAmI service so we can see if
				the user is already authenticated..</p>

				<p>After the authenticated has been established(if required), the RequestObject is decoded with the
				child dispatcher and the invocation of the message is finally delegated to the service(the parameter to
				this message).</p>
				@param o The service/component instance representing the session we are going to dispatch on.
				@return An HTTP Response. If authentication is required and successful, it will be the response of the
					underlying dispatcher if that is not the case, it will be an HTTP response requiring the user to
					authenticate.
			*/
			@Override public Object call(final Unknown o) {
				final BasicAuth ba = o.lookupInterface(BasicAuth.class);
				final StateMachine sm = o.lookupInterface(StateMachine.class);
				final UserToken ut = getUT(o);
				if((!ba.authorized(ut, sm, ro)) && this.authRequired) { return ba.requireAuth(); }
				final Message<I> m = child.dispatch(ro);
				return m.call(o.lookupInterface(m.receiverType()));
			}

			private static final UserToken getUT(final Unknown o) {
				try {
					return o.lookupInterface(UserToken.class);
				} catch (final RuntimeException e) {
					return null;
				}
			}
		}
	}
}