From 2839a78d5b5677187970417f63d72df2f4fc3caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Chemin?= Date: Tue, 5 Dec 2023 13:41:53 +0100 Subject: [PATCH] [ENH] :sparkles: Connections to ldap can be retried MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cécile Chemin --- .../config/LdapRealmProviderDAOImpl.java | 37 ++-- .../sugoi/store/ldap/LdapReaderStore.java | 28 +-- .../fr/insee/sugoi/store/ldap/LdapStore.java | 7 +- .../sugoi/store/ldap/LdapStoreBeans.java | 9 + .../sugoi/store/ldap/LdapWriterStore.java | 79 ++++++--- .../insee/sugoi/ldap/utils/LdapFactory.java | 70 +++----- .../ldap/utils/RetriableLDAPException.java | 20 +++ .../ldap/utils/RetriableLdapConnection.java | 68 ++++++++ .../utils/RetriableLdapConnectionPool.java | 165 ++++++++++++++++++ .../ldap/utils/config/LdapConfigKeys.java | 3 +- 10 files changed, 375 insertions(+), 111 deletions(-) create mode 100644 sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLDAPException.java create mode 100644 sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnection.java create mode 100644 sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnectionPool.java diff --git a/sugoi-api-ldap-config-provider/src/main/java/fr/insee/sugoi/config/LdapRealmProviderDAOImpl.java b/sugoi-api-ldap-config-provider/src/main/java/fr/insee/sugoi/config/LdapRealmProviderDAOImpl.java index 2ea8308b1..12b8aa0a5 100644 --- a/sugoi-api-ldap-config-provider/src/main/java/fr/insee/sugoi/config/LdapRealmProviderDAOImpl.java +++ b/sugoi-api-ldap-config-provider/src/main/java/fr/insee/sugoi/config/LdapRealmProviderDAOImpl.java @@ -15,7 +15,6 @@ import com.unboundid.ldap.sdk.AddRequest; import com.unboundid.ldap.sdk.Filter; -import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ModifyRequest; import com.unboundid.ldap.sdk.SearchRequest; @@ -29,6 +28,8 @@ import fr.insee.sugoi.core.realm.RealmProvider; import fr.insee.sugoi.ldap.utils.LdapFactory; import fr.insee.sugoi.ldap.utils.LdapFilter; +import fr.insee.sugoi.ldap.utils.RetriableLDAPException; +import fr.insee.sugoi.ldap.utils.RetriableLdapConnectionPool; import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; import fr.insee.sugoi.ldap.utils.exception.LdapStoreConnectionFailedException; import fr.insee.sugoi.ldap.utils.mapper.RealmLdapMapper; @@ -40,6 +41,7 @@ import fr.insee.sugoi.model.exceptions.RealmAlreadyExistException; import fr.insee.sugoi.model.exceptions.RealmNotFoundException; import fr.insee.sugoi.model.exceptions.RealmWriteFailureException; +import fr.insee.sugoi.model.exceptions.StoreException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -107,13 +109,16 @@ public class LdapRealmProviderDAOImpl implements RealmProvider { @Value("${fr.insee.sugoi.config.ldap.default.sortKey:}") private String defaultSortKey; + @Value("${fr.insee.sugoi.config.ldap.default.max-retries:10}") + private String maxRetries; + @Autowired UiMappingService uiMappingService; private static final Logger logger = LoggerFactory.getLogger(LdapRealmProviderDAOImpl.class); - private LDAPConnectionPool ldapConnectionPoolAuthenticated; + private RetriableLdapConnectionPool ldapConnectionPoolAuthenticated; - private LDAPConnectionPool ldapConnectionPool; + private RetriableLdapConnectionPool ldapConnectionPool; @Value("${fr.insee.sugoi.users.maxoutputsize:1000}") private String defaultUserMaxOutputSize; @@ -138,7 +143,7 @@ public Optional load(String realmName) { } else { return Optional.empty(); } - } catch (LDAPException e) { + } catch (RetriableLDAPException | LDAPException e) { throw new RealmNotFoundException("Impossible de charger le realm " + realmName, e); } } @@ -155,8 +160,8 @@ public List findAll() { realms.add(generateRealmFromSearchEntry(searchEntry)); } return realms; - } catch (LDAPException e) { - throw new RealmNotFoundException("Impossible de charger les realms", e); + } catch (RetriableLDAPException | LDAPException e) { + throw new StoreException("Impossible de charger les realms", e); } } @@ -187,7 +192,7 @@ public ProviderResponse createRealm(Realm realm, ProviderRequest providerRequest response.setStatus(ProviderResponseStatus.OK); response.setEntityId(realm.getName()); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new RealmWriteFailureException("Failed to create realm " + realm.getName(), e); } } else { @@ -216,7 +221,7 @@ public ProviderResponse updateRealm(Realm realm, ProviderRequest providerRequest response.setStatus(ProviderResponseStatus.OK); response.setEntityId(realm.getName()); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException | LDAPException e) { throw new RealmWriteFailureException("Failed to update realm " + realm.getName(), e); } } else { @@ -244,14 +249,14 @@ public ProviderResponse deleteRealm(String realmName, ProviderRequest providerRe response.setStatus(ProviderResponseStatus.OK); response.setEntityId(realmName); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new RealmWriteFailureException("Failed to delete realm " + realmName, e); } } private List loadUserStorages( String realmName, Map> defaultRealmProperties) - throws LDAPException { + throws RetriableLDAPException, LDAPException { List userstorages = ldapPoolConnection() .search( @@ -268,7 +273,8 @@ private List loadUserStorages( return userstorages; } - private Realm generateRealmFromSearchEntry(SearchResultEntry searchEntry) throws LDAPException { + private Realm generateRealmFromSearchEntry(SearchResultEntry searchEntry) + throws RetriableLDAPException, LDAPException { Realm realm = RealmLdapMapper.mapFromSearchEntry(searchEntry); realm.setUserStorages(loadUserStorages(realm.getName(), realm.getProperties())); addDefaultOnRealm(realm); @@ -330,7 +336,7 @@ private void addDefaultOnRealm(Realm realm) { List.of(defaultOrganizationMaxOutputSize)); } - private LDAPConnectionPool ldapPoolConnection() { + private RetriableLdapConnectionPool ldapPoolConnection() { try { if (ldapConnectionPool == null) { if (useAuthenticatedConnectionForReading) { @@ -351,7 +357,7 @@ private LDAPConnectionPool ldapPoolConnection() { } } - private LDAPConnectionPool ldapConnectionPoolAuthenticated() { + private RetriableLdapConnectionPool ldapConnectionPoolAuthenticated() { try { if (ldapConnectionPoolAuthenticated == null) { Map config = new HashMap<>(); @@ -361,9 +367,10 @@ private LDAPConnectionPool ldapConnectionPoolAuthenticated() { config.put(LdapConfigKeys.USERNAME, defaultUsername); config.put(LdapConfigKeys.PASSWORD, defaultPassword); config.put(LdapConfigKeys.LDAP_CONNECTION_TIMEOUT, connectionTimeout); - ldapConnectionPool = LdapFactory.getConnectionPoolAuthenticated(config); + config.put(LdapConfigKeys.MAX_RETRIES, maxRetries); + ldapConnectionPoolAuthenticated = LdapFactory.getConnectionPoolAuthenticated(config); } - return ldapConnectionPool; + return ldapConnectionPoolAuthenticated; } catch (LDAPException e) { throw new LdapStoreConnectionFailedException( String.format("Failed authenticated connection to ldap realm store %s:%d", url, port), e); diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java index 095206202..593c57ee4 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapReaderStore.java @@ -19,6 +19,7 @@ import fr.insee.sugoi.ldap.utils.LdapFactory; import fr.insee.sugoi.ldap.utils.LdapFilter; import fr.insee.sugoi.ldap.utils.LdapUtils; +import fr.insee.sugoi.ldap.utils.RetriableLDAPException; import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; import fr.insee.sugoi.ldap.utils.mapper.AddressLdapMapper; import fr.insee.sugoi.ldap.utils.mapper.ApplicationLdapMapper; @@ -53,10 +54,8 @@ public LdapReaderStore( if (Boolean.TRUE.equals( Boolean.valueOf(config.get(LdapConfigKeys.READ_CONNECTION_AUTHENTICATED)))) { this.ldapPoolConnection = LdapFactory.getConnectionPoolAuthenticated(config); - this.ldapMonoConnection = LdapFactory.getSingleConnectionAuthenticated(config); } else { this.ldapPoolConnection = LdapFactory.getConnectionPool(config); - this.ldapMonoConnection = LdapFactory.getSingleConnection(config); } this.config = config; userLdapMapper = new UserLdapMapper(config, mappings.get(MappingType.USERMAPPING)); @@ -300,7 +299,7 @@ private SearchResultEntry getEntryByDn(String dn) { logger.debug("Fetching {}", dn); return ldapPoolConnection.getEntry(dn, "+", "*"); - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to execute " + dn, e); } } @@ -326,25 +325,16 @@ private PageResult searchOnLdap( if (pageableResult != null) { LdapUtils.setRequestControls(searchRequest, pageableResult, config); } - SearchResult searchResult = null; + SearchResult searchResult; try { - searchResult = ldapMonoConnection.search(searchRequest); - } catch (LDAPException e) { - if (e.getResultCode().intValue() == ResultCode.SERVER_DOWN_INT_VALUE) { - try { - if (Boolean.TRUE.equals( - Boolean.valueOf(config.get(LdapConfigKeys.READ_CONNECTION_AUTHENTICATED)))) { - ldapMonoConnection = LdapFactory.getSingleConnectionAuthenticated(config, true); - } else { - ldapMonoConnection = LdapFactory.getSingleConnection(config, true); - } - } catch (LDAPException e1) { - throw new StoreException("Search failed", e1); - } - searchResult = ldapMonoConnection.search(searchRequest); + if (Boolean.TRUE.equals( + Boolean.valueOf(config.get(LdapConfigKeys.READ_CONNECTION_AUTHENTICATED)))) { + searchResult = LdapFactory.getSingleConnectionAuthenticated(config).search(searchRequest); } else { - throw new StoreException("search failed", e); + searchResult = LdapFactory.getSingleConnection(config).search(searchRequest); } + } catch (LDAPException | RetriableLDAPException e) { + throw new StoreException("search failed", e); } PageResult pageResult = new PageResult<>(); pageResult.setResults( diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStore.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStore.java index ea3283211..571a925b0 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStore.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStore.java @@ -13,9 +13,8 @@ */ package fr.insee.sugoi.store.ldap; -import com.unboundid.ldap.sdk.LDAPConnection; -import com.unboundid.ldap.sdk.LDAPConnectionPool; import fr.insee.sugoi.core.configuration.GlobalKeysConfig; +import fr.insee.sugoi.ldap.utils.RetriableLdapConnectionPool; import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; import fr.insee.sugoi.ldap.utils.mapper.AddressLdapMapper; import fr.insee.sugoi.ldap.utils.mapper.ApplicationLdapMapper; @@ -30,9 +29,7 @@ public class LdapStore { - protected LDAPConnectionPool ldapPoolConnection; - protected LDAPConnection ldapMonoConnection; - + protected RetriableLdapConnectionPool ldapPoolConnection; protected static final Logger logger = LoggerFactory.getLogger(LdapStore.class); protected UserLdapMapper userLdapMapper; diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java index e0470bcff..c24111111 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapStoreBeans.java @@ -95,6 +95,9 @@ public class LdapStoreBeans { @Value("#{'${fr.insee.sugoi.ldap.default.application-mapping:name:ou,String,rw}'.split(';')}") private List defaultApplicationMapping; + @Value("${fr.insee.sugoi.config.ldap.default.max-retries:10}") + private String maxRetries; + @Bean("LdapReaderStore") @Lazy @Scope("prototype") @@ -190,6 +193,12 @@ public Map generateConfig(Realm realm, UserStorage user && !userStorage.getProperties().get(LdapConfigKeys.ADDRESS_OBJECT_CLASSES).isEmpty() ? userStorage.getProperties().get(LdapConfigKeys.ADDRESS_OBJECT_CLASSES).get(0) : defaultAddressObjectClasses); + config.put( + LdapConfigKeys.MAX_RETRIES, + realm.getProperties().get(LdapConfigKeys.MAX_RETRIES) != null + && !realm.getProperties().get(LdapConfigKeys.MAX_RETRIES).isEmpty() + ? realm.getProperties().get(LdapConfigKeys.MAX_RETRIES).get(0) + : maxRetries); return config; } diff --git a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java index 7e98d8b98..a856ae16c 100644 --- a/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java +++ b/sugoi-api-ldap-store-provider/src/main/java/fr/insee/sugoi/store/ldap/LdapWriterStore.java @@ -17,7 +17,6 @@ import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.DeleteRequest; import com.unboundid.ldap.sdk.ExtendedResult; -import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.Modification; import com.unboundid.ldap.sdk.ModificationType; @@ -33,6 +32,8 @@ import fr.insee.sugoi.core.service.impl.CertificateServiceImpl; import fr.insee.sugoi.core.store.WriterStore; import fr.insee.sugoi.ldap.utils.LdapFactory; +import fr.insee.sugoi.ldap.utils.RetriableLDAPException; +import fr.insee.sugoi.ldap.utils.RetriableLdapConnectionPool; import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; import fr.insee.sugoi.ldap.utils.mapper.AddressLdapMapper; import fr.insee.sugoi.ldap.utils.mapper.ApplicationLdapMapper; @@ -72,7 +73,7 @@ public class LdapWriterStore extends LdapStore implements WriterStore { private static final Logger logger = LoggerFactory.getLogger(LdapWriterStore.class); - private final LDAPConnectionPool ldapPoolConnection; + private final RetriableLdapConnectionPool ldapPoolConnection; private final UserLdapMapper userLdapMapper; private final OrganizationLdapMapper organizationLdapMapper; @@ -127,7 +128,7 @@ public ProviderResponse deleteUser(String id, ProviderRequest providerRequest) { response.setStatus(ProviderResponseStatus.OK); response.setEntityId(id); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to delete user " + id, e); } } @@ -147,7 +148,7 @@ public ProviderResponse createUser(User user, ProviderRequest providerRequest) { new AddRequest( getUserDN(user.getUsername()), userLdapMapper.mapToAttributesForCreation(user)); ldapPoolConnection.add(userAddRequest); - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to create user. Provider message : " + e.getMessage(), e); } ProviderResponse response = new ProviderResponse(); @@ -182,7 +183,7 @@ public ProviderResponse updateUser(User updatedUser, ProviderRequest providerReq new ModifyRequest( getUserDN(updatedUser.getUsername()), userLdapMapper.createMods(updatedUser)); ldapPoolConnection.modify(mr); - } catch (LDAPException e) { + } catch (LDAPException | RetriableLDAPException e) { throw new StoreException("Failed to update user while writing to LDAP", e); } ProviderResponse response = new ProviderResponse(); @@ -207,7 +208,7 @@ public ProviderResponse deleteGroup( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(groupName); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to delete group " + groupName, e); } } @@ -245,7 +246,7 @@ public ProviderResponse createGroup( } else { throw new StoragePolicyNotMetException("Group pattern won't match"); } - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to create group " + group.getName(), e); } ProviderResponse response = new ProviderResponse(); @@ -269,7 +270,7 @@ public ProviderResponse updateGroup( getGroupDN(appName, updatedGroup.getName()), groupLdapMapper.createMods(updatedGroup)); ldapPoolConnection.modify(mr); - } catch (LDAPException e) { + } catch (LDAPException | RetriableLDAPException e) { throw new StoreException( "Failed to update group " + updatedGroup.getName() + " while writing to LDAP", e); } @@ -302,7 +303,7 @@ public ProviderResponse deleteOrganization(String name, ProviderRequest provider response.setStatus(ProviderResponseStatus.OK); response.setEntityId(name); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to delete organisation " + name, e); } } @@ -325,7 +326,7 @@ public ProviderResponse createOrganization( getOrganizationDN(organization.getIdentifiant()), organizationLdapMapper.mapToAttributesForCreation(organization)); ldapPoolConnection.add(ar); - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException( "Failed to create organization " + organization.getIdentifiant(), e); } @@ -376,7 +377,7 @@ public ProviderResponse updateOrganization( getOrganizationDN(updatedOrganization.getIdentifiant()), organizationLdapMapper.createMods(updatedOrganization)); ldapPoolConnection.modify(mr); - } catch (LDAPException e) { + } catch (LDAPException | RetriableLDAPException e) { throw new StoreException( "Failed to update organization " + updatedOrganization.getIdentifiant() @@ -420,6 +421,8 @@ public ProviderResponse deleteUserFromGroup( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(userId); return response; + } catch (RetriableLDAPException e) { + throw new StoreException("Failed to remove user to group " + groupName, e); } } @@ -454,6 +457,8 @@ public ProviderResponse addUserToGroup( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(userId); return response; + } catch (RetriableLDAPException e) { + throw new StoreException("Failed to add user to group " + groupName, e); } } @@ -481,7 +486,7 @@ public ProviderResponse reinitPassword( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(user.getUsername()); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to reinit password for user " + user.getUsername(), e); } } @@ -506,7 +511,7 @@ public ProviderResponse initPassword( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(user.getUsername()); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to init password for user " + user.getUsername(), e); } } @@ -544,8 +549,8 @@ public ProviderResponse changePassword( throw new StoreException( "Unexpected error when changing password", new LDAPException(result.getResultCode())); } - } catch (NumberFormatException | LDAPException e) { - logger.error("problème de conversion numérique ou d'accès au ldap", e); + } catch (NumberFormatException | RetriableLDAPException e) { + throw new StoreException("Unexpected error when changing password", e); } ProviderResponse response = new ProviderResponse(); response.setStatus(ProviderResponseStatus.OK); @@ -569,7 +574,7 @@ private ProviderResponse changePasswordResetStatus(String userId, boolean isRese response.setStatus(ProviderResponseStatus.OK); response.setEntityId(user.getUsername()); return response; - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to change password reset setting", e); } } @@ -599,7 +604,7 @@ public ProviderResponse createApplication( getGroupManagerSource(application.getName()), new Attribute("objectClass", "top", "groupOfUniqueNames")); ldapPoolConnection.add(groupManagerAR); - } catch (LDAPException e) { + } catch (RetriableLDAPException e) { throw new StoreException("Failed to create application" + application.getName(), e); } ProviderResponse response = new ProviderResponse(); @@ -655,7 +660,7 @@ public ProviderResponse updateApplication( createGroup(updatedApplication.getName(), updatedGroup, providerRequest); } } - } catch (LDAPException e) { + } catch (LDAPException | RetriableLDAPException e) { throw new StoreException( "Failed to update application " + updatedApplication.getName() + "while writing to LDAP", e); @@ -677,7 +682,8 @@ public ProviderResponse deleteApplication( new ApplicationNotFoundException( config.get(LdapConfigKeys.REALM_NAME), applicationName)); try { - (new SubtreeDeleter()).delete(ldapPoolConnection, getApplicationDN(applicationName)); + (new SubtreeDeleter()) + .delete(ldapPoolConnection.getLdapConnectionPool(), getApplicationDN(applicationName)); } catch (LDAPException e) { throw new StoreException("Failed to delete application " + applicationName, e); } @@ -715,6 +721,14 @@ public ProviderResponse addAppManagedAttribute( } response.setStatus(ProviderResponseStatus.OK); response.setEntityId(userId); + } catch (RetriableLDAPException e) { + throw new StoreException( + "Failed to update user attribute " + + attributeKey + + " with value " + + attributeValue + + " while writing to LDAP", + e); } return response; } @@ -747,6 +761,14 @@ public ProviderResponse deleteAppManagedAttribute( } response.setStatus(ProviderResponseStatus.OK); response.setEntityId(userId); + } catch (RetriableLDAPException e) { + throw new StoreException( + "Failed to update user attribute " + + attributeKey + + " with value " + + attributeValue + + " while writing to LDAP", + e); } return response; } @@ -758,7 +780,7 @@ public ProviderResponse deleteAppManagedAttribute( * @return chosen id * @throws LDAPException */ - private UUID createAddress(PostalAddress address) throws LDAPException { + private UUID createAddress(PostalAddress address) throws RetriableLDAPException { UUID addressUUID = UUID.randomUUID(); AddRequest addressAddRequest = new AddRequest( @@ -767,13 +789,14 @@ private UUID createAddress(PostalAddress address) throws LDAPException { return addressUUID; } - private void updateAddress(String id, PostalAddress newAddress) throws LDAPException { + private void updateAddress(String id, PostalAddress newAddress) + throws RetriableLDAPException, LDAPException { ModifyRequest modifyRequest = new ModifyRequest(getAddressDN(id), addressLdapMapper.createMods(newAddress)); ldapPoolConnection.modify(modifyRequest); } - private void deleteAddress(String id) throws LDAPException { + private void deleteAddress(String id) throws RetriableLDAPException { DeleteRequest deleteRequest = new DeleteRequest(getAddressDN(id)); ldapPoolConnection.delete(deleteRequest); } @@ -816,7 +839,7 @@ public ProviderResponse updateUserCertificate( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(user.getUsername()); return response; - } catch (CertificateException | LDAPException e) { + } catch (CertificateException | RetriableLDAPException | LDAPException e) { throw new UnableToUpdateCertificateException(e.toString(), e); } } @@ -845,7 +868,7 @@ public ProviderResponse deleteUserCertificate(User user, ProviderRequest provide response.setStatus(ProviderResponseStatus.OK); response.setEntityId(user.getUsername()); return response; - } catch (Exception e) { + } catch (Exception | RetriableLDAPException e) { throw new UnableToUpdateCertificateException(e.toString(), e); } } @@ -867,7 +890,7 @@ public ProviderResponse updateOrganizationGpgKey( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(organization.getIdentifiant()); return response; - } catch (Exception e) { + } catch (Exception | RetriableLDAPException e) { throw new UnabletoUpdateGPGKeyException(e.toString(), e); } } @@ -886,7 +909,7 @@ public ProviderResponse deleteOrganizationGpgKey( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(organization.getIdentifiant()); return response; - } catch (Exception e) { + } catch (Exception | RetriableLDAPException e) { throw new UnabletoUpdateGPGKeyException(e.toString(), e); } } @@ -922,6 +945,8 @@ public ProviderResponse addUserToGroupManager( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(userId); return response; + } catch (RetriableLDAPException e) { + throw new StoreException("Failed to add user to manager group ", e); } } @@ -950,6 +975,8 @@ public ProviderResponse deleteUserFromManagerGroup( response.setStatus(ProviderResponseStatus.OK); response.setEntityId(userId); return response; + } catch (RetriableLDAPException e) { + throw new StoreException("Failed to add user to manager group ", e); } } } diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/LdapFactory.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/LdapFactory.java index 90548b3c1..99caf5973 100644 --- a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/LdapFactory.java +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/LdapFactory.java @@ -15,21 +15,20 @@ import com.unboundid.ldap.sdk.BindResult; import com.unboundid.ldap.sdk.LDAPConnection; -import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPException; import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; import fr.insee.sugoi.model.RealmConfigKeys; import java.util.HashMap; import java.util.Map; -import org.springframework.stereotype.Component; -@Component public class LdapFactory { - private static final Map openLdapPoolConnection = new HashMap<>(); + private static final Map openLdapPoolConnection = + new HashMap<>(); private static final Map openLdapPoolConnectionConfig = new HashMap<>(); private static final Map openLdapMonoConnectionConfig = new HashMap<>(); - private static final Map openLdapMonoConnection = new HashMap<>(); + private static final Map openLdapMonoConnection = + new HashMap<>(); /** * Give an unauthenticated Ldap Connection Pool @@ -38,7 +37,7 @@ public class LdapFactory { * @return * @throws LDAPException */ - public static LDAPConnectionPool getConnectionPool(Map config) + public static RetriableLdapConnectionPool getConnectionPool(Map config) throws LDAPException { // Check if a ldap connection pool already exist for this userStorage and create // it if it doesn't exist @@ -61,25 +60,22 @@ public static LDAPConnectionPool getConnectionPool(Map } LDAPConnection ldapConnection = new LDAPConnection( - config.get(LdapConfigKeys.URL), Integer.valueOf(config.get(LdapConfigKeys.PORT))); + config.get(LdapConfigKeys.URL), Integer.parseInt(config.get(LdapConfigKeys.PORT))); setConnectionTimeout(ldapConnection, config); openLdapPoolConnection.put( name, - new LDAPConnectionPool( - ldapConnection, Integer.valueOf(config.get(LdapConfigKeys.POOL_SIZE)))); + new RetriableLdapConnectionPool( + ldapConnection, + Integer.parseInt(config.get(LdapConfigKeys.POOL_SIZE)), + Integer.parseInt(config.get(LdapConfigKeys.MAX_RETRIES)))); // Only put key if ldap connection correctly open openLdapPoolConnectionConfig.put(key, name); } return openLdapPoolConnection.get(name); } - public static LDAPConnection getSingleConnection(Map config) + public static RetriableLdapConnection getSingleConnection(Map config) throws LDAPException { - return getSingleConnection(config, false); - } - - public static LDAPConnection getSingleConnection( - Map config, boolean forceErase) throws LDAPException { String key = config.get(LdapConfigKeys.REALM_NAME) + "_" @@ -93,16 +89,11 @@ public static LDAPConnection getSingleConnection( + config.get(LdapConfigKeys.USERSTORAGE_NAME) + "_R"; - if (!openLdapMonoConnectionConfig.containsKey(key) || forceErase) { - if (openLdapMonoConnection.containsKey(name) && openLdapMonoConnection.get(name) != null - || forceErase) { + if (!openLdapMonoConnectionConfig.containsKey(key)) { + if (openLdapMonoConnection.containsKey(name) && openLdapMonoConnection.get(name) != null) { openLdapMonoConnection.get(name).close(); } - LDAPConnection ldapConnection = - new LDAPConnection( - config.get(LdapConfigKeys.URL), Integer.valueOf(config.get(LdapConfigKeys.PORT))); - setConnectionTimeout(ldapConnection, config); - openLdapMonoConnection.put(name, ldapConnection); + openLdapMonoConnection.put(name, new RetriableLdapConnection(config)); // Only put key if ldap connection correctly open openLdapMonoConnectionConfig.put(key, name); } @@ -116,7 +107,7 @@ public static LDAPConnection getSingleConnection( * @return * @throws LDAPException */ - public static LDAPConnectionPool getConnectionPoolAuthenticated( + public static RetriableLdapConnectionPool getConnectionPoolAuthenticated( Map config) throws LDAPException { // Check if a ldap connection pool already exist for this userStorage and create // it if it doesn't exist @@ -140,27 +131,24 @@ public static LDAPConnectionPool getConnectionPoolAuthenticated( LDAPConnection ldapConnection = new LDAPConnection( config.get(LdapConfigKeys.URL), - Integer.valueOf(config.get(LdapConfigKeys.PORT)), + Integer.parseInt(config.get(LdapConfigKeys.PORT)), config.get(LdapConfigKeys.USERNAME), config.get(LdapConfigKeys.PASSWORD)); setConnectionTimeout(ldapConnection, config); openLdapPoolConnection.put( name, - new LDAPConnectionPool( - ldapConnection, Integer.valueOf(config.get(LdapConfigKeys.POOL_SIZE)))); + new RetriableLdapConnectionPool( + ldapConnection, + Integer.parseInt(config.get(LdapConfigKeys.POOL_SIZE)), + Integer.parseInt(config.get(LdapConfigKeys.MAX_RETRIES)))); // Only put key if ldap connection correctly open openLdapPoolConnectionConfig.put(key, name); } return openLdapPoolConnection.get(name); } - public static LDAPConnection getSingleConnectionAuthenticated(Map config) - throws LDAPException { - return getSingleConnectionAuthenticated(config, false); - } - - public static LDAPConnection getSingleConnectionAuthenticated( - Map config, boolean forceErase) throws LDAPException { + public static RetriableLdapConnection getSingleConnectionAuthenticated( + Map config) throws LDAPException { String name = config.get(LdapConfigKeys.REALM_NAME) + "_" @@ -173,19 +161,11 @@ public static LDAPConnection getSingleConnectionAuthenticated( + "_" + config.hashCode() + "_RW"; - if (!openLdapMonoConnectionConfig.containsKey(key) || forceErase) { - if (openLdapMonoConnection.containsKey(name) && openLdapMonoConnection.get(name) != null - || forceErase) { + if (!openLdapMonoConnectionConfig.containsKey(key)) { + if (openLdapMonoConnection.containsKey(name) && openLdapMonoConnection.get(name) != null) { openLdapMonoConnection.get(name).close(); } - LDAPConnection ldapConnection = - new LDAPConnection( - config.get(LdapConfigKeys.URL), - Integer.valueOf(config.get(LdapConfigKeys.PORT)), - config.get(LdapConfigKeys.USERNAME), - config.get(LdapConfigKeys.PASSWORD)); - setConnectionTimeout(ldapConnection, config); - openLdapMonoConnection.put(name, ldapConnection); + openLdapMonoConnection.put(name, new RetriableLdapConnection(config)); // Only put key if ldap connection correctly open openLdapMonoConnectionConfig.put(key, name); } diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLDAPException.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLDAPException.java new file mode 100644 index 000000000..dbd8b0f6d --- /dev/null +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLDAPException.java @@ -0,0 +1,20 @@ +/* +* Licensed 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 fr.insee.sugoi.ldap.utils; + +public class RetriableLDAPException extends Throwable { + public RetriableLDAPException(String message) { + super(message); + } +} diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnection.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnection.java new file mode 100644 index 000000000..cdcb7fbfb --- /dev/null +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnection.java @@ -0,0 +1,68 @@ +/* +* Licensed 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 fr.insee.sugoi.ldap.utils; + +import com.unboundid.ldap.sdk.*; +import fr.insee.sugoi.ldap.utils.config.LdapConfigKeys; +import fr.insee.sugoi.model.RealmConfigKeys; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RetriableLdapConnection { + + private final int maxRetries; + private LDAPConnection ldapConnection; + Map config; + public static final Logger logger = LoggerFactory.getLogger(RetriableLdapConnectionPool.class); + + public RetriableLdapConnection(Map config) throws LDAPException { + this.config = config; + this.ldapConnection = createLdapConnectionFromConfig(); + this.maxRetries = Integer.parseInt(config.get(LdapConfigKeys.MAX_RETRIES)); + } + + private LDAPConnection createLdapConnectionFromConfig() throws LDAPException { + LDAPConnection ldapConnection = + new LDAPConnection( + config.get(LdapConfigKeys.URL), + Integer.parseInt(config.get(LdapConfigKeys.PORT)), + config.get(LdapConfigKeys.USERNAME), + config.get(LdapConfigKeys.PASSWORD)); + ldapConnection + .getConnectionOptions() + .setResponseTimeoutMillis( + Integer.parseInt(config.get(LdapConfigKeys.LDAP_CONNECTION_TIMEOUT))); + return ldapConnection; + } + + public SearchResult search(SearchRequest searchRequest) + throws RetriableLDAPException, LDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnection.search(searchRequest); + } catch (LDAPException e) { + ldapConnection = createLdapConnectionFromConfig(); + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to retrieve entry after " + maxRetries + " retries."); + } + + public void close() { + ldapConnection.close(); + } +} diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnectionPool.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnectionPool.java new file mode 100644 index 000000000..efb439d0b --- /dev/null +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/RetriableLdapConnectionPool.java @@ -0,0 +1,165 @@ +/* +* Licensed 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 fr.insee.sugoi.ldap.utils; + +import com.unboundid.ldap.sdk.*; +import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RetriableLdapConnectionPool { + + private final int maxRetries; + private final LDAPConnectionPool ldapConnectionPool; + public static final Logger logger = LoggerFactory.getLogger(RetriableLdapConnectionPool.class); + + public LDAPConnectionPool getLdapConnectionPool() { + return ldapConnectionPool; + } + + public RetriableLdapConnectionPool(LDAPConnection connection, int numConnections, int maxRetries) + throws LDAPException { + this.ldapConnectionPool = new LDAPConnectionPool(connection, numConnections); + this.maxRetries = maxRetries; + } + + public SearchResultEntry getEntry(String dn, String... attributes) throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.getEntry(dn, attributes); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to retrieve entry after " + maxRetries + " retries."); + } + + public SearchResult search(SearchRequest searchRequest) throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.search(searchRequest); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to find entry after " + maxRetries + " retries."); + } + + public SearchResult search(String baseDN, SearchScope scope, Filter filter) + throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.search(baseDN, scope, filter); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to find entry after " + maxRetries + " retries."); + } + + public LDAPResult add(AddRequest addRequest) throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.add(addRequest); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to add entry after " + maxRetries + " retries."); + } + + public LDAPResult modify(ModifyRequest modifyRequest) + throws LDAPException, RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.modify(modifyRequest); + } catch (LDAPException e) { + if (e.getResultCode().equals(ResultCode.NO_SUCH_ATTRIBUTE) + || e.getResultCode().equals(ResultCode.ATTRIBUTE_OR_VALUE_EXISTS)) { + throw e; + } + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to modify entry after " + maxRetries + " retries."); + } + + public LDAPResult modify(String dn, Modification modification) throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.modify(dn, modification); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to modify entry after " + maxRetries + " retries."); + } + + public LDAPResult delete(DeleteRequest deleteRequest) throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.delete(deleteRequest); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to delete entry after " + maxRetries + " retries."); + } + + public LDAPResult delete(String dn) throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.delete(dn); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException("Failed to delete entry after " + maxRetries + " retries."); + } + + public ExtendedResult processExtendedOperation(PasswordModifyExtendedRequest pmer) + throws RetriableLDAPException { + int retryCount = 0; + while (retryCount < maxRetries) { + try { + return ldapConnectionPool.processExtendedOperation(pmer); + } catch (LDAPException e) { + logger.info("Failed to connect to ldap after " + retryCount + " try : " + e.getMessage()); + retryCount++; + } + } + throw new RetriableLDAPException( + "Failed to process extended operation after " + maxRetries + " retries."); + } + + public void close() { + ldapConnectionPool.close(); + } +} diff --git a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java index 81f2ca244..3f0b00645 100644 --- a/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java +++ b/sugoi-api-ldap-utils/src/main/java/fr/insee/sugoi/ldap/utils/config/LdapConfigKeys.java @@ -36,7 +36,8 @@ public enum LdapConfigKeys implements RealmConfigKeys { ADDRESS_OBJECT_CLASSES("address_object_classes"), USERSTORAGE_NAME("userstorage_name"), READ_CONNECTION_AUTHENTICATED("read_connection_authenticated"), - LDAP_CONNECTION_TIMEOUT("ldap_connection_timeout"); + LDAP_CONNECTION_TIMEOUT("ldap_connection_timeout"), + MAX_RETRIES("max_retries"); private String name;