IhawuModule

class IhawuModule(resolver: ResourcePolicyResolver, failureSink: MaskingFailureSink = Slf4jMaskingFailureSink(), resolverErrorMode: ResolverErrorMode = ResolverErrorMode.MASK_ALL) : SimpleModule

Jackson module that activates Ihawu masking on an ObjectMapper.

Registering this module installs a serializer modifier that intercepts every org.ihawu.core.annotation.IhawuResource-annotated type and applies the org.ihawu.core.policy.FieldPolicy list resolved by resolver for the call's org.ihawu.core.policy.IhawuPrincipal. Types without the annotation serialize unchanged.

Ihawu stays a Policy Enforcement Point: it enforces the decisions resolver returns and never evaluates policy conditions itself. The masking decisions run through a serialization-neutral DefaultMaskingEngine; this module is the Jackson backend that executes them and logs fail-closed drops via SLF4J.

Samples

// HIDE omits a field, REDACT replaces its value (a placeholder for String fields, JSON null for
// other types), unlisted fields pass through, and nested @IhawuResource objects are masked by
// their own policy. Masked fields are declared nullable so the output stays type-valid.

// A resolver supplies the field policies for each resource.
val resolver =
    StaticPolicyResolver(
        mapOf(
            "employee.profile" to
                listOf(
                    FieldPolicy("ssn", MaskingStrategy.HIDE),
                    FieldPolicy("salary", MaskingStrategy.REDACT),
                ),
            "employee.address" to
                listOf(FieldPolicy("postalCode", MaskingStrategy.HIDE)),
        ),
    )

// Register Ihawu on the application's ObjectMapper.
val mapper = ObjectMapper().registerModule(IhawuModule(resolver))

// The caller's identity is attached to this single serialization call.
val principal = IhawuPrincipal("u1", roles = setOf("MANAGER"), attributes = emptyMap())
val profile =
    EmployeeProfile(
        name = "Jane Doe",
        ssn = "123-45-6789",
        salary = 90_000,
        address = Address(city = "Harare", postalCode = "00263"),
    )

val json =
    mapper
        .writer()
        .withAttribute(IhawuSerialization.PRINCIPAL, principal)
        .writeValueAsString(profile)

val tree = mapper.readTree(json)
check(!tree.has("ssn")) // HIDE -> field omitted
check(tree["salary"].isNull) // REDACT on a non-String -> JSON null
check(tree["name"].asText() == "Jane Doe") // not in policy -> unchanged
check(!tree["address"].has("postalCode")) // nested resource masked by its own policy
check(tree["address"]["city"].asText() == "Harare")
// Every item in a collection of resources receives the same field masking.
val resolver =
    StaticPolicyResolver(
        mapOf("employee.profile" to listOf(FieldPolicy("ssn", MaskingStrategy.HIDE))),
    )
val mapper = ObjectMapper().registerModule(IhawuModule(resolver))
val principal = IhawuPrincipal("u1", roles = setOf("MANAGER"), attributes = emptyMap())

val profiles =
    listOf(
        EmployeeProfile("Jane Doe", "123-45-6789", 90_000, Address("Harare", "00263")),
        EmployeeProfile("John Roe", "987-65-4321", 80_000, Address("Bulawayo", "00264")),
    )

val json =
    mapper
        .writer()
        .withAttribute(IhawuSerialization.PRINCIPAL, principal)
        .writeValueAsString(profiles)

val tree = mapper.readTree(json)

check(tree.all { !it.has("ssn") }) // every item masked
// With no principal attached to the call, the serializer fails closed and emits an empty object.
val resolver =
    StaticPolicyResolver(
        mapOf("employee.profile" to listOf(FieldPolicy("ssn", MaskingStrategy.HIDE))),
    )
val mapper = ObjectMapper().registerModule(IhawuModule(resolver))

val profile = EmployeeProfile("Jane Doe", "123-45-6789", 90_000, Address("Harare", "00263"))

