/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.atlas.web.security;

import org.apache.atlas.ApplicationProperties;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

@Component
@Scope("prototype")
public class AtlasAuthenticationProvider extends AtlasAbstractAuthenticationProvider {
    private static final Logger LOG = LoggerFactory.getLogger(AtlasAuthenticationProvider.class);

    public static final String FILE_AUTH_METHOD     = "atlas.authentication.method.file";
    public static final String LDAP_AUTH_METHOD     = "atlas.authentication.method.ldap";
    public static final String LDAP_TYPE            = "atlas.authentication.method.ldap.type";
    public static final String PAM_AUTH_METHOD      = "atlas.authentication.method.pam";
    public static final String KEYCLOAK_AUTH_METHOD = "atlas.authentication.method.keycloak";

    final AtlasLdapAuthenticationProvider ldapAuthenticationProvider;
    final AtlasFileAuthenticationProvider fileAuthenticationProvider;
    final AtlasADAuthenticationProvider adAuthenticationProvider;
    final AtlasPamAuthenticationProvider pamAuthenticationProvider;
    final AtlasKeycloakAuthenticationProvider atlasKeycloakAuthenticationProvider;

    private boolean fileAuthenticationMethodEnabled = true;
    private boolean pamAuthenticationEnabled;
    private boolean keycloakAuthenticationEnabled;
    private String  ldapType             = "NONE";
    private boolean ssoEnabled;

    @Inject
    public AtlasAuthenticationProvider(AtlasLdapAuthenticationProvider ldapAuthenticationProvider,
            AtlasFileAuthenticationProvider fileAuthenticationProvider, AtlasADAuthenticationProvider adAuthenticationProvider,
            AtlasPamAuthenticationProvider pamAuthenticationProvider, AtlasKeycloakAuthenticationProvider atlasKeycloakAuthenticationProvider) {
        this.ldapAuthenticationProvider          = ldapAuthenticationProvider;
        this.fileAuthenticationProvider          = fileAuthenticationProvider;
        this.adAuthenticationProvider            = adAuthenticationProvider;
        this.pamAuthenticationProvider           = pamAuthenticationProvider;
        this.atlasKeycloakAuthenticationProvider = atlasKeycloakAuthenticationProvider;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (ssoEnabled) {
            if (authentication != null) {
                authentication = getSSOAuthentication(authentication);

                if (authentication != null && authentication.isAuthenticated()) {
                    return authentication;
                }
            }
        } else {
            if (ldapType.equalsIgnoreCase("LDAP")) {
                try {
                    authentication = ldapAuthenticationProvider.authenticate(authentication);
                } catch (Exception ex) {
                    LOG.error("Error while LDAP authentication", ex);
                }
            } else if (ldapType.equalsIgnoreCase("AD")) {
                try {
                    authentication = adAuthenticationProvider.authenticate(authentication);
                } catch (Exception ex) {
                    LOG.error("Error while AD authentication", ex);
                }
            } else if (pamAuthenticationEnabled) {
                try {
                    authentication = pamAuthenticationProvider.authenticate(authentication);
                } catch (Exception ex) {
                    LOG.error("Error while PAM authentication", ex);
                }
            } else if (keycloakAuthenticationEnabled) {
                try {
                    authentication = atlasKeycloakAuthenticationProvider.authenticate(authentication);
                } catch (Exception ex) {
                    LOG.error("Error while Keycloak authentication", ex);
                }
            }
        }

        if (authentication != null) {
            if (authentication.isAuthenticated()) {
                return authentication;
            } else if (fileAuthenticationMethodEnabled) {  // If the LDAP/AD/PAM authentication fails try the local filebased login method
                authentication = fileAuthenticationProvider.authenticate(authentication);

                if (authentication != null && authentication.isAuthenticated()) {
                    return authentication;
                }
            }
        }

        LOG.error("Authentication failed.");

        throw new AtlasAuthenticationException("Authentication failed.");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        if (pamAuthenticationEnabled) {
            return pamAuthenticationProvider.supports(authentication);
        } else if (ldapType.equalsIgnoreCase("LDAP")) {
            return ldapAuthenticationProvider.supports(authentication);
        } else if (ldapType.equalsIgnoreCase("AD")) {
            return adAuthenticationProvider.supports(authentication);
        } else if (keycloakAuthenticationEnabled) {
            return atlasKeycloakAuthenticationProvider.supports(authentication);
        } else {
            return super.supports(authentication);
        }
    }

    public boolean isSsoEnabled() {
        return ssoEnabled;
    }

    public void setSsoEnabled(boolean ssoEnabled) {
        this.ssoEnabled = ssoEnabled;
    }

    @PostConstruct
    void setAuthenticationMethod() {
        try {
            Configuration configuration = ApplicationProperties.get();

            this.fileAuthenticationMethodEnabled = configuration.getBoolean(FILE_AUTH_METHOD, true);
            this.pamAuthenticationEnabled        = configuration.getBoolean(PAM_AUTH_METHOD, false);
            this.keycloakAuthenticationEnabled   = configuration.getBoolean(KEYCLOAK_AUTH_METHOD, false);

            boolean ldapAuthenticationEnabled = configuration.getBoolean(LDAP_AUTH_METHOD, false);

            if (ldapAuthenticationEnabled) {
                this.ldapType = configuration.getString(LDAP_TYPE, "NONE");
            } else {
                this.ldapType = "NONE";
            }
        } catch (Exception e) {
            LOG.error("Error while getting atlas.login.method application properties", e);
        }
    }

    private Authentication getSSOAuthentication(Authentication authentication) throws AuthenticationException {
        return authentication;
    }
}
