Skip to content

Commit

Permalink
WIP encrypt PrimaryKeys
Browse files Browse the repository at this point in the history
- add rest.jersey.server-module to inject encrypted reader/writer
- add IIdCodecFlag to parametrize IdCodec-calls
- add idEncryption to ScoutDataObjectModuleContext and pass context to
  all IId-serializers/deserializers
- add IIdEncryptionDataObjectMapper/JacksonIdEncryptionDataObjectMapper
  that uses the ScoutDataObjectModuleContexts idEncryption flag
- use IIdEncryptionDataObjectMapper in Json layer

340299
  • Loading branch information
fschinkel committed Mar 11, 2024
1 parent 5197f47 commit 125ead0
Show file tree
Hide file tree
Showing 37 changed files with 1,272 additions and 150 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -12,8 +12,10 @@
import static org.junit.Assert.*;

import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

import org.eclipse.scout.rt.dataobject.fixture.FixtureCompositeId;
Expand All @@ -22,6 +24,7 @@
import org.eclipse.scout.rt.dataobject.fixture.FixtureStringId;
import org.eclipse.scout.rt.dataobject.fixture.FixtureUuId;
import org.eclipse.scout.rt.dataobject.fixture.FixtureWrapperCompositeId;
import org.eclipse.scout.rt.dataobject.id.IdCodec.IdCodecFlag;
import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.util.Assertions.AssertionException;
Expand Down Expand Up @@ -513,19 +516,19 @@ public void testFromQualified_UnsupportedWrappedType() {
@Test
public void testFromQualifiedLenient_Default() {
FixtureUuId id = FixtureUuId.of(TEST_UUID);
IId id2 = getCodec().fromQualifiedLenient("scout.FixtureUuId:" + TEST_UUID);
IId id2 = getCodec().fromQualified("scout.FixtureUuId:" + TEST_UUID, IdCodecFlag.LENIENT);
assertEquals(id, id2);
}

@Test
public void testFromQualifiedLenient_UnknownType() {
IId id = getCodec().fromQualifiedLenient("DoesNotExist:" + TEST_UUID);
IId id = getCodec().fromQualified("DoesNotExist:" + TEST_UUID, IdCodecFlag.LENIENT);
assertNull(id);
}

@Test
public void testFromQualifiedLenient_WrongFormat() {
IId id = getCodec().fromQualifiedLenient("Does:Not:Exist:" + TEST_UUID);
IId id = getCodec().fromQualified("Does:Not:Exist:" + TEST_UUID, IdCodecFlag.LENIENT);
assertNull(id);
}

Expand All @@ -536,6 +539,58 @@ public void testRegisterTypeMapper() {
assertThrows(AssertionException.class, () -> getCodec().registerRawTypeMapper(String.class, x -> x, null));
}

@Test
public void testQualifiedEncryption() {
Set<IId> ids = new HashSet<>();
collectEncryptionIds(ids);
ids.forEach(id -> {
String encrypted = getCodec().toQualified(id, IdCodecFlag.ENCRYPTION);
IId decrypted = getCodec().fromQualified(encrypted, IdCodecFlag.ENCRYPTION);
assertQualifiedEncryption(id, encrypted, decrypted);
});
}

@Test
public void testUnqualifiedEncryption() {
Set<IId> ids = new HashSet<>();
collectEncryptionIds(ids);
ids.forEach(id -> {
String encrypted = getCodec().toUnqualified(id, IdCodecFlag.ENCRYPTION);
IId decrypted = getCodec().fromUnqualified(id.getClass(), encrypted, IdCodecFlag.ENCRYPTION);
assertUnqualifiedEncryption(id, encrypted, decrypted);
});
}

protected void assertQualifiedEncryption(IId id, String encrypted, IId decrypted) {
assertEncryption(id, encrypted, decrypted);
}

protected void assertUnqualifiedEncryption(IId id, String encrypted, IId decrypted) {
assertEncryption(id, encrypted, decrypted);
}

protected void assertEncryption(IId id, String encrypted, IId decrypted) {
assertEquals(id, decrypted);
}

protected void collectEncryptionIds(Set<IId> ids) {
ids.add(FixtureUuId.of(TEST_UUID));
ids.add(FixtureStringId.of(TEST_STRING));
ids.add(FixtureDateId.of(TEST_DATE));
ids.add(FixtureLocaleId.of(Locale.ITALY));
ids.add(FixtureCompositeId.of(TEST_STRING, TEST_UUID));
ids.add(FixtureWrapperCompositeId.of(TEST_STRING, TEST_UUID, TEST_STRING_2));
ids.add(FixtureCompositeWithNullValuesId.of(null, UUID.fromString("711dc5d6-0a42-4f54-b79c-50110b9e742a")));
ids.add(FixtureCompositeWithNullStringValuesId.of("foo", ""));
ids.add(FixtureCompositeWithNullStringValuesId.of("", "bar"));
ids.add(FixtureCompositeWithAllTypesId.of("foo", null, null, null, null, null));
ids.add(FixtureCompositeWithAllTypesId.of(null, TEST_UUID, null, null, null, null));
ids.add(FixtureCompositeWithAllTypesId.of(null, null, 42L, null, null, null));
ids.add(FixtureCompositeWithAllTypesId.of(null, null, null, 43, null, null));
ids.add(FixtureCompositeWithAllTypesId.of(null, null, null, null, TEST_DATE, null));
ids.add(FixtureCompositeWithAllTypesId.of(null, null, null, null, null, Locale.GERMANY));
}

@IdTypeName("scout.FixtureDateId")
protected static final class FixtureDateId extends AbstractRootId<Date> {
private static final long serialVersionUID = 1L;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -20,6 +20,7 @@
import org.eclipse.scout.rt.dataobject.id.IIds;
import org.eclipse.scout.rt.dataobject.id.IRootId;
import org.eclipse.scout.rt.dataobject.id.IdCodec;
import org.eclipse.scout.rt.dataobject.id.IdCodec.IIdCodecFlag;
import org.eclipse.scout.rt.platform.exception.PlatformException;

/**
Expand Down Expand Up @@ -50,7 +51,7 @@ public ICompositeId replaceOrVisit(ICompositeId value, UnaryOperator<Object> cha
}

/**
* Similar as in {@link IdCodec#toUnqualified(IId)}.
* Similar as in {@link IdCodec#toUnqualified(IId, IIdCodecFlag...)}.
*/
protected void unwrap(IId component, List<Object> unwrappedComponents) {
if (component instanceof IRootId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.scout.rt.dataobject;

import org.eclipse.scout.rt.dataobject.id.IId;

/**
* Interface to a data mapper that uses encryption/decryption for all {@link IId} instances.
*
* @see IDataObjectMapper
*/
public interface IIdEncryptionDataObjectMapper extends IDataObjectMapper {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -10,24 +10,27 @@
package org.eclipse.scout.rt.dataobject.id;

import static org.eclipse.scout.rt.platform.util.Assertions.assertNotNull;
import static org.eclipse.scout.rt.platform.util.CollectionUtility.hashSet;
import static org.eclipse.scout.rt.platform.util.ObjectUtility.isOneOf;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

import jakarta.annotation.PostConstruct;

import org.eclipse.scout.rt.platform.ApplicationScoped;
import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.util.LazyValue;
import org.eclipse.scout.rt.platform.util.StringUtility;

import jakarta.annotation.PostConstruct;

/**
* Codec used to convert between {@link IId} instances and their qualified/unqualified representation as {@link String}.
*/
Expand All @@ -40,6 +43,18 @@ public class IdCodec {
protected final Map<Class<?>, Function<String, Object>> m_rawTypeFromStringMapper = new HashMap<>();
protected final Map<Class<?>, Function<Object, String>> m_rawTypeToStringMapper = new HashMap<>();

public interface IIdCodecFlag {
}

public enum IdCodecFlag implements IIdCodecFlag {
/**
* Does not throw an exception but return {@code null} if the given string does not match the expected format or the
* referenced class is not found.
*/
LENIENT,
ENCRYPTION
}

@PostConstruct
protected void initialize() {
// setup default type mappings between raw type <--> string
Expand All @@ -62,15 +77,22 @@ protected void initialize() {
* converted to their string representation, separated by ';'.
* </ul>
*/
public String toQualified(IId id) {
public String toQualified(IId id, IIdCodecFlag... flags) {
return toQualified(id, hashSet(flags));
}

/**
* @see #toQualified(IId, IIdCodecFlag...)
*/
public String toQualified(IId id, Set<IIdCodecFlag> flags) {
if (id == null) {
return null;
}
String typeName = m_idInventory.get().getTypeName(id);
if (StringUtility.isNullOrEmpty(typeName)) {
throw new PlatformException("Missing @{} in class {}", IdTypeName.class.getSimpleName(), id.getClass());
}
return typeName + ":" + toUnqualified(id);
return typeName + ":" + toUnqualified(id, flags);
}

/**
Expand All @@ -81,7 +103,14 @@ public String toQualified(IId id) {
* converted to their string representation, separated by ';'.
* </ul>
*/
public String toUnqualified(IId id) {
public String toUnqualified(IId id, IIdCodecFlag... flags) {
return toUnqualified(id, hashSet(flags));
}

/**
* @see #toUnqualified(IId, IIdCodecFlag...)
*/
public String toUnqualified(IId id, Set<IIdCodecFlag> flags) {
if (id == null) {
return null;
}
Expand All @@ -96,11 +125,11 @@ public String toUnqualified(IId id) {
else if (id instanceof ICompositeId) {
List<? extends IId> components = ((ICompositeId) id).unwrap();
return components.stream()
.map(this::toUnqualified)
.map(comp -> toUnqualified(comp, flags))
.map(s -> s == null ? "" : s) // empty string if component is null just in case of composite id
.collect(Collectors.joining(";"));
}
return handleToUnqualifiedUnknownIdType(id);
return handleToUnqualifiedUnknownIdType(id, flags);
}

// ---------------- String to IId ----------------
Expand All @@ -112,18 +141,15 @@ else if (id instanceof ICompositeId) {
* @throws PlatformException
* if the given string does not match the expected format or the referenced class is not found.
*/
public IId fromQualified(String qualifiedId) {
return fromQualifiedInternal(qualifiedId, false);
public IId fromQualified(String qualifiedId, IIdCodecFlag... flags) {
return fromQualified(qualifiedId, hashSet(flags));
}

/**
* Parses a string in the format {@code [type-name]:[raw-id;raw-id;...]}.
*
* @return {@code IId} parsed from {@code qualifiedId} or {@code null} if the given string does not match the expected
* format or the referenced class is not found.
* @see #fromQualified(String, IIdCodecFlag...)
*/
public IId fromQualifiedLenient(String qualifiedId) {
return fromQualifiedInternal(qualifiedId, true);
public IId fromQualified(String qualifiedId, Set<IIdCodecFlag> flags) {
return fromQualifiedInternal(qualifiedId, flags);
}

/**
Expand All @@ -133,14 +159,21 @@ public IId fromQualifiedLenient(String qualifiedId) {
* @throws PlatformException
* if the given string does not match the expected format
*/
public <ID extends IId> ID fromUnqualified(Class<ID> idClass, String unqualifiedId) {
public <ID extends IId> ID fromUnqualified(Class<ID> idClass, String unqualifiedId, IIdCodecFlag... flags) {
return fromUnqualified(idClass, unqualifiedId, hashSet(flags));
}

/**
* @see #fromUnqualified(Class, String, IIdCodecFlag...)
*/
public <ID extends IId> ID fromUnqualified(Class<ID> idClass, String unqualifiedId, Set<IIdCodecFlag> flags) {
if (idClass == null) {
throw new PlatformException("Missing id class to parse unqualified id {}", unqualifiedId);
}
if (StringUtility.isNullOrEmpty(unqualifiedId)) {
return null;
}
return fromUnqualifiedUnchecked(idClass, unqualifiedId);
return fromUnqualifiedUnchecked(idClass, unqualifiedId, flags);
}

/**
Expand Down Expand Up @@ -177,23 +210,23 @@ public void unregisterRawTypeMapper(Class<?> rawType) {
/**
* Callback method to implement if the codec should be extended to handle qualification of unknown {@link IId} types.
*/
protected String handleToUnqualifiedUnknownIdType(IId id) {
protected String handleToUnqualifiedUnknownIdType(IId id, Set<IIdCodecFlag> flags) {
throw new PlatformException("Unsupported id type {}, cannot convert id {}", id.getClass(), id);
}

/**
* Parses a string in the format {@code [type-name]:[raw-id;raw-id;...]}.
*
* @param lenient
* If the structure of the given {@code qualifiedId} is invalid and {@code lenient} flag is set to
* {@code true}, value {@code null} is returned. If {@code lenient} flag is set to {@code false}, an
* exception is thrown.
* @param flags
* If the structure of the given {@code qualifiedId} is invalid and {@code IdCodecFlag.LENIENT} flag is set,
* value {@code null} is returned. If {@code IdCodecFlag.LENIENT} flag is not set, an exception is thrown.
* @return {@code IId} parsed from {@code qualifiedId}
*/
protected IId fromQualifiedInternal(String qualifiedId, boolean lenient) {
protected IId fromQualifiedInternal(String qualifiedId, Set<IIdCodecFlag> flags) {
if (StringUtility.isNullOrEmpty(qualifiedId)) {
return null;
}
boolean lenient = isOneOf(IdCodecFlag.LENIENT, flags);
String[] tmp = qualifiedId.split(":", 2); // split into at most two parts
if (tmp.length < 2) { // no ":" found
if (lenient) {
Expand All @@ -213,7 +246,7 @@ protected IId fromQualifiedInternal(String qualifiedId, boolean lenient) {
throw new PlatformException("No class found for type name '{}'", typeName);
}
}
return fromUnqualified(idClass, tmp[1]);
return fromUnqualified(idClass, tmp[1], flags);
}

/**
Expand All @@ -224,16 +257,16 @@ protected IId fromQualifiedInternal(String qualifiedId, boolean lenient) {
* @throws PlatformException
* if the given string does not match the expected format
*/
protected <ID extends IId> ID fromUnqualifiedUnchecked(Class<ID> idClass, String unqualifiedId) {
protected <ID extends IId> ID fromUnqualifiedUnchecked(Class<ID> idClass, String unqualifiedId, Set<IIdCodecFlag> flags) {
String[] rawComponents = unqualifiedId.split(";", -1 /* force empty strings for empty components */);
Object[] components = parseComponents(idClass, rawComponents);
Object[] components = parseComponents(idClass, rawComponents, flags);
return m_idFactory.get().createInternal(idClass, components);
}

/**
* Parses given {@code rawComponents} based on the declared component types of given {@code idClass}.
*/
protected Object[] parseComponents(Class<? extends IId> idClass, String[] rawComponents) {
protected Object[] parseComponents(Class<? extends IId> idClass, String[] rawComponents, Set<IIdCodecFlag> flags) {
List<Class<?>> componentTypes = m_idFactory.get().getRawTypes(idClass);
if (!(componentTypes.size() == rawComponents.length)) {
throw new PlatformException("Wrong argument size, expected {} parameter, got {} raw components {}, idType={}", componentTypes.size(), rawComponents.length, Arrays.toString(rawComponents), idClass.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -17,12 +17,14 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.eclipse.scout.rt.dataobject.id.IdCodec.IIdCodecFlag;

/**
* Annotation used to define the unique type name for an {@link IId} class, used when serializing or deserializing
* instances.
*
* @see IdCodec#toQualified(IId)
* @see IdCodec#fromQualified(String)
* @see IdCodec#toQualified(IId, IIdCodecFlag...)
* @see IdCodec#fromQualified(String, IIdCodecFlag...)
* @see TypedId
*/
@Documented
Expand Down

0 comments on commit 125ead0

Please sign in to comment.