content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to you 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.apache.calcite.avatica.server;
  
  import org.apache.calcite.avatica.remote.AuthenticationType;
  import org.apache.calcite.avatica.remote.AvaticaServersForTest;
  import org.apache.calcite.avatica.remote.Driver.Serialization;
  
  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.Parameters;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.List;
  import java.util.Properties;
  import java.util.concurrent.Callable;
  
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.core.StringContains.containsString;
  import static org.junit.Assert.fail;
  
  /**
   * Test class for HTTP Basic authentication with an (insecure) specification of the "real" user
   * via an HTTP query string parameter.
   *
   * @see HttpQueryStringParameterRemoteUserExtractor
   */
  @RunWith(Parameterized.class)
  public class HttpQueryStringParameterRemoteUserExtractorTest extends HttpAuthBase {
    private static final Logger LOG =
        LoggerFactory.getLogger(HttpQueryStringParameterRemoteUserExtractorTest.class);
    private static final AvaticaServersForTest SERVERS = new AvaticaServersForTest();
    private static final Properties PROXY_SERVER_PROPERTIES = new Properties();
  
    private final HttpServer server;
    private final String missingDoAsUrl;
    private final String disallowedDoAsUserUrl;
    private final String allowedDoAsUserUrl;
  
    @Parameters(name = "{0}")
    public static List<Object[]> parameters() throws Exception {
      SERVERS.startServers(SERVER_CONFIG);
      return SERVERS.getJUnitParameters();
    }
  
    @BeforeClass
    public static void loadProxyServerProperties() {
      // The connecting system has one set of credentials.
      PROXY_SERVER_PROPERTIES.put("avatica_user", "USER1");
      PROXY_SERVER_PROPERTIES.put("avatica_password", "password1");
    }
  
    public HttpQueryStringParameterRemoteUserExtractorTest(Serialization serialization,
        HttpServer server) throws Exception {
      this.server = server;
      int port = this.server.getPort();
      // Create some JDBC urls for basic authentication with varying "doAs" configuration
      missingDoAsUrl = SERVERS.getJdbcUrl(port, serialization) + ";authentication=BASIC";
      // USER4 is valid for HTTP basic auth, but disallowed by the server config (below)
      disallowedDoAsUserUrl = SERVERS.getJdbcUrl(port, serialization, "?doAs=USER4")
          + ";authentication=BASIC";
      allowedDoAsUserUrl = SERVERS.getJdbcUrl(port,  serialization, "?doAs=USER2")
          + ";authenticati0n=BASIC";
      // Create and grant permissions to our users
      createHsqldbUsers();
    }
  
    private static final AvaticaServerConfiguration SERVER_CONFIG = new AvaticaServerConfiguration() {
      @Override public AuthenticationType getAuthenticationType() {
        // HTTP Basic authentication
        return AuthenticationType.BASIC;
      }
  
      @Override public String getKerberosRealm() {
        return null;
      }
  
      @Override public String getKerberosPrincipal() {
        return null;
      }
  
      @Override public String getKerberosServiceName() {
        return null;
      }
  
      @Override public String getKerberosHostName() {
        return null;
      }
  
      @Override public boolean supportsImpersonation() {
        // Impersonation is allowed
        return true;
      }
  
      @Override public <T> T doAsRemoteUser(String remoteUserName, String remoteAddress,
                                            Callable<T> action) throws Exception {
        // We disallow the remote user "USER4", allow all others
        if (remoteUserName.equals("USER4")) {
          throw new RemoteUserDisallowedException("USER4 is a disallowed user!");
        } else {
          return action.call();
        }
      }
  
      @Override public RemoteUserExtractor getRemoteUserExtractor() {
        // We extract the "real" user via the "doAs" query string parameter
        return new HttpQueryStringParameterRemoteUserExtractor("doAs");
      }
  
      @Override public String[] getAllowedRoles() {
        return new String[] { "users" };
      }
  
      @Override public String getHashLoginServiceRealm() {
        return "Avatica";
      }
  
      @Override public String getHashLoginServiceProperties() {
        return HttpAuthBase.getHashLoginServicePropertiesString();
      }
    };
  
    @AfterClass public static void stopServer() throws Exception {
      if (null != SERVERS) {
        SERVERS.stopServers();
      }
    }
  
    @Test public void testUserWithDisallowedRole() throws Exception {
      try {
        readWriteData(missingDoAsUrl, "MISSING_DO_AS", PROXY_SERVER_PROPERTIES);
        fail("Expected an exception");
      } catch (RuntimeException e) {
        LOG.info("Caught expected exception", e);
        assertThat(e.getMessage(), containsString("Failed to execute HTTP Request, got HTTP/401"));
      }
    }
  
    @Test public void testUserWithDisallowedDoAsRole() throws Exception {
      try {
        readWriteData(disallowedDoAsUserUrl, "DISALLOWED_USER", PROXY_SERVER_PROPERTIES);
        fail("Expected an exception");
      } catch (RuntimeException e) {
        LOG.info("Caught expected exception", e);
        assertThat(e.getMessage(), containsString("Failed to execute HTTP Request, got HTTP/403"));
      }
    }
  
    @Test public void testAllowedDoAsUser() throws Exception {
      // When we connect with valid credentials and provide a valid "doAs" user, things should work
      readWriteData(allowedDoAsUserUrl, "ALLOWED_USER", PROXY_SERVER_PROPERTIES);
    }
  }
  
  // End HttpQueryStringParameterRemoteUserExtractorTest.java
  