/*
 * Copyright 2002-2017 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.web.server.util.matcher;

import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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

/**
 * Matches if all the provided {@link ServerWebExchangeMatcher} match
 * @author Rob Winch
 * @since 5.0
 * @see OrServerWebExchangeMatcher
 */
public class AndServerWebExchangeMatcher implements ServerWebExchangeMatcher {
	private final List<ServerWebExchangeMatcher> matchers;

	public AndServerWebExchangeMatcher(List<ServerWebExchangeMatcher> matchers) {
		Assert.notEmpty(matchers, "matchers cannot be empty");
		this.matchers = matchers;
	}

	public AndServerWebExchangeMatcher(ServerWebExchangeMatcher... matchers) {
		this(Arrays.asList(matchers));
	}

	/* (non-Javadoc)
	 * @see org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher#matches(org.springframework.web.server.ServerWebExchange)
	 */
	@Override
	public Mono<MatchResult> matches(ServerWebExchange exchange) {
		return Mono.defer(() -> {
			Map<String, Object> variables = new HashMap<>();
			return Flux.fromIterable(matchers)
				.flatMap(matcher -> matcher.matches(exchange))
				.doOnNext(matchResult -> variables.putAll(matchResult.getVariables()))
				.all(MatchResult::isMatch)
				.flatMap(allMatch -> allMatch ? MatchResult.match(variables) : MatchResult.notMatch());
		});
	}

	@Override
	public String toString() {
		return "AndServerWebExchangeMatcher{" +
				"matchers=" + matchers +
				'}';
	}
}


/*
 * Copyright 2002-2017 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.web.authentication.rememberme;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.util.Assert;

/**
 * {@link RememberMeServices} implementation based on Barry Jaspan's Improved
 * Persistent Login Cookie Best Practice.
 *
 * There is a slight modification to the described approach, in that the username is not
 * stored as part of the cookie but obtained from the persistent store via an
 * implementation of {@link PersistentTokenRepository}. The latter should place a unique
 * constraint on the series identifier, so that it is impossible for the same identifier
 * to be allocated to two different users.
 *
 *

 * User management such as changing passwords, removing users and setting user status
 * should be combined with maintenance of the user's persistent tokens.
 *

 *
 *

 * Note that while this class will use the date a token was created to check whether a
 * presented cookie is older than the configured <tt>tokenValiditySeconds</tt> property
 * and deny authentication in this case, it will not delete these tokens from storage. A
 * suitable batch process should be run periodically to remove expired tokens from the
 * database.
 *

 *
 * @author Luke Taylor
 * @since 2.0
 */
public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices {

	private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
	private SecureRandom random;

	public static final int DEFAULT_SERIES_LENGTH = 16;
	public static final int DEFAULT_TOKEN_LENGTH = 16;

	private int seriesLength = DEFAULT_SERIES_LENGTH;
	private int tokenLength = DEFAULT_TOKEN_LENGTH;

	public PersistentTokenBasedRememberMeServices(String key,
			UserDetailsService userDetailsService,
			PersistentTokenRepository tokenRepository) {
		super(key, userDetailsService);
		random = new SecureRandom();
		this.tokenRepository = tokenRepository;
	}

	/**
	 * Locates the presented cookie data in the token repository, using the series id. If
	 * the data compares successfully with that in the persistent store, a new token is
	 * generated and stored with the same series. The corresponding cookie value is set on
	 * the response.
	 *
	 * @param cookieTokens the series and token values
	 *
	 * @throws RememberMeAuthenticationException if there is no stored token corresponding
	 * to the submitted cookie, or if the token in the persistent store has expired.
	 * @throws InvalidCookieException if the cookie doesn't have two tokens as expected.
	 * @throws CookieTheftException if a presented series value is found, but the stored
	 * token is different from the one presented.
	 */
	protected UserDetails processAutoLoginCookie(String[] cookieTokens,
			HttpServletRequest request, HttpServletResponse response) {

		if (cookieTokens.length != 2) {
			throw new InvalidCookieException("Cookie token did not contain " + 2
					+ " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
		}

		final String presentedSeries = cookieTokens[0];
		final String presentedToken = cookieTokens[1];

		PersistentRememberMeToken token = tokenRepository
				.getTokenForSeries(presentedSeries);

		if (token == null) {
			// No series match, so we can't authenticate using this cookie
			throw new RememberMeAuthenticationException(
					"No persistent token found for series id: " + presentedSeries);
		}

		// We have a match for this user/series combination
		if (!presentedToken.equals(token.getTokenValue())) {
			// Token doesn't match series value. Delete all logins for this user and throw
			// an exception to warn them.
			tokenRepository.removeUserTokens(token.getUsername());

			throw new CookieTheftException(
					messages.getMessage(
							"PersistentTokenBasedRememberMeServices.cookieStolen",
							"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
		}

		if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
				.currentTimeMillis()) {
			throw new RememberMeAuthenticationException("Remember-me login has expired");
		}

		// Token also matches, so login is valid. Update the token value, keeping the
		// *same* series number.
		if (logger.isDebugEnabled()) {
			logger.debug("Refreshing persistent login token for user '"
					+ token.getUsername() + "', series '" + token.getSeries() + "'");
		}

		PersistentRememberMeToken newToken = new PersistentRememberMeToken(
				token.getUsername(), token.getSeries(), generateTokenData(), new Date());

		try {
			tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
					newToken.getDate());
			addCookie(newToken, request, response);
		}
		catch (Exception e) {
			logger.error("Failed to update token: ", e);
			throw new RememberMeAuthenticationException(
					"Autologin failed due to data access problem");
		}

		return getUserDetailsService().loadUserByUsername(token.getUsername());
	}

	/**
	 * Creates a new persistent login token with a new series number, stores the data in
	 * the persistent token repository and adds the corresponding cookie to the response.
	 *
	 */
	protected void onLoginSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication successfulAuthentication) {
		String username = successfulAuthentication.getName();

		logger.debug("Creating new persistent login for user " + username);

		PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
				username, generateSeriesData(), generateTokenData(), new Date());
		try {
			tokenRepository.createNewToken(persistentToken);
			addCookie(persistentToken, request, response);
		}
		catch (Exception e) {
			logger.error("Failed to save persistent token ", e);
		}
	}

	@Override
	public void logout(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) {
		super.logout(request, response, authentication);

		if (authentication != null) {
			tokenRepository.removeUserTokens(authentication.getName());
		}
	}

	protected String generateSeriesData() {
		byte[] newSeries = new byte[seriesLength];
		random.nextBytes(newSeries);
		return new String(Base64.getEncoder().encode(newSeries));
	}

	protected String generateTokenData() {
		byte[] newToken = new byte[tokenLength];
		random.nextBytes(newToken);
		return new String(Base64.getEncoder().encode(newToken));
	}

	private void addCookie(PersistentRememberMeToken token, HttpServletRequest request,
			HttpServletResponse response) {
		setCookie(new String[] { token.getSeries(), token.getTokenValue() },
				getTokenValiditySeconds(), request, response);
	}

	public void setSeriesLength(int seriesLength) {
		this.seriesLength = seriesLength;
	}

	public void setTokenLength(int tokenLength) {
		this.tokenLength = tokenLength;
	}

	@Override
	public void setTokenValiditySeconds(int tokenValiditySeconds) {
		Assert.isTrue(tokenValiditySeconds > 0,
				"tokenValiditySeconds must be positive for this implementation");
		super.setTokenValiditySeconds(tokenValiditySeconds);
	}
}


/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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
 *
 *      https://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 org.springframework.security.web.authentication.switchuser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.UrlPathHelper;

/**
 * Switch User processing filter responsible for user context switching.
 *

 * This filter is similar to Unix 'su' however for Spring Security-managed web
 * applications. A common use-case for this feature is the ability to allow
 * higher-authority users (e.g. ROLE_ADMIN) to switch to a regular user (e.g. ROLE_USER).
 *

 * This filter assumes that the user performing the switch will be required to be logged
 * in as normal (i.e. as a ROLE_ADMIN user). The user will then access a page/controller
 * that enables the administrator to specify who they wish to become (see
 * switchUserUrl).
 *

 * Note: This URL will be required to have appropriate security constraints configured
 * so that only users of that role can access it (e.g. ROLE_ADMIN).
 *

 * On a successful switch, the user's SecurityContext will be updated to
 * reflect the specified user and will also contain an additional
 * {@link org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority}
 * which contains the original user. Before switching, a check will be made on whether the
 * user is already currently switched, and any current switch will be exited to prevent
 * "nested" switches.
 *

 * To 'exit' from a user context, the user needs to access a URL (see
 * exitUserUrl) that will switch back to the original user as identified by
 * the ROLE_PREVIOUS_ADMINISTRATOR.
 *

 * To configure the Switch User Processing Filter, create a bean definition for the Switch
 * User processing filter and add to the filterChainProxy. Note that the filter must come
 * after the <tt>FilterSecurityInteceptor</tt> in the chain, in order to apply the
 * correct constraints to the <tt>switchUserUrl</tt>. Example:
 *
 *

 * <bean id="switchUserProcessingFilter" class="org.springframework.security.web.authentication.switchuser.SwitchUserFilter">
 *    <property name="userDetailsService" ref="userDetailsService" />
 *    <property name="switchUserUrl" value="/login/impersonate" />
 *    <property name="exitUserUrl" value="/logout/impersonate" />
 *    <property name="targetUrl" value="/index.jsp" />
 * </bean>
 *

 *
 * @author Mark St.Godard
 *
 * @see SwitchUserGrantedAuthority
 */
public class SwitchUserFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
	// ~ Static fields/initializers
	// =====================================================================================

	public static final String SPRING_SECURITY_SWITCH_USERNAME_KEY = "username";
	public static final String ROLE_PREVIOUS_ADMINISTRATOR = "ROLE_PREVIOUS_ADMINISTRATOR";

	// ~ Instance fields
	// ================================================================================================

	private ApplicationEventPublisher eventPublisher;
	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private RequestMatcher exitUserMatcher = createMatcher("/logout/impersonate");
	private RequestMatcher switchUserMatcher = createMatcher("/login/impersonate");
	private String targetUrl;
	private String switchFailureUrl;
	private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY;
	private String switchAuthorityRole = ROLE_PREVIOUS_ADMINISTRATOR;
	private SwitchUserAuthorityChanger switchUserAuthorityChanger;
	private UserDetailsService userDetailsService;
	private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
	private AuthenticationSuccessHandler successHandler;
	private AuthenticationFailureHandler failureHandler;

	// ~ Methods
	// ========================================================================================================

	@Override
	public void afterPropertiesSet() {
		Assert.notNull(this.userDetailsService, "userDetailsService must be specified");
		Assert.isTrue(this.successHandler != null || this.targetUrl != null,
				"You must set either a successHandler or the targetUrl");
		if (this.targetUrl != null) {
			Assert.isNull(this.successHandler,
					"You cannot set both successHandler and targetUrl");
			this.successHandler = new SimpleUrlAuthenticationSuccessHandler(
					this.targetUrl);
		}

		if (this.failureHandler == null) {
			this.failureHandler = this.switchFailureUrl == null
					? new SimpleUrlAuthenticationFailureHandler()
					: new SimpleUrlAuthenticationFailureHandler(this.switchFailureUrl);
		}
		else {
			Assert.isNull(this.switchFailureUrl,
					"You cannot set both a switchFailureUrl and a failureHandler");
		}
	}

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		// check for switch or exit request
		if (requiresSwitchUser(request)) {
			// if set, attempt switch and store original
			try {
				Authentication targetUser = attemptSwitchUser(request);

				// update the current context to the new target user
				SecurityContextHolder.getContext().setAuthentication(targetUser);

				// redirect to target url
				this.successHandler.onAuthenticationSuccess(request, response,
						targetUser);
			}
			catch (AuthenticationException e) {
				this.logger.debug("Switch User failed", e);
				this.failureHandler.onAuthenticationFailure(request, response, e);
			}

			return;
		}
		else if (requiresExitUser(request)) {
			// get the original authentication object (if exists)
			Authentication originalUser = attemptExitUser(request);

			// update the current context back to the original user
			SecurityContextHolder.getContext().setAuthentication(originalUser);

			// redirect to target url
			this.successHandler.onAuthenticationSuccess(request, response, originalUser);

			return;
		}

		chain.doFilter(request, response);
	}

	/**
	 * Attempt to switch to another user. If the user does not exist or is not active,
	 * return null.
	 *
	 * @return The new Authentication request if successfully switched to
	 * another user, null otherwise.
	 *
	 * @throws UsernameNotFoundException If the target user is not found.
	 * @throws LockedException if the account is locked.
	 * @throws DisabledException If the target user is disabled.
	 * @throws AccountExpiredException If the target user account is expired.
	 * @throws CredentialsExpiredException If the target user credentials are expired.
	 */
	protected Authentication attemptSwitchUser(HttpServletRequest request)
			throws AuthenticationException {
		UsernamePasswordAuthenticationToken targetUserRequest;

		String username = request.getParameter(this.usernameParameter);

		if (username == null) {
			username = "";
		}

		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Attempt to switch to user [" + username + "]");
		}

		UserDetails targetUser = this.userDetailsService.loadUserByUsername(username);
		this.userDetailsChecker.check(targetUser);

		// OK, create the switch user token
		targetUserRequest = createSwitchUserToken(request, targetUser);

		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Switch User Token [" + targetUserRequest + "]");
		}

		// publish event
		if (this.eventPublisher != null) {
			this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
					SecurityContextHolder.getContext().getAuthentication(), targetUser));
		}

		return targetUserRequest;
	}

	/**
	 * Attempt to exit from an already switched user.
	 *
	 * @param request The http servlet request
	 *
	 * @return The original Authentication object or null
	 * otherwise.
	 *
	 * @throws AuthenticationCredentialsNotFoundException If no
	 * Authentication associated with this request.
	 */
	protected Authentication attemptExitUser(HttpServletRequest request)
			throws AuthenticationCredentialsNotFoundException {
		// need to check to see if the current user has a SwitchUserGrantedAuthority
		Authentication current = SecurityContextHolder.getContext().getAuthentication();

		if (null == current) {
			throw new AuthenticationCredentialsNotFoundException(
					this.messages.getMessage("SwitchUserFilter.noCurrentUser",
							"No current user associated with this request"));
		}

		// check to see if the current user did actual switch to another user
		// if so, get the original source user so we can switch back
		Authentication original = getSourceAuthentication(current);

		if (original == null) {
			this.logger.debug("Could not find original user Authentication object!");
			throw new AuthenticationCredentialsNotFoundException(
					this.messages.getMessage("SwitchUserFilter.noOriginalAuthentication",
							"Could not find original Authentication object"));
		}

		// get the source user details
		UserDetails originalUser = null;
		Object obj = original.getPrincipal();

		if ((obj != null) && obj instanceof UserDetails) {
			originalUser = (UserDetails) obj;
		}

		// publish event
		if (this.eventPublisher != null) {
			this.eventPublisher.publishEvent(
					new AuthenticationSwitchUserEvent(current, originalUser));
		}

		return original;
	}

	/**
	 * Create a switch user token that contains an additional <tt>GrantedAuthority</tt>
	 * that contains the original Authentication object.
	 *
	 * @param request The http servlet request.
	 * @param targetUser The target user
	 *
	 * @return The authentication token
	 *
	 * @see SwitchUserGrantedAuthority
	 */
	private UsernamePasswordAuthenticationToken createSwitchUserToken(
			HttpServletRequest request, UserDetails targetUser) {

		UsernamePasswordAuthenticationToken targetUserRequest;

		// grant an additional authority that contains the original Authentication object
		// which will be used to 'exit' from the current switched user.

		Authentication currentAuth;

		try {
			// SEC-1763. Check first if we are already switched.
			currentAuth = attemptExitUser(request);
		}
		catch (AuthenticationCredentialsNotFoundException e) {
			currentAuth = SecurityContextHolder.getContext().getAuthentication();
		}

		GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(
				this.switchAuthorityRole, currentAuth);

		// get the original authorities
		Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();

		// Allow subclasses to change the authorities to be granted
		if (this.switchUserAuthorityChanger != null) {
			orig = this.switchUserAuthorityChanger.modifyGrantedAuthorities(targetUser,
					currentAuth, orig);
		}

		// add the new switch user authority
		List<GrantedAuthority> newAuths = new ArrayList<>(orig);
		newAuths.add(switchAuthority);

		// create the new authentication token
		targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
				targetUser.getPassword(), newAuths);

		// set details
		targetUserRequest
				.setDetails(this.authenticationDetailsSource.buildDetails(request));

		return targetUserRequest;
	}

	/**
	 * Find the original Authentication object from the current user's
	 * granted authorities. A successfully switched user should have a
	 * SwitchUserGrantedAuthority that contains the original source user
	 * Authentication object.
	 *
	 * @param current The current Authentication object
	 *
	 * @return The source user Authentication object or null
	 * otherwise.
	 */
	private Authentication getSourceAuthentication(Authentication current) {
		Authentication original = null;

		// iterate over granted authorities and find the 'switch user' authority
		Collection<? extends GrantedAuthority> authorities = current.getAuthorities();

		for (GrantedAuthority auth : authorities) {
			// check for switch user type of authority
			if (auth instanceof SwitchUserGrantedAuthority) {
				original = ((SwitchUserGrantedAuthority) auth).getSource();
				this.logger.debug("Found original switch user granted authority ["
						+ original + "]");
			}
		}

		return original;
	}

	/**
	 * Checks the request URI for the presence of <tt>exitUserUrl</tt>.
	 *
	 * @param request The http servlet request
	 *
	 * @return true if the request requires a exit user, false
	 * otherwise.
	 *
	 * @see SwitchUserFilter#setExitUserUrl(String)
	 */
	protected boolean requiresExitUser(HttpServletRequest request) {
		return this.exitUserMatcher.matches(request);
	}

	/**
	 * Checks the request URI for the presence of <tt>switchUserUrl</tt>.
	 *
	 * @param request The http servlet request
	 *
	 * @return true if the request requires a switch, false
	 * otherwise.
	 *
	 * @see SwitchUserFilter#setSwitchUserUrl(String)
	 */
	protected boolean requiresSwitchUser(HttpServletRequest request) {
		return this.switchUserMatcher.matches(request);
	}

	public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
			throws BeansException {
		this.eventPublisher = eventPublisher;
	}

	public void setAuthenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		Assert.notNull(authenticationDetailsSource,
				"AuthenticationDetailsSource required");
		this.authenticationDetailsSource = authenticationDetailsSource;
	}

	public void setMessageSource(MessageSource messageSource) {
		Assert.notNull(messageSource, "messageSource cannot be null");
		this.messages = new MessageSourceAccessor(messageSource);
	}

	/**
	 * Sets the authentication data access object.
	 *
	 * @param userDetailsService The <tt>UserDetailService</tt> which will be used to load
	 * information for the user that is being switched to.
	 */
	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	/**
	 * Set the URL to respond to exit user processing.
	 *
	 * @param exitUserUrl The exit user URL.
	 */
	public void setExitUserUrl(String exitUserUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(exitUserUrl),
				"exitUserUrl cannot be empty and must be a valid redirect URL");
		this.exitUserMatcher = createMatcher(exitUserUrl);
	}

	/**
	 * Set the matcher to respond to exit user processing. This is a shortcut for
	 * {@link #setExitUserMatcher(RequestMatcher)}
	 *
	 * @param exitUserMatcher The exit matcher to use
	 */
	public void setExitUserMatcher(RequestMatcher exitUserMatcher) {
		Assert.notNull(exitUserMatcher, "exitUserMatcher cannot be null");
		this.exitUserMatcher = exitUserMatcher;
	}

	/**
	 * Set the URL to respond to switch user processing. This is a shortcut for
	 * {@link #setSwitchUserMatcher(RequestMatcher)}
	 *
	 * @param switchUserUrl The switch user URL.
	 */
	public void setSwitchUserUrl(String switchUserUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(switchUserUrl),
				"switchUserUrl cannot be empty and must be a valid redirect URL");
		this.switchUserMatcher = createMatcher(switchUserUrl);
	}

	/**
	 * Set the matcher to respond to switch user processing.
	 *
	 * @param switchUserMatcher The switch user matcher.
	 */
	public void setSwitchUserMatcher(RequestMatcher switchUserMatcher) {
		Assert.notNull(switchUserMatcher, "switchUserMatcher cannot be null");
		this.switchUserMatcher = switchUserMatcher;
	}

	/**
	 * Sets the URL to go to after a successful switch / exit user request. Use
	 * {@link #setSuccessHandler(AuthenticationSuccessHandler) setSuccessHandler} instead
	 * if you need more customized behaviour.
	 *
	 * @param targetUrl The target url.
	 */
	public void setTargetUrl(String targetUrl) {
		this.targetUrl = targetUrl;
	}

	/**
	 * Used to define custom behaviour on a successful switch or exit user.
	 *

	 * Can be used instead of setting <tt>targetUrl</tt>.
	 */
	public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
		Assert.notNull(successHandler, "successHandler cannot be null");
		this.successHandler = successHandler;
	}

	/**
	 * Sets the URL to which a user should be redirected if the switch fails. For example,
	 * this might happen because the account they are attempting to switch to is invalid
	 * (the user doesn't exist, account is locked etc).
	 *

	 * If not set, an error message will be written to the response.
	 *

	 * Use {@link #setFailureHandler(AuthenticationFailureHandler) failureHandler} instead
	 * if you need more customized behaviour.
	 *
	 * @param switchFailureUrl the url to redirect to.
	 */
	public void setSwitchFailureUrl(String switchFailureUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(switchFailureUrl),
				"switchFailureUrl must be a valid redirect URL");
		this.switchFailureUrl = switchFailureUrl;
	}

	/**
	 * Used to define custom behaviour when a switch fails.
	 *

	 * Can be used instead of setting <tt>switchFailureUrl</tt>.
	 */
	public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
		Assert.notNull(failureHandler, "failureHandler cannot be null");
		this.failureHandler = failureHandler;
	}

	/**
	 * @param switchUserAuthorityChanger to use to fine-tune the authorities granted to
	 * subclasses (may be null if SwitchUserFilter should not fine-tune the authorities)
	 */
	public void setSwitchUserAuthorityChanger(
			SwitchUserAuthorityChanger switchUserAuthorityChanger) {
		this.switchUserAuthorityChanger = switchUserAuthorityChanger;
	}

	public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
		this.userDetailsChecker = userDetailsChecker;
	}

	/**
	 * Allows the parameter containing the username to be customized.
	 *
	 * @param usernameParameter the parameter name. Defaults to {@code username}
	 */
	public void setUsernameParameter(String usernameParameter) {
		this.usernameParameter = usernameParameter;
	}

	/**
	 * Allows the role of the switchAuthority to be customized.
	 *
	 * @param switchAuthorityRole the role name. Defaults to
	 * {@link #ROLE_PREVIOUS_ADMINISTRATOR}
	 */
	public void setSwitchAuthorityRole(String switchAuthorityRole) {
		Assert.notNull(switchAuthorityRole, "switchAuthorityRole cannot be null");
		this.switchAuthorityRole = switchAuthorityRole;
	}

	private static RequestMatcher createMatcher(String pattern) {
		return new AntPathRequestMatcher(pattern, null, true, new UrlPathHelper());
	}
}


/*
 * Copyright 2002-2016 the original author or authors.
 *
 * 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
 *
 *      https://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 sample.contact;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * Controller which handles simple, single request use cases such as index pages and
 * contact deletion.
 *
 * @author Luke Taylor
 * @since 3.0
 */
@Controller
public class IndexController {
	private final static Permission[] HAS_DELETE = new Permission[] {
			BasePermission.DELETE, BasePermission.ADMINISTRATION };
	private final static Permission[] HAS_ADMIN = new Permission[] { BasePermission.ADMINISTRATION };

	// ~ Instance fields
	// ================================================================================================

	@Autowired
	private ContactManager contactManager;
	@Autowired
	private PermissionEvaluator permissionEvaluator;

	// ~ Methods
	// ========================================================================================================

	/**
	 * The public index page, used for unauthenticated users.
	 */
	@RequestMapping(value = "/hello.htm", method = RequestMethod.GET)
	public ModelAndView displayPublicIndex() {
		Contact rnd = contactManager.getRandomContact();

		return new ModelAndView("hello", "contact", rnd);
	}

	/**
	 * The index page for an authenticated user.
	 *

	 * This controller displays a list of all the contacts for which the current user has
	 * read or admin permissions. It makes a call to {@link ContactManager#getAll()} which
	 * automatically filters the returned list using Spring Security's ACL mechanism (see
	 * the expression annotations on this interface for the details).
	 *

	 * In addition to rendering the list of contacts, the view will also include a "Del"
	 * or "Admin" link beside the contact, depending on whether the user has the
	 * corresponding permissions (admin permission is assumed to imply delete here). This
	 * information is stored in the model using the injected {@link PermissionEvaluator}
	 * instance. The implementation should be an instance of
	 * {@link AclPermissionEvaluator} or one which is compatible with Spring Security's
	 * ACL module.
	 */
	@RequestMapping(value = "/secure/index.htm", method = RequestMethod.GET)
	public ModelAndView displayUserContacts() {
		List<Contact> myContactsList = contactManager.getAll();
		Map<Contact, Boolean> hasDelete = new HashMap<>(
				myContactsList.size());
		Map<Contact, Boolean> hasAdmin = new HashMap<>(
				myContactsList.size());

		Authentication user = SecurityContextHolder.getContext().getAuthentication();

		for (Contact contact : myContactsList) {
			hasDelete.put(contact, Boolean.valueOf(permissionEvaluator.hasPermission(
					user, contact, HAS_DELETE)));
			hasAdmin.put(contact, Boolean.valueOf(permissionEvaluator.hasPermission(user,
					contact, HAS_ADMIN)));
		}

		Map<String, Object> model = new HashMap<>();
		model.put("contacts", myContactsList);
		model.put("hasDeletePermission", hasDelete);
		model.put("hasAdminPermission", hasAdmin);

		return new ModelAndView("index", "model", model);
	}
}


/*
 * Copyright 2002-2016 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.web.firewall;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import java.io.IOException;
import java.util.*;

/**
 * Request wrapper which ensures values of {@code servletPath} and {@code pathInfo} are
 * returned which are suitable for pattern matching against. It strips out path parameters
 * and extra consecutive '/' characters.
 *
 *
Path Parameters
 Parameters (as defined in RFC 2396) are stripped from the path
 * segments of the {@code servletPath} and {@code pathInfo} values of the request.
 *

 * The parameter sequence is demarcated by a semi-colon, so each segment is checked for
 * the occurrence of a ";" character and truncated at that point if it is present.
 *

 * The behaviour differs between servlet containers in how they interpret the servlet
 * spec, which does not clearly state what the behaviour should be. For consistency, we
 * make sure they are always removed, to avoid the risk of URL matching rules being
 * bypassed by the malicious addition of parameters to the path component.
 *
 * @author Luke Taylor
 */
final class RequestWrapper extends FirewalledRequest {
	private final String strippedServletPath;
	private final String strippedPathInfo;
	private boolean stripPaths = true;

	public RequestWrapper(HttpServletRequest request) {
		super(request);
		strippedServletPath = strip(request.getServletPath());
		String pathInfo = strip(request.getPathInfo());
		if (pathInfo != null && pathInfo.length() == 0) {
			pathInfo = null;
		}
		strippedPathInfo = pathInfo;
	}

	/**
	 * Removes path parameters from each path segment in the supplied path and truncates
	 * sequences of multiple '/' characters to a single '/'.
	 *
	 * @param path either the {@code servletPath} and {@code pathInfo} from the original
	 * request
	 *
	 * @return the supplied value, with path parameters removed and sequences of multiple
	 * '/' characters truncated, or null if the supplied path was null.
	 */
	private String strip(String path) {
		if (path == null) {
			return null;
		}

		int scIndex = path.indexOf(';');

		if (scIndex < 0) {
			int doubleSlashIndex = path.indexOf("//");
			if (doubleSlashIndex < 0) {
				// Most likely case, no parameters in any segment and no '//', so no
				// stripping required
				return path;
			}
		}

		StringTokenizer st = new StringTokenizer(path, "/");
		StringBuilder stripped = new StringBuilder(path.length());

		if (path.charAt(0) == '/') {
			stripped.append('/');
		}

		while (st.hasMoreTokens()) {
			String segment = st.nextToken();
			scIndex = segment.indexOf(';');

			if (scIndex >= 0) {
				segment = segment.substring(0, scIndex);
			}
			stripped.append(segment).append('/');
		}

		// Remove the trailing slash if the original path didn't have one
		if (path.charAt(path.length() - 1) != '/') {
			stripped.deleteCharAt(stripped.length() - 1);
		}

		return stripped.toString();
	}

	@Override
	public String getPathInfo() {
		return stripPaths ? strippedPathInfo : super.getPathInfo();
	}

	@Override
	public String getServletPath() {
		return stripPaths ? strippedServletPath : super.getServletPath();
	}

	@Override
	public RequestDispatcher getRequestDispatcher(String path) {
		return this.stripPaths ? new FirewalledRequestAwareRequestDispatcher(path)
				: super.getRequestDispatcher(path);
	}

	public void reset() {
		this.stripPaths = false;
	}

	/**
	 * Ensures {@link FirewalledRequest#reset()} is called prior to performing a forward.
	 * It then delegates work to the {@link RequestDispatcher} from the original
	 * {@link HttpServletRequest}.
	 *
	 * @author Rob Winch
	 */
	private class FirewalledRequestAwareRequestDispatcher implements RequestDispatcher {
		private final String path;

		/**
		 *
		 * @param path the {@code path} that will be used to obtain the delegate
		 * {@link RequestDispatcher} from the original {@link HttpServletRequest}.
		 */
		public FirewalledRequestAwareRequestDispatcher(String path) {
			this.path = path;
		}

		public void forward(ServletRequest request, ServletResponse response)
				throws ServletException, IOException {
			reset();
			getDelegateDispatcher().forward(request, response);
		}

		public void include(ServletRequest request, ServletResponse response)
				throws ServletException, IOException {
			getDelegateDispatcher().include(request, response);
		}

		private RequestDispatcher getDelegateDispatcher() {
			return RequestWrapper.super.getRequestDispatcher(path);
		}
	}
}


/*
 * Copyright 2002-2019 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.config.annotation.web.configuration;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor;
import org.springframework.security.context.DelegatingApplicationListener;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**
 * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
 * based security for Spring Security. It then exports the necessary beans. Customizations
 * can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter}
 * and exposing it as a {@link Configuration} or implementing
 * {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
 * configuration is imported when using {@link EnableWebSecurity}.
 *
 * @see EnableWebSecurity
 * @see WebSecurity
 *
 * @author Rob Winch
 * @author Keesun Baik
 * @since 3.2
 */
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
	private WebSecurity webSecurity;

	private Boolean debugEnabled;

	private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

	private ClassLoader beanClassLoader;

	@Autowired(required = false)
	private ObjectPostProcessor<Object> objectObjectPostProcessor;

	@Bean
	public static DelegatingApplicationListener delegatingApplicationListener() {
		return new DelegatingApplicationListener();
	}

	@Bean
	@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
		return webSecurity.getExpressionHandler();
	}

	/**
	 * Creates the Spring Security Filter Chain
	 * @return the {@link Filter} that represents the security filter chain
	 * @throws Exception
	 */
	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		return webSecurity.build();
	}

	/**
	 * Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
	 * tag support.
	 * @return the {@link WebInvocationPrivilegeEvaluator}
	 * @throws Exception
	 */
	@Bean
	@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
		return webSecurity.getPrivilegeEvaluator();
	}

	/**
	 * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>}
	 * instances used to create the web configuration.
	 *
	 * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
	 * {@link WebSecurity} instance
	 * @param webSecurityConfigurers the
	 * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
	 * create the web configuration
	 * @throws Exception
	 */
	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
		webSecurity = objectPostProcessor
				.postProcess(new WebSecurity(objectPostProcessor));
		if (debugEnabled != null) {
			webSecurity.debug(debugEnabled);
		}

		Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);

		Integer previousOrder = null;
		Object previousConfig = null;
		for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
			Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
			if (previousOrder != null && previousOrder.equals(order)) {
				throw new IllegalStateException(
						"@Order on WebSecurityConfigurers must be unique. Order of "
								+ order + " was already used on " + previousConfig + ", so it cannot be used on "
								+ config + " too.");
			}
			previousOrder = order;
			previousConfig = config;
		}
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}

	@Bean
	public static BeanFactoryPostProcessor conversionServicePostProcessor() {
		return new RsaKeyConversionServicePostProcessor();
	}

	@Bean
	public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
			ConfigurableListableBeanFactory beanFactory) {
		return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
	}

	/**
	 * A custom verision of the Spring provided AnnotationAwareOrderComparator that uses
	 * {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class
	 * instances for the {@link Order} annotation.
	 *
	 * @author Rob Winch
	 * @since 3.2
	 */
	private static class AnnotationAwareOrderComparator extends OrderComparator {
		private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();

		@Override
		protected int getOrder(Object obj) {
			return lookupOrder(obj);
		}

		private static int lookupOrder(Object obj) {
			if (obj instanceof Ordered) {
				return ((Ordered) obj).getOrder();
			}
			if (obj != null) {
				Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
				Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
				if (order != null) {
					return order.value();
				}
			}
			return Ordered.LOWEST_PRECEDENCE;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.
	 * springframework.core.type.AnnotationMetadata)
	 */
	public void setImportMetadata(AnnotationMetadata importMetadata) {
		Map<String, Object> enableWebSecurityAttrMap = importMetadata
				.getAnnotationAttributes(EnableWebSecurity.class.getName());
		AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
				.fromMap(enableWebSecurityAttrMap);
		debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
		if (webSecurity != null) {
			webSecurity.debug(debugEnabled);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.
	 * lang.ClassLoader)
	 */
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}
}


/*
 * Copyright 2002-2016 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.messaging.access.expression;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.expression.Expression;
import org.springframework.messaging.Message;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource;
import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource;
import org.springframework.security.messaging.util.matcher.MessageMatcher;

/**
 * A class used to create a {@link MessageSecurityMetadataSource} that uses
 * {@link MessageMatcher} mapped to Spring Expressions.
 *
 * @since 4.0
 * @author Rob Winch
 */
public final class ExpressionBasedMessageSecurityMetadataSourceFactory {

	/**
	 * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher}
	 * mapped to Spring Expressions. Each entry is considered in order and only the first
	 * match is used.
	 *
	 * For example:
	 *
	 *

	 *     LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
	 *
	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
	 *

	 *
	 *

	 * If our destination is "/public/hello", it would match on "/public/**" and on "/**".
	 * However, only "/public/**" would be used since it is the first entry. That means
	 * that a destination of "/public/hello" will be mapped to "permitAll".
	 *
	 *

	 * For a complete listing of expressions see {@link MessageSecurityExpressionRoot}
	 *
	 * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings
	 * that are turned into an Expression using
	 * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()}
	 * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null.
	 */
	public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(
			LinkedHashMap<MessageMatcher<?>, String> matcherToExpression) {
		return createExpressionMessageMetadataSource(matcherToExpression, new DefaultMessageSecurityExpressionHandler<>());
	}

	/**
	 * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher}
	 * mapped to Spring Expressions. Each entry is considered in order and only the first
	 * match is used.
	 *
	 * For example:
	 *
	 *

	 *     LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
	 *
	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
	 *

	 *
	 *

	 * If our destination is "/public/hello", it would match on "/public/**" and on "/**".
	 * However, only "/public/**" would be used since it is the first entry. That means
	 * that a destination of "/public/hello" will be mapped to "permitAll".
	 *

	 *
	 *

	 * For a complete listing of expressions see {@link MessageSecurityExpressionRoot}
	 *

	 *
	 * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings
	 * that are turned into an Expression using
	 * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()}
	 * @param handler the {@link SecurityExpressionHandler} to use
	 * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null.
	 */
	public static MessageSecurityMetadataSource createExpressionMessageMetadataSource(
			LinkedHashMap<MessageMatcher<?>, String> matcherToExpression, SecurityExpressionHandler<Message<Object>> handler) {

		LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>> matcherToAttrs = new LinkedHashMap<MessageMatcher<?>, Collection<ConfigAttribute>>();

		for (Map.Entry<MessageMatcher<?>, String> entry : matcherToExpression.entrySet()) {
			MessageMatcher<?> matcher = entry.getKey();
			String rawExpression = entry.getValue();
			Expression expression = handler.getExpressionParser().parseExpression(
					rawExpression);
			ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression);
			matcherToAttrs.put(matcher, Arrays.asList(attribute));
		}
		return new DefaultMessageSecurityMetadataSource(matcherToAttrs);
	}

	private ExpressionBasedMessageSecurityMetadataSourceFactory() {
	}
}


/*
 * Copyright 2002-2019 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.oauth2.jwt;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.RemoteKeySourceException;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.ImmutableSecret;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.Resource;
import com.nimbusds.jose.util.ResourceRetriever;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.nimbusds.jwt.proc.JWTProcessor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A low-level Nimbus implementation of {@link JwtDecoder} which takes a raw Nimbus configuration.
 *
 * @author Josh Cummings
 * @author Joe Grandja
 * @since 5.2
 */
public final class NimbusJwtDecoder implements JwtDecoder {
	private static final String DECODING_ERROR_MESSAGE_TEMPLATE =
			"An error occurred while attempting to decode the Jwt: %s";

	private final JWTProcessor<SecurityContext> jwtProcessor;

	private Converter<Map<String, Object>, Map<String, Object>> claimSetConverter =
			MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
	private OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefault();

	/**
	 * Configures a {@link NimbusJwtDecoder} with the given parameters
	 *
	 * @param jwtProcessor - the {@link JWTProcessor} to use
	 */
	public NimbusJwtDecoder(JWTProcessor<SecurityContext> jwtProcessor) {
		Assert.notNull(jwtProcessor, "jwtProcessor cannot be null");
		this.jwtProcessor = jwtProcessor;
	}

	/**
	 * Use this {@link Jwt} Validator
	 *
	 * @param jwtValidator - the Jwt Validator to use
	 */
	public void setJwtValidator(OAuth2TokenValidator<Jwt> jwtValidator) {
		Assert.notNull(jwtValidator, "jwtValidator cannot be null");
		this.jwtValidator = jwtValidator;
	}

	/**
	 * Use the following {@link Converter} for manipulating the JWT's claim set
	 *
	 * @param claimSetConverter the {@link Converter} to use
	 */
	public void setClaimSetConverter(Converter<Map<String, Object>, Map<String, Object>> claimSetConverter) {
		Assert.notNull(claimSetConverter, "claimSetConverter cannot be null");
		this.claimSetConverter = claimSetConverter;
	}

	/**
	 * Decode and validate the JWT from its compact claims representation format
	 *
	 * @param token the JWT value
	 * @return a validated {@link Jwt}
	 * @throws JwtException
	 */
	@Override
	public Jwt decode(String token) throws JwtException {
		JWT jwt = parse(token);
		if (jwt instanceof SignedJWT) {
			Jwt createdJwt = createJwt(token, jwt);
			return validateJwt(createdJwt);
		}
		throw new JwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
	}

	private JWT parse(String token) {
		try {
			return JWTParser.parse(token);
		} catch (Exception ex) {
			throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
		}
	}

	private Jwt createJwt(String token, JWT parsedJwt) {
		Jwt jwt;

		try {
			// Verify the signature
			JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);

			Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
			Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());

			Instant expiresAt = (Instant) claims.get(JwtClaimNames.EXP);
			Instant issuedAt = (Instant) claims.get(JwtClaimNames.IAT);
			jwt = new Jwt(token, issuedAt, expiresAt, headers, claims);
		} catch (RemoteKeySourceException ex) {
			if (ex.getCause() instanceof ParseException) {
				throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed Jwk set"));
			} else {
				throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
			}
		} catch (Exception ex) {
			if (ex.getCause() instanceof ParseException) {
				throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed payload"));
			} else {
				throw new JwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
			}
		}

		return jwt;
	}

	private Jwt validateJwt(Jwt jwt){
		OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt);
		if (result.hasErrors()) {
			String description = result.getErrors().iterator().next().getDescription();
			throw new JwtValidationException(
					String.format(DECODING_ERROR_MESSAGE_TEMPLATE, description),
					result.getErrors());
		}

		return jwt;
	}

	/**
	 * Use the given
	 * JWK Set uri.
	 *
	 * @param jwkSetUri the JWK Set uri to use
	 * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
	 */
	public static JwkSetUriJwtDecoderBuilder withJwkSetUri(String jwkSetUri) {
		return new JwkSetUriJwtDecoderBuilder(jwkSetUri);
	}

	/**
	 * Use the given public key to validate JWTs
	 *
	 * @param key the public key to use
	 * @return a {@link PublicKeyJwtDecoderBuilder} for further configurations
	 */
	public static PublicKeyJwtDecoderBuilder withPublicKey(RSAPublicKey key) {
		return new PublicKeyJwtDecoderBuilder(key);
	}

	/**
	 * Use the given {@code SecretKey} to validate the MAC on a JSON Web Signature (JWS).
	 *
	 * @param secretKey the {@code SecretKey} used to validate the MAC
	 * @return a {@link SecretKeyJwtDecoderBuilder} for further configurations
	 */
	public static SecretKeyJwtDecoderBuilder withSecretKey(SecretKey secretKey) {
		return new SecretKeyJwtDecoderBuilder(secretKey);
	}

	/**
	 * A builder for creating {@link NimbusJwtDecoder} instances based on a
	 * JWK Set uri.
	 */
	public static final class JwkSetUriJwtDecoderBuilder {
		private String jwkSetUri;
		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
		private RestOperations restOperations = new RestTemplate();

		private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
			Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
			this.jwkSetUri = jwkSetUri;
		}

		/**
		 * Use the given signing
		 * algorithm.
		 *
		 * @param jwsAlgorithm the algorithm to use
		 * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
		 */
		public JwkSetUriJwtDecoderBuilder jwsAlgorithm(JwsAlgorithm jwsAlgorithm) {
			Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
			this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm.getName());
			return this;
		}

		/**
		 * Use the given {@link RestOperations} to coordinate with the authorization servers indicated in the
		 * JWK Set uri
		 * as well as the
		 * Issuer.
		 *
		 * @param restOperations
		 * @return
		 */
		public JwkSetUriJwtDecoderBuilder restOperations(RestOperations restOperations) {
			Assert.notNull(restOperations, "restOperations cannot be null");
			this.restOperations = restOperations;
			return this;
		}

		JWTProcessor<SecurityContext> processor() {
			ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations);
			JWKSource<SecurityContext> jwkSource = new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever);
			JWSKeySelector<SecurityContext> jwsKeySelector =
					new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
			ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
			jwtProcessor.setJWSKeySelector(jwsKeySelector);

			// Spring Security validates the claim set independent from Nimbus
			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });

			return jwtProcessor;
		}

		/**
		 * Build the configured {@link NimbusJwtDecoder}.
		 *
		 * @return the configured {@link NimbusJwtDecoder}
		 */
		public NimbusJwtDecoder build() {
			return new NimbusJwtDecoder(processor());
		}

		private static URL toURL(String url) {
			try {
				return new URL(url);
			} catch (MalformedURLException ex) {
				throw new IllegalArgumentException("Invalid JWK Set URL \"" + url + "\" : " + ex.getMessage(), ex);
			}
		}

		private static class RestOperationsResourceRetriever implements ResourceRetriever {
			private final RestOperations restOperations;

			RestOperationsResourceRetriever(RestOperations restOperations) {
				Assert.notNull(restOperations, "restOperations cannot be null");
				this.restOperations = restOperations;
			}

			@Override
			public Resource retrieveResource(URL url) throws IOException {
				HttpHeaders headers = new HttpHeaders();
				headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));

				ResponseEntity<String> response;
				try {
					RequestEntity<Void> request = new RequestEntity<>(headers, HttpMethod.GET, url.toURI());
					response = this.restOperations.exchange(request, String.class);
				} catch (Exception ex) {
					throw new IOException(ex);
				}

				if (response.getStatusCodeValue() != 200) {
					throw new IOException(response.toString());
				}

				return new Resource(response.getBody(), "UTF-8");
			}
		}
	}

	/**
	 * A builder for creating {@link NimbusJwtDecoder} instances based on a public key.
	 */
	public static final class PublicKeyJwtDecoderBuilder {
		private JWSAlgorithm jwsAlgorithm;
		private RSAKey key;

		private PublicKeyJwtDecoderBuilder(RSAPublicKey key) {
			Assert.notNull(key, "key cannot be null");
			this.jwsAlgorithm = JWSAlgorithm.RS256;
			this.key = rsaKey(key);
		}

		private static RSAKey rsaKey(RSAPublicKey publicKey) {
			return new RSAKey.Builder(publicKey)
					.build();
		}

		/**
		 * Use the given signing
		 * algorithm.
		 *
		 * The value should be one of
		 * RS256, RS384, or RS512.
		 *
		 * @param signatureAlgorithm the algorithm to use
		 * @return a {@link PublicKeyJwtDecoderBuilder} for further configurations
		 */
		public PublicKeyJwtDecoderBuilder signatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
			Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null");
			this.jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName());
			return this;
		}

		JWTProcessor<SecurityContext> processor() {
			if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) {
				throw new IllegalStateException("The provided key is of type RSA; " +
						"however the signature algorithm is of some other type: " +
						this.jwsAlgorithm + ". Please indicate one of RS256, RS384, or RS512.");
			}

			JWKSet jwkSet = new JWKSet(this.key);
			JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(jwkSet);
			JWSKeySelector<SecurityContext> jwsKeySelector =
					new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
			DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
			jwtProcessor.setJWSKeySelector(jwsKeySelector);

			// Spring Security validates the claim set independent from Nimbus
			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });

			return jwtProcessor;
		}

		/**
		 * Build the configured {@link NimbusJwtDecoder}.
		 *
		 * @return the configured {@link NimbusJwtDecoder}
		 */
		public NimbusJwtDecoder build() {
			return new NimbusJwtDecoder(processor());
		}
	}

	/**
	 * A builder for creating {@link NimbusJwtDecoder} instances based on a {@code SecretKey}.
	 */
	public static final class SecretKeyJwtDecoderBuilder {
		private final SecretKey secretKey;
		private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;

		private SecretKeyJwtDecoderBuilder(SecretKey secretKey) {
			Assert.notNull(secretKey, "secretKey cannot be null");
			this.secretKey = secretKey;
		}

		/**
		 * Use the given
		 * algorithm
		 * when generating the MAC.
		 *
		 * The value should be one of
		 * HS256, HS384 or HS512.
		 *
		 * @param macAlgorithm the MAC algorithm to use
		 * @return a {@link SecretKeyJwtDecoderBuilder} for further configurations
		 */
		public SecretKeyJwtDecoderBuilder macAlgorithm(MacAlgorithm macAlgorithm) {
			Assert.notNull(macAlgorithm, "macAlgorithm cannot be null");
			this.jwsAlgorithm = JWSAlgorithm.parse(macAlgorithm.getName());
			return this;
		}

		/**
		 * Build the configured {@link NimbusJwtDecoder}.
		 *
		 * @return the configured {@link NimbusJwtDecoder}
		 */
		public NimbusJwtDecoder build() {
			return new NimbusJwtDecoder(processor());
		}

		JWTProcessor<SecurityContext> processor() {
			JWKSource<SecurityContext> jwkSource = new ImmutableSecret<>(this.secretKey);
			JWSKeySelector<SecurityContext> jwsKeySelector =
					new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
			DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
			jwtProcessor.setJWSKeySelector(jwsKeySelector);

			// Spring Security validates the claim set independent from Nimbus
			jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });

			return jwtProcessor;
		}
	}
}


/*
 * Copyright 2002-2015 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.web.authentication;

import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * An {@link AuthenticationFailureHandler} that delegates to other
 * {@link AuthenticationFailureHandler} instances based upon the type of
 * {@link AuthenticationException} passed into
 * {@link #onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)}
 * .
 *
 * @author Kazuki Shimizu
 * @since 4.0
 */
public class DelegatingAuthenticationFailureHandler implements
		AuthenticationFailureHandler {

	private final LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler> handlers;

	private final AuthenticationFailureHandler defaultHandler;

	/**
	 * Creates a new instance
	 *
	 * @param handlers a map of the {@link AuthenticationException} class to the
	 * {@link AuthenticationFailureHandler} that should be used. Each is considered in the
	 * order they are specified and only the first {@link AuthenticationFailureHandler} is
	 * ued. This parameter cannot specify null or empty.
	 * @param defaultHandler the default {@link AuthenticationFailureHandler} that should
	 * be used if none of the handlers matches. This parameter cannot specify null.
	 * @throws IllegalArgumentException if invalid argument is specified
	 */
	public DelegatingAuthenticationFailureHandler(
			LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler> handlers,
			AuthenticationFailureHandler defaultHandler) {
		Assert.notEmpty(handlers, "handlers cannot be null or empty");
		Assert.notNull(defaultHandler, "defaultHandler cannot be null");
		this.handlers = handlers;
		this.defaultHandler = defaultHandler;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		for (Map.Entry<Class<? extends AuthenticationException>, AuthenticationFailureHandler> entry : handlers
				.entrySet()) {
			Class<? extends AuthenticationException> handlerMappedExceptionClass = entry
					.getKey();
			if (handlerMappedExceptionClass.isAssignableFrom(exception.getClass())) {
				AuthenticationFailureHandler handler = entry.getValue();
				handler.onAuthenticationFailure(request, response, exception);
				return;
			}
		}
		defaultHandler.onAuthenticationFailure(request, response, exception);
	}

}


