gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-bank] branch master updated (a232ea1 -> bda6379)


From: gnunet
Subject: [taler-bank] branch master updated (a232ea1 -> bda6379)
Date: Mon, 17 Feb 2020 20:01:37 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository bank.

    from a232ea1  allow free-form withdrawal amounts
     new a81fd7a  editor
     new bda6379  accounts API

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .vscode/settings.json                              |   2 +-
 .../app/management/commands/add_bank_account.py    |   2 +-
 .../management/commands/changepassword_unsafe.py   |   6 +-
 talerbank/app/migrations/0001_initial.py           | 104 +++++++++++---
 talerbank/app/models.py                            |   3 +-
 talerbank/app/tests.py                             |   8 +-
 talerbank/app/urls.py                              |  50 ++++++-
 talerbank/app/views.py                             | 154 +++++++++++++++++++--
 talerbank/settings.py                              |   4 +-
 9 files changed, 284 insertions(+), 49 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index da18bc7..393d090 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
 {
     "python.linting.pylintEnabled": false,
-    "python.linting.pep8Enabled": false,
+    "python.linting.pycodestyleEnabled": false,
     "python.linting.enabled": false,
     "python.formatting.provider": "yapf",
     "python.linting.banditEnabled": false,
diff --git a/talerbank/app/management/commands/add_bank_account.py 
b/talerbank/app/management/commands/add_bank_account.py
index b0e6513..cf01162 100644
--- a/talerbank/app/management/commands/add_bank_account.py
+++ b/talerbank/app/management/commands/add_bank_account.py
@@ -35,6 +35,7 @@ import uuid
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.INFO)
 
+
 class Command(BaseCommand):
     help = "Add bank accounts."
 
@@ -67,4 +68,3 @@ class Command(BaseCommand):
             )
         else:
             print(f"Bank account {accountname} already exists.")
-
diff --git a/talerbank/app/management/commands/changepassword_unsafe.py 
b/talerbank/app/management/commands/changepassword_unsafe.py
index d2f88d1..56a730f 100644
--- a/talerbank/app/management/commands/changepassword_unsafe.py
+++ b/talerbank/app/management/commands/changepassword_unsafe.py
@@ -35,6 +35,7 @@ import uuid
 LOGGER = logging.getLogger(__name__)
 LOGGER.setLevel(logging.INFO)
 
+
 class Command(BaseCommand):
     help = "Add bank accounts."
 
@@ -59,10 +60,7 @@ class Command(BaseCommand):
             existing_user.set_password(password)
             existing_user.save()
         except User.DoesNotExist:
-            print(
-                f"Account {accountname} does not exist"
-            )
+            print(f"Account {accountname} does not exist")
             sys.exit(1)
         else:
             print(f"Password for {accountname} changed")
