/*
 * Copyright 2012-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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.security;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.Ordered;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.util.StringUtils;

/**
 * Properties for the security aspects of an application.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 */
@ConfigurationProperties(prefix = "security")
public class SecurityProperties implements SecurityPrerequisite {

	/**
	 * Order before the basic authentication access control provided by Boot. This is a
	 * useful place to put user-defined access rules if you want to override the default
	 * access rules.
	 */
	public static final int ACCESS_OVERRIDE_ORDER = SecurityProperties.BASIC_AUTH_ORDER
			- 2;

	/**
	 * Order applied to the WebSecurityConfigurerAdapter that is used to configure basic
	 * authentication for application endpoints. If you want to add your own
	 * authentication for all or some of those endpoints the best thing to do is to add
	 * your own WebSecurityConfigurerAdapter with lower order.
	 */
	public static final int BASIC_AUTH_ORDER = Ordered.LOWEST_PRECEDENCE - 5;

	/**
	 * Order applied to the WebSecurityConfigurer that ignores standard static resource
	 * paths.
	 */
	public static final int IGNORED_ORDER = Ordered.HIGHEST_PRECEDENCE;

	/**
	 * Default order of Spring Security's Filter in the servlet container (i.e. amongst
	 * other filters registered with the container). There is no connection between this
	 * and the <code>@Order</code> on a WebSecurityConfigurer.
	 */
	public static final int DEFAULT_FILTER_ORDER = FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER
			- 100;

	/**
	 * Enable secure channel for all requests.
	 */
	private boolean requireSsl;

	/**
	 * Enable Cross Site Request Forgery support.
	 */
	// Flip this when session creation is disabled by default
	private boolean enableCsrf = false;

	private Basic basic = new Basic();

	private final Headers headers = new Headers();

	/**
	 * Session creation policy (always, never, if_required, stateless).
	 */
	private SessionCreationPolicy sessions = SessionCreationPolicy.STATELESS;

	/**
	 * Comma-separated list of paths to exclude from the default secured paths.
	 */
	private List<String> ignored = new ArrayList<>();

	private final User user = new User();

	/**
	 * Security filter chain order.
	 */
	private int filterOrder = DEFAULT_FILTER_ORDER;

	/**
	 * Security filter chain dispatcher types.
	 */
	private Set<String> filterDispatcherTypes = new HashSet<>(
			Arrays.asList("ASYNC", "ERROR", "REQUEST"));

	public Headers getHeaders() {
		return this.headers;
	}

	public User getUser() {
		return this.user;
	}

	public SessionCreationPolicy getSessions() {
		return this.sessions;
	}

	public void setSessions(SessionCreationPolicy sessions) {
		this.sessions = sessions;
	}

	public Basic getBasic() {
		return this.basic;
	}

	public void setBasic(Basic basic) {
		this.basic = basic;
	}

	public boolean isRequireSsl() {
		return this.requireSsl;
	}

	public void setRequireSsl(boolean requireSsl) {
		this.requireSsl = requireSsl;
	}

	public boolean isEnableCsrf() {
		return this.enableCsrf;
	}

	public void setEnableCsrf(boolean enableCsrf) {
		this.enableCsrf = enableCsrf;
	}

	public void setIgnored(List<String> ignored) {
		this.ignored = new ArrayList<>(ignored);
	}

	public List<String> getIgnored() {
		return this.ignored;
	}

	public int getFilterOrder() {
		return this.filterOrder;
	}

	public void setFilterOrder(int filterOrder) {
		this.filterOrder = filterOrder;
	}

	public Set<String> getFilterDispatcherTypes() {
		return this.filterDispatcherTypes;
	}

	public void setFilterDispatcherTypes(Set<String> filterDispatcherTypes) {
		this.filterDispatcherTypes = filterDispatcherTypes;
	}

	public static class Headers {

		public enum HSTS {

			NONE, DOMAIN, ALL

		}

		public enum ContentSecurityPolicyMode {

			/**
			 * Use the 'Content-Security-Policy' header.
			 */
			DEFAULT,

			/**
			 * Use the 'Content-Security-Policy-Report-Only' header.
			 */
			REPORT_ONLY

		}

		/**
		 * Enable cross site scripting (XSS) protection.
		 */
		private boolean xss = true;

		/**
		 * Enable cache control HTTP headers.
		 */
		private boolean cache = true;

		/**
		 * Enable "X-Frame-Options" header.
		 */
		private boolean frame = true;

		/**
		 * Enable "X-Content-Type-Options" header.
		 */
		private boolean contentType = true;

		/**
		 * Value for content security policy header.
		 */
		private String contentSecurityPolicy;

		/**
		 * Content security policy mode.
		 */
		private ContentSecurityPolicyMode contentSecurityPolicyMode = ContentSecurityPolicyMode.DEFAULT;

		/**
		 * HTTP Strict Transport Security (HSTS) mode (none, domain, all).
		 */
		private HSTS hsts = HSTS.ALL;

		public boolean isXss() {
			return this.xss;
		}

		public void setXss(boolean xss) {
			this.xss = xss;
		}

		public boolean isCache() {
			return this.cache;
		}

		public void setCache(boolean cache) {
			this.cache = cache;
		}

		public boolean isFrame() {
			return this.frame;
		}

		public void setFrame(boolean frame) {
			this.frame = frame;
		}

		public boolean isContentType() {
			return this.contentType;
		}

		public void setContentType(boolean contentType) {
			this.contentType = contentType;
		}

		public String getContentSecurityPolicy() {
			return this.contentSecurityPolicy;
		}

		public void setContentSecurityPolicy(String contentSecurityPolicy) {
			this.contentSecurityPolicy = contentSecurityPolicy;
		}

		public ContentSecurityPolicyMode getContentSecurityPolicyMode() {
			return this.contentSecurityPolicyMode;
		}

		public void setContentSecurityPolicyMode(
				ContentSecurityPolicyMode contentSecurityPolicyMode) {
			this.contentSecurityPolicyMode = contentSecurityPolicyMode;
		}

		public HSTS getHsts() {
			return this.hsts;
		}

		public void setHsts(HSTS hsts) {
			this.hsts = hsts;
		}

	}

	public static class Basic {

		/**
		 * Enable basic authentication.
		 */
		private boolean enabled = true;

		/**
		 * HTTP basic realm name.
		 */
		private String realm = "Spring";

		/**
		 * Comma-separated list of paths to secure.
		 */
		private String[] path = new String[] { "/**" };

		/**
		 * Security authorize mode to apply.
		 */
		private SecurityAuthorizeMode authorizeMode = SecurityAuthorizeMode.ROLE;

		public boolean isEnabled() {
			return this.enabled;
		}

		public void setEnabled(boolean enabled) {
			this.enabled = enabled;
		}

		public String getRealm() {
			return this.realm;
		}

		public void setRealm(String realm) {
			this.realm = realm;
		}

		public String[] getPath() {
			return this.path;
		}

		public void setPath(String... paths) {
			this.path = paths;
		}

		public SecurityAuthorizeMode getAuthorizeMode() {
			return this.authorizeMode;
		}

		public void setAuthorizeMode(SecurityAuthorizeMode authorizeMode) {
			this.authorizeMode = authorizeMode;
		}

	}

	public static class User {

		/**
		 * Default user name.
		 */
		private String name = "user";

		/**
		 * Password for the default user name.
		 */
		private String password = UUID.randomUUID().toString();

		/**
		 * Granted roles for the default user name.
		 */
		private List<String> role = new ArrayList<>(Collections.singletonList("USER"));

		private boolean defaultPassword = true;

		public String getName() {
			return this.name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			if (password.startsWith("${") && password.endsWith("}")
					|| !StringUtils.hasLength(password)) {
				return;
			}
			this.defaultPassword = false;
			this.password = password;
		}

		public List<String> getRole() {
			return this.role;
		}

		public void setRole(List<String> role) {
			this.role = new ArrayList<>(role);
		}

		public boolean isDefaultPassword() {
			return this.defaultPassword;
		}

	}

}
