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

org.structr.web.auth.UiAuthenticator Maven / Gradle / Ivy

Go to download

Structr is an open source framework based on the popular Neo4j graph database.

The newest version!
/**
 * Copyright (C) 2010-2016 Structr GmbH
 *
 * This file is part of Structr .
 *
 * Structr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Structr is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Structr.  If not, see .
 */
package org.structr.web.auth;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.structr.common.AccessMode;
import org.structr.common.PathHelper;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.Services;
import org.structr.core.app.StructrApp;
import org.structr.core.auth.Authenticator;
import org.structr.core.auth.exception.AuthenticationException;
import org.structr.core.auth.exception.UnauthorizedException;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.Person;
import org.structr.core.entity.Principal;
import org.structr.core.entity.ResourceAccess;
import org.structr.core.entity.SuperUser;
import org.structr.core.property.PropertyKey;
import org.structr.rest.auth.AuthHelper;
import org.structr.rest.auth.SessionHelper;
import org.structr.rest.service.HttpService;
import org.structr.web.entity.User;
import org.structr.web.resource.RegistrationResource;
import org.structr.web.servlet.HtmlServlet;

//~--- classes ----------------------------------------------------------------

/**
 *
 *
 */
public class UiAuthenticator implements Authenticator {

	private static final Logger logger       = Logger.getLogger(UiAuthenticator.class.getName());

	protected boolean examined = false;
	protected static boolean userAutoCreate;
	protected static boolean userAutoLogin;
	private static Class userClass;

	private enum Method { GET, PUT, POST, DELETE, HEAD, OPTIONS }
	private static final Map methods = new LinkedHashMap();

	// HTTP methods
	static {

		methods.put("GET", Method.GET);
		methods.put("PUT", Method.PUT);
		methods.put("POST", Method.POST);
		methods.put("HEAD", Method.HEAD);
		methods.put("DELETE", Method.DELETE);
		methods.put("OPTIONS", Method.OPTIONS);

	}

	// access flags
	public static final long FORBIDDEN		= 0;
	public static final long AUTH_USER_GET		= 1;
	public static final long AUTH_USER_PUT		= 2;
	public static final long AUTH_USER_POST		= 4;
	public static final long AUTH_USER_DELETE	= 8;

	public static final long NON_AUTH_USER_GET	= 16;
	public static final long NON_AUTH_USER_PUT	= 32;
	public static final long NON_AUTH_USER_POST	= 64;
	public static final long NON_AUTH_USER_DELETE	= 128;

	public static final long AUTH_USER_OPTIONS	= 256;
	public static final long NON_AUTH_USER_OPTIONS	= 512;

	public static final long AUTH_USER_HEAD		= 1024;
	public static final long NON_AUTH_USER_HEAD	= 2048;

	//~--- methods --------------------------------------------------------

	/**
	 * Examine request and try to find a user.
	 *
	 * First, check session id, then try external (OAuth) authentication,
	 * finally, check standard login by credentials.
	 *
	 * @param request
	 * @param response
	 * @return security context
	 * @throws FrameworkException
	 */
	@Override
	public SecurityContext initializeAndExamineRequest(final HttpServletRequest request, final HttpServletResponse response) throws FrameworkException {

		// Initialize custom user class
		getUserClass();

		SecurityContext securityContext;

		Principal user = SessionHelper.checkSessionAuthentication(request);

		if (user == null) {

			user = checkExternalAuthentication(request, response);

		}

		if (user == null) {

			user = getUser(request, true);

		}

		if (user == null) {

			// If no user could be determined, assume frontend access
			securityContext = SecurityContext.getInstance(user, request, AccessMode.Frontend);

		} else {


			if (user instanceof SuperUser) {

				securityContext = SecurityContext.getSuperUserInstance(request);

			} else {

				securityContext = SecurityContext.getInstance(user, request, AccessMode.Backend);

			}

		}

		securityContext.setAuthenticator(this);

		// Check CORS settings (Cross-origin resource sharing, see http://en.wikipedia.org/wiki/Cross-origin_resource_sharing)
		final String origin = request.getHeader("Origin");
		if (!StringUtils.isBlank(origin)) {

			final Services services = Services.getInstance();

			response.setHeader("Access-Control-Allow-Origin", origin);

			 // allow cross site resource sharing (read only)
			final String maxAge = services.getConfigurationValue(Services.ACCESS_CONTROL_MAX_AGE);
			if (StringUtils.isNotBlank(maxAge)) {
				response.setHeader("Access-Control-MaxAge", maxAge);
			}

			final String allowMethods = services.getConfigurationValue(Services.ACCESS_CONTROL_ALLOW_METHODS);
			if (StringUtils.isNotBlank(allowMethods)) {
				response.setHeader("Access-Control-Allow-Methods", allowMethods);
			}

			final String allowHeaders = services.getConfigurationValue(Services.ACCESS_CONTROL_ALLOW_HEADERS);
			if (StringUtils.isNotBlank(allowHeaders)) {
				response.setHeader("Access-Control-Allow-Headers", allowHeaders);
			}

			final String allowCredentials = services.getConfigurationValue(Services.ACCESS_CONTROL_ALLOW_CREDENTIALS);
			if (StringUtils.isNotBlank(allowCredentials)) {
				response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
			}

			final String exposeHeaders = services.getConfigurationValue(Services.ACCESS_CONTROL_EXPOSE_HEADERS);
			if (StringUtils.isNotBlank(exposeHeaders)) {
				response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
			}
		 }

		examined = true;

		// store a reference of the response object in SecurityContext
		// to be able to stream data directly from builtin functions
		securityContext.setResponse(response);

		return securityContext;

	}

	@Override
	public boolean hasExaminedRequest() {

		return examined;

	}

	@Override
	public void setUserAutoCreate(final boolean userAutoCreate) {

		UiAuthenticator.userAutoCreate = userAutoCreate;
	}

	@Override
	public void setUserAutoLogin(boolean userAutoLogin) {

		UiAuthenticator.userAutoLogin = userAutoLogin;
	}

	@Override
	public void checkResourceAccess(final SecurityContext securityContext, final HttpServletRequest request, final String rawResourceSignature, final String propertyView)
		throws FrameworkException {

		final ResourceAccess resourceAccess = ResourceAccess.findGrant(securityContext, rawResourceSignature);
		final Method method                 = methods.get(request.getMethod());
		final Principal user                = securityContext.getUser(false);
		final boolean validUser             = (user != null);

		// super user is always authenticated
		if (validUser && (user instanceof SuperUser || user.getProperty(Principal.isAdmin))) {
			return;
		}

		// no grants => no access rights
		if (resourceAccess == null) {

			logger.log(Level.INFO, "No resource access grant found for signature {0}. (URI: {1})", new Object[] { rawResourceSignature, securityContext.getCompoundRequestURI() } );

			throw new UnauthorizedException("Forbidden");

		} else {

			switch (method) {

				case GET :

					if (!validUser && resourceAccess.hasFlag(NON_AUTH_USER_GET)) {

						return;

					}

					if (validUser && resourceAccess.hasFlag(AUTH_USER_GET)) {

						return;

					}

					break;

				case PUT :

					if (!validUser && resourceAccess.hasFlag(NON_AUTH_USER_PUT)) {

						return;

					}

					if (validUser && resourceAccess.hasFlag(AUTH_USER_PUT)) {

						return;

					}

					break;

				case POST :

					if (!validUser && resourceAccess.hasFlag(NON_AUTH_USER_POST)) {

						return;

					}

					if (validUser && resourceAccess.hasFlag(AUTH_USER_POST)) {

						return;

					}

					break;

				case DELETE :

					if (!validUser && resourceAccess.hasFlag(NON_AUTH_USER_DELETE)) {

						return;

					}

					if (validUser && resourceAccess.hasFlag(AUTH_USER_DELETE)) {

						return;

					}

					break;

				case OPTIONS :

					if (!validUser && resourceAccess.hasFlag(NON_AUTH_USER_OPTIONS)) {

						return;

					}

					if (validUser && resourceAccess.hasFlag(AUTH_USER_OPTIONS)) {

						return;

					}

					break;

				case HEAD :

					if (!validUser && resourceAccess.hasFlag(NON_AUTH_USER_HEAD)) {

						return;

					}

					if (validUser && resourceAccess.hasFlag(AUTH_USER_HEAD)) {

						return;

					}

					break;
			}
		}

		logger.log(Level.INFO, "Resource access grant found for signature {0}, but method {1} not allowed for {2}.", new Object[] { rawResourceSignature, method, validUser ? "authenticated users" : "public users" } );

		throw new UnauthorizedException("Forbidden");

	}

	@Override
	public Principal doLogin(final HttpServletRequest request, final String emailOrUsername, final String password) throws AuthenticationException, FrameworkException {

		final Principal user = AuthHelper.getPrincipalForPassword(Person.eMail, emailOrUsername, password);
		if  (user != null) {

			final String allowLoginBeforeConfirmation = Services.getInstance().getConfigurationValue(RegistrationResource.ALLOW_LOGIN_BEFORE_CONFIRMATION);
			if (user.getProperty(User.confirmationKey) != null && Boolean.FALSE.equals(Boolean.parseBoolean(allowLoginBeforeConfirmation))) {
				logger.log(Level.WARNING, "Login as {0} not allowed before confirmation.", user);
				throw new AuthenticationException(AuthHelper.STANDARD_ERROR_MSG);
			}

			AuthHelper.doLogin(request, user);
		}

		return user;
	}

	@Override
	public void doLogout(final HttpServletRequest request) {

		try {
			final Principal user = getUser(request, false);
			if (user != null) {

				AuthHelper.doLogout(request, user);
			}

			final HttpSession session = request.getSession(false);
			if (session != null) {

				session.invalidate();
			}

		} catch (IllegalStateException | FrameworkException ex) {

			logger.log(Level.WARNING, "Error while logging out user", ex);
		}
	}

