<commit_before>/*
 * Copyright (c) 2010-2017 Evolveum
 *
 * 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 com.evolveum.midpoint.web.security;

import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.api.*;
import com.evolveum.midpoint.security.enforcer.api.AccessDecision;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
import com.evolveum.midpoint.security.enforcer.api.ObjectSecurityConstraints;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.Producer;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.application.DescriptorLoader;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class MidPointGuiAuthorizationEvaluator implements SecurityEnforcer, SecurityContextManager, AccessDecisionManager {
	
	private static final Trace LOGGER = TraceManager.getTrace(MidPointGuiAuthorizationEvaluator.class);

	private final SecurityEnforcer securityEnforcer;
	private final SecurityContextManager securityContextManager;
	private final TaskManager taskManager;
	
    public MidPointGuiAuthorizationEvaluator(SecurityEnforcer securityEnforcer, SecurityContextManager securityContextManager, TaskManager taskManager) {
		super();
		this.securityEnforcer = securityEnforcer;
		this.securityContextManager = securityContextManager;
		this.taskManager = taskManager;
	}

    @Override
	public UserProfileService getUserProfileService() {
		return securityContextManager.getUserProfileService();
	}

	@Override
	public void setUserProfileService(UserProfileService userProfileService) {
		securityContextManager.setUserProfileService(userProfileService);
	}

    @Override
    public void setupPreAuthenticatedSecurityContext(Authentication authentication) {
    	securityContextManager.setupPreAuthenticatedSecurityContext(authentication);
    }

    @Override
    public void setupPreAuthenticatedSecurityContext(PrismObject<UserType> user) throws SchemaException {
    	securityContextManager.setupPreAuthenticatedSecurityContext(user);
	}
    
    @Override
	public void setupPreAuthenticatedSecurityContext(MidPointPrincipal principal) {
    	securityContextManager.setupPreAuthenticatedSecurityContext(principal);
	}
    
    @Override
	public boolean isAuthenticated() {
		return securityContextManager.isAuthenticated();
	}

    @Override
	public MidPointPrincipal getPrincipal() throws SecurityViolationException {
		return securityContextManager.getPrincipal();
	}
    
	@Override
	public <O extends ObjectType, T extends ObjectType> void failAuthorization(String operationUrl,
			AuthorizationPhaseType phase, AuthorizationParameters<O,T> params,
			OperationResult result) throws SecurityViolationException {
    	securityEnforcer.failAuthorization(operationUrl, phase, params, result);
	}

	// MidPoint pages invoke this method (through PageBase)
	@Override
	public <O extends ObjectType, T extends ObjectType> boolean isAuthorized(String operationUrl, AuthorizationPhaseType phase,
			AuthorizationParameters<O,T> params, OwnerResolver ownerResolver, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException {
		return securityEnforcer.isAuthorized(operationUrl, phase, params, ownerResolver, task, result);
	}
	
	@Override
	public boolean supports(ConfigAttribute attribute) {
		if (attribute instanceof SecurityConfig
				// class name equals, because WebExpressionConfigAttribute is non public class
				|| "org.springframework.security.web.access.expression.WebExpressionConfigAttribute".equals(attribute.getClass().getName())) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public boolean supports(Class<?> clazz) {
		if (MethodInvocation.class.isAssignableFrom(clazz)) {
			return true;
		} else if (FilterInvocation.class.isAssignableFrom(clazz)) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public <O extends ObjectType, T extends ObjectType> void authorize(String operationUrl, AuthorizationPhaseType phase,
			AuthorizationParameters<O,T> params, OwnerResolver ownerResolver, Task task, OperationResult result)
			throws SecurityViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException {
		securityEnforcer.authorize(operationUrl, phase, params, ownerResolver, task, result);
	}

	// Spring security invokes this method
	@Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
		
		// Too lound, just for testing
//		LOGGER.trace("decide input: authentication={}, object={}, configAttributes={}",
//				authentication, object, configAttributes);
				
        if (!(object instanceof FilterInvocation)) {
        	LOGGER.trace("DECIDE: PASS because object is not FilterInvocation, it is {}", object);
            return;
        }

        FilterInvocation filterInvocation = (FilterInvocation) object;
        if (isPermitAll(filterInvocation)) {
        	LOGGER.trace("DECIDE: authentication={}, object={}: ALLOW ALL (permitAll)",
        			authentication, object);
            return;
        }
        
        if ("/".equals(filterInvocation.getRequest().getServletPath())) {
        	// Special case, this is in fact "magic" redirect to home page or login page. It handles autz in its own way.
        	LOGGER.trace("DECIDE: authentication={}, object={}: ALLOW ALL (/)",
        			authentication, object);
            return;
        }

        List<String> requiredActions = new ArrayList<>(); 

        for (PageUrlMapping urlMapping : PageUrlMapping.values()) {
            addSecurityConfig(filterInvocation, requiredActions, urlMapping.getUrl(), urlMapping.getAction());
        }

        Map<String, DisplayableValue<String>[]> actions = DescriptorLoader.getActions();
        for (Map.Entry<String, DisplayableValue<String>[]> entry : actions.entrySet()) {
            addSecurityConfig(filterInvocation, requiredActions, entry.getKey(), entry.getValue());
        }
        
        if (requiredActions.isEmpty()) {
        	LOGGER.trace("DECIDE: DENY because determined empty required actions from {}", filterInvocation);
        	SecurityUtil.logSecurityDeny(object, ": Not authorized (page without authorizations)", null, requiredActions);
    		// Sparse exception method by purpose. We do not want to expose details to attacker.
    		// Better message is logged.
    		throw new AccessDeniedException("Not authorized");
        }
        <commit_msg>MID-4214 fixed context redirect<commit_after>/*
 * Copyright (c) 2010-2017 Evolveum
 *
 * 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 com.evolveum.midpoint.web.security;

import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.api.*;
import com.evolveum.midpoint.security.enforcer.api.AccessDecision;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
import com.evolveum.midpoint.security.enforcer.api.ObjectSecurityConstraints;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.Producer;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.application.DescriptorLoader;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class MidPointGuiAuthorizationEvaluator implements SecurityEnforcer, SecurityContextManager, AccessDecisionManager {
	
	private static final Trace LOGGER = TraceManager.getTrace(MidPointGuiAuthorizationEvaluator.class);

	private final SecurityEnforcer securityEnforcer;
	private final SecurityContextManager securityContextManager;
	private final TaskManager taskManager;
	
    public MidPointGuiAuthorizationEvaluator(SecurityEnforcer securityEnforcer, SecurityContextManager securityContextManager, TaskManager taskManager) {
		super();
		this.securityEnforcer = securityEnforcer;
		this.securityContextManager = securityContextManager;
		this.taskManager = taskManager;
	}

    @Override
	public UserProfileService getUserProfileService() {
		return securityContextManager.getUserProfileService();
	}

	@Override
	public void setUserProfileService(UserProfileService userProfileService) {
		securityContextManager.setUserProfileService(userProfileService);
	}

    @Override
    public void setupPreAuthenticatedSecurityContext(Authentication authentication) {
    	securityContextManager.setupPreAuthenticatedSecurityContext(authentication);
    }

    @Override
    public void setupPreAuthenticatedSecurityContext(PrismObject<UserType> user) throws SchemaException {
    	securityContextManager.setupPreAuthenticatedSecurityContext(user);
	}
    
    @Override
	public void setupPreAuthenticatedSecurityContext(MidPointPrincipal principal) {
    	securityContextManager.setupPreAuthenticatedSecurityContext(principal);
	}
    
    @Override
	public boolean isAuthenticated() {
		return securityContextManager.isAuthenticated();
	}

    @Override
	public MidPointPrincipal getPrincipal() throws SecurityViolationException {
		return securityContextManager.getPrincipal();
	}
    
	@Override
	public <O extends ObjectType, T extends ObjectType> void failAuthorization(String operationUrl,
			AuthorizationPhaseType phase, AuthorizationParameters<O,T> params,
			OperationResult result) throws SecurityViolationException {
    	securityEnforcer.failAuthorization(operationUrl, phase, params, result);
	}

	// MidPoint pages invoke this method (through PageBase)
	@Override
	public <O extends ObjectType, T extends ObjectType> boolean isAuthorized(String operationUrl, AuthorizationPhaseType phase,
			AuthorizationParameters<O,T> params, OwnerResolver ownerResolver, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException {
		return securityEnforcer.isAuthorized(operationUrl, phase, params, ownerResolver, task, result);
	}
	
	@Override
	public boolean supports(ConfigAttribute attribute) {
		if (attribute instanceof SecurityConfig
				// class name equals, because WebExpressionConfigAttribute is non public class
				|| "org.springframework.security.web.access.expression.WebExpressionConfigAttribute".equals(attribute.getClass().getName())) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public boolean supports(Class<?> clazz) {
		if (MethodInvocation.class.isAssignableFrom(clazz)) {
			return true;
		} else if (FilterInvocation.class.isAssignableFrom(clazz)) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public <O extends ObjectType, T extends ObjectType> void authorize(String operationUrl, AuthorizationPhaseType phase,
			AuthorizationParameters<O,T> params, OwnerResolver ownerResolver, Task task, OperationResult result)
			throws SecurityViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException {
		securityEnforcer.authorize(operationUrl, phase, params, ownerResolver, task, result);
	}

	// Spring security invokes this method
	@Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
		
		// Too lound, just for testing
//		LOGGER.trace("decide input: authentication={}, object={}, configAttributes={}",
//				authentication, object, configAttributes);
				
        if (!(object instanceof FilterInvocation)) {
        	LOGGER.trace("DECIDE: PASS because object is not FilterInvocation, it is {}", object);
            return;
        }

        FilterInvocation filterInvocation = (FilterInvocation) object;
        if (isPermitAll(filterInvocation)) {
        	LOGGER.trace("DECIDE: authentication={}, object={}: ALLOW ALL (permitAll)",
        			authentication, object);
            return;
        }

        String servletPath = filterInvocation.getRequest().getServletPath();
        if ("".equals(servletPath) || "/".equals(servletPath)) {
        	// Special case, this is in fact "magic" redirect to home page or login page. It handles autz in its own way.
        	LOGGER.trace("DECIDE: authentication={}, object={}: ALLOW ALL (/)",
        			authentication, object);
            return;
        }

        List<String> requiredActions = new ArrayList<>(); 

        for (PageUrlMapping urlMapping : PageUrlMapping.values()) {
            addSecurityConfig(filterInvocation, requiredActions, urlMapping.getUrl(), urlMapping.getAction());
        }

        Map<String, DisplayableValue<String>[]> actions = DescriptorLoader.getActions();
        for (Map.Entry<String, DisplayableValue<String>[]> entry : actions.entrySet()) {
            addSecurityConfig(filterInvocation, requiredActions, entry.getKey(), entry.getValue());
        }
        
        if (requiredActions.isEmpty()) {
        	LOGGER.trace("DECIDE: DENY because determined empty required actions from {}", filterInvocation);
        	SecurityUtil.logSecurityDeny(object, ": Not authorized (page without authorizations)", null, requiredActions);
    		// Sparse exception method by purpose. We do not want to expose details to attacker.
    		// Better message is logged.
    		throw new AccessDeniedException("Not authorized");
        }
        
{'fraction_non_alphanumeric': 0.08535242290748898, 'fraction_numerical': 0.001982378854625551, 'mean_word_length': 5.143775372124493, 'pattern_counts': {'":': 2, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1095133', 'n_tokens_mistral': 4879, 'n_tokens_neox': 4720, 'n_words': 1101}