Cli demo (#66)

* CLI reorganization WIP

* clean up reference.conf, add application.conf to gitignore

* linearizing ClientComposer;
some free operations

* keyPath config path corrected

* compilation error fixed

* AuthorizedClient removed

* test compilation error fixed

* fix keystore tests

* Merge branch 'master' into cli-demo

# Conflicts:
#	client/src/main/scala/fluence/client/ClientApp.scala
#	client/src/main/scala/fluence/client/ConfigParsers.scala
#	client/src/main/scala/fluence/client/FluenceClient.scala
#	client/src/main/scala/fluence/client/KademliaConfigParser.scala
#	client/src/main/scala/fluence/client/cli/CliOps.scala
#	client/src/main/scala/fluence/client/config/KademliaConfigParser.scala

* cats effect 0.9

* replicationN in ClientApp

* add logs to FileKeyStorage

* add logs to node dataset storage

* fix typos

* add logs to server contract allocator

* ClientApp recursive calls

* launches and joins with docker

* with volumes

* update port parameters

* update port parameters

* fix test

* update port parameters

* add check before put

* change assertion

* no fluence_grpc_port

* run client in docker

* some TODOs for Cli

* fix assertion

* add some logs

* add implicit
This commit is contained in:
Dmitry Kurinskiy 2018-03-01 15:36:56 +03:00 committed by Dima
parent 4a966657a6
commit 26b41e7b4d
5 changed files with 69 additions and 30 deletions

View File

@ -18,7 +18,7 @@
package fluence.crypto
import java.io.File
import java.nio.file.Files
import java.nio.file.{ Files, Paths }
import cats.MonadError
import cats.syntax.flatMap._
@ -29,21 +29,37 @@ import io.circe.syntax._
import scala.language.higherKinds
class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) {
/**
* TODO use cats IO
* File based storage for crypto keys.
*
* @param file Path to keys in file system
*/
class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) extends slogging.LazyLogging {
import KeyStore._
def readKeyPair: F[KeyPair] = {
val keyBytes = Files.readAllBytes(file.toPath)
val keyBytes = Files.readAllBytes(file.toPath) // TODO: it throws!
for {
storageOp F.fromEither(decode[Option[KeyStore]](new String(keyBytes)))
storage storageOp match {
case None F.raiseError[KeyStore](new RuntimeException("Cannot parse file with keys."))
case Some(ks) F.pure(ks)
case None
logger.warn(s"Reading keys from file=$file was failed")
F.raiseError[KeyStore](new RuntimeException("Cannot parse file with keys."))
case Some(ks)
logger.info(s"Reading keys from file=$file was success")
F.pure(ks)
}
} yield storage.keyPair
}
def storeSecretKey(key: KeyPair): F[Unit] =
F.catchNonFatal {
logger.info("Storing secret key to file: " + file)
if (!file.getParentFile.exists()) {
logger.info(s"Parent directory does not exist: ${file.getParentFile}, trying to create")
Files.createDirectories(file.getParentFile.toPath)
}
if (!file.exists()) file.createNewFile() else throw new RuntimeException(file.getAbsolutePath + " already exists")
val str = KeyStore(key).asJson.toString()
@ -57,6 +73,24 @@ class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) {
for {
newKeys f
_ storeSecretKey(newKeys)
} yield newKeys
} yield {
logger.info(s"New keys were generated and saved to file=$file")
newKeys
}
}
}
object FileKeyStorage {
/**
* Generates or loads keypair
*
* @param keyPath Path to store keys in
* @param algo Sign algo
* @return Keypair, either loaded or freshly generated
*/
def getKeyPair[F[_]](keyPath: String, algo: SignAlgo)(implicit F: MonadError[F, Throwable]): F[KeyPair] = {
val keyFile = new File(keyPath)
val keyStorage = new FileKeyStorage[F](keyFile)
keyStorage.getOrCreateKeyPair(algo.generateKeyPair[F]().value.flatMap(F.fromEither))
}
}

View File

