summaryrefslogtreecommitdiff
path: root/talerbank
diff options
context:
space:
mode:
authorMarcello Stanisci <stanisci.m@gmail.com>2017-12-22 21:55:56 +0100
committerMarcello Stanisci <stanisci.m@gmail.com>2017-12-22 21:55:56 +0100
commit4e1f605a97f48f6300f632b64f46b4d9e98714f5 (patch)
tree7df2771acd9aa0313ddf282c6e9d86e3befe6e66 /talerbank
parent9db330755e89eb233900474fcf454320eb5f678c (diff)
downloadbank-4e1f605a97f48f6300f632b64f46b4d9e98714f5.tar.gz
bank-4e1f605a97f48f6300f632b64f46b4d9e98714f5.tar.bz2
bank-4e1f605a97f48f6300f632b64f46b4d9e98714f5.zip
Implementing #5222.
Diffstat (limited to 'talerbank')
-rw-r--r--talerbank/app/amount.py3
-rw-r--r--talerbank/app/middleware.py62
-rw-r--r--talerbank/app/models.py63
-rw-r--r--talerbank/app/schemas.py77
-rw-r--r--talerbank/app/templates/profile_page.html36
-rw-r--r--talerbank/app/tests.py29
-rw-r--r--talerbank/app/views.py423
-rw-r--r--talerbank/settings.py1
8 files changed, 317 insertions, 377 deletions
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 = [