/*
 * 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.prestosql.server.security;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import io.prestosql.server.InternalAuthenticationManager;
import io.prestosql.spi.security.Identity;

import javax.annotation.Priority;
import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.emptyToNull;
import static io.prestosql.client.PrestoHeaders.PRESTO_USER;
import static io.prestosql.server.ServletSecurityUtils.sendErrorMessage;
import static io.prestosql.server.ServletSecurityUtils.sendWwwAuthenticate;
import static io.prestosql.server.ServletSecurityUtils.setAuthenticatedIdentity;
import static io.prestosql.server.security.BasicAuthCredentials.extractBasicAuthCredentials;
import static java.util.Objects.requireNonNull;
import static javax.ws.rs.Priorities.AUTHENTICATION;
import static javax.ws.rs.core.Response.Status.FORBIDDEN;

@Priority(AUTHENTICATION)
public class AuthenticationFilter
        implements ContainerRequestFilter
{
    private final List<Authenticator> authenticators;
    private final InternalAuthenticationManager internalAuthenticationManager;

    @Inject
    public AuthenticationFilter(List<Authenticator> authenticators, InternalAuthenticationManager internalAuthenticationManager)
    {
        this.authenticators = ImmutableList.copyOf(requireNonNull(authenticators, "authenticators is null"));
        checkArgument(!authenticators.isEmpty(), "authenticators is empty");
        this.internalAuthenticationManager = requireNonNull(internalAuthenticationManager, "internalAuthenticationManager is null");
    }

    @Override
    public void filter(ContainerRequestContext request)
    {
        if (InternalAuthenticationManager.isInternalRequest(request)) {
            internalAuthenticationManager.handleInternalRequest(request);
            return;
        }

        // authentication not allowed over insecure connections
        if (!request.getSecurityContext().isSecure()) {
            handleInsecureRequest(request);
            return;
        }

        // try to authenticate, collecting errors and authentication headers
        Set<String> messages = new LinkedHashSet<>();
        Set<String> authenticateHeaders = new LinkedHashSet<>();

        for (Authenticator authenticator : authenticators) {
            Identity authenticatedIdentity;
            try {
                authenticatedIdentity = authenticator.authenticate(request);
            }
            catch (AuthenticationException e) {
                if (e.getMessage() != null) {
                    messages.add(e.getMessage());
                }
                e.getAuthenticateHeader().ifPresent(authenticateHeaders::add);
                continue;
            }

            // authentication succeeded
            setAuthenticatedIdentity(request, authenticatedIdentity);
            return;
        }

        // authentication failed
        if (messages.isEmpty()) {
            messages.add("Unauthorized");
        }
        // The error string is used by clients for exception messages and
        // is presented to the end user, thus it should be a single line.
        String error = Joiner.on(" | ").join(messages);

        sendWwwAuthenticate(request, error, authenticateHeaders);
    }

    private static void handleInsecureRequest(ContainerRequestContext request)
    {
        Optional<BasicAuthCredentials> basicAuthCredentials;
        try {
            basicAuthCredentials = extractBasicAuthCredentials(request);
        }
        catch (AuthenticationException e) {
            sendErrorMessage(request, FORBIDDEN, e.getMessage());
            return;
        }

        String user;
        if (basicAuthCredentials.isPresent()) {
            if (basicAuthCredentials.get().getPassword().isPresent()) {
                sendErrorMessage(request, FORBIDDEN, "Password not allowed for insecure request");
                return;
            }
            user = basicAuthCredentials.get().getUser();
        }
        else {
            user = emptyToNull(request.getHeaders().getFirst(PRESTO_USER));
        }

        if (user == null) {
            sendWwwAuthenticate(request, "Basic authentication or " + PRESTO_USER + " must be sent", ImmutableList.of("Basic realm=\"Presto\""));
            return;
        }

        setAuthenticatedIdentity(request, user);
    }
}

@@ -1011,35 +1011,8 @@
 st;%0A
-import java.util.Optional;%0A
 impo
@@ -1029,16 +1029,16 @@
 il.Set;%0A
+
 %0Aimport 
@@ -1100,200 +1100,8 @@
 nt;%0A
-import static com.google.common.base.Strings.emptyToNull;%0Aimport static io.prestosql.client.PrestoHeaders.PRESTO_USER;%0Aimport static io.prestosql.server.ServletSecurityUtils.sendErrorMessage;%0A
 impo
@@ -1257,101 +1257,8 @@
 ty;%0A
-import static io.prestosql.server.security.BasicAuthCredentials.extractBasicAuthCredentials;%0A
 impo
@@ -1357,66 +1357,8 @@
 ION;
-%0Aimport static javax.ws.rs.core.Response.Status.FORBIDDEN;
 %0A%0A@P
@@ -2320,36 +2320,38 @@
      %7D%0A%0A        
-// a
+List%3CA
 uthentication no
@@ -2348,49 +2348,27 @@
 icat
-ion not allowed over insecure connections
+or%3E authenticators;
 %0A   
@@ -2376,17 +2376,16 @@
     if (
-!
 request.
@@ -2435,46 +2435,52 @@
     
-handleInsecureRequest(request)
+authenticators = this.authenticators
 ;%0A      
     
@@ -2471,36 +2471,117 @@
 tors;%0A      
+  %7D%0A
       
-return
+  else %7B%0A            authenticators = ImmutableList.of(new InsecureAuthenticator())
 ;%0A        %7D%0A
@@ -3735,24 +3735,24 @@
 messages);%0A%0A
+
         send
@@ -3814,1099 +3814,7 @@
    %7D
-%0A%0A    private static void handleInsecureRequest(ContainerRequestContext request)%0A    %7B%0A        Optional%3CBasicAuthCredentials%3E basicAuthCredentials;%0A        try %7B%0A            basicAuthCredentials = extractBasicAuthCredentials(request);%0A        %7D%0A        catch (AuthenticationException e) %7B%0A            sendErrorMessage(request, FORBIDDEN, e.getMessage());%0A            return;%0A        %7D%0A%0A        String user;%0A        if (basicAuthCredentials.isPresent()) %7B%0A            if (basicAuthCredentials.get().getPassword().isPresent()) %7B%0A                sendErrorMessage(request, FORBIDDEN, %22Password not allowed for insecure request%22);%0A                return;%0A            %7D%0A            user = basicAuthCredentials.get().getUser();%0A        %7D%0A        else %7B%0A            user = emptyToNull(request.getHeaders().getFirst(PRESTO_USER));%0A        %7D%0A%0A        if (user == null) %7B%0A            sendWwwAuthenticate(request, %22Basic authentication or %22 + PRESTO_USER + %22 must be sent%22, ImmutableList.of(%22Basic realm=%5C%22Presto%5C%22%22));%0A            return;%0A        %7D%0A%0A        setAuthenticatedIdentity(request, user);%0A    %7D
 %0A%7D%0A

Use insecure authenticator for http requests