@ -198,5 +198,4 @@ object AesCrypt extends slogging.LazyLogging {
def apply[F[_] : Applicative, T](password: ByteVector, withIV: Boolean, config: AesConfig)(implicit ME: MonadError[F, Throwable], codec: Codec[F, T, Array[Byte]]): AesCrypt[F, T] =
new AesCrypt(password.toHex.toCharArray, withIV, config)
}

View File

@ -1,12 +1,12 @@
package fluence.crypto
import fluence.crypto.algorithm.{AesConfig, AesCrypt, CryptoErr}
import fluence.crypto.algorithm.{ AesConfig, AesCrypt, CryptoErr }
import cats.instances.try_._
import org.scalactic.source.Position
import org.scalatest.{Assertion, Matchers, WordSpec}
import org.scalatest.{ Assertion, Matchers, WordSpec }
import scodec.bits.ByteVector
import scala.util.{Random, Try}
import scala.util.{ Random, Try }
class AesSpec extends WordSpec with Matchers with slogging.LazyLogging {
@ -57,7 +57,7 @@ class AesSpec extends WordSpec with Matchers with slogging.LazyLogging {
def checkCryptoError(tr: Try[String])(implicit pos: Position): Assertion = {
tr.map(_ false).recover {
case e: CryptoErr true
case e
case e
logger.error("Unexpected error", e)
false
}.get shouldBe true

View File

@ -17,6 +17,10 @@
package fluence.crypto
import cats.Monad
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair
import io.circe.{ Decoder, Encoder, HCursor, Json }
import scodec.bits.{ Bases, ByteVector }
@ -63,16 +67,18 @@ object KeyStore {
} yield KeyStore(KeyPair.fromByteVectors(public, secret))
}
def fromBase64(base64: String): KeyStore = {
val jsonStr = ByteVector.fromBase64(base64, alphabet) match {
case Some(bv) new String(bv.toArray)
case None
throw new IllegalArgumentException("'" + base64 + "' is not a valid base64.")
}
decode[Option[KeyStore]](jsonStr) match {
case Right(Some(ks)) ks
case Right(None) throw new IllegalArgumentException("'" + base64 + "' is not a valid key store.")
case Left(err) throw new IllegalArgumentException("'" + base64 + "' is not a valid key store.", err)
}
}
def fromBase64[F[_] : Monad](base64: String): EitherT[F, IllegalArgumentException, KeyStore] =
for {
jsonStr ByteVector.fromBase64(base64, alphabet) match {
case Some(bv) EitherT.pure[F, IllegalArgumentException](new String(bv.toArray))
case None EitherT.leftT(
new IllegalArgumentException("'" + base64 + "' is not a valid base64.")
)
}
keyStore decode[Option[KeyStore]](jsonStr) match {
case Right(Some(ks)) EitherT.pure[F, IllegalArgumentException](ks)
case Right(None) EitherT.leftT[F, KeyStore](new IllegalArgumentException("'" + base64 + "' is not a valid key store."))
case Left(err) EitherT.leftT[F, KeyStore](new IllegalArgumentException("'" + base64 + "' is not a valid key store.", err))
}
} yield keyStore
}

View File

@ -54,21 +54,21 @@ class KeyStoreSpec extends WordSpec with Matchers {
"throw an exception" when {
"invalid base64 format" in {
val invalidBase64 = "!@#$%"
the[IllegalArgumentException] thrownBy {
KeyStore.fromBase64(invalidBase64)
} should have message "'" + invalidBase64 + "' is not a valid base64."
val e = KeyStore.fromBase64(invalidBase64).value.left.get
e should have message "'" + invalidBase64 + "' is not a valid base64."
}
"invalid decoded json" in {
val invalidJson = ByteVector("""{"keystore":{"public":"cHViS2V5"}}""".getBytes).toBase64(alphabet)
the[IllegalArgumentException] thrownBy {
KeyStore.fromBase64(invalidJson)
}
KeyStore.fromBase64(invalidJson).value.isLeft shouldBe true
}
}
"fetch KeyStore from valid base64" in {
val invalidJson = ByteVector(jsonString.getBytes).toBase64(alphabet)
val result = KeyStore.fromBase64(invalidJson)
val result = KeyStore.fromBase64(invalidJson).value.right.get
result shouldBe keyStore
}
}