Skip to main content

Usage

Basic Usage

Once you enabled your SBT (sub)project(s) via sbt-plugin (or by manually adding the dependencies), usage of @redacted is really straightforward. Assume the (purely educational) scenario where you would like to secure your own implementation for HTTP headers, you could do something like this

com/organization/project/HttpHeaders.scala
import io.github.polentino.redacted._

final case class HttpHeaders(
contentType: String,
@redacted userID: Long,
@redacetd authorization: String,
acceptedLanguage: Set[Locale])

and now you will be able to log securely to file or console your headers, without fear of leaking by mistake the authorization token or the userID

val httpHeaders: HttpHeaders = // a reference to the http headers of your application
logger.debug(httpHeaders)

which will print for example

> HttpHeaders(application/json,***,***,Set(ENGLISH))

But that can be a little confusing; if you like, you can also create value classes for sensitive data like passwords, tokens, certificates and so on

com/organization/project/secrets/package.scala
import io.github.polentino.redacted._

package object secrets {
final case class UserID(@redacted value: Long) extends AnyVal
final case class Password(@redacted value: String) extends AnyVal
final case class Token(@redacted value: String) extends AnyVal
final case class PemCertificate(@redacted bytes: List[Byte]) extends AnyVal
// ... other case class definitions for sensitive data
}

and change the definition of HttpHeaders like so

com/organization/project/HttpHeaders.scala
import com.organization.project.secrets._

final case class HttpHeaders(
contentType: String,
userId: UserID,
authorization: Token,
acceptedLanguage: Set[Locale])

and now, the same example shown above will print this instead

> HttpHeaders(application/json,UserID(***),Token(***),Set(ENGLISH))

Looks nicer, right?

Quick recap

With the example above, you saw already a few good use cases of how @redacted works:

  • it works with case classes
  • it works with value classes as well
  • it works with one or more annotated fields

However, there's more to it: keep reading!

Will it work if ...

I nest the case class into another class/object ?

Yes! This is a totally valid, alternate way to write our educational HttpHeaders class

com/organization/project/HttpHeaders.scala
import HttpHeaders._

final case class HttpHeaders(
contentType: String,
userId: UserID,
authorization: Token,
acceptedLanguage: Set[Locale])

object HttpHeaders {
final case class UserID(@redacted value: Long) extends AnyVal
final case class Password(@redacted value: String) extends AnyVal
}

I use the FQDN of the annotation ?

Yes! Once again, here's an alternate way to write our educational secrets package object

com/organization/project/secrets/package.scala
import io.github.polentino.redacted._

package object secrets {
final case class UserID(@io.github.polentino.redacted.redacted value: Long) extends AnyVal
final case class Password(@io.github.polentino.redacted.redacted value: String) extends AnyVal
final case class Token(@io.github.polentino.redacted.redacted value: String) extends AnyVal
final case class PemCertificate(@io.github.polentino.redacted.redacted bytes: List[Byte]) extends AnyVal
// ... other case class definitions for sensitive data
}

I define an alias for @redacted ?

Yes! Here's another alternate way to define the secrets package object

com/organization/project/secrets/package.scala
import io.github.polentino.redacted.{redacted => obfuscated}

package object secrets {
final case class UserID(@obfuscated value: Long) extends AnyVal
final case class Password(@obfuscated value: String) extends AnyVal
final case class Token(@obfuscated value: String) extends AnyVal
final case class PemCertificate(@obfuscated bytes: List[Byte]) extends AnyVal
// ... other case class definitions for sensitive data
}

What happens if ...

the hashCode of a redacted case class is used ?

hashCode is essential for hash-based collections such as HashMap, HashSet and so on, therefore its correctness is of utmost importance.

Since @redacted doesn't change the values of the annotated fields, but only the way toString is implemented, hashCode remains unaltered.

tip

And, just in case, there is also a specific test that ensures the hashCode of two similar (in fields definition) case classes, one redacted, the other not, yield the same hashCode result.

I use it in a curried parameter ?

It won't work.

While the following definition is syntactically valid Scala code

CurriedParameter.scala
final case class CurriedParameter(id: Long)(@redacted email: String)

the field email won't contribute at all to the toString result, therefore using the annotation is irrelevant.

I use it in a method of the case class ?

It won't work, and the plugin won't confuse method parameters with case class fields, if that's what you're asking.

For example, this won't redact email at all

ConfusingParameter.scala
final case class CurriedParameter(id: Long, email: String) {
// WRONG!
def toUpper(@redacted email: String): String = ...
}

while this will, although it won't affect the email parameter of the toUpper method

ConfusingParameter.scala
final case class CurriedParameter(id: Long,@redacted email: String) {
def toUpper(email: String): String = ...
}

I define a fake redacted annotation ?

This won't work either. If you try to define a fake redacted annotation

com/organization/project/redacted.scala
import scala.annotation.StaticAnnotation

class redacted extends StaticAnnotation

and then try to use it in our educational HttpHeaders case class

com/organization/project/HttpHeaders.scala
import com.organization.project.redacted

final case class HttpHeaders(
contentType: String,
@redacted userID: Long,
@redacetd authorization: String,
acceptedLanguage: Set[Locale])

those annotations won't have any effect!