	/**
	 * This method checks all configured external authentication services.
	 *
	 * @param request
	 * @param response
	 * @return user
	 */
	protected static Principal checkExternalAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws FrameworkException {

		final String path = PathHelper.clean(request.getPathInfo());
		final String[] uriParts = PathHelper.getParts(path);

		logger.log(Level.FINE, "Checking external authentication ...");

		if (uriParts == null || uriParts.length != 3 || !("oauth".equals(uriParts[0]))) {

			logger.log(Level.FINE, "Incorrect URI parts for OAuth process, need /oauth//");
			return null;
		}

		final String name   = uriParts[1];
		final String action = uriParts[2];

		// Try to getValue an OAuth2 server for the given name
		final StructrOAuthClient oauthServer = StructrOAuthClient.getServer(name);

		if (oauthServer == null) {

			logger.log(Level.FINE, "No OAuth2 authentication server configured for {0}", path);
			return null;

		}

		if ("login".equals(action)) {

			try {

				response.sendRedirect(oauthServer.getEndUserAuthorizationRequestUri(request));
				return null;

			} catch (Exception ex) {

				logger.log(Level.SEVERE, "Could not send redirect to authorization server", ex);
			}

		} else if ("auth".equals(action)) {

			final String accessToken = oauthServer.getAccessToken(request);
			final SecurityContext superUserContext = SecurityContext.getSuperUserInstance();

			if (accessToken != null) {

				logger.log(Level.FINE, "Got access token {0}", accessToken);
				//securityContext.setAttribute("OAuthAccessToken", accessToken);

				String value = oauthServer.getCredential(request);
				logger.log(Level.FINE, "Got credential value: {0}", new Object[] { value });

				if (value != null) {

					PropertyKey credentialKey = oauthServer.getCredentialKey();

					Principal user = AuthHelper.getPrincipalForCredential(credentialKey, value);

					if (user == null && userAutoCreate) {

						user = RegistrationResource.createUser(superUserContext, credentialKey, value, true, userClass);

					}

					if (user != null) {

						AuthHelper.doLogin(request, user);
						HtmlServlet.setNoCacheHeaders(response);

						try {

							logger.log(Level.FINE, "Response status: {0}", response.getStatus());

							response.sendRedirect(oauthServer.getReturnUri());

						} catch (IOException ex) {

							logger.log(Level.SEVERE, "Could not redirect to {0}: {1}", new Object[]{oauthServer.getReturnUri(), ex});

						}
						return user;
					}
				}
			}
		}

		try {

			response.sendRedirect(oauthServer.getErrorUri());

		} catch (IOException ex) {

			logger.log(Level.SEVERE, "Could not redirect to {0}: {1}", new Object[]{ oauthServer.getReturnUri(), ex });

		}

		return null;

	}

	public static void writeUnauthorized(final HttpServletResponse response) throws IOException {

		response.setHeader("WWW-Authenticate", "BASIC realm=\"Restricted Access\"");
		response.sendError(HttpServletResponse.SC_UNAUTHORIZED);

	}

	public static void writeNotFound(final HttpServletResponse response) throws IOException {

		response.sendError(HttpServletResponse.SC_NOT_FOUND);

	}

	public static void writeInternalServerError(final HttpServletResponse response) {

		try {

			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

		} catch (IOException ignore) {}

	}

	@Override
	public boolean getUserAutoCreate() {
		return userAutoCreate;
	}

	@Override
	public boolean getUserAutoLogin() {
		return userAutoLogin;
	}

	@Override
	public Class getUserClass() {

		if (userClass == null) {

			String configuredCustomClassName = StructrApp.getConfigurationValue("Registration.customUserClass");
			if (StringUtils.isEmpty(configuredCustomClassName)) {
				configuredCustomClassName = User.class.getSimpleName();
			}
			userClass = StructrApp.getConfiguration().getNodeEntityClass(configuredCustomClassName);

		}

		return userClass;

	}

	@Override
	public Principal getUser(final HttpServletRequest request, final boolean tryLogin) throws FrameworkException {

		Principal user = null;

		if (request.getAttribute(SessionHelper.SESSION_IS_NEW) != null) {

			// First, check session (JSESSIONID cookie)
			final HttpSession session = request.getSession(false);

			if (session != null) {

				user = AuthHelper.getPrincipalForSessionId(session.getId());

			}
		}

		if (user == null) {

			// Second, check X-Headers
			String userName = request.getHeader("X-User");
			String password = request.getHeader("X-Password");
			String token    = request.getHeader("X-StructrSessionToken");

			// Try to authorize with a session token first
			if (token != null) {

				user = AuthHelper.getPrincipalForSessionId(token);

			} else if ((userName != null) && (password != null)) {

				if (tryLogin) {

					user = AuthHelper.getPrincipalForPassword(AbstractNode.name, userName, password);

				}
			}
		}

		return user;

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy