Skip to content

Instantly share code, notes, and snippets.

@stsatlantis
Last active May 7, 2019 12:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stsatlantis/f287e8ea6fd2e2a27c02640a20ed1009 to your computer and use it in GitHub Desktop.
Save stsatlantis/f287e8ea6fd2e2a27c02640a20ed1009 to your computer and use it in GitHub Desktop.
Scala-Shapeless POC of tagged type to modify only marked/tagged values
package util
import eu.timepit.refined.api.RefType
import io.circe.Encoder
import io.circe.refined.refinedEncoder
import shapeless._
import shapeless.ops.hlist.Mapper
import shapeless.tag.@@
trait Sensitive
object SensitiveString {
type SensitiveString = String @@ Sensitive
def apply(input: String): SensitiveString = tag[Sensitive][String](input)
implicit val sensitiveEncoder: Encoder[SensitiveString] = refinedEncoder
}
class EncryptionService(f: String => String, reverseF: String => String) {
import util.Encryption.EncryptionPoly
object Enc extends EncryptionPoly(f)
object Dec extends EncryptionPoly(reverseF)
def encrypt[T, L <: HList](input: T)(
implicit
gen: Generic.Aux[T, L],
hListMapper: Lazy[Mapper.Aux[Enc.type, L, L]]
): T = {
gen.from(gen.to(input).map(Enc)(hListMapper.value))
}
def decrypt[T, L <: HList](input: T)(
implicit
gen: Generic.Aux[T, L],
hListMapper: Lazy[Mapper.Aux[Dec.type, L, L]]
): T = {
gen.from(gen.to(input).map(Dec)(hListMapper.value))
}
}
object EncryptionService {
def apply(f: String => String, reverseF: String => String): EncryptionService = new EncryptionService(f, reverseF)
}
private[util] object Encryption {
import SensitiveString._
trait LowPrioEncryptionPoly extends Poly1 {
implicit def string[T]: Case.Aux[T, T] = at[T] (identity)
}
trait MidPrioEncryptionPoly extends LowPrioEncryptionPoly {
implicit def genericMapper[T <: Product, L <: HList](
implicit
gen: Generic.Aux[T, L],
mapper: Lazy[Mapper.Aux[this.type, L, L]]): Case.Aux[T, T] = {
at[T](input => gen.from(gen.to(input).map(this)(mapper.value)))
}
}
abstract class EncryptionPoly(f: String => String) extends MidPrioEncryptionPoly {
implicit def sensitiveMasker(implicit refType: RefType[@@]): Case.Aux[SensitiveString, SensitiveString] =
at[SensitiveString](e => tag[Sensitive][String](f(refType.unwrap(e))))
implicit def optionMasker(implicit refType: RefType[@@]): Case.Aux[Option[SensitiveString], Option[SensitiveString]] =
at[Option[SensitiveString]](_.map(e => tag[Sensitive][String](f(refType.unwrap(e)))))
}
}
import java.util.Base64
import util.SensitiveString.SensitiveString
import org.scalatest.{ Matchers, WordSpec }
class EncryptionSpec
extends WordSpec
with Matchers {
case class Person(email: SensitiveString, age: Int, nickname: String)
case class Class(owner: Person, name: String)
case class School(c: Class, address: SensitiveString)
case class Address(name: String, postCode: Option[SensitiveString])
val encryptionFn: String => String = e => new String(Base64.getEncoder.encode(e.getBytes()))
val decryptionFn: String => String = e => new String(Base64.getDecoder.decode(e))
val encryptionService = Encryption(encryptionFn, decryptionFn)
"Person with sensitive data" in {
val peter = Person(SensitiveString("peter@localhost"), 1, "Peti")
encryptionService.encrypt(peter) shouldBe Person(SensitiveString("cGV0ZXJAbG9jYWxob3N0"), 1, "Peti")
encryptionService.decrypt(encryptionService.encrypt(peter)) shouldBe peter
}
"Address with SensitiveString data" in {
val address = Address("Calle", Some(SensitiveString("peter@localhost")))
encryptionService.encrypt(address) shouldBe Address("Calle", Some(SensitiveString("cGV0ZXJAbG9jYWxob3N0")))
encryptionService.decrypt(encryptionService.encrypt(address)) shouldBe address
}
"Class with SensitiveString data" in {
val peter = Person(SensitiveString("peter@localhost"), 1, "Peti")
val PetersClass = Class(peter, "Kecske")
encryptionService.encrypt(PetersClass) shouldBe Class(Person(SensitiveString("cGV0ZXJAbG9jYWxob3N0"), 1, "Peti"), "Kecske")
encryptionService.decrypt(encryptionService.encrypt(PetersClass)) shouldBe PetersClass
}
"School with SensitiveString data" in {
val peter = Person(SensitiveString("peter@localhost"), 1, "Peti")
val PetersClass = Class(peter, "Kecske")
val school = School(PetersClass, SensitiveString("Bezeredj"))
encryptionService.encrypt(school) shouldBe School(Class(Person(SensitiveString("cGV0ZXJAbG9jYWxob3N0"), 1, "Peti"), "Kecske"), SensitiveString("QmV6ZXJlZGo="))
encryptionService.decrypt(encryptionService.encrypt(school)) shouldBe school
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment