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

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.acls.model.SidRetrievalStrategy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * Default implementation of {@link AclAuthorizationStrategy}.
 *

 * Permission will be granted if at least one of the following conditions is true for the
 * current principal.
 *

 *
is the owner (as defined by the ACL).

 *
holds the relevant system-wide {@link GrantedAuthority} injected into the
 * constructor.

 *
has {@link BasePermission#ADMINISTRATION} permission (as defined by the ACL).

 *

 *
 * @author Ben Alex
 */
public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
	// ~ Instance fields
	// ================================================================================================

	private final GrantedAuthority gaGeneralChanges;
	private final GrantedAuthority gaModifyAuditing;
	private final GrantedAuthority gaTakeOwnership;
	private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();

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

	/**
	 * Constructor. The only mandatory parameter relates to the system-wide
	 * {@link GrantedAuthority} instances that can be held to always permit ACL changes.
	 *
	 * @param auths the GrantedAuthoritys that have special permissions
	 * (index 0 is the authority needed to change ownership, index 1 is the authority
	 * needed to modify auditing details, index 2 is the authority needed to change other
	 * ACL and ACE details) (required)
	 *

	 * Alternatively, a single value can be supplied for all three permissions.
	 */
	public AclAuthorizationStrategyImpl(GrantedAuthority... auths) {
		Assert.isTrue(auths != null && (auths.length == 3 || auths.length == 1),
				"One or three GrantedAuthority instances required");
		if (auths.length == 3) {
			gaTakeOwnership = auths[0];
			gaModifyAuditing = auths[1];
			gaGeneralChanges = auths[2];
		}
		else {
			gaTakeOwnership = gaModifyAuditing = gaGeneralChanges = auths[0];
		}
	}

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

	public void securityCheck(Acl acl, int changeType) {
		if ((SecurityContextHolder.getContext() == null)
				|| (SecurityContextHolder.getContext().getAuthentication() == null)
				|| !SecurityContextHolder.getContext().getAuthentication()
						.isAuthenticated()) {
			throw new AccessDeniedException(
					"Authenticated principal required to operate with ACLs");
		}

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

		// Check if authorized by virtue of ACL ownership
		Sid currentUser = createCurrentUser(authentication);

		if (currentUser.equals(acl.getOwner())
				&& ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
			return;
		}

		// Not authorized by ACL ownership; try via adminstrative permissions
		GrantedAuthority requiredAuthority;

		if (changeType == CHANGE_AUDITING) {
			requiredAuthority = this.gaModifyAuditing;
		}
		else if (changeType == CHANGE_GENERAL) {
			requiredAuthority = this.gaGeneralChanges;
		}
		else if (changeType == CHANGE_OWNERSHIP) {
			requiredAuthority = this.gaTakeOwnership;
		}
		else {
			throw new IllegalArgumentException("Unknown change type");
		}

		// Iterate this principal's authorities to determine right
		Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
		if (authorities.contains(requiredAuthority.getAuthority())) {
			return;
		}

		// Try to get permission via ACEs within the ACL
		List<Sid> sids = sidRetrievalStrategy.getSids(authentication);

		if (acl.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), sids, false)) {
			return;
		}

		throw new AccessDeniedException(
				"Principal does not have required ACL permissions to perform requested operation");
	}

	/**
	 * Creates a principal-like sid from the authentication information.
	 *
	 * @param authentication the authentication information that can provide principal and
	 * thus the sid's id will be dependant on the value inside
	 * @return a sid with the ID taken from the authentication information
	 */
	protected Sid createCurrentUser(Authentication authentication) {
		return new PrincipalSid(authentication);
	}

	public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
		Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required");
		this.sidRetrievalStrategy = sidRetrievalStrategy;
	}
}


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

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
import org.springframework.util.MultiValueMap;

/**
 * Creates Spring Security's MethodSecurityMetadataSourceAdvisor only when
 * using proxy based method security (i.e. do not do it when using ASPECTJ).
 * The conditional logic is controlled through {@link GlobalMethodSecuritySelector}.
 *
 * @author Rob Winch
 * @since 4.0.2
 * @see GlobalMethodSecuritySelector
 */
class MethodSecurityMetadataSourceAdvisorRegistrar implements
		ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableGlobalMethodSecurity#proxyTargetClass()} attribute on the
	 * importing {@code @Configuration} class.
	 */
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry registry) {

		BeanDefinitionBuilder advisor = BeanDefinitionBuilder
				.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
		advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		advisor.addConstructorArgValue("methodSecurityInterceptor");
		advisor.addConstructorArgReference("methodSecurityMetadataSource");
		advisor.addConstructorArgValue("methodSecurityMetadataSource");

		MultiValueMap<String, Object> attributes = importingClassMetadata.getAllAnnotationAttributes(EnableGlobalMethodSecurity.class.getName());
		Integer order = (Integer) attributes.getFirst("order");
		if (order != null) {
			advisor.addPropertyValue("order", order);
		}

		registry.registerBeanDefinition("metaDataSourceAdvisor",
				advisor.getBeanDefinition());
	}
}


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

import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.util.Assert;

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

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * LDAP Utility methods.
 *
 * @author Luke Taylor
 */
public final class LdapUtils {
	// ~ Static fields/initializers
	// =====================================================================================

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

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

	private LdapUtils() {
	}

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

	public static void closeContext(Context ctx) {
		if (ctx instanceof DirContextAdapter) {
			return;
		}

		try {
			if (ctx != null) {
				ctx.close();
			}
		}
		catch (NamingException e) {
			logger.error("Failed to close context.", e);
		}
	}

	public static void closeEnumeration(NamingEnumeration ne) {
		try {
			if (ne != null) {
				ne.close();
			}
		}
		catch (NamingException e) {
			logger.error("Failed to close enumeration.", e);
		}
	}

	/**
	 * Obtains the part of a DN relative to a supplied base context.
	 *

	 * If the DN is "cn=bob,ou=people,dc=springframework,dc=org" and the base context name
	 * is "ou=people,dc=springframework,dc=org" it would return "cn=bob".
	 *

	 *
	 * @param fullDn the DN
	 * @param baseCtx the context to work out the name relative to.
	 *
	 * @return the
	 *
	 * @throws NamingException any exceptions thrown by the context are propagated.
	 */
	public static String getRelativeName(String fullDn, Context baseCtx)
			throws NamingException {

		String baseDn = baseCtx.getNameInNamespace();

		if (baseDn.length() == 0) {
			return fullDn;
		}

		DistinguishedName base = new DistinguishedName(baseDn);
		DistinguishedName full = new DistinguishedName(fullDn);

		if (base.equals(full)) {
			return "";
		}

		Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");

		full.removeFirst(base);

		return full.toString();
	}

