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

sss.openstar.tools.ClaimIdentity.scala Maven / Gradle / Ivy

package sss.openstar.tools

import scorex.crypto.signatures.PublicKey
import sss.ancillary.ByteArrayEncodedStrOps._
import sss.ancillary.Logging
import sss.openstar.MessageKeys
import sss.openstar.account.NodeIdentityManager.PasswordCredentials
import sss.openstar.account.NodeSigner.KeyType
import sss.openstar.account.Ops.MakeTxSigArys
import sss.openstar.account._
import sss.openstar.account.impl.EncryptedKeyFileAccountSignerCreator
import sss.openstar.chains.TxWriterActor.{InternalCommit, InternalTempNack}
import sss.openstar.controller.Utils
import sss.openstar.identityledger.GenericAttributeCategory.PublicKeyIdCategory
import sss.openstar.identityledger.IdentityService.defaultTag
import sss.openstar.identityledger.{Claim, Claim0, GenericAttributeCategory, IdentityLedgerTx, IdentityRoleAttribute, IdentityService, LedgerAttribute, LinkRescuer, SignerVerifierDetails, defaultPaywallCategory}
import sss.openstar.ledger.{LedgerItem, SeqLedgerItem, SignedTxEntry}
import sss.openstar.tools.SendTxSupport.SendTx
import us.monoid.web.Resty
import sss.openstar.identityledger.GenericAttribute

import java.nio.charset.StandardCharsets
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Try}


/**
  * Created by alan on 6/7/16.
  */
object ClaimIdentity extends Logging {

  case class DeleteKeyFileException(msg: String) extends RuntimeException(msg)

  def makeDefaultTagSignerVerifierDetails(
                                           identity: String,
                                           pubKey: PublicKey,
                                           keyType: KeyType): SignerVerifierDetails = {
    SignerVerifierDetails(
      NodeIdTag(identity), None, TypedPublicKey(pubKey, keyType),
      EncryptedKeyFileAccountSignerCreator.SecurityLevel,
      EncryptedKeyFileAccountSignerCreator.SignerType,
      EncryptedKeyFileAccountSignerCreator.InteractionType
    )
  }

  def claimRemote(
                   numRetries: Int,
                   claimUrl: String,
                   signerVerifierDetails: SignerVerifierDetails): Try[String] = Retry.retry(numRetries, 3.seconds) {


    val pkey = signerVerifierDetails.typedPublicKey.publicKey.toBase64Str
    val identity = signerVerifierDetails.nodeIdTag.nodeId
    require(signerVerifierDetails.nodeIdTag.tag == NodeIdTag.defaultTag, s"Cannot claim using tag ${signerVerifierDetails.nodeIdTag.tag}, must use default tag")
    val keyType = signerVerifierDetails.typedPublicKey.keyType
    val securityLevel = signerVerifierDetails.securityLevel.toString
    val signerType = signerVerifierDetails.signerType
    val interactionType = signerVerifierDetails.interactionType

    val resty = new Resty(Resty.Option.timeout(2000))
    val resultResource = resty.text(s"${claimUrl}console/command?1=claim&2=$identity&3=${pkey}&4=${keyType}&5=${securityLevel}&6=${signerType}&7=${interactionType}")
    val result = resultResource.toString

    if (result.contains("ok")) {
      println(s"Identity $identity is now locked to public key $pkey")
      println(s"The public key is identified by tag $defaultTag")
      println(s"The private key corresponding to the public key is unlocked by the password you just typed in.")
      println(signerVerifierDetails)

    } else if (result.contains("fail")) {
      println(s"Couldn't register identity $identity - $result")
    } else {
      println(s"Failed to reach remote claim service ($identity - $result)")
      throw new RuntimeException(result)
    }
    result
  }

  def claimAndSetupUsingLocalKeyFile(
                     identity: String,
             phrase: String,
             rescuers: Set[String], // will be linked as rescuers for the created user
             toIdentityLedgerOwner: NodeIdentity => Option[NodeIdentity],
             hostingNodeIdentity: NodeIdentity,
             keyPersister: KeyPersister,
             keyGenerator: AccountKeysFactory,
             claimUrl: String,
             identityTransactions: List[IdentityLedgerTx] = Nil,
             ownedTransactions: List[IdentityLedgerTx] = Nil
           )(implicit
             nodeIdentityManager: NodeIdentityManager,
             sendTx: SendTx,
             ec: ExecutionContext
           ): Future[NodeIdentity] = {


    val newNodeIdentity = () => nodeIdentityManager(identity, IdentityService.defaultTag,
      Seq(PasswordCredentials(NodeIdTag(identity, IdentityService.defaultTag), phrase)))

    keyPersister.keyExists(identity, IdentityService.defaultTag) flatMap {
      case true =>
        Future.failed(new RuntimeException(s"Identifier=`$identity` is already taken"))
      case false =>
        val kpGen = () => keyGenerator.createKeyPair()
        //creating it here if necessary means it will exist later.
        keyPersister.findOrCreate(
          identity,
          IdentityService.defaultTag,
          phrase,
          kpGen
        ).flatMap { keyPair =>
          toIdentityLedgerOwner(hostingNodeIdentity) match {
            case Some(identityLedgerOwner) =>

              val idTag = NodeIdTag(identity, IdentityService.defaultTag)
              val priv = keyPair.priv
              val pub = keyPair.pub
              val claimTx = Claim0(identityLedgerOwner.id, identity, pub)
              Utils.simplySignedIdentityLedgerItem(identityLedgerOwner, claimTx) flatMap { claimLedgerItem =>
                val newGuySigner = keyGenerator.accountKeysImp.signer(priv)
                val identityLedgerItems = (rescuers.toList.map(LinkRescuer(_, identity)) ::: identityTransactions)
                  .map { item =>
                    val sig = newGuySigner(item.txId)
                    val sigs = Seq(idTag.nodeIdBytes, idTag.tagBytes, sig)
                    val ste = SignedTxEntry(item.toBytes, Seq(sigs).txSig)
                    LedgerItem(MessageKeys.IdentityLedger, item.txId, ste.toBytes)
                  }
                val ownedLedgerItemsF = Future.sequence(ownedTransactions.map(
                  Utils.simplySignedIdentityLedgerItem(identityLedgerOwner, _)
                ))
                ownedLedgerItemsF map { ownedLedgerItems =>
                  Left(claimLedgerItem :: (identityLedgerItems ::: ownedLedgerItems))
                }
              }

            case None =>
              val signerVerifierDetails: SignerVerifierDetails =
                makeDefaultTagSignerVerifierDetails(identity, keyPair.pub, keyGenerator.keysType)

              Future.fromTry(
                ClaimIdentity.claimRemote(1, claimUrl, signerVerifierDetails).map(Right(_))
              )
          }
        } flatMap {
            case Left(claimAndRest) =>
              sendTx(SeqLedgerItem(claimAndRest.head)).whenAvailableLocally.map(_.commitTxResult.internalCommit).flatMap {
                case _: InternalCommit if claimAndRest.size > 1 =>
                  sendTx(SeqLedgerItem(claimAndRest.tail)).whenAvailableLocally.map(_.commitTxResult.internalCommit).flatMap {
                    case _: InternalCommit =>
                      newNodeIdentity()
                    case nack =>
                      println(s"Failed to fully claim identity=`$identity`, $nack")
                      newNodeIdentity()
                  }
                case _: InternalCommit =>
                  newNodeIdentity()

//                case nack: InternalTempNack =>
//                  println(s"Failed to claim identity=`$identity`, temp_nack=$nack")
//                  Future.failed(DeleteKeyFileException("System temporarily busy, try again."))
                case res =>
                  println(s"Failed to claim identity=`$identity`, result=$res")
                  //it is possible the someone else will attempt to claim the same identity
                  //at the same time. If they interleave with this attempt it is possible
                  //they succeed to get the identity registered on the ledger
                  //this exception will then cause the keyfile to be deleted locking the
                  //identity out forever. TODO TODO
                  Future.failed(DeleteKeyFileException(s"Failed to claim identity=`$identity`"))
              }
            case Right(res) if !res.contains("fail") =>
              Future {
                Thread.sleep(5000)
              } flatMap (_ => newNodeIdentity())
            case x =>
              Future.failed(DeleteKeyFileException(s"Failed to claim identity=`$identity` ${x.toString}"))
          } andThen {
          case Failure(e: DeleteKeyFileException) =>
            println(s"Deleting the new keyfile on failure: ${e.getMessage}")
            keyPersister.deleteKey(identity, NodeIdTag.defaultTag)
        }
    }
  }

  def claim(
              claimUrl:String,
              details: SignerVerifierDetails,
              toIdentityLedgerOwner: NodeIdentity => Option[NodeIdentity],
              hostingNodeIdentity: NodeIdentity,

            )(implicit
              nodeIdentityManager: NodeIdentityManager,
              sendTx: SendTx,
              ec: ExecutionContext
            ): Future[NodeIdentity] = {

    val identity = details.nodeIdTag.nodeId
    require(details.nodeIdTag.tag == NodeIdTag.defaultTag, "Must use default tag to claim")

    Future {
      toIdentityLedgerOwner(hostingNodeIdentity) match {
        case Some(identityLedgerOwner) =>
          val claimTx = Claim(identityLedgerOwner.id, identity, details, Seq(IdentityRoleAttribute()))
          Utils.simplySignedIdentityLedgerItem(identityLedgerOwner, claimTx) map { claimLedgerItem =>
            Left(SeqLedgerItem(claimLedgerItem))
          }
        case None =>
          log.warn("181:CLAIMING REMOTE using only public key, all other signerverifier details will use defaults ")
          Future.fromTry(claimRemote(1, claimUrl, details).map(Right(_)))

      }
    }
      .flatten
      .flatMap {
        case Left(le) =>
        sendTx(le).whenAvailableLocally.map(_.commitTxResult.internalCommit).flatMap {
          case _: InternalCommit =>
            nodeIdentityManager(identity, details.nodeIdTag.tag, Seq.empty)

//          case nack: InternalTempNack =>
//            println(s"Failed to claim identity=`$identity`, temp_nack=$nack")
//            Future.failed(new RuntimeException("System temporarily busy, try again."))
          case res =>
            println(s"Failed to claim identity=`$identity`, result=$res")
            //it is possible the someone else will attempt to claim the same identity
            //at the same time. If they interleave with this attempt it is possible
            //they succeed to get the identity registered on the ledger
            //this exception will then cause the keyfile to be deleted locking the
            //identity out forever. TODO TODO
            Future.failed(new RuntimeException(s"Failed to claim identity=`$identity`"))
        }
        case Right(ok) if ok.contains("ok") =>
          //It might take some time for the remote txs' to sync to the local.
          Future {Thread.sleep(5000L) } flatMap { _ =>
            nodeIdentityManager(identity, details.nodeIdTag.tag, Seq.empty)
          }

        case x =>
          Future.failed(new RuntimeException(s"Failed to claim identity=`$identity` ${x.toString}"))
      }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy