blob: ea343fda1c3a993be0ed34872d643b422dbb8695 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cn.provisioner.internal.service;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.Result;
import org.apache.fineract.cn.provisioner.config.ProvisionerConstants;
import org.apache.fineract.cn.provisioner.internal.repository.ApplicationEntity;
import org.apache.fineract.cn.provisioner.internal.repository.TenantApplicationEntity;
import org.apache.fineract.cn.provisioner.internal.repository.TenantCassandraRepository;
import org.apache.fineract.cn.provisioner.internal.repository.TenantEntity;
import org.apache.fineract.cn.provisioner.internal.service.applications.AnubisInitializer;
import org.apache.fineract.cn.provisioner.internal.service.applications.IdentityServiceInitializer;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.apache.fineract.cn.anubis.api.v1.domain.ApplicationSignatureSet;
import org.apache.fineract.cn.anubis.config.TenantSignatureRepository;
import org.apache.fineract.cn.cassandra.core.CassandraSessionProvider;
import org.apache.fineract.cn.lang.AutoTenantContext;
import org.apache.fineract.cn.lang.ServiceException;
import org.apache.fineract.cn.lang.listening.EventExpectation;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class TenantApplicationService {
private final CassandraSessionProvider cassandraSessionProvider;
private final AnubisInitializer anubisInitializer;
private final IdentityServiceInitializer identityServiceInitializer;
private final TenantSignatureRepository tenantSignatureRepository;
private final TenantCassandraRepository tenantCassandraRepository;
private final Logger logger;
@Autowired
public TenantApplicationService(final CassandraSessionProvider cassandraSessionProvider,
final AnubisInitializer anubisInitializer,
final IdentityServiceInitializer identityServiceInitializer,
@SuppressWarnings("SpringJavaAutowiringInspection") final TenantSignatureRepository tenantSignatureRepository,
final TenantCassandraRepository tenantCassandraRepository,
@Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger) {
super();
this.cassandraSessionProvider = cassandraSessionProvider;
this.anubisInitializer = anubisInitializer;
this.identityServiceInitializer = identityServiceInitializer;
this.tenantSignatureRepository = tenantSignatureRepository;
this.tenantCassandraRepository = tenantCassandraRepository;
this.logger = logger;
}
@Async
public void assign(final @Nonnull TenantApplicationEntity tenantApplicationEntity, final @Nonnull Map<String, String> appNameToUriMap) {
Assert.notNull(tenantApplicationEntity);
Assert.notNull(appNameToUriMap);
final TenantEntity tenantEntity = tenantCassandraRepository.get(tenantApplicationEntity.getTenantIdentifier())
.orElseThrow(() -> ServiceException
.notFound("Tenant {0} not found.", tenantApplicationEntity.getTenantIdentifier()));
checkApplicationsExist(tenantApplicationEntity.getApplications());
saveTenantApplicationAssignment(tenantApplicationEntity);
final Set<ApplicationNameToUriPair> applicationNameToUriPairs =
getApplicationNameToUriPairs(tenantApplicationEntity, appNameToUriMap);
final Optional<ApplicationSignatureSet> latestIdentityManagerSignatureSet = getLatestIdentityManagerSignatureSet(tenantEntity);
latestIdentityManagerSignatureSet.ifPresent(y -> {
try {
initializeSecurity(tenantEntity, y, applicationNameToUriPairs);
} catch (final InterruptedException e) {
logger.error("Because of interruption, started but didn't finish application assignment for {} in tenant {}.",
appNameToUriMap.keySet(), tenantApplicationEntity.getTenantIdentifier(), e);
}
});
if (!latestIdentityManagerSignatureSet.isPresent()) {
logger.warn("No identity manager signature is available, so security is not initialized for applications {}",
appNameToUriMap.keySet());
}
}
private void initializeSecurity(final TenantEntity tenantEntity,
final ApplicationSignatureSet identityManagerSignatureSet,
final Set<ApplicationNameToUriPair> applicationNameToUriPairs) throws InterruptedException {
final String tenantIdentifier = tenantEntity.getIdentifier();
final String identityManagerApplicationName = tenantEntity.getIdentityManagerApplicationName();
final String identityManagerApplicationUri = tenantEntity.getIdentityManagerApplicationUri();
//Permittable groups must be posted before resource initialization occurs because resource initialization
//may request callback from another service. For example, Services X, Y, and Identity.
// X.initializeResources -> Y.requestCallback at X.address
// Y.requestCallback -> Identity.requestPermission to call X.address
// Therefore Identity must know of the permittable group for X.address before X.initializeResources is called.
final Stream<EventExpectation> eventExpectations = applicationNameToUriPairs.stream().flatMap(x ->
identityServiceInitializer.postApplicationPermittableGroups(
tenantIdentifier,
identityManagerApplicationName,
identityManagerApplicationUri,
x.uri).stream());
for (final EventExpectation eventExpectation : eventExpectations.collect(Collectors.toList())) {
if (!eventExpectation.waitForOccurrence(5, TimeUnit.SECONDS)) {
logger.warn("Expected action in identity didn't complete {}.", eventExpectation);
}
}
applicationNameToUriPairs.forEach(x -> {
final ApplicationSignatureSet applicationSignatureSet = anubisInitializer.createSignatureSet(tenantIdentifier, x.name, x.uri, identityManagerSignatureSet.getTimestamp(), identityManagerSignatureSet.getIdentityManagerSignature());
identityServiceInitializer.postApplicationDetails(
tenantIdentifier,
identityManagerApplicationName,
identityManagerApplicationUri,
x.name,
x.uri,
applicationSignatureSet);
//InitializeResources on the service being added should occur last, for two reasons:
// 1.) When the initialization event is put on the queue for this app/tenant combo, the app is fully ready for business.
// 2.) If the app depeneds on the provisioning of identitypermissions in its initialization, those resources will be there.
anubisInitializer.initializeResources(tenantIdentifier, x.name, x.uri);
});
}
private void saveTenantApplicationAssignment(final @Nonnull TenantApplicationEntity tenantApplicationEntity) {
final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
tenantApplicationEntityMapper.save(tenantApplicationEntity);
}
private Set<ApplicationNameToUriPair> getApplicationNameToUriPairs(
final @Nonnull TenantApplicationEntity tenantApplicationEntity,
final @Nonnull Map<String, String> appNameToUriMap) {
return tenantApplicationEntity.getApplications().stream()
.map(x -> new TenantApplicationService.ApplicationNameToUriPair(x, appNameToUriMap.get(x)))
.collect(Collectors.toSet());
}
private static class ApplicationNameToUriPair
{
String name;
String uri;
ApplicationNameToUriPair(String name, String uri) {
this.name = name;
this.uri = uri;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApplicationNameToUriPair that = (ApplicationNameToUriPair) o;
return Objects.equals(name, that.name) &&
Objects.equals(uri, that.uri);
}
@Override
public int hashCode() {
return Objects.hash(name, uri);
}
}
private Optional<ApplicationSignatureSet> getLatestIdentityManagerSignatureSet(final @Nonnull TenantEntity tenantEntity) {
try (final AutoTenantContext ignored = new AutoTenantContext(tenantEntity.getIdentifier())) {
return tenantSignatureRepository.getLatestSignatureSet();
}
}
public TenantApplicationEntity find(final String tenantIdentifier) {
checkTenant(tenantIdentifier);
final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
return tenantApplicationEntityMapper.get(tenantIdentifier);
}
void deleteTenant(final String tenantIdentifier) {
final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
tenantApplicationEntityMapper.delete(tenantIdentifier);
}
void removeApplication(final String name) {
final ResultSet tenantApplicationResultSet =
this.cassandraSessionProvider.getAdminSession().execute("SELECT * FROM tenant_applications");
if (tenantApplicationResultSet != null) {
final Mapper<TenantApplicationEntity> tenantApplicationEntityMapper =
this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(TenantApplicationEntity.class);
final Result<TenantApplicationEntity> mappedTenantApplications = tenantApplicationEntityMapper.map(tenantApplicationResultSet);
for (TenantApplicationEntity tenantApplicationEntity : mappedTenantApplications) {
if (tenantApplicationEntity.getApplications().contains(name)) {
tenantApplicationEntity.getApplications().remove(name);
tenantApplicationEntityMapper.save(tenantApplicationEntity);
}
}
}
}
private void checkApplicationsExist(final Set<String> applications) {
final Mapper<ApplicationEntity> applicationEntityMapper =
this.cassandraSessionProvider.getAdminSessionMappingManager().mapper(ApplicationEntity.class);
for (final String name : applications) {
if (applicationEntityMapper.get(name) == null) {
throw ServiceException.badRequest("Application {0} not found!", name);
}
}
}
private void checkTenant(final @Nonnull String tenantIdentifier) {
final Optional<TenantEntity> tenantEntity = tenantCassandraRepository.get(tenantIdentifier);
tenantEntity.orElseThrow(() -> ServiceException.notFound("Tenant {0} not found.", tenantIdentifier));
}
}