AMBARI-21307 AddedLDAP configuration provider for loading and maintaining the LDAP configuration in the application
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/a5397e2b Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/a5397e2b Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/a5397e2b Branch: refs/heads/feature-branch-AMBARI-21307 Commit: a5397e2b91515bc75242112b13bdfd11715b2b24 Parents: 176356b Author: lpuskas <lpus...@apache.org> Authored: Tue Aug 29 14:55:09 2017 +0200 Committer: lpuskas <lpus...@apache.org> Committed: Fri Oct 13 17:20:50 2017 +0200 ---------------------------------------------------------------------- .../services/ldap/LdapConfigurationService.java | 1 + .../AmbariConfigurationResourceProvider.java | 35 +++++- .../ambari/server/events/AmbariEvent.java | 11 +- .../events/AmbariLdapConfigChangedEvent.java | 37 ++++++ .../server/ldap/LdapConfigurationFactory.java | 2 +- .../apache/ambari/server/ldap/LdapModule.java | 3 + .../AmbariLdapConfigurationProvider.java | 114 +++++++++++++++++++ .../server/ldap/service/AmbariLdapFacade.java | 7 +- .../server/orm/dao/AmbariConfigurationDAO.java | 48 ++++++++ .../orm/entities/AmbariConfigurationEntity.java | 4 +- .../DefaultLdapConfigurationServiceTest.java | 10 +- 11 files changed, 260 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ldap/LdapConfigurationService.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ldap/LdapConfigurationService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ldap/LdapConfigurationService.java index fc6bd41..1b8427b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ldap/LdapConfigurationService.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ldap/LdapConfigurationService.java @@ -82,6 +82,7 @@ public class LdapConfigurationService extends AmbariConfigurationService { @Produces(MediaType.APPLICATION_JSON) public Response validateConfiguration(LdapConfigurationRequest ldapConfigurationRequest) { + // check if the user is authorized to perform the operation authorize(); Set<String> groups = Sets.newHashSet(); http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AmbariConfigurationResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AmbariConfigurationResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AmbariConfigurationResourceProvider.java index 2302d8b..4f4cc70 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AmbariConfigurationResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AmbariConfigurationResourceProvider.java @@ -35,6 +35,9 @@ import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; import org.apache.ambari.server.controller.utilities.PredicateHelper; +import org.apache.ambari.server.events.AmbariEvent; +import org.apache.ambari.server.events.AmbariLdapConfigChangedEvent; +import org.apache.ambari.server.events.publishers.AmbariEventPublisher; import org.apache.ambari.server.orm.dao.AmbariConfigurationDAO; import org.apache.ambari.server.orm.entities.AmbariConfigurationEntity; import org.apache.ambari.server.orm.entities.ConfigurationBaseEntity; @@ -113,6 +116,10 @@ public class AmbariConfigurationResourceProvider extends AbstractAuthorizedResou @Inject private AmbariConfigurationDAO ambariConfigurationDAO; + @Inject + private AmbariEventPublisher publisher; + + private Gson gson; @AssistedInject @@ -142,7 +149,18 @@ public class AmbariConfigurationResourceProvider extends AbstractAuthorizedResou } LOGGER.info("Persisting new ambari configuration: {} ", ambariConfigurationEntity); - ambariConfigurationDAO.create(ambariConfigurationEntity); + + try { + ambariConfigurationDAO.create(ambariConfigurationEntity); + } catch (Exception e) { + LOGGER.error("Failed to create resource", e); + throw new ResourceAlreadyExistsException(e.getMessage()); + } + + // todo filter by configuration type + // notify subscribers about the configuration changes + publisher.publish(new AmbariLdapConfigChangedEvent(AmbariEvent.AmbariEventType.LDAP_CONFIG_CHANGED, + ambariConfigurationEntity.getId())); return getRequestStatus(null); } @@ -183,6 +201,10 @@ public class AmbariConfigurationResourceProvider extends AbstractAuthorizedResou } + // notify subscribers about the configuration changes + publisher.publish(new AmbariLdapConfigChangedEvent(AmbariEvent.AmbariEventType.LDAP_CONFIG_CHANGED, idFromRequest)); + + return getRequestStatus(null); } @@ -209,11 +231,15 @@ public class AmbariConfigurationResourceProvider extends AbstractAuthorizedResou persistedEntity.getConfigurationBaseEntity().setConfigurationAttributes(entityFromRequest.getConfigurationBaseEntity().getConfigurationAttributes()); - ambariConfigurationDAO.create(persistedEntity); + ambariConfigurationDAO.update(persistedEntity); } catch (AmbariException e) { throw new NoSuchParentResourceException(e.getMessage()); } + publisher.publish(new AmbariLdapConfigChangedEvent(AmbariEvent.AmbariEventType.LDAP_CONFIG_CHANGED, + persistedEntity.getId())); + + return getRequestStatus(null); } @@ -251,6 +277,11 @@ public class AmbariConfigurationResourceProvider extends AbstractAuthorizedResou throw new AmbariException("There must be only one resource specified in the request"); } + // the configuration type must be set + if (getValueFromResourceProperties(ResourcePropertyId.TYPE, resourcePropertiesSet.iterator().next()) == null) { + throw new AmbariException("The configuration type must be set"); + } + for (ResourcePropertyId resourcePropertyId : ResourcePropertyId.values()) { Object requestValue = getValueFromResourceProperties(resourcePropertyId, resourcePropertiesSet.iterator().next()); http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java index 9a5ee79..0f9ff52 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariEvent.java @@ -140,7 +140,13 @@ public abstract class AmbariEvent { /** * Local user has been created. */ - USER_CREATED; + USER_CREATED, + + /** + * LDAP config changed event; + */ + LDAP_CONFIG_CHANGED; + } /** @@ -151,8 +157,7 @@ public abstract class AmbariEvent { /** * Constructor. * - * @param eventType - * the type of event (not {@code null}). + * @param eventType the type of event (not {@code null}). */ public AmbariEvent(AmbariEventType eventType) { m_eventType = eventType; http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariLdapConfigChangedEvent.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariLdapConfigChangedEvent.java b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariLdapConfigChangedEvent.java new file mode 100644 index 0000000..48799d7 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/events/AmbariLdapConfigChangedEvent.java @@ -0,0 +1,37 @@ +/* + * 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 org.apache.ambari.server.events; + +/** + * Event signaling the creation or changing of an LDAP configuration entry. + */ +public class AmbariLdapConfigChangedEvent extends AmbariEvent { + + private Long configurationId; + + /** + * Constructor. + * + * @param eventType the type of event (not {@code null}). + */ + public AmbariLdapConfigChangedEvent(AmbariEventType eventType, Long configurationId) { + super(eventType); + this.configurationId = configurationId; + } + + public Long getConfigurationId() { + return configurationId; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapConfigurationFactory.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapConfigurationFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapConfigurationFactory.java index bcd6e39..57cdf6e 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapConfigurationFactory.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapConfigurationFactory.java @@ -18,4 +18,4 @@ import java.util.Map; public interface LdapConfigurationFactory { AmbariLdapConfiguration createLdapConfiguration(Map<String, Object> configuration); -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapModule.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapModule.java b/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapModule.java index 3ae4587..81f2a44 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapModule.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/ldap/LdapModule.java @@ -15,6 +15,7 @@ package org.apache.ambari.server.ldap; +import org.apache.ambari.server.ldap.service.AmbariLdapConfigurationProvider; import org.apache.ambari.server.ldap.service.AmbariLdapFacade; import org.apache.ambari.server.ldap.service.LdapConnectionService; import org.apache.ambari.server.ldap.service.LdapFacade; @@ -35,6 +36,8 @@ public class LdapModule extends AbstractModule { bind(LdapConfigurationService.class).to(DefaultLdapConfigurationService.class); bind(LdapConnectionService.class).to(DefaultLdapConnectionService.class); + bind(AmbariLdapConfiguration.class).toProvider(AmbariLdapConfigurationProvider.class); + install(new FactoryModuleBuilder().build(LdapConfigurationFactory.class)); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapConfigurationProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapConfigurationProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapConfigurationProvider.java new file mode 100644 index 0000000..7f3e8a9 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapConfigurationProvider.java @@ -0,0 +1,114 @@ +/* + * 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 org.apache.ambari.server.ldap.service; + +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.apache.ambari.server.events.AmbariLdapConfigChangedEvent; +import org.apache.ambari.server.events.publishers.AmbariEventPublisher; +import org.apache.ambari.server.ldap.AmbariLdapConfiguration; +import org.apache.ambari.server.orm.dao.AmbariConfigurationDAO; +import org.apache.ambari.server.orm.entities.AmbariConfigurationEntity; +import org.apache.ambari.server.security.authorization.AmbariLdapAuthenticationProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.eventbus.Subscribe; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Provider implementation for LDAP configurations. It needs to be registered in the related GUICE module as a provider + * It's responsible for managing LDAP configurations in the application. + * Whenever requested, this provider returns an AmbariLdapConfiguration which is always in sync with the persisted LDAP + * configuration resource. + * + * The provider receives notifications on CRUD operations related to the persisted resource and reloads the cached + * configuration instance accordingly. + */ +@Singleton +public class AmbariLdapConfigurationProvider implements Provider<AmbariLdapConfiguration> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AmbariLdapAuthenticationProvider.class); + private AmbariLdapConfiguration instance; + + @Inject + private AmbariEventPublisher publisher; + + @Inject + private Provider<AmbariConfigurationDAO> ambariConfigurationDAOProvider; + + private Gson gson = new GsonBuilder().create(); + + @Inject + public AmbariLdapConfigurationProvider() { + } + + @Inject + void register() { + publisher.register(this); + } + + @Override + public AmbariLdapConfiguration get() { + return instance != null ? instance : loadInstance(null); + } + + /** + * Loads the AmbariLdapConfiguration from the database. + * + * @param configurationId the configuration id + * @return the AmbariLdapConfiguration instance + */ + private AmbariLdapConfiguration loadInstance(Long configurationId) { + AmbariConfigurationEntity configEntity = null; + + LOGGER.info("Loading LDAP configuration ..."); + if (null != configurationId) { + + LOGGER.debug("Reloading configuration based on the provied id: {}", configurationId); + configEntity = ambariConfigurationDAOProvider.get().findByPK(configurationId); + + } else { + + LOGGER.debug("Initial loading of the ldap configuration ..."); + configEntity = ambariConfigurationDAOProvider.get().getLdapConfiguration(); + + } + + if (configEntity != null) { + Set propertyMaps = gson.fromJson(configEntity.getConfigurationBaseEntity().getConfigurationData(), Set.class); + instance = new AmbariLdapConfiguration((Map<String, Object>) propertyMaps.iterator().next()); + } + + LOGGER.info("Loaded LDAP configuration instance: [ {} ]", instance); + + return instance; + } + + @Subscribe + public void ambariLdapConfigChanged(AmbariLdapConfigChangedEvent event) { + LOGGER.info("LDAP config changed event received: {}", event); + loadInstance(event.getConfigurationId()); + LOGGER.info("Refreshed LDAP config instance."); + } + + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapFacade.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapFacade.java b/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapFacade.java index 683ed43..90a5ba7 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapFacade.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/ldap/service/AmbariLdapFacade.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; import org.apache.ambari.server.ldap.AmbariLdapConfiguration; @@ -54,6 +55,10 @@ public class AmbariLdapFacade implements LdapFacade { @Inject private LdapConnectionService ldapConnectionService; + //todo remove this, added for testing purposes only + @Inject + private Provider<AmbariLdapConfiguration> ambariLdapConfigurationProvider; + @Inject public AmbariLdapFacade() { } @@ -75,7 +80,7 @@ public class AmbariLdapFacade implements LdapFacade { @Override public void detectAttributes(AmbariLdapConfiguration ambariLdapConfiguration) { LOGGER.info("Detecting LDAP configuration attributes ..."); - throw new UnsupportedOperationException("Not yet implemented"); + LOGGER.info("LDAP config: {}", ambariLdapConfigurationProvider.get()); } @Override http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AmbariConfigurationDAO.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AmbariConfigurationDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AmbariConfigurationDAO.java index 5710a7f..83293ef 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AmbariConfigurationDAO.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/AmbariConfigurationDAO.java @@ -16,8 +16,13 @@ package org.apache.ambari.server.orm.dao; import javax.inject.Inject; import javax.inject.Singleton; +import javax.persistence.EntityExistsException; +import javax.persistence.EntityNotFoundException; +import javax.persistence.TypedQuery; import org.apache.ambari.server.orm.entities.AmbariConfigurationEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.inject.persist.Transactional; @@ -29,6 +34,8 @@ import com.google.inject.persist.Transactional; @Singleton public class AmbariConfigurationDAO extends CrudDAO<AmbariConfigurationEntity, Long> { + private static final Logger LOGGER = LoggerFactory.getLogger(AmbariConfigurationDAO.class); + @Inject public AmbariConfigurationDAO() { super(AmbariConfigurationEntity.class); @@ -36,6 +43,47 @@ public class AmbariConfigurationDAO extends CrudDAO<AmbariConfigurationEntity, L @Transactional public void create(AmbariConfigurationEntity entity) { + // make sure only one LDAP config entry exists + if ("ldap-configuration".equals(entity.getConfigurationBaseEntity().getType())) { + AmbariConfigurationEntity ldapConfigEntity = getLdapConfiguration(); + if (ldapConfigEntity != null) { + LOGGER.error("Only one LDAP configuration entry can exist!"); + throw new EntityExistsException("LDAP configuration entity already exists!"); + } + } super.create(entity); } + + + @Transactional + public void update(AmbariConfigurationEntity entity) { + if (entity.getId() == null || findByPK(entity.getId()) == null) { + String msg = String.format("The entity with id [ %s ] is not found", entity.getId()); + LOGGER.debug(msg); + throw new EntityNotFoundException(msg); + } + + // updating the existing entity + super.merge(entity); + entityManagerProvider.get().flush(); + } + + /** + * Returns the LDAP configuration from the database. + * + * @return the configuration entity + */ + @Transactional + public AmbariConfigurationEntity getLdapConfiguration() { + LOGGER.info("Looking up the LDAP configuration ...."); + AmbariConfigurationEntity ldapConfigEntity = null; + + TypedQuery<AmbariConfigurationEntity> query = entityManagerProvider.get().createNamedQuery( + "AmbariConfigurationEntity.findByType", AmbariConfigurationEntity.class); + query.setParameter("typeName", "ldap-configuration"); + + ldapConfigEntity = daoUtils.selectSingle(query); + LOGGER.info("Returned entity: {} ", ldapConfigEntity); + return ldapConfigEntity; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AmbariConfigurationEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AmbariConfigurationEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AmbariConfigurationEntity.java index 34fa221..c9f4695 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AmbariConfigurationEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AmbariConfigurationEntity.java @@ -29,8 +29,8 @@ import javax.persistence.Table; @Table(name = "ambari_configuration") @NamedQueries({ @NamedQuery( - name = "AmbariConfigurationEntity.findAll", - query = "select ace from AmbariConfigurationEntity ace") + name = "AmbariConfigurationEntity.findByType", + query = "select ace from AmbariConfigurationEntity ace where ace.configurationBaseEntity.type = :typeName") }) public class AmbariConfigurationEntity { http://git-wip-us.apache.org/repos/asf/ambari/blob/a5397e2b/ambari-server/src/test/java/org/apache/ambari/server/ldap/service/ads/DefaultLdapConfigurationServiceTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/ldap/service/ads/DefaultLdapConfigurationServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/ldap/service/ads/DefaultLdapConfigurationServiceTest.java index 2b7448e..b5978a5 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/ldap/service/ads/DefaultLdapConfigurationServiceTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/ldap/service/ads/DefaultLdapConfigurationServiceTest.java @@ -28,6 +28,7 @@ import org.apache.directory.api.ldap.model.message.SearchScope; import org.apache.directory.ldap.client.api.LdapConnection; import org.apache.directory.ldap.client.api.LdapConnectionConfig; import org.apache.directory.ldap.client.api.LdapNetworkConnection; +import org.apache.directory.ldap.client.api.search.FilterBuilder; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,15 +47,18 @@ public class DefaultLdapConfigurationServiceTest { // WHEN LdapConnectionConfig config = new LdapConnectionConfig(); - config.setLdapHost("localhost"); + config.setLdapHost("172.22.112.167"); config.setLdapPort(389); LdapConnection connection = new LdapNetworkConnection(config); // THEN - connection.anonymousBind(); + connection.bind("CN=Robert Levas,CN=Users,DC=HWQE,DC=HORTONWORKS,DC=COM", "Hadoop1234"); + String filter = FilterBuilder.and( + FilterBuilder.equal(SchemaConstants.OBJECT_CLASS_AT, "person"), + FilterBuilder.equal("name", "User1 Levas")).toString(); - EntryCursor cursor = connection.search("dc=dev,dc=local", "(objectclass=*)", SearchScope.ONELEVEL); + EntryCursor cursor = connection.search("OU=levas,DC=hwqe,DC=hortonworks,DC=com", filter, SearchScope.SUBTREE); for (Entry entry : cursor) { assertNotNull(entry);