-
diff --git a/talerbank/app/migrations/0001_initial.py 
b/talerbank/app/migrations/0001_initial.py
index 73a5206..a25b205 100644
--- a/talerbank/app/migrations/0001_initial.py
+++ b/talerbank/app/migrations/0001_initial.py
@@ -17,37 +17,97 @@ class Migration(migrations.Migration):
 
     operations = [
         migrations.CreateModel(
-            name='BankAccount',
+            name="BankAccount",
             fields=[
-                ('is_public', models.BooleanField(default=False)),
-                ('account_no', models.AutoField(primary_key=True, 
serialize=False)),
-                ('balance', 
talerbank.app.models.SignedAmountField(default=talerbank.app.models.get_zero_signed_amount)),
-                ('user', 
models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, 
to=settings.AUTH_USER_MODEL)),
+                ("is_public", models.BooleanField(default=False)),
+                ("account_no", models.AutoField(primary_key=True, 
serialize=False)),
+                (
+                    "balance",
+                    talerbank.app.models.SignedAmountField(
+                        default=talerbank.app.models.get_zero_signed_amount
+                    ),
+                ),
+                (
+                    "user",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='TalerWithdrawOperation',
+            name="TalerWithdrawOperation",
             fields=[
-                ('withdraw_id', models.UUIDField(default=uuid.uuid4, 
editable=False, primary_key=True, serialize=False)),
-                ('amount', talerbank.app.models.AmountField(default=False)),
-                ('selection_done', models.BooleanField(default=False)),
-                ('withdraw_done', models.BooleanField(default=False)),
-                ('selected_reserve_pub', models.TextField(null=True)),
-                ('selected_exchange_account', models.ForeignKey(null=True, 
on_delete=django.db.models.deletion.CASCADE, 
related_name='selected_exchange_account', to='app.BankAccount')),
-                ('withdraw_account', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='withdraw_account', to='app.BankAccount')),
+                (
+                    "withdraw_id",
+                    models.UUIDField(
+                        default=uuid.uuid4,
+                        editable=False,
+                        primary_key=True,
+                        serialize=False,
+                    ),
+                ),
+                ("amount", talerbank.app.models.AmountField(default=False)),
+                ("selection_done", models.BooleanField(default=False)),
+                ("confirmation_done", models.BooleanField(default=False)),
+                ("aborted", models.BooleanField(default=False)),
+                ("selected_reserve_pub", models.TextField(null=True)),
+                (
+                    "selected_exchange_account",
+                    models.ForeignKey(
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="selected_exchange_account",
+                        to="app.BankAccount",
+                    ),
+                ),
+                (
+                    "withdraw_account",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="withdraw_account",
+                        to="app.BankAccount",
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='BankTransaction',
+            name="BankTransaction",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, 
serialize=False, verbose_name='ID')),
-                ('amount', talerbank.app.models.AmountField(default=False)),
-                ('subject', models.CharField(default='(no subject given)', 
max_length=200)),
-                ('date', models.DateTimeField(auto_now=True, db_index=True)),
-                ('cancelled', models.BooleanField(default=False)),
-                ('request_uid', models.CharField(max_length=128, unique=True)),
-                ('credit_account', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='credit_account', to='app.BankAccount')),
-                ('debit_account', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='debit_account', to='app.BankAccount')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("amount", talerbank.app.models.AmountField(default=False)),
+                (
+                    "subject",
+                    models.CharField(default="(no subject given)", 
max_length=200),
+                ),
+                ("date", models.DateTimeField(auto_now=True, db_index=True)),
+                ("cancelled", models.BooleanField(default=False)),
+                ("request_uid", models.CharField(max_length=128, unique=True)),
+                (
+                    "credit_account",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="credit_account",
+                        to="app.BankAccount",
+                    ),
+                ),
+                (
+                    "debit_account",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="debit_account",
+                        to="app.BankAccount",
+                    ),
+                ),
             ],
         ),
     ]
diff --git a/talerbank/app/models.py b/talerbank/app/models.py
index 3833c22..5c4c9ac 100644
--- a/talerbank/app/models.py
+++ b/talerbank/app/models.py
@@ -205,7 +205,8 @@ class TalerWithdrawOperation(models.Model):
         related_name="withdraw_account",
     )
     selection_done = models.BooleanField(default=False)
-    withdraw_done = models.BooleanField(default=False)
+    confirmation_done = models.BooleanField(default=False)
+    aborted = models.BooleanField(default=False)
     selected_exchange_account = models.ForeignKey(
         BankAccount,
         null=True,
diff --git a/talerbank/app/tests.py b/talerbank/app/tests.py
index ad792b7..abbf9f3 100644
--- a/talerbank/app/tests.py
+++ b/talerbank/app/tests.py
@@ -385,9 +385,10 @@ class AddIncomingTestCase(TestCase):
     def test_add_incoming(self):
         client = Client()
         request_body = dict(
-                reserve_pub="TESTWTID",
-                amount=f"{settings.TALER_CURRENCY}:1.0",
-                debit_account="payto://x-taler-bank/bank_user")
+            reserve_pub="TESTWTID",
+            amount=f"{settings.TALER_CURRENCY}:1.0",
+            debit_account="payto://x-taler-bank/bank_user",
+        )
         response = client.post(
             reverse("twg-add-incoming", urlconf=urls, args=["user_user"]),
             data=json.dumps(request_body),
@@ -430,7 +431,6 @@ class HistoryTestCase(TestCase):
         clear_db()
 
     def test_history(self):
-
         def histquery(**urlargs):
             response = self.client.get(
                 reverse("history", urlconf=urls),
diff --git a/talerbank/app/urls.py b/talerbank/app/urls.py
index e929125..c2a524c 100644
--- a/talerbank/app/urls.py
+++ b/talerbank/app/urls.py
@@ -23,23 +23,63 @@ from django.views.generic.base import RedirectView
 from django.contrib.auth import views as auth_views
 from . import views
 
+# These paths are part of the GNU Taler wire gatweay API
 taler_wire_gateway_patterns = [
     path("<str:acct_id>/", views.twg_base, name="twg-base"),
-    path("<str:acct_id>/admin/add-incoming", views.twg_add_incoming, 
name="twg-add-incoming"),
-    path("<str:acct_id>/history/incoming", views.twg_history_incoming, 
name="twg-history-incoming"),
-    path("<str:acct_id>/history/outgoing", views.twg_history_outgoing, 
name="twg-history-outgoing"),
+    path(
+        "<str:acct_id>/admin/add-incoming",
+        views.twg_add_incoming,
+        name="twg-add-incoming",
+    ),
+    path(
+        "<str:acct_id>/history/incoming",
+        views.twg_history_incoming,
+        name="twg-history-incoming",
+    ),
+    path(
+        "<str:acct_id>/history/outgoing",
+        views.twg_history_outgoing,
+        name="twg-history-outgoing",
+    ),
     path("<str:acct_id>/transfer", views.twg_transfer, name="twg-transfer"),
 ]
 
+# These paths are part of the bank integration API
 taler_bank_api_patterns = [
+    path(
+        "withdrawal-operation/<str:withdraw_id>",
+        views.register_headless,
+        name="tba-withdrawal-operation",
+    ),
+]
+
+taler_bank_accounts_api_patterns = [
+    path("accounts/<str:acct_id>/balance", views.bank_accounts_api_balance),
+    path(
+        "accounts/<str:acct_id>/withdrawals", 
views.bank_accounts_api_create_withdrawal
+    ),
+    path(
+        "accounts/<str:acct_id>/withdrawals/<str:wid>",
+        views.bank_accounts_api_get_withdrawal,
+    ),
+    path(
+        "accounts/<str:acct_id>/withdrawals/<str:wid>/confirm",
+        views.bank_accounts_api_confirm_withdrawal,
+    ),
+    path(
+        "accounts/<str:acct_id>/withdrawals/<str:wid>/abort",
+        views.bank_accounts_api_abort_withdrawal,
+    ),
     path("testing/withdraw", views.withdraw_headless, name="testing-withdraw"),
-    path("testing/withdraw-uri", views.withdraw_headless_uri, 
name="testing-withdraw-uri"),
+    path(
+        "testing/withdraw-uri", views.withdraw_headless_uri, 
name="testing-withdraw-uri"
+    ),
     path("testing/register", views.register_headless, 
name="testing-withdraw-register"),
-    path("withdrawal-operation/<str:withdraw_id>", views.register_headless, 
name="tba-withdrawal-operation"),
 ]
 
 urlpatterns = [
     path("taler-bank-api/", include(taler_bank_api_patterns)),
+    path("", include(taler_bank_accounts_api_patterns)),
     path("taler-wire-gateway/", include(taler_wire_gateway_patterns)),
     path("", RedirectView.as_view(pattern_name="profile"), name="index"),
     path("favicon.ico", views.ignore),
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index 16a4b06..2e74f8f 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -408,12 +408,13 @@ def internal_register(request):
 
     # Registration goes through.
     with transaction.atomic():
+        bank_internal_account = BankAccount.objects.get(account_no=1)
+
         user = User.objects.create_user(username=username, password=password)
         user_account = BankAccount(user=user)
         user_account.save()
 
         # Give the user their joining bonus
-        bank_internal_account = BankAccount.objects.get(account_no=1)
         wire_transfer(
             Amount(settings.TALER_CURRENCY, 100, 0),
             bank_internal_account,
@@ -835,7 +836,9 @@ def twg_transfer(request, user_account, acct_id):
 
     subject = f"{wtid} {exchange_base_url}"
 
-    wtrans = wire_transfer(amount, exchange_account, credit_account, subject, 
request_uid)
+    wtrans = wire_transfer(
+        amount, exchange_account, credit_account, subject, request_uid
+    )
 
     return JsonResponse(
         {
@@ -1015,7 +1018,7 @@ def api_withdraw_operation(request, withdraw_id):
         selected_reserve_pub = data.get("reserve_pub")
         if not isinstance(selected_reserve_pub, str):
             return JsonResponse(dict(error="reserve_pub must be a string"), 
status=400)
-        if op.selection_done or op.withdraw_done:
+        if op.selection_done or op.confirmation_done:
             if (
                 op.selected_exchange_account != exchange_account
                 or op.selected_reserve_pub != selected_reserve_pub
@@ -1026,17 +1029,26 @@ def api_withdraw_operation(request, withdraw_id):
                 )
             # No conflict, same data!
             return JsonResponse(dict(), status=200)
-        op.selected_exchange_account = exchange_account
-        op.selected_reserve_pub = selected_reserve_pub
-        op.selection_done = True
-        op.save()
+        with transaction.atomic():
+            op.selected_exchange_account = exchange_account
+            op.selected_reserve_pub = selected_reserve_pub
+            if op.confirmation_done and not op.selection_done:
+                # Confirmation already happened, we still need to transfer 
funds!
+                wire_transfer(
+                    op.amount,
+                    user_account,
+                    op.selected_exchange_account,
+                    op.selected_reserve_pub,
+                )
+            op.selection_done = True
+            op.save()
         return JsonResponse(dict(), status=200)
     elif request.method == "GET":
         host = request.get_host()
         return JsonResponse(
             dict(
                 selection_done=op.selection_done,
-                transfer_done=op.withdraw_done,
+                transfer_done=op.confirmation_done,
                 amount=op.amount.stringify(),
                 wire_types=["x-taler-bank"],
                 
sender_wire=f"payto://x-taler-bank/{host}/{op.withdraw_account.user.username}",
@@ -1106,7 +1118,7 @@ def confirm_withdrawal(request, withdraw_id):
     op = TalerWithdrawOperation.objects.get(withdraw_id=withdraw_id)
     if not op.selection_done:
         raise Exception("invalid state (withdrawal parameter selection not 
done)")
-    if op.withdraw_done:
+    if op.confirmation_done:
         return redirect("profile")
     if request.method == "POST":
         hashed_attempt = hash_answer(request.POST.get("pin_0", ""))
@@ -1119,7 +1131,7 @@ def confirm_withdrawal(request, withdraw_id):
             )
             request.session["captcha_failed"] = True, False, "Wrong CAPTCHA 
answer."
             return redirect("withdraw-confirm", withdraw_id=withdraw_id)
-        op.withdraw_done = True
+        op.confirmation_done = True
         op.save()
         wire_transfer(
             op.amount,
@@ -1218,3 +1230,125 @@ def wire_transfer(amount, debit_account, 
credit_account, subject, request_uid=No
         transaction_item.save()
 
     return transaction_item
+
+
+@csrf_exempt
+@require_GET
+@login_via_headers
+def bank_accounts_api_balance(request, user_account, acct_id):
+    """
+    Query the balance for an account.
+    """
+    acct = user_account.bankaccount
+
+    if acct_id != user_account.username:
+        # FIXME: respond nicely
+        raise Exception(
+            f"credentials do not match URL ('{acct_id}' vs 
'{user_account.username}')"
+        )
+
+    return JsonResponse(dict(balance=acct.balance.stringify()))
+
+
+@csrf_exempt
+@require_POST
+@login_via_headers
+def bank_accounts_api_create_withdrawal(request, user, acct_id):
+    user_account = BankAccount.objects.get(user=user)
+
+    if acct_id != user_account.user.username:
+        # FIXME: respond nicely
+        raise Exception(
+            f"credentials do not match URL ('{acct_id}' vs 
'{user_account.username}')"
+        )
+
+    data = WithdrawHeadlessUri(json.loads(decode_body(request)))
+    amount = Amount.parse(data.get("amount"))
+    withdraw_amount = SignedAmount(True, amount)
+    debt_threshold = SignedAmount.parse(settings.TALER_MAX_DEBT)
+    user_balance = user_account.balance
+    if user_balance - withdraw_amount < -debt_threshold:
+        raise DebitLimitException(
+            f"Aborting payment initiated by '{user_account.user.username}', 
debit limit {debt_threshold} crossed."
+        )
+    op = TalerWithdrawOperation(amount=amount, withdraw_account=user_account)
+    op.save()
+    host = request.get_host()
+    taler_withdraw_uri = f"taler://withdraw/{host}/-/{op.withdraw_id}"
+    return JsonResponse(
+        {"taler_withdraw_uri": taler_withdraw_uri, "withdrawal_id": 
op.withdraw_id}
+    )
+
+
+@csrf_exempt
+@require_GET
+@login_via_headers
+def bank_accounts_api_get_withdrawal(request, user, acct_id, wid):
+    user_account = BankAccount.objects.get(user=user)
+    if acct_id != user_account.user.username:
+        # FIXME: respond nicely
+        raise Exception(
+            f"credentials do not match URL ('{acct_id}' vs 
'{user_account.username}')"
+        )
+    op = TalerWithdrawOperation.objects.get(withdraw_id=wid)
+    selected_exchange_account = None
+    if op.selected_exchange_account:
+        selected_exchange_account = op.selected_exchange_account.user.name
+    return JsonResponse(
+        {
+            "amount": op.amount.stringify(),
+            "selection_done": op.selection_done,
+            "confirmation_done": op.confirmation_done,
+            "selected_reserve_pub": op.selected_reserve_pub,
+            "selected_exchange_account": selected_exchange_account,
+            "aborted": op.aborted,
+        }
+    )
+
+
+@csrf_exempt
+@require_POST
+@login_via_headers
+def bank_accounts_api_abort_withdrawal(request, user, acct_id, wid):
+    user_account = BankAccount.objects.get(user=user)
+    if acct_id != user_account.user.username:
+        # FIXME: respond nicely
+        raise Exception(
+            f"credentials do not match URL ('{acct_id}' vs 
'{user_account.username}')"
+        )
+    op = TalerWithdrawOperation.objects.get(withdraw_id=wid)
+
+    if op.confirmation_done:
+        return JsonResponse(dict(hint="can't abort confirmed withdrawal"), 
status=409)
+    op.aborted = True
+    op.save()
+    return JsonResponse(dict(), status=200)
+
+
+@csrf_exempt
+@require_POST
+@login_via_headers
+def bank_accounts_api_confirm_withdrawal(request, user, acct_id, wid):
+    user_account = BankAccount.objects.get(user=user)
+    if acct_id != user_account.user.username:
+        # FIXME: respond nicely
+        raise Exception(
+            f"credentials do not match URL ('{acct_id}' vs 
'{user_account.username}')"
+        )
+    op = TalerWithdrawOperation.objects.get(withdraw_id=wid)
+    if op.confirmation_done:
+        return JsonResponse(dict(), status=200)
+    if op.aborted:
+        return JsonResponse(dict(hint="can't confirm aborted withdrawal"), 
status=409)
+
+    with transaction.atomic():
+        if op.selection_done:
+            wire_transfer(
+                op.amount,
+                user_account,
+                op.selected_exchange_account,
+                op.selected_reserve_pub,
+            )
+        op.confirmation_done = True
+        op.save()
+    return JsonResponse(dict(), status=200)
diff --git a/talerbank/settings.py b/talerbank/settings.py
index ba880e3..8024907 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings.py
@@ -217,7 +217,9 @@ else:
     ALLOW_REGISTRATIONS = False
 
 
-_show_freeform_withdrawal = TC.value_string("bank", 
"SHOW_FREEFORM_WITHDRAWAL", default="no")
+_show_freeform_withdrawal = TC.value_string(
+    "bank", "SHOW_FREEFORM_WITHDRAWAL", default="no"
+)
 if _show_freeform_withdrawal.lower() == "yes":
     SHOW_FREEFORM_WITHDRAWAL = True
 else:

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]