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")Content copied to clipboard
// 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 maskedContent copied to clipboard
// 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 leakedContent copied to clipboard
// 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 dataContent copied to clipboard
Constructors
Link copied to clipboard
constructor(resolver: ResourcePolicyResolver, failureSink: MaskingFailureSink = Slf4jMaskingFailureSink(), resolverErrorMode: ResolverErrorMode = ResolverErrorMode.MASK_ALL)
Functions
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard