[ 
https://issues.apache.org/jira/browse/KNOX-3048?focusedWorklogId=970508&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-970508
 ]

ASF GitHub Bot logged work on KNOX-3048:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 23/May/25 14:47
            Start Date: 23/May/25 14:47
    Worklog Time Spent: 10m 
      Work Description: 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.





Issue Time Tracking
-------------------

    Worklog Id:     (was: 970508)
    Time Spent: 2.5h  (was: 2h 20m)

> Surrogate proxy user configuration for user groups
> --------------------------------------------------
>
>                 Key: KNOX-3048
>                 URL: https://issues.apache.org/jira/browse/KNOX-3048
>             Project: Apache Knox
>          Issue Type: Improvement
>          Components: Server
>    Affects Versions: 2.0.0
>            Reporter: Philip Zampino
>            Assignee: Sandeep More
>            Priority: Major
>             Fix For: 2.1.0
>
>          Time Spent: 2.5h
>  Remaining Estimate: 0h
>
> *Problem Statement:*
> Currently Knox has the ability for specific users (say for e.g. {{sp_user}}) 
> to impersonate other users (say for e.g.{{ot_user}}). This is driven by 
> configs defined in a topology. Currently these configs are needed for each 
> user that impersonates other users (i.e. {{sp_user}}), this can get out of 
> hand quickly and can be difficult to maintain.
> To solve this problem the proposed solution uses a group level impersonation 
> configuration. This configuration will be based on the virtual groups feature 
> that is already available in Knox. With this new configuration we can have 
> specific users who belong to a virtual group/s (based on conditions such as 
> {{(match groups 'analyst|scientist') }}) impersonate other users. This will 
> significantly cut down on the config properties and keep them readable and 
> maintainable.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to