This is an automated email from the ASF dual-hosted git repository. myrle pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract-cn-provisioner.git
commit d7b6cf9225698681eeeb6c9d40840a62cb17758a Author: myrle-krantz <mkra...@mifos.org> AuthorDate: Fri Jun 2 22:01:14 2017 +0200 Added activeMQ listeners to provisioner, and reordered initialization calls: Now the permittable groups are passed to identity before further initialization, and the listeners are used to ensure that that's complete before proceeding to further initialize. --- .../provisioner/api/v1/domain/ApplicationTest.java | 15 ++ .../api/v1/domain/AssignedApplicationTest.java | 15 ++ .../provisioner/api/v1/domain/TenantTest.java | 15 ++ .../io/mifos/provisioner/AbstractServiceTest.java | 7 +- .../tenant/TestTenantApplicationAssignment.java | 171 ++++++++++++++++----- service/build.gradle | 3 + .../config/ProvisionerActiveMQProperties.java | 55 +++++++ .../config/ProvisionerServiceConfig.java | 27 ++++ .../internal/listener/EventExpectation.java | 87 +++++++++++ .../provisioner/internal/listener/EventKey.java | 57 +++++++ .../internal/listener/IdentityListener.java | 126 +++++++++++++++ .../internal/service/TenantApplicationService.java | 63 ++++++-- .../applications/IdentityServiceInitializer.java | 62 ++++++-- .../IdentityServiceInitializerTest.java | 24 ++- shared.gradle | 2 + 15 files changed, 657 insertions(+), 72 deletions(-) diff --git a/api/src/test/java/io/mifos/provisioner/api/v1/domain/ApplicationTest.java b/api/src/test/java/io/mifos/provisioner/api/v1/domain/ApplicationTest.java index 96f426c..f93c86f 100644 --- a/api/src/test/java/io/mifos/provisioner/api/v1/domain/ApplicationTest.java +++ b/api/src/test/java/io/mifos/provisioner/api/v1/domain/ApplicationTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.api.v1.domain; import io.mifos.core.test.domain.ValidationTest; diff --git a/api/src/test/java/io/mifos/provisioner/api/v1/domain/AssignedApplicationTest.java b/api/src/test/java/io/mifos/provisioner/api/v1/domain/AssignedApplicationTest.java index 19f4f9f..0ee0a01 100644 --- a/api/src/test/java/io/mifos/provisioner/api/v1/domain/AssignedApplicationTest.java +++ b/api/src/test/java/io/mifos/provisioner/api/v1/domain/AssignedApplicationTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.api.v1.domain; import io.mifos.core.test.domain.ValidationTest; diff --git a/api/src/test/java/io/mifos/provisioner/api/v1/domain/TenantTest.java b/api/src/test/java/io/mifos/provisioner/api/v1/domain/TenantTest.java index ddf87e3..ceb8954 100644 --- a/api/src/test/java/io/mifos/provisioner/api/v1/domain/TenantTest.java +++ b/api/src/test/java/io/mifos/provisioner/api/v1/domain/TenantTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.api.v1.domain; import io.mifos.core.test.domain.ValidationTest; diff --git a/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java b/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java index 1fc4cdd..f47ca69 100644 --- a/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java +++ b/component-test/src/main/java/io/mifos/provisioner/AbstractServiceTest.java @@ -17,6 +17,7 @@ package io.mifos.provisioner; import io.mifos.core.test.env.TestEnvironment; import io.mifos.provisioner.api.v1.client.Provisioner; +import io.mifos.provisioner.config.ProvisionerActiveMQProperties; import io.mifos.provisioner.config.ProvisionerServiceConfig; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -36,7 +37,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, - classes = {AbstractServiceTest.TestConfiguration.class}) + classes = {AbstractServiceTest.TestConfiguration.class}, + properties = { + ProvisionerActiveMQProperties.ACTIVEMQ_BROKER_URL_PROP + "=" + ProvisionerActiveMQProperties.ACTIVEMQ_BROKER_URL_DEFAULT, + ProvisionerActiveMQProperties.ACTIVEMQ_CONCURRENCY_PROP + "=" + ProvisionerActiveMQProperties.ACTIVEMQ_CONCURRENCY_DEFAULT} +) public class AbstractServiceTest { private static final String APP_NAME = "provisioner-v1"; private static final String CLIENT_ID = "sillyRabbit"; diff --git a/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java b/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java index 9b46af3..a72bb38 100644 --- a/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java +++ b/component-test/src/main/java/io/mifos/provisioner/tenant/TestTenantApplicationAssignment.java @@ -15,6 +15,7 @@ */ package io.mifos.provisioner.tenant; +import com.google.gson.Gson; import io.mifos.anubis.api.v1.client.Anubis; import io.mifos.anubis.api.v1.domain.AllowedOperation; import io.mifos.anubis.api.v1.domain.ApplicationSignatureSet; @@ -22,7 +23,6 @@ import io.mifos.anubis.api.v1.domain.PermittableEndpoint; import io.mifos.anubis.api.v1.domain.Signature; import io.mifos.anubis.provider.SystemRsaKeyProvider; import io.mifos.anubis.test.v1.SystemSecurityEnvironment; -import io.mifos.anubis.token.TokenSerializationResult; import io.mifos.core.api.context.AutoSeshat; import io.mifos.core.api.util.ApiConstants; import io.mifos.core.api.util.ApiFactory; @@ -33,14 +33,17 @@ import io.mifos.identity.api.v1.client.IdentityManager; import io.mifos.identity.api.v1.domain.CallEndpointSet; import io.mifos.identity.api.v1.domain.Permission; import io.mifos.identity.api.v1.domain.PermittableGroup; +import io.mifos.identity.api.v1.events.ApplicationSignatureEvent; import io.mifos.permittedfeignclient.api.v1.client.ApplicationPermissionRequirements; import io.mifos.permittedfeignclient.api.v1.domain.ApplicationPermission; import io.mifos.provisioner.ProvisionerCassandraInitializer; import io.mifos.provisioner.ProvisionerMariaDBInitializer; import io.mifos.provisioner.api.v1.client.Provisioner; import io.mifos.provisioner.api.v1.domain.*; +import io.mifos.provisioner.config.ProvisionerActiveMQProperties; import io.mifos.provisioner.config.ProvisionerConstants; import io.mifos.provisioner.config.ProvisionerServiceConfig; +import io.mifos.provisioner.internal.listener.IdentityListener; import io.mifos.provisioner.internal.service.applications.ApplicationCallContextProvider; import io.mifos.provisioner.internal.util.TokenProvider; import org.junit.*; @@ -66,7 +69,6 @@ import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; @@ -75,7 +77,11 @@ import static org.mockito.Mockito.*; * @author Myrle Krantz */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = { + ProvisionerActiveMQProperties.ACTIVEMQ_BROKER_URL_PROP + "=" + ProvisionerActiveMQProperties.ACTIVEMQ_BROKER_URL_DEFAULT, + ProvisionerActiveMQProperties.ACTIVEMQ_CONCURRENCY_PROP + "=" + ProvisionerActiveMQProperties.ACTIVEMQ_CONCURRENCY_DEFAULT} +) public class TestTenantApplicationAssignment { private static final String APP_NAME = "provisioner-v1"; private static final String CLIENT_ID = "sillyRabbit"; @@ -135,6 +141,12 @@ public class TestTenantApplicationAssignment { @Autowired protected SystemRsaKeyProvider systemRsaKeyProvider; + @Autowired + protected IdentityListener identityListener; + + @Autowired + protected Gson gson; + private AutoSeshat autoSeshat; public TestTenantApplicationAssignment() { @@ -164,17 +176,6 @@ public class TestTenantApplicationAssignment { autoSeshat.close(); } - private static class TokenChecker implements Answer<TokenSerializationResult> { - TokenSerializationResult result = null; - - @Override - public TokenSerializationResult answer(final InvocationOnMock invocation) throws Throwable { - result = (TokenSerializationResult) invocation.callRealMethod(); - Assert.assertNotNull(result); - return result; - } - } - private class VerifyIsisInitializeContext implements Answer<ApplicationSignatureSet> { private final String keyTimestamp; @@ -211,6 +212,10 @@ public class TestTenantApplicationAssignment { return ret; } + String getKeyTimestamp() { + return keyTimestamp; + } + boolean isValidSecurityContext() { return validSecurityContext; } @@ -314,13 +319,103 @@ public class TestTenantApplicationAssignment { } } + private class VerifyIsisCreatePermittableGroup implements Answer<Void> { + + private boolean validSecurityContext = true; + private final String tenantIdentifier; + private int callCount = 0; + + private VerifyIsisCreatePermittableGroup(final String tenantIdentifier) { + this.tenantIdentifier = tenantIdentifier; + } + + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + final boolean validSecurityContextForThisCall = systemSecurityEnvironment.isValidSystemSecurityContext("identity", "1", tenantIdentifier); + validSecurityContext = validSecurityContext && validSecurityContextForThisCall; + callCount++; + + final PermittableGroup arg = invocation.getArgumentAt(0, PermittableGroup.class); + identityListener.onCreatePermittableGroup(tenantIdentifier, arg.getIdentifier()); + return null; + } + + boolean isValidSecurityContext() { + return validSecurityContext; + } + + int getCallCount() { + return callCount; + } + } + + private class VerifyIsisSetApplicationSignature implements Answer<Void> { + + private boolean validSecurityContext = false; + private final String tenantIdentifier; + + private VerifyIsisSetApplicationSignature(final String tenantIdentifier) { + this.tenantIdentifier = tenantIdentifier; + } + + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + validSecurityContext = systemSecurityEnvironment.isValidSystemSecurityContext("identity", "1", tenantIdentifier); + + final String applicationIdentifier = invocation.getArgumentAt(0, String.class); + final String keyTimestamp = invocation.getArgumentAt(1, String.class); + identityListener.onSetApplicationSignature(tenantIdentifier, + gson.toJson(new ApplicationSignatureEvent(applicationIdentifier, keyTimestamp))); + return null; + } + + boolean isValidSecurityContext() { + return validSecurityContext; + } + } + + private class VerifyIsisCreateApplicationPermission implements Answer<Void> { + + private boolean validSecurityContext = true; + private final String tenantIdentifier; + private final String applicationIdentifier; + private int callCount = 0; + + private VerifyIsisCreateApplicationPermission(final String tenantIdentifier, final String applicationIdentifier) { + this.tenantIdentifier = tenantIdentifier; + this.applicationIdentifier = applicationIdentifier; + } + + @Override + public Void answer(final InvocationOnMock invocation) throws Throwable { + final boolean validSecurityContextForThisCall = systemSecurityEnvironment.isValidSystemSecurityContext("identity", "1", tenantIdentifier); + validSecurityContext = validSecurityContext && validSecurityContextForThisCall; + callCount++; + + final String callApplicationIdentifier = invocation.getArgumentAt(0, String.class); + Assert.assertEquals(this.applicationIdentifier, callApplicationIdentifier); + return null; + } + + + + boolean isValidSecurityContext() { + return validSecurityContext; + } + + int getCallCount() { + return callCount; + } + } + @Test - public void testTenantApplicationAssignment() throws InterruptedException { + public void testTenantApplicationAssignment() throws Exception { //Create io.mifos.provisioner.tenant final Tenant tenant = Fixture.getCompTestTenant(); provisioner.createTenant(tenant); + //Create identity service application final Application identityServiceApp = new Application(); identityServiceApp.setName("identity-v1"); @@ -348,9 +443,6 @@ public class TestTenantApplicationAssignment { } doAnswer(verifyInitializeContextAndReturnSignature).when(identityServiceMock).initialize(anyString()); - final TokenChecker tokenChecker = new TokenChecker(); - doAnswer(tokenChecker).when(tokenProviderSpy).createToken(tenant.getIdentifier(), "identity-v1", 2L, TimeUnit.MINUTES); - { final IdentityManagerInitialization identityServiceAdminInitialization = provisioner.assignIdentityManager(tenant.getIdentifier(), identityServiceAssigned); @@ -360,9 +452,6 @@ public class TestTenantApplicationAssignment { Assert.assertNotNull(identityServiceAdminInitialization.getAdminPassword()); } - verify(applicationCallContextProviderSpy, atMost(2)).getApplicationCallContext(tenant.getIdentifier(), "identity-v1"); - - //Create horus application. final Application officeApp = new Application(); officeApp.setName("office-v1"); @@ -397,43 +486,47 @@ public class TestTenantApplicationAssignment { final VerifyCreateSignatureSetContext verifyCreateSignatureSetContext; final VerifyAnubisPermittablesContext verifyAnubisPermittablesContext; final VerifyAnputRequiredPermissionsContext verifyAnputRequiredPermissionsContext; + final VerifyIsisCreatePermittableGroup verifyIsisCreatePermittableGroup; + final VerifyIsisSetApplicationSignature verifyIsisSetApplicationSignature; + final VerifyIsisCreateApplicationPermission verifyIsisCreateApplicationPermission; try (final AutoTenantContext ignored = new AutoTenantContext(tenant.getIdentifier())) { verifyAnubisInitializeContext = new VerifyAnubisInitializeContext("office", tenant.getIdentifier()); verifyCreateSignatureSetContext = new VerifyCreateSignatureSetContext(keysInApplicationSignature, "office", tenant.getIdentifier()); verifyAnubisPermittablesContext = new VerifyAnubisPermittablesContext(Arrays.asList(xxPermittableEndpoint, xxPermittableEndpoint, xyPermittableEndpoint, xyGetPermittableEndpoint, mPermittableEndpoint), tenant.getIdentifier()); verifyAnputRequiredPermissionsContext = new VerifyAnputRequiredPermissionsContext(Arrays.asList(forFooPermission, forBarPermission), tenant.getIdentifier()); + verifyIsisCreatePermittableGroup = new VerifyIsisCreatePermittableGroup(tenant.getIdentifier()); + verifyIsisSetApplicationSignature = new VerifyIsisSetApplicationSignature(tenant.getIdentifier()); + verifyIsisCreateApplicationPermission = new VerifyIsisCreateApplicationPermission(tenant.getIdentifier(), "office-v1"); } doAnswer(verifyAnubisInitializeContext).when(anubisMock).initializeResources(); doAnswer(verifyCreateSignatureSetContext).when(anubisMock).createSignatureSet(anyString(), anyObject()); doAnswer(verifyAnubisPermittablesContext).when(anubisMock).getPermittableEndpoints(); doAnswer(verifyAnputRequiredPermissionsContext).when(anputMock).getRequiredPermissions(); - + doAnswer(verifyIsisCreatePermittableGroup).when(identityServiceMock).createPermittableGroup(new PermittableGroup("x", Arrays.asList(xxPermittableEndpoint, xyPermittableEndpoint, xyGetPermittableEndpoint))); + doAnswer(verifyIsisCreatePermittableGroup).when(identityServiceMock).createPermittableGroup(new PermittableGroup("m", Collections.singletonList(mPermittableEndpoint))); + doAnswer(verifyIsisSetApplicationSignature).when(identityServiceMock).setApplicationSignature( + "office-v1", + verifyInitializeContextAndReturnSignature.getKeyTimestamp(), + new Signature(keysInApplicationSignature.getPublicKeyMod(), keysInApplicationSignature.getPublicKeyExp())); + doAnswer(verifyIsisCreateApplicationPermission).when(identityServiceMock).createApplicationPermission("office-v1", new Permission("x", AllowedOperation.ALL)); + doAnswer(verifyIsisCreateApplicationPermission).when(identityServiceMock).createApplicationPermission("office-v1", new Permission("m", Collections.singleton(AllowedOperation.READ))); + doAnswer(verifyIsisCreateApplicationPermission).when(identityServiceMock).createApplicationCallEndpointSet("office-v1", new CallEndpointSet("forPurposeFoo", Collections.singletonList("x"))); + doAnswer(verifyIsisCreateApplicationPermission).when(identityServiceMock).createApplicationCallEndpointSet("office-v1", new CallEndpointSet("forPurposeBar", Collections.singletonList("m"))); { provisioner.assignApplications(tenant.getIdentifier(), Collections.singletonList(officeAssigned)); - Thread.sleep(1500L); //Application assigning is asynchronous and I have no message queue. - } - - verify(applicationCallContextProviderSpy).getApplicationCallContext(tenant.getIdentifier(), "office-v1"); - verify(applicationCallContextProviderSpy, never()).getApplicationCallContext(eq(Fixture.TENANT_NAME), Mockito.anyString()); - verify(tokenProviderSpy).createToken(tenant.getIdentifier(), "office-v1", 2L, TimeUnit.MINUTES); - try (final AutoTenantContext ignored = new AutoTenantContext(tenant.getIdentifier())) { - verify(identityServiceMock).setApplicationSignature( - "office-v1", - systemSecurityEnvironment.tenantKeyTimestamp(), - new Signature(keysInApplicationSignature.getPublicKeyMod(), keysInApplicationSignature.getPublicKeyExp())); - verify(identityServiceMock).createPermittableGroup(new PermittableGroup("x", Arrays.asList(xxPermittableEndpoint, xyPermittableEndpoint, xyGetPermittableEndpoint))); - verify(identityServiceMock).createPermittableGroup(new PermittableGroup("m", Collections.singletonList(mPermittableEndpoint))); - verify(identityServiceMock).createApplicationPermission("office-v1", new Permission("x", AllowedOperation.ALL)); - verify(identityServiceMock).createApplicationPermission("office-v1", new Permission("m", Collections.singleton(AllowedOperation.READ))); - verify(identityServiceMock).createApplicationCallEndpointSet("office-v1", new CallEndpointSet("forPurposeFoo", Collections.singletonList("x"))); - verify(identityServiceMock).createApplicationCallEndpointSet("office-v1", new CallEndpointSet("forPurposeBar", Collections.singletonList("m"))); + Thread.sleep(1500L); //Application assigning is asynchronous and I have no message queue. } Assert.assertTrue(verifyAnubisInitializeContext.isValidSecurityContext()); Assert.assertTrue(verifyCreateSignatureSetContext.isValidSecurityContext()); Assert.assertTrue(verifyAnubisPermittablesContext.isValidSecurityContext()); Assert.assertTrue(verifyAnputRequiredPermissionsContext.isValidSecurityContext()); + Assert.assertEquals(2, verifyIsisCreatePermittableGroup.getCallCount()); + Assert.assertTrue(verifyIsisCreatePermittableGroup.isValidSecurityContext()); + Assert.assertTrue(verifyIsisSetApplicationSignature.isValidSecurityContext()); + Assert.assertEquals(4, verifyIsisCreateApplicationPermission.getCallCount()); + Assert.assertTrue(verifyIsisCreateApplicationPermission.isValidSecurityContext()); } } diff --git a/service/build.gradle b/service/build.gradle index edb1055..2bd249e 100644 --- a/service/build.gradle +++ b/service/build.gradle @@ -30,6 +30,9 @@ dependencies { [group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'], [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'], [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'], + [group: 'org.springframework', name: 'spring-jms', version: versions.springcontext], + [group: 'org.apache.activemq', name: 'activemq-broker', version: versions.activeMQ], + [group: 'org.apache.activemq', name: 'activemq-spring', version: versions.activeMQ], [group: 'io.mifos.provisioner', name: 'api', version: project.version], [group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis], [group: 'io.mifos.anubis', name: 'api', version: versions.frameworkanubis], diff --git a/service/src/main/java/io/mifos/provisioner/config/ProvisionerActiveMQProperties.java b/service/src/main/java/io/mifos/provisioner/config/ProvisionerActiveMQProperties.java new file mode 100644 index 0000000..640b921 --- /dev/null +++ b/service/src/main/java/io/mifos/provisioner/config/ProvisionerActiveMQProperties.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Myrle Krantz + */ +@ConfigurationProperties(prefix = "activemq") +public class ProvisionerActiveMQProperties { + @SuppressWarnings("unused") + final public static String ACTIVEMQ_BROKER_URL_PROP = "activemq.brokerUrl"; + @SuppressWarnings("unused") + final public static String ACTIVEMQ_CONCURRENCY_PROP = "activemq.concurrency"; + @SuppressWarnings("WeakerAccess") + final public static String ACTIVEMQ_BROKER_URL_DEFAULT = "vm://localhost?broker.persistent=false"; + @SuppressWarnings("WeakerAccess") + final public static String ACTIVEMQ_CONCURRENCY_DEFAULT = "3-10"; + + private String brokerUrl = ACTIVEMQ_BROKER_URL_DEFAULT; + private String concurrency = ACTIVEMQ_CONCURRENCY_DEFAULT; + + public ProvisionerActiveMQProperties() { + } + + public String getBrokerUrl() { + return brokerUrl; + } + + public void setBrokerUrl(String brokerUrl) { + this.brokerUrl = brokerUrl; + } + + public String getConcurrency() { + return concurrency; + } + + public void setConcurrency(String concurrency) { + this.concurrency = concurrency; + } +} diff --git a/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java b/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java index 2d80cce..05b849a 100644 --- a/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java +++ b/service/src/main/java/io/mifos/provisioner/config/ProvisionerServiceConfig.java @@ -26,14 +26,19 @@ import io.mifos.core.lang.config.EnableServiceException; import io.mifos.core.mariadb.config.EnableMariaDB; import io.mifos.provisioner.internal.util.TokenProvider; import io.mifos.tool.crypto.config.EnableCrypto; +import org.apache.activemq.jms.pool.PooledConnectionFactory; +import org.apache.activemq.spring.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; +import org.springframework.jms.config.DefaultJmsListenerContainerFactory; +import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -43,6 +48,7 @@ import java.math.BigInteger; @EnableAutoConfiguration @ComponentScan({ "io.mifos.provisioner.internal.service", + "io.mifos.provisioner.internal.listener", "io.mifos.provisioner.internal.service.applications", "io.mifos.provisioner.internal.repository", "io.mifos.provisioner.rest.controller", @@ -54,6 +60,7 @@ import java.math.BigInteger; @EnableCassandra @EnableServiceException @EnableApplicationName +@EnableConfigurationProperties({ProvisionerActiveMQProperties.class}) public class ProvisionerServiceConfig extends WebMvcConfigurerAdapter { public ProvisionerServiceConfig() { @@ -86,4 +93,24 @@ public class ProvisionerServiceConfig extends WebMvcConfigurerAdapter { public void configurePathMatch(final PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(Boolean.FALSE); } + + @Bean + public PooledConnectionFactory jmsFactory(final ProvisionerActiveMQProperties activeMQProperties) { + final PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(); + final ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(); + activeMQConnectionFactory.setBrokerURL(activeMQProperties.getBrokerUrl()); + pooledConnectionFactory.setConnectionFactory(activeMQConnectionFactory); + + return pooledConnectionFactory; + } + + + @Bean + public JmsListenerContainerFactory jmsListenerContainerFactory(final PooledConnectionFactory jmsFactory, final ProvisionerActiveMQProperties activeMQProperties) { + final DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); + factory.setPubSubDomain(true); + factory.setConnectionFactory(jmsFactory); + factory.setConcurrency(activeMQProperties.getConcurrency()); + return factory; + } } diff --git a/service/src/main/java/io/mifos/provisioner/internal/listener/EventExpectation.java b/service/src/main/java/io/mifos/provisioner/internal/listener/EventExpectation.java new file mode 100644 index 0000000..23d0e99 --- /dev/null +++ b/service/src/main/java/io/mifos/provisioner/internal/listener/EventExpectation.java @@ -0,0 +1,87 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.internal.listener; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author Myrle Krantz + */ +public class EventExpectation { + private final EventKey key; + private boolean eventFound = false; + private boolean eventWithdrawn = false; + + private final ReentrantLock lock = new ReentrantLock(); + + private final Condition found = lock.newCondition(); + + EventExpectation(final EventKey key) { + this.key = key; + } + + EventKey getKey() { + return key; + } + + void setEventFound(boolean eventFound) { + lock.lock(); + try { + this.eventFound = eventFound; + found.signal(); + } + finally { + lock.unlock(); + } + } + + void setEventWithdrawn(boolean eventWithdrawn) { + lock.lock(); + try { + this.eventWithdrawn = eventWithdrawn; + found.signal(); + } + finally { + lock.unlock(); + } + } + + public boolean waitForOccurrence(long timeout, TimeUnit timeUnit) throws InterruptedException { + + lock.lock(); + try { + if (eventFound) + return true; + + if (eventWithdrawn) + return false; + + found.await(timeout, timeUnit); + + return (eventFound); + } + finally { + lock.unlock(); + } + } + + @Override + public String toString() { + return key.toString(); + } +} diff --git a/service/src/main/java/io/mifos/provisioner/internal/listener/EventKey.java b/service/src/main/java/io/mifos/provisioner/internal/listener/EventKey.java new file mode 100644 index 0000000..13f985e --- /dev/null +++ b/service/src/main/java/io/mifos/provisioner/internal/listener/EventKey.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.internal.listener; + +import java.util.Objects; + +/** + * @author Myrle Krantz + */ +class EventKey { + private String tenantIdentifier; + private String eventName; + private Object event; + + EventKey(final String tenantIdentifier, final String eventName, final Object event) { + this.tenantIdentifier = tenantIdentifier; + this.eventName = eventName; + this.event = event; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EventKey eventKey = (EventKey) o; + return Objects.equals(tenantIdentifier, eventKey.tenantIdentifier) && + Objects.equals(eventName, eventKey.eventName) && + Objects.equals(event, eventKey.event); + } + + @Override + public int hashCode() { + return Objects.hash(tenantIdentifier, eventName, event); + } + + @Override + public String toString() { + return "EventKey{" + + "tenantIdentifier='" + tenantIdentifier + '\'' + + ", eventName='" + eventName + '\'' + + ", event=" + event + + '}'; + } +} diff --git a/service/src/main/java/io/mifos/provisioner/internal/listener/IdentityListener.java b/service/src/main/java/io/mifos/provisioner/internal/listener/IdentityListener.java new file mode 100644 index 0000000..fb4d638 --- /dev/null +++ b/service/src/main/java/io/mifos/provisioner/internal/listener/IdentityListener.java @@ -0,0 +1,126 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * 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 io.mifos.provisioner.internal.listener; + +import com.google.gson.Gson; +import io.mifos.core.lang.config.TenantHeaderFilter; +import io.mifos.identity.api.v1.events.ApplicationPermissionEvent; +import io.mifos.identity.api.v1.events.ApplicationSignatureEvent; +import io.mifos.identity.api.v1.events.EventConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static io.mifos.identity.api.v1.events.EventConstants.OPERATION_POST_APPLICATION_PERMISSION; +import static io.mifos.identity.api.v1.events.EventConstants.OPERATION_POST_PERMITTABLE_GROUP; +import static io.mifos.identity.api.v1.events.EventConstants.OPERATION_PUT_APPLICATION_SIGNATURE; + +/** + * @author Myrle Krantz + */ +@Component +public class IdentityListener { + private final Gson gson; + private final Map<EventKey, EventExpectation> eventExpectations = new ConcurrentHashMap<>(); + + @Autowired + public IdentityListener(final Gson gson) { + this.gson = gson; + } + + @JmsListener( + subscription = EventConstants.DESTINATION, + destination = EventConstants.DESTINATION, + selector = EventConstants.SELECTOR_POST_PERMITTABLE_GROUP + ) + public void onCreatePermittableGroup( + @Header(TenantHeaderFilter.TENANT_HEADER)final String tenantIdentifier, + final String payload) throws Exception { + final EventExpectation eventExpectation = eventExpectations.remove(new EventKey(tenantIdentifier, OPERATION_POST_PERMITTABLE_GROUP, payload)); + if (eventExpectation != null) { + eventExpectation.setEventFound(true); + } + } + + @JmsListener( + subscription = EventConstants.DESTINATION, + destination = EventConstants.DESTINATION, + selector = EventConstants.SELECTOR_POST_APPLICATION_PERMISSION + ) + public void onCreateApplicationPermission( + @Header(TenantHeaderFilter.TENANT_HEADER)final String tenantIdentifier, + final String payload) throws Exception { + final ApplicationPermissionEvent event = gson.fromJson(payload, ApplicationPermissionEvent.class); + final EventExpectation eventExpectation = eventExpectations.remove(new EventKey(tenantIdentifier, OPERATION_POST_APPLICATION_PERMISSION, event)); + if (eventExpectation != null) { + eventExpectation.setEventFound(true); + } + } + + @JmsListener( + subscription = EventConstants.DESTINATION, + destination = EventConstants.DESTINATION, + selector = EventConstants.SELECTOR_PUT_APPLICATION_SIGNATURE + ) + public void onSetApplicationSignature( + @Header(TenantHeaderFilter.TENANT_HEADER)final String tenantIdentifier, + final String payload) throws Exception { + final ApplicationSignatureEvent event = gson.fromJson(payload, ApplicationSignatureEvent.class); + final EventExpectation eventExpectation = eventExpectations.remove(new EventKey(tenantIdentifier, OPERATION_PUT_APPLICATION_SIGNATURE, event)); + if (eventExpectation != null) { + eventExpectation.setEventFound(true); + } + } + + public EventExpectation expectPermittableGroupCreation(final String tenantIdentifier, + final String permittableGroupIdentifier) { + final EventKey key = new EventKey(tenantIdentifier, OPERATION_POST_PERMITTABLE_GROUP, permittableGroupIdentifier); + final EventExpectation value = new EventExpectation(key); + eventExpectations.put(key, value); + return value; + } + + public EventExpectation expectApplicationPermissionCreation(final String tenantIdentifier, + final String applicationIdentifier, + final String permittableGroupIdentifier) { + final ApplicationPermissionEvent expectedEvent = new ApplicationPermissionEvent(applicationIdentifier, permittableGroupIdentifier); + final EventKey key = new EventKey(tenantIdentifier, OPERATION_POST_APPLICATION_PERMISSION, expectedEvent); + final EventExpectation value = new EventExpectation(key); + eventExpectations.put(key, value); + return value; + } + + public EventExpectation expectApplicationSignatureSet(final String tenantIdentifier, + final String applicationIdentifier, + final String keyTimestamp) { + final ApplicationSignatureEvent expectedEvent = new ApplicationSignatureEvent(applicationIdentifier, keyTimestamp); + final EventKey key = new EventKey(tenantIdentifier, OPERATION_PUT_APPLICATION_SIGNATURE, expectedEvent); + final EventExpectation value = new EventExpectation(key); + eventExpectations.put(key, value); + return value; + } + + public void withdrawExpectation(final EventExpectation eventExpectation) { + final EventExpectation expectation = eventExpectations.remove(eventExpectation.getKey()); + if (expectation != null) { + eventExpectation.setEventWithdrawn(true); + } + } +} diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java b/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java index 149f062..cc073b4 100644 --- a/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java +++ b/service/src/main/java/io/mifos/provisioner/internal/service/TenantApplicationService.java @@ -23,23 +23,26 @@ import io.mifos.anubis.config.TenantSignatureRepository; import io.mifos.core.cassandra.core.CassandraSessionProvider; import io.mifos.core.lang.AutoTenantContext; import io.mifos.core.lang.ServiceException; +import io.mifos.provisioner.config.ProvisionerConstants; +import io.mifos.provisioner.internal.listener.EventExpectation; import io.mifos.provisioner.internal.repository.ApplicationEntity; import io.mifos.provisioner.internal.repository.TenantApplicationEntity; import io.mifos.provisioner.internal.repository.TenantCassandraRepository; import io.mifos.provisioner.internal.repository.TenantEntity; import io.mifos.provisioner.internal.service.applications.AnubisInitializer; import io.mifos.provisioner.internal.service.applications.IdentityServiceInitializer; +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; import javax.annotation.Nonnull; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; @Component public class TenantApplicationService { @@ -49,19 +52,22 @@ public class TenantApplicationService { 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) { + 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 @@ -79,25 +85,58 @@ public class TenantApplicationService { final Set<ApplicationNameToUriPair> applicationNameToUriPairs = getApplicationNameToUriPairs(tenantApplicationEntity, appNameToUriMap); - getLatestIdentityManagerSignatureSet(tenantEntity) - .ifPresent(y -> initializeSecurity(tenantEntity, y, applicationNameToUriPairs)); + 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) { + 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.initializeAnubis( - tenantEntity.getIdentifier(), + tenantIdentifier, x.name, x.uri, identityManagerSignatureSet.getTimestamp(), identityManagerSignatureSet.getIdentityManagerSignature()); identityServiceInitializer.postApplicationDetails( - tenantEntity.getIdentifier(), - tenantEntity.getIdentityManagerApplicationName(), - tenantEntity.getIdentityManagerApplicationUri(), + tenantIdentifier, + identityManagerApplicationName, + identityManagerApplicationUri, x.name, x.uri, applicationSignatureSet); diff --git a/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java b/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java index a2b24db..bca9b4e 100644 --- a/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java +++ b/service/src/main/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializer.java @@ -30,6 +30,8 @@ import io.mifos.identity.api.v1.domain.PermittableGroup; import io.mifos.permittedfeignclient.api.v1.client.ApplicationPermissionRequirements; import io.mifos.permittedfeignclient.api.v1.domain.ApplicationPermission; import io.mifos.provisioner.config.ProvisionerConstants; +import io.mifos.provisioner.internal.listener.EventExpectation; +import io.mifos.provisioner.internal.listener.IdentityListener; import io.mifos.tool.crypto.HashGenerator; import org.apache.commons.lang.RandomStringUtils; import org.slf4j.Logger; @@ -41,6 +43,7 @@ import org.springframework.util.Base64Utils; import javax.annotation.Nonnull; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -49,7 +52,7 @@ import java.util.stream.Stream; */ @Component public class IdentityServiceInitializer { - + private final IdentityListener identityListener; private final ApplicationCallContextProvider applicationCallContextProvider; private final HashGenerator hashGenerator; private final Logger logger; @@ -83,9 +86,11 @@ public class IdentityServiceInitializer { @Autowired public IdentityServiceInitializer( + final IdentityListener identityListener, final ApplicationCallContextProvider applicationCallContextProvider, final HashGenerator hashGenerator, @Qualifier(ProvisionerConstants.LOGGER_NAME) final Logger logger) { + this.identityListener = identityListener; this.applicationCallContextProvider = applicationCallContextProvider; this.hashGenerator = hashGenerator; this.logger = logger; @@ -128,18 +133,42 @@ public class IdentityServiceInitializer { } } + public List<EventExpectation> postApplicationPermittableGroups( + final @Nonnull String tenantIdentifier, + final @Nonnull String identityManagerApplicationName, + final @Nonnull String identityManagerApplicationUri, + final @Nonnull String applicationUri) { + final List<PermittableEndpoint> permittables; + try (final AutoCloseable ignored = applicationCallContextProvider.getApplicationCallGuestContext(tenantIdentifier)) { + permittables = getPermittables(applicationUri); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + + try (final AutoCloseable ignored + = applicationCallContextProvider.getApplicationCallContext(tenantIdentifier, identityManagerApplicationName)) { + final IdentityManager identityService = applicationCallContextProvider.getApplication(IdentityManager.class, identityManagerApplicationUri); + + final Stream<PermittableGroup> permittableGroups = getPermittableGroups(permittables); + //You might look at this and wonder: "Why isn't she returning a stream here? She's just turning it back into + //a stream on the other side..." + //The answer is that you need the createOrFindPermittableGroup to be executed in the proper tenant context. If you + //return the stream, the call to createOrFindPermittableGroup will be executed when the stream is itereated over. + return permittableGroups.map(x -> createOrFindPermittableGroup(identityService, x)).collect(Collectors.toList()); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } + public void postApplicationDetails( final @Nonnull String tenantIdentifier, final @Nonnull String identityManagerApplicationName, final @Nonnull String identityManagerApplicationUri, final @Nonnull String applicationName, final @Nonnull String applicationUri, - final @Nonnull ApplicationSignatureSet applicationSignatureSet) - { - final List<PermittableEndpoint> permittables; + final @Nonnull ApplicationSignatureSet applicationSignatureSet) { final List<ApplicationPermission> applicationPermissionRequirements; try (final AutoCloseable ignored = applicationCallContextProvider.getApplicationCallGuestContext(tenantIdentifier)) { - permittables = getPermittables(applicationUri); applicationPermissionRequirements = getApplicationPermissionRequirements(applicationName, applicationUri); } catch (final Exception e) { @@ -150,11 +179,11 @@ public class IdentityServiceInitializer { = applicationCallContextProvider.getApplicationCallContext(tenantIdentifier, identityManagerApplicationName)) { final IdentityManager identityService = applicationCallContextProvider.getApplication(IdentityManager.class, identityManagerApplicationUri); + final EventExpectation eventExpectation = identityListener.expectApplicationSignatureSet(tenantIdentifier, applicationName, applicationSignatureSet.getTimestamp()); identityService.setApplicationSignature(applicationName, applicationSignatureSet.getTimestamp(), applicationSignatureSet.getApplicationSignature()); - //TODO: I need to know when this is done. ActiveMQ. sigh. - - final Stream<PermittableGroup> permittableGroups = getPermittableGroups(permittables); - permittableGroups.forEach(x -> createOrFindPermittableGroup(identityService, x)); + if (!eventExpectation.waitForOccurrence(5, TimeUnit.SECONDS)) { + logger.warn("Expected action in identity didn't complete {}.", eventExpectation); + } applicationPermissionRequirements.forEach(x -> createOrFindApplicationPermission(identityService, applicationName, x)); @@ -218,32 +247,37 @@ public class IdentityServiceInitializer { }); } - void createOrFindPermittableGroup( + EventExpectation createOrFindPermittableGroup( final @Nonnull IdentityManager identityService, final @Nonnull PermittableGroup permittableGroup) { + final EventExpectation eventExpectation = identityListener.expectPermittableGroupCreation(TenantContextHolder.checkedGetIdentifier(), permittableGroup.getIdentifier()); try { identityService.createPermittableGroup(permittableGroup); - logger.info("Group '{}' successfully created in identity service for tenant {}.", permittableGroup.getIdentifier(), TenantContextHolder.checkedGetIdentifier()); + logger.info("Group '{}' creation successfully requested in identity service for tenant {}.", permittableGroup.getIdentifier(), TenantContextHolder.checkedGetIdentifier()); } catch (final PermittableGroupAlreadyExistsException groupAlreadyExistsException) { //if the group already exists, read out and compare. If the group is the same, there is nothing left to do. final PermittableGroup existingGroup = identityService.getPermittableGroup(permittableGroup.getIdentifier()); if (!existingGroup.getIdentifier().equals(permittableGroup.getIdentifier())) { - logger.error("Group '{}' already exists, but has a different name {} (strange).", permittableGroup.getIdentifier(), existingGroup.getIdentifier()); + logger.error("Group '{}' already exists for tenant {}, but has a different name {} (strange).", permittableGroup.getIdentifier(), TenantContextHolder.checkedGetIdentifier(), existingGroup.getIdentifier()); + identityListener.withdrawExpectation(eventExpectation); } //Compare as sets because I'm not going to get into a hissy fit over order. final Set<PermittableEndpoint> existingGroupPermittables = new HashSet<>(existingGroup.getPermittables()); final Set<PermittableEndpoint> newGroupPermittables = new HashSet<>(permittableGroup.getPermittables()); if (!existingGroupPermittables.equals(newGroupPermittables)) { - logger.error("Group '{}' already exists, but has different contents.", permittableGroup.getIdentifier()); + logger.error("Group '{}' already exists for tenant {}, but has different contents.", permittableGroup.getIdentifier(), TenantContextHolder.checkedGetIdentifier()); + identityListener.withdrawExpectation(eventExpectation); } } catch (final RuntimeException unexpected) { - logger.error("Creating group '{}' failed.", permittableGroup.getIdentifier(), unexpected); + logger.error("Creating group '{}' for tenant {} failed.", permittableGroup.getIdentifier(), TenantContextHolder.checkedGetIdentifier(), unexpected); + identityListener.withdrawExpectation(eventExpectation); } + return eventExpectation; } private void createOrFindApplicationPermission( diff --git a/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java b/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java index ebe6e18..9644fbf 100644 --- a/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java +++ b/service/src/test/java/io/mifos/provisioner/internal/service/applications/IdentityServiceInitializerTest.java @@ -17,9 +17,11 @@ package io.mifos.provisioner.internal.service.applications; import io.mifos.anubis.api.v1.client.Anubis; import io.mifos.anubis.api.v1.domain.PermittableEndpoint; +import io.mifos.core.lang.AutoTenantContext; import io.mifos.identity.api.v1.client.IdentityManager; import io.mifos.identity.api.v1.client.PermittableGroupAlreadyExistsException; import io.mifos.identity.api.v1.domain.PermittableGroup; +import io.mifos.provisioner.internal.listener.IdentityListener; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -56,6 +58,7 @@ public class IdentityServiceInitializerTest { @Test public void getPermittablesAnubisCallFails() throws Exception { + final IdentityListener identityListenerMock = Mockito.mock(IdentityListener.class); final ApplicationCallContextProvider applicationCallContextProviderMock = Mockito.mock(ApplicationCallContextProvider.class); final Logger loggerMock = Mockito.mock(Logger.class); final Anubis anubisMock = Mockito.mock(Anubis.class); @@ -64,7 +67,7 @@ public class IdentityServiceInitializerTest { //noinspection unchecked when(anubisMock.getPermittableEndpoints()).thenThrow(IllegalStateException.class); - final List<PermittableEndpoint> ret = new IdentityServiceInitializer(applicationCallContextProviderMock, null, loggerMock) + final List<PermittableEndpoint> ret = new IdentityServiceInitializer(identityListenerMock, applicationCallContextProviderMock, null, loggerMock) .getPermittables("blah"); Assert.assertEquals(ret, Collections.emptyList()); @@ -89,39 +92,48 @@ public class IdentityServiceInitializerTest { @Test public void createOrFindPermittableGroupThatAlreadyExists() throws Exception { + final IdentityListener identityListenerMock = Mockito.mock(IdentityListener.class); final Logger loggerMock = Mockito.mock(Logger.class); final IdentityManager identityServiceMock = Mockito.mock(IdentityManager.class); doThrow(PermittableGroupAlreadyExistsException.class).when(identityServiceMock).createPermittableGroup(group1); doReturn(reorderedGroup1).when(identityServiceMock).getPermittableGroup(group1.getIdentifier()); - new IdentityServiceInitializer(null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1); + try (final AutoTenantContext ignored = new AutoTenantContext("blah")) { + new IdentityServiceInitializer(identityListenerMock, null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1); + } } @Test public void createOrFindPermittableGroupThatAlreadyExistsDifferently() throws Exception { + final IdentityListener identityListenerMock = Mockito.mock(IdentityListener.class); final Logger loggerMock = Mockito.mock(Logger.class); final IdentityManager identityServiceMock = Mockito.mock(IdentityManager.class); doThrow(PermittableGroupAlreadyExistsException.class).when(identityServiceMock).createPermittableGroup(group1); doReturn(changedGroup1).when(identityServiceMock).getPermittableGroup(group1.getIdentifier()); - new IdentityServiceInitializer(null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1); + try (final AutoTenantContext ignored = new AutoTenantContext("blah")) { + new IdentityServiceInitializer(identityListenerMock, null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1); + } - verify(loggerMock).error(anyString(), anyString()); + verify(loggerMock).error(anyString(), anyString(), anyString()); } @Test public void createOrFindPermittableGroupWhenIsisCallFails() throws Exception { + final IdentityListener identityListenerMock = Mockito.mock(IdentityListener.class); final Logger loggerMock = Mockito.mock(Logger.class); final IdentityManager identityServiceMock = Mockito.mock(IdentityManager.class); doThrow(IllegalStateException.class).when(identityServiceMock).createPermittableGroup(group1); doReturn(changedGroup1).when(identityServiceMock).getPermittableGroup(group1.getIdentifier()); - new IdentityServiceInitializer(null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1); + try (final AutoTenantContext ignored = new AutoTenantContext("blah")) { + new IdentityServiceInitializer(identityListenerMock, null, null, loggerMock).createOrFindPermittableGroup(identityServiceMock, group1); + } - verify(loggerMock).error(anyString(), anyString(), isA(IllegalStateException.class)); + verify(loggerMock).error(anyString(), anyString(), anyString(), isA(IllegalStateException.class)); } diff --git a/shared.gradle b/shared.gradle index 77669da..e4f5378 100644 --- a/shared.gradle +++ b/shared.gradle @@ -12,6 +12,8 @@ ext.versions = [ frameworkmariadb : '0.1.0-BUILD-SNAPSHOT', frameworkcrypto : '0.1.0-BUILD-SNAPSHOT', frameworktest : '0.1.0-BUILD-SNAPSHOT', + springcontext : '4.3.3.RELEASE', + activeMQ : '5.13.2', validator : '5.3.0.Final' ] -- To stop receiving notification emails like this one, please contact my...@apache.org.