/*
 * 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.web.header.writers;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Provides support for HTTP Public Key Pinning (HPKP).
 *
 *

 * Since Section 4.1 states
 * that a value on the order of 60 days (5,184,000 seconds) may be considered a good balance,
 * we use this value as the default. This can be customized using {@link #setMaxAgeInSeconds(long)}.
 *

 *
 *

 * Because Appendix B recommends
 * that operators should first deploy public key pinning by using the report-only mode,
 * we opted to use this mode as default. This can be customized using {@link #setReportOnly(boolean)}.
 *

 *
 *

 * Since we need to validate a certificate chain, the "Public-Key-Pins" or "Public-Key-Pins-Report-Only" header
 * will only be added when {@link HttpServletRequest#isSecure()} returns {@code true}.
 *

 *
 *

 * To set the pins you first need to extract the public key information from your certificate or key file
 * and encode them using Base64. The following commands will help you extract the Base64 encoded information
 * from a key file, a certificate signing request, or a certificate.
 *
 *

 * openssl rsa -in my-key-file.key -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64
 *
 * openssl req -in my-signing-request.csr -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
 *
 * openssl x509 -in my-certificate.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
 *

 *
 *
 * The following command will extract the Base64 encoded information for a website.
 *
 *

 * openssl s_client -servername www.example.com -connect www.example.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
 *

 *

 *
 *

 * Some examples:
 *
 *

 * Public-Key-Pins: max-age=3000;
 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="
 *
 * Public-Key-Pins: max-age=5184000;
 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
 * 		pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
 *
 * Public-Key-Pins: max-age=5184000;
 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
 * 		pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
 * 		report-uri="https://example.com/pkp-report"
 *
 * Public-Key-Pins-Report-Only: max-age=5184000;
 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
 * 		pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
 * 		report-uri="https://other.example.net/pkp-report"
 *
 * Public-Key-Pins: max-age=5184000;
 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
 * 		pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
 * 		includeSubDomains
 *

 *

 *
 * @author Tim Ysewyn
 * @author Ankur Pathak
 * @since 4.1
 */
public final class HpkpHeaderWriter implements HeaderWriter {
	private static final long DEFAULT_MAX_AGE_SECONDS = 5184000;

	private static final String HPKP_HEADER_NAME = "Public-Key-Pins";

	private static final String HPKP_RO_HEADER_NAME = "Public-Key-Pins-Report-Only";

	private final Log logger = LogFactory.getLog(getClass());

	private final RequestMatcher requestMatcher = new SecureRequestMatcher();

	private Map<String, String> pins = new LinkedHashMap<>();

	private long maxAgeInSeconds;

	private boolean includeSubDomains;

	private boolean reportOnly;

	private URI reportUri;

	private String hpkpHeaderValue;

	/**
	 * Creates a new instance
	 *
	 * @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)}
	 * @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)}
	 * @param reportOnly maps to {@link #setReportOnly(boolean)}
	 */
	public HpkpHeaderWriter(long maxAgeInSeconds, boolean includeSubDomains, boolean reportOnly) {
		this.maxAgeInSeconds = maxAgeInSeconds;
		this.includeSubDomains = includeSubDomains;
		this.reportOnly = reportOnly;
		updateHpkpHeaderValue();
	}

	/**
	 * Creates a new instance
	 *
	 * @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)}
	 * @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)}
	 */
	public HpkpHeaderWriter(long maxAgeInSeconds, boolean includeSubDomains) {
		this(maxAgeInSeconds, includeSubDomains, true);
	}

	/**
	 * Creates a new instance
	 *
	 * @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)}
	 */
	public HpkpHeaderWriter(long maxAgeInSeconds) {
		this(maxAgeInSeconds, false);
	}

	/**
	 * Creates a new instance
	 */
	public HpkpHeaderWriter() {
		this(DEFAULT_MAX_AGE_SECONDS);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.web.headers.HeaderWriter#writeHeaders(javax
	 * .servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
		if (requestMatcher.matches(request)) {
			if (!pins.isEmpty()) {
				String headerName = reportOnly ? HPKP_RO_HEADER_NAME : HPKP_HEADER_NAME;
				if (!response.containsHeader(headerName)) {
					response.setHeader(headerName, hpkpHeaderValue);
				}
			} if (logger.isDebugEnabled()) {
				logger.debug("Not injecting HPKP header since there aren't any pins");
			}
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("Not injecting HPKP header since it wasn't a secure connection");
		}
	}

	/**
	 *

	 * Sets the value for the pin- directive of the Public-Key-Pins header.
	 *

	 *
	 *

	 * The pin directive specifies a way for web host operators to indicate
	 * a cryptographic identity that should be bound to a given web host.
	 * See Section 2.1.1 for additional details.
	 *

	 *
	 *

	 * To get a pin of
	 *
	 * Public-Key-Pins:
	 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="
	 *
	 * Use
	 *
	 *
	 * Map<String, String> pins = new HashMap<String, String>();
	 * pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256");
	 * pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256");
	 *
	 *

	 *
	 * @param pins the map of base64-encoded SPKI fingerprint & cryptographic hash algorithm pairs.
	 * @throws IllegalArgumentException if pins is null
	 */
	public void setPins(Map<String, String> pins) {
		Assert.notNull(pins, "pins cannot be null");
		this.pins = pins;
		updateHpkpHeaderValue();
	}

	/**
	 *

	 * Adds a list of SHA256 hashed pins for the pin- directive of the Public-Key-Pins header.
	 *

	 *
	 *

	 * The pin directive specifies a way for web host operators to indicate
	 * a cryptographic identity that should be bound to a given web host.
	 * See Section 2.1.1 for additional details.
	 *

	 *
	 *

	 * To get a pin of
	 *
	 * Public-Key-Pins-Report-Only:
	 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="
	 *
	 * Use
	 *
	 * HpkpHeaderWriter hpkpHeaderWriter = new HpkpHeaderWriter();
	 * hpkpHeaderWriter.addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM", "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=");
	 *

	 *
	 * @param pins a list of base64-encoded SPKI fingerprints.
	 * @throws IllegalArgumentException if a pin is null
	 */
	public void addSha256Pins(String ... pins) {
		for (String pin : pins) {
			Assert.notNull(pin, "pin cannot be null");
			this.pins.put(pin, "sha256");
		}
		updateHpkpHeaderValue();
	}

	/**
	 *

	 * Sets the value (in seconds) for the max-age directive of the Public-Key-Pins header. The default is 60 days.
	 *

	 *
	 *

	 * This instructs browsers how long they should regard the host (from whom the message was received)
	 * as a known pinned host. See Section
	 * 2.1.2 for additional details.
	 *

	 *
	 *

	 * To get a header like
	 *
	 * Public-Key-Pins-Report-Only: max-age=2592000;
	 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="
	 *
	 * Use
	 *
	 * HpkpHeaderWriter hpkpHeaderWriter = new HpkpHeaderWriter();
	 * hpkpHeaderWriter.setMaxAgeInSeconds(TimeUnit.DAYS.toSeconds(30));
	 *

	 *
	 * @param maxAgeInSeconds the maximum amount of time (in seconds) to regard the host
	 * as a known pinned host. (i.e. TimeUnit.DAYS.toSeconds(30) would set this to 30 days)
	 * @throws IllegalArgumentException if maxAgeInSeconds is negative
	 */
	public void setMaxAgeInSeconds(long maxAgeInSeconds) {
		if (maxAgeInSeconds < 0) {
			throw new IllegalArgumentException(
					"maxAgeInSeconds must be non-negative. Got " + maxAgeInSeconds);
		}
		this.maxAgeInSeconds = maxAgeInSeconds;
		updateHpkpHeaderValue();
	}

	/**
	 *

	 * If true, the pinning policy applies to this pinned host as well as any subdomains
	 * of the host's domain name. The default is false.
	 *

	 *
	 *

	 * See Section 2.1.3
	 * for additional details.
	 *

	 *
	 *

	 * To get a header like
	 *
	 * Public-Key-Pins-Report-Only: max-age=5184000;
	 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
	 * 		includeSubDomains
	 *
	 * you should set this to true.
	 *

	 *
	 * @param includeSubDomains true to include subdomains, else false
	 */
	public void setIncludeSubDomains(boolean includeSubDomains) {
		this.includeSubDomains = includeSubDomains;
		updateHpkpHeaderValue();
	}

	/**
	 *

	 * To get a Public-Key-Pins header you should set this to false,
	 * otherwise the header will be Public-Key-Pins-Report-Only. When in report-only mode,
	 * the browser should not terminate the connection with the server. By default this is true.
	 *

	 *
	 *

	 * See Section 2.1
	 * for additional details.
	 *

	 *
	 *

	 * To get a header like
	 *
	 * Public-Key-Pins: max-age=5184000;
	 * 		pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="
	 *
	 * you should the this to false.
	 *

	 *
	 * @param reportOnly true to report only, else false
	 */
	public void setReportOnly(boolean reportOnly) {
		this.reportOnly = reportOnly;
	}

	/**
	 *

	 * Sets the URI to which the browser should report pin validation failures.
	 *

	 *
	 *

	 * See Section 2.1.4
	 * for additional details.
	 *

	 *
	 *

	 * To get a header like
	 *
	 * Public-Key-Pins-Report-Only: max-age=5184000;
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
	 * 		pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
	 * 		report-uri="https://other.example.net/pkp-report"
	 *
	 * Use
	 *
	 * HpkpHeaderWriter hpkpHeaderWriter = new HpkpHeaderWriter();
	 * hpkpHeaderWriter.setReportUri(new URI("https://other.example.net/pkp-report"));
	 *

	 *
	 * @param reportUri the URI where the browser should send the report to.
	 */
	public void setReportUri(URI reportUri) {
		this.reportUri = reportUri;
		updateHpkpHeaderValue();
	}

	/**
	 *

	 * Sets the URI to which the browser should report pin validation failures.
	 *

	 *
	 *

	 * See Section 2.1.4
	 * for additional details.
	 *

	 *
	 *

	 * To get a header like
	 *
	 * Public-Key-Pins-Report-Only: max-age=5184000;
	 * 		pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
	 * 		pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=";
	 * 		report-uri="https://other.example.net/pkp-report"
	 *
	 * Use
	 *
	 * HpkpHeaderWriter hpkpHeaderWriter = new HpkpHeaderWriter();
	 * hpkpHeaderWriter.setReportUri("https://other.example.net/pkp-report");
	 *

	 *
	 * @param reportUri the URI where the browser should send the report to.
	 * @throws IllegalArgumentException if the reportUri is not a valid URI
	 */
	public void setReportUri(String reportUri) {
		try {
			this.reportUri = new URI(reportUri);
		} catch (URISyntaxException e) {
			throw new IllegalArgumentException(e);
		}
		updateHpkpHeaderValue();
	}

	private void updateHpkpHeaderValue() {
		String headerValue = "max-age=" + maxAgeInSeconds;
		for (Map.Entry<String, String> pin : pins.entrySet()) {
			headerValue += " ; pin-" + pin.getValue() + "=\"" + pin.getKey() + "\"";
		}
		if (reportUri != null) {
			headerValue += " ; report-uri=\"" + reportUri.toString() + "\"";
		}
		if (includeSubDomains) {
			headerValue += " ; includeSubDomains";
		}
		this.hpkpHeaderValue = headerValue;
	}

	private static final class SecureRequestMatcher implements RequestMatcher {
		public boolean matches(HttpServletRequest request) {
			return request.isSecure();
		}
	}
}


/*
 * 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.method;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Stores a list of <tt>ConfigAttribute</tt>s for a method or class signature.
 *
 *

 * This class is the preferred implementation of {@link MethodSecurityMetadataSource} for
 * XML-based definition of method security metadata. To assist in XML-based definition,
 * wildcard support is provided.
 *

 *
 * @author Ben Alex
 * @since 2.0
 */
public class MapBasedMethodSecurityMetadataSource extends
		AbstractFallbackMethodSecurityMetadataSource implements BeanClassLoaderAware {

	// ~ Instance fields
	// ================================================================================================
	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

	/** Map from RegisteredMethod to ConfigAttribute list */
	protected final Map<RegisteredMethod, List<ConfigAttribute>> methodMap = new HashMap<>();

	/** Map from RegisteredMethod to name pattern used for registration */
	private final Map<RegisteredMethod, String> nameMap = new HashMap<>();

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

	public MapBasedMethodSecurityMetadataSource() {
	}

	/**
	 * Creates the <tt>MapBasedMethodSecurityMetadataSource</tt> from a
	 * @param methodMap map of method names to <tt>ConfigAttribute</tt>s.
	 */
	public MapBasedMethodSecurityMetadataSource(
			Map<String, List<ConfigAttribute>> methodMap) {
		for (Map.Entry<String, List<ConfigAttribute>> entry : methodMap.entrySet()) {
			addSecureMethod(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * Implementation does not support class-level attributes.
	 */
	@Override
	protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
		return null;
	}

	/**
	 * Will walk the method inheritance tree to find the most specific declaration
	 * applicable.
	 */
	@Override
	protected Collection<ConfigAttribute> findAttributes(Method method,
			Class<?> targetClass) {
		if (targetClass == null) {
			return null;
		}

		return findAttributesSpecifiedAgainst(method, targetClass);
	}

	private List<ConfigAttribute> findAttributesSpecifiedAgainst(Method method,
			Class<?> clazz) {
		RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz);
		if (methodMap.containsKey(registeredMethod)) {
			return (List<ConfigAttribute>) methodMap.get(registeredMethod);
		}
		// Search superclass
		if (clazz.getSuperclass() != null) {
			return findAttributesSpecifiedAgainst(method, clazz.getSuperclass());
		}
		return null;
	}

	/**
	 * Add configuration attributes for a secure method. Method names can end or start
	 * with * for matching multiple methods.
	 *
	 * @param name type and method name, separated by a dot
	 * @param attr the security attributes associated with the method
	 */
	private void addSecureMethod(String name, List<ConfigAttribute> attr) {
		int lastDotIndex = name.lastIndexOf(".");

		if (lastDotIndex == -1) {
			throw new IllegalArgumentException("'" + name
					+ "' is not a valid method name: format is FQN.methodName");
		}

		String methodName = name.substring(lastDotIndex + 1);
		Assert.hasText(methodName, () -> "Method not found for '" + name + "'");

		String typeName = name.substring(0, lastDotIndex);
		Class<?> type = ClassUtils.resolveClassName(typeName, this.beanClassLoader);

		addSecureMethod(type, methodName, attr);
	}

	/**
	 * Add configuration attributes for a secure method. Mapped method names can end or
	 * start with * for matching multiple methods.
	 *
	 * @param javaType target interface or class the security configuration attribute
	 * applies to
	 * @param mappedName mapped method name, which the javaType has declared or inherited
	 * @param attr required authorities associated with the method
	 */
	public void addSecureMethod(Class<?> javaType, String mappedName,
			List<ConfigAttribute> attr) {
		String name = javaType.getName() + '.' + mappedName;

		if (logger.isDebugEnabled()) {
			logger.debug("Request to add secure method [" + name + "] with attributes ["
					+ attr + "]");
		}

		Method[] methods = javaType.getMethods();
		List<Method> matchingMethods = new ArrayList<>();

		for (Method m : methods) {
			if (m.getName().equals(mappedName) || isMatch(m.getName(), mappedName)) {
				matchingMethods.add(m);
			}
		}

		if (matchingMethods.isEmpty()) {
			throw new IllegalArgumentException("Couldn't find method '" + mappedName
					+ "' on '" + javaType + "'");
		}

		// register all matching methods
		for (Method method : matchingMethods) {
			RegisteredMethod registeredMethod = new RegisteredMethod(method, javaType);
			String regMethodName = (String) this.nameMap.get(registeredMethod);

			if ((regMethodName == null)
					|| (!regMethodName.equals(name) && (regMethodName.length() <= name
							.length()))) {
				// no already registered method name, or more specific
				// method name specification now -> (re-)register method
				if (regMethodName != null) {
					logger.debug("Replacing attributes for secure method [" + method
							+ "]: current name [" + name + "] is more specific than ["
							+ regMethodName + "]");
				}

				this.nameMap.put(registeredMethod, name);
				addSecureMethod(registeredMethod, attr);
			}
			else {
				logger.debug("Keeping attributes for secure method [" + method
						+ "]: current name [" + name + "] is not more specific than ["
						+ regMethodName + "]");
			}
		}
	}

	/**
	 * Adds configuration attributes for a specific method, for example where the method
	 * has been matched using a pointcut expression. If a match already exists in the map
	 * for the method, then the existing match will be retained, so that if this method is
	 * called for a more general pointcut it will not override a more specific one which
	 * has already been added.
	 *

	 * This method should only be called during initialization of the {@code BeanFactory}.
	 */
	public void addSecureMethod(Class<?> javaType, Method method,
			List<ConfigAttribute> attr) {
		RegisteredMethod key = new RegisteredMethod(method, javaType);

		if (methodMap.containsKey(key)) {
			logger.debug("Method [" + method
					+ "] is already registered with attributes [" + methodMap.get(key)
					+ "]");
			return;
		}

		methodMap.put(key, attr);
	}

	/**
	 * Add configuration attributes for a secure method.
	 *
	 * @param method the method to be secured
	 * @param attr required authorities associated with the method
	 */
	private void addSecureMethod(RegisteredMethod method, List<ConfigAttribute> attr) {
		Assert.notNull(method, "RegisteredMethod required");
		Assert.notNull(attr, "Configuration attribute required");
		if (logger.isInfoEnabled()) {
			logger.info("Adding secure method [" + method + "] with attributes [" + attr
					+ "]");
		}
		this.methodMap.put(method, attr);
	}

	/**
	 * Obtains the configuration attributes explicitly defined against this bean.
	 *
	 * @return the attributes explicitly defined against this bean
	 */
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		Set<ConfigAttribute> allAttributes = new HashSet<>();

		for (List<ConfigAttribute> attributeList : methodMap.values()) {
			allAttributes.addAll(attributeList);
		}

		return allAttributes;
	}

	/**
	 * Return if the given method name matches the mapped name. The default implementation
	 * checks for "xxx" and "xxx" matches.
	 *
	 * @param methodName the method name of the class
	 * @param mappedName the name in the descriptor
	 *
	 * @return if the names match
	 */
	private boolean isMatch(String methodName, String mappedName) {
		return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0,
				mappedName.length() - 1)))
				|| (mappedName.startsWith("*") && methodName.endsWith(mappedName
						.substring(1, mappedName.length())));
	}

	@Override
	public void setBeanClassLoader(ClassLoader beanClassLoader) {
		Assert.notNull(beanClassLoader, "Bean class loader required");
		this.beanClassLoader = beanClassLoader;
	}

	/**
	 * @return map size (for unit tests and diagnostics)
	 */
	public int getMethodMapSize() {
		return methodMap.size();
	}

	/**
	 * Stores both the Java Method as well as the Class we obtained the Method from. This
	 * is necessary because Method only provides us access to the declaring class. It
	 * doesn't provide a way for us to introspect which Class the Method was registered
	 * against. If a given Class inherits and redeclares a method (i.e. calls super();)
	 * the registered Class and declaring Class are the same. If a given class merely
	 * inherits but does not redeclare a method, the registered Class will be the Class
	 * we're invoking against and the Method will provide details of the declared class.
	 */
	private static class RegisteredMethod {
		private final Method method;
		private final Class<?> registeredJavaType;

		public RegisteredMethod(Method method, Class<?> registeredJavaType) {
			Assert.notNull(method, "Method required");
			Assert.notNull(registeredJavaType, "Registered Java Type required");
			this.method = method;
			this.registeredJavaType = registeredJavaType;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj != null && obj instanceof RegisteredMethod) {
				RegisteredMethod rhs = (RegisteredMethod) obj;
				return method.equals(rhs.method)
						&& registeredJavaType.equals(rhs.registeredJavaType);
			}
			return false;
		}

		@Override
		public int hashCode() {
			return method.hashCode() * registeredJavaType.hashCode();
		}

		@Override
		public String toString() {
			return "RegisteredMethod[" + registeredJavaType.getName() + "; " + method
					+ "]";
		}
	}

}


