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

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

                Author: ASF GitHub Bot
            Created on: 20/Oct/26 07:23
            Start Date: 20/Oct/26 07:23
    Worklog Time Spent: 10m 
      Work Description: handavid commented on code in PR #1454:
URL: https://github.com/apache/knox/pull/1454#discussion_r4322446624


##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java:
##########
@@ -80,6 +83,8 @@ public class LdapProxyBackend implements LdapBackend {
     private static final String[] ALL_ATTRIBUTES = new String[]{"*"};
     private static final String[] ALL_ATTRIBUTES_AND_MEMBEROF = new 
String[]{"*", "memberOf"};
 
+    private static final String DEFAULT_MAX_FILTER_LENGTH = "2000";

Review Comment:
   default to unlimited



##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/roles/rest/RestApiLdapRolesReverseLookup.java:
##########
@@ -0,0 +1,198 @@
+/*
+ * 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.services.ldap.roles.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+import org.apache.knox.gateway.services.ldap.RoleAssignees;
+import org.apache.knox.gateway.services.ldap.RoleAssignment;
+import org.apache.knox.gateway.services.ldap.roles.LdapRolesReverseLookup;
+import org.apache.knox.gateway.services.ldap.roles.RoleException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * REST API based implementation of LdapRolesReverseLookup.
+ */
+public class RestApiLdapRolesReverseLookup implements LdapRolesReverseLookup {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final String listEndpoint;
+    private final String getEndpoint;
+
+    public RestApiLdapRolesReverseLookup(String listEndpoint, String 
getEndpoint) {
+        this.listEndpoint = listEndpoint;
+        this.getEndpoint = getEndpoint;
+    }
+
+    @Override
+    public Collection<String> listRoles() throws RoleException {
+        final List<Role> roles = listRoles(null);
+        Set<String> result = new HashSet<>();
+        for (Role role : roles) {
+            result.add(new RoleAssignment(role.getScope(), 
role.getName()).getDisplayValue());
+        }
+        return result;
+    }
+
+    @Override
+    public RoleAssignees lookupRoleAssignees(String role) throws RoleException 
{
+        if (role != null) {
+            RoleAssignment roleAssignment = 
RoleAssignment.createFromDisplayValue(role);
+            List<Role> rolesList = listRoles(roleAssignment);
+            String roleId = findRoleId(roleAssignment, rolesList);
+            if (roleId != null) {
+                List<String> users = new ArrayList<>();
+                List<String> groups = new ArrayList<>();
+                List<RoleMember> members = getMembers(roleId);
+                if (members != null) {
+                    for (RoleMember member : members) {
+                        if (member.getMemberType().equalsIgnoreCase("user")) {
+                            users.add(member.getMemberId());
+                        } else if 
(member.getMemberType().equalsIgnoreCase("group")) {
+                            groups.add(member.getMemberId());
+                        } else {
+                            // ignore unknown member types
+                            LOG.restRolesAssigneeTypeUnknown(role, 
member.getMemberType());
+                        }
+                    }
+                }
+                return new RoleAssignees(users, groups);
+            }
+        }
+        return new RoleAssignees(List.of(), List.of());
+    }
+
+    private List<RoleMember> getMembers(String roleId) throws RoleException {
+        String encodedRoleId = URLEncoder.encode(roleId, 
StandardCharsets.UTF_8)
+                .replace("+", "%20"); // URLEncoder converts spaces to '+', 
path params need '%20'
+        String getUrl = getEndpoint + "/" + encodedRoleId;
+
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            LOG.restRolesGetRequest(getUrl, roleId);
+            HttpGet getRequest = new HttpGet(getUrl);
+            getRequest.setHeader("Accept", "application/json");
+            try (CloseableHttpResponse response = 
httpClient.execute(getRequest)) {
+                int statusCode = response.getStatusLine().getStatusCode();
+                HttpEntity entity = response.getEntity();
+                final String jsonResponse = entity == null ? null : 
EntityUtils.toString(entity);
+                LOG.restRolesGetResponse(getUrl, statusCode, jsonResponse);
+
+                if (statusCode != 200) {
+                    LOG.restRolesLookupHttpError(getUrl, statusCode, 
jsonResponse);
+                    throw new RoleException("Failed to get roles: HTTP " + 
statusCode);
+                }
+
+                if (jsonResponse == null) {
+                    throw new RoleException("Empty response from role get 
API");
+                }
+
+                return mapper.readValue(jsonResponse, Role.class).getMembers();
+            }
+        } catch (IOException e) {
+            throw new RoleException("Error while executing role get", e);
+        }
+    }
+
+    private List<Role> listRoles(RoleAssignment roleAssignment) throws 
RoleException {
+        String listUrl = null;
+        int numRolesRetrieved = 0;
+        int totalExpectedRoles = 0;
+        List<Role> roles = new ArrayList<>();
+
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            do {
+                URIBuilder uriBuilder = new URIBuilder(listEndpoint);
+                if (roleAssignment != null) {
+                    if (roleAssignment.getName() != null) {
+                        uriBuilder.addParameter("name", 
roleAssignment.getName());
+                    }
+                    if (roleAssignment.getScope() != null) {
+                        uriBuilder.addParameter("scope", 
roleAssignment.getScope());
+                    }
+                }
+                uriBuilder.addParameter("offset", 
Integer.toString(numRolesRetrieved));
+                URI listURI = uriBuilder.build();
+                listUrl = listURI.toString();
+                LOG.restRolesListRequest(listUrl);
+
+                HttpGet listRequest = new HttpGet(listUrl);
+                listRequest.setHeader("Accept", "application/json");
+                try (CloseableHttpResponse response = 
httpClient.execute(listRequest)) {
+                    int statusCode = response.getStatusLine().getStatusCode();
+                    HttpEntity entity = response.getEntity();
+                    final String jsonResponse = entity == null ? null : 
EntityUtils.toString(entity);
+                    LOG.restRolesListResponse(listUrl, statusCode, 
jsonResponse);
+
+                    if (statusCode != 200) {
+                        LOG.restRolesLookupHttpError(listUrl, statusCode, 
jsonResponse);
+                        throw new RoleException("Failed to list roles: HTTP " 
+ statusCode);
+                    }
+
+                    if (jsonResponse == null) {
+                        throw new RoleException("Empty response from role list 
API");
+                    }
+
+                    ListRolesResponse listRolesResponse = 
mapper.readValue(jsonResponse, ListRolesResponse.class);
+                    numRolesRetrieved += listRolesResponse.getData().size();

Review Comment:
   added null-checks



##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/roles/rest/RestApiLdapRolesReverseLookup.java:
##########
@@ -0,0 +1,198 @@
+/*
+ * 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.services.ldap.roles.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+import org.apache.knox.gateway.services.ldap.RoleAssignees;
+import org.apache.knox.gateway.services.ldap.RoleAssignment;
+import org.apache.knox.gateway.services.ldap.roles.LdapRolesReverseLookup;
+import org.apache.knox.gateway.services.ldap.roles.RoleException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * REST API based implementation of LdapRolesReverseLookup.
+ */
+public class RestApiLdapRolesReverseLookup implements LdapRolesReverseLookup {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final String listEndpoint;
+    private final String getEndpoint;
+
+    public RestApiLdapRolesReverseLookup(String listEndpoint, String 
getEndpoint) {
+        this.listEndpoint = listEndpoint;
+        this.getEndpoint = getEndpoint;
+    }
+
+    @Override
+    public Collection<String> listRoles() throws RoleException {
+        final List<Role> roles = listRoles(null);
+        Set<String> result = new HashSet<>();
+        for (Role role : roles) {
+            result.add(new RoleAssignment(role.getScope(), 
role.getName()).getDisplayValue());
+        }
+        return result;
+    }
+
+    @Override
+    public RoleAssignees lookupRoleAssignees(String role) throws RoleException 
{
+        if (role != null) {
+            RoleAssignment roleAssignment = 
RoleAssignment.createFromDisplayValue(role);
+            List<Role> rolesList = listRoles(roleAssignment);
+            String roleId = findRoleId(roleAssignment, rolesList);
+            if (roleId != null) {
+                List<String> users = new ArrayList<>();
+                List<String> groups = new ArrayList<>();
+                List<RoleMember> members = getMembers(roleId);
+                if (members != null) {
+                    for (RoleMember member : members) {
+                        if (member.getMemberType().equalsIgnoreCase("user")) {
+                            users.add(member.getMemberId());
+                        } else if 
(member.getMemberType().equalsIgnoreCase("group")) {
+                            groups.add(member.getMemberId());
+                        } else {
+                            // ignore unknown member types
+                            LOG.restRolesAssigneeTypeUnknown(role, 
member.getMemberType());
+                        }
+                    }
+                }
+                return new RoleAssignees(users, groups);
+            }
+        }
+        return new RoleAssignees(List.of(), List.of());
+    }
+
+    private List<RoleMember> getMembers(String roleId) throws RoleException {
+        String encodedRoleId = URLEncoder.encode(roleId, 
StandardCharsets.UTF_8)
+                .replace("+", "%20"); // URLEncoder converts spaces to '+', 
path params need '%20'
+        String getUrl = getEndpoint + "/" + encodedRoleId;

Review Comment:
   switched to URIBuilder



##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/roles/rest/RestApiLdapRolesReverseLookup.java:
##########
@@ -0,0 +1,198 @@
+/*
+ * 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.services.ldap.roles.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+import org.apache.knox.gateway.services.ldap.RoleAssignees;
+import org.apache.knox.gateway.services.ldap.RoleAssignment;
+import org.apache.knox.gateway.services.ldap.roles.LdapRolesReverseLookup;
+import org.apache.knox.gateway.services.ldap.roles.RoleException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * REST API based implementation of LdapRolesReverseLookup.
+ */
+public class RestApiLdapRolesReverseLookup implements LdapRolesReverseLookup {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final String listEndpoint;
+    private final String getEndpoint;
+
+    public RestApiLdapRolesReverseLookup(String listEndpoint, String 
getEndpoint) {
+        this.listEndpoint = listEndpoint;
+        this.getEndpoint = getEndpoint;
+    }
+
+    @Override
+    public Collection<String> listRoles() throws RoleException {
+        final List<Role> roles = listRoles(null);
+        Set<String> result = new HashSet<>();
+        for (Role role : roles) {
+            result.add(new RoleAssignment(role.getScope(), 
role.getName()).getDisplayValue());
+        }
+        return result;
+    }
+
+    @Override
+    public RoleAssignees lookupRoleAssignees(String role) throws RoleException 
{
+        if (role != null) {
+            RoleAssignment roleAssignment = 
RoleAssignment.createFromDisplayValue(role);
+            List<Role> rolesList = listRoles(roleAssignment);
+            String roleId = findRoleId(roleAssignment, rolesList);
+            if (roleId != null) {
+                List<String> users = new ArrayList<>();
+                List<String> groups = new ArrayList<>();
+                List<RoleMember> members = getMembers(roleId);
+                if (members != null) {
+                    for (RoleMember member : members) {
+                        if (member.getMemberType().equalsIgnoreCase("user")) {
+                            users.add(member.getMemberId());
+                        } else if 
(member.getMemberType().equalsIgnoreCase("group")) {
+                            groups.add(member.getMemberId());
+                        } else {
+                            // ignore unknown member types
+                            LOG.restRolesAssigneeTypeUnknown(role, 
member.getMemberType());
+                        }
+                    }
+                }
+                return new RoleAssignees(users, groups);
+            }
+        }
+        return new RoleAssignees(List.of(), List.of());
+    }
+
+    private List<RoleMember> getMembers(String roleId) throws RoleException {
+        String encodedRoleId = URLEncoder.encode(roleId, 
StandardCharsets.UTF_8)
+                .replace("+", "%20"); // URLEncoder converts spaces to '+', 
path params need '%20'
+        String getUrl = getEndpoint + "/" + encodedRoleId;
+
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            LOG.restRolesGetRequest(getUrl, roleId);
+            HttpGet getRequest = new HttpGet(getUrl);
+            getRequest.setHeader("Accept", "application/json");
+            try (CloseableHttpResponse response = 
httpClient.execute(getRequest)) {
+                int statusCode = response.getStatusLine().getStatusCode();
+                HttpEntity entity = response.getEntity();
+                final String jsonResponse = entity == null ? null : 
EntityUtils.toString(entity);
+                LOG.restRolesGetResponse(getUrl, statusCode, jsonResponse);
+
+                if (statusCode != 200) {
+                    LOG.restRolesLookupHttpError(getUrl, statusCode, 
jsonResponse);

Review Comment:
   fixed



##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java:
##########
@@ -576,6 +586,54 @@ public List<Entry> search(String searchBase, SearchScope 
searchScope, String fil
         }
     }
 
+    private String mapMemberOfExpression(String filter, SchemaManager 
schemaManager) throws Exception {
+        if (useMemberOf || !filter.toLowerCase(ROOT).contains("memberof")) {
+            return filter;
+        }
+        // Convert filter if it contains "memberOf" but the backend doesn't 
support "memberOf"
+        MemberOfMappingVisitor visitor = new 
MemberOfMappingVisitor(schemaManager, this::lookupGroup, this::isUser);
+        ExprNode node = FilterParser.parse(schemaManager, filter);
+        // Baseline in the same form as the visitor output so an unchanged 
visit exits on the first pass
+        String current = node.toString();
+        for (int i = 0; i < recursiveGroupResolutionMaxDepth; ++i) {

Review Comment:
   done





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

    Worklog Id:     (was: 1048382)
    Time Spent: 1h 10m  (was: 1h)

> LDAP Proxy find all members of a role
> -------------------------------------
>
>                 Key: KNOX-3487
>                 URL: https://issues.apache.org/jira/browse/KNOX-3487
>             Project: Apache Knox
>          Issue Type: Improvement
>          Components: Server
>    Affects Versions: 3.1.0
>            Reporter: David Han
>            Assignee: David Han
>            Priority: Major
>             Fix For: 3.1.0
>
>          Time Spent: 1h 10m
>  Remaining Estimate: 0h
>
> When role lookup is enabled on the LDAP Proxy, the LDAP Proxy should be able 
> to return all users that are assigned a role. E.g., 
> {code}
>  ldapsearch  -b 'ou=users,DC=proxy,DC=com' -s sub 
> '(memberOf=cn=rolename,ou=groups,dc=proxy,dc=com)' '*'
> {code}
> This would require calling the roles lookup service to get the mapped users 
> and groups for the given role. Then modifying the search filter to replace 
> the rolename with the users and groups.



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

Reply via email to