Skip to content

Commit

Permalink
✨ : encrypt state when encryption service exists
Browse files Browse the repository at this point in the history
  • Loading branch information
juwit committed Aug 24, 2020
1 parent e1c28a9 commit f9ad238
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.gaia_app.stacks.controller;

import io.gaia_app.stacks.bo.TerraformState;
import io.gaia_app.stacks.repository.TerraformStateRepository;
import io.gaia_app.stacks.service.StateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
Expand All @@ -12,16 +12,16 @@
@RestController
public class TerraformStateController {

private TerraformStateRepository repository;
private StateService stateService;

@Autowired
public TerraformStateController(TerraformStateRepository repository) {
this.repository = repository;
public TerraformStateController(StateService stateService) {
this.stateService = stateService;
}

@GetMapping("/api/state/{id}")
public Map<String, Object> getState(@PathVariable String id){
return repository.findById(id)
return stateService.findById(id)
.orElseThrow(
() -> new ResponseStatusException(HttpStatus.NOT_FOUND))
.getValue();
Expand All @@ -32,7 +32,7 @@ public void postState(@PathVariable String id, @RequestBody Map<String, Object>
var terraformState = new TerraformState();
terraformState.setId(id);
terraformState.setValue(body);
repository.save(terraformState);
stateService.save(terraformState);
}

}
62 changes: 62 additions & 0 deletions src/main/java/io/gaia_app/stacks/service/StateService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.gaia_app.stacks.service

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.gaia_app.encryption.EncryptionService
import io.gaia_app.stacks.bo.TerraformState
import io.gaia_app.stacks.repository.TerraformStateRepository
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.stereotype.Service
import java.util.*

/**
* Service that manages state
*/
interface StateService {

fun findById(id: String): Optional<TerraformState>

fun save(state: TerraformState): TerraformState
}

/**
* Pass-through implementation to the repository
*/
@Service
@ConditionalOnMissingBean(EncryptionService::class)
class StateServiceImpl(val terraformStateRepository: TerraformStateRepository): StateService {

override fun findById(id: String): Optional<TerraformState> = terraformStateRepository.findById(id)

override fun save(state: TerraformState): TerraformState = terraformStateRepository.save(state)

}

/**
* Implementation that encrypts / decrypts the content
*/
@Service
@ConditionalOnBean(EncryptionService::class)
class EncryptedStateServiceImpl(
val terraformStateRepository: TerraformStateRepository,
val encryptionService: EncryptionService,
val objectMapper: ObjectMapper): StateService {

override fun findById(id: String): Optional<TerraformState> {
val state = this.terraformStateRepository.findById(id)
return state.map {
val decrypted = encryptionService.decrypt(it.value["encrypted"] as String);
it.value = objectMapper.readValue(decrypted)
it
}
}

override fun save(state: TerraformState): TerraformState {
val stateString = objectMapper.writeValueAsString(state.value)
val encrypted = encryptionService.encrypt(stateString)
state.value = mapOf("encrypted" to encrypted)
return terraformStateRepository.save(state)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package io.gaia_app.stacks.service

import com.fasterxml.jackson.databind.ObjectMapper
import io.gaia_app.encryption.EncryptionService
import io.gaia_app.stacks.bo.TerraformState
import io.gaia_app.stacks.repository.TerraformStateRepository
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Spy
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.stubbing.Answer
import java.util.*

@ExtendWith(MockitoExtension::class)
internal class EncryptedStateServiceImplTest {

@Mock
lateinit var terraformStateRepository: TerraformStateRepository

@Mock
lateinit var encryptionService: EncryptionService

@Spy
var objectMapper = ObjectMapper()

@InjectMocks
lateinit var stateServiceImpl: EncryptedStateServiceImpl

@Test
fun `findById() should decrypt state`() {
// given
val encryptedState = TerraformState();
encryptedState.value = mapOf("encrypted" to "encryptedMapValue")

`when`(encryptionService.decrypt("encryptedMapValue"))
.thenReturn("""{
"state": "content",
"nestedContent": {
"key": "value",
"integer": 1,
"boolean": true
}
}""".trimMargin())
`when`(terraformStateRepository.findById("12")).thenReturn(Optional.of(encryptedState))

// when
val decryptedState = stateServiceImpl.findById("12")

// then
assertThat(decryptedState).isNotEmpty
assertThat(decryptedState.get().value).containsEntry("state","content")
assertThat(decryptedState.get().value["nestedContent"] as Map<String, Any?>).containsEntry("key", "value")
}

@Test
fun `save() should encrypt state`() {
// given
val plainState = TerraformState();
plainState.value = mapOf(
"state" to "content",
"nestedContent" to mapOf(
"key" to "value",
"integer" to 1,
"boolean" to true
)
)
val valueAsString = objectMapper.writeValueAsString(plainState.value)

`when`(encryptionService.encrypt(valueAsString)).thenReturn("encryptedMapValue")

`when`(terraformStateRepository.save(any(TerraformState::class.java))).thenAnswer(firstArg())

// when
val encryptedState = stateServiceImpl.save(plainState)

// then
assertThat(encryptedState.value).isEqualTo(mapOf("encrypted" to "encryptedMapValue"))
}

}

/**
* Mockito answer which returns the first argument of the mock invocation.
*/
fun firstArg(): Answer<*>? = Answer { it.arguments[0] }
41 changes: 41 additions & 0 deletions src/test/java/io/gaia_app/stacks/service/StateServiceImplTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.gaia_app.stacks.service

import io.gaia_app.stacks.bo.TerraformState
import io.gaia_app.stacks.repository.TerraformStateRepository
import io.gaia_app.test.any
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.runner.RunWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.junit.jupiter.MockitoExtension

@ExtendWith(MockitoExtension::class)
internal class StateServiceImplTest {

@Mock
lateinit var terraformStateRepository: TerraformStateRepository

@InjectMocks
lateinit var stateServiceImpl: StateServiceImpl

@Test
fun `findById() should call the repository`() {
stateServiceImpl.findById("12")

verify(terraformStateRepository).findById("12")
}

@Test
fun `save() should call the repository`() {
val state = TerraformState()
`when`(terraformStateRepository.save(state)).thenReturn(state)

stateServiceImpl.save(state)

verify(terraformStateRepository).save(state)
}
}

0 comments on commit f9ad238

Please sign in to comment.