Skip to content
Permalink
Browse files
Merge pull request #6 from myrle-krantz/develop
Fixing up authentication.  Not finished integration testing yet though.
  • Loading branch information
myrle-krantz committed May 23, 2017
2 parents 329b53b + 11f8c26 commit 4cae7c5f353e11eca2da3812d339d9b5a7fb9442
Show file tree
Hide file tree
Showing 23 changed files with 752 additions and 166 deletions.
@@ -40,7 +40,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
@@ -52,8 +52,7 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

import static org.mockito.Matchers.*;
import java.util.Optional;

/**
* @author Myrle Krantz
@@ -109,7 +108,7 @@ public Logger logger() {
@Autowired
EventRecorder eventRecorder;

@SpyBean
@MockBean
BeatPublisherService beatPublisherServiceSpy;

@Before
@@ -132,20 +131,23 @@ public boolean waitForInitialize() {
}

Beat createBeat(final String applicationName, final String beatIdentifier) throws InterruptedException {
final String tenantIdentifier = tenantDataStoreContext.getTenantName();
final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));

final Beat beat = new Beat();
beat.setIdentifier(beatIdentifier);
beat.setAlignmentHour(now.getHour());

final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, beat.getAlignmentHour());
Mockito.doReturn(true).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantDataStoreContext.getTenantName()), Matchers.eq(applicationName),
AdditionalMatchers.or(Matchers.eq(expectedBeatTimestamp), Matchers.eq(getNextTimeStamp(expectedBeatTimestamp))));
Mockito.doReturn(Optional.of("boop")).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationName));
Mockito.doReturn(true).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantIdentifier), Matchers.eq(applicationName),
AdditionalMatchers.or(Matchers.eq(expectedBeatTimestamp), Matchers.eq(getNextTimeStamp(expectedBeatTimestamp))));

this.testSubject.createBeat(applicationName, beat);

Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationName, beat.getIdentifier())));

Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).requestPermissionForBeats(tenantIdentifier, applicationName);
Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).publishBeat(beatIdentifier, tenantDataStoreContext.getTenantName(), applicationName, expectedBeatTimestamp);

return beat;
@@ -21,13 +21,13 @@
import io.mifos.rhythm.api.v1.events.EventConstants;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;

import static org.mockito.Matchers.*;
import java.util.Optional;

/**
* @author Myrle Krantz
@@ -79,6 +79,7 @@ public void shouldDeleteApplication() throws InterruptedException {

@Test
public void shouldRetryBeatPublishIfFirstAttemptFails() throws InterruptedException {
final String tenantIdentifier = tenantDataStoreContext.getTenantName();
final String appName = "funnybusiness-v4";
final String beatId = "bebopthedowop";

@@ -90,12 +91,13 @@ public void shouldRetryBeatPublishIfFirstAttemptFails() throws InterruptedExcept

final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, beat.getAlignmentHour());

Mockito.when(beatPublisherServiceSpy.publishBeat(beatId, tenantDataStoreContext.getTenantName(), appName, expectedBeatTimestamp)).thenReturn(false, false, true);
Mockito.doReturn(Optional.of("boop")).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(appName));
Mockito.when(beatPublisherServiceSpy.publishBeat(beatId, tenantIdentifier, appName, expectedBeatTimestamp)).thenReturn(false, false, true);

this.testSubject.createBeat(appName, beat);

Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(appName, beat.getIdentifier())));

Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(10_000).times(3)).publishBeat(beatId, tenantDataStoreContext.getTenantName(), appName, expectedBeatTimestamp);
Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(10_000).times(3)).publishBeat(beatId, tenantIdentifier, appName, expectedBeatTimestamp);
}
}
@@ -33,6 +33,7 @@ dependencies {
[group: 'io.mifos.rhythm', name: 'api', version: project.version],
[group: 'io.mifos.rhythm', name: 'spi', version: project.version],
[group: 'io.mifos.anubis', name: 'library', version: versions.frameworkanubis],
[group: 'io.mifos.identity', name: 'api', version: versions.identity],
[group: 'io.mifos.permitted-feign-client', name: 'library', version: versions.frameworkpermittedfeignclient],
[group: 'com.google.code.gson', name: 'gson'],
[group: 'io.mifos.core', name: 'lang', version: versions.frameworklang],
@@ -0,0 +1,32 @@
/*
* 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.rhythm.service;

import org.springframework.boot.SpringApplication;

/**
* @author Myrle Krantz
*/
public class RhythmApplication {

public RhythmApplication() {
super();
}

public static void main(String[] args) {
SpringApplication.run(RhythmConfiguration.class, args);
}
}
@@ -20,14 +20,18 @@
import io.mifos.core.async.config.EnableAsync;
import io.mifos.core.cassandra.config.EnableCassandra;
import io.mifos.core.command.config.EnableCommandProcessing;
import io.mifos.core.lang.config.EnableApplicationName;
import io.mifos.core.lang.config.EnableServiceException;
import io.mifos.core.lang.config.EnableTenantContext;
import io.mifos.core.mariadb.config.EnableMariaDB;
import io.mifos.permittedfeignclient.config.EnablePermissionRequestingFeignClient;
import io.mifos.rhythm.service.internal.identity.ApplicationPermissionRequestCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@@ -52,13 +56,15 @@
@EnableServiceException
@EnableScheduling
@EnableTenantContext
@EnablePermissionRequestingFeignClient
@EnablePermissionRequestingFeignClient(feignClasses = {ApplicationPermissionRequestCreator.class})
@RibbonClient(name = "rhythm-v1")
@EnableApplicationName
@EnableFeignClients(clients = {ApplicationPermissionRequestCreator.class})
@ComponentScan({
"io.mifos.rhythm.service.rest",
"io.mifos.rhythm.service.config",
"io.mifos.rhythm.service.internal.service",
"io.mifos.rhythm.service.internal.repository",
"io.mifos.rhythm.service.internal.scheduler",
"io.mifos.rhythm.service.internal.command.handler"
})
@EnableJpaRepositories({
@@ -19,6 +19,7 @@
import io.mifos.core.command.annotation.CommandHandler;
import io.mifos.rhythm.api.v1.events.EventConstants;
import io.mifos.rhythm.service.internal.command.DeleteApplicationCommand;
import io.mifos.rhythm.service.internal.repository.ApplicationRepository;
import io.mifos.rhythm.service.internal.repository.BeatRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
@@ -29,19 +30,25 @@
@SuppressWarnings("unused")
@Aggregate
public class ApplicationCommandHandler {
private final ApplicationRepository applicationRepository;
private final BeatRepository beatRepository;
private final EventHelper eventHelper;

@Autowired
public ApplicationCommandHandler(final BeatRepository beatRepository, final EventHelper eventHelper) {
public ApplicationCommandHandler(
final ApplicationRepository applicationRepository,
final BeatRepository beatRepository,
final EventHelper eventHelper) {
super();
this.applicationRepository = applicationRepository;
this.beatRepository = beatRepository;
this.eventHelper = eventHelper;
}

@CommandHandler
@Transactional
public void process(final DeleteApplicationCommand deleteApplicationCommand) {
this.applicationRepository.deleteByTenantIdentifierAndApplicationName(deleteApplicationCommand.getTenantIdentifier(), deleteApplicationCommand.getApplicationName());
this.beatRepository.deleteByTenantIdentifierAndApplicationName(deleteApplicationCommand.getTenantIdentifier(), deleteApplicationCommand.getApplicationName());
eventHelper.sendEvent(EventConstants.DELETE_APPLICATION, deleteApplicationCommand.getTenantIdentifier(), deleteApplicationCommand.getApplicationName());
}
@@ -20,12 +20,16 @@
import io.mifos.core.lang.ServiceException;
import io.mifos.rhythm.api.v1.events.BeatEvent;
import io.mifos.rhythm.api.v1.events.EventConstants;
import io.mifos.rhythm.service.ServiceConstants;
import io.mifos.rhythm.service.internal.command.CreateBeatCommand;
import io.mifos.rhythm.service.internal.command.DeleteBeatCommand;
import io.mifos.rhythm.service.internal.mapper.BeatMapper;
import io.mifos.rhythm.service.internal.repository.BeatEntity;
import io.mifos.rhythm.service.internal.repository.BeatRepository;
import io.mifos.rhythm.service.internal.service.IdentityPermittableGroupService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;
@@ -36,20 +40,33 @@
@SuppressWarnings("unused")
@Aggregate
public class BeatCommandHandler {

private final IdentityPermittableGroupService identityPermittableGroupService;
private final BeatRepository beatRepository;
private final EventHelper eventHelper;
private final Logger logger;

@Autowired
public BeatCommandHandler(final BeatRepository beatRepository, final EventHelper eventHelper) {
public BeatCommandHandler(
final IdentityPermittableGroupService identityPermittableGroupService,
final BeatRepository beatRepository,
final EventHelper eventHelper,
@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
super();
this.identityPermittableGroupService = identityPermittableGroupService;
this.beatRepository = beatRepository;
this.eventHelper = eventHelper;
this.logger = logger;
}

@CommandHandler
@Transactional
public void process(final CreateBeatCommand createBeatCommand) {
final boolean applicationHasRequestForAccessPermission = identityPermittableGroupService.checkThatApplicationHasRequestForAccessPermission(
createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationName());
if (!applicationHasRequestForAccessPermission) {
logger.warn("Rhythm needs permission to publish beats to application, but couldn't request that permission for tenant '{}' and application '{}'.",
createBeatCommand.getApplicationName(), createBeatCommand.getTenantIdentifier());
}

final BeatEntity entity = BeatMapper.map(
createBeatCommand.getTenantIdentifier(),
@@ -0,0 +1,44 @@
/*
* 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.rhythm.service.internal.identity;

import io.mifos.anubis.annotation.Permittable;
import io.mifos.core.api.annotation.ThrowsException;
import io.mifos.identity.api.v1.client.ApplicationPermissionAlreadyExistsException;
import io.mifos.identity.api.v1.domain.Permission;
import io.mifos.permittedfeignclient.annotation.EndpointSet;
import io.mifos.permittedfeignclient.annotation.PermittedFeignClientsConfiguration;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
* @author Myrle Krantz
*/
@EndpointSet(identifier = "rhythm__v1__identity__v1")
@FeignClient(name="identity-v1", path="/identity/v1", configuration=PermittedFeignClientsConfiguration.class)
public interface ApplicationPermissionRequestCreator {

@RequestMapping(value = "/applications/{applicationidentifier}/permissions", method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
@ThrowsException(status = HttpStatus.CONFLICT, exception = ApplicationPermissionAlreadyExistsException.class)
@Permittable(groupId = io.mifos.identity.api.v1.PermittableGroupIds.APPLICATION_SELF_MANAGEMENT)
void createApplicationPermission(@PathVariable("applicationidentifier") String applicationIdentifier, Permission permission);
}

0 comments on commit 4cae7c5

Please sign in to comment.