/*
 * 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.jmeter.protocol.http.control;

import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

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.auth.login.LoginException;

import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * Takes in charge Kerberos auth mechanism
 * @since 2.10
 */
public class KerberosManager implements Serializable {

    private static final long serialVersionUID = 2L;

    private static final Logger log = LoggingManager.getLoggerForClass();

    private static final String JAAS_APPLICATION = JMeterUtils.getPropDefault("kerberos_jaas_application", "JMeter"); //$NON-NLS-1$ $NON-NLS-2$
    private final ConcurrentMap<String, Future<Subject>> subjects = new ConcurrentHashMap<>();

    public KerberosManager() {
    }

    void clearSubjects() {
        subjects.clear();
    }

    public Subject getSubjectForUser(final String username,
            final String password) {
        Callable<Subject> callable = new Callable<Subject>() {

            @Override
            public Subject call() throws Exception {
                LoginContext loginCtx;
                try {
                    loginCtx = new LoginContext(JAAS_APPLICATION,
                            new LoginCallbackHandler(username, password));
                    loginCtx.login();
                    return loginCtx.getSubject();
                } catch (LoginException e) {
                    log.warn("Could not log in user " + username, e);
                }
                return null;
            }
        };

        FutureTask<Subject> task = new FutureTask<>(callable);
        if(log.isDebugEnabled()) {
            log.debug("Subject cached:"+subjects.keySet() +" before:"+username);
        }
        Future<Subject> subjectFuture = subjects.putIfAbsent(username, task);
        if (subjectFuture == null) {
            subjectFuture = task;
            task.run();
        }
        try {
            return subjectFuture.get();
        } catch (InterruptedException e1) {
            log.warn("Interrupted while getting subject for " + username, e1);
        } catch (ExecutionException e1) {
            log.warn("Execution of getting subject for " + username + " failed", e1);
        }
        return null;
    }

    // Needs to be package-protected to avoid problem with serialisation tests
    static class LoginCallbackHandler implements CallbackHandler {
        private final String password;
        private final String username;

        public LoginCallbackHandler(final String username, final String password) {
            super();
            this.username = username;
            this.password = password;
        }

        @Override
        public void handle(Callback[] callbacks) throws IOException,
                UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof NameCallback && username != null) {
                    NameCallback nc = (NameCallback) callback;
                    nc.setName(username);
                } else if (callback instanceof PasswordCallback) {
                    PasswordCallback pc = (PasswordCallback) callback;
                    pc.setPassword(password.toCharArray());
                } else {
                    throw new UnsupportedCallbackException( callback,
                            "Unrecognized Callback"); //$NON-NLS-1$
                }
            }
        }
    }
    
    public String getKrb5Conf() {
        return System.getProperty("java.security.krb5.conf"); //$NON-NLS-1$
    }

    public boolean getKrb5Debug() {
        return Boolean.getBoolean("java.security.krb5.debug"); //$NON-NLS-1$
    }

    public String getJaasConf() {
        return System.getProperty("java.security.auth.login.config"); //$NON-NLS-1$
    }
    
    @Override
    public String toString() {
        return "KerberosManager[jaas: " + getJaasConf() + ", krb5: " + getKrb5Conf() + ", debug: " + getKrb5Debug() +"]";
    }
}

@@ -908,46 +908,8 @@
 le;%0A
-import java.util.concurrent.Callable;%0A
 impo
@@ -2163,24 +2163,41 @@
 Manager() %7B%0A
+        super();%0A
     %7D%0A%0A    v
@@ -2360,143 +2360,60 @@
     
-Callable%3CSubject%3E callable = new Callable%3CSubject%3E() %7B%0A%0A            @Override%0A            public Subject call() throws Exception %7B%0A    
+FutureTask%3CSubject%3E task = new FutureTask%3C%3E(() -%3E %7B%0A
     
@@ -2451,38 +2451,30 @@
             
-    
 try %7B%0A
-    
             
@@ -2547,20 +2547,16 @@
         
-    
 new Logi
@@ -2590,28 +2590,24 @@
 password));%0A
-    
             
@@ -2640,28 +2640,24 @@
             
-    
 return login
@@ -2682,28 +2682,24 @@
             
-   
- 
 %7D catch (Log
@@ -2711,28 +2711,24 @@
 eption e) %7B%0A
-    
             
@@ -2789,34 +2789,26 @@
             
-    %7D%0A    
+%7D%0A
             
@@ -2832,86 +2832,9 @@
     
-  
-  %7D%0A        %7D;%0A%0A        FutureTask%3CSubject%3E task = new FutureTask%3C%3E(callable
+%7D
 );%0A 
@@ -3126,24 +3126,58 @@
  task.run();
+ // NOSONAR we just execute method
 %0A        %7D%0A 
@@ -3348,24 +3348,72 @@
 rname, e1);%0A
+            Thread.currentThread().interrupt();%0A
         %7D ca

fix errors