From 3028a94b8c7ef4eead7f45ea6b91b9e1a72e6368 Mon Sep 17 00:00:00 2001 From: Yuriy M <95305560+yuremm@users.noreply.github.com> Date: Wed, 21 Dec 2022 20:59:20 +0300 Subject: [PATCH] feat: problems with handling custom attributes #2752 (#3378) Co-authored-by: Yuriy Movchan --- .../orm/exception/SearchEntryException.java | 30 + .../java/io/jans/orm/model/EntryData.java | 18 +- .../ldap/impl/LdapBatchOperationWraper.java | 7 +- .../jans/orm/ldap/impl/LdapEntryManager.java | 304 ++++----- .../ldap/operation/LdapOperationService.java | 37 +- .../impl/LdapOperationServiceImpl.java | 577 +++++++++++------- .../spanner/impl/SpannerEntryManager.java | 4 +- .../orm/sql/SqlPagedUserSearchSample.java | 57 ++ .../io/jans/orm/sql/impl/SqlEntryManager.java | 4 +- .../impl/SqlOperationServiceImpl.java | 8 +- .../StandalonePersistanceFactoryService.java | 13 +- .../java/io/jans/orm/util/ArrayHelper.java | 8 + 12 files changed, 605 insertions(+), 462 deletions(-) create mode 100644 jans-orm/core/src/main/java/io/jans/orm/exception/SearchEntryException.java create mode 100644 jans-orm/sql-sample/src/main/java/io/jans/orm/sql/SqlPagedUserSearchSample.java diff --git a/jans-orm/core/src/main/java/io/jans/orm/exception/SearchEntryException.java b/jans-orm/core/src/main/java/io/jans/orm/exception/SearchEntryException.java new file mode 100644 index 00000000000..a67be3e5f5e --- /dev/null +++ b/jans-orm/core/src/main/java/io/jans/orm/exception/SearchEntryException.java @@ -0,0 +1,30 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.orm.exception; + +/** + * An exception is a result if search operation fails + * + * @author Yuriy Movchan Date: 2022/11/04 + */ +public class SearchEntryException extends BasePersistenceException { + + private static final long serialVersionUID = 1321766232087075304L; + + public SearchEntryException(Throwable root) { + super(root); + } + + public SearchEntryException(String string, Throwable root) { + super(string, root); + } + + public SearchEntryException(String s) { + super(s); + } + +} diff --git a/jans-orm/core/src/main/java/io/jans/orm/model/EntryData.java b/jans-orm/core/src/main/java/io/jans/orm/model/EntryData.java index a673ce5693b..17295897205 100644 --- a/jans-orm/core/src/main/java/io/jans/orm/model/EntryData.java +++ b/jans-orm/core/src/main/java/io/jans/orm/model/EntryData.java @@ -15,21 +15,26 @@ */ public class EntryData { private final List attributeData; + private String dn; public EntryData(List attributeData) { this.attributeData = attributeData; } + public EntryData(String dn, List attributeData) { + this.dn = dn; + this.attributeData = attributeData; + } + public List getAttributeData() { return attributeData; } - @Override - public String toString() { - return "EntryData [attributeData=" + attributeData + "]"; + public String getDN() { + return dn; } - public AttributeData getAttributeDate(String internalAttribute) { + public AttributeData getAttributeData(String internalAttribute) { if (attributeData == null) { return null; } @@ -43,4 +48,9 @@ public AttributeData getAttributeDate(String internalAttribute) { return null; } + @Override + public String toString() { + return "EntryData [attributeData=" + attributeData + ", dn=" + dn + "]"; + } + } diff --git a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapBatchOperationWraper.java b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapBatchOperationWraper.java index ea496809875..81e434a36c9 100644 --- a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapBatchOperationWraper.java +++ b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapBatchOperationWraper.java @@ -9,6 +9,7 @@ import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import io.jans.orm.model.BatchOperation; +import io.jans.orm.model.EntryData; import io.jans.orm.reflect.property.PropertyAnnotation; import java.util.ArrayList; @@ -43,14 +44,12 @@ public final BatchOperation getBatchOperation() { return batchOperation; } - public List createEntities(SearchResult searchResult) { + public List createEntities(List entryDataList) { if (ldapEntryManager == null) { return new ArrayList(0); } - SearchResultEntry[] searchResultEntry = searchResult.getSearchEntries() - .toArray(new SearchResultEntry[searchResult.getSearchEntries().size()]); - return ldapEntryManager.createEntities(entryClass, propertiesAnnotations, searchResultEntry); + return ldapEntryManager.createEntities(entryClass, propertiesAnnotations, entryDataList); } } diff --git a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapEntryManager.java b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapEntryManager.java index 4d43c15cafb..e356fc8b326 100644 --- a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapEntryManager.java +++ b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/impl/LdapEntryManager.java @@ -6,8 +6,40 @@ package io.jans.orm.ldap.impl; -import com.unboundid.ldap.sdk.*; +import static io.jans.orm.model.base.LocalizedString.EMPTY_LANG_TAG; +import static io.jans.orm.model.base.LocalizedString.LANG_JOINER; +import static io.jans.orm.model.base.LocalizedString.LANG_PREFIX; +import static io.jans.orm.model.base.LocalizedString.LANG_SEPARATOR; +import static io.jans.orm.model.base.LocalizedString.LOCALIZED; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.Modification; +import com.unboundid.ldap.sdk.ModificationType; +import com.unboundid.ldap.sdk.ResultCode; +import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.util.StaticUtils; + import io.jans.orm.PersistenceEntryManager; import io.jans.orm.annotation.AttributeName; import io.jans.orm.event.DeleteNotifier; @@ -21,25 +53,21 @@ import io.jans.orm.impl.BaseEntryManager; import io.jans.orm.ldap.operation.LdapOperationService; import io.jans.orm.ldap.operation.impl.LdapOperationServiceImpl; -import io.jans.orm.model.*; -import io.jans.orm.model.SearchScope; +import io.jans.orm.model.AttributeData; +import io.jans.orm.model.AttributeDataModification; import io.jans.orm.model.AttributeDataModification.AttributeModificationType; +import io.jans.orm.model.BatchOperation; +import io.jans.orm.model.DefaultBatchOperation; +import io.jans.orm.model.EntryData; +import io.jans.orm.model.PagedResult; +import io.jans.orm.model.SearchScope; +import io.jans.orm.model.SortOrder; import io.jans.orm.model.base.LocalizedString; import io.jans.orm.reflect.property.Getter; import io.jans.orm.reflect.property.PropertyAnnotation; import io.jans.orm.search.filter.Filter; import io.jans.orm.util.ArrayHelper; import io.jans.orm.util.StringHelper; -import org.apache.commons.codec.binary.Base64; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.text.ParseException; -import java.util.*; - -import static io.jans.orm.model.base.LocalizedString.*; /** * LDAP Entry Manager @@ -185,14 +213,14 @@ public void merge(String dn, String[] objectClasses, List() { @Override public int compare(String o1, String o2) { - return o1.toLowerCase().compareTo(o2.toLowerCase()); + return o1.toLowerCase().compareTo(o2.toLowerCase()); } }); if (idx >= 0) { @@ -311,21 +339,15 @@ public int remove(String baseDN, Class entryClass, Filter filter, int cou } DeleteBatchOperation batchOperation = new DeleteBatchOperation(this); - SearchResult searchResult = null; try { LdapBatchOperationWraper batchOperationWraper = new LdapBatchOperationWraper(batchOperation, this, entryClass, propertiesAnnotations); - searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(SearchScope.SUB), batchOperationWraper, - 0, 100, count, null, LdapOperationService.DN); - + getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(SearchScope.SUB), batchOperationWraper, + 0, count, 100, null, LdapOperationService.DN); } catch (Exception ex) { throw new EntryDeleteException(String.format("Failed to delete entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex); } - if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) { - throw new EntryDeleteException(String.format("Failed to delete entries with baseDN: %s, filter: %s", baseDN, searchFilter)); - } - return batchOperation.getCountEntries(); } @@ -351,22 +373,20 @@ public void removeRecursivelyFromDn(String dn, String[] objectClasses) { private void removeSubtreeThroughIteration(String dn, String[] objectClasses) { SearchScope scope = SearchScope.SUB; - SearchResult searchResult = null; + PagedResult searchResult = null; try { searchResult = getOperationService().search(dn, toLdapFilter(Filter.createPresenceFilter("objectClass")), toLdapSearchScope(scope), null, 0, 0, 0, null, "dn"); - if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) { - throw new EntryPersistenceException(String.format("Failed to find sub-entries of entry '%s' for removal", dn)); - } } catch (SearchScopeException ex) { throw new AuthenticationException(String.format("Failed to convert scope: %s", scope), ex); } catch (SearchException ex) { throw new EntryDeleteException(String.format("Failed to find sub-entries of entry '%s' for removal", dn), ex); } - List removeEntriesDn = new ArrayList(searchResult.getEntryCount()); - for (SearchResultEntry searchResultEntry : searchResult.getSearchEntries()) { - removeEntriesDn.add(searchResultEntry.getDN()); - } + List removeEntriesDn = new ArrayList(searchResult.getEntriesCount()); + for (EntryData entry : searchResult.getEntries()) { + // TODO: Double check this + removeEntriesDn.add(entry.getAttributeData(LdapOperationService.DN).getStringValues()[0]); + } Collections.sort(removeEntriesDn, LINE_LENGHT_COMPARATOR); @@ -379,10 +399,9 @@ private void removeSubtreeThroughIteration(String dn, String[] objectClasses) { protected List find(String dn, String[] objectClasses, Map propertiesAnnotationsMap, String... ldapReturnAttributes) { try { // Load entry - SearchResultEntry entry = getOperationService().lookup(dn, ldapReturnAttributes); - List result = getAttributeDataList(entry); + EntryData result = getOperationService().lookup(dn, ldapReturnAttributes); if (result != null) { - return result; + return result.getAttributeData(); } } catch (Exception ex) { throw new EntryPersistenceException(String.format("Failed to find entry: %s", dn), ex); @@ -393,7 +412,7 @@ protected List find(String dn, String[] objectClasses, Map List findEntries(String baseDN, Class entryClass, Filter filter, SearchScope scope, String[] ldapReturnAttributes, - BatchOperation batchOperation, int start, int count, int chunkSize) { + BatchOperation batchOperation, int start, int count, int chunkSize) { if (StringHelper.isEmptyString(baseDN)) { throw new MappingException("Base DN to find entries is null"); } @@ -414,26 +433,21 @@ public List findEntries(String baseDN, Class entryClass, Filter filter } else { searchFilter = filter; } - SearchResult searchResult = null; + PagedResult searchResult = null; try { LdapBatchOperationWraper batchOperationWraper = new LdapBatchOperationWraper(batchOperation, this, entryClass, propertiesAnnotations); searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(scope), batchOperationWraper, - start, chunkSize, count, null, currentLdapReturnAttributes); + start, count, chunkSize, null, currentLdapReturnAttributes); } catch (Exception ex) { throw new EntryPersistenceException(String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex); } - if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) { - throw new EntryPersistenceException(String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter)); - } - - if (searchResult.getEntryCount() == 0) { + if (searchResult.getEntriesCount() == 0) { return new ArrayList(0); } - List entries = createEntities(entryClass, propertiesAnnotations, - searchResult.getSearchEntries().toArray(new SearchResultEntry[searchResult.getSearchEntries().size()])); + List entries = createEntities(entryClass, propertiesAnnotations, searchResult.getEntries()); // Default sort if needed sortEntriesIfNeeded(entryClass, entries); @@ -443,7 +457,7 @@ public List findEntries(String baseDN, Class entryClass, Filter filter @Override public PagedResult findPagedEntries(String baseDN, Class entryClass, Filter filter, String[] ldapReturnAttributes, String sortBy, - SortOrder sortOrder, int start, int count, int chunkSize) { + SortOrder sortOrder, int start, int count, int chunkSize) { if (StringHelper.isEmptyString(baseDN)) { throw new MappingException("Base DN to find entries is null"); } @@ -466,71 +480,27 @@ public PagedResult findPagedEntries(String baseDN, Class entryClass, F } List searchResultEntries; - PagedResult vlvResponse = new PagedResult(); + PagedResult searchResponse; try { - searchResultEntries = getOperationService().searchSearchResultEntryList(baseDN, toLdapFilter(searchFilter), - toLdapSearchScope(SearchScope.SUB), start, count, chunkSize, sortBy, sortOrder, vlvResponse, currentLdapReturnAttributes); + searchResponse = getOperationService().searchPagedEntries(baseDN, toLdapFilter(searchFilter), + toLdapSearchScope(SearchScope.SUB), start, count, chunkSize, sortBy, sortOrder, currentLdapReturnAttributes); } catch (Exception ex) { throw new EntryPersistenceException(String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex); } - List entries = new ArrayList(0); - if (searchResultEntries.size() > 0) { - entries = createEntitiesVirtualListView(entryClass, propertiesAnnotations, searchResultEntries.toArray(new SearchResultEntry[]{})); - } - vlvResponse.setEntries(entries); - - return vlvResponse; - - } - - @Deprecated - public List findEntriesVirtualListView(String baseDN, Class entryClass, Filter filter, int start, int count, String sortBy, - SortOrder sortOrder, PagedResult vlvResponse, String[] ldapReturnAttributes) { - - if (StringHelper.isEmptyString(baseDN)) { - throw new MappingException("Base DN to find entries is null"); - } - - // Check entry class - checkEntryClass(entryClass, false); - String[] objectClasses = getTypeObjectClasses(entryClass); - List propertiesAnnotations = getEntryPropertyAnnotations(entryClass); - String[] currentLdapReturnAttributes = ldapReturnAttributes; - if (ArrayHelper.isEmpty(currentLdapReturnAttributes)) { - currentLdapReturnAttributes = getAttributes(null, propertiesAnnotations, false); - } - - // Find entries - Filter searchFilter; - if (objectClasses.length > 0) { - searchFilter = addObjectClassFilter(filter, objectClasses); - } else { - searchFilter = filter; - } - - SearchResult searchResult = null; - try { - - searchResult = getOperationService().searchVirtualListView(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(SearchScope.SUB), - start, count, sortBy, sortOrder, vlvResponse, currentLdapReturnAttributes); - - if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) { - throw new EntryPersistenceException(String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter)); - } - - } catch (Exception ex) { - throw new EntryPersistenceException(String.format("Failed to find entries with baseDN: %s, filter: %s", baseDN, searchFilter), ex); - } + PagedResult result = new PagedResult(); + result.setEntriesCount(searchResponse.getEntriesCount()); + result.setTotalEntriesCount(searchResponse.getTotalEntriesCount()); + result.setStart(start); - if (searchResult.getEntryCount() == 0) { - return new ArrayList(0); + List entries = new ArrayList(0); + if (searchResponse.getEntriesCount() > 0) { + entries = createEntitiesVirtualListView(entryClass, propertiesAnnotations, searchResponse.getEntries()); } + result.setEntries(entries); - List entries = createEntitiesVirtualListView(entryClass, propertiesAnnotations, - searchResult.getSearchEntries().toArray(new SearchResultEntry[searchResult.getSearchEntries().size()])); + return result; - return entries; } @Override @@ -549,12 +519,9 @@ protected boolean contains(String baseDN, String[] objectClasses, Class e SearchScope scope = SearchScope.SUB; - SearchResult searchResult = null; + PagedResult searchResult = null; try { searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(scope), null, 0, 1, 1, null, ldapReturnAttributes); - if ((searchResult == null) || !ResultCode.SUCCESS.equals(searchResult.getResultCode())) { - throw new EntryPersistenceException(String.format("Failed to find entry with baseDN: %s, filter: %s", baseDN, searchFilter)); - } } catch (SearchScopeException ex) { throw new AuthenticationException(String.format("Failed to convert scope: %s", scope), ex); } catch (SearchException ex) { @@ -563,22 +530,18 @@ protected boolean contains(String baseDN, String[] objectClasses, Class e } } - return (searchResult != null) && (searchResult.getEntryCount() > 0); + return (searchResult != null) && (searchResult.getEntriesCount() > 0); } protected List createEntities(Class entryClass, List propertiesAnnotations, - SearchResultEntry... searchResultEntries) { - List result = new ArrayList(searchResultEntries.length); + List searchResultEntries) { + List result = new ArrayList(searchResultEntries.size()); Map> entriesAttributes = new HashMap>(100); int count = 0; - for (int i = 0; i < searchResultEntries.length; i++) { + for (EntryData entry : searchResultEntries) { count++; - SearchResultEntry entry = searchResultEntries[i]; - entriesAttributes.put(entry.getDN(), getAttributeDataList(entry)); - - // Remove reference to allow java clean up object - searchResultEntries[i] = null; + entriesAttributes.put(entry.getDN(), entry.getAttributeData()); // Allow java to clean up temporary objects if (count >= 100) { @@ -596,27 +559,20 @@ protected List createEntities(Class entryClass, List List createEntitiesVirtualListView(Class entryClass, List propertiesAnnotations, - SearchResultEntry... searchResultEntries) { + List searchResultEntries) { List result = new LinkedList(); Map> entriesAttributes = new LinkedHashMap>(100); int count = 0; - for (int i = 0; i < searchResultEntries.length; i++) { - + for (EntryData entry : searchResultEntries) { count++; - SearchResultEntry entry = searchResultEntries[i]; - LinkedList attributeDataLinkedList = new LinkedList(); - attributeDataLinkedList.addAll(getAttributeDataList(entry)); + attributeDataLinkedList.addAll(entry.getAttributeData()); entriesAttributes.put(entry.getDN(), attributeDataLinkedList); - // Remove reference to allow java clean up object - searchResultEntries[i] = null; - // Allow java to clean up temporary objects if (count >= 100) { @@ -667,52 +623,6 @@ protected void loadLocalizedString(Map attributesMap, Loc }); } - private List getAttributeDataList(SearchResultEntry entry) { - if (entry == null) { - return null; - } - - List result = new ArrayList(); - for (Attribute attribute : entry.getAttributes()) { - String[] attributeValueStrings = NO_STRINGS; - String attributeName = attribute.getName(); - if (LOG.isTraceEnabled()) { - if (attribute.needsBase64Encoding()) { - LOG.trace("Found binary attribute: " + attributeName + ". Is defined in LDAP config: " - + getOperationService().isBinaryAttribute(attributeName)); - } - } - - attributeValueStrings = attribute.getValues(); - if (attribute.needsBase64Encoding()) { - boolean binaryAttribute = getOperationService().isBinaryAttribute(attributeName); - boolean certificateAttribute = getOperationService().isCertificateAttribute(attributeName); - - if (binaryAttribute || certificateAttribute) { - byte[][] attributeValues = attribute.getValueByteArrays(); - if (attributeValues != null) { - attributeValueStrings = new String[attributeValues.length]; - for (int i = 0; i < attributeValues.length; i++) { - attributeValueStrings[i] = Base64.encodeBase64String(attributeValues[i]); - LOG.trace("Binary attribute: " + attribute.getName() + " value (hex): " - + org.apache.commons.codec.binary.Hex.encodeHexString(attributeValues[i]) + " value (base64): " - + attributeValueStrings[i]); - } - } - } - if (certificateAttribute) { - attributeName = getOperationService().getCertificateAttributeName(attributeName); - } - } - - boolean multiValued = attributeValueStrings.length > 1; - AttributeData tmpAttribute = new AttributeData(attributeName, attributeValueStrings, multiValued); - result.add(tmpAttribute); - } - - return result; - } - @Override public boolean authenticate(String baseDN, Class entryClass, String userName, String password) { if (StringHelper.isEmptyString(baseDN)) { @@ -729,14 +639,14 @@ public boolean authenticate(String baseDN, Class entryClass, String userN searchFilter = addObjectClassFilter(searchFilter, objectClasses); } - SearchScope scope = SearchScope.SUB; + SearchScope scope = SearchScope.SUB; try { - SearchResult searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(scope), null, 0, 1, 1, null, LdapOperationService.UID_ARRAY); - if ((searchResult == null) || (searchResult.getEntryCount() != 1)) { + PagedResult searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(scope), null, 0, 1, 1, null, LdapOperationService.UID_ARRAY); + if ((searchResult == null) || (searchResult.getEntriesCount() != 1)) { return false; } - String bindDn = searchResult.getSearchEntries().get(0).getDN(); + String bindDn = searchResult.getEntries().get(0).getDN(); return getOperationService().authenticate(bindDn, password, null); } catch (ConnectionException ex) { @@ -794,20 +704,20 @@ public int countEntries(String baseDN, Class entryClass, Filter filter, S String[] ldapReturnAttributes; CountBatchOperation batchOperation; if (SearchScope.BASE == searchScope) { - ldapReturnAttributes = new String[]{"numsubordinates"}; // Don't load attributes + ldapReturnAttributes = new String[] { "numsubordinates" }; // Don't load attributes batchOperation = null; } else { - ldapReturnAttributes = new String[]{""}; // Don't load attributes + ldapReturnAttributes = new String[] { "" }; // Don't load attributes batchOperation = new CountBatchOperation(); } - SearchResult searchResult; + PagedResult searchResult; try { LdapBatchOperationWraper batchOperationWraper = null; if (batchOperation != null) { batchOperationWraper = new LdapBatchOperationWraper(batchOperation); } - searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(searchScope), batchOperationWraper, 0, 100, 0, null, + searchResult = getOperationService().search(baseDN, toLdapFilter(searchFilter), toLdapSearchScope(searchScope), batchOperationWraper, 0, 0, 100, null, ldapReturnAttributes); } catch (Exception ex) { throw new EntryPersistenceException( @@ -818,11 +728,12 @@ public int countEntries(String baseDN, Class entryClass, Filter filter, S return batchOperation.getCountEntries(); } - if (searchResult.getEntryCount() != 1) { + if (searchResult.getEntriesCount() != 1) { throw new EntryPersistenceException(String.format("Failed to calculate the number of entries due to missing result entry with baseDN: %s, filter: %s", baseDN, searchFilter)); } - Long result = searchResult.getSearchEntries().get(0).getAttributeValueAsLong("numsubordinates"); + // TODO: Double check this + Integer result = (Integer) searchResult.getEntries().get(0).getAttributeData("numsubordinates").getValue(); if (result == null) { throw new EntryPersistenceException(String.format("Failed to calculate the number of entries due to missing attribute 'numsubordinates' with baseDN: %s, filter: %s", baseDN, searchFilter)); } @@ -883,13 +794,11 @@ public boolean loadLdifFileContent(String ldifFileContent) { @Override public List exportEntry(String dn) { try { - SearchResultEntry searchResultEntry = getOperationService().lookup(dn, (String[]) null); - - List result = getAttributeDataList(searchResultEntry); + EntryData result = getOperationService().lookup(dn, (String[]) null); if (result != null) { - return result; + return result.getAttributeData(); } - + return null; } catch (ConnectionException | SearchException ex) { throw new EntryPersistenceException(String.format("Failed to find entry: %s", dn), ex); @@ -906,7 +815,7 @@ public int getSupportedLDAPVersion() { } private Modification createModification(final ModificationType modificationType, final String attributeName, final String... attributeValues) { - String realAttributeName = attributeName; + String realAttributeName = attributeName; if (getOperationService().isCertificateAttribute(realAttributeName)) { realAttributeName += ";binary"; byte[][] binaryValues = toBinaryValues(attributeValues); @@ -917,6 +826,23 @@ private Modification createModification(final ModificationType modificationType, return new Modification(modificationType, realAttributeName, attributeValues); } + private String[] convertValuesToStringValues(final Object... attributeValues) { + if (attributeValues == null) { + return null; + } + + String[] attributeStringValues = new String[attributeValues.length]; + for (int i = 0; i < attributeValues.length; i++) { + if (attributeValues[i] instanceof Date) { + attributeStringValues[i] = StaticUtils.encodeGeneralizedTime((Date) attributeValues[i]); + } else { + attributeStringValues[i] = attributeValues[i].toString(); + } + } + + return attributeStringValues; + } + private com.unboundid.ldap.sdk.Filter toLdapFilter(Filter genericFilter) throws SearchException { return LDAP_FILTER_CONVERTER.convertToLdapFilter(genericFilter); } diff --git a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/LdapOperationService.java b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/LdapOperationService.java index 424a3d78cab..7f19f920f0e 100644 --- a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/LdapOperationService.java +++ b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/LdapOperationService.java @@ -9,15 +9,6 @@ import java.util.Collection; import java.util.List; -import io.jans.orm.ldap.impl.LdapBatchOperationWraper; -import io.jans.orm.exception.operation.ConnectionException; -import io.jans.orm.exception.operation.DuplicateEntryException; -import io.jans.orm.exception.operation.SearchException; -import io.jans.orm.ldap.operation.impl.LdapConnectionProvider; -import io.jans.orm.model.PagedResult; -import io.jans.orm.model.SortOrder; -import io.jans.orm.operation.PersistenceOperationService; - import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.Control; import com.unboundid.ldap.sdk.Filter; @@ -25,11 +16,19 @@ import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.Modification; -import com.unboundid.ldap.sdk.SearchResult; -import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; import com.unboundid.ldif.LDIFChangeRecord; +import io.jans.orm.exception.operation.ConnectionException; +import io.jans.orm.exception.operation.DuplicateEntryException; +import io.jans.orm.exception.operation.SearchException; +import io.jans.orm.ldap.impl.LdapBatchOperationWraper; +import io.jans.orm.ldap.operation.impl.LdapConnectionProvider; +import io.jans.orm.model.EntryData; +import io.jans.orm.model.PagedResult; +import io.jans.orm.model.SortOrder; +import io.jans.orm.operation.PersistenceOperationService; + public interface LdapOperationService extends PersistenceOperationService { static final String DN = "dn"; @@ -54,26 +53,22 @@ public interface LdapOperationService extends PersistenceOperationService { void releaseConnection(LDAPConnection connection); - SearchResult search(String dn, Filter filter, SearchScope scope, LdapBatchOperationWraper batchOperationWraper, int start, - int searchLimit, int count, Control[] controls, String... attributes) throws SearchException; + PagedResult search(String dn, Filter filter, SearchScope scope, LdapBatchOperationWraper batchOperationWraper, int start, + int count, int pageSize, Control[] controls, String... attributes) throws SearchException; - List searchSearchResultEntryList(String dn, Filter filter, SearchScope scope, int startIndex, + PagedResult searchPagedEntries(String dn, Filter filter, SearchScope scope, int startIndex, int count, int pageSize, String sortBy, SortOrder sortOrder, - PagedResult vlvResponse, String... attributes) throws Exception; - - SearchResult searchVirtualListView(String dn, Filter filter, SearchScope scope, int start, int count, - String sortBy, SortOrder sortOrder, PagedResult vlvResponse, String... attributes) - throws Exception; + String... attributes) throws Exception; /** * Lookup entry in the directory * * @param dn * @param attributes - * @return SearchResultEntry + * @return EntryData * @throws ConnectionException */ - SearchResultEntry lookup(String dn, String... attributes) throws ConnectionException, SearchException; + EntryData lookup(String dn, String... attributes) throws ConnectionException, SearchException; /** * Use this method to add new entry diff --git a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/impl/LdapOperationServiceImpl.java b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/impl/LdapOperationServiceImpl.java index 9b6c1bbd6cc..a81d75a3058 100644 --- a/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/impl/LdapOperationServiceImpl.java +++ b/jans-orm/ldap/src/main/java/io/jans/orm/ldap/operation/impl/LdapOperationServiceImpl.java @@ -16,29 +16,12 @@ import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; -import io.jans.orm.ldap.exception.InvalidSimplePageControlException; -import io.jans.orm.ldap.impl.LdapBatchOperationWraper; -import io.jans.orm.ldap.operation.watch.OperationDurationUtil; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; - -import io.jans.orm.exception.AuthenticationException; -import io.jans.orm.exception.MappingException; -import io.jans.orm.exception.operation.ConnectionException; -import io.jans.orm.exception.operation.DuplicateEntryException; -import io.jans.orm.exception.operation.SearchException; -import io.jans.orm.extension.PersistenceExtension; -import io.jans.orm.ldap.operation.LdapOperationService; -import io.jans.orm.model.BatchOperation; -import io.jans.orm.model.PagedResult; -import io.jans.orm.model.SortOrder; -import io.jans.orm.operation.auth.PasswordEncryptionHelper; -import io.jans.orm.operation.auth.PasswordEncryptionMethod; -import io.jans.orm.util.ArrayHelper; -import io.jans.orm.util.Pair; -import io.jans.orm.util.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,16 +43,34 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; -import com.unboundid.ldap.sdk.SearchResultReference; import com.unboundid.ldap.sdk.SearchScope; -import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl; import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; -import com.unboundid.ldap.sdk.controls.SortKey; import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl; -import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl; -import com.unboundid.ldap.sdk.controls.VirtualListViewResponseControl; import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; import com.unboundid.ldif.LDIFChangeRecord; +import com.unboundid.util.StaticUtils; + +import io.jans.orm.exception.AuthenticationException; +import io.jans.orm.exception.MappingException; +import io.jans.orm.exception.SearchEntryException; +import io.jans.orm.exception.operation.ConnectionException; +import io.jans.orm.exception.operation.DuplicateEntryException; +import io.jans.orm.exception.operation.SearchException; +import io.jans.orm.extension.PersistenceExtension; +import io.jans.orm.ldap.exception.InvalidSimplePageControlException; +import io.jans.orm.ldap.impl.LdapBatchOperationWraper; +import io.jans.orm.ldap.operation.LdapOperationService; +import io.jans.orm.ldap.operation.watch.OperationDurationUtil; +import io.jans.orm.model.AttributeData; +import io.jans.orm.model.BatchOperation; +import io.jans.orm.model.EntryData; +import io.jans.orm.model.PagedResult; +import io.jans.orm.model.SortOrder; +import io.jans.orm.operation.auth.PasswordEncryptionHelper; +import io.jans.orm.operation.auth.PasswordEncryptionMethod; +import io.jans.orm.util.ArrayHelper; +import io.jans.orm.util.Pair; +import io.jans.orm.util.StringHelper; /** * OperationsFacade is the base class that performs all the ldap operations @@ -88,9 +89,10 @@ public class LdapOperationServiceImpl implements LdapOperationService { private PersistenceExtension persistenceExtension; private static Map> ATTRIBUTE_DATA_TYPES = new HashMap>(); - private static List OBJECT_CLASSES = new ArrayList(); private static final Map> OID_SYNTAX_CLASS_MAPPING; + protected static final String[] NO_STRINGS = new String[0]; + static { //Populates the mapping of syntaxes that will support comparison of attribute values. //Only accounting for the most common and existing in Jans Schema @@ -156,7 +158,9 @@ public LDAPConnection getConnection() throws LDAPException { @Override public void releaseConnection(LDAPConnection connection) { - connectionProvider.releaseConnection(connection); + if (connection != null) { + connectionProvider.releaseConnection(connection); + } } @Override @@ -176,12 +180,24 @@ private boolean authenticateImpl(final String bindDn, final String password) thr // Try to authenticate if the password was encrypted with additional mechanism List additionalPasswordMethods = this.connectionProvider.getAdditionalPasswordMethods(); if ((persistenceExtension != null) || !additionalPasswordMethods.isEmpty()) { - SearchResultEntry searchResult = lookup(bindDn, USER_PASSWORD); - if (searchResult == null) { - throw new ConnectionException("Failed to find use by dn"); + EntryData entryData = lookup(bindDn, USER_PASSWORD); + if (entryData == null) { + throw new ConnectionException("Failed to find user by dn"); } - String userPassword = searchResult.getAttribute(USER_PASSWORD).getValue(); + Object userPasswordObj = null; + for (AttributeData attribute : entryData.getAttributeData()) { + if (StringHelper.equalsIgnoreCase(attribute.getName(), USER_PASSWORD)) { + userPasswordObj = attribute.getValue(); + } + + } + + String userPassword = null; + if (userPasswordObj instanceof String) { + userPassword = (String) userPasswordObj; + } + if (userPassword != null) { if (persistenceExtension != null) { result = persistenceExtension.compareHashedPasswords(password, userPassword); @@ -248,63 +264,52 @@ private boolean authenticateBindConnectionPoolImpl(final String bindDn, final St } @Override - public SearchResult search(String dn, Filter filter, SearchScope scope, LdapBatchOperationWraper batchOperationWraper, int start, - int searchLimit, int count, Control[] controls, String... attributes) throws SearchException { + public PagedResult search(String dn, Filter filter, SearchScope scope, LdapBatchOperationWraper batchOperationWraper, int start, + int count, int pageSize, Control[] controls, String... attributes) throws SearchException { Instant startTime = OperationDurationUtil.instance().now(); - SearchResult result = searchImpl(dn, filter, scope, batchOperationWraper, start, searchLimit, count, controls, attributes); + PagedResult result = searchImpl(dn, filter, scope, batchOperationWraper, start, count, pageSize, controls, attributes); Duration duration = OperationDurationUtil.instance().duration(startTime); - OperationDurationUtil.instance().logDebug("LDAP operation: search, duration: {}, dn: {}, filter: {}, scope: {}, batchOperationWraper: {}, start: {}, searchLimit: {}, count: {}, controls: {}, attributes: {}", duration, dn, filter, scope, batchOperationWraper, start, searchLimit, count, controls, attributes); + OperationDurationUtil.instance().logDebug("LDAP operation: search, duration: {}, dn: {}, filter: {}, scope: {}, batchOperationWraper: {}, start: {}, searchLimit: {}, count: {}, controls: {}, attributes: {}", duration, dn, filter, scope, batchOperationWraper, start, pageSize, count, controls, attributes); return result; } - private SearchResult searchImpl(String dn, Filter filter, SearchScope scope, LdapBatchOperationWraper batchOperationWraper, int start, - int searchLimit, int count, Control[] controls, String... attributes) throws SearchException { - SearchRequest searchRequest; - - BatchOperation ldapBatchOperation = null; + private PagedResult searchImpl(String dn, Filter filter, SearchScope scope, LdapBatchOperationWraper batchOperationWraper, int start, + int count, int pageSize, Control[] controls, String... attributes) throws SearchException { + BatchOperation batchOperation = null; if (batchOperationWraper != null) { - ldapBatchOperation = (BatchOperation) batchOperationWraper.getBatchOperation(); + batchOperation = (BatchOperation) batchOperationWraper.getBatchOperation(); } if (LOG.isTraceEnabled()) { // Find whole tree search. This can be very slow - if (StringHelper.equalsIgnoreCase(dn, "o=jans")) { + if (StringHelper.equalsIgnoreCase(dn, "o=gluu")) { LOG.trace("Search in whole LDAP tree", new Exception()); } } + SearchRequest searchRequest; if (attributes == null) { searchRequest = new SearchRequest(dn, scope, filter); } else { searchRequest = new SearchRequest(dn, scope, filter, attributes); } - boolean useSizeLimit = count > 0; - - if (useSizeLimit) { - // Use paged result to limit search - searchLimit = count; - } + List searchResultList = new LinkedList(); SearchResult searchResult = null; - List searchResultList = new ArrayList(); - List searchResultEntries = new ArrayList(); - List searchResultReferences = new ArrayList(); - if ((searchLimit > 0) || (start > 0)) { - if (searchLimit == 0) { + if ((pageSize > 0) || (start > 0)) { + if (pageSize == 0) { // Default page size - searchLimit = 100; + pageSize = 100; } - boolean collectSearchResult; - LDAPConnection ldapConnection = null; try { - ldapConnection = getConnectionPool().getConnection(); + ldapConnection = getConnection(); ASN1OctetString cookie = null; SimplePagedResponse simplePagedResponse = null; if (start > 0) { @@ -319,30 +324,48 @@ private SearchResult searchImpl(String dn, Filter filter, SearchScope scope, } if ((cookie != null) && (cookie.getValueLength() == 0)) { - SearchResult searchResultTemp = simplePagedResponse.getLastSearchResult(); - return new SearchResult(searchResultTemp.getMessageID(), searchResultTemp.getResultCode(), searchResultTemp.getDiagnosticMessage(), - searchResultTemp.getMatchedDN(), searchResultTemp.getReferralURLs(), searchResultEntries, searchResultReferences, - searchResultEntries.size(), searchResultReferences.size(), searchResultTemp.getResponseControls()); + PagedResult result = new PagedResult(); + result.setEntries(searchResultList); + result.setEntriesCount(searchResultList.size()); + result.setStart(start); + + return result; } + boolean collectSearchResult; + + List lastResult = null; + int currentLimit; + int resultCount = 0; + int lastCountRows = 0; do { - collectSearchResult = true; - searchRequest.setControls(new Control[] {new SimplePagedResultsControl(searchLimit, cookie)}); + currentLimit = pageSize; + if (count > 0) { + currentLimit = Math.min(pageSize, count - resultCount); + } + + searchRequest.setControls(new Control[] {new SimplePagedResultsControl(currentLimit, cookie)}); setControls(searchRequest, controls); + searchResult = ldapConnection.search(searchRequest); + if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) { + throw new SearchEntryException(String.format("Failed to search entries with baseDN: %s, filter: %s", dn, filter)); + } + + lastResult = getEntryDataList(searchResult); + lastCountRows = lastResult.size(); - if (ldapBatchOperation != null) { - collectSearchResult = ldapBatchOperation.collectSearchResult(searchResult.getEntryCount()); + collectSearchResult = true; + if (batchOperation != null) { + collectSearchResult = batchOperation.collectSearchResult(searchResult.getEntryCount()); } if (collectSearchResult) { - searchResultList.add(searchResult); - searchResultEntries.addAll(searchResult.getSearchEntries()); - searchResultReferences.addAll(searchResult.getSearchReferences()); + searchResultList.addAll(lastResult); } - if (ldapBatchOperation != null) { - List entries = batchOperationWraper.createEntities(searchResult); - ldapBatchOperation.performAction(entries); + if (batchOperation != null) { + List entries = batchOperationWraper.createEntities(lastResult); + batchOperation.performAction(entries); } cookie = null; try { @@ -354,40 +377,48 @@ private SearchResult searchImpl(String dn, Filter filter, SearchScope scope, LOG.error("Error while accessing cookies" + ex.getMessage()); } - if (useSizeLimit) { + if (((count > 0) && (resultCount >= count)) || (lastCountRows < currentLimit)) { break; } - } while ((cookie != null) && (cookie.getValueLength() > 0)); + } while ((cookie != null) && (cookie.getValueLength() > 0) && (lastCountRows > 0)); } catch (LDAPException ex) { throw new SearchException("Failed to scroll to specified start", ex, ex.getResultCode().intValue()); } finally { - if (ldapConnection != null) { - getConnectionPool().releaseConnection(ldapConnection); - } - } - - if (!collectSearchResult) { - return new SearchResult(searchResult.getMessageID(), searchResult.getResultCode(), searchResult.getDiagnosticMessage(), - searchResult.getMatchedDN(), searchResult.getReferralURLs(), searchResultEntries, searchResultReferences, - searchResultEntries.size(), searchResultReferences.size(), searchResult.getResponseControls()); - } - - if (!searchResultList.isEmpty()) { - SearchResult searchResultTemp = searchResultList.get(0); - return new SearchResult(searchResultTemp.getMessageID(), searchResultTemp.getResultCode(), searchResultTemp.getDiagnosticMessage(), - searchResultTemp.getMatchedDN(), searchResultTemp.getReferralURLs(), searchResultEntries, searchResultReferences, - searchResultEntries.size(), searchResultReferences.size(), searchResultTemp.getResponseControls()); + releaseConnection(ldapConnection); } } else { setControls(searchRequest, controls); try { searchResult = getConnectionPool().search(searchRequest); + if (!ResultCode.SUCCESS.equals(searchResult.getResultCode())) { + throw new SearchEntryException(String.format("Failed to ssearch entries with baseDN: %s, filter: %s", dn, filter)); + } + + List lastResult = getEntryDataList(searchResult); + + boolean collectSearchResult = true; + if (batchOperation != null) { + collectSearchResult = batchOperation.collectSearchResult(searchResult.getEntryCount()); + } + if (collectSearchResult) { + searchResultList.addAll(lastResult); + } + + if (batchOperation != null) { + List entries = batchOperationWraper.createEntities(lastResult); + batchOperation.performAction(entries); + } } catch (LDAPSearchException ex) { throw new SearchException(ex.getMessage(), ex, ex.getResultCode().intValue()); } } - return searchResult; + PagedResult result = new PagedResult(); + result.setEntries(searchResultList); + result.setEntriesCount(searchResultList.size()); + result.setStart(start); + + return result; } private SimplePagedResponse scrollSimplePagedResultsControl(LDAPConnection ldapConnection, String dn, Filter filter, SearchScope scope, @@ -419,89 +450,93 @@ private SimplePagedResponse scrollSimplePagedResultsControl(LDAPConnection ldapC } @Override - public List searchSearchResultEntryList(String dn, Filter filter, SearchScope scope, int startIndex, + public PagedResult searchPagedEntries(String dn, Filter filter, SearchScope scope, int startIndex, int count, int pageSize, String sortBy, SortOrder sortOrder, - PagedResult vlvResponse, String... attributes) throws Exception { + String... attributes) throws Exception { Instant startTime = OperationDurationUtil.instance().now(); - List result = searchSearchResultEntryListImpl(dn, filter, scope, startIndex, count, pageSize, sortBy, sortOrder, vlvResponse, attributes); + PagedResult result = searchSearchResultEntryListImpl(dn, filter, scope, startIndex, count, pageSize, sortBy, sortOrder, attributes); Duration duration = OperationDurationUtil.instance().duration(startTime); - OperationDurationUtil.instance().logDebug("LDAP operation: search_result_list, duration: {}, dn: {}, filter: {}, scope: {}, startIndex: {}, count: {}, pageSize: {}, sortBy: {}, sortOrder: {}, vlvResponse: {}, attributes: {}", duration, dn, filter, scope, startIndex, count, pageSize, sortBy, sortOrder, vlvResponse, attributes); + OperationDurationUtil.instance().logDebug("LDAP operation: search_result_list, duration: {}, dn: {}, filter: {}, scope: {}, startIndex: {}, count: {}, pageSize: {}, sortBy: {}, sortOrder: {}, attributes: {}, result: {}", duration, dn, filter, scope, startIndex, count, pageSize, sortBy, sortOrder, attributes, result); return result; } - private List searchSearchResultEntryListImpl(String dn, Filter filter, SearchScope scope, int start, int count, - int pageSize, String sortBy, SortOrder sortOrder, PagedResult vlvResponse, String... attributes) throws LDAPException, Exception { + private PagedResult searchSearchResultEntryListImpl(String dn, Filter filter, SearchScope scope, int start, int count, + int pageSize, String sortBy, SortOrder sortOrder, String... attributes) throws LDAPException, Exception { //This method does not assume that count <= pageSize as occurs in SCIM, but it's more general - //Why this? - if (StringHelper.equalsIgnoreCase(dn, "o=jans")) { - (new Exception()).printStackTrace(); - } - + List searchResultEntryList = new ArrayList(); List searchEntries; - ASN1OctetString resumeCookie = null; - LDAPConnection conn = getConnection(); - SearchRequest searchRequest = new SearchRequest(dn, scope, filter, attributes); - int totalResults = 0; - do { - //Keep searching while we reach start index... - SearchResult searchResult = nextSearchResult(conn, searchRequest, pageSize, resumeCookie); - searchEntries = searchResult.getSearchEntries(); - totalResults += searchEntries.size(); - - resumeCookie = getSearchResultCookie(searchResult); - } while (totalResults < start && resumeCookie != null); - - List searchResultEntryList = new ArrayList(); - - if (totalResults > start) { - //Take the interesting ones, ie. skip [0, start) interval - int lowerBound = searchEntries.size() - (totalResults - start); - int upperBound = Math.min(searchEntries.size(), lowerBound + count); - searchResultEntryList.addAll(searchEntries.subList(lowerBound, upperBound)); - } - - //Continue adding results till reaching count if needed - while (resumeCookie != null && totalResults < count + start) { - SearchResult searchResult = nextSearchResult(conn, searchRequest, pageSize, resumeCookie); - searchEntries = searchResult.getSearchEntries(); - searchResultEntryList.addAll(searchEntries); - totalResults += searchEntries.size(); - - resumeCookie = getSearchResultCookie(searchResult); + ASN1OctetString resumeCookie = null; + LDAPConnection conn = null; + try { + conn = getConnection(); + SearchRequest searchRequest = new SearchRequest(dn, scope, filter, attributes); + + + do { + //Keep searching while we reach start index... + SearchResult searchResult = nextSearchResult(conn, searchRequest, pageSize, resumeCookie); + searchEntries = searchResult.getSearchEntries(); + totalResults += searchEntries.size(); + + resumeCookie = getSearchResultCookie(searchResult); + } while (totalResults < start && resumeCookie != null); + + + if (totalResults > start) { + //Take the interesting ones, ie. skip [0, start) interval + int lowerBound = searchEntries.size() - (totalResults - start); + int upperBound = Math.min(searchEntries.size(), lowerBound + count); + searchResultEntryList.addAll(searchEntries.subList(lowerBound, upperBound)); + } + + //Continue adding results till reaching count if needed + while (resumeCookie != null && totalResults < count + start) { + SearchResult searchResult = nextSearchResult(conn, searchRequest, pageSize, resumeCookie); + searchEntries = searchResult.getSearchEntries(); + searchResultEntryList.addAll(searchEntries); + totalResults += searchEntries.size(); + + resumeCookie = getSearchResultCookie(searchResult); + } + + if (totalResults > count + start) { + //Remove the uninteresting tail + searchResultEntryList = searchResultEntryList.subList(0, count); + } + + //skip the rest and update the number of total results only + while (resumeCookie != null) { + SearchResult searchResult = nextSearchResult(conn, searchRequest, pageSize, resumeCookie); + searchEntries = searchResult.getSearchEntries(); + totalResults += searchEntries.size(); + + resumeCookie = getSearchResultCookie(searchResult); + } + + if (StringUtils.isNotEmpty(sortBy)) { + boolean ascending = sortOrder == null || sortOrder.equals(SortOrder.ASCENDING); + searchResultEntryList = sortListByAttributes(searchResultEntryList, SearchResultEntry.class, false, ascending, sortBy); + } + } finally { + releaseConnection(conn); } - if (totalResults > count + start) { - //Remove the uninteresting tail - searchResultEntryList = searchResultEntryList.subList(0, count); - } - //skip the rest and update the number of total results only - while (resumeCookie != null) { - SearchResult searchResult = nextSearchResult(conn, searchRequest, pageSize, resumeCookie); - searchEntries = searchResult.getSearchEntries(); - totalResults += searchEntries.size(); + List entryDataList = getEntryDataList(searchResultEntryList); - resumeCookie = getSearchResultCookie(searchResult); - } + PagedResult result = new PagedResult(); + result.setEntries(entryDataList); + result.setEntriesCount(entryDataList.size()); + result.setTotalEntriesCount(totalResults); + result.setStart(start); - if (StringUtils.isNotEmpty(sortBy)) { - boolean ascending = sortOrder == null || sortOrder.equals(SortOrder.ASCENDING); - searchResultEntryList = sortListByAttributes(searchResultEntryList, SearchResultEntry.class, false, ascending, sortBy); - } - - // Get results info - vlvResponse.setEntriesCount(searchResultEntryList.size()); - vlvResponse.setTotalEntriesCount(totalResults); - vlvResponse.setStart(start); - - releaseConnection(conn); - return searchResultEntryList; + return result; } private ASN1OctetString getSearchResultCookie(SearchResult searchResult) throws Exception { @@ -524,68 +559,6 @@ private SearchResult nextSearchResult(LDAPConnection connection, SearchRequest s } - @Deprecated - public SearchResult searchVirtualListView(String dn, Filter filter, SearchScope scope, int start, int count, String sortBy, - SortOrder sortOrder, PagedResult vlvResponse, String... attributes) throws Exception { - Instant startTime = OperationDurationUtil.instance().now(); - - SearchResult result = searchVirtualListViewImpl(dn, filter, scope, start, count, sortBy, sortOrder, vlvResponse, attributes); - - Duration duration = OperationDurationUtil.instance().duration(startTime); - OperationDurationUtil.instance().logDebug("LDAP operation: search_virtual_list_view, duration: {}, dn: {}, filter: {}, scope: {}, start: {}, count: {}, sortBy: {}, sortOrder: {}, vlvResponse: {}, attributes: {}", duration, dn, filter, scope, start, count, sortBy, sortOrder, vlvResponse, attributes); - - return result; - } - - private SearchResult searchVirtualListViewImpl(String dn, Filter filter, SearchScope scope, int start, int count, String sortBy, - SortOrder sortOrder, PagedResult vlvResponse, String... attributes) throws LDAPSearchException, LDAPException { - if (StringHelper.equalsIgnoreCase(dn, "o=jans")) { - (new Exception()).printStackTrace(); - } - - SearchRequest searchRequest; - - if (attributes == null) { - searchRequest = new SearchRequest(dn, scope, filter); - } else { - searchRequest = new SearchRequest(dn, scope, filter, attributes); - } - - // start and count should be "cleansed" before arriving here - int targetOffset = start; - int beforeCount = 0; - int afterCount = (count > 0) ? (count - 1) : 0; - int contentCount = 0; - - boolean reverseOrder = false; - if (sortOrder != null) { - reverseOrder = sortOrder.equals(SortOrder.DESCENDING) ? true : false; - } - - // Note that the VLV control always requires the server-side sort control. - searchRequest.setControls(new ServerSideSortRequestControl(new SortKey(sortBy, reverseOrder)), - new VirtualListViewRequestControl(targetOffset, beforeCount, afterCount, contentCount, null)); - - SearchResult searchResult = getConnectionPool().search(searchRequest); - - /* - * for (SearchResultEntry searchResultEntry : searchResult.getSearchEntries()) { - * log.info("##### searchResultEntry = " + searchResultEntry.toString()); } - */ - - // LDAPTestUtils.assertHasControl(searchResult, - // VirtualListViewResponseControl.VIRTUAL_LIST_VIEW_RESPONSE_OID); - - VirtualListViewResponseControl vlvResponseControl = VirtualListViewResponseControl.get(searchResult); - - // Get results info - vlvResponse.setEntriesCount(searchResult.getEntryCount()); - vlvResponse.setTotalEntriesCount(vlvResponseControl.getContentCount()); - vlvResponse.setStart(vlvResponseControl.getTargetPosition()); - - return searchResult; - } - private void setControls(SearchRequest searchRequest, Control... controls) { if (!ArrayHelper.isEmpty(controls)) { Control[] newControls; @@ -600,10 +573,10 @@ private void setControls(SearchRequest searchRequest, Control... controls) { } @Override - public SearchResultEntry lookup(String dn, String... attributes) throws ConnectionException, SearchException { + public EntryData lookup(String dn, String... attributes) throws ConnectionException, SearchException { Instant startTime = OperationDurationUtil.instance().now(); - SearchResultEntry result = lookupImpl(dn, attributes); + EntryData result = lookupImpl(dn, attributes); Duration duration = OperationDurationUtil.instance().duration(startTime); OperationDurationUtil.instance().logDebug("LDAP operation: lookup, duration: {}, dn: {}, attributes: {}", duration, dn, attributes); @@ -611,15 +584,16 @@ public SearchResultEntry lookup(String dn, String... attributes) throws Connecti return result; } - private SearchResultEntry lookupImpl(String dn, String... attributes) throws SearchException { + private EntryData lookupImpl(String dn, String... attributes) throws SearchException { try { - SearchResultEntry result; + SearchResultEntry searchResultEntry; if (attributes == null) { - result = getConnectionPool().getEntry(dn); + searchResultEntry = getConnectionPool().getEntry(dn); } else { - result = getConnectionPool().getEntry(dn, attributes); + searchResultEntry = getConnectionPool().getEntry(dn, attributes); } + EntryData result = getEntryData(searchResultEntry); if (result != null) { return result; } @@ -837,6 +811,142 @@ public boolean destroy() { return result; } + private EntryData getEntryData(SearchResultEntry entry) { + List attributeData = getAttributeDataList(entry); + if (attributeData == null) { + return null; + } + + EntryData result = new EntryData(entry.getDN(), attributeData); + + return result; + } + + private List getAttributeDataList(SearchResultEntry entry) { + if (entry == null) { + return null; + } + + List result = new ArrayList(); + for (Attribute attribute : entry.getAttributes()) { + Object[] attributeValues = NO_STRINGS; + String attributeName = attribute.getName(); + + if (LOG.isTraceEnabled()) { + if (attribute.needsBase64Encoding()) { + LOG.trace("Found binary attribute: " + attributeName + ". Is defined in LDAP config: " + + isBinaryAttribute(attributeName)); + } + } + + if (attribute.needsBase64Encoding()) { + boolean binaryAttribute = isBinaryAttribute(attributeName); + boolean certificateAttribute = isCertificateAttribute(attributeName); + + if (binaryAttribute || certificateAttribute) { + byte[][] attributeValuesByteArrays = attribute.getValueByteArrays(); + if (attributeValuesByteArrays != null) { + attributeValues = new String[attributeValuesByteArrays.length]; + for (int i = 0; i < attributeValuesByteArrays.length; i++) { + attributeValues[i] = Base64.encodeBase64String(attributeValuesByteArrays[i]); + LOG.trace("Binary attribute: " + attribute.getName() + " value (hex): " + + org.apache.commons.codec.binary.Hex.encodeHexString(attributeValuesByteArrays[i]) + " value (base64): " + + attributeValues[i]); + } + } + } else { + attributeValues = attribute.getValues(); + } + if (certificateAttribute) { + attributeName = getCertificateAttributeName(attributeName); + } + } else { + attributeValues = attribute.getValues(); + + String attributeNameLower = attribute.getName().toLowerCase(); + Class attributeType = ATTRIBUTE_DATA_TYPES.get(attributeNameLower); + if (attributeType != null) { + // Attempt to convert values to required java types + if (attributeType.equals(Integer.class)) { + Integer[] attributeValuesTyped = new Integer[attributeValues.length]; + for (int i = 0; i < attributeValues.length; i++) { + try { + if (attributeValues[i] != null) { + attributeValuesTyped[i] = Integer.valueOf(((String) attributeValues[i])); + } + } catch (final NumberFormatException ex) { + attributeValuesTyped[i] = null; + LOG.debug("Failed to parse integer", ex); + } + } + attributeValues = attributeValuesTyped; + } else if (attributeType.equals(Boolean.class)) { + Boolean[] attributeValuesTyped = new Boolean[attributeValues.length]; + for (int i = 0; i < attributeValues.length; i++) { + if (attributeValues[i] != null) { + String lowerValue = StringHelper.toLowerCase((String) attributeValues[i]); + if (lowerValue.equals("true") || lowerValue.equals("t") || lowerValue.equals("yes") + || lowerValue.equals("y") || lowerValue.equals("on") + || lowerValue.equals("1")) { + attributeValuesTyped[i] = Boolean.TRUE; + } else if (lowerValue.equals("false") || lowerValue.equals("f") + || lowerValue.equals("no") || lowerValue.equals("n") || lowerValue.equals("off") + || lowerValue.equals("0")) { + attributeValuesTyped[i] = Boolean.FALSE; + } else { + attributeValuesTyped[i] = null; + } + } + } + attributeValues = attributeValuesTyped; + } else if (attributeType.equals(Date.class)) { + Date[] attributeValuesTyped = new Date[attributeValues.length]; + for (int i = 0; i < attributeValues.length; i++) { + if (attributeValues[i] != null) { + try { + attributeValuesTyped[i] = StaticUtils.decodeGeneralizedTime((String) attributeValues[i]); + } catch (Exception ex) { + attributeValuesTyped[i] = null; + LOG.debug("Failed to parse date", ex); + } + } + } + attributeValues = attributeValuesTyped; + } + } + } + + boolean multiValued = attributeValues.length > 1; + AttributeData tmpAttribute = new AttributeData(attributeName, attributeValues, multiValued); + result.add(tmpAttribute); + } + + return result; + } + + private List getEntryDataList(SearchResult searchResult) { + List searchResultEntries = searchResult.getSearchEntries(); + + List entryDataList = getEntryDataList(searchResultEntries); + + return entryDataList; + } + + private List getEntryDataList(List searchResultEntries) { + List entryDataList = new LinkedList<>(); + for (SearchResultEntry entry : searchResultEntries) { + List attributeDataList = getAttributeDataList(entry); + if (attributeDataList == null) { + break; + } + + EntryData entryData = new EntryData(entry.getDN(), attributeDataList); + entryDataList.add(entryData); + } + + return entryDataList; + } + @Override public boolean isBinaryAttribute(String attributeName) { return this.connectionProvider.isBinaryAttribute(attributeName); @@ -920,11 +1030,10 @@ public List sortListByAttributes(List searchResultEntries, Class cl } private void populateAttributeDataTypesMapping(String schemaEntryDn) { - try { if (ATTRIBUTE_DATA_TYPES.size() == 0) { //schemaEntryDn="ou=schema"; - SearchResultEntry entry = lookup(schemaEntryDn, "attributeTypes"); + SearchResultEntry entry = getConnectionPool().getEntry(schemaEntryDn, "attributeTypes"); Attribute attrAttributeTypes = entry.getAttribute("attributeTypes"); Map> tmpMap = new HashMap>(); @@ -961,7 +1070,7 @@ private void populateAttributeDataTypesMapping(String schemaEntryDn) { if (syntaxOID != null) { Class cls = OID_SYNTAX_CLASS_MAPPING.get(syntaxOID); if (cls != null) { - ATTRIBUTE_DATA_TYPES.put(name, cls); + ATTRIBUTE_DATA_TYPES.put(name.toLowerCase(), cls); } } } @@ -1043,26 +1152,26 @@ public int compare(T entry1, T entry2, String attributeName) { Class cls = ATTRIBUTE_DATA_TYPES.get(attributeName); if (cls != null) { - if (cls.equals(String.class)) { - if (caseSensitive) { - result = value1.compareTo(value2); - } else { - result = value1.toLowerCase().compareTo(value2.toLowerCase()); - } - } else if (cls.equals(Integer.class)) { - result = resultEntry1.getAttributeValueAsInteger(attributeName) + return resultEntry1.getAttributeValueAsInteger(attributeName) .compareTo(resultEntry2.getAttributeValueAsInteger(attributeName)); } else if (cls.equals(Boolean.class)) { - result = resultEntry1.getAttributeValueAsBoolean(attributeName) + return resultEntry1.getAttributeValueAsBoolean(attributeName) .compareTo(resultEntry2.getAttributeValueAsBoolean(attributeName)); } else if (cls.equals(Date.class)) { - result = resultEntry1.getAttributeValueAsDate(attributeName) + return resultEntry1.getAttributeValueAsDate(attributeName) .compareTo(resultEntry2.getAttributeValueAsDate(attributeName)); } } + + // Default comparision + if (caseSensitive) { + result = value1.compareTo(value2); + } else { + result = value1.toLowerCase().compareTo(value2.toLowerCase()); + } } } } diff --git a/jans-orm/spanner/src/main/java/io/jans/orm/cloud/spanner/impl/SpannerEntryManager.java b/jans-orm/spanner/src/main/java/io/jans/orm/cloud/spanner/impl/SpannerEntryManager.java index a5c5839ea60..6ee8916d43a 100644 --- a/jans-orm/spanner/src/main/java/io/jans/orm/cloud/spanner/impl/SpannerEntryManager.java +++ b/jans-orm/spanner/src/main/java/io/jans/orm/cloud/spanner/impl/SpannerEntryManager.java @@ -567,7 +567,7 @@ protected List createEntities(Class entryClass, List boolean authenticate(String baseDN, Class entryClass, String userN return false; } - AttributeData attributeData = searchResult.getEntries().get(0).getAttributeDate(SpannerOperationService.DN); + AttributeData attributeData = searchResult.getEntries().get(0).getAttributeData(SpannerOperationService.DN); if ((attributeData == null) || (attributeData.getValue() == null)) { throw new AuthenticationException("Failed to find user DN in entry: '%s'"); } diff --git a/jans-orm/sql-sample/src/main/java/io/jans/orm/sql/SqlPagedUserSearchSample.java b/jans-orm/sql-sample/src/main/java/io/jans/orm/sql/SqlPagedUserSearchSample.java new file mode 100644 index 00000000000..34c26687cd8 --- /dev/null +++ b/jans-orm/sql-sample/src/main/java/io/jans/orm/sql/SqlPagedUserSearchSample.java @@ -0,0 +1,57 @@ +/* + * oxCore is available under the MIT License (2014). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.orm.sql; + +import java.util.List; + +import io.jans.orm.model.PagedResult; +import io.jans.orm.model.SortOrder; +import io.jans.orm.search.filter.Filter; +import io.jans.orm.sql.impl.SqlEntryManager; +import io.jans.orm.sql.model.SimpleGroup; +import io.jans.orm.sql.model.SimpleUser; +import io.jans.orm.sql.operation.impl.SqlConnectionProvider; +import io.jans.orm.sql.persistence.SqlEntryManagerSample; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Yuriy Movchan Date: 01/15/2020 + */ +public final class SqlPagedUserSearchSample { + + private static final Logger LOG = LoggerFactory.getLogger(SqlConnectionProvider.class); + + private SqlPagedUserSearchSample() { + } + + public static void main(String[] args) throws InterruptedException { + // Prepare sample connection details + final SqlEntryManagerSample sqlEntryManagerSample = new SqlEntryManagerSample(); + final SqlEntryManager sqlEntryManager = sqlEntryManagerSample.createSqlEntryManager(); + + try { + int start = 0; + int lastCount = 0; + boolean first = true; + while (first || (lastCount > 0) ) { + PagedResult listViewResponse = sqlEntryManager.findPagedEntries("o=gluu", SimpleUser.class, null, + new String[] { "uid", "displayName", "gluuStatus" }, "uid", SortOrder.ASCENDING, start + 1000, 1000, 333); + lastCount = listViewResponse.getEntriesCount(); + start += 1000; + first = false; + System.out.println("Loaded count: " + lastCount + " / " + listViewResponse.getTotalEntriesCount()); + + LOG.info("Found persons: " + listViewResponse.getEntriesCount() + ", total persons: " + listViewResponse.getTotalEntriesCount()); + } + } catch (Exception ex) { + LOG.info("Failed to search", ex); + } + + } + +} \ No newline at end of file diff --git a/jans-orm/sql/src/main/java/io/jans/orm/sql/impl/SqlEntryManager.java b/jans-orm/sql/src/main/java/io/jans/orm/sql/impl/SqlEntryManager.java index 7e0fb92c498..0ae324d9956 100644 --- a/jans-orm/sql/src/main/java/io/jans/orm/sql/impl/SqlEntryManager.java +++ b/jans-orm/sql/src/main/java/io/jans/orm/sql/impl/SqlEntryManager.java @@ -577,7 +577,7 @@ protected List createEntities(Class entryClass, List boolean authenticate(String baseDN, Class entryClass, String userN return false; } - AttributeData attributeData = searchResult.getEntries().get(0).getAttributeDate(SqlOperationService.DN); + AttributeData attributeData = searchResult.getEntries().get(0).getAttributeData(SqlOperationService.DN); if ((attributeData == null) || (attributeData.getValue() == null)) { throw new AuthenticationException("Failed to find user DN in entry: '%s'"); } diff --git a/jans-orm/sql/src/main/java/io/jans/orm/sql/operation/impl/SqlOperationServiceImpl.java b/jans-orm/sql/src/main/java/io/jans/orm/sql/operation/impl/SqlOperationServiceImpl.java index 020994c3856..a2968115d08 100644 --- a/jans-orm/sql/src/main/java/io/jans/orm/sql/operation/impl/SqlOperationServiceImpl.java +++ b/jans-orm/sql/src/main/java/io/jans/orm/sql/operation/impl/SqlOperationServiceImpl.java @@ -135,7 +135,7 @@ private boolean authenticateImpl(String key, String password, String objectClass if (password != null) { try { List attributes = lookup(key, objectClass, USER_PASSWORD); - + Object userPasswordObj = null; for (AttributeData attribute : attributes) { if (StringHelper.equalsIgnoreCase(attribute.getName(), USER_PASSWORD)) { @@ -461,8 +461,6 @@ private PagedResult searchImpl(TableMapping tableMapping, String int resultCount = 0; int lastCountRows = 0; do { - collectSearchResult = true; - currentLimit = pageSize; if (count > 0) { currentLimit = Math.min(pageSize, count - resultCount); @@ -478,6 +476,7 @@ private PagedResult searchImpl(TableMapping tableMapping, String lastCountRows = lastResult.size(); + collectSearchResult = true; if (batchOperation != null) { collectSearchResult = batchOperation.collectSearchResult(lastCountRows); } @@ -687,9 +686,8 @@ private List getAttributeDataList(TableMapping tableMapping, Resu private List getEntryDataList(TableMapping tableMapping, ResultSet resultSet) throws EntryConvertationException, SQLException { List entryDataList = new LinkedList<>(); - List attributeDataList = null; while (!resultSet.isLast()) { - attributeDataList = getAttributeDataList(tableMapping, resultSet, false); + List attributeDataList = getAttributeDataList(tableMapping, resultSet, false); if (attributeDataList == null) { break; } diff --git a/jans-orm/standalone/src/main/java/io/jans/orm/service/StandalonePersistanceFactoryService.java b/jans-orm/standalone/src/main/java/io/jans/orm/service/StandalonePersistanceFactoryService.java index 30016c085df..07c438b6d2e 100644 --- a/jans-orm/standalone/src/main/java/io/jans/orm/service/StandalonePersistanceFactoryService.java +++ b/jans-orm/standalone/src/main/java/io/jans/orm/service/StandalonePersistanceFactoryService.java @@ -9,6 +9,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -29,6 +30,7 @@ public class StandalonePersistanceFactoryService extends PersistanceFactoryServi private HashMap persistenceEntryManagerFactoryNames; private HashMap, PersistenceEntryManagerFactory> persistenceEntryManagerFactoryTypes; + private Set initializedFactories = new HashSet<>(); @Override public PersistenceEntryManagerFactory getPersistenceEntryManagerFactory(PersistenceConfiguration persistenceConfiguration) { @@ -43,10 +45,19 @@ public PersistenceEntryManagerFactory getPersistenceEntryManagerFactory(Class T[] arrayClone(T[] array) { return clonedArray; } + @SuppressWarnings("unchecked") + public static T[] arrayClone(T[] array, Class componentType) { + T[] clonedArray = (T[]) Array.newInstance(componentType, array.length); + System.arraycopy(array, 0, clonedArray, 0, array.length); + + return clonedArray; + } + public static T[] sortAndClone(T[] array) { if (array == null) { return array;