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 Jan 9, 2024
1 parent 858cd02 commit 651c3bc
Show file tree
Hide file tree
Showing 41 changed files with 1,132 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 +514,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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package org.eclipse.scout.rt.dataobject;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
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, Collection)}.
*/
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,18 @@
/*
* Copyright (c) 2010, 2023 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;

/**
* Interface to a data mapper that uses id encryption.
*
* @see IDataObjectMapper
*/
public interface IIdEncryptionDataObjectMapper extends IDataObjectMapper {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
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.arrayList;
import static org.eclipse.scout.rt.platform.util.ObjectUtility.isOneOf;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
Expand All @@ -21,13 +24,13 @@
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,14 @@ 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 {
LENIENT,
ENCRYPTION
}

@PostConstruct
protected void initialize() {
// setup default type mappings between raw type <--> string
Expand All @@ -53,6 +64,13 @@ protected void initialize() {

// ---------------- IId to String ----------------

/**
* @see #toQualified(IId, Collection)
*/
public String toQualified(IId id, IIdCodecFlag... flags) {
return toQualified(id, arrayList(flags));
}

/**
* Returns a string in the format <code>"[type-name]:[raw-id;raw-id;...]"</code>.
* <ul>
Expand All @@ -62,15 +80,22 @@ protected void initialize() {
* converted to their string representation, separated by ';'.
* </ul>
*/
public String toQualified(IId id) {
public String toQualified(IId id, Collection<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);
}

/**
* @see #toUnqualified(IId, Collection)
*/
public String toUnqualified(IId id, IIdCodecFlag... flags) {
return toUnqualified(id, arrayList(flags));
}

/**
Expand All @@ -81,7 +106,7 @@ public String toQualified(IId id) {
* converted to their string representation, separated by ';'.
* </ul>
*/
public String toUnqualified(IId id) {
public String toUnqualified(IId id, Collection<IIdCodecFlag> flags) {
if (id == null) {
return null;
}
Expand All @@ -96,34 +121,38 @@ 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 ----------------

/**
* @see #fromQualified(String, Collection)
*/
public IId fromQualified(String qualifiedId, IIdCodecFlag... flags) {
return fromQualified(qualifiedId, arrayList(flags));
}

/**
* Parses a string in the format {@code [type-name]:[raw-id;raw-id;...]}.
*
* @return {@code IId} parsed from {@code qualifiedId}
* @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, Collection<IIdCodecFlag> flags) {
return fromQualifiedInternal(qualifiedId, 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 #fromUnqualified(Class, String, Collection)
*/
public IId fromQualifiedLenient(String qualifiedId) {
return fromQualifiedInternal(qualifiedId, true);
public <ID extends IId> ID fromUnqualified(Class<ID> idClass, String unqualifiedId, IIdCodecFlag... flags) {
return fromUnqualified(idClass, unqualifiedId, arrayList(flags));
}

/**
Expand All @@ -133,14 +162,14 @@ 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, Collection<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 +206,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, Collection<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, Collection<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 +242,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 +253,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, Collection<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, Collection<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
Expand Up @@ -9,6 +9,7 @@
*/
package org.eclipse.scout.rt.dataobject.id;

import org.eclipse.scout.rt.dataobject.id.IdCodec.IdCodecFlag;
import org.eclipse.scout.rt.platform.ApplicationScoped;
import org.eclipse.scout.rt.platform.exception.ProcessingException;
import org.eclipse.scout.rt.platform.util.LazyValue;
Expand Down Expand Up @@ -54,7 +55,7 @@ public IId fromExternalForm(String externalForm) {
* format or there is no type {@code null} is returned.
*/
public IId fromExternalFormLenient(String externalForm) {
return m_codec.get().fromQualifiedLenient(externalForm);
return m_codec.get().fromQualified(externalForm, IdCodecFlag.LENIENT);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Collection;

/**
* 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, Collection)
* @see IdCodec#fromQualified(String, Collection)
* @see TypedId
*/
@Documented
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ protected static class QualifiedIIdSerializationTest_DataObjectSerializerProvide
@Override
public JsonSerializer<?> findSerializer(ScoutDataObjectModuleContext moduleContext, JavaType type, SerializationConfig config, BeanDescription beanDesc) {
if (type.hasRawClass(FixtureStringId.class) || type.hasRawClass(FixtureUuId.class) || type.hasRawClass(FixtureCompositeId.class)) {
return new QualifiedIIdSerializer();
return new QualifiedIIdSerializer(moduleContext);
}

return null;
Expand All @@ -126,15 +126,15 @@ public JsonSerializer<?> findSerializer(ScoutDataObjectModuleContext moduleConte
@Override
public JsonDeserializer<?> findDeserializer(ScoutDataObjectModuleContext moduleContext, JavaType type, DeserializationConfig config, BeanDescription beanDesc) {
if (type.hasRawClass(FixtureStringId.class) || type.hasRawClass(FixtureUuId.class) || type.hasRawClass(FixtureCompositeId.class) || type.hasRawClass(IId.class)) {
return new QualifiedIIdDeserializer(type.getRawClass().asSubclass(IId.class));
return new QualifiedIIdDeserializer(moduleContext, type.getRawClass().asSubclass(IId.class));
}
return null;
}

@Override
public JsonSerializer<?> findKeySerializer(ScoutDataObjectModuleContext moduleContext, JavaType type, SerializationConfig config, BeanDescription beanDesc) {
if (type.hasRawClass(FixtureStringId.class) || type.hasRawClass(FixtureUuId.class) || type.hasRawClass(FixtureCompositeId.class)) {
return new QualifiedIIdMapKeySerializer();
return new QualifiedIIdMapKeySerializer(moduleContext);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2010, 2023 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.jackson.dataobject;

import org.eclipse.scout.rt.dataobject.IDataObjectMapper;
import org.eclipse.scout.rt.dataobject.IIdEncryptionDataObjectMapper;
import org.eclipse.scout.rt.platform.IBean;
import org.eclipse.scout.rt.platform.Order;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
* {@link IDataObjectMapper} implementation based on jackson {@link ObjectMapper} with id encrypted
* serialization/deserialization.
*/
@Order(IBean.DEFAULT_BEAN_ORDER + 100)
public class JacksonIdEncryptionDataObjectMapper extends JacksonDataObjectMapper implements IIdEncryptionDataObjectMapper {

@Override
protected void prepareScoutDataModuleContext(ScoutDataObjectModuleContext moduleContext) {
super.prepareScoutDataModuleContext(moduleContext);
moduleContext.withIdEncryption(true);
}
}

0 comments on commit 651c3bc

Please sign in to comment.