From eb08a875f5252aa451676c336a0cdd60e393414f Mon Sep 17 00:00:00 2001 From: Viktor Lovgren Date: Fri, 23 Apr 2021 11:20:04 +0200 Subject: [PATCH] Add UseOnceSecret and ConfigValue#useOnceSecret --- .../src/main/scala/ciris/ConfigValue.scala | 23 +++++++++++ .../src/main/scala/ciris/UseOnceSecret.scala | 41 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 modules/core/src/main/scala/ciris/UseOnceSecret.scala diff --git a/modules/core/src/main/scala/ciris/ConfigValue.scala b/modules/core/src/main/scala/ciris/ConfigValue.scala index 7e117368..4156b6ab 100644 --- a/modules/core/src/main/scala/ciris/ConfigValue.scala +++ b/modules/core/src/main/scala/ciris/ConfigValue.scala @@ -306,6 +306,29 @@ sealed abstract class ConfigValue[+F[_], A] { override final def toString: String = "ConfigValue$" + System.identityHashCode(this) } + + /** + * Returns a new [[ConfigValue]] which treats the value + * as a secret which can only be used once. + * + * Sensitive details are redacted from error messages. The + * value is wrapped in [[UseOnceSecret]] which prevents + * multiple accesses to the secret and which nullifies + * the secret once it has been used. + * + * Using `.useOnceSecret` is equivalent to using + * `.redacted.evalMap(UseOnceSecret(_))`. + */ + final def useOnceSecret(implicit ev: A <:< Array[Char]): ConfigValue[F, UseOnceSecret] = + new ConfigValue[F, UseOnceSecret] { + override final def to[G[x] >: F[x]]( + implicit G: Async[G] + ): Resource[G, ConfigEntry[UseOnceSecret]] = + self.redacted.to[G].evalMap(_.traverse(UseOnceSecret[G](_))) + + override final def toString: String = + "ConfigValue$" + System.identityHashCode(this) + } } /** diff --git a/modules/core/src/main/scala/ciris/UseOnceSecret.scala b/modules/core/src/main/scala/ciris/UseOnceSecret.scala new file mode 100644 index 00000000..f9575dc8 --- /dev/null +++ b/modules/core/src/main/scala/ciris/UseOnceSecret.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2021 Viktor Lövgren + * + * SPDX-License-Identifier: MIT + */ + +package ciris + +import cats.effect.kernel.{Resource, Sync} +import cats.implicits._ +import java.util.Arrays +import java.util.concurrent.atomic.AtomicReference + +trait UseOnceSecret { + def resource[F[_]](implicit F: Sync[F]): Resource[F, Array[Char]] + + final def useOnce[F[_], A](f: Array[Char] => F[A])(implicit F: Sync[F]): F[A] = + resource[F].use(f) +} + +object UseOnceSecret { + final def apply[F[_]](secret: Array[Char])(implicit F: Sync[F]): F[UseOnceSecret] = + F.delay(new AtomicReference(secret.some)).map { ref => + new UseOnceSecret { + override final def resource[G[_]](implicit G: Sync[G]): Resource[G, Array[Char]] = { + val acquire: G[Array[Char]] = + G.delay(ref.getAndSet(None)).flatMap { + case Some(secret) => + G.pure(secret) + case None => + G.raiseError(new IllegalStateException("Secret has already been used once")) + } + + val release: G[Unit] = + G.delay(Arrays.fill(secret, ' ')) + + Resource.make(acquire)(_ => release) + } + } + } +}