Repository: incubator-hawq
Updated Branches:
  refs/heads/2.3.0.0-incubating 5d5fee1cc -> 66afff2ce


HAWQ-1036. Implement user impersonation in PXF


Project: http://git-wip-us.apache.org/repos/asf/incubator-hawq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-hawq/commit/3612c9a3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-hawq/tree/3612c9a3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-hawq/diff/3612c9a3

Branch: refs/heads/2.3.0.0-incubating
Commit: 3612c9a35752fd3f199d65e9a3261d8c2fd79062
Parents: 5d5fee1
Author: Alexander Denissov <adenis...@pivotal.io>
Authored: Tue Feb 13 11:05:10 2018 +1100
Committer: Alexander Denissov <adenis...@pivotal.io>
Committed: Tue Feb 13 11:05:10 2018 +1100

----------------------------------------------------------------------
 pxf/build.gradle                                |   8 +-
 pxf/gradle/profiles/gpdb.properties             |   1 +
 .../src/configs/tomcat/bin/setenv.sh            |  11 +-
 .../service/rest/ServletLifecycleListener.java  |  63 ----------
 .../service/servlet/SecurityServletFilter.java  | 114 +++++++++++++++++++
 .../servlet/ServletLifecycleListener.java       |  63 ++++++++++
 .../pxf/service/utilities/ProtocolData.java     |  35 ++++--
 .../hawq/pxf/service/utilities/SecureLogin.java |  21 +++-
 pxf/pxf-service/src/main/webapp/WEB-INF/web.xml |  10 +-
 pxf/pxf-service/src/scripts/pxf-env.sh          |   3 +
 pxf/pxf-service/src/scripts/pxf-service         |   3 +-
 .../pxf/service/BridgeOutputBuilderTest.java    |   1 +
 .../pxf/service/utilities/ProtocolDataTest.java |  31 ++++-
 .../pxf/service/utilities/SecureLoginTest.java  |  65 +++++++++++
 14 files changed, 346 insertions(+), 83 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/build.gradle
----------------------------------------------------------------------
diff --git a/pxf/build.gradle b/pxf/build.gradle
index 127de0d..1ed42bf 100644
--- a/pxf/build.gradle
+++ b/pxf/build.gradle
@@ -196,12 +196,14 @@ project('pxf-service') {
     apply plugin: 'war'
     tasks.war {
         archiveName = 'pxf.war'
+        filter(ReplaceTokens, tokens: ['pxfProtocolVersion': 
project.pxfProtocolVersion])
         processResources {
             filesMatching('**/pxf-*') {
                 details ->
                     details.exclude()
             }
         }
+
     }
     dependencies {
         compile(project(':pxf-api'))
@@ -269,7 +271,8 @@ project('pxf-service') {
                     tokens: ['pxfLogDir'     :  databaseProperties.pxfLogDir,
                              'pxfRunDir'     :  databaseProperties.pxfRunDir,
                              'pxfPortNum'    :  databaseProperties.pxfPortNum,
-                             'pxfDefaultUser': 
(databaseProperties.pxfDefaultUser == null ? "" : 
databaseProperties.pxfDefaultUser)])
+                             'pxfDefaultUser': 
(databaseProperties.pxfDefaultUser == null ? "" : 
databaseProperties.pxfDefaultUser),
+                             'pxfDefaultUserImpersonation': 
(databaseProperties.pxfDefaultUserImpersonation == null ? "" : 
databaseProperties.pxfDefaultUserImpersonation)])
             into "/etc/pxf-${project.version}/conf"
         }
         
@@ -645,7 +648,8 @@ task install(type: Copy, dependsOn: [subprojects.build, 
tomcatGet]) {
                 tokens: ['pxfLogDir'     : databaseProperties.pxfLogDir,
                          'pxfRunDir'     : databaseProperties.pxfRunDir,
                          'pxfPortNum'    : databaseProperties.pxfPortNum,
-                         'pxfDefaultUser': (databaseProperties.pxfDefaultUser 
== null ? "" : databaseProperties.pxfDefaultUser)]) into 'conf'
+                         'pxfDefaultUser': (databaseProperties.pxfDefaultUser 
== null ? "" : databaseProperties.pxfDefaultUser),
+                         'pxfDefaultUserImpersonation': 
(databaseProperties.pxfDefaultUserImpersonation == null ? "" : 
databaseProperties.pxfDefaultUserImpersonation)]) into 'conf'
     }
     from("pxf-service/src/configs/tomcat") { into 'tomcat-templates' }
     from("pxf-service/src/configs/templates") { into 'conf-templates' }

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/gradle/profiles/gpdb.properties
----------------------------------------------------------------------
diff --git a/pxf/gradle/profiles/gpdb.properties 
b/pxf/gradle/profiles/gpdb.properties
index 91775a3..efcbd0c 100644
--- a/pxf/gradle/profiles/gpdb.properties
+++ b/pxf/gradle/profiles/gpdb.properties
@@ -18,3 +18,4 @@
 pxfLogDir=${PXF_HOME}/logs
 pxfRunDir=${PXF_HOME}/run
 pxfPortNum=5888
+pxfDefaultUserImpersonation=true

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/configs/tomcat/bin/setenv.sh
----------------------------------------------------------------------
diff --git a/pxf/pxf-service/src/configs/tomcat/bin/setenv.sh 
b/pxf/pxf-service/src/configs/tomcat/bin/setenv.sh
index 2b8b473..9120390 100644
--- a/pxf/pxf-service/src/configs/tomcat/bin/setenv.sh
+++ b/pxf/pxf-service/src/configs/tomcat/bin/setenv.sh
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# Edit this file to set custom options
+
 # Tomcat accepts two parameters JAVA_OPTS and CATALINA_OPTS
 # JAVA_OPTS are used during START/STOP/RUN
 # CATALINA_OPTS are used during START/RUN
@@ -22,7 +22,14 @@
 AGENT_PATHS=""
 JAVA_AGENTS=""
 JAVA_LIBRARY_PATH=""
+
+# DO NOT EDIT VALUES FOR THE VARIABLES BELOW -- they are generated by the 
start script
 JVM_OPTS=""
-JAVA_OPTS="$JVM_OPTS $AGENT_PATHS $JAVA_AGENTS $JAVA_LIBRARY_PATH 
-Dpxf.log.dir=$CATALINA_BASE/logs"
+PXF_LOGDIR="$CATALINA_BASE/logs"
+PXF_USER_IMPERSONATION=""
+
+PXF_OPTS="-Dpxf.log.dir=$PXF_LOGDIR 
-Dpxf.service.user.impersonation.enabled=$PXF_USER_IMPERSONATION"
+JAVA_OPTS="$JVM_OPTS $AGENT_PATHS $JAVA_AGENTS $JAVA_LIBRARY_PATH $PXF_OPTS"
+
 CATALINA_PID="$CATALINA_BASE/logs/catalina.pid"
 CATALINA_OUT="$CATALINA_BASE/logs/catalina.out"

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/rest/ServletLifecycleListener.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/rest/ServletLifecycleListener.java
 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/rest/ServletLifecycleListener.java
deleted file mode 100644
index f7b897a..0000000
--- 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/rest/ServletLifecycleListener.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.apache.hawq.pxf.service.rest;
-
-/*
- * 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.
- */
-
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import javax.servlet.ServletContextListener;
-import javax.servlet.ServletContextEvent;
-
-import org.apache.hawq.pxf.service.utilities.Log4jConfigure;
-import org.apache.hawq.pxf.service.utilities.SecureLogin;
-
-/**
- * Listener on lifecycle events of our webapp
- */
-public class ServletLifecycleListener implements ServletContextListener {
-
-    private static final Log LOG = 
LogFactory.getLog(ServletContextListener.class);
-
-       /**
-        * Called after the webapp has been initialized.
-        *
-        * 1. Initializes log4j.
-        * 2. Initiates a Kerberos login when Hadoop security is on.
-        */
-       @Override
-       public void contextInitialized(ServletContextEvent event) {
-               // 1. Initialize log4j:
-               Log4jConfigure.configure(event);
-
-               LOG.info("webapp initialized");
-
-               // 2. Initiate secure login
-               SecureLogin.login();
-       }
-
-       /**
-        * Called before the webapp is about to go down
-        */
-       @Override
-       public void contextDestroyed(ServletContextEvent event) {
-               LOG.info("webapp about to go down");
-       }
-}

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/SecurityServletFilter.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/SecurityServletFilter.java
 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/SecurityServletFilter.java
new file mode 100644
index 0000000..b2cbf58
--- /dev/null
+++ 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/SecurityServletFilter.java
@@ -0,0 +1,114 @@
+package org.apache.hawq.pxf.service.servlet;
+
+/*
+ * 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.
+ */
+
+
+import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hawq.pxf.service.utilities.SecureLogin;
+
+
+/**
+ * Listener on lifecycle events of our webapp
+ */
+public class SecurityServletFilter implements Filter {
+
+    private static final Log LOG = 
LogFactory.getLog(SecurityServletFilter.class);
+    private static final String USER_HEADER = "X-GP-USER";
+    private static final String MISSING_HEADER_ERROR = String.format("Header 
%s is missing in the request", USER_HEADER);
+    private static final String EMPTY_HEADER_ERROR = String.format("Header %s 
is empty in the request", USER_HEADER);
+
+    /**
+     * Initializes the filter.
+     *
+     * @param filterConfig filter configuration
+     */
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+    /**
+     * If user impersonation is configured, examines the request for the 
presense of the expected security headers
+     * and create a proxy user to execute further request chain. Responds with 
an HTTP error if the header is missing
+     * or the chain processing throws an exception.
+     *
+     * @param request http request
+     * @param response http response
+     * @param chain filter chain
+     */
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse 
response, final FilterChain chain) throws IOException, ServletException {
+
+        if (SecureLogin.isUserImpersonationEnabled()) {
+
+            // retrieve user header and make sure header is present and is not 
empty
+            final String user = ((HttpServletRequest) 
request).getHeader(USER_HEADER);
+            if (user == null) {
+                throw new IllegalArgumentException(MISSING_HEADER_ERROR);
+            } else if (user.trim().isEmpty()) {
+                throw new IllegalArgumentException(EMPTY_HEADER_ERROR);
+            }
+
+            // TODO refresh Kerberos token when security is enabled
+
+            // prepare pivileged action to run on behalf of proxy user
+            PrivilegedExceptionAction<Boolean> action = new 
PrivilegedExceptionAction<Boolean>() {
+                @Override
+                public Boolean run() throws IOException, ServletException {
+                    LOG.debug("Performing request chain call for proxy user = 
" + user);
+                    chain.doFilter(request, response);
+                    return true;
+                }
+            };
+
+            // create proxy user UGI from the UGI of the logged in user and 
execute the servlet chain as that user
+            try {
+                LOG.debug("Creating proxy user for " + user);
+                UserGroupInformation proxyUGI = 
UserGroupInformation.createProxyUser(user, 
UserGroupInformation.getLoginUser());;
+                proxyUGI.doAs(action);
+            } catch (UndeclaredThrowableException ute) {
+                // unwrap the real exception thrown by the action
+                throw new ServletException(ute.getCause());
+            } catch (InterruptedException ie) {
+                throw new ServletException(ie);
+            }
+        } else {
+            // no user impersonation is configured
+            chain.doFilter(request, response);
+        }
+    }
+
+    /**
+     * Destroys the filter.
+     */
+    @Override
+    public void destroy() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/ServletLifecycleListener.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/ServletLifecycleListener.java
 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/ServletLifecycleListener.java
new file mode 100644
index 0000000..3461a72
--- /dev/null
+++ 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/servlet/ServletLifecycleListener.java
@@ -0,0 +1,63 @@
+package org.apache.hawq.pxf.service.servlet;
+
+/*
+ * 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.
+ */
+
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletContextEvent;
+
+import org.apache.hawq.pxf.service.utilities.Log4jConfigure;
+import org.apache.hawq.pxf.service.utilities.SecureLogin;
+
+/**
+ * Listener on lifecycle events of our webapp
+ */
+public class ServletLifecycleListener implements ServletContextListener {
+
+    private static final Log LOG = 
LogFactory.getLog(ServletContextListener.class);
+
+       /**
+        * Called after the webapp has been initialized.
+        *
+        * 1. Initializes log4j.
+        * 2. Initiates a Kerberos login when Hadoop security is on.
+        */
+       @Override
+       public void contextInitialized(ServletContextEvent event) {
+               // 1. Initialize log4j:
+               Log4jConfigure.configure(event);
+
+               LOG.info("webapp initialized");
+
+               // 2. Initiate secure login
+               SecureLogin.login();
+       }
+
+       /**
+        * Called before the webapp is about to go down
+        */
+       @Override
+       public void contextDestroyed(ServletContextEvent event) {
+               LOG.info("webapp about to go down");
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/ProtocolData.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/ProtocolData.java
 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/ProtocolData.java
index 1688982..39926ed 100644
--- 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/ProtocolData.java
+++ 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/ProtocolData.java
@@ -52,6 +52,7 @@ public class ProtocolData extends InputData {
     protected int port;
     protected String host;
     protected String token;
+    protected String user;
     // statistics parameters
     protected int statsMaxFragments;
     protected float statsSampleRatio;
@@ -105,10 +106,7 @@ public class ProtocolData extends InputData {
         metadata = getUserProperty("METADATA");
         dataSource = getProperty("DATA-DIR");
 
-        /* Kerberos token information */
-        if (UserGroupInformation.isSecurityEnabled()) {
-            token = getProperty("TOKEN");
-        }
+        parseSecurityProperties();
 
         parseFragmentMetadata();
         parseUserData();
@@ -165,6 +163,7 @@ public class ProtocolData extends InputData {
         this.remoteLogin = copy.remoteLogin;
         this.remoteSecret = copy.remoteSecret;
         this.token = copy.token;
+        this.user = copy.user;
         this.statsMaxFragments = copy.statsMaxFragments;
         this.statsSampleRatio = copy.statsSampleRatio;
     }
@@ -181,10 +180,7 @@ public class ProtocolData extends InputData {
         setProfilePlugins();
         metadata = getProperty("METADATA");
 
-        /* Kerberos token information */
-        if (UserGroupInformation.isSecurityEnabled()) {
-            token = getProperty("TOKEN");
-        }
+        parseSecurityProperties();
     }
 
     /**
@@ -325,6 +321,15 @@ public class ProtocolData extends InputData {
     }
 
     /**
+     * Returns identity of the end-user making the request.
+     *
+     * @return userid
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
      * Returns Kerberos token information.
      *
      * @return token
@@ -356,6 +361,20 @@ public class ProtocolData extends InputData {
         return statsSampleRatio;
     }
 
+    private void parseSecurityProperties() {
+        // obtain identity of the end-user -- mandatory only when 
impersonation is enabled
+        if (SecureLogin.isUserImpersonationEnabled()) {
+            this.user = getProperty("USER");
+        } else {
+            this.user = getOptionalProperty("USER");
+        }
+
+        /* Kerberos token information */
+        if (UserGroupInformation.isSecurityEnabled()) {
+            this.token = getProperty("TOKEN");
+        }
+    }
+
     /**
      * Sets the thread safe parameter. Default value - true.
      */

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/SecureLogin.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/SecureLogin.java
 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/SecureLogin.java
index 6ce05ed..3118676 100644
--- 
a/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/SecureLogin.java
+++ 
b/pxf/pxf-service/src/main/java/org/apache/hawq/pxf/service/utilities/SecureLogin.java
@@ -43,19 +43,36 @@ import org.apache.hadoop.security.SecurityUtil;
  */
 public class SecureLogin {
     private static final Log LOG = LogFactory.getLog(SecureLogin.class);
+
+    private static final String PROPERTY_KEY_USER_IMPERSONATION = 
"pxf.service.user.impersonation.enabled";
+
     private static final String CONFIG_KEY_SERVICE_KEYTAB = 
"pxf.service.kerberos.keytab";
     private static final String CONFIG_KEY_SERVICE_PRINCIPAL = 
"pxf.service.kerberos.principal";
 
+    /**
+     * Establishes Login Context for the PXF service principal using Kerberos 
keytab.
+     */
     public static void login() {
         try {
             Configuration config = new Configuration();
             config.addResource("pxf-site.xml");
 
-            SecurityUtil.login(config, CONFIG_KEY_SERVICE_KEYTAB,
-                    CONFIG_KEY_SERVICE_PRINCIPAL);
+            SecurityUtil.login(config, CONFIG_KEY_SERVICE_KEYTAB, 
CONFIG_KEY_SERVICE_PRINCIPAL);
+
+            LOG.info("User impersonation is " + (isUserImpersonationEnabled() 
? "enabled" : "disabled"));
+
         } catch (Exception e) {
             LOG.error("PXF service login failed");
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Returns whether user impersonation has been configured as enabled.
+     *
+     * @return true if user impersonation is enabled, false otherwise
+     */
+    public static boolean isUserImpersonationEnabled() {
+        return System.getProperty(PROPERTY_KEY_USER_IMPERSONATION, 
"").equalsIgnoreCase("true") ? true : false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/pxf/pxf-service/src/main/webapp/WEB-INF/web.xml 
b/pxf/pxf-service/src/main/webapp/WEB-INF/web.xml
index 4ba6629..8d033e1 100644
--- a/pxf/pxf-service/src/main/webapp/WEB-INF/web.xml
+++ b/pxf/pxf-service/src/main/webapp/WEB-INF/web.xml
@@ -59,8 +59,16 @@ under the License.
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
     <listener>
-          
<listener-class>org.apache.hawq.pxf.service.rest.ServletLifecycleListener</listener-class>
+       
<listener-class>org.apache.hawq.pxf.service.servlet.ServletLifecycleListener</listener-class>
     </listener>
+    <filter>
+        <filter-name>PXF Security Filter</filter-name>
+        
<filter-class>org.apache.hawq.pxf.service.servlet.SecurityServletFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>PXF Security Filter</filter-name>
+        <url-pattern>/@pxfProtocolVersion@/*</url-pattern>
+    </filter-mapping>
 
     <!-- log4j configuration 
          Log4jConfigListener looks for a file under log4jConfigLocation.

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/scripts/pxf-env.sh
----------------------------------------------------------------------
diff --git a/pxf/pxf-service/src/scripts/pxf-env.sh 
b/pxf/pxf-service/src/scripts/pxf-env.sh
index 0f24c79..c55105b 100644
--- a/pxf/pxf-service/src/scripts/pxf-env.sh
+++ b/pxf/pxf-service/src/scripts/pxf-env.sh
@@ -54,3 +54,6 @@ export HADOOP_DISTRO=${HADOOP_DISTRO}
 # Parent directory of Hadoop client installation (optional)
 # used in case of tarball-based installation when all clients are under a 
common parent directory
 export HADOOP_ROOT=${HADOOP_ROOT}
+
+# End-user identity impersonation, set to true to enable
+export PXF_USER_IMPERSONATION=@pxfDefaultUserImpersonation@

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/scripts/pxf-service
----------------------------------------------------------------------
diff --git a/pxf/pxf-service/src/scripts/pxf-service 
b/pxf/pxf-service/src/scripts/pxf-service
index 13fcf44..6113190 100644
--- a/pxf/pxf-service/src/scripts/pxf-service
+++ b/pxf/pxf-service/src/scripts/pxf-service
@@ -196,7 +196,7 @@ function configureWebapp()
     popd > /dev/null
 
     
context_file=${instance_root}/${instance_name}/webapps/pxf/META-INF/context.xml
-    sed -i -e 
"s:classpathFiles=\"[a-zA-Z0-9\/\;.-]*\":classpathFiles=\"${PXF_HOME}\/conf\/pxf-private.classpath\":"
 ${context_file} 
+    sed -i -e 
"s:classpathFiles=\"[a-zA-Z0-9\/\;.-]*\":classpathFiles=\"${PXF_HOME}\/conf\/pxf-private.classpath\":"
 ${context_file}
     sed -i -e 
"s:secondaryClasspathFiles=\"[a-zA-Z0-9\/\;.-]*\":secondaryClasspathFiles=\"${PXF_HOME}\/conf\/pxf-public.classpath\":"
 ${context_file}
 
     web_file=${instance_root}/${instance_name}/webapps/pxf/WEB-INF/web.xml
@@ -210,6 +210,7 @@ function configureWebapp()
     catalinaEnv=${instance_root}/${instance_name}/bin/setenv.sh
     sed -i -e "s|JVM_OPTS=.*$|JVM_OPTS=\"${PXF_JVM_OPTS}\"|g" ${catalinaEnv}
     sed -i -e "s|-Dpxf.log.dir=[^[:space:]^\"]*|-Dpxf.log.dir=${PXF_LOGDIR} 
|g" ${catalinaEnv}
+    sed -i -e 
"s|^[[:blank:]]*PXF_USER_IMPERSONATION=.*$|PXF_USER_IMPERSONATION=\"${PXF_USER_IMPERSONATION}\"|g"
 ${catalinaEnv}
     sed -i -e 
"s|^[[:blank:]]*CATALINA_PID=.*$|CATALINA_PID=${PXF_RUNDIR}/catalina.pid|g" 
${catalinaEnv}
     sed -i -e 
"s|^[[:blank:]]*CATALINA_OUT=.*$|CATALINA_OUT=${PXF_LOGDIR}/catalina.out|g" 
${catalinaEnv}
 

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/BridgeOutputBuilderTest.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/BridgeOutputBuilderTest.java
 
b/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/BridgeOutputBuilderTest.java
index 9dbd906..274d72d 100644
--- 
a/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/BridgeOutputBuilderTest.java
+++ 
b/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/BridgeOutputBuilderTest.java
@@ -469,6 +469,7 @@ public class BridgeOutputBuilderTest {
         parameters.put("X-GP-DATA-DIR", "i'm/ready/to/go");
         parameters.put("X-GP-FRAGMENT-METADATA", 
"U29tZXRoaW5nIGluIHRoZSB3YXk=");
         parameters.put("X-GP-OPTIONS-I'M-STANDING-HERE", "outside-your-door");
+        parameters.put("X-GP-USER", "alex");
 
         ProtocolData protocolData = new ProtocolData(parameters);
         BridgeOutputBuilder builder = new BridgeOutputBuilder(protocolData);

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/ProtocolDataTest.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/ProtocolDataTest.java
 
b/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/ProtocolDataTest.java
index 6cbd2f1..eddb9d7 100644
--- 
a/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/ProtocolDataTest.java
+++ 
b/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/ProtocolDataTest.java
@@ -45,7 +45,7 @@ import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ UserGroupInformation.class, ProfilesConf.class })
+@PrepareForTest({ UserGroupInformation.class, ProfilesConf.class, 
SecureLogin.class })
 public class ProtocolDataTest {
     Map<String, String> parameters;
 
@@ -67,8 +67,8 @@ public class ProtocolDataTest {
         assertEquals(protocolData.getAccessor(), "are");
         assertEquals(protocolData.getResolver(), "packed");
         assertEquals(protocolData.getDataSource(), "i'm/ready/to/go");
-        assertEquals(protocolData.getUserProperty("i'm-standing-here"),
-                "outside-your-door");
+        assertEquals(protocolData.getUserProperty("i'm-standing-here"), 
"outside-your-door");
+        assertEquals(protocolData.getUser(), "alex");
         assertEquals(protocolData.getParametersMap(), parameters);
         assertNull(protocolData.getLogin());
         assertNull(protocolData.getSecret());
@@ -205,7 +205,7 @@ public class ProtocolDataTest {
 
         try {
             new ProtocolData(parameters);
-            fail("null X-GP-TOKEN should throw");
+            fail("null X-GP-TOKEN should throw exception");
         } catch (IllegalArgumentException e) {
             assertEquals(e.getMessage(),
                     "Internal server error. Property \"TOKEN\" has no value in 
current request");
@@ -213,6 +213,26 @@ public class ProtocolDataTest {
     }
 
     @Test
+    public void nullUserThrowsWhenImpersonationEnabled() throws Exception {
+        when(SecureLogin.isUserImpersonationEnabled()).thenReturn(true);
+        parameters.remove("X-GP-USER");
+        try {
+            new ProtocolData(parameters);
+            fail("null X-GP-USER should throw exception");
+        } catch (IllegalArgumentException e) {
+            assertEquals(e.getMessage(),
+                    "Internal server error. Property \"USER\" has no value in 
current request");
+        }
+    }
+
+    @Test
+    public void nullUserDoesNotThrowWhenImpersonationDisabled() throws 
Exception {
+        parameters.remove("X-GP-USER");
+        ProtocolData protocolData = new ProtocolData(parameters);
+        assertNull(protocolData.getUser());
+    }
+
+    @Test
     public void filterUtf8() throws Exception {
         parameters.remove("X-GP-HAS-FILTER");
         parameters.put("X-GP-HAS-FILTER", "1");
@@ -430,8 +450,11 @@ public class ProtocolDataTest {
         parameters.put("X-GP-DATA-DIR", "i'm/ready/to/go");
         parameters.put("X-GP-FRAGMENT-METADATA", 
"U29tZXRoaW5nIGluIHRoZSB3YXk=");
         parameters.put("X-GP-OPTIONS-I'M-STANDING-HERE", "outside-your-door");
+        parameters.put("X-GP-USER", "alex");
 
         PowerMockito.mockStatic(UserGroupInformation.class);
+        PowerMockito.mockStatic(SecureLogin.class);
+
     }
 
     /*

http://git-wip-us.apache.org/repos/asf/incubator-hawq/blob/3612c9a3/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/SecureLoginTest.java
----------------------------------------------------------------------
diff --git 
a/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/SecureLoginTest.java
 
b/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/SecureLoginTest.java
new file mode 100644
index 0000000..3dc8970
--- /dev/null
+++ 
b/pxf/pxf-service/src/test/java/org/apache/hawq/pxf/service/utilities/SecureLoginTest.java
@@ -0,0 +1,65 @@
+package org.apache.hawq.pxf.service.utilities;
+
+/*
+ * 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.
+ */
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(PowerMockRunner.class)
+public class SecureLoginTest {
+
+    private String PROPERTY_KEY_USER_IMPERSONATION = 
"pxf.service.user.impersonation.enabled";
+
+    @Test
+    public void testImpersonationPropertyAbsent() {
+        System.clearProperty(PROPERTY_KEY_USER_IMPERSONATION);
+        assertFalse(SecureLogin.isUserImpersonationEnabled());
+    }
+
+    @Test
+    public void testImpersonationPropertyEmpty() {
+        System.setProperty(PROPERTY_KEY_USER_IMPERSONATION, "");
+        assertFalse(SecureLogin.isUserImpersonationEnabled());
+    }
+
+    @Test
+    public void testImpersonationPropertyFalse() {
+        System.setProperty(PROPERTY_KEY_USER_IMPERSONATION, "foo");
+        assertFalse(SecureLogin.isUserImpersonationEnabled());
+    }
+
+    @Test
+    public void testImpersonationPropertyTRUE() {
+        System.setProperty(PROPERTY_KEY_USER_IMPERSONATION, "TRUE");
+        assertTrue(SecureLogin.isUserImpersonationEnabled());
+    }
+
+    @Test
+    public void testImpersonationPropertyTrue() {
+        System.setProperty(PROPERTY_KEY_USER_IMPERSONATION, "true");
+        assertTrue(SecureLogin.isUserImpersonationEnabled());
+    }
+
+}
+

Reply via email to