	/**
	 * Gets the full dn of a name by prepending the name of the context it is relative to.
	 * If the name already contains the base name, it is returned unaltered.
	 */
	public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx)
			throws NamingException {
		DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());

		if (dn.contains(baseDn)) {
			return dn;
		}

		baseDn.append(dn);

		return baseDn;
	}

	public static String convertPasswordToString(Object passObj) {
		Assert.notNull(passObj, "Password object to convert must not be null");

		if (passObj instanceof byte[]) {
			return Utf8.decode((byte[]) passObj);
		}
		else if (passObj instanceof String) {
			return (String) passObj;
		}
		else {
			throw new IllegalArgumentException(
					"Password object was not a String or byte array.");
		}
	}

	/**
	 * Works out the root DN for an LDAP URL.
	 *

	 * For example, the URL <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>
	 * has the root DN "dc=springframework,dc=org".
	 *

	 *
	 * @param url the LDAP URL
	 *
	 * @return the root DN
	 */
	public static String parseRootDnFromUrl(String url) {
		Assert.hasLength(url, "url must have length");

		String urlRootDn;

		if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
			URI uri = parseLdapUrl(url);
			urlRootDn = uri.getRawPath();
		}
		else {
			// Assume it's an embedded server
			urlRootDn = url;
		}

		if (urlRootDn.startsWith("/")) {
			urlRootDn = urlRootDn.substring(1);
		}

		return urlRootDn;
	}

	/**
	 * Parses the supplied LDAP URL.
	 * @param url the URL (e.g.
	 * <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>).
	 * @return the URI object created from the URL
	 * @throws IllegalArgumentException if the URL is null, empty or the URI syntax is
	 * invalid.
	 */

	private static URI parseLdapUrl(String url) {
		Assert.hasLength(url, "url must have length");

		try {
			return new URI(url);
		}
		catch (URISyntaxException e) {
			IllegalArgumentException iae = new IllegalArgumentException(
					"Unable to parse url: " + url);
			iae.initCause(e);
			throw iae;
		}
	}
}


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

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.Assert;

/**
 * Reactive version of {@link JwtAuthenticationConverter} for converting a {@link Jwt}
 * to a {@link AbstractAuthenticationToken Mono<AbstractAuthenticationToken>}.
 *
 * @author Eric Deandrea
 * @since 5.2
 */
public final class ReactiveJwtAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
	private Converter<Jwt, Flux<GrantedAuthority>> jwtGrantedAuthoritiesConverter
			= new ReactiveJwtGrantedAuthoritiesConverterAdapter(new JwtGrantedAuthoritiesConverter());

	@Override
	public Mono<AbstractAuthenticationToken> convert(Jwt jwt) {
		return this.jwtGrantedAuthoritiesConverter.convert(jwt)
				.collectList()
				.map(authorities -> new JwtAuthenticationToken(jwt, authorities));
	}

	/**
	 * Sets the {@link Converter Converter<Jwt, Flux<GrantedAuthority>>} to use.
	 * Defaults to a reactive {@link JwtGrantedAuthoritiesConverter}.
	 *
	 * @param jwtGrantedAuthoritiesConverter The converter
	 * @see JwtGrantedAuthoritiesConverter
	 */
	public void setJwtGrantedAuthoritiesConverter(Converter<Jwt, Flux<GrantedAuthority>> jwtGrantedAuthoritiesConverter) {
		Assert.notNull(jwtGrantedAuthoritiesConverter, "jwtGrantedAuthoritiesConverter cannot be null");
		this.jwtGrantedAuthoritiesConverter = jwtGrantedAuthoritiesConverter;
	}
}


/*
 * 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.web.server.transport;

import java.net.URI;
import java.util.Optional;

import reactor.core.publisher.Mono;

import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.UriComponentsBuilder;

import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.anyExchange;

/**
 * Redirects any non-HTTPS request to its HTTPS equivalent.
 *
 * Can be configured to use a {@link ServerWebExchangeMatcher} to narrow which requests get redirected.
 *
 * Can also be configured for custom ports using {@link PortMapper}.
 *
 * @author Josh Cummings
 * @since 5.1
 */
public final class HttpsRedirectWebFilter implements WebFilter {
	private PortMapper portMapper = new PortMapperImpl();

	private ServerWebExchangeMatcher requiresHttpsRedirectMatcher = anyExchange();

	private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		return Mono.just(exchange)
				.filter(this::isInsecure)
				.flatMap(this.requiresHttpsRedirectMatcher::matches)
				.filter(matchResult -> matchResult.isMatch())
				.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
				.map(matchResult -> createRedirectUri(exchange))
				.flatMap(uri -> this.redirectStrategy.sendRedirect(exchange, uri));
	}

	/**
	 * Use this {@link PortMapper} for mapping custom ports
	 *
	 * @param portMapper the {@link PortMapper} to use
	 */
	public void setPortMapper(PortMapper portMapper) {
		Assert.notNull(portMapper, "portMapper cannot be null");
		this.portMapper = portMapper;
	}

	/**
	 * Use this {@link ServerWebExchangeMatcher} to narrow which requests are redirected to HTTPS.
	 *
	 * The filter already first checks for HTTPS in the uri scheme, so it is not necessary
	 * to include that check in this matcher.
	 *
	 * @param requiresHttpsRedirectMatcher the {@link ServerWebExchangeMatcher} to use
	 */
	public void setRequiresHttpsRedirectMatcher
			(ServerWebExchangeMatcher requiresHttpsRedirectMatcher) {

		Assert.notNull(requiresHttpsRedirectMatcher,
				"requiresHttpsRedirectMatcher cannot be null");
		this.requiresHttpsRedirectMatcher = requiresHttpsRedirectMatcher;
	}

	private boolean isInsecure(ServerWebExchange exchange) {
		return !"https".equals(exchange.getRequest().getURI().getScheme());
	}

	private URI createRedirectUri(ServerWebExchange exchange) {
		int port = exchange.getRequest().getURI().getPort();

		UriComponentsBuilder builder =
				UriComponentsBuilder.fromUri(exchange.getRequest().getURI());

		if (port > 0) {
			Optional.ofNullable(this.portMapper.lookupHttpsPort(port))
					.map(builder::port)
					.orElseThrow(() -> new IllegalStateException(
							"HTTP Port '" + port + "' does not have a corresponding HTTPS Port"));
		}

		return builder.scheme("https").build().toUri();
	}
}


/*
 * Copyright 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
 *
 *        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 io.restassured.module.mockmvc.internal;

import io.restassured.config.LogConfig;
import io.restassured.config.RestAssuredConfig;
import io.restassured.filter.log.RequestLoggingFilter;
import io.restassured.http.*;
import io.restassured.internal.MapCreator;
import io.restassured.internal.MapCreator.CollisionStrategy;
import io.restassured.internal.log.LogRepository;
import io.restassured.internal.mapping.ObjectMapping;
import io.restassured.internal.support.ParameterUpdater;
import io.restassured.mapper.ObjectMapper;
import io.restassured.mapper.ObjectMapperType;
import io.restassured.module.mockmvc.config.RestAssuredMockMvcConfig;
import io.restassured.module.mockmvc.intercept.MockHttpServletRequestBuilderInterceptor;
import io.restassured.module.mockmvc.response.MockMvcResponse;
import io.restassured.module.mockmvc.specification.*;
import io.restassured.module.spring.commons.BodyHelper;
import io.restassured.module.spring.commons.CookieHelper;
import io.restassured.module.spring.commons.HeaderHelper;
import io.restassured.module.spring.commons.Serializer;
import io.restassured.module.spring.commons.config.AsyncConfig;
import io.restassured.module.spring.commons.config.ConfigConverter;
import io.restassured.module.spring.commons.config.ConfigMergeUtils;
import io.restassured.specification.ResponseSpecification;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;

import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.security.Principal;
import java.util.*;

import static io.restassured.internal.common.assertion.AssertParameter.notNull;
import static io.restassured.internal.serialization.SerializationSupport.isSerializableCandidate;
import static io.restassured.module.mockmvc.internal.SpringSecurityClassPathChecker.isSpringSecurityInClasspath;
import static io.restassured.module.mockmvc.internal.UpdateStrategyConverter.convert;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

public class MockMvcRequestSpecificationImpl implements MockMvcRequestSpecification, MockMvcAuthenticationSpecification {

    private static final String CONTENT_TYPE = "Content-Type";
    private static final String ACCEPT = "Accept";

    private LogRepository logRepository;

    private MockMvcFactory mockMvcFactory;

    private String basePath;

    private final ResponseSpecification responseSpecification;

    private final Map<String, Object> params = new LinkedHashMap<String, Object>();
    private final Map<String, Object> queryParams = new LinkedHashMap<String, Object>();
    private final Map<String, Object> formParams = new LinkedHashMap<String, Object>();
    private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();

    private Object requestBody = null;

    private RestAssuredMockMvcConfig cfg;

    private Headers requestHeaders = new Headers();

    private Cookies cookies = new Cookies();

    private List<MockMvcMultiPart> multiParts = new ArrayList<MockMvcMultiPart>();

    private RequestLoggingFilter requestLoggingFilter;

    private final ParameterUpdater
            parameterUpdater = new ParameterUpdater(new ParameterUpdater.Serializer() {
        public String serializeIfNeeded(Object value) {
            return MockMvcRequestSpecificationImpl.this.serializeIfNeeded(value);
        }
    });

    private final List<ResultHandler> resultHandlers = new ArrayList<ResultHandler>();

    private final List<RequestPostProcessor> requestPostProcessors = new ArrayList<RequestPostProcessor>();

    private MockHttpServletRequestBuilderInterceptor interceptor;

    private Object authentication;

    private AsyncConfig asyncConfig;

    private final Map<String, Object> sessionAttributes = new LinkedHashMap<String, Object>();

    public MockMvcRequestSpecificationImpl(MockMvcFactory mockMvcFactory, RestAssuredMockMvcConfig config, List<ResultHandler> resultHandlers,
                                           List<RequestPostProcessor> requestPostProcessors, String basePath,
                                           MockMvcRequestSpecification requestSpecification, ResponseSpecification responseSpecification,
                                           MockMvcAuthenticationScheme authentication) {
        this.logRepository = new LogRepository();
        this.mockMvcFactory = mockMvcFactory == null ? new MockMvcFactory() : mockMvcFactory;
        this.basePath = basePath;
        this.responseSpecification = responseSpecification;
        assignConfig(config);

        if (resultHandlers != null) {
            this.resultHandlers.addAll(resultHandlers);
        }

        if (requestPostProcessors != null) {
            this.requestPostProcessors.addAll(requestPostProcessors);
        }

        if (requestSpecification != null) {
            spec(requestSpecification);
        }

        if (authentication != null) {
            authentication.authenticate(this);
        }
    }

    public MockMvcRequestSpecification mockMvc(MockMvc mockMvc) {
        notNull(mockMvc, MockMvc.class);
        return changeMockMvcFactoryTo(new MockMvcFactory(mockMvc));
    }

    public MockMvcRequestSpecification standaloneSetup(Object... controllerOrMockMvcConfigurer) {
        MockMvcFactory mockMvcFactory = StandaloneMockMvcFactory.of(controllerOrMockMvcConfigurer);
        return changeMockMvcFactoryTo(mockMvcFactory);
    }

    public MockMvcRequestSpecification standaloneSetup(MockMvcBuilder builder) {
        notNull(builder, MockMvcBuilder.class);
        return changeMockMvcFactoryTo(new MockMvcFactory(builder));
    }

    public MockMvcRequestSpecification webAppContextSetup(WebApplicationContext context, MockMvcConfigurer... mockMvcConfigurers) {
        DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context);
        if (mockMvcConfigurers != null && mockMvcConfigurers.length > 0) {
            for (MockMvcConfigurer mockMvcConfigurer : mockMvcConfigurers) {
                builder.apply(mockMvcConfigurer);
            }
        }
        return changeMockMvcFactoryTo(new MockMvcFactory(builder));
    }

    public MockMvcRequestSpecification interceptor(MockHttpServletRequestBuilderInterceptor interceptor) {
        this.interceptor = interceptor;
        return this;
    }

    public MockMvcRequestSpecification and() {
        return this;
    }

    public MockMvcRequestSpecification postProcessors(RequestPostProcessor postProcessor, RequestPostProcessor... additionalPostProcessors) {
        notNull(postProcessor, RequestPostProcessor.class);
        this.requestPostProcessors.add(postProcessor);
        if (additionalPostProcessors != null && additionalPostProcessors.length >= 1) {
            Collections.addAll(this.requestPostProcessors, additionalPostProcessors);
        }
        return this;
    }

    public MockMvcAuthenticationSpecification auth() {
        return this;
    }

    public MockMvcRequestSpecification contentType(ContentType contentType) {
        notNull(contentType, "contentType");
        return header(CONTENT_TYPE, contentType.toString());
    }

    public MockMvcRequestSpecification contentType(String contentType) {
        notNull(contentType, "contentType");
        return header(CONTENT_TYPE, contentType);
    }

    public MockMvcRequestSpecification accept(ContentType contentType) {
        notNull(contentType, "contentType");
        return header(ACCEPT, contentType.getAcceptHeader());
    }

    public MockMvcRequestSpecification accept(MediaType... mediaTypes) {
        notNull(mediaTypes, "mediaTypes");
        return header(ACCEPT, MediaType.toString(Arrays.asList(mediaTypes)));
    }

    public MockMvcRequestSpecification accept(String mediaTypes) {
        notNull(mediaTypes, "mediaTypes");
        return header(ACCEPT, mediaTypes);
    }

    public MockMvcRequestSpecification headers(String firstHeaderName, Object firstHeaderValue, Object... headerNameValuePairs) {
        return headers(MapCreator.createMapFromParams(CollisionStrategy.MERGE, firstHeaderName, firstHeaderValue, headerNameValuePairs));
    }

    public MockMvcRequestSpecification headers(Map<String, ?> headers) {
        notNull(headers, "headers");
        this.requestHeaders = HeaderHelper.headers(requestHeaders, headers, cfg);
        return this;
    }

    public MockMvcRequestSpecification headers(Headers headers) {
        notNull(headers, "Headers");
        requestHeaders = HeaderHelper.headers(requestHeaders, headers, cfg.getHeaderConfig());
        return this;
    }

    public MockMvcRequestSpecification header(final String headerName, final Object headerValue, Object... additionalHeaderValues) {
        notNull(headerName, "Header name");
        notNull(headerValue, "Header value");
        return headers(HeaderHelper.headers(requestHeaders, headerName, headerValue, cfg, additionalHeaderValues));
    }

    public MockMvcRequestSpecification header(Header header) {
        notNull(header, "Header");
        return headers(new Headers(Collections.singletonList(header)));
    }

    public MockMvcRequestLogSpecification log() {
        return new MockMvcRequestLogSpecificationImpl(this);
    }

    public MockMvcRequestSpecification params(String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
        notNull(firstParameterName, "firstParameterName");
        notNull(firstParameterValue, "firstParameterValue");
        return params(MapCreator.createMapFromParams(CollisionStrategy.OVERWRITE, firstParameterName, firstParameterValue, parameterNameValuePairs));
    }

    public MockMvcRequestSpecification params(Map<String, ?> parametersMap) {
        notNull(parametersMap, "parametersMap");
        parameterUpdater.updateParameters(convert(cfg.getParamConfig().requestParamsUpdateStrategy()), (Map<String, Object>) parametersMap, params);
        return this;
    }

    public MockMvcRequestSpecification param(String parameterName, Object... parameterValues) {
        notNull(parameterName, "parameterName");
        parameterUpdater.updateZeroToManyParameters(convert(cfg.getParamConfig().requestParamsUpdateStrategy()), params, parameterName, parameterValues);
        return this;
    }

    public MockMvcRequestSpecification param(String parameterName, Collection<?> parameterValues) {
        notNull(parameterName, "parameterName");
        notNull(parameterValues, "parameterValues");
        parameterUpdater.updateCollectionParameter(convert(cfg.getParamConfig().requestParamsUpdateStrategy()), params, parameterName, (Collection<Object>) parameterValues);
        return this;
    }

    public MockMvcRequestSpecification queryParams(String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
        notNull(firstParameterName, "firstParameterName");
        notNull(firstParameterValue, "firstParameterValue");
        return queryParams(MapCreator.createMapFromParams(CollisionStrategy.OVERWRITE, firstParameterName, firstParameterValue, parameterNameValuePairs));
    }

    public MockMvcRequestSpecification queryParams(Map<String, ?> parametersMap) {
        notNull(parametersMap, "parametersMap");
        parameterUpdater.updateParameters(convert(cfg.getParamConfig().queryParamsUpdateStrategy()), (Map<String, Object>) parametersMap, queryParams);
        return this;
    }

    public MockMvcRequestSpecification queryParam(String parameterName, Object... parameterValues) {
        notNull(parameterName, "parameterName");
        parameterUpdater.updateZeroToManyParameters(convert(cfg.getParamConfig().queryParamsUpdateStrategy()), queryParams, parameterName, parameterValues);
        return this;
    }

    public MockMvcRequestSpecification queryParam(String parameterName, Collection<?> parameterValues) {
        notNull(parameterName, "parameterName");
        notNull(parameterValues, "parameterValues");
        parameterUpdater.updateCollectionParameter(convert(cfg.getParamConfig().queryParamsUpdateStrategy()), queryParams, parameterName, (Collection<Object>) parameterValues);
        return this;
    }

    public MockMvcRequestSpecification formParams(String firstParameterName, Object firstParameterValue, Object... parameterNameValuePairs) {
        notNull(firstParameterName, "firstParameterName");
        notNull(firstParameterValue, "firstParameterValue");
        return formParams(MapCreator.createMapFromParams(CollisionStrategy.OVERWRITE, firstParameterName, firstParameterValue, parameterNameValuePairs));
    }

    public MockMvcRequestSpecification formParams(Map<String, ?> parametersMap) {
        notNull(parametersMap, "parametersMap");
        parameterUpdater.updateParameters(convert(cfg.getParamConfig().formParamsUpdateStrategy()), (Map<String, Object>) parametersMap, formParams);
        return this;
    }

    public MockMvcRequestSpecification formParam(String parameterName, Object... parameterValues) {
        notNull(parameterName, "parameterName");
        parameterUpdater.updateZeroToManyParameters(convert(cfg.getParamConfig().formParamsUpdateStrategy()), formParams, parameterName, parameterValues);
        return this;
    }

    public MockMvcRequestSpecification formParam(String parameterName, Collection<?> parameterValues) {
        notNull(parameterName, "parameterName");
        notNull(parameterValues, "parameterValues");
        parameterUpdater.updateCollectionParameter(convert(cfg.getParamConfig().formParamsUpdateStrategy()), formParams, parameterName, (Collection<Object>) parameterValues);
        return this;
    }

    public MockMvcRequestSpecification attribute(String attributeName, Object attributeValue) {
        notNull(attributeName, "attributeName");
        notNull(attributeValue, "attributeValue");
        parameterUpdater.updateZeroToManyParameters(convert(cfg.getMockMvcParamConfig().attributeUpdateStrategy()), attributes, attributeName, attributeValue);
        return this;
    }

    public MockMvcRequestSpecification attributes(Map<String, ?> attributesMap) {
        notNull(attributesMap, "attributesMap");
        parameterUpdater.updateParameters(convert(cfg.getMockMvcParamConfig().attributeUpdateStrategy()), (Map<String, Object>) attributesMap, attributes);
        return this;
    }

    public MockMvcRequestSpecification body(String body) {
        this.requestBody = body;
        return this;
    }

    public MockMvcRequestSpecification body(byte[] body) {
        this.requestBody = body;
        return this;
    }

    public MockMvcRequestSpecification body(File body) {
        this.requestBody = body;
        return this;
    }

    public MockMvcRequestSpecification body(Object object) {
        notNull(object, "object");
        if (!isSerializableCandidate(object)) {
            return body(object.toString());
        }

        String requestContentType = getRequestContentType();
        this.requestBody = ObjectMapping.serialize(object, requestContentType,
                Serializer.findEncoderCharsetOrReturnDefault(requestContentType, cfg), null, cfg.getObjectMapperConfig(), cfg.getEncoderConfig());
        return this;
    }

    public MockMvcRequestSpecification body(Object object, ObjectMapper mapper) {
        notNull(object, "object");
        notNull(mapper, "Object mapper");
        this.requestBody = BodyHelper.toSerializedBody(object, mapper, cfg, requestHeaders);
        return this;
    }

    public MockMvcRequestSpecification body(Object object, ObjectMapperType mapperType) {
        notNull(object, "object");
        notNull(mapperType, "Object mapper type");
        String requestContentType = getRequestContentType();
        this.requestBody = ObjectMapping.serialize(object, requestContentType,
                Serializer.findEncoderCharsetOrReturnDefault(requestContentType, cfg), mapperType, cfg.getObjectMapperConfig(), cfg.getEncoderConfig());
        return this;
    }

    public MockMvcRequestSpecification cookies(String firstCookieName, Object firstCookieValue, Object... cookieNameValuePairs) {
        return cookies(MapCreator.createMapFromParams(CollisionStrategy.OVERWRITE, firstCookieName, firstCookieValue, cookieNameValuePairs));
    }

    public MockMvcRequestSpecification cookies(Map<String, ?> cookies) {
        notNull(cookies, "cookies");
        this.cookies = CookieHelper.cookies(this.cookies, cookies, requestHeaders, cfg);
        return this;
    }

    public MockMvcRequestSpecification cookies(Cookies cookies) {
        notNull(cookies, "Cookies");
        this.cookies = CookieHelper.cookies(this.cookies, cookies);
        return this;
    }

    public MockMvcRequestSpecification cookie(final String cookieName, final Object cookieValue, Object... additionalValues) {
        notNull(cookieName, "Cookie name");
        notNull(cookieValue, "Cookie value");
        return cookies(CookieHelper.cookie(cookieName, cookieValue, requestHeaders, cfg, additionalValues));
    }

    public MockMvcRequestSpecification cookie(Cookie cookie) {
        notNull(cookie, "Cookie");
        return cookies(new Cookies(Collections.singletonList(cookie)));
    }

    public MockMvcRequestSpecification multiPart(File file) {
        multiParts.add(new MockMvcMultiPart(cfg.getMultiPartConfig(), file));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, File file) {
        multiParts.add(new MockMvcMultiPart(controlName, file));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, File file, String mimeType) {
        multiParts.add(new MockMvcMultiPart(controlName, file, mimeType));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, Object object) {
        multiParts.add(new MockMvcMultiPart(cfg.getMultiPartConfig(), controlName, serializeIfNeeded(object)));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, Object object, String mimeType) {
        multiParts.add(new MockMvcMultiPart(cfg.getMultiPartConfig(), controlName,
                Serializer.serializeIfNeeded(object, mimeType, cfg), mimeType));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String filename, Object object, String mimeType) {
        multiParts.add(new MockMvcMultiPart(controlName, filename, Serializer.serializeIfNeeded(object, mimeType, cfg), mimeType));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String fileName, byte[] bytes) {
        multiParts.add(new MockMvcMultiPart(controlName, fileName, bytes));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String fileName, byte[] bytes, String mimeType) {
        multiParts.add(new MockMvcMultiPart(controlName, fileName, bytes, mimeType));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String fileName, InputStream stream) {
        multiParts.add(new MockMvcMultiPart(controlName, fileName, stream));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String fileName, InputStream stream, String mimeType) {
        multiParts.add(new MockMvcMultiPart(controlName, fileName, stream, mimeType));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String contentBody) {
        multiParts.add(new MockMvcMultiPart(cfg.getMultiPartConfig(), controlName, contentBody));
        return this;
    }

    public MockMvcRequestSpecification multiPart(String controlName, String contentBody, String mimeType) {
        multiParts.add(new MockMvcMultiPart(cfg.getMultiPartConfig(), controlName, contentBody, mimeType));
        return this;
    }

    public MockMvcRequestSpecification config(RestAssuredMockMvcConfig config) {
        assignConfig(config);
        return this;
    }

    public MockMvcRequestSpecification spec(MockMvcRequestSpecification requestSpecificationToMerge) {
        notNull(requestSpecificationToMerge, MockMvcRequestSpecification.class);

        if (!(requestSpecificationToMerge instanceof MockMvcRequestSpecificationImpl)) {
            throw new IllegalArgumentException("requestSpecificationToMerge must be an instance of " + MockMvcRequestSpecificationImpl.class.getName());
        }
        MockMvcRequestSpecificationImpl that = (MockMvcRequestSpecificationImpl) requestSpecificationToMerge;

        Object otherRequestBody = that.getRequestBody();
        if (otherRequestBody != null) {
            this.requestBody = otherRequestBody;

        }

        if (isNotEmpty(that.getBasePath())) {
            this.basePath = that.getBasePath();
        }

        MockMvcFactory otherMockMvcFactory = that.getMockMvcFactory();
        if (otherMockMvcFactory != null && otherMockMvcFactory.isAssigned()) {
            this.changeMockMvcFactoryTo(otherMockMvcFactory);
        }

        this.cookies(that.getCookies());

        this.headers(that.getRequestHeaders());

        mergeConfig(this, that);

        MockHttpServletRequestBuilderInterceptor otherInterceptor = that.getInterceptor();
        if (otherInterceptor != null) {
            this.interceptor = otherInterceptor;
        }

        this.formParams(that.getFormParams());
        this.queryParams(that.getQueryParams());
        this.params(that.getParams());
        this.attributes(that.getAttributes());

        this.multiParts.addAll(that.getMultiParts());
        this.resultHandlers.addAll(that.getResultHandlers());
        this.requestPostProcessors.addAll(that.getRequestPostProcessors());

        RequestLoggingFilter otherRequestLoggingFilter = that.getRequestLoggingFilter();
        if (otherRequestLoggingFilter != null) {
            this.requestLoggingFilter = otherRequestLoggingFilter;
        }

        Object otherAuth = that.getAuthentication();
        if (otherAuth != null) {
            this.authentication = otherAuth;
        }

        AsyncConfig otherAsyncConfig = that.getAsyncConfig();
        if (otherAsyncConfig != null) {
            this.asyncConfig = otherAsyncConfig;
        }

        return this;
    }

    /**
     * Set session attributes.
     *
     * @param sessionAttributes the session attributes
     */
    public MockMvcRequestSpecification sessionAttrs(Map<String, Object> sessionAttributes) {
        notNull(sessionAttributes, "sessionAttributes");
        parameterUpdater.updateParameters(convert(cfg.getMockMvcParamConfig().sessionAttributesUpdateStrategy()), sessionAttributes, this.sessionAttributes);
        return this;
    }

    public MockMvcRequestSpecification sessionId(String sessionIdValue) {
        return sessionId(cfg.getSessionConfig().sessionIdName(), sessionIdValue);
    }

    public MockMvcRequestSpecification sessionId(String sessionIdName, String sessionIdValue) {
        notNull(sessionIdName, "Session id name");
        notNull(sessionIdValue, "Session id value");
        if (cookies.hasCookieWithName(sessionIdName)) {
            CookieHelper.sessionId(cookies, sessionIdName, sessionIdValue);
        } else {
            cookie(sessionIdName, sessionIdValue);
        }
        return this;
    }

    public MockMvcRequestSpecification resultHandlers(ResultHandler resultHandler, ResultHandler... resultHandlers) {
        notNull(resultHandler, ResultHandler.class);
        this.resultHandlers.add(resultHandler);
        if (resultHandlers != null && resultHandlers.length >= 1) {
            Collections.addAll(this.resultHandlers, resultHandlers);
        }
        return this;
    }

    public MockMvcRequestAsyncSender when() {
        LogConfig logConfig = cfg.getLogConfig();
        if (requestLoggingFilter == null && logConfig.isLoggingOfRequestAndResponseIfValidationFailsEnabled()) {
            log().ifValidationFails(logConfig.logDetailOfRequestAndResponseIfValidationFails(), logConfig.isPrettyPrintingEnabled());
        }
        MockMvc mockMvc = mockMvcFactory.build(cfg.getMockMvcConfig());
        return new MockMvcRequestSenderImpl(mockMvc, params, queryParams, formParams, attributes, cfg, requestBody,
                requestHeaders, cookies, sessionAttributes, multiParts, requestLoggingFilter, resultHandlers, requestPostProcessors, interceptor, basePath, responseSpecification, authentication,
                logRepository);
    }

    private String serializeIfNeeded(Object object) {
        return Serializer.serializeIfNeeded(object, getRequestContentType(), cfg);
    }

    public MockMvcResponse get(String path, Object... pathParams) {
        return when().get(path, pathParams);
    }

    public MockMvcResponse get(String path, Map<String, ?> pathParams) {
        return when().get(path, pathParams);
    }

    public MockMvcResponse post(String path, Object... pathParams) {
        return when().post(path, pathParams);
    }

    public MockMvcResponse post(String path, Map<String, ?> pathParams) {
        return when().post(path, pathParams);
    }

    public MockMvcResponse put(String path, Object... pathParams) {
        return when().put(path, pathParams);
    }

    public MockMvcResponse put(String path, Map<String, ?> pathParams) {
        return when().put(path, pathParams);
    }

    public MockMvcResponse delete(String path, Object... pathParams) {
        return when().delete(path, pathParams);
    }

    public MockMvcResponse delete(String path, Map<String, ?> pathParams) {
        return when().delete(path, pathParams);
    }

    public MockMvcResponse head(String path, Object... pathParams) {
        return when().head(path, pathParams);
    }

    public MockMvcResponse head(String path, Map<String, ?> pathParams) {
        return when().head(path, pathParams);
    }

    public MockMvcResponse patch(String path, Object... pathParams) {
        return when().patch(path, pathParams);
    }

    public MockMvcResponse patch(String path, Map<String, ?> pathParams) {
        return when().patch(path, pathParams);
    }

    public MockMvcResponse options(String path, Object... pathParams) {
        return when().options(path, pathParams);
    }

    public MockMvcResponse options(String path, Map<String, ?> pathParams) {
        return when().options(path, pathParams);
    }

    public MockMvcResponse get(URI uri) {
        return when().get(uri);
    }

    public MockMvcResponse post(URI uri) {
        return when().post(uri);
    }

    public MockMvcResponse put(URI uri) {
        return when().put(uri);
    }

    public MockMvcResponse delete(URI uri) {
        return when().delete(uri);
    }

    public MockMvcResponse head(URI uri) {
        return when().head(uri);
    }

    public MockMvcResponse patch(URI uri) {
        return when().patch(uri);
    }

    public MockMvcResponse options(URI uri) {
        return when().options(uri);
    }

    public MockMvcResponse get(URL url) {
        return when().get(url);
    }

    public MockMvcResponse post(URL url) {
        return when().post(url);
    }

    public MockMvcResponse put(URL url) {
        return when().put(url);
    }

    public MockMvcResponse delete(URL url) {
        return when().delete(url);
    }

    public MockMvcResponse head(URL url) {
        return when().head(url);
    }

    public MockMvcResponse patch(URL url) {
        return when().patch(url);
    }

    public MockMvcResponse options(URL url) {
        return when().options(url);
    }

    public MockMvcResponse get() {
        return when().get();
    }

    public MockMvcResponse post() {
        return when().post();
    }

    public MockMvcResponse put() {
        return when().put();
    }

    public MockMvcResponse delete() {
        return when().delete();
    }

    public MockMvcResponse head() {
        return when().head();
    }

    public MockMvcResponse patch() {
        return when().patch();
    }

    public MockMvcResponse options() {
        return when().options();
    }

    public MockMvcResponse request(Method method) {
        return when().request(method);
    }

    public MockMvcResponse request(String method) {
        return when().request(method);
    }

    public MockMvcResponse request(Method method, String path, Object... pathParams) {
        return when().request(method, path, pathParams);
    }

    public MockMvcResponse request(String method, String path, Object... pathParams) {
        return when().request(method, path, pathParams);
    }

    public MockMvcResponse request(Method method, URI uri) {
        return when().request(method, uri);
    }

    public MockMvcResponse request(Method method, URL url) {
        return when().request(method, url);
    }

    public MockMvcResponse request(String method, URI uri) {
        return when().request(method, uri);
    }

    public MockMvcResponse request(String method, URL url) {
        return when().request(method, url);
    }

    public RestAssuredConfig getRestAssuredConfig() {
        return ConfigConverter.convertToRestAssuredConfig(cfg);
    }

    public void setRequestLoggingFilter(RequestLoggingFilter requestLoggingFilter) {
        this.requestLoggingFilter = requestLoggingFilter;
    }

    private MockMvcRequestSpecification changeMockMvcFactoryTo(MockMvcFactory mockMvcFactory) {
        this.mockMvcFactory = mockMvcFactory;
        return this;
    }

    private void assignConfig(RestAssuredMockMvcConfig config) {
        if (config == null) {
            this.cfg = new RestAssuredMockMvcConfig();
        } else {
            this.cfg = config;
        }
    }

    public MockMvcFactory getMockMvcFactory() {
        return mockMvcFactory;
    }

    public String getBasePath() {
        return basePath;
    }

    public Map<String, Object> getParams() {
        return params;
    }

    public Map<String, Object> getQueryParams() {
        return queryParams;
    }

    public Map<String, Object> getFormParams() {
        return formParams;
    }

    public Map<String, Object> getAttributes() {
        return attributes;
    }

    public Object getRequestBody() {
        return requestBody;
    }

    public Object getAuthentication() {
        return authentication;
    }

    public RestAssuredMockMvcConfig getRestAssuredMockMvcConfig() {
        return cfg;
    }

    public Headers getRequestHeaders() {
        return requestHeaders;
    }

    public Cookies getCookies() {
        return cookies;
    }

    public String getRequestContentType() {
        Header header = requestHeaders.get(CONTENT_TYPE);
        if (header != null) {
            return header.getValue();
        }
        return null;
    }

    public List<MockMvcMultiPart> getMultiParts() {
        return multiParts;
    }

    public RequestLoggingFilter getRequestLoggingFilter() {
        return requestLoggingFilter;
    }

    public List<ResultHandler> getResultHandlers() {
        return resultHandlers;
    }

    public List<RequestPostProcessor> getRequestPostProcessors() {
        return requestPostProcessors;
    }

    public MockHttpServletRequestBuilderInterceptor getInterceptor() {
        return interceptor;
    }

    public MockMvcRequestSpecification basePath(String path) {
        notNull(path, "Base path");
        this.basePath = path;
        return this;
    }

    public MockMvcRequestSpecification principal(Principal principal) {
        notNull(principal, Principal.class);
        this.authentication = principal;
        return this;
    }

    public MockMvcRequestSpecification with(RequestPostProcessor requestPostProcessor, RequestPostProcessor... additionalRequestPostProcessor) {
        notNull(requestPostProcessor, RequestPostProcessor.class);
        this.authentication = null;
        this.requestPostProcessors.add(requestPostProcessor);
        if (!(additionalRequestPostProcessor == null || additionalRequestPostProcessor.length == 0)) {
            this.requestPostProcessors.addAll(Arrays.asList(additionalRequestPostProcessor));
        }
        return this;
    }

    public MockMvcRequestSpecification principal(Object principal) {
        return principalWithCredentials(principal, "");
    }

    public MockMvcRequestSpecification principalWithCredentials(Object principal, Object credentials, String... authorities) {
        return authentication(new org.springframework.security.authentication.TestingAuthenticationToken(principal, credentials, authorities));
    }

    public MockMvcRequestSpecification authentication(Object authentication) {
        if (!isSpringSecurityInClasspath()) {
            throw new IllegalArgumentException("Cannot use this authentication method since Spring Security was not found in classpath.");
        }
        notNull(authentication, org.springframework.security.core.Authentication.class);
        if (!(authentication instanceof org.springframework.security.core.Authentication)) {
            throw new IllegalArgumentException("authentication object must be an instance of " + org.springframework.security.core.Authentication.class.getName());
        }
        this.authentication = authentication;
        return this;
    }

    public MockMvcRequestSpecification none() {
        this.authentication = null;
        return this;
    }

    public LogRepository getLogRepository() {
        return logRepository;
    }

    public AsyncConfig getAsyncConfig() {
        return asyncConfig;
    }

    /**
     * Set a session attribute.
     *
     * @param name  the session attribute name
     * @param value the session attribute value
     */
    public MockMvcRequestSpecification sessionAttr(String name, Object value) {
        notNull(name, "Session attribute name");
        parameterUpdater.updateZeroToManyParameters(convert(cfg.getMockMvcParamConfig().sessionAttributesUpdateStrategy()), sessionAttributes, name, value);
        return this;
    }

    private void mergeConfig(MockMvcRequestSpecificationImpl thisOne, MockMvcRequestSpecificationImpl other) {
        config((RestAssuredMockMvcConfig) ConfigMergeUtils.mergeConfig(thisOne.getRestAssuredMockMvcConfig(),
                other.getRestAssuredMockMvcConfig()));
    }
}


/*
 * Copyright 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
 *
 *        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 io.restassured.authentication;

import io.restassured.config.LogConfig;
import io.restassured.filter.log.LogDetail;
import org.apache.commons.lang3.StringUtils;

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

import static io.restassured.internal.common.assertion.AssertParameter.notNull;
import static java.lang.String.format;

/**
 * Configuration of form authentication to correctly identify which form that contains the username and password
 * and the action of the form.
 */
public class FormAuthConfig {
    private final String formAction;
    private final String userInputTagName;
    private final String passwordInputTagName;
    private final LogConfig logConfig;
    private final LogDetail logDetail;
    private final List<String> additionalInputFieldNames;
    private final String csrfFieldName;
    private final boolean autoDetectCsrfFieldName;
    private final boolean sendCsrfTokenAsFormParam;

    /**
     * Create a form auth config with a pre-defined form action, username input tag, password input tag.
     * E.g. let's say that the login form on your login page looks like this:
     *

     *  <form action="/j_spring_security_check">
     *     <label for="j_username">Username</label>
     *     <input type="text" name="j_username" id="j_username"/>
     *     <br/>
     *     <label for="j_password">Password</label>
     *     <input type="password" name="j_password" id="j_password"/>
     *     <br/>
     *     <input type='checkbox' name='_spring_security_remember_me'/> Remember me on this computer.
     *     <br/>
     *     <input type="submit" value="Login"/>
     * </form>
     *

     *

     * This means that formAction should be set to /j_spring_security_check, userNameInputTagName
     * should be set to j_username and passwordInputTagName should be set to j_password.
     *
     * @param formAction           The action of the form
     * @param userNameInputTagName The name of the username input tag in the login form
     * @param passwordInputTagName The name of the password input tag in the login form
     */
    public FormAuthConfig(String formAction, String userNameInputTagName, String passwordInputTagName) {
        this(formAction, userNameInputTagName, passwordInputTagName, null, null, null, false, true,
                Collections.<String>emptyList());
    }

