package org.apereo.cas.adaptors.ldap.services; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.configuration.model.support.ldap.serviceregistry.LdapServiceRegistryProperties; import org.apereo.cas.configuration.support.Beans; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.ServiceRegistryDao; import org.apereo.cas.util.LdapUtils; import org.ldaptive.ConnectionFactory; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.Response; import org.ldaptive.SearchFilter; import org.ldaptive.SearchResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.LinkedList; import java.util.List; /** * Implementation of the ServiceRegistryDao interface which stores the services in a LDAP Directory. * * @author Misagh Moayyed * @author Marvin S. Addison * @since 4.0.0 */ public class LdapServiceRegistryDao implements ServiceRegistryDao { private static final Logger LOGGER = LoggerFactory.getLogger(LdapServiceRegistryDao.class); private final ConnectionFactory connectionFactory; private final LdapRegisteredServiceMapper ldapServiceMapper; private final String baseDn; private final String searchFilter; private final String loadFilter; public LdapServiceRegistryDao(final ConnectionFactory connectionFactory, final String baseDn, final LdapRegisteredServiceMapper ldapServiceMapper, final LdapServiceRegistryProperties ldapProperties) { this.connectionFactory = connectionFactory; this.baseDn = baseDn; if (ldapServiceMapper == null) { this.ldapServiceMapper = new DefaultLdapRegisteredServiceMapper(ldapProperties); } else { this.ldapServiceMapper = ldapServiceMapper; } this.searchFilter = '(' + this.ldapServiceMapper.getIdAttribute() + "={0})"; LOGGER.debug("Configured search filter to [{}]", this.searchFilter); this.loadFilter = "(objectClass=" + this.ldapServiceMapper.getObjectClass() + ')'; LOGGER.debug("Configured load filter to [{}]", this.loadFilter); } @Override public RegisteredService save(final RegisteredService rs) { if (rs.getId() != RegisteredService.INITIAL_IDENTIFIER_VALUE) { return update(rs); } try { final LdapEntry entry = this.ldapServiceMapper.mapFromRegisteredService(this.baseDn, rs); LdapUtils.executeAddOperation(this.connectionFactory, entry); } catch (final LdapException e) { LOGGER.error(e.getMessage(), e); } return rs; } /** * Update the ldap entry with the given registered service. * * @param rs the rs * @return the registered service */ private RegisteredService update(final RegisteredService rs) { String currentDn = null; try { final Response<SearchResult> response = searchForServiceById(rs.getId()); if (LdapUtils.containsResultEntry(response)) { currentDn = response.getResult().getEntry().getDn(); } } catch (final Exception e) { LOGGER.error(e.getMessage(), e); } if (StringUtils.isNotBlank(currentDn)) { LOGGER.debug("Updating registered service at [{}]", currentDn); final LdapEntry entry = this.ldapServiceMapper.mapFromRegisteredService(this.baseDn, rs); LdapUtils.executeModifyOperation(currentDn, this.connectionFactory, entry); } return rs; } @Override public boolean delete(final RegisteredService registeredService) { try { final Response<SearchResult> response = searchForServiceById(registeredService.getId()); if (LdapUtils.containsResultEntry(response)) { final LdapEntry entry = response.getResult().getEntry(); return LdapUtils.executeDeleteOperation(this.connectionFactory, entry); } } catch (final LdapException e) { LOGGER.error(e.getMessage(), e); } return false; } /** * {@inheritDoc} * This may be an expensive operation. * In order to count the number of available definitions in LDAP, * this call will attempt to execute a search query to load services * and the results will be counted. Do NOT attempt to call this * operation in a loop. * * @return number of entries in the service registry */ @Override public long size() { try { final Response<SearchResult> response = getSearchResultResponse(); if (LdapUtils.containsResultEntry(response)) { return response.getResult().size(); } } catch (final LdapException e) { LOGGER.error(e.getMessage(), e); } return 0; } @Override public List<RegisteredService> load() { final List<RegisteredService> list = new LinkedList<>(); try { final Response<SearchResult> response = getSearchResultResponse(); if (LdapUtils.containsResultEntry(response)) { response.getResult().getEntries().stream().map(this.ldapServiceMapper::mapToRegisteredService).forEach(list::add); } } catch (final LdapException e) { LOGGER.error(e.getMessage(), e); } return list; } private Response<SearchResult> getSearchResultResponse() throws LdapException { return LdapUtils.executeSearchOperation(this.connectionFactory, this.baseDn, Beans.newLdaptiveSearchFilter(this.loadFilter)); } @Override public RegisteredService findServiceById(final long id) { try { final Response<SearchResult> response = searchForServiceById(id); if (LdapUtils.containsResultEntry(response)) { return this.ldapServiceMapper.mapToRegisteredService(response.getResult().getEntry()); } } catch (final LdapException e) { LOGGER.error(e.getMessage(), e); } return null; } @Override public RegisteredService findServiceById(final String id) { return load().stream().filter(r -> r.matches(id)).findFirst().orElse(null); } /** * Search for service by id. * * @param id the id * @return the response * @throws LdapException the ldap exception */ private Response<SearchResult> searchForServiceById(final Long id) throws LdapException { final SearchFilter filter = Beans.newLdaptiveSearchFilter(this.searchFilter, Beans.LDAP_SEARCH_FILTER_DEFAULT_PARAM_NAME, Arrays.asList(id.toString())); return LdapUtils.executeSearchOperation(this.connectionFactory, this.baseDn, filter); } @Override public String toString() { return getClass().getSimpleName(); } }