// No IhawuSerialization.PRINCIPAL attribute set on the writer.
val json = mapper.writeValueAsString(profile)

check(mapper.readTree(json).isEmpty) // {} -> nothing leaked
// If the resolver throws — a misconfiguration or a policy-store outage — Ihawu fails closed:
// the protected resource serializes as an empty object instead of leaking unmasked data.
val failingResolver =
    object : ResourcePolicyResolver {
        override fun resolve(
            principal: IhawuPrincipal,
            resource: String,
        ): List<FieldPolicy> = throw IhawuCoreException("policy store unavailable")
    }
val mapper = ObjectMapper().registerModule(IhawuModule(failingResolver))
val principal = IhawuPrincipal("u1", roles = setOf("MANAGER"), attributes = emptyMap())

val profile = EmployeeProfile("Jane Doe", "123-45-6789", 90_000, Address("Harare", "00263"))

val json =
    mapper
        .writer()
        .withAttribute(IhawuSerialization.PRINCIPAL, principal)
        .writeValueAsString(profile)

check(mapper.readTree(json).isEmpty) // {} -> a resolver failure never leaks data

Constructors

Link copied to clipboard
constructor(resolver: ResourcePolicyResolver, failureSink: MaskingFailureSink = Slf4jMaskingFailureSink(), resolverErrorMode: ResolverErrorMode = ResolverErrorMode.MASK_ALL)

Properties

Link copied to clipboard
open val dependencies: Iterable<out Module?>?
Link copied to clipboard
open val moduleName: String?
Link copied to clipboard
open val typeId: Any?

Functions

Link copied to clipboard
open fun <T : Any?> addAbstractTypeMapping(superType: Class<T?>?, subType: Class<out T?>?): SimpleModule?
Link copied to clipboard
open fun <T : Any?> addDeserializer(type: Class<T?>?, deser: JsonDeserializer<out T?>?): SimpleModule?
Link copied to clipboard
open fun addKeyDeserializer(type: Class<*>?, deser: KeyDeserializer?): SimpleModule?
Link copied to clipboard
open fun <T : Any?> addKeySerializer(type: Class<out T?>?, ser: JsonSerializer<T?>?): SimpleModule?
Link copied to clipboard
open fun addSerializer(ser: JsonSerializer<*>?): SimpleModule?
open fun <T : Any?> addSerializer(type: Class<out T?>?, ser: JsonSerializer<T?>?): SimpleModule?
Link copied to clipboard
open fun addValueInstantiator(beanType: Class<*>?, inst: ValueInstantiator?): SimpleModule?
Link copied to clipboard
open fun registerSubtypes(subtypes: Collection<Class<*>?>?): SimpleModule?
open fun registerSubtypes(vararg subtypes: NamedType?): SimpleModule?
open fun registerSubtypes(vararg subtypes: Class<*>?): SimpleModule?
Link copied to clipboard
open fun setAbstractTypes(atr: SimpleAbstractTypeResolver?)
Link copied to clipboard
open fun setDeserializerModifier(mod: BeanDeserializerModifier?): SimpleModule?
Link copied to clipboard
open fun setDeserializers(d: SimpleDeserializers?)
Link copied to clipboard
open fun setKeyDeserializers(kd: SimpleKeyDeserializers?)
Link copied to clipboard
open fun setKeySerializers(ks: SimpleSerializers?)
Link copied to clipboard
open fun setMixInAnnotation(targetType: Class<*>?, mixinClass: Class<*>?): SimpleModule?
Link copied to clipboard
open fun setSerializerModifier(mod: BeanSerializerModifier?): SimpleModule?
Link copied to clipboard
open fun setSerializers(s: SimpleSerializers?)
Link copied to clipboard
open override fun setupModule(context: Module.SetupContext)
Link copied to clipboard
open fun setValueInstantiators(svi: SimpleValueInstantiators?)
Link copied to clipboard
open fun version(): Version?