summaryrefslogtreecommitdiff
path: root/talerbank/app
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-12-19 11:15:19 +0100
committerFlorian Dold <florian.dold@gmail.com>2019-12-19 11:15:35 +0100
commit45bb124b81d94938483a932338137465e6876a91 (patch)
tree0820477333248ad1802b8b9ff86feb87e1c0c240 /talerbank/app
parent11ecfd15ffe16ba35dac54c3b8794bb51c2091df (diff)
downloadbank-45bb124b81d94938483a932338137465e6876a91.tar.gz
bank-45bb124b81d94938483a932338137465e6876a91.tar.bz2
bank-45bb124b81d94938483a932338137465e6876a91.zip
formatting / debugging
Diffstat (limited to 'talerbank/app')
-rw-r--r--talerbank/app/views.py261
1 files changed, 122 insertions, 139 deletions
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index a0a58b1..a8aaa4f 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -49,9 +49,15 @@ import qrcode
import qrcode.image.svg
import lxml
from .schemas import (
- HistoryParams, HistoryRangeParams, URLParamValidationError, RejectData,
- AddIncomingData, JSONFieldException, InvalidSession, WithdrawHeadless,
- WithdrawHeadlessUri
+ HistoryParams,
+ HistoryRangeParams,
+ URLParamValidationError,
+ RejectData,
+ AddIncomingData,
+ JSONFieldException,
+ InvalidSession,
+ WithdrawHeadless,
+ WithdrawHeadlessUri,
)
LOGGER = logging.getLogger(__name__)
@@ -60,7 +66,7 @@ LOGGER = logging.getLogger(__name__)
# Constant value for the biggest number the bank handles.
# This value is just equal to the biggest number that JavaScript
# can handle (because of the wallet).
-UINT64_MAX = (2**64) - 1
+UINT64_MAX = (2 ** 64) - 1
##
# Exception raised upon failing login.
@@ -72,20 +78,23 @@ class LoginFailed(Exception):
self.http_status_code = 401
self.taler_error_code = 5109
+
class InvalidInputData(Exception):
def __init__(self, msg):
super(InvalidInputData, self).__init__(msg)
- self.hint = msg # should mention the picked username
+ self.hint = msg # should mention the picked username
self.http_status_code = 400
self.taler_error_code = 5400
+
class UsernameUnavailable(Exception):
def __init__(self, msg):
super(UsernameUnavailable, self).__init__(msg)
- self.hint = msg # should mention the picked username
+ self.hint = msg # should mention the picked username
self.http_status_code = 406
self.taler_error_code = 5400
+
##
# Exception raised when the public history from
# a ordinary user account is tried to be accessed.
@@ -130,6 +139,7 @@ class RejectNoRightsException(Exception):
self.http_status_code = 403
self.taler_error_code = 5200
+
class UnhandledException(Exception):
def __init__(self, msg="Unhandled exception happened!"):
super(UnhandledException, self).__init__(msg)
@@ -137,6 +147,7 @@ class UnhandledException(Exception):
self.http_status_code = 500
self.taler_error_code = 5300
+
##
# The authentication for users to log in the bank.
#
@@ -164,6 +175,7 @@ def ignore(request):
def decode_body(request):
return request.body.decode("utf-8")
+
##
# Get a flag from the session and clear it.
#
@@ -194,8 +206,12 @@ def get_session_hint(request, hintId):
return False, False, None
+
def set_profile_hint(request, *, success, failure, hint):
- set_session_hint(request, "profile_hint", success=success, failure=failure, hint=hint)
+ set_session_hint(
+ request, "profile_hint", success=success, failure=failure, hint=hint
+ )
+
def set_session_hint(request, hintId, *, success, failure, hint):
if hintId in request.session:
@@ -256,8 +272,7 @@ class InputDatalist(forms.TextInput):
html = super().render(name, value, attrs=attrs, renderer=renderer)
datalist = '<datalist id="%slist">' % self._name
for dl_value, dl_text in self._datalist:
- datalist += '<option value="%s">%s</option>' \
- % (dl_value, dl_text)
+ datalist += '<option value="%s">%s</option>' % (dl_value, dl_text)
datalist += "</datalist>"
return html + datalist
@@ -268,8 +283,7 @@ class InputDatalist(forms.TextInput):
#
class WTForm(forms.Form):
amount = forms.FloatField(
- min_value=0.1,
- widget=forms.NumberInput(attrs={"class": "currency-input"})
+ min_value=0.1, widget=forms.NumberInput(attrs={"class": "currency-input"})
)
receiver = forms.IntegerField(
min_value=1, widget=InputDatalist(predefined_accounts_list, "receiver")
@@ -298,21 +312,18 @@ def profile_page(request):
wtf = WTForm(request.POST)
if wtf.is_valid():
amount_parts = (
- settings.TALER_CURRENCY, wtf.cleaned_data.get("amount") + 0.0
+ settings.TALER_CURRENCY,
+ wtf.cleaned_data.get("amount") + 0.0,
)
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")
+ BankAccount.objects.get(account_no=wtf.cleaned_data.get("receiver")),
+ wtf.cleaned_data.get("subject"),
)
set_profile_hint(
- request,
- failure=False,
- success=True,
- hint="Wire transfer successful!"
+ request, failure=False, success=True, hint="Wire transfer successful!"
)
return redirect("profile")
@@ -330,9 +341,7 @@ def profile_page(request):
currency=request.user.bankaccount.amount.currency,
account_no=request.user.bankaccount.account_no,
wt_form=wtf,
- history=extract_history(
- request.user.bankaccount, -1 * (UINT64_MAX / 2 / 2)
- )
+ history=extract_history(request.user.bankaccount, -1 * (UINT64_MAX / 2 / 2)),
)
if settings.TALER_SUGGESTED_EXCHANGE:
context["suggested_exchange"] = settings.TALER_SUGGESTED_EXCHANGE
@@ -397,8 +406,7 @@ def internal_register(request):
input_data = UserReg(request.POST)
if not input_data.is_valid():
- msg = "Wrong field(s): %s." % \
- ", ".join(input_data.errors.keys())
+ msg = "Wrong field(s): %s." % ", ".join(input_data.errors.keys())
raise InvalidInputData(msg)
username = input_data.cleaned_data["username"]
@@ -409,9 +417,7 @@ def internal_register(request):
# Registration goes through.
with transaction.atomic():
- user = User.objects.create_user(
- username=username,
- password=password)
+ user = User.objects.create_user(username=username, password=password)
user_account = BankAccount(user=user)
user_account.save()
bank_internal_account = BankAccount.objects.get(account_no=1)
@@ -429,7 +435,8 @@ def internal_register(request):
wire_transfer(
Amount(settings.TALER_CURRENCY, 100, 0),
bank_internal_account,
- user_account, "Joining bonus"
+ user_account,
+ "Joining bonus",
)
return user
@@ -479,25 +486,23 @@ def register(request):
except InvalidInputData as e:
return render(
- request, "register.html", {
+ request,
+ "register.html",
+ {
"wrong": True,
- "hint": "Wrong field(s): %s." % ", ".join(form.errors.keys())
- }
+ "hint": "Wrong field(s): %s." % ", ".join(form.errors.keys()),
+ },
)
except DebitLimitException as e:
return render(
- request, "register.html", {
- "wrong": True,
- "hint": "Out of business, cannot admit new customers."
- }
+ request,
+ "register.html",
+ {"wrong": True, "hint": "Out of business, cannot admit new customers."},
)
set_profile_hint(
- request,
- success=True,
- failure=False,
- hint="Registration successful!"
+ request, success=True, failure=False, hint="Registration successful!"
)
django.contrib.auth.login(request, user)
@@ -545,7 +550,7 @@ def extract_history(account, delta, start=None):
counterpart=counterpart.account_no,
counterpart_username=counterpart.user.username,
subject=item.subject,
- date=item.date.strftime("%d/%m/%y %H:%M")
+ date=item.date.strftime("%d/%m/%y %H:%M"),
)
history.append(entry)
return history
@@ -583,7 +588,7 @@ def serve_public_accounts(request, name=None, page=None):
# and django/python is not allowing slicing with big numbers.
UINT64_MAX / 2 / 2,
0,
- "descending"
+ "descending",
).count()
DELTA = 30
# '//' operator is NO floating point.
@@ -592,8 +597,9 @@ def serve_public_accounts(request, name=None, page=None):
public_accounts = BankAccount.objects.filter(is_public=True)
# Retrieve DELTA records younger than 'start_row' (included).
- history = extract_history(user.bankaccount, DELTA * page,
- 0)[DELTA * (page - 1):(DELTA * page)]
+ history = extract_history(user.bankaccount, DELTA * page, 0)[
+ DELTA * (page - 1) : (DELTA * page)
+ ]
pages = list(range(1, num_pages + 1))
@@ -603,11 +609,9 @@ def serve_public_accounts(request, name=None, page=None):
forth=page + 1 if page < num_pages else None,
public_accounts=public_accounts,
selected_account=dict(
- name=name,
- number=user.bankaccount.account_no,
- history=history,
+ name=name, number=user.bankaccount.account_no, history=history,
),
- pages=pages
+ pages=pages,
)
return render(request, "public_accounts.html", context)
@@ -640,8 +644,7 @@ def login_via_headers(view_func):
# is called.
def direction_switch(bank_account, direction):
direction_switch = {
- "both":
- (Q(debit_account=bank_account) | Q(credit_account=bank_account)),
+ "both": (Q(debit_account=bank_account) | Q(credit_account=bank_account)),
"credit": Q(credit_account=bank_account),
"debit": Q(debit_account=bank_account),
"cancel+": (Q(credit_account=bank_account) & Q(cancelled=True)),
@@ -673,8 +676,7 @@ def direction_switch(bank_account, direction):
# youngest entry in the first position.
def query_history_range(bank_account, direction, start, end, descending):
qs = BankTransaction.objects.filter(
- direction_switch(bank_account, direction), Q(date__gte=start),
- Q(date__lte=end)
+ direction_switch(bank_account, direction), Q(date__gte=start), Q(date__lte=end)
)
order = "-id" if descending else "id"
@@ -720,7 +722,7 @@ def query_history(bank_account, direction, delta, start, ordering):
direction_switch(bank_account, direction), sign_filter
)
order = "-id" if "descending" == ordering else "id"
- return qs.order_by(order)[:abs(delta)]
+ return qs.order_by(order)[: abs(delta)]
##
@@ -740,8 +742,7 @@ def build_history_response(qs, cancelled, user_account):
sign_ = "-"
if entry.cancelled and cancelled == "omit":
continue
- if entry.credit_account.account_no == \
- user_account.bankaccount.account_no:
+ if entry.credit_account.account_no == user_account.bankaccount.account_no:
counterpart = entry.debit_account.account_no
sign_ = "+"
cancel = "cancel" if entry.cancelled else ""
@@ -753,7 +754,7 @@ def build_history_response(qs, cancelled, user_account):
sign=sign_,
wt_subject=entry.subject,
row_id=entry.id,
- date="/Date(" + str(int(entry.date.timestamp())) + ")/"
+ date="/Date(" + str(int(entry.date.timestamp())) + ")/",
)
)
return history
@@ -779,12 +780,10 @@ def serve_history_range(request, user_account):
args.get("direction", "both"),
start_td,
end_td,
- args.get("ordering")
+ args.get("ordering"),
)
- history = build_history_response(
- qs, args.get("cancelled", "show"), user_account
- )
+ history = build_history_response(qs, args.get("cancelled", "show"), user_account)
if not history:
return HttpResponse(status=204)
@@ -806,12 +805,10 @@ def serve_history(request, user_account):
args.get("direction"),
args.get("delta"),
args.get("start", None),
- args.get("ordering", "descending")
+ args.get("ordering", "descending"),
)
- history = build_history_response(
- qs, args.get("cancelled", "show"), user_account
- )
+ history = build_history_response(qs, args.get("cancelled", "show"), user_account)
if not history:
return HttpResponse(status=204)
@@ -833,9 +830,7 @@ def auth_and_login(request):
password = request.META.get("HTTP_X_TALER_BANK_PASSWORD")
if not username or not password:
raise LoginFailed("missing user/password")
- return django.contrib.auth.authenticate(
- username=username, password=password
- )
+ return django.contrib.auth.authenticate(username=username, password=password)
##
@@ -855,8 +850,7 @@ def reject(request, user_account):
data = RejectData(json.loads(decode_body(request)))
trans = BankTransaction.objects.get(id=data.get("row_id"))
- if trans.credit_account.account_no != \
- user_account.bankaccount.account_no:
+ if trans.credit_account.account_no != user_account.bankaccount.account_no:
raise RejectNoRightsException()
trans.cancelled = True
if trans.debit_account.debit:
@@ -865,11 +859,11 @@ def reject(request, user_account):
# debit_account.amount <= trans.amount
trans.debit_account.debit = False
tmp = Amount(**trans.amount.dump())
- tmp.subtract (trans.debit_account.amount)
- trans.debit_account.amount.set (**tmp.dump())
+ tmp.subtract(trans.debit_account.amount)
+ trans.debit_account.amount.set(**tmp.dump())
else:
# debit_account > trans.amount
- trans.debit_account.amount.subtract (trans.amount)
+ trans.debit_account.amount.subtract(trans.amount)
else:
# balance is positive, simply add
trans.debit_account.amount.add(trans.amount)
@@ -881,8 +875,8 @@ def reject(request, user_account):
# credit_account.amount < trans.amount
trans.credit_account.debit = True
tmp = Amount(**trans.amount.dump())
- tmp.subtract (trans.credit_account.amount)
- trans.credit_account.amount.set (**tmp.dump())
+ tmp.subtract(trans.credit_account.amount)
+ trans.credit_account.amount.set(**tmp.dump())
else:
# credit_account.amount >= trans.amount
trans.credit_account.amount.subtract(trans.amount)
@@ -906,24 +900,20 @@ def add_incoming(request, user_account):
data = AddIncomingData(json.loads(decode_body(request)))
- subject = "%s %s" % (
- data.get("subject"), data.get("exchange_url")
- )
+ subject = "%s %s" % (data.get("subject"), data.get("exchange_url"))
- credit_account = BankAccount.objects.get(
- account_no=data.get("credit_account")
- )
+ credit_account = BankAccount.objects.get(account_no=data.get("credit_account"))
wtrans = wire_transfer(
- Amount.parse(data.get("amount")), user_account.bankaccount,
- credit_account, subject
+ Amount.parse(data.get("amount")),
+ user_account.bankaccount,
+ credit_account,
+ subject,
)
- return JsonResponse({
- "row_id": wtrans.id,
- "timestamp": "/Date(%s)/" % int(wtrans.date.timestamp())
- })
-
+ return JsonResponse(
+ {"row_id": wtrans.id, "timestamp": "/Date(%s)/" % int(wtrans.date.timestamp())}
+ )
@login_via_headers
@@ -937,9 +927,8 @@ def withdraw_headless_uri(request, user):
op.save()
host = request.get_host()
taler_withdraw_uri = f"taler://withdraw/{host}/-/{op.withdraw_id}"
- return JsonResponse({
- "taler_withdraw_uri": taler_withdraw_uri,
- })
+ return JsonResponse({"taler_withdraw_uri": taler_withdraw_uri,})
+
##
# Serves a headless withdrawal request for the Taler protocol.
@@ -950,22 +939,20 @@ def withdraw_headless_uri(request, user):
@csrf_exempt
@require_POST
def withdraw_headless(request, user):
-
+
data = WithdrawHeadless(json.loads(decode_body(request)))
- sender_payto = "payto://x-taler-bank/%s/%d" % \
- (request.get_host(), user.bankaccount.account_no)
- ret_obj = ({"sender_wire_details": sender_payto})
+ sender_payto = "payto://x-taler-bank/%s/%d" % (
+ request.get_host(),
+ user.bankaccount.account_no,
+ )
+ ret_obj = {"sender_wire_details": sender_payto}
exchange_payto = data.get("exchange_wire_details")
if not exchange_payto:
- exchange_accno = get_acct_from_payto(
- settings.TALER_SUGGESTED_EXCHANGE_PAYTO
- )
+ exchange_accno = get_acct_from_payto(settings.TALER_SUGGESTED_EXCHANGE_PAYTO)
ret_obj.update(exchange_url=settings.TALER_SUGGESTED_EXCHANGE)
else:
- exchange_accno = get_acct_from_payto(
- exchange_payto
- )
+ exchange_accno = get_acct_from_payto(exchange_payto)
exchange_bankaccount = BankAccount.objects.get(account_no=exchange_accno)
@@ -973,7 +960,7 @@ def withdraw_headless(request, user):
Amount.parse(data.get("amount")),
user.bankaccount,
exchange_bankaccount,
- data.get("reserve_pub")
+ data.get("reserve_pub"),
)
return JsonResponse(ret_obj)
@@ -986,9 +973,7 @@ def api_withdraw_operation(request, withdraw_id):
try:
op = TalerWithdrawOperation.objects.get(withdraw_id=withdraw_id)
except ObjectDoesNotExist:
- return JsonResponse(
- dict(error="withdraw operation does not exist"), status=404
- )
+ return JsonResponse(dict(error="withdraw operation does not exist"), status=404)
user_acct_no = op.withdraw_account.account_no
host = request.get_host()
@@ -998,9 +983,7 @@ def api_withdraw_operation(request, withdraw_id):
try:
account_no = get_acct_from_payto(exchange_payto_uri)
except:
- return JsonResponse(
- dict(error="exchange payto URI malformed"), status=400
- )
+ return JsonResponse(dict(error="exchange payto URI malformed"), status=400)
try:
exchange_acct = BankAccount.objects.get(account_no=account_no)
except ObjectDoesNotExist:
@@ -1009,9 +992,7 @@ def api_withdraw_operation(request, withdraw_id):
)
selected_reserve_pub = data.get("reserve_pub")
if not isinstance(selected_reserve_pub, str):
- return JsonResponse(
- dict(error="reserve_pub must be a string"), status=400
- )
+ return JsonResponse(dict(error="reserve_pub must be a string"), status=400)
if op.selection_done or op.withdraw_done:
if (
op.selected_exchange_account != exchange_acct
@@ -1019,7 +1000,7 @@ def api_withdraw_operation(request, withdraw_id):
):
return JsonResponse(
dict(error="selection of withdraw parameters already done"),
- status=409
+ status=409,
)
# No conflict, same data!
return JsonResponse(dict(), status=200)
@@ -1038,14 +1019,12 @@ def api_withdraw_operation(request, withdraw_id):
sender_wire=f"payto://x-taler-bank/{host}/{user_acct_no}",
suggested_exchange=settings.TALER_SUGGESTED_EXCHANGE,
confirm_transfer_url=request.build_absolute_uri(
- reverse("withdraw-confirm", args=(withdraw_id, ))
- )
+ reverse("withdraw-confirm", args=(withdraw_id,))
+ ),
)
)
else:
- return JsonResponse(
- dict(error="only GET and POST are allowed"), status=305
- )
+ return JsonResponse(dict(error="only GET and POST are allowed"), status=305)
##
@@ -1097,9 +1076,7 @@ def show_withdrawal(request, withdraw_id):
def confirm_withdrawal(request, withdraw_id):
op = TalerWithdrawOperation.objects.get(withdraw_id=withdraw_id)
if not op.selection_done:
- raise Exception(
- "invalid state (withdrawal parameter selection not done)"
- )
+ raise Exception("invalid state (withdrawal parameter selection not done)")
if op.withdraw_done:
return redirect("profile")
if request.method == "POST":
@@ -1107,24 +1084,23 @@ def confirm_withdrawal(request, withdraw_id):
hashed_solution = request.POST.get("pin_1", "")
if hashed_attempt != hashed_solution:
LOGGER.warning(
- "Wrong CAPTCHA answer: %s vs %s", type(hashed_attempt),
- type(request.POST.get("pin_1"))
+ "Wrong CAPTCHA answer: %s vs %s",
+ type(hashed_attempt),
+ type(request.POST.get("pin_1")),
)
- request.session["captcha_failed"
- ] = True, False, "Wrong CAPTCHA answer."
+ request.session["captcha_failed"] = True, False, "Wrong CAPTCHA answer."
return redirect("withdraw-confirm", withdraw_id=withdraw_id)
op.withdraw_done = True
op.save()
wire_transfer(
- op.amount, BankAccount.objects.get(user=request.user),
- op.selected_exchange_account, op.selected_reserve_pub
+ op.amount,
+ BankAccount.objects.get(user=request.user),
+ op.selected_exchange_account,
+ op.selected_reserve_pub,
)
-
+
set_profile_hint(
- request,
- success=True,
- failure=False,
- hint="Withdrawal successful!"
+ request, success=True, failure=False, hint="Withdrawal successful!"
)
request.session["just_withdrawn"] = True
@@ -1137,7 +1113,7 @@ def confirm_withdrawal(request, withdraw_id):
hashed_answer=hashed_answer,
withdraw_id=withdraw_id,
amount=op.amount.stringify(settings.TALER_DIGITS),
- exchange=op.selected_exchange_account.user
+ exchange=op.selected_exchange_account.user,
)
return render(request, "withdraw_confirm.html", context)
raise Exception("not reached")
@@ -1153,9 +1129,12 @@ def confirm_withdrawal(request, withdraw_id):
# @return a @a BankTransaction object.
def wire_transfer(amount, debit_account, credit_account, subject):
LOGGER.debug(
- "%s => %s, %s, %s" % (
- debit_account.account_no, credit_account.account_no,
- amount.stringify(2), subject
+ "%s => %s, %s, %s"
+ % (
+ debit_account.account_no,
+ credit_account.account_no,
+ amount.stringify(2),
+ subject,
)
)
if debit_account.pk == credit_account.pk:
@@ -1166,7 +1145,7 @@ def wire_transfer(amount, debit_account, credit_account, subject):
amount=amount,
credit_account=credit_account,
debit_account=debit_account,
- subject=subject
+ subject=subject,
)
if debit_account.debit:
@@ -1196,12 +1175,16 @@ def wire_transfer(amount, debit_account, credit_account, subject):
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(
+ f"Account {debit_account.user.username} "
+ + f"(bal {debit_account.amount.stringify()}, thr {threshold.stringify()} requested transfer of"
+ + f"{threshold.stringify()} to account {credit_account.user.username}"
+ )
+
+ if Amount.cmp(debit_account.amount, threshold) > 0 and debit_account.debit:
raise DebitLimitException(
- f"Aborting payment initiated by '{debit_account.user.username}' for debit unallowed"
+ f"Aborting payment initiated by '{debit_account.user.username}', debit limit crossed."
)
with transaction.atomic():