loadComponent('JsonRequestClient'); $this->loadComponent('JsonRpcRequestClient'); //$this->Auth->allow(['add', 'edit']); $this->Auth->allow('index'); } public function index() { if($this->request->is('get')) { $method = $this->request->getQuery('method'); switch($method) { case 'getRunningUserTasks': return $this->getRunningUserTasks(); } return $this->returnJson(['state' => 'error', 'msg' => 'unknown method for get', 'details' => $method]); } else if($this->request->is('post')) { $jsonData = $this->request->input('json_decode'); //var_dump($jsonData); if($jsonData == NULL || !isset($jsonData->method)) { return $this->returnJson(['state' => 'error', 'msg' => 'parameter error']); } $method = $jsonData->method; switch($method) { case 'putTransaction': if(!isset($jsonData->transaction)) { return $this->returnJson(['state' => 'error', 'msg' => 'parameter error']); } else { return $this->putTransaction($jsonData->transaction); } case 'userDelete': return $this->userDelete($jsonData->user); case 'moveTransaction': return $this->moveTransaction($jsonData->pubkeys, $jsonData->memo, $jsonData->session_id); case 'checkUser': return $this->checkUser($jsonData->email, $jsonData->last_name); case 'getUsers' : return $this->getUsers($jsonData->page, $jsonData->limit); case 'getUserBalance': return $this->getUserBalance($jsonData->email, $jsonData->last_name); case 'errorInTransaction': return $this->errorInTransaction($jsonData); case 'updateReadNode': return $this->updateReadNode(); case 'addUser' : return $this->addUser($jsonData->user); } return $this->returnJson(['state' => 'error', 'msg' => 'unknown method for post', 'details' => $method]); } return $this->returnJson(['state' => 'error', 'msg' => 'no post or get']); } private function addUser($newUser) { $stateUsersTable = TableRegistry::getTableLocator()->get('StateUsers'); $entity = $stateUsersTable->newEntity(); $required_fields = ['first_name', 'last_name', 'email', 'public_key', 'disabled']; foreach($required_fields as $required_field) { if(!isset($newUser->$required_field)) { return $this->returnJson(['state' => 'error', 'msg' => 'missing required field in addUser', 'details' => $required_field]); } if('public_key' == $required_field) { $entity->$required_field = hex2bin($newUser->public_hex); } else { $entity->$required_field = $newUser->$required_field; } } if($stateUsersTable->save($entity)) { return $this->returnJson(['state' => 'success']); } else { return $this->returnJson(['state' => 'error', 'msg' => 'error saving state_user', 'details' => $entity->getErrors()]); } } // Called from login server like a cron job every 10 minutes or after sending transaction to hedera private function updateReadNode() { $this->autoRender = false; $response = $this->response->withType('application/json'); $transactionsTable = TableRegistry::getTableLocator()->get('Transactions'); $last_transaction_query = $transactionsTable->find('all')->order(['id' => 'DESC']); $last_transaction_id = 0; if(!$last_transaction_query->isEmpty()) { $last_transaction_id = $last_transaction_query->first()->id; } $last_known_sequence_number = $last_transaction_id; if($last_transaction_query->count() < $last_transaction_id) { $last_transaction_id = $last_transaction_query->count(); } //$last_transaction_id = 0; $group_alias = Configure::read('GroupAlias'); $result = (array)$this->JsonRpcRequestClient->request('getTransactions', ['groupAlias' => $group_alias, 'lastKnownSequenceNumber' => $last_transaction_id]); if(isset($result['state']) && $result['state'] == 'error') { return $this->returnJson(['state' => 'error', 'msg' => 'jsonrpc error', 'details' => ['return' => $result, 'groupAlias' => $group_alias]]); } /* example $result = json_decode("[ { \"record_type\":\"GRADIDO_TRANSACTION\", \"transaction\":{ \"version_number\":1, \"signature\":{ \"pubkey\":\"2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b\", \"signature\":\"aed6725baacabf903e51f92503d49fa7e6b93c6402d56d9e3784be9a3366a77459213d858af46b579287aba8b1b63d206febce18bc80cec6fa63da6289e56403\" }, \"signature_count\":1, \"hedera_transaction\":{ \"consensusTimestamp\":{ \"seconds\":1604392811, \"nanos\":172812 }, \"runningHash\":\"f9ccf04137be418c3117a28bb5add6dced9745bcab74b7a2f46c182c8c98eeabf0127c131d15ebea7d0ac376f5d2de45\", \"sequenceNumber\":94, \"runningHashVersion\":3 }, \"transaction_type\":\"ADD_USER\", \"add_user\":{ \"user\":\"2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b\" }, \"result\":\"result\", \"parts\":1, \"memo\":\"\" } }, { \"record_type\":\"GRADIDO_TRANSACTION\", \"transaction\":{ \"version_number\":1, \"signature\":{ \"pubkey\":\"8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d\", \"signature\":\"3134adcd6cbccee17c2db398f91b6b6bdd098b6306fb2fa213eb9eb5a322af9078acca4d8b0383d4e906f3139eb3369e7c1ef0f3ac5fec724be0d085ba44af0b\" }, \"signature_count\":2, \"hedera_transaction\":{ \"consensusTimestamp\":{ \"seconds\":1604392886, \"nanos\":1528 }, \"runningHash\":\"e1df5526331e3def11d6b652b8f248d20c250739b6eb98f1fe7b338901753d9d573a14601ba84f61318a48940b3c237a\", \"sequenceNumber\":95, \"runningHashVersion\":3 }, \"transaction_type\":\"ADD_USER\", \"add_user\":{ \"user\":\"8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d\" }, \"result\":\"result\", \"parts\":2, \"memo\":\"\" } }, { \"record_type\":\"SIGNATURES\", \"signature\":[ { \"pubkey\":\"2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b\", \"signature\":\"401717e768617c0f3311931c34a61e66ab362599a0e2a48ae7c4955645aec6573773985dafb84a11bfaf2bc12140c30b2f8c8ee094bc35d609bc56d15b4e9f04\" } ] }, { \"record_type\": \"GRADIDO_TRANSACTION\", \"transaction\":{ \"version_number\":1, \"signature\":{ \"pubkey\":\"2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b\", \"signature\":\"99665dee9f2b475e426a2f449d0dae61924f6cf025903666ff72f2c7ef1af27523ebcd5fb684d17813fe7906b2f8cfe5ef4bdbb264ebf3ef80363491d9b86807\" }, \"signature_count\":1, \"hedera_transaction\":{ \"consensusTimestamp\":{ \"seconds\":1604392904, \"nanos\":798541 }, \"runningHash\":\"f1fd03610a9788e9bac01e1efb8b99bafae450f9088cb940db954842e0799235c57d842be83d998e6c21786f77f967a7\", \"sequenceNumber\":96, \"runningHashVersion\":3 }, \"transaction_type\":\"GRADIDO_CREATION\", \"gradido_creation\":{ \"user\":\"8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d\", \"new_balance\":10000000, \"prev_transfer_rec_num\":0, \"amount\":10000000 }, \"result\":\"result\", \"parts\":1, \"memo\":\"\" } }, { \"record_type\": \"GRADIDO_TRANSACTION\", \"transaction\":{ \"version_number\":1, \"signature\":{ \"pubkey\":\"8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d\", \"signature\":\"90125e0cfce61397d50ed9ba6c5df4cd4e0cf6fee8b10c70fee2898765982570d9a1208c222981429ae3c229e3fd36c2bf2333518cd0a4f0515937822e499d0b\" }, \"signature_count\":1, \"hedera_transaction\":{ \"consensusTimestamp\":{ \"seconds\":1604392929, \"nanos\":52539 }, \"runningHash\":\"a4be8f54be4f806b61d31f6bd770d7742822f14f03ffe09c07f08bac3031a06d12de5e38fec5c307149c7faf6e9879b8\", \"sequenceNumber\":97, \"runningHashVersion\":3 }, \"transaction_type\":\"LOCAL_TRANSFER\", \"local_transfer\":{ \"sender\":{ \"user\":\"8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d\", \"new_balance\":9825500, \"prev_transfer_rec_num\":0 }, \"receiver\":{ \"user\":\"2ed28a1cf5e116d83615406bc577152221c2f774a5656f66a0e7540f7576d71b\", \"new_balance\":174500, \"prev_transfer_rec_num\":0 }, \"amount\":174500 }, \"result\":\"result\", \"parts\":1, \"memo\":\"\" } } ]", true);*/ $part_count = -1; $temp_record = new Record; $errors = []; foreach($result['blocks'] as $_record) { if(is_string($_record)) continue; $parse_result = $temp_record->parseRecord($_record); if($parse_result == true) { $sequenceNumber = $temp_record->getSequenceNumber(); if($part_count == -1) { $part_count = $temp_record->getPartCount(); } $part_count--; if($part_count == 0) { if($sequenceNumber > $last_known_sequence_number) { $finalize_result = $temp_record->finalize(); if($finalize_result !== true) { $errors[] = ['msg' => 'error in finalize', 'record' => $_record, 'details' => $finalize_result, 'sequenceNumber' => $sequenceNumber]; } } $temp_record = new Record; $part_count = -1; } } else { $temp_record = new Record; $part_count = -1; $errors[] = ['msg' => 'error in parse record', 'record' => $_record, 'details' => $parse_result]; } } if(count($errors)) { return $this->returnJson(['state' => 'error', 'msg' => 'error in parsing records', 'details' => $errors]); } return $this->returnJson(['state' => 'success']); } /* * payload.set("created", created); * payload.set("id", task_model->getID()); * payload.set("type", task_model->getTaskTypeString()); * payload.set("public_key", user_model->getPublicKeyHex()); * payload.set("error", error); * payload.set("errorMessage", errorDetails); */ //! \param $transactionCreated creation of transaction in timestamp in seconds //! -1 if transaction couldn't decode //! \param $transactionBodyBase64Sha256 generic hash from transaction body serialized and converted to base64 //! using sodium_crypto_generichash to calculate // hash also in base64 format //! \param $error short error name in user language //! \param $errorDetails more detailed error message in user language private function errorInTransaction($jsonData) { $stateErrorTable = TableRegistry::getTableLocator()->get('StateErrors'); $stateUsersTable = TableRegistry::getTableLocator()->get('StateUsers'); $transactionTypesTable = TableRegistry::getTableLocator()->get('TransactionTypes'); $stateError = $stateErrorTable->newEntity(); // $pubkey = hex2bin($jsonData->public_key); $user_query = $stateUsersTable->find('all')->select(['id'])->where(['public_key' => $pubkey]); if($user_query->count() != 1) { return $this->returnJson(['state' => 'error', 'msg' => 'user not found', 'details' => 'user pubkey hex:' . $jsonData->public_key]); } $stateError->state_user_id = $user_query->first()->id; //$stateError->transaction_type_id // TODO: // - show state errors in navi_notify.ctp $transaction_type_query = $transactionTypesTable->find('all')->select(['id'])->where(['name' => $jsonData->type]); if($transaction_type_query->count() != 1) { return $this->returnJson(['state' => 'error', 'msg' => 'transaction type not found', 'details' => 'transaction type name: ' . $jsonData->type]); } $stateError->transaction_type_id = $transaction_type_query->first()->id; $stateError->created = $jsonData->created; $stateError->message_json = json_encode(['task_id' => $jsonData->id, 'error' => $jsonData->error, 'errorMessage' => $jsonData->errorMessage]); if(!$stateErrorTable->save($stateError)) { $this->returnJsonSaveError($stateError, [ 'state' => 'error', 'msg' => 'error saving state_error in db', 'details' => json_encode($stateError->getErrors()) ]); } return $this->returnJson(['state' => 'success']); } private function sendEMailTransactionFailed($transaction, $reason_type) { $disable_email = Configure::read('disableEmail', false); if($disable_email) { return; } $transaction_body = $transaction->getTransactionBody(); $senderUser = $transaction->getFirstSigningUser(); if($transaction_body != null) { $transaction_type_name = $transaction_body->getTransactionTypeName(); if($transaction_type_name === 'transfer') { $senderUser = $transaction_body->getSpecificTransaction()->getSenderUser(); } } // send notification email $noReplyEmail = Configure::read('noReplyEmail'); if($senderUser) { try { $email = new Email(); $emailViewBuilder = $email->viewBuilder(); $emailViewBuilder->setTemplate('notificationTransactionFailed') ->setVars(['user' => $senderUser, 'transaction' => $transaction, 'reason' => $reason_type]); $receiverNames = $senderUser->getNames(); if($receiverNames == '' || $senderUser->email == '') { $this->addError('TransactionCreation::sendNotificationEmail', 'to email is empty for user: ' . $senderUser->id); return false; } $email->setFrom([$noReplyEmail => 'Gradido (nicht antworten)']) ->setTo([$senderUser->email => $senderUser->getNames()]) ->setSubject(__('Gradido Transaktion fehlgeschlagen!')) ->send(); } catch(Exception $e) { $this->addAdminError('JsonRequestController', 'sendEMailTransactionFailed', [$e->getMessage(), $reason_type], $senderUser->id); } } } private function putTransaction($transactionBase64) { $transaction = new Transaction($transactionBase64); //echo "new transaction\n$transactionBase64\n"; /*try { $transactionBin = sodium_base642bin($transactionBase64, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); $transaction = new Transaction($transactionBin); } catch(\SodiumException $e) { //echo 'exception: '. $e->getMessage(); return $this->returnJson(['state' => 'error', 'msg' => 'error decoding base 64', 'details' => $e->getMessage(), 'base64' => $transactionBase64]); }*/ //echo "after new transaction
"; if($transaction->hasErrors()) { $this->sendEMailTransactionFailed($transaction, 'parse'); return $this->returnJson(['state' => 'error', 'msg' => 'error parsing transaction', 'details' => $transaction->getErrors()]); } //echo "after check on errors
"; if(!$transaction->validate()) { //$transaction_details $this->sendEMailTransactionFailed($transaction, 'validate'); return $this->returnJsonSaveError($transaction, ['state' => 'error', 'msg' => 'error validate transaction', 'details' => $transaction->getErrors()]); } //echo "after validate
"; if ($transaction->save()) { // success return $this->returnJson(['state' => 'success']); } else { $this->sendEMailTransactionFailed($transaction, 'save'); return $this->returnJsonSaveError($transaction, [ 'state' => 'error', 'msg' => 'error saving transaction in db', 'details' => json_encode($transaction->getErrors()) ]); } } private function moveTransaction($pubkeys, $memo, $session_id) { //$pubkeys->sender //$pubkeys->receiver $stateUserTable = TableRegistry::getTableLocator()->get('StateUsers'); $user = $stateUserTable->find('all')->where(['public_key' => hex2bin($pubkeys->sender)])->contain(['StateBalances']); if(!$user->count()) { return $this->returnJson(['state' => 'not found', 'msg' => 'user not found or empty balance']); } $amountCent = $user->first()->state_balances[0]->amount; //var_dump($user->first()); $builderResult = TransactionTransfer::build( $amountCent, $memo, $pubkeys->receiver, $pubkeys->sender ); if($builderResult['state'] === 'success') { $http = new Client(); try { $loginServer = Configure::read('LoginServer'); $url = $loginServer['host'] . ':' . $loginServer['port']; $response = $http->post($url . '/checkTransaction', json_encode([ 'session_id' => $session_id, 'transaction_base64' => base64_encode($builderResult['transactionBody']->serializeToString()), 'balance' => $amountCent ]), ['type' => 'json']); $json = $response->getJson(); if($json['state'] != 'success') { if($json['msg'] == 'session not found') { return $this->returnJson(['state' => 'error', 'msg' => 'session not found']); } else { //$this->Flash->error(__('login server return error: ' . json_encode($json))); return $this->returnJson(['state' => 'error', 'msg' => 'login server return error', 'details' => $json]); } } else { return $this->returnJson(['state' => 'success']); } } catch(\Exception $e) { $msg = $e->getMessage(); //$this->Flash->error(__('error http request: ') . $msg); return $this->returnJson(['state' => 'error', 'msg' => 'error http request', 'details' => $msg]); } } else { return $this->returnJson(['state' => 'error', 'msg' => 'error building transaction']); } } private function userDelete($userPubkeyHex) { $stateUserTable = TableRegistry::getTableLocator()->get('StateUsers'); $user = $stateUserTable->find('all')->where(['public_key' => hex2bin($userPubkeyHex)]); if(!$user || $user->count == 0) { return $this->returnJson(['state' => 'error', 'msg' => 'user not found']); } } private function checkUser($email, $last_name) { $userTable = TableRegistry::getTableLocator()->get('Users'); $user = $userTable->find('all') ->where(['email' => $email]) ->contain([]) ->select(['first_name', 'last_name', 'email']); if(!$user->count()) { return $this->returnJson(['state' => 'not found', 'msg' => 'user not found']); } if($user->count() == 1 && $user->first()->last_name == $last_name) { return $this->returnJson(['state' => 'success']); } return $this->returnJson(['state' => 'not identical', 'user' => $user->toArray()]); } private function getUserBalance($email, $last_name) { $stateUserTable = TableRegistry::getTableLocator()->get('StateUsers'); $stateUsers = $stateUserTable->find('all')->where(['OR' => ['email' => $email, 'last_name' => $last_name]])->contain(['StateBalances']); $gdds = []; foreach($stateUsers as $stateUser) { foreach($stateUser->state_balances as $stateBalance) { if(!isset($gdds[$stateUser->email])) { $gdds[$stateUser->email] = []; } if(!isset($gdds[$stateUser->email][$stateUser->last_name])) { $gdds[$stateUser->email][$stateUser->last_name] = 0; } $gdds[$stateUser->email][$stateUser->last_name] += $stateBalance->amount; } } return $this->returnJson(['state' => 'success', 'gdds' => $gdds]); } private function getUsers($page, $count) { $userTable = TableRegistry::getTableLocator()->get('Users'); $this->paginate = [ 'limit' => $count, 'page' => $page ]; $usersQuery = $userTable->find('all') ->select(['first_name', 'last_name', 'email']) ->order(['id']); try { return $this->returnJson(['state' => 'success', 'users' => $this->paginate($usersQuery)]); } catch (Exception $ex) { return $this->returnJson(['state' => 'exception', 'msg' => 'error paginate users', 'details' => $ex->getMessage()]); } //return $this->returnJson(['state' => 'success', 'users' => $users->toArray()]); } private function getRunningUserTasks() { $session = $this->getRequest()->getSession(); $state_user_email = $session->read('StateUser.email'); $requestResult = $this->JsonRequestClient->getRunningUserTasks($state_user_email);; return $this->returnJson($requestResult); } private function returnJsonSaveError($transaction, $errorArray) { $json = json_encode($errorArray); $stateUserTable = TableRegistry::getTableLocator()->get('StateUsers'); $pub = $transaction->getFirstPublic(); $stateUserQuery = $stateUserTable ->find('all') ->where(['public_key' => $pub]) ->contain(false); if($stateUserQuery->count() == 1) { $stateErrorsTable = TableRegistry::getTableLocator()->get('StateErrors'); $stateErrorEntity = $stateErrorsTable->newEntity(); $stateErrorEntity->state_user_id = $stateUserQuery->first()->id; $stateErrorEntity->transaction_type_id = $transaction->getTransactionBody()->getTransactionTypeId(); $stateErrorEntity->message_json = $json; $stateErrorsTable->save($stateErrorEntity); } else { $errorArray['user_error'] = "user with $pub not found"; $json = json_encode($errorArray); } return $this->returnJsonEncoded($json); } }