Merge remote-tracking branch 'asf/trunk' into trunk
diff --git a/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasAuthAdminClient.java b/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasAuthAdminClient.java
new file mode 100644
index 0000000..1ce0c4d
--- /dev/null
+++ b/has-project/has-client/src/main/java/org/apache/kerby/has/client/HasAuthAdminClient.java
@@ -0,0 +1,501 @@
+/**
+ *  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.kerby.has.client;
+
+import org.apache.kerby.KOptions;
+import org.apache.kerby.has.common.HasConfig;
+import org.apache.kerby.has.common.ssl.SSLFactory;
+import org.apache.kerby.has.common.util.URLConnectionFactory;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.Kadmin;
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HasAuthAdminClient implements Kadmin {
+    public static final Logger LOG = LoggerFactory.getLogger(HasAuthAdminClient.class);
+
+    private HasConfig hasConfig;
+
+    /**
+     * Create an instance of the HasAuthAdminClient.
+     *
+     * @param hasConfig the has config
+     */
+    public HasAuthAdminClient(HasConfig hasConfig) {
+        this.hasConfig = hasConfig;
+    }
+
+    protected HttpURLConnection getHttpsConnection(URL url, boolean isSpnego) throws Exception {
+        HasConfig conf = new HasConfig();
+
+        conf.setString(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
+        String sslClientConf = hasConfig.getSslClientConf();
+        conf.setString(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConf);
+        conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, false);
+
+        URLConnectionFactory connectionFactory = URLConnectionFactory
+            .newDefaultURLConnectionFactory(conf);
+        return (HttpURLConnection) connectionFactory.openConnection(url, isSpnego, hasConfig);
+    }
+
+    /**
+     * Create an authenticated connection to the Has server.
+     * <p>
+     * It uses Hadoop-auth client authentication which by default supports
+     * Kerberos HTTP SPNEGO, Pseudo/Simple and anonymous.
+     *
+     * @param url    the URL to open a HTTP connection to.
+     * @param method the HTTP method for the HTTP connection.
+     * @return an authenticated connection to the has server.
+     * @throws IOException if an IO error occurred.
+     */
+    protected HttpURLConnection createConnection(URL url, String method) {
+        HttpURLConnection conn = null;
+        if (hasConfig.getHttpsPort() != null && hasConfig.getHttpsHost() != null) {
+            try {
+                conn = getHttpsConnection(url, true);
+            } catch (Exception e) {
+                throw new RuntimeException("Error occurred when creating https connection.");
+            }
+        }
+        if (method.equals("POST") || method.equals("PUT")) {
+            conn.setDoOutput(true);
+        }
+        return conn;
+    }
+
+    private String getBaseURL() {
+        String url = null;
+        if (hasConfig.getHttpsPort() != null && hasConfig.getHttpsHost() != null) {
+            url = "https://" + hasConfig.getHttpsHost() + ":" + hasConfig.getHttpsPort()
+                + "/has/v1/admin/";
+        }
+        if (url == null) {
+            throw new RuntimeException("Please set the https address and port.");
+        }
+        return url;
+    }
+
+    @Override
+    public void addPrincipal(String principal) throws KrbException {
+        HttpURLConnection httpConn;
+
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "addprincipal?principal=" + principal);
+        } catch (MalformedURLException e) {
+            throw new KrbException("Failed to create a URL object.", e);
+        }
+
+        httpConn = createConnection(url, "POST");
+
+        httpConn.setRequestProperty("Content-Type",
+            "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("POST");
+        } catch (ProtocolException e) {
+            LOG.error("Fail to add principal. " + e);
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        try {
+            httpConn.setDoOutput(true);
+            httpConn.setDoInput(true);
+            httpConn.connect();
+
+            if (httpConn.getResponseCode() == 200) {
+                LOG.info(getResponse(httpConn));
+            } else {
+                throw new KrbException(getResponse(httpConn));
+            }
+        } catch (IOException e) {
+            throw new KrbException("IO error occurred.", e);
+        }
+    }
+
+    @Override
+    public void addPrincipal(String principal, String password) throws KrbException {
+        HttpURLConnection httpConn;
+
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "addprincipal?principal=" + principal
+                + "&password=" + password);
+        } catch (MalformedURLException e) {
+            throw new KrbException("Failed to create a URL object.", e);
+        }
+
+        httpConn = createConnection(url, "POST");
+
+        httpConn.setRequestProperty("Content-Type",
+            "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("POST");
+        } catch (ProtocolException e) {
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        try {
+            httpConn.setDoOutput(true);
+            httpConn.setDoInput(true);
+            httpConn.connect();
+
+            if (httpConn.getResponseCode() == 200) {
+                LOG.info(getResponse(httpConn));
+            } else {
+                throw new KrbException(getResponse(httpConn));
+            }
+        } catch (IOException e) {
+            throw new KrbException("IO error occurred.", e);
+        }
+    }
+
+    @Override
+    public void addPrincipal(String principal, String password, KOptions kOptions) throws KrbException {
+
+    }
+
+    @Override
+    public void deletePrincipal(String principal) throws KrbException {
+        HttpURLConnection httpConn;
+
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "deleteprincipal?principal=" + principal);
+        } catch (MalformedURLException e) {
+            throw new KrbException("Failed to create a URL object.", e);
+        }
+
+        httpConn = createConnection(url, "DELETE");
+
+        httpConn.setRequestProperty("Content-Type",
+            "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("DELETE");
+        } catch (ProtocolException e) {
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        try {
+            httpConn.setDoOutput(true);
+            httpConn.setDoInput(true);
+            httpConn.connect();
+
+            if (httpConn.getResponseCode() == 200) {
+                LOG.info(getResponse(httpConn));
+            } else {
+                throw new KrbException("Connection deined.");
+            }
+        } catch (IOException e) {
+            throw new KrbException("IO error occurred.", e);
+        }
+    }
+
+    @Override
+    public void modifyPrincipal(String principal, KOptions kOptions) throws KrbException {
+
+    }
+
+    @Override
+    public void renamePrincipal(String oldPrincipal, String newPrincipal) throws KrbException {
+        HttpURLConnection httpConn;
+
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "renameprincipal?oldprincipal=" + oldPrincipal
+                + "&newprincipal=" + newPrincipal);
+        } catch (MalformedURLException e) {
+            throw new KrbException("Failed to create a URL object.", e);
+        }
+
+        httpConn = createConnection(url, "POST");
+
+        httpConn.setRequestProperty("Content-Type",
+            "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("POST");
+        } catch (ProtocolException e) {
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        try {
+            httpConn.setDoOutput(true);
+            httpConn.setDoInput(true);
+            httpConn.connect();
+
+            if (httpConn.getResponseCode() == 200) {
+                LOG.info(getResponse(httpConn));
+            } else {
+                throw new KrbException(getResponse(httpConn));
+            }
+        } catch (IOException e) {
+            throw new KrbException("IO error occurred.", e);
+        }
+    }
+
+    @Override
+    public List<String> getPrincipals() throws KrbException {
+        HttpURLConnection httpConn;
+
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "getprincipals");
+        } catch (MalformedURLException e) {
+            throw new KrbException("Failed to create a URL object.", e);
+        }
+
+        httpConn = createConnection(url, "GET");
+
+        httpConn.setRequestProperty("Content-Type",
+            "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("GET");
+        } catch (ProtocolException e) {
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        String response;
+        try {
+            httpConn.setDoInput(true);
+            httpConn.connect();
+
+            if (httpConn.getResponseCode() == 200) {
+                response = getResponse(httpConn);
+            } else {
+                throw new KrbException(getResponse(httpConn));
+            }
+        } catch (IOException e) {
+            LOG.error("IO error occurred." + e.getMessage());
+            throw new KrbException("IO error occurred.", e);
+        }
+        return getPrincsList(response);
+    }
+
+    @Override
+    public List<String> getPrincipals(String exp) throws KrbException {
+        HttpURLConnection httpConn;
+
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "getprincipals?exp=" + exp);
+        } catch (MalformedURLException e) {
+            throw new KrbException("Failed to create a URL object. ", e);
+        }
+
+        httpConn = createConnection(url, "GET");
+
+        httpConn.setRequestProperty("Content-Type",
+            "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("GET");
+        } catch (ProtocolException e) {
+            LOG.error("Failed to set the method for URL request." + e.getMessage());
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        String response;
+        try {
+            httpConn.setDoOutput(true);
+            httpConn.setDoInput(true);
+            httpConn.connect();
+
+            if (httpConn.getResponseCode() == 200) {
+                response = getResponse(httpConn);
+            } else {
+                throw new KrbException(getResponse(httpConn));
+            }
+        } catch (IOException e) {
+            throw new KrbException("IO error occurred.", e);
+        }
+        return getPrincsList(response);
+    }
+
+    /**
+     * Change principals JSON string to a List.
+     *
+     * @param princs principals JSON string which like
+     *               "["HTTP\/host1@HADOOP.COM","HTTP\/host2@HADOOP.COM"]"
+     * @return principalLists.
+     */
+    private List<String> getPrincsList(String princs) throws KrbException {
+        List<String> principalLists = new ArrayList<>();
+        try {
+            JSONArray principals = new JSONArray(princs);
+            for (int i = 0; i < principals.length(); i++) {
+                principalLists.add("\t" + principals.getString(i));
+            }
+        } catch (JSONException e) {
+            throw new KrbException("JSON Exception occurred. ", e);
+        }
+        return principalLists;
+    }
+
+    @Override
+    public void exportKeytab(File keytab, String principal) throws KrbException {
+        URL url;
+        try {
+            url = new URL(getBaseURL() + "exportkeytab?principal=" + principal);
+        } catch (MalformedURLException e) {
+            LOG.error("Failed to create a URL object." + e.getMessage());
+            throw new KrbException("Failed to create a URL object.", e);
+        }
+
+        HttpURLConnection httpConn = createConnection(url, "GET");
+        httpConn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+        try {
+            httpConn.setRequestMethod("GET");
+        } catch (ProtocolException e) {
+            throw new KrbException("Failed to set the method for URL request.", e);
+        }
+        httpConn.setDoOutput(true);
+        httpConn.setDoInput(true);
+        try {
+            httpConn.connect();
+            if (httpConn.getResponseCode() != 200) {
+                throw new KrbException(getResponse(httpConn));
+            }
+            FileOutputStream fos = new FileOutputStream(keytab);
+            InputStream in = httpConn.getInputStream();
+            byte[] buffer = new byte[3 * 1024];
+            int read;
+            while ((read = in.read(buffer)) > 0) {
+                fos.write(buffer, 0, read);
+            }
+            fos.close();
+            in.close();
+        } catch (IOException e) {
+            throw new KrbException("IO error occurred.", e);
+        }
+        LOG.info("Receive keytab file \"" + keytab.getName() + "\" from server successfully.");
+    }
+
+    @Override
+    public void exportKeytab(File keytabFile, List<String> principals) throws KrbException {
+        HttpURLConnection httpConn;
+        for (String principal : principals) {
+            String request = getBaseURL() + "exportkeytab?principal=" + principal;
+            URL url;
+            try {
+                url = new URL(request);
+            } catch (MalformedURLException e) {
+                throw new KrbException("Failed to create a URL object.");
+            }
+            httpConn = createConnection(url, "GET");
+            httpConn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+            try {
+                httpConn.setRequestMethod("GET");
+            } catch (ProtocolException e) {
+                throw new KrbException("Failed to set the method for URL request.", e);
+            }
+            httpConn.setDoOutput(true);
+            httpConn.setDoInput(true);
+            try {
+                httpConn.connect();
+                if (httpConn.getResponseCode() != 200) {
+                    throw new KrbException(getResponse(httpConn));
+                }
+                FileOutputStream fos = new FileOutputStream(keytabFile);
+                InputStream in = httpConn.getInputStream();
+                byte[] buffer = new byte[4 * 1024];
+                int read;
+                while ((read = in.read(buffer)) > 0) {
+                    fos.write(buffer, 0, read);
+                }
+                fos.close();
+                in.close();
+            } catch (IOException e) {
+                throw new KrbException("IO error occurred.", e);
+            }
+        }
+        LOG.info("Accept keytab file \"" + keytabFile.getName() + "\" from server.");
+    }
+
+    @Override
+    public void addPrincipal(String principal, KOptions kOptions) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public String getKadminPrincipal() {
+        return null;
+    }
+
+    @Override
+    public void exportKeytab(File keytabFile) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public void removeKeytabEntriesOf(File keytabFile, String principal) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public void removeKeytabEntriesOf(File keytabFile, String principal, int kvno) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public void removeOldKeytabEntriesOf(File keytabFile, String principal) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public void changePassword(String principal,
+                               String newPassword) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public void updateKeys(String principal) throws KrbException {
+        throw new KrbException("Unsupported feature");
+    }
+
+    @Override
+    public void release() throws KrbException {
+
+    }
+
+    private String getResponse(HttpURLConnection httpConn) throws IOException {
+        StringBuilder data = new StringBuilder();
+        InputStream inputStream;
+        if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
+            inputStream = httpConn.getInputStream();
+        } else {
+            /* Error from server */
+            inputStream = httpConn.getErrorStream();
+        }
+        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
+        String s;
+        while ((s = br.readLine()) != null) {
+            data.append(s);
+        }
+        return data.toString();
+    }
+}
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java
index 06a8822..cf74340 100644
--- a/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/util/URLConnectionFactory.java
@@ -165,7 +165,6 @@
       throws IOException, AuthenticationException {
     if (isSpnego && hasConfig != null) {
       LOG.debug("open AuthenticatedURL connection {}", url);
-//      UserGroupInformation.getCurrentUser().checkTGTAndReloginFromKeytab();
       final AuthenticatedURL.Token authToken = new AuthenticatedURL.Token();
       return new AuthenticatedURL(new KerberosHasAuthenticator(hasConfig.getAdminKeytab(),
           hasConfig.getAdminKeytabPrincipal()),
diff --git a/kerby-dist/has-dist/bin/hasinit.sh b/kerby-dist/has-dist/bin/has-init.sh
similarity index 100%
rename from kerby-dist/has-dist/bin/hasinit.sh
rename to kerby-dist/has-dist/bin/has-init.sh