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 package fluence.crypto
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.{ Files, Paths }
import cats.MonadError import cats.MonadError
import cats.syntax.flatMap._ import cats.syntax.flatMap._
@ -29,21 +29,37 @@ import io.circe.syntax._
import scala.language.higherKinds 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._ import KeyStore._
def readKeyPair: F[KeyPair] = { def readKeyPair: F[KeyPair] = {
val keyBytes = Files.readAllBytes(file.toPath) val keyBytes = Files.readAllBytes(file.toPath) // TODO: it throws!
for { for {
storageOp F.fromEither(decode[Option[KeyStore]](new String(keyBytes))) storageOp F.fromEither(decode[Option[KeyStore]](new String(keyBytes)))
storage storageOp match { storage storageOp match {
case None F.raiseError[KeyStore](new RuntimeException("Cannot parse file with keys.")) case None
case Some(ks) F.pure(ks) 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 } yield storage.keyPair
} }
def storeSecretKey(key: KeyPair): F[Unit] = def storeSecretKey(key: KeyPair): F[Unit] =
F.catchNonFatal { 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") if (!file.exists()) file.createNewFile() else throw new RuntimeException(file.getAbsolutePath + " already exists")
val str = KeyStore(key).asJson.toString() val str = KeyStore(key).asJson.toString()
@ -57,6 +73,24 @@ class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) {
for { for {
newKeys f newKeys f
_ storeSecretKey(newKeys) _ 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] = 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) new AesCrypt(password.toHex.toCharArray, withIV, config)
} }

View File

@ -17,6 +17,10 @@
package fluence.crypto package fluence.crypto
import cats.Monad
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.data.EitherT
import fluence.crypto.keypair.KeyPair import fluence.crypto.keypair.KeyPair
import io.circe.{ Decoder, Encoder, HCursor, Json } import io.circe.{ Decoder, Encoder, HCursor, Json }
import scodec.bits.{ Bases, ByteVector } import scodec.bits.{ Bases, ByteVector }
@ -63,16 +67,18 @@ object KeyStore {
} yield KeyStore(KeyPair.fromByteVectors(public, secret)) } yield KeyStore(KeyPair.fromByteVectors(public, secret))
} }
def fromBase64(base64: String): KeyStore = { def fromBase64[F[_] : Monad](base64: String): EitherT[F, IllegalArgumentException, KeyStore] =
val jsonStr = ByteVector.fromBase64(base64, alphabet) match { for {
case Some(bv) new String(bv.toArray) jsonStr ByteVector.fromBase64(base64, alphabet) match {
case None case Some(bv) EitherT.pure[F, IllegalArgumentException](new String(bv.toArray))
throw new IllegalArgumentException("'" + base64 + "' is not a valid base64.") case None EitherT.leftT(
} 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)
} }
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 { "throw an exception" when {
"invalid base64 format" in { "invalid base64 format" in {
val invalidBase64 = "!@#$%" val invalidBase64 = "!@#$%"
the[IllegalArgumentException] thrownBy {
KeyStore.fromBase64(invalidBase64) val e = KeyStore.fromBase64(invalidBase64).value.left.get
} should have message "'" + invalidBase64 + "' is not a valid base64." e should have message "'" + invalidBase64 + "' is not a valid base64."
} }
"invalid decoded json" in { "invalid decoded json" in {
val invalidJson = ByteVector("""{"keystore":{"public":"cHViS2V5"}}""".getBytes).toBase64(alphabet) 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 { "fetch KeyStore from valid base64" in {
val invalidJson = ByteVector(jsonString.getBytes).toBase64(alphabet) val invalidJson = ByteVector(jsonString.getBytes).toBase64(alphabet)
val result = KeyStore.fromBase64(invalidJson) val result = KeyStore.fromBase64(invalidJson).value.right.get
result shouldBe keyStore result shouldBe keyStore
} }
} }