Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add paging support for search results #1

Draft
wants to merge 6 commits into
base: bdo
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/org/wso2/carbon/connector/ldap/AddEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void connect(MessageContext messageContext) throws ConnectException {
OMElement result = factory.createOMElement(LDAPConstants.RESULT, ns);
OMElement message = factory.createOMElement(LDAPConstants.MESSAGE, ns);
try {
DirContext context = LDAPUtils.getDirectoryContext(messageContext);
DirContext context = LDAPUtils.getLdapContext(messageContext);
String classes[] = objectClass.split(",");
Attributes entry = new BasicAttributes();
Attribute obClassAttr = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void connect(MessageContext messageContext) throws ConnectException {
OMElement message = factory.createOMElement(LDAPConstants.MESSAGE, ns);

try {
DirContext context = LDAPUtils.getDirectoryContext(messageContext);
DirContext context = LDAPUtils.getLdapContext(messageContext);
try {
Attributes matchingAttributes = new BasicAttributes();
//search for the existance of dn
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/wso2/carbon/connector/ldap/LDAPUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
Expand All @@ -45,7 +45,7 @@ public class LDAPUtils {
protected static Log log = LogFactory.getLog(LDAPUtils.class);
private static OMNamespace ns = fac.createOMNamespace(LDAPConstants.CONNECTOR_NAMESPACE, LDAPConstants.NAMESPACE);

protected static DirContext getDirectoryContext(MessageContext messageContext)
protected static LdapContext getLdapContext(MessageContext messageContext)
throws NamingException {
String providerUrl = LDAPUtils.lookupContextParams(messageContext, LDAPConstants.PROVIDER_URL);
String securityPrincipal = LDAPUtils.lookupContextParams(messageContext, LDAPConstants.SECURITY_PRINCIPAL);
Expand Down Expand Up @@ -94,8 +94,8 @@ protected static DirContext getDirectoryContext(MessageContext messageContext)
}
}

DirContext ctx = null;
ctx = new InitialDirContext(env);
LdapContext ctx = null;
ctx = new InitialLdapContext(env, null);
return ctx;
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/wso2/carbon/connector/ldap/Rename.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void connect(MessageContext messageContext) {
OMElement message = factory.createOMElement(LDAPConstants.MESSAGE, ns);

try {
DirContext context = LDAPUtils.getDirectoryContext(messageContext);
DirContext context = LDAPUtils.getLdapContext(messageContext);
context.rename(oldName, newName);
message.setText(LDAPConstants.SUCCESS);
result.addChild(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void connect(MessageContext messageContext) {
LDAPConstants.NAMESPACE);
OMElement result = factory.createOMElement(LDAPConstants.RESULT, ns);
try {
DirContext context = LDAPUtils.getDirectoryContext(messageContext);
DirContext context = LDAPUtils.getLdapContext(messageContext);
String searchFilter = generateSearchFilter(objectClass, filter, messageContext);
try {
NamingEnumeration<SearchResult> results = searchInUserBase(dn, searchFilter, returnAttributes,
Expand Down
318 changes: 318 additions & 0 deletions src/main/java/org/wso2/carbon/connector/ldap/SearchEntryPaged.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.connector.ldap;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.commons.logging.Log;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.json.JSONException;
import org.json.JSONObject;
import org.wso2.carbon.connector.core.AbstractConnector;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;

public class SearchEntryPaged extends AbstractConnector {
protected static Log log = LogFactory.getLog(SearchEntry.class);

@Override
public void connect(MessageContext messageContext) {
String objectClass = (String) getParameter(messageContext, LDAPConstants.OBJECT_CLASS);
String filter = (String) getParameter(messageContext, LDAPConstants.FILTERS);
String dn = (String) getParameter(messageContext, LDAPConstants.DN);
String returnAttributes[] = {};
String returnAttributesValue = (String) getParameter(messageContext, LDAPConstants.ATTRIBUTES);
if (!(returnAttributesValue == null || returnAttributesValue.isEmpty())) {
returnAttributes = returnAttributesValue.split(",");
}
String scope = (String) getParameter(messageContext, LDAPConstants.SCOPE);
int searchScope = getSearchScope(scope);
int limit = 0;
String searchLimit = (String) getParameter(messageContext, LDAPConstants.LIMIT);
if (!StringUtils.isEmpty(searchLimit)) {
try {
limit = Integer.parseInt(searchLimit);
} catch (NumberFormatException ex) {
log.error("Invalid value specified for Search limit. Setting default limit value of 0 (unlimited)");
}
}

boolean onlyOneReference = Boolean.valueOf(
(String) getParameter(messageContext, LDAPConstants.ONLY_ONE_REFERENCE));
OMFactory factory = OMAbstractFactory.getOMFactory();
OMNamespace ns = factory.createOMNamespace(LDAPConstants.CONNECTOR_NAMESPACE,
LDAPConstants.NAMESPACE);
OMElement result = factory.createOMElement(LDAPConstants.RESULT, ns);
try {
LdapContext context = LDAPUtils.getLdapContext(messageContext);
String searchFilter = generateSearchFilter(objectClass, filter, messageContext);
try {
SearchResult entityResult;
byte[] cookie = null;
int pageSize = 3000;
context.setRequestControls(new Control[]{
new PagedResultsControl(pageSize, cookie, Control.CRITICAL) });
do {
NamingEnumeration<SearchResult> results = searchInUserBase(dn, searchFilter, returnAttributes,
searchScope, context, limit);
if (!onlyOneReference) {
if (results != null && results.hasMore()) {
while (results.hasMoreElements()) {
entityResult = results.next();
Attributes attributes = entityResult.getAttributes();
if (attributes != null) {
Attribute attribute = attributes.get(LDAPConstants.OBJECT_GUID);
if (attribute != null) {

Object attObject = attribute.get(0);
final byte[] bytes = (byte[]) attObject;

// https://community.oracle.com/thread/1157698
// Represent objectGUID in UUID
if (bytes.length == 16) {
final ByteBuffer bb = ByteBuffer.wrap(swapBytes(bytes));
String attr = new java.util.UUID(bb.getLong(), bb.getLong()).toString();
entityResult.getAttributes().put(LDAPConstants.OBJECT_GUID, attr);
}
}
}

result.addChild(prepareNode(entityResult, factory, ns, returnAttributes));
}
}
// Examine the paged results control response
Control[] controls = context.getResponseControls();
if (controls != null) {
for (int i = 0; i < controls.length; i++) {
if (controls[i] instanceof PagedResultsResponseControl) {
PagedResultsResponseControl prrc =
(PagedResultsResponseControl)controls[i];
cookie = prrc.getCookie();
}
}
}
// Re-activate paged results
context.setRequestControls(new Control[]{
new PagedResultsControl(pageSize, cookie, Control.CRITICAL) });
} else {
entityResult = makeSureOnlyOneMatch(results);
if (entityResult != null) {
result.addChild(prepareNode(entityResult, factory, ns, returnAttributes));
}
}
} while (cookie != null);
LDAPUtils.preparePayload(messageContext, result);
if (context != null) {
context.close();
}
} catch (PartialResultException e) {
if (!dn.contains("OU=")) {
LDAPUtils.preparePayload(messageContext, result);
if (context != null) {
context.close();
}
} else {
LDAPUtils.handleErrorResponse(messageContext, LDAPConstants.ErrorConstants.SEARCH_ERROR, e);
throw new SynapseException(e);
}
} catch (NamingException | IOException e) { // LDAP Errors are catched
LDAPUtils.handleErrorResponse(messageContext, LDAPConstants.ErrorConstants.SEARCH_ERROR, e);
throw new SynapseException(e);
}
} catch (NamingException e) { // Authentication failures are catched
LDAPUtils.handleErrorResponse(messageContext, LDAPConstants.ErrorConstants.INVALID_LDAP_CREDENTIALS, e);
throw new SynapseException(e);
}
}

private OMElement prepareNode(SearchResult entityResult, OMFactory factory, OMNamespace ns,
String returnAttributes[])
throws NamingException {
Attributes attributes = entityResult.getAttributes();
Attribute attribute;
OMElement entry = factory.createOMElement(LDAPConstants.ENTRY, ns);
OMElement dnattr = factory.createOMElement(LDAPConstants.DN, ns);
dnattr.setText(entityResult.getNameInNamespace());
entry.addChild(dnattr);
if (returnAttributes.length == 0) {
NamingEnumeration<String> ids = attributes.getIDs();
while (ids.hasMore()) {
String id = ids.next();
Attribute attr = attributes.get(id);
NamingEnumeration ne = attr.getAll();
while (ne.hasMoreElements()) {
Object element = ne.next();
String elementType = element.getClass().toString();
String value = "";
if (elementType.equals("class java.lang.String")) {
value = element.toString();
} else if (elementType.equals("class [B")) {
Attribute attributeValue = attributes.get(id);
value = new String((byte[]) attributeValue.get());
}
OMElement omElement = factory.createOMElement(id, ns);
omElement.setText(value);
entry.addChild(omElement);
}
}
} else {
for (int i = 0; i < returnAttributes.length; i++) {
attribute = attributes.get(returnAttributes[i]);

// Remove ";" from returnAttribute elements to prevent invalid xml generation
if (returnAttributes[i].contains(";")) {
String[] splitResult = returnAttributes[i].split("(?=;)");
returnAttributes[i] = splitResult[0];
}
if (attribute != null) {
NamingEnumeration ne = attribute.getAll();
while (ne.hasMoreElements()) {
Object element = ne.next();
String elementType = element.getClass().toString();
String value = "";
if (elementType.equals("class java.lang.String")) {
value = (String) element.toString();
} else if (elementType.equals("class [B")) {
Attribute attributeValue = attributes.get(returnAttributes[i]);
value = new String((byte[]) attributeValue.get());
}
OMElement attr = factory.createOMElement(returnAttributes[i], ns);
attr.setText(value);
entry.addChild(attr);
}
}
}
}
return entry;
}

private SearchResult makeSureOnlyOneMatch(NamingEnumeration<SearchResult> results) throws NamingException {
SearchResult searchResult = null;

if (results.hasMoreElements()) {
searchResult = (SearchResult) results.nextElement();

// Make sure there is not another item available, there should be only 1 match
if (results.hasMoreElements()) {
// Here the code has matched multiple objects for the searched target
throw new NamingException("Multiple objects for the searched target have been found. Try to " +
"change onlyOneReference option");
}
}

return searchResult;
}

/**
* swap the bytes 0<->3, 1<->2,4<->5,6<->7 of the objectGUID byte array,
* because objectGUID byte order is not big-endian
*
* @param bytes byte array needed to be swapped
* @return swapped byte array
*/
protected byte[] swapBytes(byte[] bytes) {
// bytes[0] <-> bytes[3]
byte swap = bytes[3];
bytes[3] = bytes[0];
bytes[0] = swap;
// bytes[1] <-> bytes[2]
swap = bytes[2];
bytes[2] = bytes[1];
bytes[1] = swap;
// bytes[4] <-> bytes[5]
swap = bytes[5];
bytes[5] = bytes[4];
bytes[4] = swap;
// bytes[6] <-> bytes[7]
swap = bytes[7];
bytes[7] = bytes[6];
bytes[6] = swap;
return bytes;
}

private NamingEnumeration<SearchResult> searchInUserBase(String dn, String searchFilter,
String[] returningAttributes,
int searchScope, DirContext rootContext, int limit)
throws NamingException {
String userBase = dn;
SearchControls userSearchControl = new SearchControls();
if (returningAttributes.length > 0) {
userSearchControl.setReturningAttributes(returningAttributes);
}
userSearchControl.setCountLimit(limit);
userSearchControl.setSearchScope(searchScope);
NamingEnumeration<SearchResult> userSearchResults;
userSearchResults = rootContext.search(userBase, searchFilter, userSearchControl);
return userSearchResults;

}

private String generateSearchFilter(String objectClass, String filter, MessageContext messageContext) {
String attrFilter = "";
try {
JSONObject object = new JSONObject(filter);
Iterator keys = object.keys();
while (keys.hasNext()) {
String key = (String) keys.next();
attrFilter += "(";
attrFilter += key + "=" + object.getString(key);
attrFilter += ")";
}
} catch (JSONException e) {
handleException("Error while passing the JSON object", e, messageContext);
}
if (objectClass != null && !objectClass.isEmpty()) {
return "(&(objectClass=" + objectClass + ")" + attrFilter + ")";
} else {
return attrFilter;
}
}

private int getSearchScope(String scope) {
int searchScope = 2;
if (scope != null && !scope.isEmpty()) {
if (scope.equalsIgnoreCase("OBJECT")) {
searchScope = 0;
} else if (scope.equalsIgnoreCase("ONE_LEVEL")) {
searchScope = 1;
}
}
return searchScope;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void connect(MessageContext messageContext) throws ConnectException {
OMElement message = factory.createOMElement(LDAPConstants.MESSAGE, ns);

try {
DirContext context = LDAPUtils.getDirectoryContext(messageContext);
DirContext context = LDAPUtils.getLdapContext(messageContext);

Attributes entry = new BasicAttributes();
ModificationItem pwdItem = null;
Expand Down
Loading