Git Product home page Git Product logo

redacted's Introduction

Actions Status GitHub Tag Sonatype Nexus (Releases) Sonatype Nexus (Releases)

Redacted

Prevents leaking sensitive fields defined inside case class.

Simple example of @redacted usage

In Scala, case class(es) are omnipresent: they are the building blocks for complex business domain models, due to how easily they can be defined and instantiated; on top of that, the Scala compiler provides a convenient toString method for us that will pretty print in console/log their content, for example:

case class UserPreferences(useDarkTheme: Boolean, maxHistoryItems: Int)

val id = 123
val up = store.getUserPreferencesByID(123)
log.info(s"user preferences for user $id are $up")

will print

user preferences for user 123 are UserPreferences(true, 5)

However, this becomes a double-edge sword when handling sensitive data: assume you're writing an HTTP server, and you have a case class to pass its headers around, i.e.

case class HttpHeaders(userId: String, apiKey: String, languages: Seq[Locale], correlationId: String)

or a case class representing a user in a DB

case class User(id: UUID, nickname: String, email: String)

you probably wouldn't want to leak by mistake an apiKey (for security reasons) or an email (for PII/GDPR reasons).

Sure, you can get creative and define middleware layers/utility methods and so on to circumvent the issue, but wouldn't it be better if you were simply to say "when I dump the whole object, I don't want this field to be printed out" ?

@redacted to the rescue!

Usage

in your build.sbt file, add the following lines

val redactedVersion = // use latest version of the library
resolvers += DefaultMavenRepository,
libraryDependencies ++= Seq(
  "io.github.polentino" %% "redacted" % redactedVersion cross CrossVersion.full,
  compilerPlugin("io.github.polentino" %% "redacted-plugin" % redactedVersion cross CrossVersion.full)
)

and then, in your case class definitions

import io.github.polentino.redacted.redacted

case class HttpHeaders(userId: UUID, @redacted apiKey: String, languages: Seq[Locale], correlationId: String)

case class User(id: UUID, nickname: String, @redacted email: String)

That's all!

From now on, every time you'll try to dump the whole object,or invoke toString method

val headers: HttpHeaders = HttpHeaders(
  userId = UUID.randomUUID(),
  apiKey = "abcdefghijklmnopqrstuvwxyz",
  languages = Seq("it_IT", "en_US"),
  correlationId = "corr-id-123"
)
val user: User = User(
  id = UUID.randomUUID(),
  nickname = "polentino911",
  email = "[email protected]"
)
println(headers)
println(user)

this will actually be printed

$ HttpHeaders(d58b6a78-5411-4bd4-a0d3-e1ed38b579c4, ***, Seq(it_IT, en_US), corr-id-123)
$ User(8b2d4570-d043-473b-a56d-fe98105ccc2b, polentino911, ***)

But, of course, accessing the field itself will return its content, i.e.

println(headers.apiKey)
println(user.email)

will still print the real values:

$ abcdefghijklmnopqrstuvwxyz
$ [email protected]

Nested case class!

It also works with nested case classes:

case class Wrapper(id: String, user: User)

val wrapper = Wrapper("id-1", user) // user is the same object defined above
println(wrapper)

will print

Wrapper(id-1,User(8b2d4570-d043-473b-a56d-fe98105ccc2b, polentino911, ***))

Nested case class with upper level annotation!

It also works with nested case classes:

case class Wrapper(id: String, @redacted user: User)

val wrapper = Wrapper("id-1", user) // user is the same object defined above
println(wrapper)

will print

Wrapper(id-1,***)

How it works

Given a case class with at least one field annotated with @redacted, i.e.

final case class User(id: UUID, @redacted name: String)

the compiler plugin will replace the default implementation of its toString method with this

final case class User(id: UUID, @redacted name: String) {
  def toString(): String = "User(" + this.id + ",***" + ")"
}

The way it's done is the following:

PatchToString phase will inspect every class type definition and check whether the class being analysed is a case class, and if it has at least one of its fields annotated with @redacted ; if that's the case, it will then proceed to rewrite the default toString implementation by selectively returning either the *** string, or the value of the field, depending on the presence (or not) of @redacted like so:

def toString(): String =
  "<class name>(" + this.<field not redacted> + "," + "***" + ... + ")"

Improvements

  • create Sbt plugin
  • add some benchmarks with jmh

Credits

redacted's People

Contributors

polentino avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

arcilli

redacted's Issues

Add support for scala 2

As per description: the compiler plugin targets specifically Scala 3, but it would be nicer to have it for Scala 2 as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.