/*
 * Copyright 2002-2013 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.configurers;

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

import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;

/**
 * Allows configuring a shared {@link PortMapper} instance used to determine the ports
 * when redirecting between HTTP and HTTPS. The {@link PortMapper} can be obtained from
 * {@link HttpSecurity#getSharedObject(Class)}.
 *
 * @author Rob Winch
 * @since 3.2
 */
public final class PortMapperConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractHttpConfigurer<PortMapperConfigurer<H>, H> {
	private PortMapper portMapper;
	private Map<String, String> httpsPortMappings = new HashMap<>();

	/**
	 * Creates a new instance
	 */
	public PortMapperConfigurer() {
	}

	/**
	 * Allows specifying the {@link PortMapper} instance.
	 * @param portMapper
	 * @return the {@link PortMapperConfigurer} for further customizations
	 */
	public PortMapperConfigurer<H> portMapper(PortMapper portMapper) {
		this.portMapper = portMapper;
		return this;
	}

	/**
	 * Adds a port mapping
	 * @param httpPort the HTTP port that maps to a specific HTTPS port.
	 * @return {@link HttpPortMapping} to define the HTTPS port
	 */
	public HttpPortMapping http(int httpPort) {
		return new HttpPortMapping(httpPort);
	}

	@Override
	public void init(H http) throws Exception {
		http.setSharedObject(PortMapper.class, getPortMapper());
	}

	/**
	 * Gets the {@link PortMapper} to use. If {@link #portMapper(PortMapper)} was not
	 * invoked, builds a {@link PortMapperImpl} using the port mappings specified with
	 * {@link #http(int)}.
	 *
	 * @return the {@link PortMapper} to use
	 */
	private PortMapper getPortMapper() {
		if (portMapper == null) {
			PortMapperImpl portMapper = new PortMapperImpl();
			portMapper.setPortMappings(httpsPortMappings);
			this.portMapper = portMapper;
		}
		return portMapper;
	}

	/**
	 * Allows specifying the HTTPS port for a given HTTP port when redirecting between
	 * HTTP and HTTPS.
	 *
	 * @author Rob Winch
	 * @since 3.2
	 */
	public final class HttpPortMapping {
		private final int httpPort;

		/**
		 * Creates a new instance
		 * @param httpPort
		 * @see PortMapperConfigurer#http(int)
		 */
		private HttpPortMapping(int httpPort) {
			this.httpPort = httpPort;
		}

		/**
		 * Maps the given HTTP port to the provided HTTPS port and vice versa.
		 * @param httpsPort the HTTPS port to map to
		 * @return the {@link PortMapperConfigurer} for further customization
		 */
		public PortMapperConfigurer<H> mapsTo(int httpsPort) {
			httpsPortMappings.put(String.valueOf(httpPort), String.valueOf(httpsPort));
			return PortMapperConfigurer.this;
		}
	}
}


/*
 * Copyright 2002-2013 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.csrf;

import java.util.UUID;

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

import org.springframework.util.Assert;

/**
 * A {@link CsrfTokenRepository} that stores the {@link CsrfToken} in the
 * {@link HttpSession}.
 *
 * @author Rob Winch
 * @since 3.2
 */
public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
	private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

	private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";

	private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class
			.getName().concat(".CSRF_TOKEN");

	private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;

	private String headerName = DEFAULT_CSRF_HEADER_NAME;

	private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.
	 * springframework .security.web.csrf.CsrfToken,
	 * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public void saveToken(CsrfToken token, HttpServletRequest request,
			HttpServletResponse response) {
		if (token == null) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				session.removeAttribute(this.sessionAttributeName);
			}
		}
		else {
			HttpSession session = request.getSession();
			session.setAttribute(this.sessionAttributeName, token);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet
	 * .http.HttpServletRequest)
	 */
	public CsrfToken loadToken(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		if (session == null) {
			return null;
		}
		return (CsrfToken) session.getAttribute(this.sessionAttributeName);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.web.csrf.CsrfTokenRepository#generateToken(javax.
	 * servlet .http.HttpServletRequest)
	 */
	public CsrfToken generateToken(HttpServletRequest request) {
		return new DefaultCsrfToken(this.headerName, this.parameterName,
				createNewToken());
	}

	/**
	 * Sets the {@link HttpServletRequest} parameter name that the {@link CsrfToken} is
	 * expected to appear on
	 * @param parameterName the new parameter name to use
	 */
	public void setParameterName(String parameterName) {
		Assert.hasLength(parameterName, "parameterName cannot be null or empty");
		this.parameterName = parameterName;
	}

	/**
	 * Sets the header name that the {@link CsrfToken} is expected to appear on and the
	 * header that the response will contain the {@link CsrfToken}.
	 *
	 * @param headerName the new header name to use
	 */
	public void setHeaderName(String headerName) {
		Assert.hasLength(headerName, "headerName cannot be null or empty");
		this.headerName = headerName;
	}

	/**
	 * Sets the {@link HttpSession} attribute name that the {@link CsrfToken} is stored in
	 * @param sessionAttributeName the new attribute name to use
	 */
	public void setSessionAttributeName(String sessionAttributeName) {
		Assert.hasLength(sessionAttributeName,
				"sessionAttributename cannot be null or empty");
		this.sessionAttributeName = sessionAttributeName;
	}

	private String createNewToken() {
		return UUID.randomUUID().toString();
	}
}


/*
 * Copyright 2002-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.oauth2.jwt;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Map;

/**
 * Allows creating a {@link ReactiveJwtDecoder} from an
 * OpenID Provider Configuration.
 *
 * @author Josh Cummings
 * @since 5.1
 */
public final class ReactiveJwtDecoders {

	/**
	 * Creates a {@link ReactiveJwtDecoder} using the provided
	 * Issuer by making an
	 * OpenID Provider
	 * Configuration Request and using the values in the
	 * OpenID
	 * Provider Configuration Response to initialize the {@link ReactiveJwtDecoder}.
	 *
	 * @param oidcIssuerLocation the Issuer
	 * @return a {@link ReactiveJwtDecoder} that was initialized by the OpenID Provider Configuration.
	 */
	public static ReactiveJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) {
		Map<String, Object> openidConfiguration = getOpenidConfiguration(oidcIssuerLocation);
		String metadataIssuer = "(unavailable)";
		if (openidConfiguration.containsKey("issuer")) {
			metadataIssuer = openidConfiguration.get("issuer").toString();
		}
		if (!oidcIssuerLocation.equals(metadataIssuer)) {
			throw new IllegalStateException("The Issuer \"" + metadataIssuer + "\" provided in the OpenID Configuration " +
					"did not match the requested issuer \"" + oidcIssuerLocation + "\"");
		}

		OAuth2TokenValidator<Jwt> jwtValidator =
				JwtValidators.createDefaultWithIssuer(oidcIssuerLocation);

		NimbusReactiveJwtDecoder jwtDecoder =
				new NimbusReactiveJwtDecoder(openidConfiguration.get("jwks_uri").toString());
		jwtDecoder.setJwtValidator(jwtValidator);

		return jwtDecoder;
	}

	private static Map<String, Object> getOpenidConfiguration(String issuer) {
		ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<Map<String, Object>>() {};
		RestTemplate rest = new RestTemplate();
		try {
			URI uri = UriComponentsBuilder.fromUriString(issuer + "/.well-known/openid-configuration")
					.build()
					.toUri();
			RequestEntity<Void> request = RequestEntity.get(uri).build();
			return rest.exchange(request, typeReference).getBody();
		} catch(RuntimeException e) {
			throw new IllegalArgumentException("Unable to resolve the OpenID Configuration with the provided Issuer of " +
					"\"" + issuer + "\"", e);
		}
	}

	private ReactiveJwtDecoders() {}
}


/*
 * 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 java.util.Arrays;
import java.util.List;

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

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

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

	public OrServerWebExchangeMatcher(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 Flux.fromIterable(matchers)
			.flatMap(m -> m.matches(exchange))
			.filter(m -> m.isMatch())
			.next()
			.switchIfEmpty(MatchResult.notMatch());
	}

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


/*
 * Copyright 2002-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.authorization;

import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * A {@link ReactiveAuthorizationManager} that determines if the current user is
 * authorized by evaluating if the {@link Authentication} contains a specified authority.
 *
 * @author Rob Winch
 * @since 5.0
 * @param <T> the type of object being authorized
 */
public class AuthorityReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
	private final List<String> authorities;

	private AuthorityReactiveAuthorizationManager(String... authorities) {
		this.authorities = Arrays.asList(authorities);
	}

	@Override
	public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {
		return authentication
			.filter(a -> a.isAuthenticated())
			.flatMapIterable( a -> a.getAuthorities())
			.map(g -> g.getAuthority())
			.any(a -> this.authorities.contains(a))
			.map( hasAuthority -> new AuthorizationDecision(hasAuthority))
			.defaultIfEmpty(new AuthorizationDecision(false));
	}

	/**
	 * Creates an instance of {@link AuthorityReactiveAuthorizationManager} with the
	 * provided authority.
	 *
	 * @param authority the authority to check for
	 * @param <T> the type of object being authorized
	 * @return the new instance
	 */
	public static <T> AuthorityReactiveAuthorizationManager<T> hasAuthority(String authority) {
		Assert.notNull(authority, "authority cannot be null");
		return new AuthorityReactiveAuthorizationManager<>(authority);
	}

	/**
	 * Creates an instance of {@link AuthorityReactiveAuthorizationManager} with the
	 * provided authorities.
	 *
	 * @author Robbie Martinus
	 * @param authorities the authorities to check for
	 * @param <T> the type of object being authorized
	 * @return the new instance
	 */
	public static <T> AuthorityReactiveAuthorizationManager<T> hasAnyAuthority(String... authorities) {
		Assert.notNull(authorities, "authorities cannot be null");
		for (String authority : authorities) {
			Assert.notNull(authority, "authority cannot be null");
		}

		return new AuthorityReactiveAuthorizationManager<>(authorities);
	}

	/**
	 * Creates an instance of {@link AuthorityReactiveAuthorizationManager} with the
	 * provided authority.
	 *
	 * @param role the authority to check for prefixed with "ROLE_"
	 * @param <T> the type of object being authorized
	 * @return the new instance
	 */
	public static <T> AuthorityReactiveAuthorizationManager<T> hasRole(String role) {
		Assert.notNull(role, "role cannot be null");
		return hasAuthority("ROLE_" + role);
	}

	/**
	 * Creates an instance of {@link AuthorityReactiveAuthorizationManager} with the
	 * provided authorities.
	 *
	 * @author Robbie Martinus
	 * @param roles the authorities to check for prefixed with "ROLE_"
	 * @param <T> the type of object being authorized
	 * @return the new instance
	 */
	public static <T> AuthorityReactiveAuthorizationManager<T> hasAnyRole(String... roles) {
		Assert.notNull(roles, "roles cannot be null");
		for (String role : roles) {
			Assert.notNull(role, "role cannot be null");
		}

		return hasAnyAuthority(Stream.of(roles)
				.map(r -> "ROLE_" + r)
				.toArray(String[]::new)
		);
	}
}


/*
 * 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.context;

import javax.servlet.AsyncContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.Transient;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;

/**
 * A {@code SecurityContextRepository} implementation which stores the security context in
 * the {@code HttpSession} between requests.
 *

 * The {@code HttpSession} will be queried to retrieve the {@code SecurityContext} in the
 * <tt>loadContext</tt> method (using the key {@link #SPRING_SECURITY_CONTEXT_KEY} by
 * default). If a valid {@code SecurityContext} cannot be obtained from the
 * {@code HttpSession} for whatever reason, a fresh {@code SecurityContext} will be
 * created by calling by {@link SecurityContextHolder#createEmptyContext()} and this
 * instance will be returned instead.
 *

 * When <tt>saveContext</tt> is called, the context will be stored under the same key,
 * provided
 *

 *
The value has changed

 *
The configured <tt>AuthenticationTrustResolver</tt> does not report that the
 * contents represent an anonymous user

 *

 *

 * With the standard configuration, no {@code HttpSession} will be created during
 * <tt>loadContext</tt> if one does not already exist. When <tt>saveContext</tt> is called
 * at the end of the web request, and no session exists, a new {@code HttpSession} will
 * only be created if the supplied {@code SecurityContext} is not equal to an empty
 * {@code SecurityContext} instance. This avoids needless HttpSession
 * creation, but automates the storage of changes made to the context during the request.
 * Note that if {@link SecurityContextPersistenceFilter} is configured to eagerly create
 * sessions, then the session-minimisation logic applied here will not make any
 * difference. If you are using eager session creation, then you should ensure that the
 * <tt>allowSessionCreation</tt> property of this class is set to <tt>true</tt> (the
 * default).
 *

 * If for whatever reason no {@code HttpSession} should ever be created (for
 * example, if Basic authentication is being used or similar clients that will never
 * present the same {@code jsessionid}), then {@link #setAllowSessionCreation(boolean)
 * allowSessionCreation} should be set to false. Only do this if you really
 * need to conserve server memory and ensure all classes using the
 * {@code SecurityContextHolder} are designed to have no persistence of the
 * {@code SecurityContext} between web requests.
 *
 * @author Luke Taylor
 * @since 3.0
 */
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
	/**
	 * The default key under which the security context will be stored in the session.
	 */
	public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";

	protected final Log logger = LogFactory.getLog(this.getClass());

	/**
	 * SecurityContext instance used to check for equality with default (unauthenticated)
	 * content
	 */
	private final Object contextObject = SecurityContextHolder.createEmptyContext();
	private boolean allowSessionCreation = true;
	private boolean disableUrlRewriting = false;
	private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;

	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

	/**
	 * Gets the security context for the current request (if available) and returns it.
	 *

	 * If the session is null, the context object is null or the context object stored in
	 * the session is not an instance of {@code SecurityContext}, a new context object
	 * will be generated and returned.
	 */
	public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
		HttpServletRequest request = requestResponseHolder.getRequest();
		HttpServletResponse response = requestResponseHolder.getResponse();
		HttpSession httpSession = request.getSession(false);

		SecurityContext context = readSecurityContextFromSession(httpSession);

		if (context == null) {
			if (logger.isDebugEnabled()) {
				logger.debug("No SecurityContext was available from the HttpSession: "
						+ httpSession + ". " + "A new one will be created.");
			}
			context = generateNewContext();

		}

		SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(
				response, request, httpSession != null, context);
		requestResponseHolder.setResponse(wrappedResponse);

		requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(
				request, wrappedResponse));

		return context;
	}

	public void saveContext(SecurityContext context, HttpServletRequest request,
			HttpServletResponse response) {
		SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils
				.getNativeResponse(response,
						SaveContextOnUpdateOrErrorResponseWrapper.class);
		if (responseWrapper == null) {
			throw new IllegalStateException(
					"Cannot invoke saveContext on response "
							+ response
							+ ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
		}
		// saveContext() might already be called by the response wrapper
		// if something in the chain called sendError() or sendRedirect(). This ensures we
		// only call it
		// once per request.
		if (!responseWrapper.isContextSaved()) {
			responseWrapper.saveContext(context);
		}
	}

	public boolean containsContext(HttpServletRequest request) {
		HttpSession session = request.getSession(false);

		if (session == null) {
			return false;
		}

		return session.getAttribute(springSecurityContextKey) != null;
	}

	/**
	 *
	 * @param httpSession the session obtained from the request.
	 */
	private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
		final boolean debug = logger.isDebugEnabled();

		if (httpSession == null) {
			if (debug) {
				logger.debug("No HttpSession currently exists");
			}

			return null;
		}

		// Session exists, so try to obtain a context from it.

		Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);

		if (contextFromSession == null) {
			if (debug) {
				logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
			}

			return null;
		}

		// We now have the security context object from the session.
		if (!(contextFromSession instanceof SecurityContext)) {
			if (logger.isWarnEnabled()) {
				logger.warn(springSecurityContextKey
						+ " did not contain a SecurityContext but contained: '"
						+ contextFromSession
						+ "'; are you improperly modifying the HttpSession directly "
						+ "(you should always use SecurityContextHolder) or using the HttpSession attribute "
						+ "reserved for this class?");
			}

			return null;
		}

		if (debug) {
			logger.debug("Obtained a valid SecurityContext from "
					+ springSecurityContextKey + ": '" + contextFromSession + "'");
		}

		// Everything OK. The only non-null return from this method.

		return (SecurityContext) contextFromSession;
	}

	/**
	 * By default, calls {@link SecurityContextHolder#createEmptyContext()} to obtain a
	 * new context (there should be no context present in the holder when this method is
	 * called). Using this approach the context creation strategy is decided by the
	 * {@link SecurityContextHolderStrategy} in use. The default implementations will
	 * return a new <tt>SecurityContextImpl</tt>.
	 *
	 * @return a new SecurityContext instance. Never null.
	 */
	protected SecurityContext generateNewContext() {
		return SecurityContextHolder.createEmptyContext();
	}

	/**
	 * If set to true (the default), a session will be created (if required) to store the
	 * security context if it is determined that its contents are different from the
	 * default empty context value.
	 *

	 * Note that setting this flag to false does not prevent this class from storing the
	 * security context. If your application (or another filter) creates a session, then
	 * the security context will still be stored for an authenticated user.
	 *
	 * @param allowSessionCreation
	 */
	public void setAllowSessionCreation(boolean allowSessionCreation) {
		this.allowSessionCreation = allowSessionCreation;
	}

	/**
	 * Allows the use of session identifiers in URLs to be disabled. Off by default.
	 *
	 * @param disableUrlRewriting set to <tt>true</tt> to disable URL encoding methods in
	 * the response wrapper and prevent the use of <tt>jsessionid</tt> parameters.
	 */
	public void setDisableUrlRewriting(boolean disableUrlRewriting) {
		this.disableUrlRewriting = disableUrlRewriting;
	}

	/**
	 * Allows the session attribute name to be customized for this repository instance.
	 *
	 * @param springSecurityContextKey the key under which the security context will be
	 * stored. Defaults to {@link #SPRING_SECURITY_CONTEXT_KEY}.
	 */
	public void setSpringSecurityContextKey(String springSecurityContextKey) {
		Assert.hasText(springSecurityContextKey,
				"springSecurityContextKey cannot be empty");
		this.springSecurityContextKey = springSecurityContextKey;
	}

	// ~ Inner Classes
	// ==================================================================================================

	private static class SaveToSessionRequestWrapper extends
			HttpServletRequestWrapper {
		private final SaveContextOnUpdateOrErrorResponseWrapper response;

		public SaveToSessionRequestWrapper(HttpServletRequest request,
				SaveContextOnUpdateOrErrorResponseWrapper response) {
			super(request);
			this.response = response;
		}

		@Override
		public AsyncContext startAsync() {
			response.disableSaveOnResponseCommitted();
			return super.startAsync();
		}

		@Override
		public AsyncContext startAsync(ServletRequest servletRequest,
				ServletResponse servletResponse) throws IllegalStateException {
			response.disableSaveOnResponseCommitted();
			return super.startAsync(servletRequest, servletResponse);
		}
	}

	/**
	 * Wrapper that is applied to every request/response to update the
	 * HttpSession with
	 * the SecurityContext when a sendError() or
	 * sendRedirect happens. See SEC-398.
	 *

	 * Stores the necessary state from the start of the request in order to make a
	 * decision about whether the security context has changed before saving it.
	 */
	final class SaveToSessionResponseWrapper extends
			SaveContextOnUpdateOrErrorResponseWrapper {

		private final HttpServletRequest request;
		private final boolean httpSessionExistedAtStartOfRequest;
		private final SecurityContext contextBeforeExecution;
		private final Authentication authBeforeExecution;

		/**
		 * Takes the parameters required to call saveContext() successfully
		 * in addition to the request and the response object we are wrapping.
		 *
		 * @param request the request object (used to obtain the session, if one exists).
		 * @param httpSessionExistedAtStartOfRequest indicates whether there was a session
		 * in place before the filter chain executed. If this is true, and the session is
		 * found to be null, this indicates that it was invalidated during the request and
		 * a new session will now be created.
		 * @param context the context before the filter chain executed. The context will
		 * only be stored if it or its contents changed during the request.
		 */
		SaveToSessionResponseWrapper(HttpServletResponse response,
				HttpServletRequest request, boolean httpSessionExistedAtStartOfRequest,
				SecurityContext context) {
			super(response, disableUrlRewriting);
			this.request = request;
			this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
			this.contextBeforeExecution = context;
			this.authBeforeExecution = context.getAuthentication();
		}

		/**
		 * Stores the supplied security context in the session (if available) and if it
		 * has changed since it was set at the start of the request. If the
		 * AuthenticationTrustResolver identifies the current user as anonymous, then the
		 * context will not be stored.
		 *
		 * @param context the context object obtained from the SecurityContextHolder after
		 * the request has been processed by the filter chain.
		 * SecurityContextHolder.getContext() cannot be used to obtain the context as it
		 * has already been cleared by the time this method is called.
		 *
		 */
		@Override
		protected void saveContext(SecurityContext context) {
			final Authentication authentication = context.getAuthentication();
			HttpSession httpSession = request.getSession(false);

			// See SEC-776
			if (authentication == null || trustResolver.isAnonymous(authentication)) {
				if (logger.isDebugEnabled()) {
					logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.");
				}

				if (httpSession != null && authBeforeExecution != null) {
					// SEC-1587 A non-anonymous context may still be in the session
					// SEC-1735 remove if the contextBeforeExecution was not anonymous
					httpSession.removeAttribute(springSecurityContextKey);
				}
				return;
			}

			if (httpSession == null) {
				httpSession = createNewSessionIfAllowed(context);
			}

			// If HttpSession exists, store current SecurityContext but only if it has
			// actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
			if (httpSession != null) {
				// We may have a new session, so check also whether the context attribute
				// is set SEC-1561
				if (contextChanged(context)
						|| httpSession.getAttribute(springSecurityContextKey) == null) {
					httpSession.setAttribute(springSecurityContextKey, context);

					if (logger.isDebugEnabled()) {
						logger.debug("SecurityContext '" + context
								+ "' stored to HttpSession: '" + httpSession);
					}
				}
			}
		}

		private boolean contextChanged(SecurityContext context) {
			return context != contextBeforeExecution
					|| context.getAuthentication() != authBeforeExecution;
		}

		private HttpSession createNewSessionIfAllowed(SecurityContext context) {
			if (isTransientAuthentication(context.getAuthentication())) {
				return null;
			}

			if (httpSessionExistedAtStartOfRequest) {
				if (logger.isDebugEnabled()) {
					logger.debug("HttpSession is now null, but was not null at start of request; "
							+ "session was invalidated, so do not create a new session");
				}

				return null;
			}

			if (!allowSessionCreation) {
				if (logger.isDebugEnabled()) {
					logger.debug("The HttpSession is currently null, and the "
							+ HttpSessionSecurityContextRepository.class.getSimpleName()
							+ " is prohibited from creating an HttpSession "
							+ "(because the allowSessionCreation property is false) - SecurityContext thus not "
							+ "stored for next request");
				}

				return null;
			}
			// Generate a HttpSession only if we need to

			if (contextObject.equals(context)) {
				if (logger.isDebugEnabled()) {
					logger.debug("HttpSession is null, but SecurityContext has not changed from default empty context: ' "
							+ context
							+ "'; not creating HttpSession or storing SecurityContext");
				}

				return null;
			}

			if (logger.isDebugEnabled()) {
				logger.debug("HttpSession being created as SecurityContext is non-default");
			}

			try {
				return request.getSession(true);
			}
			catch (IllegalStateException e) {
				// Response must already be committed, therefore can't create a new
				// session
				logger.warn("Failed to create a session, as response has been committed. Unable to store"
						+ " SecurityContext.");
			}

			return null;
		}
	}

	private boolean isTransientAuthentication(Authentication authentication) {
		return AnnotationUtils.getAnnotation(authentication.getClass(), Transient.class) != null;
	}

	/**
	 * Sets the {@link AuthenticationTrustResolver} to be used. The default is
	 * {@link AuthenticationTrustResolverImpl}.
	 *
	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
	 * null.
	 */
	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
		Assert.notNull(trustResolver, "trustResolver cannot be null");
		this.trustResolver = trustResolver;
	}
}


/*
 * 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;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * A base class for registering {@link RequestMatcher}'s. For example, it might allow for
 * specifying which {@link RequestMatcher} require a certain level of authorization.
 *
 *
 * @param <C> The object that is returned or Chained after creating the RequestMatcher
 *
 * @author Rob Winch
 * @author Ankur Pathak
 * @since 3.2
 */
public abstract class AbstractRequestMatcherRegistry<C> {
	private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";

	private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE;

	private ApplicationContext context;

	private boolean anyRequestConfigured = false;

	protected final void setApplicationContext(ApplicationContext context) {
		this.context = context;
	}

	/**
	 * Gets the {@link ApplicationContext}
	 *
	 * @return the {@link ApplicationContext}
	 */
	protected final ApplicationContext getApplicationContext() {
		return this.context;
	}

	/**
	 * Maps any request.
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C anyRequest() {
		Assert.state(!this.anyRequestConfigured, "Can't configure anyRequest after itself");
		C configurer = requestMatchers(ANY_REQUEST);
		this.anyRequestConfigured = true;
		return configurer;
	}

	/**
	 * Maps a {@link List} of
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher}
	 * instances.
	 *
	 * @param method the {@link HttpMethod} to use for any
	 * {@link HttpMethod}.
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C antMatchers(HttpMethod method) {
		return antMatchers(method, new String[] { "/**" });
	}

	/**
	 * Maps a {@link List} of
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher}
	 * instances.
	 *
	 * @param method the {@link HttpMethod} to use or {@code null} for any
	 * {@link HttpMethod}.
	 * @param antPatterns the ant patterns to create. If {@code null} or empty, then matches on nothing.
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} from
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C antMatchers(HttpMethod method, String... antPatterns) {
		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
		return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
	}

	/**
	 * Maps a {@link List} of
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher}
	 * instances that do not care which {@link HttpMethod} is used.
	 *
	 * @param antPatterns the ant patterns to create
	 * {@link org.springframework.security.web.util.matcher.AntPathRequestMatcher} from
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C antMatchers(String... antPatterns) {
		Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
		return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
	}

	/**
	 *

	 * Maps an {@link MvcRequestMatcher} that does not care which {@link HttpMethod} is
	 * used. This matcher will use the same rules that Spring MVC uses for matching. For
	 * example, often times a mapping of the path "/path" will match on "/path", "/path/",
	 * "/path.html", etc.
	 *

	 *

	 * If the current request will not be processed by Spring MVC, a reasonable default
	 * using the pattern as a ant pattern will be used.
	 *

	 *
	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
	 * Spring MVC
	 * @return the object that is chained after creating the {@link RequestMatcher}.
	 */
	public abstract C mvcMatchers(String... mvcPatterns);

	/**
	 *

	 * Maps an {@link MvcRequestMatcher} that also specifies a specific {@link HttpMethod}
	 * to match on. This matcher will use the same rules that Spring MVC uses for
	 * matching. For example, often times a mapping of the path "/path" will match on
	 * "/path", "/path/", "/path.html", etc.
	 *

	 *

	 * If the current request will not be processed by Spring MVC, a reasonable default
	 * using the pattern as a ant pattern will be used.
	 *

	 *
	 * @param method the HTTP method to match on
	 * @param mvcPatterns the patterns to match on. The rules for matching are defined by
	 * Spring MVC
	 * @return the object that is chained after creating the {@link RequestMatcher}.
	 */
	public abstract C mvcMatchers(HttpMethod method, String... mvcPatterns);

	/**
	 * Creates {@link MvcRequestMatcher} instances for the method and patterns passed in
	 *
	 * @param method the HTTP method to use or null if any should be used
	 * @param mvcPatterns the Spring MVC patterns to match on
	 * @return a List of {@link MvcRequestMatcher} instances
	 */
	protected final List<MvcRequestMatcher> createMvcMatchers(HttpMethod method,
			String... mvcPatterns) {
		Assert.state(!this.anyRequestConfigured, "Can't configure mvcMatchers after anyRequest");
		ObjectPostProcessor<Object> opp = this.context.getBean(ObjectPostProcessor.class);
		if (!this.context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
			throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME +" of type " + HandlerMappingIntrospector.class.getName()
				+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
		}
		HandlerMappingIntrospector introspector = this.context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME,
			HandlerMappingIntrospector.class);
		List<MvcRequestMatcher> matchers = new ArrayList<>(
				mvcPatterns.length);
		for (String mvcPattern : mvcPatterns) {
			MvcRequestMatcher matcher = new MvcRequestMatcher(introspector, mvcPattern);
			opp.postProcess(matcher);

			if (method != null) {
				matcher.setMethod(method);
			}
			matchers.add(matcher);
		}
		return matchers;
	}

	/**
	 * Maps a {@link List} of
	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher}
	 * instances.
	 *
	 * @param method the {@link HttpMethod} to use or {@code null} for any
	 * {@link HttpMethod}.
	 * @param regexPatterns the regular expressions to create
	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C regexMatchers(HttpMethod method, String... regexPatterns) {
		Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
		return chainRequestMatchers(RequestMatchers.regexMatchers(method, regexPatterns));
	}

	/**
	 * Create a {@link List} of
	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} instances
	 * that do not specify an {@link HttpMethod}.
	 *
	 * @param regexPatterns the regular expressions to create
	 * {@link org.springframework.security.web.util.matcher.RegexRequestMatcher} from
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C regexMatchers(String... regexPatterns) {
		Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
		return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
	}

	/**
	 * Associates a list of {@link RequestMatcher} instances with the
	 * {@link AbstractConfigAttributeRequestMatcherRegistry}
	 *
	 * @param requestMatchers the {@link RequestMatcher} instances
	 *
	 * @return the object that is chained after creating the {@link RequestMatcher}
	 */
	public C requestMatchers(RequestMatcher... requestMatchers) {
		Assert.state(!this.anyRequestConfigured, "Can't configure requestMatchers after anyRequest");
		return chainRequestMatchers(Arrays.asList(requestMatchers));
	}

	/**
	 * Subclasses should implement this method for returning the object that is chained to
	 * the creation of the {@link RequestMatcher} instances.
	 *
	 * @param requestMatchers the {@link RequestMatcher} instances that were created
	 * @return the chained Object for the subclass which allows association of something
	 * else to the {@link RequestMatcher}
	 */
	protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);

	/**
	 * Utilities for creating {@link RequestMatcher} instances.
	 *
	 * @author Rob Winch
	 * @since 3.2
	 */
	private static final class RequestMatchers {

		/**
		 * Create a {@link List} of {@link AntPathRequestMatcher} instances.
		 *
		 * @param httpMethod the {@link HttpMethod} to use or {@code null} for any
		 * {@link HttpMethod}.
		 * @param antPatterns the ant patterns to create {@link AntPathRequestMatcher}
		 * from
		 *
		 * @return a {@link List} of {@link AntPathRequestMatcher} instances
		 */
		public static List<RequestMatcher> antMatchers(HttpMethod httpMethod,
				String... antPatterns) {
			String method = httpMethod == null ? null : httpMethod.toString();
			List<RequestMatcher> matchers = new ArrayList<>();
			for (String pattern : antPatterns) {
				matchers.add(new AntPathRequestMatcher(pattern, method));
			}
			return matchers;
		}

		/**
		 * Create a {@link List} of {@link AntPathRequestMatcher} instances that do not
		 * specify an {@link HttpMethod}.
		 *
		 * @param antPatterns the ant patterns to create {@link AntPathRequestMatcher}
		 * from
		 *
		 * @return a {@link List} of {@link AntPathRequestMatcher} instances
		 */
		public static List<RequestMatcher> antMatchers(String... antPatterns) {
			return antMatchers(null, antPatterns);
		}

		/**
		 * Create a {@link List} of {@link RegexRequestMatcher} instances.
		 *
		 * @param httpMethod the {@link HttpMethod} to use or {@code null} for any
		 * {@link HttpMethod}.
		 * @param regexPatterns the regular expressions to create
		 * {@link RegexRequestMatcher} from
		 *
		 * @return a {@link List} of {@link RegexRequestMatcher} instances
		 */
		public static List<RequestMatcher> regexMatchers(HttpMethod httpMethod,
				String... regexPatterns) {
			String method = httpMethod == null ? null : httpMethod.toString();
			List<RequestMatcher> matchers = new ArrayList<>();
			for (String pattern : regexPatterns) {
				matchers.add(new RegexRequestMatcher(pattern, method));
			}
			return matchers;
		}

		/**
		 * Create a {@link List} of {@link RegexRequestMatcher} instances that do not
		 * specify an {@link HttpMethod}.
		 *
		 * @param regexPatterns the regular expressions to create
		 * {@link RegexRequestMatcher} from
		 *
		 * @return a {@link List} of {@link RegexRequestMatcher} instances
		 */
		public static List<RequestMatcher> regexMatchers(String... regexPatterns) {
			return regexMatchers(null, regexPatterns);
		}

		private RequestMatchers() {
		}
	}

}


/*
 * Copyright 2002-2013 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.context;

import java.lang.annotation.Annotation;

import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;

/**
 * Allows resolving the {@link Authentication#getPrincipal()} using the
 * {@link AuthenticationPrincipal} annotation. For example, the following
 * {@link Controller}:
 *
 *

 * @Controller
 * public class MyController {
 *     @MessageMapping("/im")
 *     public void im(@AuthenticationPrincipal CustomUser customUser) {
 *         // do something with CustomUser
 *     }
 * }
 *

 *
 *

 * Will resolve the CustomUser argument using {@link Authentication#getPrincipal()} from
 * the {@link SecurityContextHolder}. If the {@link Authentication} or
 * {@link Authentication#getPrincipal()} is null, it will return null. If the types do not
 * match, null will be returned unless
 * {@link AuthenticationPrincipal#errorOnInvalidType()} is true in which case a
 * {@link ClassCastException} will be thrown.
 *
 *

 * Alternatively, users can create a custom meta annotation as shown below:
 *
 *

 * @Target({ ElementType.PARAMETER })
 * @Retention(RetentionPolicy.RUNTIME)
 * @AuthenticationPrincipal
 * public @interface CurrentUser {
 * }
 *

 *
 *

 * The custom annotation can then be used instead. For example:
 *
 *

 * @Controller
 * public class MyController {
 *     @MessageMapping("/im")
 *     public void im(@CurrentUser CustomUser customUser) {
 *         // do something with CustomUser
 *     }
 * }
 *

 *
 * @author Rob Winch
 * @since 4.0
 */
public final class AuthenticationPrincipalArgumentResolver
		implements HandlerMethodArgumentResolver {

	private ExpressionParser parser = new SpelExpressionParser();

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver#
	 * supportsParameter(org.springframework.core.MethodParameter)
	 */
	public boolean supportsParameter(MethodParameter parameter) {
		return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver#
	 * resolveArgument(org.springframework.core.MethodParameter,
	 * org.springframework.messaging.Message)
	 */
	public Object resolveArgument(MethodParameter parameter, Message<?> message)
			throws Exception {
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		if (authentication == null) {
			return null;
		}
		Object principal = authentication.getPrincipal();

		AuthenticationPrincipal authPrincipal = findMethodAnnotation(
				AuthenticationPrincipal.class, parameter);

		String expressionToParse = authPrincipal.expression();
		if (StringUtils.hasLength(expressionToParse)) {
			StandardEvaluationContext context = new StandardEvaluationContext();
			context.setRootObject(principal);
			context.setVariable("this", principal);

			Expression expression = this.parser.parseExpression(expressionToParse);
			principal = expression.getValue(context);
		}

		if (principal != null
				&& !parameter.getParameterType().isAssignableFrom(principal.getClass())) {
			if (authPrincipal.errorOnInvalidType()) {
				throw new ClassCastException(principal + " is not assignable to "
						+ parameter.getParameterType());
			}
			else {
				return null;
			}
		}
		return principal;
	}

	/**
	 * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
	 *
	 * @param annotationClass the class of the {@link Annotation} to find on the
	 * {@link MethodParameter}
	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
	 * @return the {@link Annotation} that was found or null.
	 */
	private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass,
			MethodParameter parameter) {
		T annotation = parameter.getParameterAnnotation(annotationClass);
		if (annotation != null) {
			return annotation;
		}
		Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
		for (Annotation toSearch : annotationsToSearch) {
			annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(),
					annotationClass);
			if (annotation != null) {
				return annotation;
			}
		}
		return null;
	}
}


/*
 * Copyright 2010-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.authentication.jaas;

import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.springframework.security.authentication.jaas.memory.InMemoryConfiguration;
import org.springframework.util.Assert;

/**
 *

 * Creates a LoginContext using the Configuration provided to it. This allows the
 * configuration to be injected regardless of the value of
 * {@link Configuration#getConfiguration()}.
 *

 *

 * While not bound to any particular Configuration implementation, an in memory version of
 * a JAAS configuration can be represented using {@link InMemoryConfiguration}.
 *

 *

 * The following JAAS configuration:
 *

 *
 *

 * SPRINGSECURITY {
 *    sample.SampleLoginModule required;
 *  };
 *

 *
 *

 * Can be represented as follows:
 *

 *
 *

 * <bean id="jaasAuthProvider" class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
 *   <property name="configuration">
 *     <bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
 *       <constructor-arg>
 *         <map>
 *           <!-- SPRINGSECURITY is the default loginContextName for AbstractJaasAuthenticationProvider-->
 *           <entry key="SPRINGSECURITY">
 *             <array>
 *               <bean class="javax.security.auth.login.AppConfigurationEntry">
 *                 <constructor-arg value="sample.SampleLoginModule" />
 *                 <constructor-arg>
 *                   <util:constant static-field="javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED" />
 *                 </constructor-arg>
 *                 <constructor-arg>
 *                   <map></map>
 *                 </constructor-arg>
 *               </bean>
 *             </array>
 *           </entry>
 *         </map>
 *       </constructor-arg>
 *     </bean>
 *   </property>
 *   <property name="authorityGranters">
 *     <list>
 *       <!-- You will need to write your own implementation of AuthorityGranter -->
 *       <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
 *     </list>
 *   </property>
 * </bean>
 *

 *
 * @author Rob Winch
 * @see AbstractJaasAuthenticationProvider
 * @see InMemoryConfiguration
 */
public class DefaultJaasAuthenticationProvider
		extends AbstractJaasAuthenticationProvider {
	// ~ Instance fields
	// ================================================================================================

	private Configuration configuration;

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

	@Override
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();
		Assert.notNull(this.configuration, "configuration cannot be null.");
	}

	/**
	 * Creates a LoginContext using the Configuration that was specified in
	 * {@link #setConfiguration(Configuration)}.
	 */
	@Override
	protected LoginContext createLoginContext(CallbackHandler handler)
			throws LoginException {
		return new LoginContext(getLoginContextName(), null, handler, getConfiguration());
	}

	protected Configuration getConfiguration() {
		return this.configuration;
	}

	/**
	 * Sets the Configuration to use for Authentication.
	 *
	 * @param configuration the Configuration that is used when
	 * {@link #createLoginContext(CallbackHandler)} is called.
	 */
	public void setConfiguration(Configuration configuration) {
		this.configuration = configuration;
	}
}


/*
 * 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.ldap.authentication;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.ldap.ppolicy.PasswordPolicyControl;
import org.springframework.security.ldap.ppolicy.PasswordPolicyControlExtractor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;

/**
 * An authenticator which binds as a user.
 *
 * @author Luke Taylor
 *
 * @see AbstractLdapAuthenticator
 */
public class BindAuthenticator extends AbstractLdapAuthenticator {
	// ~ Static fields/initializers
	// =====================================================================================

	private static final Log logger = LogFactory.getLog(BindAuthenticator.class);

	// ~ Constructors
	// ===================================================================================================

	/**
	 * Create an initialized instance using the {@link BaseLdapPathContextSource}
	 * provided.
	 *
	 * @param contextSource the BaseLdapPathContextSource instance against which bind
	 * operations will be performed.
	 *
	 */
	public BindAuthenticator(BaseLdapPathContextSource contextSource) {
		super(contextSource);
	}

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

	public DirContextOperations authenticate(Authentication authentication) {
		DirContextOperations user = null;
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				"Can only process UsernamePasswordAuthenticationToken objects");

		String username = authentication.getName();
		String password = (String) authentication.getCredentials();

		if (!StringUtils.hasLength(password)) {
			logger.debug("Rejecting empty password for user " + username);
			throw new BadCredentialsException(messages.getMessage(
					"BindAuthenticator.emptyPassword", "Empty Password"));
		}

		// If DN patterns are configured, try authenticating with them directly
		for (String dn : getUserDns(username)) {
			user = bindWithDn(dn, username, password);

			if (user != null) {
				break;
			}
		}

		// Otherwise use the configured search object to find the user and authenticate
		// with the returned DN.
		if (user == null && getUserSearch() != null) {
			DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
			user = bindWithDn(userFromSearch.getDn().toString(), username, password,
					userFromSearch.getAttributes());
		}

		if (user == null) {
			throw new BadCredentialsException(messages.getMessage(
					"BindAuthenticator.badCredentials", "Bad credentials"));
		}

		return user;
	}

	private DirContextOperations bindWithDn(String userDnStr, String username,
			String password) {
		return bindWithDn(userDnStr, username, password, null);
	}

	private DirContextOperations bindWithDn(String userDnStr, String username,
			String password, Attributes attrs) {
		BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
		DistinguishedName userDn = new DistinguishedName(userDnStr);
		DistinguishedName fullDn = new DistinguishedName(userDn);
		fullDn.prepend(ctxSource.getBaseLdapPath());

		logger.debug("Attempting to bind as " + fullDn);

		DirContext ctx = null;
		try {
			ctx = getContextSource().getContext(fullDn.toString(), password);
			// Check for password policy control
			PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor
					.extractControl(ctx);

			logger.debug("Retrieving attributes...");
			if (attrs == null || attrs.size()==0) {
				attrs = ctx.getAttributes(userDn, getUserAttributes());
			}

			DirContextAdapter result = new DirContextAdapter(attrs, userDn,
					ctxSource.getBaseLdapPath());

			if (ppolicy != null) {
				result.setAttributeValue(ppolicy.getID(), ppolicy);
			}

			return result;
		}
		catch (NamingException e) {
			// This will be thrown if an invalid user name is used and the method may
			// be called multiple times to try different names, so we trap the exception
			// unless a subclass wishes to implement more specialized behaviour.
			if ((e instanceof org.springframework.ldap.AuthenticationException)
					|| (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
				handleBindException(userDnStr, username, e);
			}
			else {
				throw e;
			}
		}
		catch (javax.naming.NamingException e) {
			throw LdapUtils.convertLdapException(e);
		}
		finally {
			LdapUtils.closeContext(ctx);
		}

		return null;
	}

	/**
	 * Allows subclasses to inspect the exception thrown by an attempt to bind with a
	 * particular DN. The default implementation just reports the failure to the debug
	 * logger.
	 */
	protected void handleBindException(String userDn, String username, Throwable cause) {
		if (logger.isDebugEnabled()) {
			logger.debug("Failed to bind as " + userDn + ": " + cause);
		}
	}
}


/*
 * Copyright 2002-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.oauth2.client.userinfo;

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.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Collections;

import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;

/**
 * A {@link Converter} that converts the provided {@link OAuth2UserRequest}
 * to a {@link RequestEntity} representation of a request for the UserInfo Endpoint.
 *
 * @author Joe Grandja
 * @since 5.1
 * @see Converter
 * @see OAuth2UserRequest
 * @see RequestEntity
 */
public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserRequest, RequestEntity<?>> {
	private static final MediaType DEFAULT_CONTENT_TYPE = MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");

	/**
	 * Returns the {@link RequestEntity} used for the UserInfo Request.
	 *
	 * @param userRequest the user request
	 * @return the {@link RequestEntity} used for the UserInfo Request
	 */
	@Override
	public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
		ClientRegistration clientRegistration = userRequest.getClientRegistration();

		HttpMethod httpMethod = HttpMethod.GET;
		if (AuthenticationMethod.FORM.equals(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())) {
			httpMethod = HttpMethod.POST;
		}
		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
		URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
				.build()
				.toUri();

		RequestEntity<?> request;
		if (HttpMethod.POST.equals(httpMethod)) {
			headers.setContentType(DEFAULT_CONTENT_TYPE);
			MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
			formParameters.add(OAuth2ParameterNames.ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue());
			request = new RequestEntity<>(formParameters, headers, httpMethod, uri);
		} else {
			headers.setBearerAuth(userRequest.getAccessToken().getTokenValue());
			request = new RequestEntity<>(headers, httpMethod, uri);
		}

		return request;
	}
}


/*
 * Copyright 2002-2013 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.csrf;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 *

 * Applies
 * CSRF
 * protection using a synchronizer token pattern. Developers are required to ensure that
 * {@link CsrfFilter} is invoked for any request that allows state to change. Typically
 * this just means that they should ensure their web application follows proper REST
 * semantics (i.e. do not change state with the HTTP methods GET, HEAD, TRACE, OPTIONS).
 *

 *
 *

 * Typically the {@link CsrfTokenRepository} implementation chooses to store the
 * {@link CsrfToken} in {@link HttpSession} with {@link HttpSessionCsrfTokenRepository}
 * wrapped by a {@link LazyCsrfTokenRepository}. This is preferred to storing the token in
 * a cookie which can be modified by a client application.
 *

 *
 * @author Rob Winch
 * @since 3.2
 */
public final class CsrfFilter extends OncePerRequestFilter {
	/**
	 * The default {@link RequestMatcher} that indicates if CSRF protection is required or
	 * not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
	 * requests.
	 */
	public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();

	private final Log logger = LogFactory.getLog(getClass());
	private final CsrfTokenRepository tokenRepository;
	private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
	private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

	public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
		Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
		this.tokenRepository = csrfTokenRepository;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet
	 * .http.HttpServletRequest, javax.servlet.http.HttpServletResponse,
	 * javax.servlet.FilterChain)
	 */
	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);

		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if (!csrfToken.getToken().equals(actualToken)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return;
		}

		filterChain.doFilter(request, response);
	}

	/**
	 * Specifies a {@link RequestMatcher} that is used to determine if CSRF protection
	 * should be applied. If the {@link RequestMatcher} returns true for a given request,
	 * then CSRF protection is applied.
	 *
	 *

	 * The default is to apply CSRF protection for any HTTP method other than GET, HEAD,
	 * TRACE, OPTIONS.
	 *

	 *
	 * @param requireCsrfProtectionMatcher the {@link RequestMatcher} used to determine if
	 * CSRF protection should be applied.
	 */
	public void setRequireCsrfProtectionMatcher(
			RequestMatcher requireCsrfProtectionMatcher) {
		Assert.notNull(requireCsrfProtectionMatcher,
				"requireCsrfProtectionMatcher cannot be null");
		this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
	}

	/**
	 * Specifies a {@link AccessDeniedHandler} that should be used when CSRF protection
	 * fails.
	 *
	 *

	 * The default is to use AccessDeniedHandlerImpl with no arguments.
	 *

	 *
	 * @param accessDeniedHandler the {@link AccessDeniedHandler} to use
	 */
	public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
		Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
		this.accessDeniedHandler = accessDeniedHandler;
	}

	private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
		private final HashSet<String> allowedMethods = new HashSet<>(
				Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

		/*
		 * (non-Javadoc)
		 *
		 * @see
		 * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
		 * servlet.http.HttpServletRequest)
		 */
		@Override
		public boolean matches(HttpServletRequest request) {
			return !this.allowedMethods.contains(request.getMethod());
		}
	}
}