zeroflag commented on code in PR #1043:
URL: https://github.com/apache/knox/pull/1043#discussion_r2104470497


##########
gateway-provider-identity-assertion-proxygroups/src/main/java/org/apache/knox/gateway/identityasserter/proxygroups/filter/ProxygroupIdentityAssertionFilter.java:
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.knox.gateway.identityasserter.proxygroups.filter;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import 
org.apache.knox.gateway.identityasserter.hadoop.groups.filter.HadoopGroupProviderFilter;
+import org.apache.knox.gateway.security.SubjectUtils;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.util.AuthFilterUtils;
+import org.apache.knox.gateway.util.AuthorizationException;
+import org.apache.knox.gateway.util.HttpExceptionUtils;
+
+import javax.security.auth.Subject;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
+import static 
org.apache.knox.gateway.util.AuthFilterUtils.DEFAULT_IMPERSONATION_MODE;
+import static org.apache.knox.gateway.util.AuthFilterUtils.IMPERSONATION_MODE;
+import static 
org.apache.knox.gateway.util.AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION;
+
+public class ProxygroupIdentityAssertionFilter extends 
HadoopGroupProviderFilter {
+    private static final ProxygroupProviderMessages LOG = 
MessagesFactory.get(ProxygroupProviderMessages.class);
+    private String impersonationMode = DEFAULT_IMPERSONATION_MODE;
+    private EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags;
+    private String topologyName;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        super.init(filterConfig);
+        topologyName = (String) 
filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+        impersonationFlags = 
AuthFilterUtils.getImpersonationEnabledFlags(filterConfig);
+        impersonationMode = filterConfig.getInitParameter(IMPERSONATION_MODE) 
!= null ? filterConfig.getInitParameter(IMPERSONATION_MODE) : 
DEFAULT_IMPERSONATION_MODE;
+
+        /* Add group impersonation provider */
+        if (!impersonationFlags.isEmpty() && 
impersonationFlags.contains(GROUP_IMPERSONATION)) {

Review Comment:
   Isn't this `isEmpty()` check redundant?



##########
gateway-provider-identity-assertion-proxygroups/src/main/java/org/apache/knox/gateway/identityasserter/proxygroups/filter/ProxygroupIdentityAssertionFilter.java:
##########
@@ -0,0 +1,167 @@
+/*
+ * 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.knox.gateway.identityasserter.proxygroups.filter;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import 
org.apache.knox.gateway.identityasserter.hadoop.groups.filter.HadoopGroupProviderFilter;
+import org.apache.knox.gateway.security.SubjectUtils;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.util.AuthFilterUtils;
+import org.apache.knox.gateway.util.AuthorizationException;
+import org.apache.knox.gateway.util.HttpExceptionUtils;
+
+import javax.security.auth.Subject;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
+import static 
org.apache.knox.gateway.util.AuthFilterUtils.DEFAULT_IMPERSONATION_MODE;
+import static org.apache.knox.gateway.util.AuthFilterUtils.IMPERSONATION_MODE;
+import static 
org.apache.knox.gateway.util.AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION;
+
+public class ProxygroupIdentityAssertionFilter extends 
HadoopGroupProviderFilter {
+    private static final ProxygroupProviderMessages LOG = 
MessagesFactory.get(ProxygroupProviderMessages.class);
+    private String impersonationMode = DEFAULT_IMPERSONATION_MODE;
+    private EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags;
+    private String topologyName;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        super.init(filterConfig);
+        topologyName = (String) 
filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+        impersonationFlags = 
AuthFilterUtils.getImpersonationEnabledFlags(filterConfig);
+        impersonationMode = filterConfig.getInitParameter(IMPERSONATION_MODE) 
!= null ? filterConfig.getInitParameter(IMPERSONATION_MODE) : 
DEFAULT_IMPERSONATION_MODE;
+
+        /* Add group impersonation provider */
+        if (!impersonationFlags.isEmpty() && 
impersonationFlags.contains(GROUP_IMPERSONATION)) {
+            final List<String> initParameterNames = 
AuthFilterUtils.getInitParameterNamesAsList(filterConfig);
+            String topologyName = (String) 
filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+            AuthFilterUtils.refreshProxyGroupsConfiguration(filterConfig, 
initParameterNames, topologyName, 
ProxygroupsIdentityAsserterDeploymentContributor.NAME);
+        }
+
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain)
+            throws IOException, ServletException {
+
+        final Subject subject = getSubject();
+        /*
+         * in case of user auth failure, mapped principal is same as real 
principal, this is so that
+         * group lookup works needed for group proxy auth check
+         */
+        String mappedPrincipalName = 
SubjectUtils.getEffectivePrincipalName(subject);
+
+        boolean isProxyUserAuthorized = false;
+        boolean isProxyGroupAuthorized = false;
+
+        /* Save exceptions if needed */
+        AuthorizationException userAuthorizationException = null;
+        AuthorizationException groupsAuthorizationException = null;
+
+        if 
(impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.USER_IMPERSONATION))
 {
+            try {
+                mappedPrincipalName = handleProxyUserImpersonation(request, 
subject);
+                isProxyUserAuthorized = true;
+            } catch (AuthorizationException e) {
+                LOG.hadoopAuthProxyUserFailed(e);
+                userAuthorizationException = e;
+            }
+        }
+
+        mappedPrincipalName = getMappedPrincipalName(request, 
mappedPrincipalName, subject);
+        String[] groups = getMappedGroups(request, mappedPrincipalName, 
subject);
+
+        if 
(impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))
 {
+            try {
+                mappedPrincipalName = handleProxyGroupImpersonation(request, 
subject, groups);
+                isProxyGroupAuthorized = true;
+            } catch (AuthorizationException e) {
+                LOG.hadoopAuthProxyGroupFailed(e);
+                groupsAuthorizationException = e;
+            }
+        }
+
+        // Determine if authorization succeeded based on the impersonation mode
+        /*
+         * Impersonation mode can only come into play when both proxy user and 
proxy group are enabled.
+         * For cases where only one of them is enabled, we set impersonation 
mode to OR and rely on either
+         * of the modes to succeed.
+         *
+         * This is the case where one of the impersonations is disabled but 
hadoop.impersonation.mode value is
+         * configured.
+         */
+        if (impersonationFlags.isEmpty() || 
(!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.USER_IMPERSONATION)
 || 
!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION)))
 {
+            impersonationMode = DEFAULT_IMPERSONATION_MODE;
+        }
+
+        boolean isAuthorized = "AND".equalsIgnoreCase(impersonationMode)
+                ? (isProxyUserAuthorized && isProxyGroupAuthorized)
+                : (isProxyUserAuthorized || isProxyGroupAuthorized);
+
+        if (isAuthorized) {
+            
LOG.hadoopAuthProxyAccessSuccess(SubjectUtils.getEffectivePrincipalName(subject),
 request.getParameter(AuthFilterUtils.QUERY_PARAMETER_DOAS), impersonationMode);
+        } else {
+            // Handle authorization failures
+            if (groupsAuthorizationException != null) {
+                LOG.hadoopAuthProxyGroupFailed(groupsAuthorizationException);
+                
HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) 
response, HttpServletResponse.SC_FORBIDDEN, groupsAuthorizationException);
+                return;
+            } else if (userAuthorizationException != null) {
+                LOG.hadoopAuthProxyGroupFailed(groupsAuthorizationException);
+                
HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) 
response, HttpServletResponse.SC_FORBIDDEN, userAuthorizationException);
+                return;
+            } else {
+                AuthorizationException e = new AuthorizationException("User: " 
+ SubjectUtils.getEffectivePrincipalName(subject) + " is not allowed to 
impersonate." + "Proxyuser auth result: " + isProxyUserAuthorized + " 
Proxygroup auth result: " + isProxyGroupAuthorized);
+                LOG.hadoopAuthProxyGroupFailed(e);
+                
HttpExceptionUtils.createServletExceptionResponse((HttpServletResponse) 
response, HttpServletResponse.SC_FORBIDDEN, e);
+                return;
+            }
+        }
+
+        HttpServletRequestWrapper wrapper = wrapHttpServletRequest(request, 
mappedPrincipalName);
+        continueChainAsPrincipal(wrapper, response, chain, 
mappedPrincipalName, unique(groups));
+    }
+
+    private String handleProxyGroupImpersonation(final ServletRequest request, 
final Subject subject, String[] groups) throws AuthorizationException {
+        String principalName = SubjectUtils.getEffectivePrincipalName(subject);
+        if (!impersonationFlags.isEmpty() && 
impersonationFlags.contains(GROUP_IMPERSONATION)) {

Review Comment:
   Isn't this `isEmpty()` check redundant?



##########
gateway-spi/src/main/java/org/apache/knox/gateway/util/GroupBasedImpersonationProvider.java:
##########
@@ -0,0 +1,228 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.util;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.DefaultImpersonationProvider;
+import org.apache.hadoop.util.MachineList;
+import org.apache.knox.gateway.i18n.GatewaySpiMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * An extension of Hadoop's DefaultImpersonationProvider that adds support for 
group-based impersonation.
+ * This provider allows users who belong to specific groups to impersonate 
other users.
+ */
+public class GroupBasedImpersonationProvider extends 
DefaultImpersonationProvider {
+    private static final GatewaySpiMessages LOG = 
MessagesFactory.get(GatewaySpiMessages.class);
+    private static final String CONF_HOSTS = ".hosts";
+    private static final String CONF_USERS = ".users";
+    private static final String CONF_GROUPS = ".groups";
+    private final Map<String, AccessControlList> proxyGroupsAcls = new 
HashMap<>();
+    EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags = 
EnumSet.noneOf(AuthFilterUtils.ImpersonationFlags.class);
+    private Map<String, MachineList> groupProxyHosts = new HashMap<>();
+    private String groupConfigPrefix;
+
+    public 
GroupBasedImpersonationProvider(EnumSet<AuthFilterUtils.ImpersonationFlags> 
impersonationFlags) {
+        super();
+        this.impersonationFlags = impersonationFlags;
+    }
+
+    @Override
+    public Configuration getConf() {
+        return super.getConf();
+    }
+
+    @Override
+    public void setConf(Configuration conf) {
+        super.setConf(conf);
+    }
+
+    @Override
+    public void init(String proxyGroupPrefix) {
+        initGroupBasedProvider(proxyGroupPrefix);
+    }
+
+    private void initGroupBasedProvider(String proxyGroupPrefix) {
+        groupConfigPrefix = proxyGroupPrefix +
+                (proxyGroupPrefix.endsWith(".") ? "" : ".");
+
+        // constructing regex to match the following patterns:
+        //   $configPrefix.[ANY].users
+        //   $configPrefix.[ANY].groups
+        //   $configPrefix.[ANY].hosts
+        //
+        String prefixRegEx = groupConfigPrefix.replace(".", "\\.");
+        String usersGroupsRegEx = prefixRegEx + "[\\S]*(" +
+                Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + 
")";
+        String hostsRegEx = prefixRegEx + "[\\S]*" + Pattern.quote(CONF_HOSTS);
+
+        // get list of users and groups per proxygroup
+        // Map of <hadoop.proxygroup.[VIRTUAL_GROUP].users|groups, 
group1,group2>
+        Map<String, String> allMatchKeys =
+                getConf().getValByRegex(usersGroupsRegEx);
+
+        for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+            //aclKey = hadoop.proxygroup.[VIRTUAL_GROUP]
+            String aclKey = getAclKey(entry.getKey());
+
+            if (!proxyGroupsAcls.containsKey(aclKey)) {
+                proxyGroupsAcls.put(aclKey, new AccessControlList(
+                        allMatchKeys.get(aclKey + CONF_USERS),
+                        allMatchKeys.get(aclKey + CONF_GROUPS)));
+            }
+        }
+
+        // get hosts per proxygroup
+        allMatchKeys = getConf().getValByRegex(hostsRegEx);
+        for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+            groupProxyHosts.put(entry.getKey(),
+                    new MachineList(entry.getValue()));
+        }
+    }
+
+    private String getAclKey(String key) {
+        int endIndex = key.lastIndexOf('.');
+        if (endIndex != -1) {
+            return key.substring(0, endIndex);
+        }
+        return key;
+    }
+
+    /**
+     * Authorization based on groups that are already in Subject
+     *
+     * @param user the user information attempting the operation, which 
includes the real
+     *             user and the effective impersonated user.
+     * @param remoteAddress the remote address from which the user is 
connecting.
+     * @throws AuthorizationException if the user is not authorized based on 
the
+     *                                configured impersonation and group 
policies.
+     */
+    @Override
+    public void authorize(UserGroupInformation user, InetAddress 
remoteAddress) throws AuthorizationException {
+        // If both authorization methods are disabled, allow the operation to 
proceed
+        if (impersonationFlags.isEmpty() || 
!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))
 {
+            LOG.successfulImpersonation(user.getRealUser().getUserName(), 
user.getUserName());
+            return;
+        }
+        checkProxyGroupAuthorization(user, remoteAddress, 
Collections.emptyList());
+    }
+
+    /**
+     * Authorization based on groups that are provided as a function agument
+     *
+     * @param user the user information attempting the operation, which 
includes the real
+     *             user and the effective impersonated user.
+     * @param groups the list of groups to check for authorization.
+     * @param remoteAddress the remote address from which the user is 
connecting.
+     * @throws AuthorizationException if the user is not authorized based on 
the
+     *                                configured impersonation and group 
policies.
+     */
+    public void authorize(UserGroupInformation user, InetAddress 
remoteAddress, List<String> groups) throws AuthorizationException {
+        // If both authorization methods are disabled, allow the operation to 
proceed
+        if (impersonationFlags.isEmpty() || 
!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))
 {

Review Comment:
   Isn't this `isEmpty()` check redundant?



##########
gateway-spi/src/main/java/org/apache/knox/gateway/util/GroupBasedImpersonationProvider.java:
##########
@@ -0,0 +1,228 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.util;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.DefaultImpersonationProvider;
+import org.apache.hadoop.util.MachineList;
+import org.apache.knox.gateway.i18n.GatewaySpiMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * An extension of Hadoop's DefaultImpersonationProvider that adds support for 
group-based impersonation.
+ * This provider allows users who belong to specific groups to impersonate 
other users.
+ */
+public class GroupBasedImpersonationProvider extends 
DefaultImpersonationProvider {
+    private static final GatewaySpiMessages LOG = 
MessagesFactory.get(GatewaySpiMessages.class);
+    private static final String CONF_HOSTS = ".hosts";
+    private static final String CONF_USERS = ".users";
+    private static final String CONF_GROUPS = ".groups";
+    private final Map<String, AccessControlList> proxyGroupsAcls = new 
HashMap<>();
+    EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags = 
EnumSet.noneOf(AuthFilterUtils.ImpersonationFlags.class);
+    private Map<String, MachineList> groupProxyHosts = new HashMap<>();
+    private String groupConfigPrefix;
+
+    public 
GroupBasedImpersonationProvider(EnumSet<AuthFilterUtils.ImpersonationFlags> 
impersonationFlags) {
+        super();
+        this.impersonationFlags = impersonationFlags;
+    }
+
+    @Override
+    public Configuration getConf() {
+        return super.getConf();
+    }
+
+    @Override
+    public void setConf(Configuration conf) {
+        super.setConf(conf);
+    }
+
+    @Override
+    public void init(String proxyGroupPrefix) {
+        initGroupBasedProvider(proxyGroupPrefix);
+    }
+
+    private void initGroupBasedProvider(String proxyGroupPrefix) {
+        groupConfigPrefix = proxyGroupPrefix +
+                (proxyGroupPrefix.endsWith(".") ? "" : ".");
+
+        // constructing regex to match the following patterns:
+        //   $configPrefix.[ANY].users
+        //   $configPrefix.[ANY].groups
+        //   $configPrefix.[ANY].hosts
+        //
+        String prefixRegEx = groupConfigPrefix.replace(".", "\\.");
+        String usersGroupsRegEx = prefixRegEx + "[\\S]*(" +
+                Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + 
")";
+        String hostsRegEx = prefixRegEx + "[\\S]*" + Pattern.quote(CONF_HOSTS);
+
+        // get list of users and groups per proxygroup
+        // Map of <hadoop.proxygroup.[VIRTUAL_GROUP].users|groups, 
group1,group2>
+        Map<String, String> allMatchKeys =
+                getConf().getValByRegex(usersGroupsRegEx);
+
+        for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+            //aclKey = hadoop.proxygroup.[VIRTUAL_GROUP]
+            String aclKey = getAclKey(entry.getKey());
+
+            if (!proxyGroupsAcls.containsKey(aclKey)) {
+                proxyGroupsAcls.put(aclKey, new AccessControlList(
+                        allMatchKeys.get(aclKey + CONF_USERS),
+                        allMatchKeys.get(aclKey + CONF_GROUPS)));
+            }
+        }
+
+        // get hosts per proxygroup
+        allMatchKeys = getConf().getValByRegex(hostsRegEx);
+        for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+            groupProxyHosts.put(entry.getKey(),
+                    new MachineList(entry.getValue()));
+        }
+    }
+
+    private String getAclKey(String key) {
+        int endIndex = key.lastIndexOf('.');
+        if (endIndex != -1) {
+            return key.substring(0, endIndex);
+        }
+        return key;
+    }
+
+    /**
+     * Authorization based on groups that are already in Subject
+     *
+     * @param user the user information attempting the operation, which 
includes the real
+     *             user and the effective impersonated user.
+     * @param remoteAddress the remote address from which the user is 
connecting.
+     * @throws AuthorizationException if the user is not authorized based on 
the
+     *                                configured impersonation and group 
policies.
+     */
+    @Override
+    public void authorize(UserGroupInformation user, InetAddress 
remoteAddress) throws AuthorizationException {
+        // If both authorization methods are disabled, allow the operation to 
proceed
+        if (impersonationFlags.isEmpty() || 
!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))
 {

Review Comment:
   Isn't this `isEmpty()` check redundant?



##########
gateway-spi/src/main/java/org/apache/knox/gateway/util/GroupBasedImpersonationProvider.java:
##########
@@ -0,0 +1,228 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.util;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.DefaultImpersonationProvider;
+import org.apache.hadoop.util.MachineList;
+import org.apache.knox.gateway.i18n.GatewaySpiMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * An extension of Hadoop's DefaultImpersonationProvider that adds support for 
group-based impersonation.
+ * This provider allows users who belong to specific groups to impersonate 
other users.
+ */
+public class GroupBasedImpersonationProvider extends 
DefaultImpersonationProvider {
+    private static final GatewaySpiMessages LOG = 
MessagesFactory.get(GatewaySpiMessages.class);
+    private static final String CONF_HOSTS = ".hosts";
+    private static final String CONF_USERS = ".users";
+    private static final String CONF_GROUPS = ".groups";
+    private final Map<String, AccessControlList> proxyGroupsAcls = new 
HashMap<>();
+    EnumSet<AuthFilterUtils.ImpersonationFlags> impersonationFlags = 
EnumSet.noneOf(AuthFilterUtils.ImpersonationFlags.class);
+    private Map<String, MachineList> groupProxyHosts = new HashMap<>();
+    private String groupConfigPrefix;
+
+    public 
GroupBasedImpersonationProvider(EnumSet<AuthFilterUtils.ImpersonationFlags> 
impersonationFlags) {
+        super();
+        this.impersonationFlags = impersonationFlags;
+    }
+
+    @Override
+    public Configuration getConf() {
+        return super.getConf();
+    }
+
+    @Override
+    public void setConf(Configuration conf) {
+        super.setConf(conf);
+    }
+
+    @Override
+    public void init(String proxyGroupPrefix) {
+        initGroupBasedProvider(proxyGroupPrefix);
+    }
+
+    private void initGroupBasedProvider(String proxyGroupPrefix) {
+        groupConfigPrefix = proxyGroupPrefix +
+                (proxyGroupPrefix.endsWith(".") ? "" : ".");
+
+        // constructing regex to match the following patterns:
+        //   $configPrefix.[ANY].users
+        //   $configPrefix.[ANY].groups
+        //   $configPrefix.[ANY].hosts
+        //
+        String prefixRegEx = groupConfigPrefix.replace(".", "\\.");
+        String usersGroupsRegEx = prefixRegEx + "[\\S]*(" +
+                Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + 
")";
+        String hostsRegEx = prefixRegEx + "[\\S]*" + Pattern.quote(CONF_HOSTS);
+
+        // get list of users and groups per proxygroup
+        // Map of <hadoop.proxygroup.[VIRTUAL_GROUP].users|groups, 
group1,group2>
+        Map<String, String> allMatchKeys =
+                getConf().getValByRegex(usersGroupsRegEx);
+
+        for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+            //aclKey = hadoop.proxygroup.[VIRTUAL_GROUP]
+            String aclKey = getAclKey(entry.getKey());
+
+            if (!proxyGroupsAcls.containsKey(aclKey)) {
+                proxyGroupsAcls.put(aclKey, new AccessControlList(
+                        allMatchKeys.get(aclKey + CONF_USERS),
+                        allMatchKeys.get(aclKey + CONF_GROUPS)));
+            }
+        }
+
+        // get hosts per proxygroup
+        allMatchKeys = getConf().getValByRegex(hostsRegEx);
+        for (Map.Entry<String, String> entry : allMatchKeys.entrySet()) {
+            groupProxyHosts.put(entry.getKey(),
+                    new MachineList(entry.getValue()));
+        }
+    }
+
+    private String getAclKey(String key) {
+        int endIndex = key.lastIndexOf('.');
+        if (endIndex != -1) {
+            return key.substring(0, endIndex);
+        }
+        return key;
+    }
+
+    /**
+     * Authorization based on groups that are already in Subject
+     *
+     * @param user the user information attempting the operation, which 
includes the real
+     *             user and the effective impersonated user.
+     * @param remoteAddress the remote address from which the user is 
connecting.
+     * @throws AuthorizationException if the user is not authorized based on 
the
+     *                                configured impersonation and group 
policies.
+     */
+    @Override
+    public void authorize(UserGroupInformation user, InetAddress 
remoteAddress) throws AuthorizationException {
+        // If both authorization methods are disabled, allow the operation to 
proceed
+        if (impersonationFlags.isEmpty() || 
!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))
 {
+            LOG.successfulImpersonation(user.getRealUser().getUserName(), 
user.getUserName());
+            return;
+        }
+        checkProxyGroupAuthorization(user, remoteAddress, 
Collections.emptyList());
+    }
+
+    /**
+     * Authorization based on groups that are provided as a function agument
+     *
+     * @param user the user information attempting the operation, which 
includes the real
+     *             user and the effective impersonated user.
+     * @param groups the list of groups to check for authorization.
+     * @param remoteAddress the remote address from which the user is 
connecting.
+     * @throws AuthorizationException if the user is not authorized based on 
the
+     *                                configured impersonation and group 
policies.
+     */
+    public void authorize(UserGroupInformation user, InetAddress 
remoteAddress, List<String> groups) throws AuthorizationException {
+        // If both authorization methods are disabled, allow the operation to 
proceed
+        if (impersonationFlags.isEmpty() || 
!impersonationFlags.contains(AuthFilterUtils.ImpersonationFlags.GROUP_IMPERSONATION))
 {
+            LOG.successfulImpersonation(user.getRealUser().getUserName(), 
user.getUserName());
+            return;
+        }
+        checkProxyGroupAuthorization(user, remoteAddress, groups);
+    }
+
+    /**
+     * Helper method to check if the group a given user belongs to is 
authorized to impersonate
+     * Returns true if the user is authorized, false otherwise.
+     *
+     * @param user
+     * @param remoteAddress
+     * @return
+     */
+    private void checkProxyGroupAuthorization(final UserGroupInformation user, 
final InetAddress remoteAddress, List<String> groups) throws 
AuthorizationException {
+        if (user == null) {
+            throw new IllegalArgumentException("user is null.");
+        }
+
+        final UserGroupInformation realUser = user.getRealUser();
+        if (realUser == null) {
+            return;
+        }
+
+        // Get the real user's groups (both real and virtual)
+        Set<String> realUserGroups = new HashSet<>();
+        /* Add provided groups */
+        if(groups != null && !groups.isEmpty()) {
+            realUserGroups.addAll(groups);
+        }
+        /* Add groups from subject */
+        if (user.getRealUser().getGroupNames() != null) {
+            Collections.addAll(realUserGroups, 
user.getRealUser().getGroupNames());
+        }
+
+        boolean proxyGroupFound = false;
+        // Check if any of the real user's groups have permission to 
impersonate the proxy user
+        for (String group : realUserGroups) {
+            final AccessControlList acl = 
proxyGroupsAcls.get(groupConfigPrefix +
+                    group);
+
+            if (acl == null || !acl.isUserAllowed(user)) {
+                continue;
+            } else {
+                proxyGroupFound = true;
+                break;
+            }
+        }
+
+        if (!proxyGroupFound) {
+            LOG.failedToImpersonateGroups(realUser.getUserName(), 
realUserGroups.toString(), user.getUserName());
+            throw new AuthorizationException("User: " + realUser.getUserName()
+                    + " with groups " + realUserGroups.toString()
+                    + " is not allowed to impersonate " + user.getUserName());
+        }
+
+        boolean proxyGroupHostFound = false;
+        for (final String group : realUserGroups) {
+            final MachineList machineList = 
groupProxyHosts.get(groupConfigPrefix + group + CONF_HOSTS);
+
+            if (machineList == null || !machineList.includes(remoteAddress)) {
+                continue;
+            } else {
+                proxyGroupHostFound = true;
+                break;
+            }
+        }
+
+        if (!proxyGroupHostFound) {
+            LOG.failedToImpersonateGroupsFromAddress(realUser.getUserName(), 
realUserGroups.toString(), user.getUserName(), remoteAddress.toString());
+            throw new AuthorizationException("User: " + realUser.getUserName()
+                    + "with groups " + realUserGroups.toString()

Review Comment:
   Missing space before `"with groups`



##########
gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java:
##########
@@ -42,212 +45,307 @@
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 
 public class AuthFilterUtils {
-  public static final String DEFAULT_AUTH_UNAUTHENTICATED_PATHS_PARAM = 
"/knoxtoken/api/v1/jwks.json";
-  public static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
-  public static final String QUERY_PARAMETER_DOAS = "doAs";
-  public static final String REAL_USER_NAME_ATTRIBUTE = "real.user.name";
-  public static final String DO_GLOBAL_LOGOUT_ATTRIBUTE = "do.global.logout";
-
-  private static final GatewaySpiMessages LOG = 
MessagesFactory.get(GatewaySpiMessages.class);
-  private static final Map<String, Map<String, ImpersonationProvider>> 
TOPOLOGY_IMPERSONATION_PROVIDERS = new ConcurrentHashMap<>();
-  private static final Lock refreshSuperUserGroupsLock = new ReentrantLock();
-
-  /**
-   * A helper method that checks whether request contains
-   * unauthenticated path
-   * @param request
-   * @return
-   */
-  public static boolean doesRequestContainUnauthPath(
-      final Set<String> unAuthenticatedPaths, final ServletRequest request) {
-    /* make sure the path matches EXACTLY to prevent auth bypass */
-    return unAuthenticatedPaths.contains(((HttpServletRequest) 
request).getPathInfo());
-  }
-
-  /**
-   * A helper method that parses a string and adds to the
-   * provided unauthenticated set.
-   * @param unAuthenticatedPaths
-   * @param list
-   */
-  public static void parseStringThenAdd(final Set<String> 
unAuthenticatedPaths, final String list) {
-    final StringTokenizer tokenizer = new StringTokenizer(list, ";,");
-    while (tokenizer.hasMoreTokens()) {
-      unAuthenticatedPaths.add(tokenizer.nextToken());
-    }
-  }
-
-  /**
-   * A method that parses a string (delimiters = ;,) and adds them to the
-   * provided un-authenticated path set.
-   * @param unAuthenticatedPaths
-   * @param list
-   * @param defaultList
-   */
-  public static void addUnauthPaths(final Set<String> unAuthenticatedPaths, 
final String list, final String defaultList) {
-    /* add default unauthenticated paths list */
-    parseStringThenAdd(unAuthenticatedPaths, defaultList);
-    /* add provided unauthenticated paths list if specified */
-    if (!StringUtils.isBlank(list)) {
-      AuthFilterUtils.parseStringThenAdd(unAuthenticatedPaths, list);
-    }
-  }
-
-  public static void refreshSuperUserGroupsConfiguration(ServletContext 
context, List<String> initParameterNames, String topologyName, String role) {
-    if (context == null) {
-      throw new IllegalArgumentException("Cannot get proxyuser configuration 
from NULL context");
-    }
-    refreshSuperUserGroupsConfiguration(context, null, initParameterNames, 
topologyName, role);
-  }
-
-  public static void refreshSuperUserGroupsConfiguration(FilterConfig 
filterConfig, List<String> initParameterNames, String topologyName, String 
role) {
-    if (filterConfig == null) {
-      throw new IllegalArgumentException("Cannot get proxyuser configuration 
from NULL filter config");
-    }
-    refreshSuperUserGroupsConfiguration(null, filterConfig, 
initParameterNames, topologyName, role);
-  }
-
-  private static void refreshSuperUserGroupsConfiguration(ServletContext 
context, FilterConfig filterConfig, List<String> initParameterNames, String 
topologyName, String role) {
-    final Configuration conf = new Configuration(false);
-    if (initParameterNames != null) {
-      initParameterNames.stream().filter(name -> 
name.startsWith(PROXYUSER_PREFIX + ".")).forEach(name -> {
-        String value = context == null ? filterConfig.getInitParameter(name) : 
context.getInitParameter(name);
-        conf.set(name, value);
-      });
-    }
-
-    saveImpersonationProvider(topologyName, role, conf);
-  }
-
-  private static void saveImpersonationProvider(String topologyName, String 
role, final Configuration conf) {
-    refreshSuperUserGroupsLock.lock();
-    try {
-      final ImpersonationProvider impersonationProvider = new 
DefaultImpersonationProvider();
-      impersonationProvider.setConf(conf);
-      impersonationProvider.init(PROXYUSER_PREFIX);
-      LOG.createImpersonationProvider(topologyName, role, PROXYUSER_PREFIX, 
conf.getPropsWithPrefix(PROXYUSER_PREFIX + ".").toString());
-      TOPOLOGY_IMPERSONATION_PROVIDERS.putIfAbsent(topologyName, new 
ConcurrentHashMap<String, ImpersonationProvider>());
-      TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).put(role, 
impersonationProvider);
-    } finally {
-      refreshSuperUserGroupsLock.unlock();
-    }
-  }
-
-  public static HttpServletRequest getProxyRequest(HttpServletRequest request, 
String doAsUser, String topologyName, String role) throws 
AuthorizationException {
-    return getProxyRequest(request, request.getUserPrincipal().getName(), 
doAsUser, topologyName, role);
-  }
-
-  public static HttpServletRequest getProxyRequest(HttpServletRequest request, 
String remoteUser, String doAsUser, String topologyName, String role) throws 
AuthorizationException {
-    final UserGroupInformation remoteRequestUgi = 
getRemoteRequestUgi(remoteUser, doAsUser);
-    if (remoteRequestUgi != null) {
-      authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, 
role);
-
-      return new HttpServletRequestWrapper(request) {
-        @Override
-        public String getRemoteUser() {
-          return remoteRequestUgi.getShortUserName();
-        }
-
-        @Override
-        public Principal getUserPrincipal() {
-          return remoteRequestUgi::getUserName;
-        }
-
-        @Override
-        public Object getAttribute(String name) {
-          if (name != null && name.equals(REAL_USER_NAME_ATTRIBUTE)) {
-            return remoteRequestUgi.getRealUser().getShortUserName();
-          } else {
-            return super.getAttribute(name);
-          }
-        }
-      };
-
-    }
-    return null;
-  }
-
-  public static void authorizeImpersonationRequest(HttpServletRequest request, 
String remoteUser, String doAsUser, String topologyName, String role) throws 
AuthorizationException {
-    final UserGroupInformation remoteRequestUgi = 
getRemoteRequestUgi(remoteUser, doAsUser);
-    if (remoteRequestUgi != null) {
-      authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, 
role);
-    }
-  }
-
-  private static void authorizeImpersonationRequest(HttpServletRequest 
request, UserGroupInformation remoteRequestUgi, String topologyName, String 
role)
-      throws AuthorizationException {
-
-    final ImpersonationProvider impersonationProvider = 
getImpersonationProvider(topologyName, role);
-
-    if (impersonationProvider != null) {
-      try {
-        impersonationProvider.authorize(remoteRequestUgi, 
request.getRemoteAddr());
-      } catch (org.apache.hadoop.security.authorize.AuthorizationException e) {
-        throw new AuthorizationException(e);
-      }
-    } else {
-      throw new AuthorizationException("ImpersonationProvider for " + 
topologyName + " / " + role + " not found!");
-    }
-  }
-
-  private static ImpersonationProvider getImpersonationProvider(String 
topologyName, String role) {
-    refreshSuperUserGroupsLock.lock();
-    final ImpersonationProvider impersonationProvider;
-    try {
-      impersonationProvider = 
(TOPOLOGY_IMPERSONATION_PROVIDERS.getOrDefault(topologyName, 
Collections.emptyMap())).get(role);
-    } finally {
-      refreshSuperUserGroupsLock.unlock();
-    }
-    return impersonationProvider;
-  }
-
-  private static UserGroupInformation getRemoteRequestUgi(String remoteUser, 
String doAsUser) {
-    if (remoteUser != null) {
-      final UserGroupInformation remoteUserUgi = 
UserGroupInformation.createRemoteUser(remoteUser);
-      return UserGroupInformation.createProxyUser(doAsUser, remoteUserUgi);
-    }
-    return null;
-  }
-
-  public static boolean hasProxyConfig(String topologyName, String role) {
-    return getImpersonationProvider(topologyName, role) != null;
-  }
-
-  public static void removeProxyUserConfig(String topologyName, String role) {
-    if (hasProxyConfig(topologyName, role)) {
-      refreshSuperUserGroupsLock.lock();
-      try {
-        TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).remove(role);
-      } finally {
-        refreshSuperUserGroupsLock.unlock();
-      }
-    }
-  }
-
-  /**
-   * FilterConfig.getInitParameters() returns an enumeration and the first 
time we
-   * iterate thru on its elements we can process the parameter names as desired
-   * (because hasMoreElements returns true). The subsequent calls, however, 
will not
-   * succeed because getInitParameters() returns the same object where the
-   * hasMoreElements returns false.
-   * <p>
-   * In classes where there are multiple iterations should be conducted, a
-   * collection should be used instead.
-   *
-   * @return the names of the filter's initialization parameters as a List of
-   *         String objects, or an empty List if the filter has no 
initialization
-   *         parameters.
-   */
-  public static List<String> getInitParameterNamesAsList(FilterConfig 
filterConfig) {
-    return filterConfig.getInitParameterNames() == null ? 
Collections.emptyList() : 
Collections.list(filterConfig.getInitParameterNames());
-  }
-
-  public static void markDoGlobalLogoutInRequest(HttpServletRequest request) {
-    request.setAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE, "true");
-  }
-
-  public static boolean shouldDoGlobalLogout(HttpServletRequest request) {
-    return request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE) == null ? false : 
Boolean.parseBoolean((String) request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE));
-  }
+    public static final String DEFAULT_AUTH_UNAUTHENTICATED_PATHS_PARAM = 
"/knoxtoken/api/v1/jwks.json";
+    public static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
+    public static final String QUERY_PARAMETER_DOAS = "doAs";
+    public static final String REAL_USER_NAME_ATTRIBUTE = "real.user.name";
+    public static final String DO_GLOBAL_LOGOUT_ATTRIBUTE = "do.global.logout";
+
+    public static final String PROXYGROUP_PREFIX = "hadoop.proxygroup";
+    public static final String IMPERSONATION_MODE = 
"hadoop.impersonation.mode";
+    public static final String DEFAULT_IMPERSONATION_MODE = "OR";
+    public static final String IMPERSONATION_ENABLED_PARAM = 
AuthFilterUtils.PROXYUSER_PREFIX + ".impersonation.enabled";
+    public static final String GROUP_IMPERSONATION_ENABLED_PARAM = 
AuthFilterUtils.PROXYGROUP_PREFIX + ".impersonation.enabled";
+
+    private static final GatewaySpiMessages LOG = 
MessagesFactory.get(GatewaySpiMessages.class);
+    private static final Map<String, Map<String, ImpersonationProvider>> 
TOPOLOGY_IMPERSONATION_PROVIDERS = new ConcurrentHashMap<>();
+    private static final Lock refreshSuperUserGroupsLock = new ReentrantLock();
+
+    /**
+     * Represents the modes of impersonation that can be configured and used
+     * within the authentication process.
+     *
+     * USER_IMPERSONATION:
+     * Indicates that the impersonation process is based on a user identity.
+     * This is typically used when one user needs to act on behalf of another.
+     *
+     * GROUP_IMPERSONATION:
+     * Represents group-based impersonation where actions can be performed
+     * based on group roles or permissions.
+     */
+    public enum ImpersonationFlags {
+        USER_IMPERSONATION, GROUP_IMPERSONATION
+    }
+
+
+    /**
+     * A helper method that checks whether request contains
+     * unauthenticated path
+     * @param request
+     * @return
+     */
+    public static boolean doesRequestContainUnauthPath(
+            final Set<String> unAuthenticatedPaths, final ServletRequest 
request) {
+        /* make sure the path matches EXACTLY to prevent auth bypass */
+        return unAuthenticatedPaths.contains(((HttpServletRequest) 
request).getPathInfo());
+    }
+
+    /**
+     * A helper method that parses a string and adds to the
+     * provided unauthenticated set.
+     * @param unAuthenticatedPaths
+     * @param list
+     */
+    public static void parseStringThenAdd(final Set<String> 
unAuthenticatedPaths, final String list) {
+        final StringTokenizer tokenizer = new StringTokenizer(list, ";,");
+        while (tokenizer.hasMoreTokens()) {
+            unAuthenticatedPaths.add(tokenizer.nextToken());
+        }
+    }
+
+    /**
+     * A method that parses a string (delimiters = ;,) and adds them to the
+     * provided un-authenticated path set.
+     * @param unAuthenticatedPaths
+     * @param list
+     * @param defaultList
+     */
+    public static void addUnauthPaths(final Set<String> unAuthenticatedPaths, 
final String list, final String defaultList) {
+        /* add default unauthenticated paths list */
+        parseStringThenAdd(unAuthenticatedPaths, defaultList);
+        /* add provided unauthenticated paths list if specified */
+        if (!StringUtils.isBlank(list)) {
+            AuthFilterUtils.parseStringThenAdd(unAuthenticatedPaths, list);
+        }
+    }
+
+    public static void refreshSuperUserGroupsConfiguration(ServletContext 
context, List<String> initParameterNames, String topologyName, String role) {
+        if (context == null) {
+            throw new IllegalArgumentException("Cannot get proxyuser 
configuration from NULL context");
+        }
+        refreshSuperUserGroupsConfiguration(context, null, initParameterNames, 
topologyName, role);
+    }
+
+    public static void refreshSuperUserGroupsConfiguration(FilterConfig 
filterConfig, List<String> initParameterNames, String topologyName, String 
role) {
+        if (filterConfig == null) {
+            throw new IllegalArgumentException("Cannot get proxyuser 
configuration from NULL filter config");
+        }
+        refreshSuperUserGroupsConfiguration(null, filterConfig, 
initParameterNames, topologyName, role);
+    }
+
+    private static void refreshSuperUserGroupsConfiguration(ServletContext 
context, FilterConfig filterConfig, List<String> initParameterNames, String 
topologyName, String role) {
+        final Configuration conf = new Configuration(false);
+        if (initParameterNames != null) {
+            initParameterNames.stream().filter(name -> 
name.startsWith(PROXYUSER_PREFIX + ".")).forEach(name -> {
+                String value = context == null ? 
filterConfig.getInitParameter(name) : context.getInitParameter(name);
+                conf.set(name, value);
+            });
+        }
+
+        saveImpersonationProvider(topologyName, role, conf, new 
DefaultImpersonationProvider(), PROXYUSER_PREFIX);
+    }
+
+    /* For proxy groups */
+    public static void refreshProxyGroupsConfiguration(FilterConfig 
filterConfig, List<String> initParameterNames, String topologyName, String 
role) {
+        if (filterConfig == null) {
+            throw new IllegalArgumentException("Cannot get proxyuser 
configuration from NULL filter config");
+        }
+        refreshProxyGroupsConfiguration(null, filterConfig, 
initParameterNames, topologyName, role);
+    }
+
+    private static void refreshProxyGroupsConfiguration(ServletContext 
context, FilterConfig filterConfig, List<String> initParameterNames, String 
topologyName, String role) {
+        final Configuration conf = new Configuration(false);
+        if (initParameterNames != null) {
+            initParameterNames.stream().filter(name -> 
name.startsWith(PROXYGROUP_PREFIX + ".")).forEach(name -> {
+                String value = context == null ? 
filterConfig.getInitParameter(name) : context.getInitParameter(name);
+                conf.set(name, value);
+            });
+            initParameterNames.stream().filter(name -> 
name.startsWith(IMPERSONATION_MODE + ".")).forEach(name -> {
+                String value = context == null ? 
filterConfig.getInitParameter(name) : context.getInitParameter(name);
+                conf.set(name, value);
+            });
+        }
+        /* For proxy group use GroupBasedImpersonationProvider */
+        saveImpersonationProvider(topologyName, role, conf, new 
GroupBasedImpersonationProvider(getImpersonationEnabledFlags(filterConfig)), 
PROXYGROUP_PREFIX);
+    }
+
+
+    private static void saveImpersonationProvider(String topologyName, String 
role, final Configuration conf, final ImpersonationProvider 
impersonationProvider, final String prefix) {
+        refreshSuperUserGroupsLock.lock();
+        try {
+            impersonationProvider.setConf(conf);
+            impersonationProvider.init(prefix);
+            LOG.createImpersonationProvider(topologyName, role, prefix, 
conf.getPropsWithPrefix(prefix + ".").toString());
+            TOPOLOGY_IMPERSONATION_PROVIDERS.putIfAbsent(topologyName, new 
ConcurrentHashMap<String, ImpersonationProvider>());
+            TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).put(role, 
impersonationProvider);
+        } finally {
+            refreshSuperUserGroupsLock.unlock();
+        }
+    }
+
+    public static HttpServletRequest getProxyRequest(HttpServletRequest 
request, String doAsUser, String topologyName, String role) throws 
AuthorizationException {
+        return getProxyRequest(request, request.getUserPrincipal().getName(), 
doAsUser, topologyName, role);
+    }
+
+    public static HttpServletRequest getProxyRequest(HttpServletRequest 
request, String remoteUser, String doAsUser, String topologyName, String role) 
throws AuthorizationException {
+        final UserGroupInformation remoteRequestUgi = 
getRemoteRequestUgi(remoteUser, doAsUser);
+        if (remoteRequestUgi != null) {
+            authorizeImpersonationRequest(request, remoteRequestUgi, 
topologyName, role);
+
+            return new HttpServletRequestWrapper(request) {
+                @Override
+                public String getRemoteUser() {
+                    return remoteRequestUgi.getShortUserName();
+                }
+
+                @Override
+                public Principal getUserPrincipal() {
+                    return remoteRequestUgi::getUserName;
+                }
+
+                @Override
+                public Object getAttribute(String name) {
+                    if (name != null && name.equals(REAL_USER_NAME_ATTRIBUTE)) 
{
+                        return 
remoteRequestUgi.getRealUser().getShortUserName();
+                    } else {
+                        return super.getAttribute(name);
+                    }
+                }
+            };
+
+        }
+        return null;
+    }
+
+    public static void authorizeGroupImpersonationRequest(HttpServletRequest 
request, String remoteUser, String doAsUser, String topologyName, String role, 
List<String> groups) throws AuthorizationException {
+        final UserGroupInformation remoteRequestUgi = 
getRemoteRequestUgi(remoteUser, doAsUser);
+
+        if (remoteRequestUgi != null) {
+            authorizeImpersonationRequest(request, remoteRequestUgi, 
topologyName, role, groups);
+        }
+    }
+
+    public static void authorizeImpersonationRequest(HttpServletRequest 
request, String remoteUser, String doAsUser, String topologyName, String role) 
throws AuthorizationException {
+        final UserGroupInformation remoteRequestUgi = 
getRemoteRequestUgi(remoteUser, doAsUser);
+        if (remoteRequestUgi != null) {
+            authorizeImpersonationRequest(request, remoteRequestUgi, 
topologyName, role);
+        }
+    }
+
+    private static void authorizeImpersonationRequest(HttpServletRequest 
request, UserGroupInformation remoteRequestUgi, String topologyName, String 
role)
+            throws AuthorizationException {
+        authorizeImpersonationRequest(request, remoteRequestUgi, topologyName, 
role, Collections.emptyList());
+    }
+
+    private static void authorizeImpersonationRequest(HttpServletRequest 
request, UserGroupInformation remoteRequestUgi, String topologyName, String 
role, List<String> groups)
+            throws AuthorizationException {
+
+        final ImpersonationProvider impersonationProvider = 
getImpersonationProvider(topologyName, role);
+
+        if (impersonationProvider != null) {
+            try {
+                if (impersonationProvider instanceof 
GroupBasedImpersonationProvider) {
+                    ((GroupBasedImpersonationProvider) 
impersonationProvider).authorize(remoteRequestUgi, 
InetAddress.getByName(request.getRemoteAddr()), groups);
+                } else {
+                    impersonationProvider.authorize(remoteRequestUgi, 
request.getRemoteAddr());
+                }
+
+            } catch 
(org.apache.hadoop.security.authorize.AuthorizationException | 
UnknownHostException e) {
+                throw new AuthorizationException(e);
+            }
+        } else {
+            throw new AuthorizationException("ImpersonationProvider for " + 
topologyName + " / " + role + " not found!");
+        }
+    }
+
+    private static ImpersonationProvider getImpersonationProvider(String 
topologyName, String role) {
+        refreshSuperUserGroupsLock.lock();
+        final ImpersonationProvider impersonationProvider;
+        try {
+            impersonationProvider = 
(TOPOLOGY_IMPERSONATION_PROVIDERS.getOrDefault(topologyName, 
Collections.emptyMap())).get(role);
+        } finally {
+            refreshSuperUserGroupsLock.unlock();
+        }
+        return impersonationProvider;
+    }
+
+    private static UserGroupInformation getRemoteRequestUgi(String remoteUser, 
String doAsUser) {
+        if (remoteUser != null) {
+            final UserGroupInformation remoteUserUgi = 
UserGroupInformation.createRemoteUser(remoteUser);
+            return UserGroupInformation.createProxyUser(doAsUser, 
remoteUserUgi);
+        }
+        return null;
+    }
+
+    public static boolean hasProxyConfig(String topologyName, String role) {
+        return getImpersonationProvider(topologyName, role) != null;
+    }
+
+    public static void removeProxyUserConfig(String topologyName, String role) 
{
+        if (hasProxyConfig(topologyName, role)) {
+            refreshSuperUserGroupsLock.lock();
+            try {
+                
TOPOLOGY_IMPERSONATION_PROVIDERS.get(topologyName).remove(role);
+            } finally {
+                refreshSuperUserGroupsLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * FilterConfig.getInitParameters() returns an enumeration and the first 
time we
+     * iterate thru on its elements we can process the parameter names as 
desired
+     * (because hasMoreElements returns true). The subsequent calls, however, 
will not
+     * succeed because getInitParameters() returns the same object where the
+     * hasMoreElements returns false.
+     * <p>
+     * In classes where there are multiple iterations should be conducted, a
+     * collection should be used instead.
+     *
+     * @return the names of the filter's initialization parameters as a List of
+     *         String objects, or an empty List if the filter has no 
initialization
+     *         parameters.
+     */
+    public static List<String> getInitParameterNamesAsList(FilterConfig 
filterConfig) {
+        return filterConfig.getInitParameterNames() == null ? 
Collections.emptyList() : 
Collections.list(filterConfig.getInitParameterNames());
+    }
+
+    public static void markDoGlobalLogoutInRequest(HttpServletRequest request) 
{
+        request.setAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE, "true");
+    }
+
+    public static boolean shouldDoGlobalLogout(HttpServletRequest request) {
+        return request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE) == null ? 
false : Boolean.parseBoolean((String) 
request.getAttribute(DO_GLOBAL_LOGOUT_ATTRIBUTE));
+    }
+
+    /**
+     * Check if user or group impersonation is enabled based on filter 
configuration.
+     *
+     * @param filterConfig The filter configuration
+     * @return A boolean array where the first element indicates if user 
impersonation is enabled

Review Comment:
   Stale javadoc comment. It no longer returns a boolean array.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscr...@knox.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to