/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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
 *
 *      https://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 org.springframework.security.access.vote;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;

/**
 * Abstract implementation of {@link AccessDecisionManager}.
 *
 *

 * Handles configuration of a bean context defined list of {@link AccessDecisionVoter}s
 * and the access control behaviour if all voters abstain from voting (defaults to deny
 * access).
 */
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,
		InitializingBean, MessageSourceAware {
	// ~ Instance fields
	// ================================================================================================
	protected final Log logger = LogFactory.getLog(getClass());

	private List<AccessDecisionVoter<? extends Object>> decisionVoters;

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private boolean allowIfAllAbstainDecisions = false;

	protected AbstractAccessDecisionManager(
			List<AccessDecisionVoter<? extends Object>> decisionVoters) {
		Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");
		this.decisionVoters = decisionVoters;
	}

	// ~ Methods
	// ========================================================================================================

	public void afterPropertiesSet() throws Exception {
		Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
		Assert.notNull(this.messages, "A message source must be set");
	}

	protected final void checkAllowIfAllAbstainDecisions() {
		if (!this.isAllowIfAllAbstainDecisions()) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
	}

	public List<AccessDecisionVoter<? extends Object>> getDecisionVoters() {
		return this.decisionVoters;
	}

	public boolean isAllowIfAllAbstainDecisions() {
		return allowIfAllAbstainDecisions;
	}

	public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
		this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public boolean supports(ConfigAttribute attribute) {
		for (AccessDecisionVoter voter : this.decisionVoters) {
			if (voter.supports(attribute)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Iterates through all AccessDecisionVoters and ensures each can support
	 * the presented class.
	 *

	 * If one or more voters cannot support the presented class, false is
	 * returned.
	 *
	 * @param clazz the type of secured object being presented
	 * @return true if this type is supported
	 */
	public boolean supports(Class<?> clazz) {
		for (AccessDecisionVoter voter : this.decisionVoters) {
			if (!voter.supports(clazz)) {
				return false;
			}
		}

		return true;
	}
}


/*
 * Copyright 2002-2019 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.oauth2.server.resource.introspection;

import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.nimbusds.oauth2.sdk.TokenIntrospectionResponse;
import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import com.nimbusds.oauth2.sdk.id.Audience;
import reactor.core.publisher.Mono;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE;
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.CLIENT_ID;
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT;
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUED_AT;
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUER;
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.NOT_BEFORE;
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SCOPE;

/**
 * A Nimbus implementation of {@link ReactiveOAuth2TokenIntrospectionClient}
 *
 * @author Josh Cummings
 * @since 5.2
 */
public class NimbusReactiveOAuth2TokenIntrospectionClient implements ReactiveOAuth2TokenIntrospectionClient {
	private URI introspectionUri;
	private WebClient webClient;

	/**
	 * Creates a {@code OAuth2IntrospectionReactiveAuthenticationManager} with the provided parameters
	 *
	 * @param introspectionUri The introspection endpoint uri
	 * @param clientId The client id authorized to introspect
	 * @param clientSecret The client secret for the authorized client
	 */
	public NimbusReactiveOAuth2TokenIntrospectionClient(String introspectionUri, String clientId, String clientSecret) {
		Assert.hasText(introspectionUri, "introspectionUri cannot be empty");
		Assert.hasText(clientId, "clientId cannot be empty");
		Assert.notNull(clientSecret, "clientSecret cannot be null");

		this.introspectionUri = URI.create(introspectionUri);
		this.webClient = WebClient.builder()
				.defaultHeaders(h -> h.setBasicAuth(clientId, clientSecret))
				.build();
	}

	/**
	 * Creates a {@code OAuth2IntrospectionReactiveAuthenticationManager} with the provided parameters
	 *
	 * @param introspectionUri The introspection endpoint uri
	 * @param webClient The client for performing the introspection request
	 */
	public NimbusReactiveOAuth2TokenIntrospectionClient(String introspectionUri, WebClient webClient) {
		Assert.hasText(introspectionUri, "introspectionUri cannot be null");
		Assert.notNull(webClient, "webClient cannot be null");

		this.introspectionUri = URI.create(introspectionUri);
		this.webClient = webClient;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Mono<Map<String, Object>> introspect(String token) {
		return Mono.just(token)
				.flatMap(this::makeRequest)
				.flatMap(this::adaptToNimbusResponse)
				.map(this::parseNimbusResponse)
				.map(this::castToNimbusSuccess)
				.doOnNext(response -> validate(token, response))
				.map(this::convertClaimsSet)
				.onErrorMap(e -> !(e instanceof OAuth2IntrospectionException), this::onError);
	}

	private Mono<ClientResponse> makeRequest(String token) {
		return this.webClient.post()
				.uri(this.introspectionUri)
				.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_UTF8_VALUE)
				.body(BodyInserters.fromFormData("token", token))
				.exchange();
	}

	private Mono<HTTPResponse> adaptToNimbusResponse(ClientResponse responseEntity) {
		HTTPResponse response = new HTTPResponse(responseEntity.rawStatusCode());
		response.setHeader(HttpHeaders.CONTENT_TYPE, responseEntity.headers().contentType().get().toString());
		if (response.getStatusCode() != HTTPResponse.SC_OK) {
			throw new OAuth2IntrospectionException(
					"Introspection endpoint responded with " + response.getStatusCode());
		}
		return responseEntity.bodyToMono(String.class)
				.doOnNext(response::setContent)
				.map(body -> response);
	}

	private TokenIntrospectionResponse parseNimbusResponse(HTTPResponse response) {
		try {
			return TokenIntrospectionResponse.parse(response);
		} catch (Exception ex) {
			throw new OAuth2IntrospectionException(ex.getMessage(), ex);
		}
	}

	private TokenIntrospectionSuccessResponse castToNimbusSuccess(TokenIntrospectionResponse introspectionResponse) {
		if (!introspectionResponse.indicatesSuccess()) {
			throw new OAuth2IntrospectionException("Token introspection failed");
		}
		return (TokenIntrospectionSuccessResponse) introspectionResponse;
	}

	private void validate(String token, TokenIntrospectionSuccessResponse response) {
		// relying solely on the authorization server to validate this token (not checking 'exp', for example)
		if (!response.isActive()) {
			throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active");
		}
	}

	private Map<String, Object> convertClaimsSet(TokenIntrospectionSuccessResponse response) {
		Map<String, Object> claims = response.toJSONObject();
		if (response.getAudience() != null) {
			List<String> audience = response.getAudience().stream()
					.map(Audience::getValue).collect(Collectors.toList());
			claims.put(AUDIENCE, Collections.unmodifiableList(audience));
		}
		if (response.getClientID() != null) {
			claims.put(CLIENT_ID, response.getClientID().getValue());
		}
		if (response.getExpirationTime() != null) {
			Instant exp = response.getExpirationTime().toInstant();
			claims.put(EXPIRES_AT, exp);
		}
		if (response.getIssueTime() != null) {
			Instant iat = response.getIssueTime().toInstant();
			claims.put(ISSUED_AT, iat);
		}
		if (response.getIssuer() != null) {
			claims.put(ISSUER, issuer(response.getIssuer().getValue()));
		}
		if (response.getNotBeforeTime() != null) {
			claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant());
		}
		if (response.getScope() != null) {
			claims.put(SCOPE, Collections.unmodifiableList(response.getScope().toStringList()));
		}

		return claims;
	}

	private URL issuer(String uri) {
		try {
			return new URL(uri);
		} catch (Exception ex) {
			throw new OAuth2IntrospectionException("Invalid " + ISSUER + " value: " + uri);
		}
	}

	private OAuth2IntrospectionException onError(Throwable e) {
		return new OAuth2IntrospectionException(e.getMessage(), e);
	}
}


/*
 * Copyright 2004-2010 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.taglibs.authz;

import java.io.IOException;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.context.ApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.context.support.SecurityWebApplicationContextUtils;
import org.springframework.util.StringUtils;

/**
 * A base class for an <authorize> tag that is independent of the tag rendering
 * technology (JSP, Facelets). It treats tag attributes as simple strings rather than
 * strings that may contain expressions with the exception of the "access" attribute,
 * which is always expected to contain a Spring EL expression.
 *

 * Subclasses are expected to extract tag attribute values from the specific rendering
 * technology, evaluate them as expressions if necessary, and set the String-based
 * attributes of this class.
 *
 * @author Francois Beausoleil
 * @author Luke Taylor
 * @author Rossen Stoyanchev
 * @author Rob Winch
 * @since 3.1.0
 */
public abstract class AbstractAuthorizeTag {
	private String access;
	private String url;
	private String method = "GET";

	/**
	 * This method allows subclasses to provide a way to access the ServletRequest
	 * according to the rendering technology.
	 */
	protected abstract ServletRequest getRequest();

	/**
	 * This method allows subclasses to provide a way to access the ServletResponse
	 * according to the rendering technology.
	 */
	protected abstract ServletResponse getResponse();

	/**
	 * This method allows subclasses to provide a way to access the ServletContext
	 * according to the rendering technology.
	 */
	protected abstract ServletContext getServletContext();

	/**
	 * Make an authorization decision by considering all <authorize> tag attributes.
	 * The following are valid combinations of attributes:
	 *

	 *
access

	 *
url, method

	 *

	 * The above combinations are mutually exclusive and evaluated in the given order.
	 *
	 * @return the result of the authorization decision
	 * @throws IOException
	 */
	public boolean authorize() throws IOException {
		boolean isAuthorized;

		if (StringUtils.hasText(getAccess())) {
			isAuthorized = authorizeUsingAccessExpression();

		}
		else if (StringUtils.hasText(getUrl())) {
			isAuthorized = authorizeUsingUrlCheck();

		}
		else {
			isAuthorized = false;

		}

		return isAuthorized;
	}

	/**
	 * Make an authorization decision based on a Spring EL expression. See the
	 * "Expression-Based Access Control" chapter in Spring Security for details on what
	 * expressions can be used.
	 *
	 * @return the result of the authorization decision
	 * @throws IOException
	 */
	public boolean authorizeUsingAccessExpression() throws IOException {
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			return false;
		}

		SecurityExpressionHandler<FilterInvocation> handler = getExpressionHandler();

		Expression accessExpression;
		try {
			accessExpression = handler.getExpressionParser().parseExpression(getAccess());

		}
		catch (ParseException e) {
			IOException ioException = new IOException();
			ioException.initCause(e);
			throw ioException;
		}

		return ExpressionUtils.evaluateAsBoolean(accessExpression,
				createExpressionEvaluationContext(handler));
	}

	/**
	 * Allows the {@code EvaluationContext} to be customized for variable lookup etc.
	 */
	protected EvaluationContext createExpressionEvaluationContext(
			SecurityExpressionHandler<FilterInvocation> handler) {
		FilterInvocation f = new FilterInvocation(getRequest(), getResponse(),
				new FilterChain() {
					public void doFilter(ServletRequest request, ServletResponse response)
							throws IOException, ServletException {
						throw new UnsupportedOperationException();
					}
				});

		return handler.createEvaluationContext(SecurityContextHolder.getContext()
				.getAuthentication(), f);
	}

	/**
	 * Make an authorization decision based on the URL and HTTP method attributes. True is
	 * returned if the user is allowed to access the given URL as defined.
	 *
	 * @return the result of the authorization decision
	 * @throws IOException
	 */
	public boolean authorizeUsingUrlCheck() throws IOException {
		String contextPath = ((HttpServletRequest) getRequest()).getContextPath();
		Authentication currentUser = SecurityContextHolder.getContext()
				.getAuthentication();
		return getPrivilegeEvaluator().isAllowed(contextPath, getUrl(), getMethod(),
				currentUser);
	}

	public String getAccess() {
		return access;
	}

	public void setAccess(String access) {
		this.access = access;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = (method != null) ? method.toUpperCase() : null;
	}

	/*------------- Private helper methods  -----------------*/

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private SecurityExpressionHandler<FilterInvocation> getExpressionHandler()
			throws IOException {
		ApplicationContext appContext = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());
		Map<String, SecurityExpressionHandler> handlers = appContext
				.getBeansOfType(SecurityExpressionHandler.class);

		for (SecurityExpressionHandler h : handlers.values()) {
			if (FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(
					h.getClass(), SecurityExpressionHandler.class))) {
				return h;
			}
		}

		throw new IOException(
				"No visible WebSecurityExpressionHandler instance could be found in the application "
						+ "context. There must be at least one in order to support expressions in JSP 'authorize' tags.");
	}

	private WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() throws IOException {
		WebInvocationPrivilegeEvaluator privEvaluatorFromRequest = (WebInvocationPrivilegeEvaluator) getRequest()
				.getAttribute(WebAttributes.WEB_INVOCATION_PRIVILEGE_EVALUATOR_ATTRIBUTE);
		if (privEvaluatorFromRequest != null) {
			return privEvaluatorFromRequest;
		}

		ApplicationContext ctx = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());
		Map<String, WebInvocationPrivilegeEvaluator> wipes = ctx
				.getBeansOfType(WebInvocationPrivilegeEvaluator.class);

		if (wipes.size() == 0) {
			throw new IOException(
					"No visible WebInvocationPrivilegeEvaluator instance could be found in the application "
							+ "context. There must be at least one in order to support the use of URL access checks in 'authorize' tags.");
		}

		return (WebInvocationPrivilegeEvaluator) wipes.values().toArray()[0];
	}
}


/*
 * Copyright 2015-2018 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.security.jackson2;

import java.io.IOException;
import java.util.List;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

/**
 * Custom deserializer for {@link UsernamePasswordAuthenticationToken}. At the time of deserialization
 * it will invoke suitable constructor depending on the value of authenticated property.
 * It will ensure that the token's state must not change.
 *

 * This deserializer is already registered with {@link UsernamePasswordAuthenticationTokenMixin} but
 * you can also registered it with your own mixin class.
 *
 * @author Jitendra Singh
 * @author Greg Turnquist
 * @author Onur Kagan Ozcan
 * @see UsernamePasswordAuthenticationTokenMixin
 * @since 4.2
 */
class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<UsernamePasswordAuthenticationToken> {

	/**
	 * This method construct {@link UsernamePasswordAuthenticationToken} object from serialized json.
	 * @param jp the JsonParser
	 * @param ctxt the DeserializationContext
	 * @return the user
	 * @throws IOException if a exception during IO occurs
	 * @throws JsonProcessingException if an error during JSON processing occurs
	 */
	@Override
	public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
		UsernamePasswordAuthenticationToken token = null;
		ObjectMapper mapper = (ObjectMapper) jp.getCodec();
		JsonNode jsonNode = mapper.readTree(jp);
		Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
		JsonNode principalNode = readJsonNode(jsonNode, "principal");
		Object principal = null;
		if (principalNode.isObject()) {
			principal = mapper.readValue(principalNode.traverse(mapper), Object.class);
		} else {
			principal = principalNode.asText();
		}
		JsonNode credentialsNode = readJsonNode(jsonNode, "credentials");
		Object credentials;
		if (credentialsNode.isNull() || credentialsNode.isMissingNode()) {
			credentials = null;
		} else {
			credentials = credentialsNode.asText();
		}
		List<GrantedAuthority> authorities = mapper.readValue(
				readJsonNode(jsonNode, "authorities").traverse(mapper), new TypeReference<List<GrantedAuthority>>() {
		});
		if (authenticated) {
			token = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
		} else {
			token = new UsernamePasswordAuthenticationToken(principal, credentials);
		}
		JsonNode detailsNode = readJsonNode(jsonNode, "details");
		if (detailsNode.isNull() || detailsNode.isMissingNode()) {
			token.setDetails(null);
		} else {
			token.setDetails(detailsNode);
		}
		return token;
	}

	private JsonNode readJsonNode(JsonNode jsonNode, String field) {
		return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
	}
}