org.apache.hive.service.auth.HttpAuthUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hive.service.auth.HttpAuthUtils.java

Source

/**
 * 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.hive.service.auth;

import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.StringTokenizer;

import javax.security.auth.Subject;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hive.shims.ShimLoader;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

/**
 * Utility functions for HTTP mode authentication.
 */
public final class HttpAuthUtils {
    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
    public static final String AUTHORIZATION = "Authorization";
    public static final String BASIC = "Basic";
    public static final String NEGOTIATE = "Negotiate";
    private static final Log LOG = LogFactory.getLog(HttpAuthUtils.class);
    private static final String COOKIE_ATTR_SEPARATOR = "&";
    private static final String COOKIE_CLIENT_USER_NAME = "cu";
    private static final String COOKIE_CLIENT_RAND_NUMBER = "rn";
    private static final String COOKIE_KEY_VALUE_SEPARATOR = "=";
    private static final Set<String> COOKIE_ATTRIBUTES = new HashSet<String>(
            Arrays.asList(COOKIE_CLIENT_USER_NAME, COOKIE_CLIENT_RAND_NUMBER));

    /**
     * @return Stringified Base64 encoded kerberosAuthHeader on success
     * @throws Exception
     */
    public static String getKerberosServiceTicket(String principal, String host, String serverHttpUrl,
            boolean assumeSubject) throws Exception {
        String serverPrincipal = ShimLoader.getHadoopThriftAuthBridge().getServerPrincipal(principal, host);
        if (assumeSubject) {
            // With this option, we're assuming that the external application,
            // using the JDBC driver has done a JAAS kerberos login already
            AccessControlContext context = AccessController.getContext();
            Subject subject = Subject.getSubject(context);
            if (subject == null) {
                throw new Exception("The Subject is not set");
            }
            return Subject.doAs(subject, new HttpKerberosClientAction(serverPrincipal, serverHttpUrl));
        } else {
            // JAAS login from ticket cache to setup the client UserGroupInformation
            UserGroupInformation clientUGI = ShimLoader.getHadoopThriftAuthBridge()
                    .getCurrentUGIWithConf("kerberos");
            return clientUGI.doAs(new HttpKerberosClientAction(serverPrincipal, serverHttpUrl));
        }
    }

    /**
     * Creates and returns a HS2 cookie token.
     * @param clientUserName Client User name.
     * @return An unsigned cookie token generated from input parameters.
     * The final cookie generated is of the following format :
     * cu=<username>&rn=<randomNumber>&s=<cookieSignature>
     */
    public static String createCookieToken(String clientUserName) {
        StringBuffer sb = new StringBuffer();
        sb.append(COOKIE_CLIENT_USER_NAME).append(COOKIE_KEY_VALUE_SEPARATOR).append(clientUserName)
                .append(COOKIE_ATTR_SEPARATOR);
        sb.append(COOKIE_CLIENT_RAND_NUMBER).append(COOKIE_KEY_VALUE_SEPARATOR)
                .append((new Random(System.currentTimeMillis())).nextLong());
        return sb.toString();
    }

    /**
     * Parses a cookie token to retrieve client user name.
     * @param tokenStr Token String.
     * @return A valid user name if input is of valid format, else returns null.
     */
    public static String getUserNameFromCookieToken(String tokenStr) {
        Map<String, String> map = splitCookieToken(tokenStr);

        if (!map.keySet().equals(COOKIE_ATTRIBUTES)) {
            LOG.error("Invalid token with missing attributes " + tokenStr);
            return null;
        }
        return map.get(COOKIE_CLIENT_USER_NAME);
    }

    /**
     * Splits the cookie token into attributes pairs.
     * @param str input token.
     * @return a map with the attribute pairs of the token if the input is valid.
     * Else, returns null.
     */
    private static Map<String, String> splitCookieToken(String tokenStr) {
        Map<String, String> map = new HashMap<String, String>();
        StringTokenizer st = new StringTokenizer(tokenStr, COOKIE_ATTR_SEPARATOR);

        while (st.hasMoreTokens()) {
            String part = st.nextToken();
            int separator = part.indexOf(COOKIE_KEY_VALUE_SEPARATOR);
            if (separator == -1) {
                LOG.error("Invalid token string " + tokenStr);
                return null;
            }
            String key = part.substring(0, separator);
            String value = part.substring(separator + 1);
            map.put(key, value);
        }
        return map;
    }

    private HttpAuthUtils() {
        throw new UnsupportedOperationException("Can't initialize class");
    }

    /**
     * We'll create an instance of this class within a doAs block so that the client's TGT credentials
     * can be read from the Subject
     */
    public static class HttpKerberosClientAction implements PrivilegedExceptionAction<String> {
        public static final String HTTP_RESPONSE = "HTTP_RESPONSE";
        public static final String SERVER_HTTP_URL = "SERVER_HTTP_URL";
        private final String serverPrincipal;
        private final String serverHttpUrl;
        private final Base64 base64codec;
        private final HttpContext httpContext;

        public HttpKerberosClientAction(String serverPrincipal, String serverHttpUrl) {
            this.serverPrincipal = serverPrincipal;
            this.serverHttpUrl = serverHttpUrl;
            base64codec = new Base64(0);
            httpContext = new BasicHttpContext();
            httpContext.setAttribute(SERVER_HTTP_URL, serverHttpUrl);
        }

        @Override
        public String run() throws Exception {
            // This Oid for Kerberos GSS-API mechanism.
            Oid mechOid = new Oid("1.2.840.113554.1.2.2");
            // Oid for kerberos principal name
            Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
            GSSManager manager = GSSManager.getInstance();
            // GSS name for server
            GSSName serverName = manager.createName(serverPrincipal, krb5PrincipalOid);
            // Create a GSSContext for authentication with the service.
            // We're passing client credentials as null since we want them to be read from the Subject.
            GSSContext gssContext = manager.createContext(serverName, mechOid, null, GSSContext.DEFAULT_LIFETIME);
            gssContext.requestMutualAuth(false);
            // Establish context
            byte[] inToken = new byte[0];
            byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length);
            gssContext.dispose();
            // Base64 encoded and stringified token for server
            return new String(base64codec.encode(outToken));
        }
    }
}