All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ciris.Secret.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2021 Viktor Lövgren
 *
 * SPDX-License-Identifier: MIT
 */

package ciris

import cats.{Eq, Show}
import cats.implicits._
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import scala.annotation.tailrec

/**
  * Secret configuration value which might contain sensitive details.
  *
  * When a secret configuration value is shown, the value is replaced
  * by the first 7 characters of the SHA-1 hash for the value. This
  * short SHA-1 hash is available as [[Secret#valueShortHash]], and
  * the full SHA-1 hash is available as [[Secret#valueHash]]. The
  * underlying configuration value is available as [[Secret#value]].
  *
  * [[ConfigValue#secret]] can be used to wrap a value in [[Secret]],
  * while also redacting sentitive details from errors.
  *
  * @example {{{
  * scala> import cats.implicits._
  * import cats.implicits._
  *
  * scala> val secret = Secret(123)
  * secret: Secret[Int] = Secret(40bd001)
  *
  * scala> secret.valueShortHash
  * res0: String = 40bd001
  *
  * scala> secret.valueHash
  * res1: String = 40bd001563085fc35165329ea1ff5c5ecbdbbeef
  *
  * scala> secret.value
  * res2: Int = 123
  * }}}
  */
sealed abstract class Secret[+A] {

  /**
    * Returns the underlying configuration value.
    */
  def value: A

  /**
    * Returns the SHA-1 hash for the configuration value.
    *
    * Hashing is done in `UTF-8` and the hash is
    * returned in hexadecimal format.
    */
  def valueHash: String

  /**
    * Returns the first 7 characters of the SHA-1 hash
    * for the configuration value.
    *
    * @see [[Secret#valueHash]]
    */
  def valueShortHash: String
}

/**
  * @groupname Create Creating Instances
  * @groupprio Create 0
  *
  * @groupname Instances Type Class Instances
  * @groupprio Instances 1
  */
object Secret {

  /**
    * Returns a new [[Secret]] for the specified configuration value.
    *
    * The [[Secret#valueHash]] will be calculated using the shown value.
    *
    * @example {{{
    * scala> import cats.implicits._
    * import cats.implicits._
    *
    * scala> Secret(12.5)
    * res0: Secret[Double] = Secret(90db4c0)
    * }}}
    *
    * @group Create
    */
  final def apply[A](value: A)(implicit show: Show[A]): Secret[A] = {
    val _value = value
    new Secret[A] {
      override final val value: A =
        _value

      override final def valueHash: String =
        sha1Hex(value.show)

      override final def valueShortHash: String =
        valueHash.take(7)

      override final def hashCode: Int =
        value.hashCode

      override final def equals(that: Any): Boolean =
        that match {
          case Secret(thatValue) => value == thatValue
          case _                 => false
        }

      override final def toString: String =
        s"Secret($valueShortHash)"
    }
  }

  /**
    * Returns the configuration value for the specified [[Secret]].
    *
    * This function enables pattern matching on [[Secret]]s.
    *
    * @example {{{
    * scala> import cats.implicits._
    * import cats.implicits._
    *
    * scala> val secret = Secret(12.5)
    * secret: Secret[Double] = Secret(90db4c0)
    *
    * scala> secret match { case Secret(value) => value }
    * res0: Double = 12.5
    * }}}
    *
    * @group Create
    */
  final def unapply[A](secret: Secret[A]): Some[A] =
    Some(secret.value)

  /**
    * @group Instances
    */
  implicit final def secretEq[A](implicit eq: Eq[A]): Eq[Secret[A]] =
    Eq.by(_.value)

  /**
    * @group Instances
    */
  implicit final def secretShow[A]: Show[Secret[A]] =
    Show.fromToString

  private[this] final val hexChars: Array[Char] =
    Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')

  private[this] final def hex(in: Array[Byte]): Array[Char] = {
    val length = in.length

    @tailrec def encode(out: Array[Char], i: Int, j: Int): Array[Char] = {
      if (i < length) {
        out(j) = hexChars((0xf0 & in(i)) >>> 4)
        out(j + 1) = hexChars(0x0f & in(i))
        encode(out, i + 1, j + 2)
      } else out
    }

    encode(new Array(length << 1), 0, 0)
  }

  private[this] final def sha1(bytes: Array[Byte]): Array[Byte] =
    MessageDigest.getInstance("SHA-1").digest(bytes)

  private[this] final def sha1Hex(s: String): String =
    new String(hex(sha1(s.getBytes(StandardCharsets.UTF_8))))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy