/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.shield.authc;

import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.transport.TransportMessage;

/**
 * An authentication service that delegates the authentication process to its configured {@link Realm realms}.
 * This service also supports request level caching of authenticated users (i.e. once a user authenticated
 * successfully, it is set on the request context to avoid subsequent redundant authentication process)
 */
public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService {

    static final String TOKEN_CTX_KEY = "_shield_token";
    static final String USER_CTX_KEY = "_shield_user";

    private final Realm[] realms;
    private final AuditTrail auditTrail;

    @Inject
    public InternalAuthenticationService(Settings settings, Realms realms, @Nullable AuditTrail auditTrail) {
        super(settings);
        this.realms = realms.realms();
        this.auditTrail = auditTrail;
    }

    @Override
    public void extractAndRegisterToken(RestRequest request) throws AuthenticationException {
        for (Realm realm : realms) {
            AuthenticationToken token = realm.token(request);
            if (token != null) {
                request.putInContext(TOKEN_CTX_KEY, token);
                return;
            }
        }
        throw new AuthenticationException("Missing authentication token");
    }

    @Override
    public AuthenticationToken token(String action, TransportMessage<?> message) {
        return token(action, message, null);
    }

    @Override
    @SuppressWarnings("unchecked")
    public AuthenticationToken token(String action, TransportMessage<?> message, AuthenticationToken defaultToken) {
        AuthenticationToken token = message.getFromContext(TOKEN_CTX_KEY);
        if (token != null) {
            return token;
        }
        for (Realm realm : realms) {
            token = realm.token(message);
            if (token != null) {
                message.putInContext(TOKEN_CTX_KEY, token);
                return token;
            }
        }

        if (defaultToken == null) {
            if (auditTrail != null) {
                auditTrail.anonymousAccess(action, message);
            }
            throw new AuthenticationException("Missing authentication token for request [" + action + "]");
        }

        message.putInContext(TOKEN_CTX_KEY, defaultToken);
        return defaultToken;
    }

    /**
     * Authenticates the user associated with the given request by delegating the authentication to
     * the configured realms. Each realm that supports the given token will be asked to perform authentication,
     * the first realm that successfully authenticates will "win" and its authenticated user will be returned.
     * If none of the configured realms successfully authenticates the request, an {@link AuthenticationException} will
     * be thrown.
     *
     * The order by which the realms are checked is defined in {@link Realms}.
     *
     * @param action    The executed action
     * @param message   The executed request
     * @param token     The authentication token
     * @return          The authenticated user
     * @throws AuthenticationException If none of the configured realms successfully authenticated the
     *                                 request
     */
    @Override
    @SuppressWarnings("unchecked")
    public User authenticate(String action, TransportMessage<?> message, AuthenticationToken token) throws AuthenticationException {
        assert token != null : "cannot authenticate null tokens";
        User user = (User) message.getContext().get(USER_CTX_KEY);
        if (user != null) {
            return user;
        }
        for (Realm realm : realms) {
            if (realm.supports(token)) {
                user = realm.authenticate(token);
                if (user != null) {
                    message.putInContext(USER_CTX_KEY, user);
                    return user;
                } else if (auditTrail != null) {
                    auditTrail.authenticationFailed(realm.type(), token, action, message);
                }
            }
        }
        if (auditTrail != null) {
            auditTrail.authenticationFailed(token, action, message);
        }
        throw new AuthenticationException("Unable to authenticate user for request");
    }
}

@@ -4161,16 +4161,34 @@
 okens%22;%0A
+        try %7B%0A    
         
@@ -4246,32 +4246,36 @@
 X_KEY);%0A        
+  
+  
 if (user != null
@@ -4282,32 +4282,36 @@
 ) %7B%0A            
+    
 return user;%0A   
@@ -4299,32 +4299,36 @@
    return user;%0A
+    
         %7D%0A      
@@ -4321,32 +4321,36 @@
       %7D%0A        
+    
 for (Realm realm
@@ -4354,32 +4354,36 @@
 alm : realms) %7B%0A
+    
             if (
@@ -4423,16 +4423,20 @@
         
+  
+  
 user = r
@@ -4469,32 +4469,36 @@
                 
+    
 if (user != null
@@ -4493,32 +4493,36 @@
 user != null) %7B%0A
+    
                 
@@ -4579,32 +4579,36 @@
                 
+    
 return user;%0A   
@@ -4596,32 +4596,36 @@
    return user;%0A
+    
                 
@@ -4669,32 +4669,36 @@
                 
+  
+  
 auditTrail.authe
@@ -4760,34 +4760,42 @@
                 
-%7D%0A
+    %7D%0A    
             %7D%0A  
@@ -4792,34 +4792,42 @@
       %7D%0A        
+ 
-%7D%0A
+   %7D%0A    
         if (audi
@@ -4848,32 +4848,36 @@
 ) %7B%0A            
+  
+  
 auditTrail.authe
@@ -4917,34 +4917,42 @@
 ssage);%0A        
-%7D%0A
+    %7D%0A    
         throw ne
@@ -5017,20 +5017,89 @@
  request%22);%0A
+        %7D finally %7B%0A            token.clearCredentials();%0A        %7D%0A%0A
     %7D%0A%7D%0A

Revert "passwordfix: This removes the password clearing from the authentication service"