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 and identical across JVM, Scala.js, and Scala Native.
Assume the (purely educational) scenario where you would like to secure your own implementation for HTTP headers, you could do something like this
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
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
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
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
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
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.
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
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
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
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
import scala.annotation.StaticAnnotation
class redacted extends StaticAnnotation
and then try to use it in our educational HttpHeaders case class
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!