diff --git a/src/Controller/AddressTypesController.php b/src/Controller/AddressTypesController.php index 92bfad6c8..348a8e9e6 100644 --- a/src/Controller/AddressTypesController.php +++ b/src/Controller/AddressTypesController.php @@ -2,6 +2,7 @@ namespace App\Controller; use App\Controller\AppController; +use Cake\I18n\Number; /** * AddressTypes Controller diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 9c43e6d69..de477a416 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -15,7 +15,8 @@ namespace App\Controller; use Cake\Controller\Controller; -use Cake\Event\Event; +//use Cake\Event\Event; +use Cake\ORM\TableRegistry; /** * Application Controller @@ -75,6 +76,25 @@ class AppController extends Controller * see https://book.cakephp.org/3.0/en/controllers/components/security.html */ //$this->loadComponent('Security'); + + + // load current balance + $session = $this->getRequest()->getSession(); + $state_user_id = $session->read('StateUser.id'); + if($state_user_id) { + $stateBalancesTable = TableRegistry::getTableLocator()->get('stateBalances'); + $stateBalanceEntry = $stateBalancesTable + ->find('all') + ->select('amount') + ->contain(false) + ->where(['state_user_id' => $state_user_id]); + if($stateBalanceEntry->count() == 1) { + //var_dump($stateBalanceEntry->first()); + $session->write('StateUser.balance', $stateBalanceEntry->first()->amount); + //echo "stateUser.balance: " . $session->read('StateUser.balance'); + } + } + //echo "initialize"; } /* public function beforeFilter(Event $event) diff --git a/src/Controller/Component/GradidoNumberComponent.php b/src/Controller/Component/GradidoNumberComponent.php new file mode 100644 index 000000000..417b1012a --- /dev/null +++ b/src/Controller/Component/GradidoNumberComponent.php @@ -0,0 +1,33 @@ +Auth->allow(['add', 'edit']); - $this->Auth->allow('index'); + $this->Auth->allow(['index', 'errorHttpRequest']); } /** * Index method @@ -58,8 +58,11 @@ class DashboardController extends AppController $loginServer = Configure::read('LoginServer'); $url = $loginServer['host'] . ':' . $loginServer['port']; //$url = 'http://***REMOVED***'; + $requestStart = microtime(true); $response = $http->get($url . '/login', ['session_id' => $session_id]); $json = $response->getJson(); + $requestEnd = microtime(true); + if(isset($json) && count($json) > 0) { @@ -70,13 +73,18 @@ class DashboardController extends AppController if($key === 'state') { continue; } $session->write('StateUser.' . $key, $value ); } + $transactionPendings = $json['Transaction.pending']; - $session->write('Transaction.pending', $transactionPendings); + //echo "read transaction pending: $transactionPendings
"; + $session->write('Transactions.pending', $transactionPendings); $session->write('session_id', $session_id); $stateUserTable = TableRegistry::getTableLocator()->get('StateUsers'); if($json['user']['public_hex'] != '') { $public_key_bin = hex2bin($json['user']['public_hex']); - $stateUserQuery = $stateUserTable->find('all')->where(['public_key' => $public_key_bin]); + $stateUserQuery = $stateUserTable + ->find('all') + ->where(['public_key' => $public_key_bin]) + ->contain(['StateBalances']); if($stateUserQuery->count() == 1) { $stateUser = $stateUserQuery->first(); if($stateUser->first_name != $json['user']['first_name'] || @@ -87,7 +95,11 @@ class DashboardController extends AppController $this->Flash->error(__('error updating state user ' . json_encode($stateUser->errors()))); } } - $session->write('StateUser.id', $stateUser['id']); + //var_dump($stateUser); + if(count($stateUser->state_balances) > 0) { + $session->write('StateUser.balance', $stateUser->state_balances[0]->amount); + } + $session->write('StateUser.id', $stateUser->id); //echo $stateUser['id']; } else { $newStateUser = $stateUserTable->newEntity(); @@ -109,6 +121,7 @@ class DashboardController extends AppController $this->set('user', $json['user']); //$this->set('json', $json); $this->set('timeUsed', microtime(true) - $startTime); + $this->set('requestTime', $requestEnd - $requestStart); } else { if($json['state'] === 'not found' ) { @@ -124,7 +137,7 @@ class DashboardController extends AppController } catch(\Exception $e) { $msg = $e->getMessage(); $this->Flash->error(__('error http request: ') . $msg); - + return $this->redirect(['controller' => 'Dashboard', 'action' => 'errorHttpRequest']); //continue; } } else { @@ -139,6 +152,13 @@ class DashboardController extends AppController return $this->redirect(Router::url('/', true) . 'account/', 303); } } + + public function errorHttpRequest() + { + $startTime = microtime(true); + $this->viewBuilder()->setLayout('frontend'); + $this->set('timeUsed', microtime(true) - $startTime); + } } diff --git a/src/Controller/StateBalancesController.php b/src/Controller/StateBalancesController.php index 20580fcf4..ae0f682e3 100644 --- a/src/Controller/StateBalancesController.php +++ b/src/Controller/StateBalancesController.php @@ -12,6 +12,13 @@ use App\Controller\AppController; */ class StateBalancesController extends AppController { + + public function initialize() + { + parent::initialize(); + //$this->Auth->allow(['add', 'edit']); + $this->Auth->allow(['overview']); + } /** * Index method * @@ -26,6 +33,11 @@ class StateBalancesController extends AppController $this->set(compact('stateBalances')); } + + public function overview() + { + $this->viewBuilder()->setLayout('frontend'); + } /** * View method diff --git a/src/Controller/StateErrorsController.php b/src/Controller/StateErrorsController.php new file mode 100644 index 000000000..98d4dc531 --- /dev/null +++ b/src/Controller/StateErrorsController.php @@ -0,0 +1,113 @@ +paginate = [ + 'contain' => ['StateUsers', 'TransactionTypes'] + ]; + $stateErrors = $this->paginate($this->StateErrors); + + $this->set(compact('stateErrors')); + } + + /** + * View method + * + * @param string|null $id State Error id. + * @return \Cake\Http\Response|null + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function view($id = null) + { + $stateError = $this->StateErrors->get($id, [ + 'contain' => ['StateUsers', 'TransactionTypes'] + ]); + + $this->set('stateError', $stateError); + } + + /** + * Add method + * + * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise. + */ + public function add() + { + $stateError = $this->StateErrors->newEntity(); + if ($this->request->is('post')) { + $stateError = $this->StateErrors->patchEntity($stateError, $this->request->getData()); + if ($this->StateErrors->save($stateError)) { + $this->Flash->success(__('The state error has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('The state error could not be saved. Please, try again.')); + } + $stateUsers = $this->StateErrors->StateUsers->find('list', ['limit' => 200]); + $transactionTypes = $this->StateErrors->TransactionTypes->find('list', ['limit' => 200]); + $this->set(compact('stateError', 'stateUsers', 'transactionTypes')); + } + + /** + * Edit method + * + * @param string|null $id State Error id. + * @return \Cake\Http\Response|null Redirects on successful edit, renders view otherwise. + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function edit($id = null) + { + $stateError = $this->StateErrors->get($id, [ + 'contain' => [] + ]); + if ($this->request->is(['patch', 'post', 'put'])) { + $stateError = $this->StateErrors->patchEntity($stateError, $this->request->getData()); + if ($this->StateErrors->save($stateError)) { + $this->Flash->success(__('The state error has been saved.')); + + return $this->redirect(['action' => 'index']); + } + $this->Flash->error(__('The state error could not be saved. Please, try again.')); + } + $stateUsers = $this->StateErrors->StateUsers->find('list', ['limit' => 200]); + $transactionTypes = $this->StateErrors->TransactionTypes->find('list', ['limit' => 200]); + $this->set(compact('stateError', 'stateUsers', 'transactionTypes')); + } + + /** + * Delete method + * + * @param string|null $id State Error id. + * @return \Cake\Http\Response|null Redirects to index. + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function delete($id = null) + { + $this->request->allowMethod(['post', 'delete']); + $stateError = $this->StateErrors->get($id); + if ($this->StateErrors->delete($stateError)) { + $this->Flash->success(__('The state error has been deleted.')); + } else { + $this->Flash->error(__('The state error could not be deleted. Please, try again.')); + } + + return $this->redirect(['action' => 'index']); + } +} diff --git a/src/Controller/TransactionCreationsController.php b/src/Controller/TransactionCreationsController.php index 245e3eb21..62b428ae4 100644 --- a/src/Controller/TransactionCreationsController.php +++ b/src/Controller/TransactionCreationsController.php @@ -4,7 +4,7 @@ namespace App\Controller; use App\Controller\AppController; use Cake\ORM\TableRegistry; use Cake\Routing\Router; -use Cake\I18n\Number; +//use Cake\I18n\Number; use Cake\Http\Client; use Cake\Core\Configure; @@ -27,6 +27,7 @@ class TransactionCreationsController extends AppController public function initialize() { parent::initialize(); + $this->loadComponent('GradidoNumber'); //$this->Auth->allow(['add', 'edit']); $this->Auth->allow('create'); } @@ -99,12 +100,9 @@ class TransactionCreationsController extends AppController $pubKeyHex = ''; $receiver = new ReceiverAmount(); - //echo 'amount: ' . $requestData['amount'] . '
'; - $floatAmount = floatval(Number::format($requestData['amount'], ['places' => 4, 'locale' => 'en_GB'])); - //echo 'set for receiver: ' . round($floatAmount * 10000) . '
'; - $receiver->setAmount(round($floatAmount * 10000)); - //echo 'after receiver amount set
'; - + + $receiver->setAmount($this->GradidoNumber->parseInputNumberToCentNumber($requestData['amount'])); + if(intval($requestData['receiver']) == 0) { if(strlen($requestData['receiver_pubkey_hex']) != 64) { $this->Flash->error(__('Invalid public Key, must contain 64 Character')); @@ -119,7 +117,9 @@ class TransactionCreationsController extends AppController } } if($pubKeyHex != '') { - $receiver->setEd25519ReceiverPubkey(hex2bin($pubKeyHex)); + $pubKeyBin = hex2bin($pubKeyHex); + + $receiver->setEd25519ReceiverPubkey($pubKeyBin); //var_dump($requestData); $creationDate = new TimestampSeconds(); diff --git a/src/Controller/TransactionJsonRequestHandlerController.php b/src/Controller/TransactionJsonRequestHandlerController.php new file mode 100644 index 000000000..eb0573f31 --- /dev/null +++ b/src/Controller/TransactionJsonRequestHandlerController.php @@ -0,0 +1,68 @@ +Auth->allow(['add', 'edit']); + $this->Auth->allow('index'); + } + + + public function index() + { + if($this->request->is('post')) { + $jsonData = $this->request->input('json_decode'); + //var_dump($jsonData); + if($jsonData == NULL || !isset($jsonData->method) || !isset($jsonData->transaction)) { + return $this->returnJson(['state' => 'error', 'msg' => 'parameter error']); + } + $method = $jsonData->method; + switch($method) { + case 'putTransaction': return $this->putTransaction($jsonData->transaction); + } + return $this->returnJson(['state' => 'error', 'msg' => 'unknown method', 'details' => $method]); + } + return $this->returnJson(['state' => 'error', 'msg' => 'no post']); + } + + private function putTransaction($transactionBase64) { + $transaction = new Transaction($transactionBase64); + if($transaction->hasErrors()) { + return $this->returnJson(['state' => 'error', 'msg' => 'error parsing transaction', 'details' => $transaction->getErrors()]); + } + if(!$transaction->validate()) { + return $this->returnJson(['state' => 'error', 'msg' => 'error validate transaction', 'details' => $transaction->getErrors()]); + } + + if ($transaction->save()) { + // success + return $this->returnJson(['state' => 'success']); + } else { + return $this->returnJson([ + 'state' => 'error', + 'msg' => 'error saving transaction in db', + 'details' => json_encode($transaction->getErrors()) + ]); + } + + return $this->returnJson(['state' => 'success']); + } + + +} \ No newline at end of file diff --git a/src/Form/CreationForm.php b/src/Form/CreationForm.php index 1e59088ea..46363de27 100644 --- a/src/Form/CreationForm.php +++ b/src/Form/CreationForm.php @@ -35,7 +35,12 @@ class CreationForm extends Form 'rule' => ['maxLength', 150], 'message' => 'max 150 character' ]) - ->ascii('memo', __('Only Ascii Character allowed')) + //->alphaNumeric('memo', __('Only Alpha Numeric Character allowed')) + ->add('memo', 'custom', [ + 'rule' => 'alphaNumeric', + 'provider' => 'custom', + 'message' => __('Only Alpha Numeric Character allowed') + ]) ->allowEmptyString('memo', null, 'create') /*->add('receiver_pubkey_hex', 'custom', [ 'rule' => 'hexKey64', diff --git a/src/Model/Entity/StateError.php b/src/Model/Entity/StateError.php new file mode 100644 index 000000000..448e9ccb3 --- /dev/null +++ b/src/Model/Entity/StateError.php @@ -0,0 +1,37 @@ + true, + 'transaction_type_id' => true, + 'created' => true, + 'message_json' => true, + 'state_user' => true, + 'transaction_type' => true + ]; +} diff --git a/src/Model/Table/StateErrorsTable.php b/src/Model/Table/StateErrorsTable.php new file mode 100644 index 000000000..62893b7bf --- /dev/null +++ b/src/Model/Table/StateErrorsTable.php @@ -0,0 +1,88 @@ +setTable('state_errors'); + $this->setDisplayField('id'); + $this->setPrimaryKey('id'); + + $this->addBehavior('Timestamp'); + + $this->belongsTo('StateUsers', [ + 'foreignKey' => 'state_user_id', + 'joinType' => 'INNER' + ]); + $this->belongsTo('TransactionTypes', [ + 'foreignKey' => 'transaction_type_id', + 'joinType' => 'INNER' + ]); + } + + /** + * Default validation rules. + * + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator + */ + public function validationDefault(Validator $validator) + { + $validator + ->integer('id') + ->allowEmptyString('id', null, 'create'); + + $validator + ->scalar('message_json') + ->requirePresence('message_json', 'create') + ->notEmptyString('message_json'); + + return $validator; + } + + /** + * Returns a rules checker object that will be used for validating + * application integrity. + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ + public function buildRules(RulesChecker $rules) + { + $rules->add($rules->existsIn(['state_user_id'], 'StateUsers')); + $rules->add($rules->existsIn(['transaction_type_id'], 'TransactionTypes')); + + return $rules; + } +} diff --git a/src/Model/Transactions/Transaction.php b/src/Model/Transactions/Transaction.php index 1a16516cd..1ae38ffbc 100644 --- a/src/Model/Transactions/Transaction.php +++ b/src/Model/Transactions/Transaction.php @@ -19,11 +19,19 @@ class Transaction extends TransactionBase { private $mTransactionBody = null; public function __construct($base64Data) { - $transactionBin = base64_decode($base64Data, true); - + //$transactionBin = base64_decode($base64Data, true); + //if($transactionBin == false) + //sodium_base64_VARIANT_URLSAFE_NO_PADDING + try { + $transactionBin = sodium_base642bin($base64Data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); + } catch(\SodiumException $e) { + $this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data); + return; + } + //*/ if($transactionBin == false) { //$this->addError('base64 decode failed'); - $this->addError('Transaction', 'base64 decode error'); + $this->addError('Transaction', 'base64 decode error: ' . $base64Data); } else { $this->mProtoTransaction = new \Model\Messages\Gradido\Transaction(); try { @@ -52,7 +60,14 @@ class Transaction extends TransactionBase { } public function validate() { - $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair(); + $sigMap = $this->mProtoTransaction->getSigMap(); + if(!$sigMap) { + $this->addError('Transaction', 'signature map is zero'); + return false; + } + //var_dump($sigMap); + //die(); + $sigPairs = $sigMap->getSigPair(); $bodyBytes = $this->mProtoTransaction->getBodyBytes(); @@ -98,7 +113,7 @@ class Transaction extends TransactionBase { //signature pubkey $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair(); - echo "sigPairs: "; var_dump($sigPairs); + //echo "sigPairs: "; var_dump($sigPairs); $signatureEntitys = []; foreach($sigPairs as $sigPair) { $signatureEntity = $transactionsSignaturesTable->newEntity(); diff --git a/src/Model/Transactions/TransactionBase.php b/src/Model/Transactions/TransactionBase.php index 2eb0f6dc7..4b1ba210e 100644 --- a/src/Model/Transactions/TransactionBase.php +++ b/src/Model/Transactions/TransactionBase.php @@ -40,4 +40,30 @@ class TransactionBase { return NULL; } + + protected function updateStateBalance($stateUserId, $newAmountCent) { + $stateBalancesTable = TableRegistry::getTableLocator()->get('stateBalances'); + $stateBalanceQuery = $stateBalancesTable + ->find('all') + ->select(['amount']) + ->contain(false) + ->where(['state_user_id' => $stateUserId]);//->first(); + //debug($stateBalanceQuery); + + if($stateBalanceQuery->count() > 0) { + $stateBalanceEntry = $stateBalanceEntry->first(); + $stateBalanceEntry->amount += $newAmountCent; + } else { + $stateBalanceEntry = $stateBalancesTable->newEntity(); + $stateBalanceEntry->state_user_id = $stateUserId; + $stateBalanceEntry->amount = $newAmountCent; + } + + if(!$stateBalancesTable->save($stateBalanceEntry)) { + $errors = $stateBalanceEntry->getErrors(); + $this->addError('TransactionBase::updateStateBalance', 'error saving state balance with: ' . json_encode($errors)); + return false; + } + return true; + } } \ No newline at end of file diff --git a/src/Model/Transactions/TransactionBody.php b/src/Model/Transactions/TransactionBody.php index 98120b28d..aeea7aff9 100644 --- a/src/Model/Transactions/TransactionBody.php +++ b/src/Model/Transactions/TransactionBody.php @@ -11,7 +11,15 @@ class TransactionBody extends TransactionBase { public function __construct($bodyBytes) { $this->mProtoTransactionBody = new \Model\Messages\Gradido\TransactionBody(); - $this->mProtoTransactionBody->mergeFromString($bodyBytes); + try { + $this->mProtoTransactionBody->mergeFromString($bodyBytes); + // cannot catch Exception with cakePHP, I don't know why + } catch(\Google\Protobuf\Internal\GPBDecodeException $e) { + //var_dump($e); + $this->addError('TransactionBody', $e->getMessage()); + return; + } + switch($this->mProtoTransactionBody->getData()) { case 'creation' : $this->mSpecificTransaction = new TransactionCreation($this->mProtoTransactionBody->getCreation()); break; case 'transfer' : $this->mSpecificTransaction = new TransactionTransfer($this->mProtoTransactionBody->getTransfer()); break; @@ -20,7 +28,7 @@ class TransactionBody extends TransactionBase { public function validate($sigPairs) { // check if creation time is in the past - if($this->mProtoTransactionBody->getCreated() > time()) { + if($this->mProtoTransactionBody->getCreated()->getSeconds() > time()) { $this->addError('TransactionBody::validate', 'Transaction were created in the past!'); return false; } diff --git a/src/Model/Transactions/TransactionCreation.php b/src/Model/Transactions/TransactionCreation.php index 554f9ce00..1538f9366 100644 --- a/src/Model/Transactions/TransactionCreation.php +++ b/src/Model/Transactions/TransactionCreation.php @@ -40,22 +40,36 @@ class TransactionCreation extends TransactionBase { } // check if creation threshold for this month isn't reached + + //$identHashBin = sprintf("%0d", $this->getIdentHash()); + // padding with zero in case hash is smaller than 32 bytes, static length binary field in db + $identHashBin = pack('a32', $this->getIdentHash()); + $existingCreations = $this->transactionCreationsTable ->find('all') ->group('ident_hash') - ->where(['ident_hash' => $this->getIdentHash()]); + ->where(['ident_hash' => $identHashBin]); $existingCreations->select(['amount_sum' => $existingCreations->func()->sum('amount')]); - debug($existingCreations); + $existingCreations->matching('Transactions', function ($q) { + return $q->where(['EXTRACT(YEAR_MONTH FROM Transactions.received) LIKE EXTRACT(YEAR_MONTH FROM NOW())']); + }); + //debug($existingCreations); if($existingCreations->count() > 0) { - var_dump($existingCreations->toArray()); - } + //var_dump($existingCreations->toArray()); + //echo "amount sum: " . $existingCreations->first()->amount_sum . "\n"; + if($this->getAmount() + $existingCreations->first()->amount_sum > 10000000) { + $this->addError('TransactionCreation::validate', 'Creation more than 1000 gr per Month not allowed'); + return false; + } + } + //die("\n"); return true; } - public function save($transaction_id, $firstPublic) { + public function save($transaction_id, $firstPublic) + { $transactionCreationEntity = $this->transactionCreationsTable->newEntity(); - $transactionCreationEntity->transaction_id = $transaction_id; // state user id @@ -72,7 +86,12 @@ class TransactionCreation extends TransactionBase { $this->addError('TransactionCreation::save', 'error saving transactionCreation with errors: ' . json_encode($transactionCreationEntity->getErrors())); return false; } - + $receiverUser = $this->getStateUserId($this->getReceiverPublic()); + // update state balance + if(!$this->updateStateBalance($receiverUser, $this->getAmount())) { + return false; + } + return true; } diff --git a/src/Model/Validation/TransactionValidation.php b/src/Model/Validation/TransactionValidation.php index aba5828a1..a3463cd5b 100644 --- a/src/Model/Validation/TransactionValidation.php +++ b/src/Model/Validation/TransactionValidation.php @@ -30,4 +30,11 @@ class TransactionValidation } return false; } + + public static function alphaNumeric($value, array $context) { + if(preg_match('/^[a-zA-Z0-9äöüÄÖÜß _-]*$/', $value)) { + return true; + } + return false; + } } \ No newline at end of file diff --git a/src/Template/Dashboard/error_http_request.ctp b/src/Template/Dashboard/error_http_request.ctp new file mode 100644 index 000000000..cdbbec19b --- /dev/null +++ b/src/Template/Dashboard/error_http_request.ctp @@ -0,0 +1,14 @@ +assign('title', __('Error, Please try again')); +?> + +
+ +
diff --git a/src/Template/Dashboard/index.ctp b/src/Template/Dashboard/index.ctp index aec371855..1cc2b405d 100644 --- a/src/Template/Dashboard/index.ctp +++ b/src/Template/Dashboard/index.ctp @@ -11,6 +11,9 @@ if(isset($user)) { } $this->assign('title', __('Willkommen') . ' ' . $user['first_name'] . ' ' . $user['last_name']); ?> + + ms +

Gradido ...

diff --git a/src/Template/Layout/frontend.ctp b/src/Template/Layout/frontend.ctp index d393355b4..0f03e7899 100644 --- a/src/Template/Layout/frontend.ctp +++ b/src/Template/Layout/frontend.ctp @@ -1,5 +1,6 @@ getRequest()->getSession(); $transactionPendings = $session->read('Transactions.pending'); +$balance = $session->read('StateUser.balance'); +//echo "balance: $balance
"; +if(!isset($balance)) { + $balance = 0; +} +//echo "balance: $balance
"; //echo "transactions pending: " . $transactionPendings; ?> @@ -45,16 +54,22 @@ $transactionPendings = $session->read('Transactions.pending');