//
//  ========================================================================
//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Password;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

/**
 * Some requests for static data that is served by ResourceHandler, but some is secured.
 * <p>
 * This is mainly here to test security bypass techniques using aliased names that should be caught.
 */
@RunWith(Parameterized.class)
public class AliasedConstraintTest
{
    private static final String TEST_REALM = "TestRealm";
    private static Server server;
    private static LocalConnector connector;
    private static ConstraintSecurityHandler security;

    @BeforeClass
    public static void startServer() throws Exception
    {
        server = new Server();
        connector = new LocalConnector(server);
        server.setConnectors(new Connector[] { connector });

        ContextHandler context = new ContextHandler();
        SessionHandler session = new SessionHandler();

        HashLoginService loginService = new HashLoginService(TEST_REALM);
        loginService.putUser("user0",new Password("password"),new String[] {});
        loginService.putUser("user",new Password("password"),new String[] { "user" });
        loginService.putUser("user2",new Password("password"),new String[] { "user" });
        loginService.putUser("admin",new Password("password"),new String[] { "user", "administrator" });
        loginService.putUser("user3",new Password("password"),new String[] { "foo" });

        context.setContextPath("/ctx");
        context.setResourceBase(MavenTestingUtils.getTestResourceDir("docroot").getAbsolutePath());
        server.setHandler(context);
        context.setHandler(session);
        // context.addAliasCheck(new AllowSymLinkAliasChecker());

        server.addBean(loginService);

        security = new ConstraintSecurityHandler();
        session.setHandler(security);
        ResourceHandler handler = new ResourceHandler();
        security.setHandler(handler);

        List<ConstraintMapping> constraints = new ArrayList<>();

        Constraint constraint0 = new Constraint();
        constraint0.setAuthenticate(true);
        constraint0.setName("forbid");
        ConstraintMapping mapping0 = new ConstraintMapping();
        mapping0.setPathSpec("/forbid/*");
        mapping0.setConstraint(constraint0);
        constraints.add(mapping0);

        Set<String> knownRoles = new HashSet<>();
        knownRoles.add("user");
        knownRoles.add("administrator");

        security.setConstraintMappings(constraints,knownRoles);
        server.start();
    }

    @AfterClass
    public static void stopServer() throws Exception
    {
        server.stop();
    }

    @Parameters(name = "{0}: {1}")
    public static Collection<Object[]> data()
    {
        List<Object[]> data = new ArrayList<>();

        final String OPENCONTENT = "this is open content";

        data.add(new Object[] { "/ctx/all/index.txt", HttpStatus.OK_200, OPENCONTENT });
        data.add(new Object[] { "/ctx/ALL/index.txt", HttpStatus.NOT_FOUND_404, null });
        data.add(new Object[] { "/ctx/ALL/Fred/../index.txt", HttpStatus.NOT_FOUND_404, null });
        data.add(new Object[] { "/ctx/../bar/../ctx/all/index.txt", HttpStatus.OK_200, OPENCONTENT });
        data.add(new Object[] { "/ctx/forbid/index.txt", HttpStatus.FORBIDDEN_403, null });
        data.add(new Object[] { "/ctx/all/../forbid/index.txt", HttpStatus.FORBIDDEN_403, null });
        data.add(new Object[] { "/ctx/FoRbId/index.txt", HttpStatus.NOT_FOUND_404, null });

        return data;
    }

    @Parameter(value = 0)
    public String uri;

    @Parameter(value = 1)
    public int expectedStatusCode;

    @Parameter(value = 2)
    public String expectedContent;

    @Test
    public void testAccess() throws Exception
    {
        StringBuilder request = new StringBuilder();
        request.append("GET ").append(uri).append(" HTTP/1.1\r\n");
        request.append("Host: localhost\r\n");
        request.append("Connection: close\r\n");
        request.append("\r\n");

        String response = connector.getResponses(request.toString());

        switch (expectedStatusCode)
        {
            case 200:
                assertThat(response,startsWith("HTTP/1.1 200 OK"));
                break;
            case 403:
                assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
                break;
            case 404:
                assertThat(response,startsWith("HTTP/1.1 404 Not Found"));
                break;
            default:
                fail("Write a handler for response status code: " + expectedStatusCode);
                break;
        }

        if (expectedContent != null)
        {
            assertThat(response,containsString("this is open content"));
        }
    }
}