    /**
     * Creates a new empty {@link FormAuthConfig}.
     */
    public FormAuthConfig() {
        this(null, null, null);
    }

    private FormAuthConfig(String formAction, String userNameInputTagName, String passwordInputTagName, LogDetail logDetail, LogConfig logConfig,
                           String csrfFieldName, boolean autoDetectCsrfFieldName, boolean sendCsrfTokenAsFormParam,
                           List<String> additionalInputFieldNames) {
        this.formAction = formAction;
        this.userInputTagName = userNameInputTagName;
        this.passwordInputTagName = passwordInputTagName;
        this.logDetail = logDetail;
        this.logConfig = logConfig;
        this.csrfFieldName = csrfFieldName;
        this.autoDetectCsrfFieldName = autoDetectCsrfFieldName;
        this.sendCsrfTokenAsFormParam = sendCsrfTokenAsFormParam;
        this.additionalInputFieldNames = Collections.unmodifiableList(additionalInputFieldNames);
    }

    /**
     * @return A predefined form authentication config for default Spring Security configuration (tested in version 3.0.5) (no CSRF detection).
     */
    public static FormAuthConfig springSecurity() {
        return new FormAuthConfig("/j_spring_security_check", "j_username", "j_password");
    }

    /**
     * Enable Cross-site request forgery (csrf) support when using form authentication by including the csrf value of the input field with the specified name.
     * For example if the login page looks like this:
     *

     * <html>
     * <head>
     *     <title>Login</title>
     * </head>
     * <body>
     * <form action="j_spring_security_check_with_csrf" method="POST">
     *     <table>
     *         <tr>
     *             <td>User:&nbsp;</td>
     *             <td><input type="text" name="j_username"></td>
     *         </tr>
     *         <tr>
     *             <td>Password:</td>
     *             <td><input type="password" name="j_password"></td>
     *         </tr>
     *         <tr>
     *             <td colspan="2"><input name="submit" type="submit"/></td>
     *         </tr>
     *     </table>
     *     <input type="hidden" name="_csrf" value="8adf2ea1-b246-40aa-8e13-a85fb7914341"/>
     * </form>
     * </body>
     * </html>
     *

     * The csrf field name is called _csrf.
     *

     * Important: When enabling csrf support then REST Assured must always make an additional request to the server in order to
     * be able to include in the csrf value which will slow down the tests.
     *
     * @param fieldName The name of the input field
     * @return A new FormAuthConfig instance.
     * @see #withAutoDetectionOfCsrf()
     */
    public FormAuthConfig withCsrfFieldName(String fieldName) {
        notNull(fieldName, "CSRF field name");
        if (autoDetectCsrfFieldName) {
            throw new IllegalStateException("Cannot defined a CSRF field name since the CSRF field name has been marked as auto-detected.");
        }
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, fieldName, false, sendCsrfTokenAsFormParam, additionalInputFieldNames);
    }

    /**
     * Include additional field when using form authentication by including input field value with the specified name.
     * For example if the login page looks like this:
     *

     * <html>
     * <head>
     *     <title>Login</title>
     * </head>
     * <body>
     * <form action="j_spring_security_check_with_csrf" method="POST">
     *     <table>
     *         <tr>
     *             <td>User:&nbsp;</td>
     *             <td><input type="text" name="j_username"></td>
     *         </tr>
     *         <tr>
     *             <td>Password:</td>
     *             <td><input type="password" name="j_password"></td>
     *         </tr>
     *         <tr>
     *             <td colspan="2"><input name="submit" type="submit"/></td>
     *         </tr>
     *     </table>
     *     <input type="hidden" name="something" value="8adf2ea1-b246-40aa-8e13-a85fb7914341"/>
     * </form>
     * </body>
     * </html>
     *

     * and you'd like to include the field named something as an additional form parameter in the request you can do like this:
     *
     *

     * given().auth().form(..., new FormAuthConfig(..).withAdditionalField("something"). ..
     *

     * and then REST Assured will send the form parameter something=8adf2ea1-b246-40aa-8e13-a85fb7914341
     *
     *

     * Important: When including an additional field without specifying a value then REST Assured must always make an additional request to the server in order to
     * be able to figure out the field value. This will slow down the tests.
     *
     * @param fieldName The first field name to include
     * @return A new FormAuthConfig instance.
     */
    public FormAuthConfig withAdditionalField(String fieldName) {
        notNull(fieldName, "Additional field name");
        List<String> list = new ArrayList<String>(additionalInputFieldNames);
        list.add(fieldName);
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, csrfFieldName, autoDetectCsrfFieldName, sendCsrfTokenAsFormParam, list);
    }

    /**
     * Include multiple additional fields when using form authentication by including input field values with the specified name.
     * This is the same as {@link #withAdditionalField(String)} but for multiple fields.
     *
     *

     * Important: When including an additional field without specifying a value then REST Assured must always make an additional request to the server in order to
     * be able to figure out the field value. This will slow down the tests.
     *
     * @param firstFieldName The first additional input field to include
     * @param secondFieldName The second additional input field to include
     * @param additionalFieldNames Additional field name to include (optional)
     * @return A new FormAuthConfig instance.
     */
    public FormAuthConfig withAdditionalFields(String firstFieldName, String secondFieldName, String... additionalFieldNames) {
        notNull(firstFieldName, "First additional field name");
        notNull(secondFieldName, "Second additional field name");
        List<String> list = new ArrayList<String>(additionalInputFieldNames);
        list.add(firstFieldName);
        list.add(secondFieldName);
        if (additionalFieldNames != null && additionalFieldNames.length > 0) {
            list.addAll(Arrays.asList(additionalFieldNames));
        }
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, csrfFieldName, autoDetectCsrfFieldName, sendCsrfTokenAsFormParam, list);
    }

    /**
     * @return Configure form authentication to send the csrf token in a header.
     */
    public FormAuthConfig sendCsrfTokenAsHeader() {
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, csrfFieldName, autoDetectCsrfFieldName, false, additionalInputFieldNames);
    }

    /**
     * @return Configure form authentication to send the csrf token as a form parameter (default setting).
     */
    public FormAuthConfig sendCsrfTokenAsFormParam() {
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, csrfFieldName, autoDetectCsrfFieldName, true, additionalInputFieldNames);
    }

    /**
     * Enable Cross-site request forgery (csrf) support when using form authentication by automatically trying to find the name and value of the csrf input field.
     * For example if the login page looks like this:
     *

     * <html>
     * <head>
     *     <title>Login</title>
     * </head>
     * <body>
     * <form action="j_spring_security_check_with_csrf" method="POST">
     *     <table>
     *         <tr>
     *             <td>User:&nbsp;</td>
     *             <td><input type="text" name="j_username"></td>
     *         </tr>
     *         <tr>
     *             <td>Password:</td>
     *             <td><input type="password" name="j_password"></td>
     *         </tr>
     *         <tr>
     *             <td colspan="2"><input name="submit" type="submit"/></td>
     *         </tr>
     *     </table>
     *     <input type="hidden" name="_csrf" value="8adf2ea1-b246-40aa-8e13-a85fb7914341"/>
     * </form>
     * </body>
     * </html>
     *

     * The csrf field name is called _csrf and REST Assured will autodetect its name since the field name is the only hidden field on this page.
     * If auto-detection fails you can consider using {@link #withCsrfFieldName(String)}.
     *

     * Important: When enabling csrf support then REST Assured must always make an additional request to the server in order to
     * be able to include in the csrf value which will slow down the tests.
     *
     * @return A new FormAuthConfig instance.
     * @see #withCsrfFieldName(String)
     */
    public FormAuthConfig withAutoDetectionOfCsrf() {
        if (hasCsrfFieldName()) {
            throw new IllegalStateException(format("Cannot use auto-detection of CSRF field name since a CSRF field name was already defined as '%s'", csrfFieldName));
        }
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, csrfFieldName, true, sendCsrfTokenAsFormParam, additionalInputFieldNames);
    }

    /**
     * Enables logging with log level {@link LogDetail#ALL} of the request made to authenticate using
     * form authentication. Both the request and the response is logged.
     *
     * @return A new FormAuthConfig instance.
     */

    public FormAuthConfig withLoggingEnabled() {
        return withLoggingEnabled(LogDetail.ALL);
    }

    /**
     * Enables logging with the supplied logDetail of the request made to authenticate using form authentication.
     * Both the request and the response is logged.
     *
     * @return A new FormAuthConfig instance.
     */
    public FormAuthConfig withLoggingEnabled(LogDetail logDetail) {
        return withLoggingEnabled(logDetail, new LogConfig());
    }

    /**
     * Enables logging with log level {@link LogDetail#ALL} of the request made to authenticate using
     * form authentication using the specified {@link LogConfig}. Both the request and the response is logged.
     *
     * @return A new FormAuthConfig instance.
     */
    public FormAuthConfig withLoggingEnabled(LogConfig logConfig) {
        return withLoggingEnabled(LogDetail.ALL, logConfig);
    }

    /**
     * Enables logging with the supplied log detail of the request made to authenticate using form authentication using the
     * specified {@link LogConfig}. Both the request and the response is logged.
     *
     * @return A new FormAuthConfig instance.
     */
    public FormAuthConfig withLoggingEnabled(LogDetail logDetail, LogConfig logConfig) {
        notNull(logDetail, LogDetail.class);
        notNull(logConfig, LogConfig.class);
        return new FormAuthConfig(formAction, userInputTagName, passwordInputTagName, logDetail, logConfig, csrfFieldName, autoDetectCsrfFieldName, sendCsrfTokenAsFormParam, additionalInputFieldNames);
    }

    /**
     * Creates a new empty {@link FormAuthConfig}.
     *
     * @return A new FormAuthConfig instance.
     */
    public static FormAuthConfig formAuthConfig() {
        return new FormAuthConfig(null, null, null);
    }

    /**
     * Syntactic sugar
     *
     * @return The same FormAuthConfig instance
     */
    public FormAuthConfig and() {
        return this;
    }

    public String getFormAction() {
        return formAction;
    }

    public String getUserInputTagName() {
        return userInputTagName;
    }

    /**
     * @return The password input tag name or null if undefined
     */
    public String getPasswordInputTagName() {
        return passwordInputTagName;
    }

    /**
     * @return The logging configuration
     */
    public LogConfig getLogConfig() {
        return logConfig;
    }

    /**
     * @return true if logging is enabled or false otherwise.
     */
    public boolean isLoggingEnabled() {
        return logConfig != null && logDetail != null;
    }

    /**
     * @return The specified log detail or null if undefined
     */
    public LogDetail getLogDetail() {
        return logDetail;
    }

    /**
     * @return The specified csrf field name or null if undefined
     */
    public String getCsrfFieldName() {
        return csrfFieldName;
    }

    /**
     * @return The additional input field names
     */
    public List<String> getAdditionalInputFieldNames() {
        return additionalInputFieldNames;
    }

    /**
     * @return true if csrf field name is defined or false otherwise.
     */
    public boolean hasCsrfFieldName() {
        return StringUtils.isNotBlank(csrfFieldName);
    }

    /**
     * @return true if additional input field name have been specified or false otherwise.
     */
    public boolean hasAdditionalInputFieldNames() {
        return !additionalInputFieldNames.isEmpty();
    }

    /**
     * @return true if auto detection of csrf field name is enabled, false otherwise.
     */
    public boolean isAutoDetectCsrfFieldName() {
        return autoDetectCsrfFieldName;
    }

    /**
     * @return true if the user input tag name is defined or false otherwise.
     */
    public boolean hasUserInputTagName() {
        return StringUtils.isNotBlank(userInputTagName);
    }

    /**
     * @return true if the password input tag name is defined or false otherwise.
     */
    public boolean hasPasswordInputTagName() {
        return StringUtils.isNotBlank(passwordInputTagName);
    }

    /**
     * @return true if the form action is defined or false otherwise.
     */
    public boolean hasFormAction() {
        return StringUtils.isNotBlank(formAction);
    }

    /**
     * @return true if the {@link FormAuthConfig} instance contains settings that require REST Assured to make a request to the server before applying form authentication, false otherwise.
     */
    public boolean requiresParsingOfLoginPage() {
        return !hasFormAction() || !hasUserInputTagName() || !hasPasswordInputTagName() || isAutoDetectCsrfFieldName() || hasCsrfFieldName() ||hasAdditionalInputFieldNames();
    }

    /**
     * @return true if the csrf token should be sent as a form param or false if it's sent as a header.
     */
    public boolean shouldSendCsrfTokenAsFormParam() {
        return sendCsrfTokenAsFormParam;
    }
}


/*
 * Copyright 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
 *
 *        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 io.restassured.authentication;

public class PreemptiveAuthProvider {
    /**
     * Use preemptive http basic authentication. This means that the authentication details are sent in the request
     * header regardless if the server has challenged for authentication or not.
     *
     * @param userName The user name.
     * @param password The password.
     *
     * @return The Request specification
     */
    public AuthenticationScheme basic(String userName, String password) {
        final PreemptiveBasicAuthScheme preemptiveBasicAuthScheme = new PreemptiveBasicAuthScheme();
        preemptiveBasicAuthScheme.setUserName(userName);
        preemptiveBasicAuthScheme.setPassword(password);
        return preemptiveBasicAuthScheme;
    }

}