All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.milton.http.AuthenticationService Maven / Gradle / Ivy

/*
 * 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.
 */
package io.milton.http;

import io.milton.resource.GetableResource;
import io.milton.resource.Resource;
import io.milton.common.StringUtils;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author brad
 */
public class AuthenticationService {

	private static final Logger log = LoggerFactory.getLogger(AuthenticationService.class);
	public static final String ATT_AUTH_STATUS = "auth.service.status";
	public static final String ATT_AUTH_CALLED = "auth.service.called";
	private final List authenticationHandlers;
	private List externalIdentityProviders;
	private boolean disableExternal;
	private final String[] browserIds = {"msie", "firefox", "chrome", "safari", "opera"};

	/**
	 * Creates a AuthenticationService using the given handlers. Use this if you
	 * don't want the default of a BasicAuthHandler and a
	 * DigestAuthenticationHandler
	 *
	 * @param authenticationHandlers
	 */
	public AuthenticationService(List authenticationHandlers) {
		this.authenticationHandlers = authenticationHandlers;
	}

	/**
	 * Looks for an AuthenticationHandler which supports the given resource and
	 * authorization header, and then returns the result of that handler's
	 * authenticate method.
	 *
	 * Returns null if no handlers support the request
	 *
	 * Caches results so can be called multiple times in one request without
	 * performacne overhead
	 *
	 * @param resource
	 * @param request
	 * @return - null if no authentication was attempted. Otherwise, an
	 * AuthStatus object containing the Auth object and a boolean indicating
	 * whether the login succeeded
	 */
	public AuthStatus authenticate(Resource resource, Request request) {

		if (request.getAttributes().containsKey(ATT_AUTH_STATUS)) {
			return (AuthStatus) request.getAttributes().get(ATT_AUTH_STATUS);
		}

		// This is to prevent recursive calls into authenticate, which can happen
		// when resource location tries to do authentication
		if (request.getAttributes().containsKey(ATT_AUTH_CALLED)) {
			return null;
		}
		request.getAttributes().put(ATT_AUTH_CALLED, Boolean.TRUE);

		AuthStatus authStatus = _authenticate(resource, request);
		request.getAttributes().put(ATT_AUTH_STATUS, authStatus); // maybe null
		return authStatus;
	}

	private AuthStatus _authenticate(Resource resource, Request request) {
		log.trace("authenticate");
		Auth auth = request.getAuthorization();
		boolean preAuthenticated = (auth != null && auth.getTag() != null);
		if (preAuthenticated) {
			log.trace("request is pre-authenticated");
			return new AuthStatus(auth, false);
		}
		if (log.isTraceEnabled()) {
			log.trace("Checking authentication with auth handlers: " + authenticationHandlers.size());
			for (AuthenticationHandler h : authenticationHandlers) {
				log.trace(" - " + h);
			}
		}

		for (AuthenticationHandler h : authenticationHandlers) {
			if (h.supports(resource, request)) {
				Object loginToken = h.authenticate(resource, request);
				if (loginToken == null) {
					log.warn("authentication failed by AuthenticationHandler:" + h.getClass());
					return new AuthStatus(auth, true);
				} else {
					if (log.isTraceEnabled()) {
						log.trace("authentication passed by: " + h.getClass());
					}
					if (auth == null) { // some authentication handlers do not require an Auth object
						auth = new Auth(Auth.Scheme.FORM, null, loginToken);
						request.setAuthorization(auth);
					}
					auth.setTag(loginToken);
				}
			} else {
				if (log.isTraceEnabled()) {
					log.trace("handler does not support this resource and request. handler: " + h.getClass() + " resource: " + resource.getClass());
				}
			}
		}

		if (auth != null) {
			return new AuthStatus(auth, false);
		}

		log.trace("authentication did not locate a user, because no handler accepted the request");
		return null;
	}

	/**
	 * Generates a list of http authentication challenges, one for each
	 * supported authentication method, to be sent to the client.
	 *
	 * @param resource - the resoruce being requested
	 * @param request - the current request
	 * @return - a list of http challenges
	 */
	public List getChallenges(Resource resource, Request request) {
		List challenges = new ArrayList<>();
		for (AuthenticationHandler h : authenticationHandlers) {
			if (h.isCompatible(resource, request)) {
				log.debug("challenge for auth: " + h.getClass());
				h.appendChallenges(resource, request, challenges);
			} else {
				log.debug("not challenging for auth: " + h.getClass() + " for resource type: " + (resource == null ? "" : resource.getClass()));
			}
		}
		return challenges;
	}

	public List getAuthenticationHandlers() {
		return authenticationHandlers;
	}

	public List getExternalIdentityProviders() {
		return externalIdentityProviders;
	}

	public void setExternalIdentityProviders(List externalIdentityProviders) {
		this.externalIdentityProviders = externalIdentityProviders;
	}

	public boolean isDisableExternal() {
		return disableExternal;
	}

	public void setDisableExternal(boolean disableExternal) {
		this.disableExternal = disableExternal;
	}

	/**
	 * Determine if we can use external identify providers to authenticate this
	 * request
	 *
	 * @param resource
	 * @param request
	 * @return
	 */
	public boolean canUseExternalAuth(Resource resource, Request request) {
		if (isDisableExternal()) {
			log.trace("auth svc has disabled external auth");
			return false;
		}
		if (getExternalIdentityProviders() == null || getExternalIdentityProviders().isEmpty()) {
			log.trace("auth service has no external auth providers");
			return false;
		}

		// external authentication requires redirecting the user's browser to another
		// site and displaying a login form.
		// This can only be done if the resource
		// being requested is a webpage. This means that it is Getable and that it
		// has a content type of html
		if (resource instanceof GetableResource) {
			GetableResource gr = (GetableResource) resource;
			String ct = gr.getContentType("text/html");
			if (ct == null || !ct.contains("html")) {
				log.trace("is not of content type html");
				return false;
			}
		} else {
			log.trace("is not getable");
			return false; // not getable, so definitely not suitable for external auth
		}

		// This can only be done for user agents which support displaying forms and redirection
		// Ie typical web browsers are ok, webdav clients are generally not ok
		String ua = request.getUserAgentHeader();
		if (StringUtils.contains(ua.toLowerCase(), browserIds)) {
			log.trace("is a known web browser, so can offer external auth");
			return true;
		} else {
			log.trace("not a known web browser, so cannot offer external auth");
			return false;
		}

	}

	/**
	 * Determine if there are any credentials present. Note this does not check
	 * if the provided credentials are valid, only if they are available
	 *
	 * @param request
	 * @return
	 */
	public boolean authenticateDetailsPresent(Request request) {
		for (AuthenticationHandler h : authenticationHandlers) {
			if (h.credentialsPresent(request)) {
				return true;
			}
		}
		return false;
	}

	public static class AuthStatus {

		public final Auth auth;
		public final boolean loginFailed;

		public AuthStatus(Auth auth, boolean loginFailed) {
			this.auth = auth;
			this.loginFailed = loginFailed;
		}

		@Override
		public String toString() {
			if (auth == null) {
				return "AuthStatus: no creds";
			}
			if (loginFailed) {
				return "AuthStatus: login failed: " + auth.getUser();
			} else {
				return "AuthStatus: logged in: " + auth.getUser();
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy