/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *      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.kie.server.services.impl.security.adapters;

import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.sasl.RealmCallback;

import org.kie.server.api.KieServerConstants;
import org.kie.server.api.security.SecurityAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JMSSecurityAdapter implements SecurityAdapter {
    private static final Logger logger = LoggerFactory.getLogger(JMSSecurityAdapter.class);

    private static final ServiceLoader<SecurityAdapter> securityAdapters = ServiceLoader.load(SecurityAdapter.class);
    private static List<SecurityAdapter> adapters = new ArrayList<>();

    private static ThreadLocal<UserDetails> currentUser = new ThreadLocal<UserDetails>();

    static  {
        for (SecurityAdapter adapter : securityAdapters) {
            adapters.add(adapter);
        }
    }

    @Override
    public String getUser(Object ... params) {
        if (currentUser.get() != null) {
            logger.debug("Returning name from JMS Adapter - {}", currentUser.get().getName());
            return currentUser.get().getName();
        }

        return null;
    }

    @Override
    public List<String> getRoles(Object ... params) {

        if (currentUser.get() != null) {
            logger.debug("Returning name from JMS Adapter - {}", currentUser.get().getName());
            return currentUser.get().getRoles();
        }

        return Collections.emptyList();
    }

    public static void login(String user, String pass) {
        if (currentUser.get() != null) {
            logger.debug("Already authenticated with user {}", currentUser.get().getName());
            return;
        }
        logger.debug("About to login as {} with pass {}", user, pass.length());
        try {
            CallbackHandler handler = new UserPassCallbackHandler(user, pass);
            final String domain = System.getProperty(KieServerConstants.KIE_SERVER_JAAS_DOMAIN, "kie-jms-login-context");
            LoginContext lc = new LoginContext( domain, handler);
            lc.login();
            Subject subject = lc.getSubject();
            logger.debug("Login successfull and subject is {}", subject);
            UserDetails userDetails = new UserDetails();
            userDetails.setName(user);
            List<String> roles = new ArrayList<String>();
            if (subject != null) {
                Set<Principal> principals = subject.getPrincipals();

                if (principals != null) {

                    roles = new ArrayList<String>();
                    for (Principal principal : principals) {
                        if (principal instanceof Group) {
                            Enumeration<? extends Principal> groups = ((Group) principal).members();

                            while (groups.hasMoreElements()) {
                                Principal groupPrincipal = (Principal) groups.nextElement();
                                roles.add(groupPrincipal.getName());
                            }
                            break;
                        }
                    }
                }
                roles.addAll(getRolesFromAdapter(subject));
            }


            userDetails.setRoles(roles);
            logger.debug("setting user details as {}", userDetails);
            currentUser.set(userDetails);

        } catch( Exception e ) {
            logger.debug( "Unable to login via JAAS with message supplied user and password", e);
        }
    }

    public static void logout() {
        currentUser.set(null);
    }

    private static class UserDetails {
        private String name;

        private List<String> roles;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<String> getRoles() {
            return roles;
        }

        public void setRoles(List<String> roles) {
            this.roles = roles;
        }

        @Override
        public String toString() {
            return "UserDetails{" +
                    "name='" + name + '\'' +
                    ", roles=" + roles +
                    '}';
        }
    }

    private static class UserPassCallbackHandler implements CallbackHandler {
        private String user;
        private String pass;

        public UserPassCallbackHandler(String user, String pass) {
            this.user = user;
            this.pass = pass;
        }

        @Override
        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
            for (Callback current : callbacks) {
                if (current instanceof NameCallback) {
                    NameCallback ncb = (NameCallback) current;
                    ncb.setName(user);
                } else if (current instanceof PasswordCallback) {
                    PasswordCallback pcb = (PasswordCallback) current;
                    pcb.setPassword(pass.toCharArray());
                } else if (current instanceof RealmCallback) {
                    RealmCallback realmCallback = (RealmCallback) current;
                    realmCallback.setText(realmCallback.getDefaultText());
                }
            }

        }
    }

    protected static List<String> getRolesFromAdapter(Subject subject) {
        List<String> roles = new ArrayList<String>();

        for (SecurityAdapter adapter : adapters) {
            List<String> adapterRoles = adapter.getRoles(subject);
            if (adapterRoles != null && !adapterRoles.isEmpty()) {
                roles.addAll(adapterRoles);
            }
        }

        return roles;
    }
}
