Skip to content

Commit

Permalink
Add new function that combines both mapping and encryption. (#19)
Browse files Browse the repository at this point in the history
Add MapAndEncryptFunc which combines both mapping and encryption
  • Loading branch information
bjornandre authored Mar 13, 2024
1 parent d987381 commit e8883e8
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public abstract class AbstractPseudoFunc implements PseudoFunc {
private final String funcDecl;

@Override
public void init(PseudoFuncInput input) {
public void init(PseudoFuncInput input, TransformDirection direction) {
// Do nothing, can be overridden by subclasses
}
}
4 changes: 2 additions & 2 deletions src/main/java/no/ssb/dapla/dlp/pseudo/func/PseudoFunc.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ public interface PseudoFunc {
String getAlgorithm();

/**
* Preprocessing of input. This will be called before apply
* Preprocessing of input. This will be called before apply or restore
*/
void init(PseudoFuncInput input);
void init(PseudoFuncInput input, TransformDirection direction);
/**
* Pseudonymize
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package no.ssb.dapla.dlp.pseudo.func;

public enum TransformDirection {
APPLY, RESTORE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package no.ssb.dapla.dlp.pseudo.func.composite;

import no.ssb.dapla.dlp.pseudo.func.AbstractPseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import no.ssb.dapla.dlp.pseudo.func.TransformDirection;

import java.util.function.Function;

import static no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFuncConfig.Param.*;

/**
* This is a composite {@code PseudoFunc} that combines two {@code PseudoFunc}s into a single operation.
* <p>
* It assumes that one is a mapping function (e.g. MapFunc) and the other is an encryption function
* (e.g FpeFunc, TinkFpeFunc, TinkDaeadFunc).
*
* The {@code MapAndEncryptFunc} must be configured with a
* {@link no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFuncConfig.Param#ENCRYPTION_FUNC_IMPL} and a
* {@link no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFuncConfig.Param#MAP_FUNC_IMPL}.
*
* The {@code PseudoFuncConfig} must also contain all the necessary configs for each of the underlying
* {@code PseudoFunc}s. For example MapFuncConfig and TinkFpeFuncConfig.
*/
public class MapAndEncryptFunc extends AbstractPseudoFunc {

final PseudoFunc encryptionFunc;
final PseudoFunc mapFunc;

public MapAndEncryptFunc(PseudoFuncConfig genericConfig) {
super(genericConfig.getFuncDecl());
genericConfig.add(PseudoFuncConfig.Param.FUNC_IMPL,
genericConfig.getRequired(ENCRYPTION_FUNC_IMPL, String.class));
var encryptionFuncConfig = genericConfig.asMap();
genericConfig.add(PseudoFuncConfig.Param.FUNC_IMPL,
genericConfig.getRequired(MAP_FUNC_IMPL, String.class));
var mapFuncConfig = genericConfig.asMap();

this.encryptionFunc = PseudoFuncFactory.create(new PseudoFuncConfig(encryptionFuncConfig));
this.mapFunc = PseudoFuncFactory.create(new PseudoFuncConfig(mapFuncConfig));
}

@Override
public String getAlgorithm() {
return encryptionFunc.getAlgorithm();
}

@Override
public void init(PseudoFuncInput input, TransformDirection direction) {
if (direction == TransformDirection.APPLY) {
// Prepare map from original value to mapped value
mapFunc.init(input, direction);
} else {
// Decrypt and then prepare to map back to original value
mapFunc.init(PseudoFuncInput.of(
encryptionFunc.restore(input).getValue()),
direction
);
}
}

@Override
public PseudoFuncOutput apply(PseudoFuncInput input) {
// Map original value to mapped value and then encrypt
return transform(input, mapFunc::apply, encryptionFunc::apply);
}
@Override
public PseudoFuncOutput restore(PseudoFuncInput input) {
// Decrypt and then map back to original value
return transform(input, encryptionFunc::restore, mapFunc::restore);
}

/**
* Apply both functions {@code inner} and {@code outer} and merge both
* outputs.
*
* @param input the original value
* @param inner the inner function to apply
* @param outer the outer function to apply
* @return the result object
*/
private PseudoFuncOutput transform(PseudoFuncInput input,
Function<PseudoFuncInput, PseudoFuncOutput> inner,
Function<PseudoFuncInput, PseudoFuncOutput> outer) {
final PseudoFuncOutput innerOutput = inner.apply(input);
final PseudoFuncOutput outerOutput = outer.apply(
PseudoFuncInput.of(innerOutput.getValue()));
innerOutput.getWarnings().forEach(outerOutput::addWarning);
innerOutput.getMetadata().forEach(outerOutput::addMetadata);
return outerOutput;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package no.ssb.dapla.dlp.pseudo.func.composite;

import lombok.Builder;
import lombok.Value;
import lombok.experimental.UtilityClass;

@Value
@Builder
public class MapAndEncryptFuncConfig {
String encryptionFunc;
String mapFunc;

@UtilityClass
public static class Param {
public static final String ENCRYPTION_FUNC_IMPL = "encryptionFunc";
public static final String MAP_FUNC_IMPL = "mapFunc";
}
}
4 changes: 3 additions & 1 deletion src/main/java/no/ssb/dapla/dlp/pseudo/func/map/MapFunc.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import no.ssb.dapla.dlp.pseudo.func.TransformDirection;

import java.util.ServiceLoader;

Expand Down Expand Up @@ -32,7 +33,8 @@ public static Mapper loadMapper() {
}

@Override
public void init(PseudoFuncInput input) {
public void init(PseudoFuncInput input, TransformDirection direction) {
// Init method is the same regardless of the direction
mapper.init(input);
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/no/ssb/dapla/dlp/pseudo/func/map/Mapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public interface Mapper {

void init(PseudoFuncInput data);
void setConfig(Map<String, Object> config);
PseudoFuncOutput map(PseudoFuncInput data);
PseudoFuncOutput map(PseudoFuncInput data) throws MappingNotFoundException;

PseudoFuncOutput restore(PseudoFuncInput mapped);
PseudoFuncOutput restore(PseudoFuncInput mapped) throws MappingNotFoundException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package no.ssb.dapla.dlp.pseudo.func.map;

public class MappingNotFoundException extends RuntimeException {
public MappingNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package no.ssb.dapla.dlp.pseudo.func.composite;

import com.google.common.collect.ImmutableMap;
import no.ssb.dapla.dlp.pseudo.func.PseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import no.ssb.dapla.dlp.pseudo.func.fpe.FpeFunc;
import no.ssb.dapla.dlp.pseudo.func.fpe.FpeFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.map.MapFunc;
import no.ssb.dapla.dlp.pseudo.func.map.MappingNotFoundException;
import no.ssb.dapla.dlp.pseudo.func.map.TestMapper;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

public class MapAndEncryptFuncTest {
private static final String BASE64_ENCODED_KEY = "w0/G6A5e/KHtTo31FD6mhhS1Tkga43l79IBK24gM4F8=";

final PseudoFuncConfig config = new PseudoFuncConfig(ImmutableMap.of(
PseudoFuncConfig.Param.FUNC_DECL, "map-fpe-test",
PseudoFuncConfig.Param.FUNC_IMPL, MapAndEncryptFunc.class.getName(),
MapAndEncryptFuncConfig.Param.MAP_FUNC_IMPL, MapFunc.class.getName(),
MapAndEncryptFuncConfig.Param.ENCRYPTION_FUNC_IMPL, FpeFunc.class.getName(),
FpeFuncConfig.Param.ALPHABET, "alphanumeric+whitespace",
FpeFuncConfig.Param.KEY_ID, "keyId1",
FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY

));
/*
* This test should transform OriginalValue -> MappedValue -> FPE encrypted MappedValue
* (and back)
*/
@Test
public void transformAndRestore() {
String expectedVal = "ygd1M9at1nK"; // FPE encrypted MappedValue
PseudoFunc func = PseudoFuncFactory.create(config);

PseudoFuncOutput pseudonymized = func.apply(PseudoFuncInput.of(TestMapper.ORIGINAL));
assertThat(pseudonymized.getValue()).isEqualTo(expectedVal);

PseudoFuncOutput depseudonymized = func.restore(PseudoFuncInput.of(pseudonymized.getValue()));
assertThat(depseudonymized.getValue()).isEqualTo(TestMapper.ORIGINAL);
}

@Test
public void call_apply_with_mapping_failure() {
PseudoFunc func = PseudoFuncFactory.create(config);

assertThrows(MappingNotFoundException.class,() ->
func.apply(PseudoFuncInput.of("unknown"))
);
}

@Test
public void call_restore_with_mapping_failure() {
PseudoFunc func = PseudoFuncFactory.create(config);

assertThrows(MappingNotFoundException.class,() ->
func.restore(PseudoFuncInput.of("unknown"))
);
}

}
29 changes: 29 additions & 0 deletions src/test/java/no/ssb/dapla/dlp/pseudo/func/map/MapFuncTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package no.ssb.dapla.dlp.pseudo.func.map;

import com.google.common.collect.ImmutableMap;
import no.ssb.dapla.dlp.pseudo.func.PseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

public class MapFuncTest {
@Test
public void transformAndRestore() {
final PseudoFuncConfig config = new PseudoFuncConfig(ImmutableMap.of(
PseudoFuncConfig.Param.FUNC_DECL, "map-test",
PseudoFuncConfig.Param.FUNC_IMPL, MapFunc.class.getName()
));
PseudoFunc func = PseudoFuncFactory.create(config);

PseudoFuncOutput mapOutput = func.apply(PseudoFuncInput.of(TestMapper.ORIGINAL));
assertThat(mapOutput.getValue()).isEqualTo(TestMapper.MAPPED);

PseudoFuncOutput depseudonymized = func.restore(PseudoFuncInput.of(mapOutput.getValue()));
assertThat(depseudonymized.getValue()).isEqualTo(TestMapper.ORIGINAL);
}

}
41 changes: 41 additions & 0 deletions src/test/java/no/ssb/dapla/dlp/pseudo/func/map/TestMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package no.ssb.dapla.dlp.pseudo.func.map;

import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;

import java.util.Map;

/**
* This class is loaded using the Java Service Provider API.
*/
public class TestMapper implements Mapper {

public static String ORIGINAL = "OriginalValue";
public static String MAPPED = "MappedValue";

@Override
public void init(PseudoFuncInput data) {
}

@Override
public void setConfig(Map<String, Object> config) {
}

@Override
public PseudoFuncOutput map(PseudoFuncInput input) throws MappingNotFoundException {
if (ORIGINAL.equals(input.value())) {
return PseudoFuncOutput.of(MAPPED);
} else {
throw new MappingNotFoundException(String.format("Could not map value %s", input.value()));
}
}

@Override
public PseudoFuncOutput restore(PseudoFuncInput mapped) throws MappingNotFoundException {
if (MAPPED.equals(mapped.value())) {
return PseudoFuncOutput.of(ORIGINAL);
} else {
throw new MappingNotFoundException(String.format("Could not map value %s", mapped.value()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
no.ssb.dapla.dlp.pseudo.func.map.TestMapper

0 comments on commit e8883e8

Please sign in to comment.