gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-bank] 01/02: Implementing #5222.


From: gnunet
Subject: [GNUnet-SVN] [taler-bank] 01/02: Implementing #5222.
Date: Sat, 23 Dec 2017 11:08:18 +0100

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

marcello pushed a commit to branch master
in repository bank.

commit 4e1f605a97f48f6300f632b64f46b4d9e98714f5
Author: Marcello Stanisci <address@hidden>
AuthorDate: Fri Dec 22 21:55:56 2017 +0100

    Implementing #5222.
---
 Makefile.am                               |   2 +
 talerbank/app/amount.py                   |   3 +
 talerbank/app/middleware.py               |  62 +++++
 talerbank/app/models.py                   |  63 +++--
 talerbank/app/schemas.py                  |  77 ++++--
 talerbank/app/templates/profile_page.html |  36 +--
 talerbank/app/tests.py                    |  29 +-
 talerbank/app/views.py                    | 423 +++++++++---------------------
 talerbank/settings.py                     |   1 +
 9 files changed, 319 insertions(+), 377 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index fdea99d..6437b4a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,6 +30,8 @@ pkgdata_DATA = \
 install-dev:
        @$(PYTHON) ./install-dev.py
 
+env:
+       @export DJANGO_SETTINGS_MODULE="talerbank.settings" 
TALER_PREFIX="@prefix@" TALER_CONFIG_FILE="bank-check.conf" && bash
 check:
        @export DJANGO_SETTINGS_MODULE="talerbank.settings" 
TALER_PREFIX="@prefix@" TALER_CONFIG_FILE="bank-check.conf" && python3 -m 
django test --no-input talerbank.app.tests
        @printf -- 
"\n\n----------------------------------------------------------------------\nTesting
 against non existent config file\n\n"
diff --git a/talerbank/app/amount.py b/talerbank/app/amount.py
index a36b880..b8447f8 100644
--- a/talerbank/app/amount.py
+++ b/talerbank/app/amount.py
@@ -25,10 +25,13 @@
 from typing import Type
 
 class CurrencyMismatch(Exception):
+    hint = "Internal logic error (currency mismatch)"
+    http_status_code = 500
     def __init__(self, curr1, curr2) -> None:
         super(CurrencyMismatch, self).__init__(
             "%s vs %s" % (curr1, curr2))
 
+
 class BadFormatAmount(Exception):
     def __init__(self, faulty_str) -> None:
         super(BadFormatAmount, self).__init__(
diff --git a/talerbank/app/middleware.py b/talerbank/app/middleware.py
new file mode 100644
index 0000000..ab4269a
--- /dev/null
+++ b/talerbank/app/middleware.py
@@ -0,0 +1,62 @@
+import logging
+from django.http import JsonResponse
+from .models import BankAccount, BankTransaction
+from .views import \
+    (DebitLimitException, SameAccountException,
+     LoginFailed, RejectNoRightsException)
+from .schemas import \
+    (URLParameterMissing, URLParameterMalformed,
+     JSONFieldException, UnknownCurrencyException)
+from .amount import CurrencyMismatch, BadFormatAmount
+
+LOGGER = logging.getLogger()
+
+EXCS = {
+    BankAccount.DoesNotExist: 0,
+    BankTransaction.DoesNotExist: 1,
+    SameAccountException: 2,
+    DebitLimitException: 3,
+    URLParameterMissing: 8,
+    URLParameterMalformed: 9,
+    JSONFieldException: 6,
+    CurrencyMismatch: 11,
+    BadFormatAmount: 11,
+    LoginFailed: 12,
+    RejectNoRightsException: 13,
+    UnknownCurrencyException: 14}
+
+APIS = {
+    "/reject": 5300,
+    "/history": 5200,
+    "/admin/add/incoming": 5100}
+
+RENDER = {
+    "/profile": "profile",
+    "/register": "index",
+    "/public-accounts": "index",
+    "/pin/verify": "profile"}
+
+class ExceptionMiddleware:
+
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        return self.get_response(request)
+
+    def process_exception(self, request, exception):
+        if not EXCS.get(exception.__class__):
+            return None
+        taler_ec = EXCS.get(exception.__class__)
+        # The way error codes compose matches definitions found
+        # at [1].
+        taler_ec += APIS.get(request.path, 1000)
+        render_to = RENDER.get(request.path)
+        if not render_to:
+            return JsonResponse({"ec": taler_ec,
+                                 "error": exception.hint},
+                                status=exception.http_status_code)
+        request.session["profile_hint"] = True, False, hint
+        return redirect(render_to)
+
+# [1] 
https://git.taler.net/exchange.git/tree/src/include/taler_error_codes.h#n1502 
diff --git a/talerbank/app/models.py b/talerbank/app/models.py
index f8c5c47..a584912 100644
--- a/talerbank/app/models.py
+++ b/talerbank/app/models.py
@@ -1,16 +1,18 @@
 #  This file is part of TALER
 #  (C) 2014, 2015, 2016 INRIA
 #
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
+#  TALER is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation; either version 3, or
+# (at your option) any later version. TALER is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
 #
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+# You should have received a copy of the GNU General Public License
+# along with TALER; see the file COPYING.  If not, see
+# <http://www.gnu.org/licenses/>
 #
 #  @author Marcello Stanisci
 #  @author Florian Dold
@@ -20,7 +22,9 @@ from typing import Any, Tuple
 from django.contrib.auth.models import User
 from django.db import models
 from django.conf import settings
-from django.core.exceptions import ValidationError
+from django.core.exceptions import \
+    ValidationError, \
+    ObjectDoesNotExist
 from .amount import Amount, BadFormatAmount
 
 class AmountField(models.Field):
@@ -28,7 +32,8 @@ class AmountField(models.Field):
     description = 'Amount object in Taler style'
 
     def deconstruct(self) -> Tuple[str, str, list, dict]:
-        name, path, args, kwargs = super(AmountField, self).deconstruct()
+        name, path, args, kwargs = super(
+            AmountField, self).deconstruct()
         return name, path, args, kwargs
 
     def db_type(self, connection: Any) -> str:
@@ -55,28 +60,42 @@ class AmountField(models.Field):
                 return Amount.parse(settings.TALER_CURRENCY)
             return Amount.parse(value)
         except BadFormatAmount:
-            raise ValidationError("Invalid input for an amount string: %s" % 
value)
+            raise ValidationError(
+                "Invalid input for an amount string: %s" % value)
 
 def get_zero_amount() -> Amount:
     return Amount(settings.TALER_CURRENCY)
 
+class BankAccountDoesNotExist(ObjectDoesNotExist):
+    hint = "Specified bank account does not exist"
+    http_status_code = 404
+
+class BankTransactionDoesNotExist(ObjectDoesNotExist):
+    hint = "Specified bank transaction does not exist"
+    http_status_code = 404
+
 class BankAccount(models.Model):
     is_public = models.BooleanField(default=False)
     debit = models.BooleanField(default=False)
     account_no = models.AutoField(primary_key=True)
     user = models.OneToOneField(User, on_delete=models.CASCADE)
     amount = AmountField(default=get_zero_amount)
+    DoesNotExist = BankAccountDoesNotExist
 
 class BankTransaction(models.Model):
     amount = AmountField(default=False)
-    debit_account = models.ForeignKey(BankAccount,
-                                      on_delete=models.CASCADE,
-                                      db_index=True,
-                                      related_name="debit_account")
-    credit_account = models.ForeignKey(BankAccount,
-                                       on_delete=models.CASCADE,
-                                       db_index=True,
-                                       related_name="credit_account")
-    subject = models.CharField(default="(no subject given)", max_length=200)
-    date = models.DateTimeField(auto_now=True, db_index=True)
+    debit_account = models.ForeignKey(
+        BankAccount,
+        on_delete=models.CASCADE,
+        db_index=True,
+        related_name="debit_account")
+    credit_account = models.ForeignKey(
+        BankAccount,
+        on_delete=models.CASCADE,
+        db_index=True,
+        related_name="credit_account")
+    subject = models.CharField(
+        default="(no subject given)", max_length=200)
+    date = models.DateTimeField(
+        auto_now=True, db_index=True)
     cancelled = models.BooleanField(default=False)
diff --git a/talerbank/app/schemas.py b/talerbank/app/schemas.py
index 26d32ef..57473ef 100644
--- a/talerbank/app/schemas.py
+++ b/talerbank/app/schemas.py
@@ -20,9 +20,33 @@ definitions of JSON schemas for validating data
 """
 
 import json
-import validictory
+from validictory import validate
+from validictory.validator import \
+    (RequiredFieldValidationError,
+     FieldValidationError)
+
 from django.conf import settings
 
+class UnknownCurrencyException(ValueError):
+    def __init__(self, hint, http_status_code):
+        self.hint = hint
+        self.http_status_code = http_status_code
+
+class URLParameterMissing(ValueError):
+    def __init__(self, param, http_status_code):
+        self.hint = "URL parameter '%s' is missing" % param
+        self.http_status_code = http_status_code
+
+class URLParameterMalformed(ValueError):
+    def __init__(self, param, http_status_code):
+        self.hint = "URL parameter '%s' is malformed" % param
+        self.http_status_code = http_status_code
+
+class JSONFieldException(ValueError):
+    def __init__(self, hint, http_status_code):
+        self.hint = hint
+        self.http_status_code = http_status_code
+
 AMOUNT_SCHEMA = {
     "type": "object",
     "properties": {
@@ -134,29 +158,50 @@ def validate_pintan_types(validator, fieldname, value, 
format_option):
             data = json.loads(value)
             validate_wiredetails(data)
     except Exception:
-        raise validictory.FieldValidationError(
+        raise FieldValidationError(
             "Malformed '%s'" % fieldname, fieldname, value)
 
-def validate_pin_tan_args(pin_tan_args):
+def validate_pin_tan(data):
     format_dict = {
         "str_to_int": validate_pintan_types,
         "wiredetails_string": validate_pintan_types}
-    validictory.validate(pin_tan_args, PIN_TAN_ARGS, 
format_validators=format_dict)
+    validate(data, PIN_TAN_ARGS, format_validators=format_dict)
 
-def validate_reject_request(reject_request):
-    validictory.validate(reject_request, REJECT_REQUEST_SCHEMA)
+def validate_reject(data):
+    validate(data, REJECT_REQUEST_SCHEMA)
 
-def validate_history_request(history_request):
-    validictory.validate(history_request, HISTORY_REQUEST_SCHEMA)
-
-def validate_amount(amount):
-    validictory.validate(amount, AMOUNT_SCHEMA)
+def validate_history(data):
+    validate(data, HISTORY_REQUEST_SCHEMA)
 
 def validate_wiredetails(wiredetails):
-    validictory.validate(wiredetails, WIREDETAILS_SCHEMA)
+    validate(wiredetails, WIREDETAILS_SCHEMA)
+
+def validate_add_incoming(data):
+    validate(data, INCOMING_REQUEST_SCHEMA)
+
+def check_withdraw_session(data):
+    validate(data, WITHDRAW_SESSION_SCHEMA)
 
-def validate_incoming_request(incoming_request):
-    validictory.validate(incoming_request, INCOMING_REQUEST_SCHEMA)
+def validate_data(request, data):
+    switch = {
+        "/reject": validate_reject,
+        "/history": validate_history,
+        "/admin/add/incoming": validate_add_incoming,
+        "/pin/verify": check_withdraw_session,
+        "/pin/question": validate_pin_tan    
+    }
+    try:
+        switch.get(request.path)(data)
+    except  RequiredFieldValidationError as exc:
+        if request.method == "GET":
+            raise URLParameterMissing(exc.fieldname, 400)
+        raise JSONFieldException(
+            "Field '%s' is missing" % exc.fieldname, 400)
+    except FieldValidationError as exc:
+        if exc.fieldname == "currency":
+            raise UnknownCurrencyException("Unknown currency", 406)
+        if request.method == "GET":
+            raise URLParameterMalformed(exc.fieldname, 400)
+        raise JSONFieldException(
+            "Malformed '%s' field" % exc.fieldname, 400)
 
-def check_withdraw_session(session):
-    validictory.validate(session, WITHDRAW_SESSION_SCHEMA)
diff --git a/talerbank/app/templates/profile_page.html 
b/talerbank/app/templates/profile_page.html
index 6b465a0..4a03e52 100644
--- a/talerbank/app/templates/profile_page.html
+++ b/talerbank/app/templates/profile_page.html
@@ -42,38 +42,20 @@
   </section>
   <section id="main">
     <article>
-      <div class="notification">
-        {% if wire_transfer_error %}
+      {% if fail_message %}
+        <div class="notification">
           <p class="informational informational-fail">
-            {% if info_bar %}
-              {{ info_bar }}
-            {% else %}
-              Could not perform wire transfer, check all fields are correctly
-              entered.
-            {% endif %}
+            {{ hint }}
           </p>
-        {% endif %}
-        {% if just_wire_transferred %}
-          <p class="informational informational-ok">
-            Wire transfer done!
-          </p>
-        {% endif %}
-        {% if no_initial_bonus %}
-          <p class="informational informational-fail">
-            No initial bonus given, poor bank!
-          </p>
-        {% endif %}
-        {% if just_withdrawn %}
-          <p class="informational informational-ok">
-            Withdrawal approved!
-          </p>
-        {% endif %}
-        {% if just_registered %}
+        </div>
+      {% endif %}
+      {% if success_message %}
+        <div class="notification">
           <p class="informational informational-ok">
-            Registration successful!
+            {{ hint }}
           </p>
-        {% endif %}
         </div>
+        {% endif %}
     </article>
     <article>
       <div class="taler-installed-hide">
diff --git a/talerbank/app/tests.py b/talerbank/app/tests.py
index fb2d437..442888a 100644
--- a/talerbank/app/tests.py
+++ b/talerbank/app/tests.py
@@ -26,7 +26,7 @@ from mock import patch, MagicMock
 from urllib.parse import unquote
 from .models import BankAccount, BankTransaction
 from . import urls
-from .views import wire_transfer
+from .views import wire_transfer, LoginFailed
 from .amount import Amount, CurrencyMismatch, BadFormatAmount
 
 LOGGER = logging.getLogger()
@@ -39,6 +39,7 @@ def clear_db():
     with connection.cursor() as cursor:
         cursor.execute("ALTER SEQUENCE app_bankaccount_account_no_seq RESTART")
         cursor.execute("ALTER SEQUENCE app_banktransaction_id_seq RESTART")
+
 class WithdrawTestCase(TestCase):
     def setUp(self):
         self.user_bank_account = BankAccount(
@@ -106,9 +107,7 @@ class WithdrawTestCase(TestCase):
             args[0].dump() == amount.dump() \
             and self.user_bank_account in args \
             and "UVZ789" in args \
-            and self.exchange_bank_account in args \
-            and kwargs.get("session_expand") == \
-                {"debt_limit": True})
+            and self.exchange_bank_account in args)
 
     def tearDown(self):
         clear_db()
@@ -193,16 +192,28 @@ class LoginTestCase(TestCase):
             user=User.objects.create_user(
                 username="test_user",
                 password="test_password")).save()
+        self.client = Client()
 
     def tearDown(self):
         clear_db()
 
     def test_login(self):
-        client = Client()
-        self.assertTrue(client.login(username="test_user",
-                                     password="test_password"))
-        self.assertFalse(client.login(username="test_user",
-                                      password="test_passwordii"))
+        self.assertTrue(self.client.login(
+            username="test_user",
+            password="test_password"))
+        self.assertFalse(self.client.login(
+            username="test_user",
+            password="test_passwordii"))
+
+    def test_failing_login(self):
+        response = self.client.get(
+            reverse("history", urlconf=urls), {"auth": "basic"},
+            **{"HTTP_X_TALER_BANK_USERNAME": "Wrong",
+               "HTTP_X_TALER_BANK_PASSWORD": "Credentials"})
+        data = response.content.decode("utf-8")
+        self.assertJSONEqual('{"error": "Wrong username/password", "ec": 
5212}', json.loads(data))
+        self.assertEqual(401, response.status_code)
+
 
 class AmountTestCase(TestCase):
 
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index a231402..7f6093d 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -42,24 +42,27 @@ from django.db.models import Q
 from django.http import (JsonResponse, HttpResponse,
                          HttpResponseBadRequest as HRBR)
 from django.shortcuts import render, redirect
-from validictory.validator import \
-    (RequiredFieldValidationError as RFVE,
-     FieldValidationError as FVE)
 from .models import BankAccount, BankTransaction
 from .amount import Amount, CurrencyMismatch, BadFormatAmount
-from .schemas import (validate_pin_tan_args, check_withdraw_session,
-                      validate_history_request,
-                      validate_incoming_request,
-                      validate_reject_request)
-
+from .schemas import validate_data
 LOGGER = logging.getLogger(__name__)
 
-class DebtLimitExceededException(Exception):
-    def __init__(self) -> None:
-        super().__init__("Debt limit exceeded")
+class LoginFailed(Exception):
+    hint = "Wrong username/password"
+    http_status_code = 401
+
+class DebitLimitException(Exception):
+    hint = "Debit too high, operation forbidden."
+    http_status_code = 403
 
 class SameAccountException(Exception):
-    pass
+    hint = "Debit and credit account are the same."
+    http_status_code = 403
+
+class RejectNoRightsException(Exception):
+    hint = "You weren't the transaction credit account, " \
+           "no rights to reject."
+    http_status_code = 403
 
 class MyAuthenticationForm(
     django.contrib.auth.forms.AuthenticationForm):
@@ -89,9 +92,10 @@ def get_session_flag(request, name):
     Get a flag from the session and clear it.
     """
     if name in request.session:
+        ret = request.session[name]
         del request.session[name]
-        return True
-    return False
+        return ret
+    return False, False, None
 
 
 def predefined_accounts_list():
@@ -142,58 +146,33 @@ def profile_page(request):
         if wtf.is_valid():
             amount_parts = (settings.TALER_CURRENCY,
                             wtf.cleaned_data.get("amount") + 0.0)
-            try:
-                wire_transfer(
-                    Amount.parse("%s:%s" % amount_parts),
-                    BankAccount.objects.get(
-                        user=request.user),
-                    BankAccount.objects.get(
-                        account_no=wtf.cleaned_data.get(
-                            "receiver")),
-                        wtf.cleaned_data.get("subject"))
-                request.session["just_wire_transferred"] = True
-            except BankAccount.DoesNotExist:
-                request.session["wire_transfer_error"] = True
-                info_bar = "Specified account for receiver does not" \
-                           " exist"
-            except WireTransferException as exc:
-                request.session["wire_transfer_error"] = True
-                info_bar = "Internal server error, sorry!"
-                if isinstance(exc.exc, SameAccountException):
-                    info_bar = "Operation not possible:" \
-                               " debit and credit account" \
-                               " are the same!"
-        else:
-            LOGGER.warning("invalid wire transfer POSTed", wtf.errors)
+            wire_transfer(
+                Amount.parse("%s:%s" % amount_parts),
+                BankAccount.objects.get(user=request.user),
+                
BankAccount.objects.get(account_no=wtf.cleaned_data.get("receiver")),
+                wtf.cleaned_data.get("subject"))
     wtf = WTForm()
-
-    just_withdrawn = get_session_flag(request, "just_withdrawn")
+    fail_message, success_message, hint = get_session_flag(request, 
"profile_hint")
     context = dict(
         name=request.user.username,
         balance=request.user.bankaccount.amount.stringify(
             settings.TALER_DIGITS, pretty=True),
         sign="-" if request.user.bankaccount.debit else "",
+        fail_message=fail_message,
+        success_message=success_message,
+        hint=hint,
         precision=settings.TALER_DIGITS,
         currency=request.user.bankaccount.amount.currency,
         account_no=request.user.bankaccount.account_no,
         wt_form=wtf,
         history=extract_history(request.user.bankaccount),
-        just_withdrawn=just_withdrawn,
-        just_registered=get_session_flag(
-            request, "just_registered"),
-        no_initial_bonus=get_session_flag(
-            request, "no_initial_bonus"),
-        just_wire_transferred=get_session_flag(
-            request, "just_wire_transferred"),
-        wire_transfer_error=get_session_flag(
-            request, "wire_transfer_error"),
-        info_bar=info_bar
     )
     if settings.TALER_SUGGESTED_EXCHANGE:
         context["suggested_exchange"] = settings.TALER_SUGGESTED_EXCHANGE
 
     response = render(request, "profile_page.html", context)
-    if just_withdrawn:
+    if "just_withdrawn" in request.session:
+        del request.session["just_withdrawn"]
         response["X-Taler-Operation"] = "confirm-reserve"
         response["X-Taler-Reserve-Pub"] = request.session.get(
             "reserve_pub")
@@ -226,21 +205,7 @@ def make_question():
 @require_GET
 @login_required
 def pin_tan_question(request):
-    try:
-        validate_pin_tan_args(request.GET.dict())
-        # Currency is not checked, as any mismatches will be
-        # detected afterwards
-    except (FVE, RFVE) as err:
-        ec = settings.TALER_EC_PARAMETER_MALFORMED
-        if isinstance(err, RFVE):
-          ec = settings.TALER_EC_PARAMETER_MISSING
-        LOGGER.error(
-            "missing/malformed parameter '%s'" % err.fieldname)
-        return JsonResponse(
-            {"error": "missing parameter '%s'" % err.fieldname,
-             "ec": ec},
-            status=400)
-
+    validate_data(request, request.GET.dict())
     user_account = BankAccount.objects.get(user=request.user)
     wd = json.loads(request.GET["exchange_wire_details"])
     request.session["exchange_account_number"] = \
@@ -278,29 +243,16 @@ def pin_tan_verify(request):
         request.session["captcha_failed"] = True
         return redirect(request.POST.get("question_url", "profile"))
     # Check the session is a "pin tan" one
-    try:
-        check_withdraw_session(request.session)
-        amount = Amount(**request.session["amount"])
-        exchange_bank_account = BankAccount.objects.get(
-            account_no=request.session["exchange_account_number"])
-        wire_transfer(amount,
-                      BankAccount.objects.get(user=request.user),
-                      exchange_bank_account,
-                      request.session["reserve_pub"],
-                      request=request,
-                      session_expand=dict(debt_limit=True))
-    except (FVE, RFVE) as exc:
-        LOGGER.warning("Not a withdrawing session")
-        return redirect("profile")
-
-    except BankAccount.DoesNotExist as exc:
-        return JsonResponse(
-            {"error": "That exchange is unknown to this bank",
-             "ec": settings.TALER_EC_BANK_ACCOUNT_NOT_FOUND},
-            status=404)
-    except WireTransferException as exc:
-        return exc.response
-    request.session["just_withdrawn"] = True
+    validate_data(request, request.session)
+    amount = Amount(**request.session["amount"])
+    exchange_bank_account = BankAccount.objects.get(
+        account_no=request.session["exchange_account_number"])
+    wire_transfer(amount,
+                  BankAccount.objects.get(user=request.user),
+                  exchange_bank_account,
+                  request.session["reserve_pub"])
+    request.session["profile_hint"] = False, True, "Withdrawal successful!"
+    request.session["just_withdraw"] = True
     return redirect("profile")
 
 class UserReg(forms.Form):
@@ -330,16 +282,11 @@ def register(request):
         user_account = BankAccount(user=user)
         user_account.save()
     bank_internal_account = BankAccount.objects.get(account_no=1)
-    try:
-        wire_transfer(Amount(settings.TALER_CURRENCY, 100, 0),
-                      bank_internal_account,
-                      user_account,
-                      "Joining bonus",
-                      request=request,
-                      session_expand=dict(no_initial_bobus=True))
-    except WireTransferException as exc:
-        return exc.response
-    request.session["just_registered"] = True
+    wire_transfer(Amount(settings.TALER_CURRENCY, 100, 0),
+                  bank_internal_account,
+                  user_account,
+                  "Joining bonus")
+    request.session["profile_hint"] = False, True, "Registration successful!"
     user = django.contrib.auth.authenticate(
         username=username, password=password)
     django.contrib.auth.login(request, user)
@@ -383,13 +330,7 @@ def extract_history(account):
 def serve_public_accounts(request, name=None):
     if not name:
         name = settings.TALER_PREDEFINED_ACCOUNTS[0]
-    try:
         user = User.objects.get(username=name)
-    except User.DoesNotExist:
-        return HttpResponse(
-            {"error": "account '%s' not found" % name,
-             "ec": settings.TALER_EC_BANK_UNKNOWN_ACCOUNT},
-             status=404)
     public_accounts = BankAccount.objects.filter(is_public=True)
     history = extract_history(account)
     context = dict(
@@ -407,15 +348,7 @@ def login_via_headers(view_func):
         user_account = auth_and_login(request)
         if not user_account:
             LOGGER.error("authentication failed")
-            switch = {
-            "serve_history":
-                settings.TALER_EC_BANK_HISTORY_NOT_AUTHORIZED}
-            ec = switch.get(view_func.__name,
-                            settings.TALER_EC_BANK_NOT_AUTHORIZED)
-            return JsonResponse(
-                {"error": "authentication failed",
-                 "ec": ec},
-                status=401)
+            raise LoginFailed("authentication failed")
         return view_func(request, user_account, *args, **kwargs)
     return wraps(view_func)(_decorator)
 
@@ -426,19 +359,7 @@ def serve_history(request, user_account):
     This API is used to get a list of transactions related to one
     user.
     """
-    try:
-        # Note, this does check the currency.
-        validate_history_request(request.GET.dict())
-    except (FVE, RFVE) as exc:
-        ec = settings.TALER_EC_PARAMETER_MALFORMED
-        if isinstance(exc, RFVE):
-          ec = settings.TALER_EC_PARAMETER_MISSING
-        LOGGER.error("/history, bad '%s' arg" % exc.fieldname)
-        return JsonResponse(
-            {"error": "invalid '%s'" % exc.fieldname,
-             "ec": ec},
-            status=400)
-
+    validate_data(request, request.GET.dict())
     # delta
     parsed_delta = re.search(r"([\+-])?([0-9]+)",
                              request.GET.get("delta"))
@@ -507,14 +428,13 @@ def auth_and_login(request):
         auth_type = request.GET.get("auth")
     if auth_type != "basic":
         LOGGER.error("auth method not supported")
-        return False
+        raise LoginFailed("auth method not supported")
 
     username = request.META.get("HTTP_X_TALER_BANK_USERNAME")
     password = request.META.get("HTTP_X_TALER_BANK_PASSWORD")
-    LOGGER.info("Trying to log '%s/%s' in" % (username, password))
     if not username or not password:
         LOGGER.error("user or password not given")
-        return False
+        raise LoginFailed("missing user/password")
     return django.contrib.auth.authenticate(
         username=username,
         password=password)
@@ -525,32 +445,11 @@ def auth_and_login(request):
 @login_via_headers
 def reject(request, user_account):
     data = json.loads(request.body.decode("utf-8"))
-    try:
-        validate_reject_request(data)
-    except (FVE, RFVE) as exc:
-        ec = settings.TALER_EC_PARAMETER_MALFORMED
-        if isinstance(err, RFVE):
-          ec = settings.TALER_EC_PARAMETER_MISSING
-        LOGGER.error("invalid %s" % exc.fieldname)
-        return JsonResponse(
-            {"error": "invalid '%s'" % exc.fieldname,
-             "ec": ec},
-            status=400)
-    try:
-        trans = BankTransaction.objects.get(id=data["row_id"])
-    except BankTransaction.DoesNotExist:
-        return JsonResponse(
-            {"error": "unknown transaction",
-             "ec": settings.TALER_EC_BANK_REJECT_NOT_FOUND},
-            status=404)
+    validate_data(request, data)
+    trans = BankTransaction.objects.get(id=data["row_id"])
     if trans.credit_account.account_no != \
             user_account.bankaccount.account_no:
-        LOGGER.error("you can only reject a transaction where you"
-                     " _got_ money")
-        return JsonResponse(
-            {"error": "no rights to reject",
-             "ec": settings.TALER_EC_BANK_REJECT_NO_RIGHTS},
-             status=401) # Unauthorized
+        raise RejectNoRightsException()
     trans.cancelled = True
     trans.debit_account.amount.add(trans.amount)
     trans.credit_account.amount.subtract(trans.amount)
@@ -570,37 +469,14 @@ def add_incoming(request, user_account):
     within the browser, and only over the private admin interface.
     """
     data = json.loads(request.body.decode("utf-8"))
-    try:
-        # Note, this does check the currency.
-        validate_incoming_request(data)
-    except (FVE, RFVE) as exc:
-        ec = settings.TALER_EC_PARAMETER_MALFORMED
-        if isinstance(exc, RFVE):
-          ec = settings.TALER_EC_PARAMETER_MISSING
-        LOGGER.error(
-            "missing/malformed parameter '%s'" % exc.fieldname)
-        return JsonResponse(
-            {"error": "missing parameter '%s'" % exc.fieldname,
-             "ec": ec},
-            status=406 if exc.fieldname == "currency" else 400)
-
+    validate_data(request, data)
     subject = "%s %s" % (data["subject"], data["exchange_url"])
-    try:
-        credit_account = BankAccount.objects.get(
-            account_no=data["credit_account"])
-        wtrans = wire_transfer(Amount(**data["amount"]),
-                               user_account.bankaccount,
-                               credit_account,
-                               subject)
-    except BankAccount.DoesNotExist:
-        return JsonResponse(
-            {"error":
-                 "credit_account (%d) not found" \
-                 % data["credit_account"],
-             "ec": settings.TALER_EC_BANK_UNKNOWN_ACCOUNT},
-            status=404)
-    except WireTransferException as exc:
-        return exc.response
+    credit_account = BankAccount.objects.get(
+        account_no=data["credit_account"])
+    wtrans = wire_transfer(Amount(**data["amount"]),
+                           user_account.bankaccount,
+                           credit_account,
+                           subject)
     return JsonResponse(
         {"row_id": wtrans.id,
          "timestamp": "/Date(%s)/" % int(wtrans.date.timestamp())})
@@ -610,21 +486,9 @@ def add_incoming(request, user_account):
 @require_POST
 def withdraw_nojs(request):
 
-    try:
-        amount = Amount.parse(
-            request.POST.get("kudos_amount", "not-given"))
-    except BadFormatAmount:
-        LOGGER.error("Amount ('%s') did not pass parsing" % amount)
-        ec = settings.TALER_EC_PARAMETER_MALFORMED
-        if amount == "not-given":
-            ec = settings.TALER_EC_PARAMETER_MISSING
-        return JsonResponse({
-            "error": "bad 'kudos_amount' parameter",
-            "ec": ec},
-            status=400)
-
+    amount = Amount.parse(
+        request.POST.get("kudos_amount", "not-given"))
     user_account = BankAccount.objects.get(user=request.user)
-
     response = HttpResponse(status=202)
     response["X-Taler-Operation"] = "create-reserve"
     response["X-Taler-Callback-Url"] = reverse("pin-question")
@@ -640,112 +504,65 @@ def withdraw_nojs(request):
             settings.TALER_SUGGESTED_EXCHANGE
     return response
 
-class WireTransferException(Exception):
-    def __init__(self, exc, response):
-        self.exc = exc
-        self.response = response
-        super().__init__()
-
-def wire_transfer(amount,
-                  debit_account,
-                  credit_account,
-                  subject,
-                  **kwargs):
-    def err_cb(exc, resp):
-        LOGGER.error(str(exc))
-        raise WireTransferException(exc, resp)
-
-    def wire_transfer_internal(amount,
-                               debit_account,
-                               credit_account,
-                               subject):
-        LOGGER.info("%s => %s, %s, %s" %
-                    (debit_account.account_no,
-                     credit_account.account_no,
-                     amount.stringify(2),
-                     subject))
-        if debit_account.pk == credit_account.pk:
-            LOGGER.error("Debit and credit account are the same!")
-            raise SameAccountException()
-
-        transaction_item = BankTransaction(
-            amount=amount,
-            credit_account=credit_account,
-            debit_account=debit_account,
-            subject=subject)
-        if debit_account.debit:
-            debit_account.amount.add(amount)
-
-        elif -1 == Amount.cmp(debit_account.amount, amount):
-            debit_account.debit = True
-            tmp = Amount(**amount.dump())
-            tmp.subtract(debit_account.amount)
-            debit_account.amount.set(**tmp.dump())
-        else:
-            debit_account.amount.subtract(amount)
-
-        if not credit_account.debit:
-            credit_account.amount.add(amount)
-        elif Amount.cmp(amount, credit_account.amount) == 1:
-            credit_account.debit = False
-            tmp = Amount(**amount.dump())
-            tmp.subtract(credit_account.amount)
-            credit_account.amount.set(**tmp.dump())
-        else:
-            credit_account.amount.subtract(amount)
-
-        # Check here if any account went beyond the allowed
-        # debit threshold.
-
-        threshold = Amount.parse(settings.TALER_MAX_DEBT)
-        if debit_account.user.username == "Bank":
-            threshold = Amount.parse(settings.TALER_MAX_DEBT_BANK)
-        if Amount.cmp(debit_account.amount, threshold) == 1 \
-                and Amount.cmp(Amount(settings.TALER_CURRENCY),
-                               threshold) != 0 \
-                and debit_account.debit:
-            LOGGER.info("Negative balance '%s' not allowed.\
-                        " % json.dumps(debit_account.amount.dump()))
-            LOGGER.info("%s's threshold is: '%s'." \
-                        % (debit_account.user.username,
-                           json.dumps(threshold.dump())))
-            raise DebtLimitExceededException()
-
-        with transaction.atomic():
-            debit_account.save()
-            credit_account.save()
-            transaction_item.save()
-
-        return transaction_item
-
-    try:
-        return wire_transfer_internal(
-            amount,
-            debit_account,
-            credit_account,
-            subject)
-    except (CurrencyMismatch, BadFormatAmount) as exc:
-        err_cb(exc, JsonResponse(
-            {"error": "internal server error",
-             "ec": settings.TALER_EC_INTERNAL_LOGIC_ERROR},
-             status=500))
-    except DebtLimitExceededException as exc:
-        if kwargs.get("request"):
-            if kwargs.get("session_expand"):
-                kwargs["request"].session.update(
-                    kwargs["session_expand"])
-            if kwargs["request"].request.path == "/pin/verify":
-                err_cb(exc, redirect("profile"))
-        else:
-            err_cb(exc, JsonResponse(
-                {"error": "Unallowed debit",
-                 "ec": settings.TALER_EC_BANK_TRANSFER_DEBIT},
-                 status=403))
-    except SameAccountException as exc:
-        err_cb(exc, JsonResponse(
-            {"error": "sender account == receiver account",
-             "ec": settings.TALER_EC_BANK_TRANSFER_SAME_ACCOUNT},
-            status=422))
+def wire_transfer(amount, debit_account, credit_account,
+                  subject):
+    LOGGER.info("%s => %s, %s, %s" %
+                (debit_account.account_no,
+                 credit_account.account_no,
+                 amount.stringify(2),
+                 subject))
+    if debit_account.pk == credit_account.pk:
+        LOGGER.error("Debit and credit account are the same!")
+        raise SameAccountException()
+
+    transaction_item = BankTransaction(
+        amount=amount,
+        credit_account=credit_account,
+        debit_account=debit_account,
+        subject=subject)
+    if debit_account.debit:
+        debit_account.amount.add(amount)
+
+    elif -1 == Amount.cmp(debit_account.amount, amount):
+        debit_account.debit = True
+        tmp = Amount(**amount.dump())
+        tmp.subtract(debit_account.amount)
+        debit_account.amount.set(**tmp.dump())
+    else:
+        debit_account.amount.subtract(amount)
+
+    if not credit_account.debit:
+        credit_account.amount.add(amount)
+    elif Amount.cmp(amount, credit_account.amount) == 1:
+        credit_account.debit = False
+        tmp = Amount(**amount.dump())
+        tmp.subtract(credit_account.amount)
+        credit_account.amount.set(**tmp.dump())
+    else:
+        credit_account.amount.subtract(amount)
+
+    # Check here if any account went beyond the allowed
+    # debit threshold.
+
+    threshold = Amount.parse(settings.TALER_MAX_DEBT)
+    if debit_account.user.username == "Bank":
+        threshold = Amount.parse(settings.TALER_MAX_DEBT_BANK)
+    if Amount.cmp(debit_account.amount, threshold) == 1 \
+            and Amount.cmp(Amount(settings.TALER_CURRENCY),
+                           threshold) != 0 \
+            and debit_account.debit:
+        LOGGER.info("Negative balance '%s' not allowed.\
+                    " % json.dumps(debit_account.amount.dump()))
+        LOGGER.info("%s's threshold is: '%s'." \
+                    % (debit_account.user.username,
+                       json.dumps(threshold.dump())))
+        raise DebitLimitException()
+
+    with transaction.atomic():
+        debit_account.save()
+        credit_account.save()
+        transaction_item.save()
 
+    return transaction_item
 
 # [1] 
https://stackoverflow.com/questions/24783275/django-form-with-choices-but-also-with-freetext-option
diff --git a/talerbank/settings.py b/talerbank/settings.py
index 77e3c4f..07b8124 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings.py
@@ -71,6 +71,7 @@ MIDDLEWARE = [
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'talerbank.app.middleware.ExceptionMiddleware',
 ]
 
 TEMPLATES = [

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



reply via email to

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