gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-bank] branch master updated (9db3307 -> cb0ec0a)


From: gnunet
Subject: [GNUnet-SVN] [taler-bank] branch master updated (9db3307 -> cb0ec0a)
Date: Sat, 23 Dec 2017 11:08:17 +0100

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

marcello pushed a change to branch master
in repository bank.

    from 9db3307  remove artificial line striker
     new 4e1f605  Implementing #5222.
     new cb0ec0a  UI fixes due to #5222.

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:
 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/login.html        |   9 +-
 talerbank/app/templates/pin_tan.html      |   4 +-
 talerbank/app/templates/profile_page.html |  36 +--
 talerbank/app/templates/register.html     |   1 +
 talerbank/app/tests.py                    |  31 +-
 talerbank/app/views.py                    | 474 ++++++++++--------------------
 talerbank/settings.py                     |   1 +
 12 files changed, 362 insertions(+), 401 deletions(-)
 create mode 100644 talerbank/app/middleware.py

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/login.html 
b/talerbank/app/templates/login.html
index 5a45aad..40a5a5b 100644
--- a/talerbank/app/templates/login.html
+++ b/talerbank/app/templates/login.html
@@ -27,15 +27,14 @@
     <article>
       <div class="login-form">
         <h2>Please login!</h2>
-        {% if form.errors %}
+        {% if fail_message %}
         <p class="informational informational-fail">
-          Your username and password didn't match. Please try again.
+          {{ hint }}
         </p>
         {% endif %}
-
-        {% if just_logged_out %}
+        {% if success_message %}
         <p class="informational informational-ok">
-          You were logged out successfully.
+          {{ hint }}
         </p>
         {% endif %}
 
diff --git a/talerbank/app/templates/pin_tan.html 
b/talerbank/app/templates/pin_tan.html
index f8f8188..de836c0 100644
--- a/talerbank/app/templates/pin_tan.html
+++ b/talerbank/app/templates/pin_tan.html
@@ -24,9 +24,9 @@
 {% endblock %}
 
 {% block content %}
-  {% if previous_failed %}
+  {% if fail_message %}
   <p class="informational informational-fail">
-    The captcha wasn't solved correctly.  Please try again.
+    {{ hint }}
   </p>
   {% endif %}
   <p>
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/templates/register.html 
b/talerbank/app/templates/register.html
index b7423e7..f01b5d9 100644
--- a/talerbank/app/templates/register.html
+++ b/talerbank/app/templates/register.html
@@ -29,6 +29,7 @@
     <article>
       <a href="{{ url('index') }}">Back</a>
       <div class="notification">
+        <!-- To be flag-ified -->
         {% if wrong %}
           <p class="informational informational-fail">
           Some fields were either not filled or filled incorrectly.
diff --git a/talerbank/app/tests.py b/talerbank/app/tests.py
index fb2d437..a7d68f3 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()
@@ -139,7 +138,7 @@ class InternalWireTransferTestCase(TestCase):
             Amount(settings.TALER_CURRENCY, 3),
             take_money.amount),
                          msg=self.take_money.amount.stringify(2))
-        self.assertEqual(200, response.status_code)
+        self.assertEqual(302, response.status_code)
 
 
 class RegisterTestCase(TestCase):
@@ -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..52e49ca 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):
@@ -74,24 +77,38 @@ def ignore(request):
     return HttpResponse()
 
 def login_view(request):
-    just_logged_out = get_session_flag(request, "just_logged_out")
+    fail_message, success_message, hint = get_session_hint(request, 
"login_hint")
     response = django.contrib.auth.views.login(
         request,
         authentication_form=MyAuthenticationForm,
         template_name="login.html",
         extra_context={"user": request.user})
     if hasattr(response, "context_data"):
-        response.context_data["just_logged_out"] = just_logged_out
+        response.context_data["fail_message"] = fail_message
+        response.context_data["success_message"] = success_message
+        response.context_data["hint"] = hint
     return response
 
 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 ret
+  return False
+
+def get_session_hint(request, name):
     """
-    Get a flag from the session and clear it.
+    Get a hint 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
+    # Fail message, success message, hint.
+    return False, False, None
 
 
 def predefined_accounts_list():
@@ -136,64 +153,40 @@ class WTForm(forms.Form):
 
 @login_required
 def profile_page(request):
-    info_bar = None
     if request.method == "POST":
         wtf = WTForm(request.POST)
         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"))
+            request.session["profile_hint"] = False, True, "Wire transfer 
successful!"
+            return redirect("profile")
     wtf = WTForm()
-
-    just_withdrawn = get_session_flag(request, "just_withdrawn")
+    fail_message, success_message, hint = get_session_hint(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 +219,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"] = \
@@ -250,19 +229,20 @@ def pin_tan_question(request):
                     int(request.GET["amount_fraction"]))
     request.session["amount"] = amount.dump()
     request.session["reserve_pub"] = request.GET["reserve_pub"]
-    request.session["sender_wiredetails"] = dict(
-        type="test",
-        bank_uri=request.build_absolute_uri(reverse("index")),
-        account_number=user_account.account_no
-    )
-    previous_failed = get_session_flag(request, "captcha_failed")
+    request.session["sender_wiredetails"] = {
+        "type": "test",
+        "bank_uri": request.build_absolute_uri(reverse("index")),
+        "account_number": user_account.account_no}
+    fail_message, success_message, hint = get_session_hint(request, 
"captcha_failed")
     question, hashed_answer = make_question()
     context = dict(
         question=question,
         hashed_answer=hashed_answer,
         amount=amount.stringify(settings.TALER_DIGITS),
-        previous_failed=previous_failed,
-        exchange=request.GET["exchange"])
+        exchange=request.GET["exchange"],
+        fail_message=fail_message,
+        success_message=success_message,
+        hint=hint)
     return render(request, "pin_tan.html", context)
 
 
@@ -275,32 +255,19 @@ def pin_tan_verify(request):
         LOGGER.warning("Wrong CAPTCHA answer: %s vs %s",
                        type(hashed_attempt),
                        type(request.POST.get("pin_1")))
-        request.session["captcha_failed"] = True
+        request.session["captcha_failed"] = True, False, "Wrong CAPTCHA 
answer."
         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):
@@ -317,12 +284,16 @@ def register(request):
     form = UserReg(request.POST)
     if not form.is_valid():
         return render(request, "register.html",
-                      dict(wrong_field=True))
+                      {"fail_message": True,
+                       "success_message": False,
+                       "hint": "Wrong field(s): %s." % ", 
".join(form.errors.keys())})
     username = form.cleaned_data["username"]
     password = form.cleaned_data["password"]
     if User.objects.filter(username=username).exists():
         return render(request, "register.html",
-                      dict(not_available=True))
+                      {"fail_message": True,
+                       "success_message": False,
+                       "hint": "Username not available."})
     with transaction.atomic():
         user = User.objects.create_user(
             username=username,
@@ -330,16 +301,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)
@@ -351,7 +317,7 @@ def logout_view(request):
     Log out the user and redirect to index page.
     """
     django.contrib.auth.logout(request)
-    request.session["just_logged_out"] = True
+    request.session["login_hint"] = False, True, "Logged out!"
     return redirect("index")
 
 
@@ -383,13 +349,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 +367,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 +378,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 +447,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 +464,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 +488,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 +505,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 +523,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]