[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[GNUnet-SVN] [taler-bank] 01/05: The big refactor.
From: |
gnunet |
Subject: |
[GNUnet-SVN] [taler-bank] 01/05: The big refactor. |
Date: |
Fri, 13 Jan 2017 14:59:48 +0100 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository bank.
commit af104fce9bc3b95c44371a1b137d1a733de027b2
Author: Florian Dold <address@hidden>
AuthorDate: Tue Nov 29 01:20:06 2016 +0100
The big refactor.
---
bank.conf | 2 +-
setup.py | 1 +
talerbank/app/admin.py | 4 +-
talerbank/app/amounts.py | 42 ++-
talerbank/app/captcha.py | 116 ------
talerbank/app/errors.py | 182 ---------
talerbank/app/funds.py | 174 ---------
talerbank/app/history.py | 72 ----
talerbank/app/middleware.py | 26 --
talerbank/app/migrations/0001_initial.py | 11 +-
talerbank/app/models.py | 13 +-
talerbank/app/schemas.py | 71 ++--
talerbank/app/static/profile-page.js | 35 +-
talerbank/app/templates/Makefile.am | 2 +-
talerbank/app/templates/account_disabled.html | 1 -
talerbank/app/templates/error_exchange.html | 12 +-
talerbank/app/templates/history.html | 33 --
.../app/templates/{home_page.html => login.html} | 61 ++-
talerbank/app/templates/pin_tan.html | 39 +-
talerbank/app/templates/profile_page.html | 36 +-
talerbank/app/templates/public_accounts.html | 81 ++++
.../app/templates/public_histories_reloaded.html | 113 ------
talerbank/app/templates/register.html | 7 +-
talerbank/app/templatetags/settings.py | 8 +
talerbank/app/urls.py | 23 +-
talerbank/app/user.py | 91 -----
talerbank/app/views.py | 414 +++++++++++++++++----
talerbank/settings.py | 61 ++-
28 files changed, 605 insertions(+), 1126 deletions(-)
diff --git a/bank.conf b/bank.conf
index 3d5be5d..67d4455 100644
--- a/bank.conf
+++ b/bank.conf
@@ -1,3 +1,3 @@
[bank]
uwsgi_serve = tcp
-database = talerbank
+database = postgres://talerbank
diff --git a/setup.py b/setup.py
index 6ccfbaa..fbb8af0 100755
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,7 @@ setup(name='talerbank',
package_data={
'talerbank.app': [
'templates/*.html',
+ 'templates/registration/*.html',
'static/web-common/*.js.tar.gz',
'static/web-common/*.css',
'static/web-common/*.html',
diff --git a/talerbank/app/admin.py b/talerbank/app/admin.py
index 8502f83..79b0037 100644
--- a/talerbank/app/admin.py
+++ b/talerbank/app/admin.py
@@ -1,7 +1,7 @@
# This file is in the public domain
from django.contrib import admin
-from .models import BankAccount, History
+from .models import BankAccount, BankTransaction
admin.site.register(BankAccount)
-admin.site.register(History)
+admin.site.register(BankTransaction)
diff --git a/talerbank/app/amounts.py b/talerbank/app/amounts.py
index a852fd6..93f65e6 100644
--- a/talerbank/app/amounts.py
+++ b/talerbank/app/amounts.py
@@ -13,36 +13,38 @@
# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
#
# @author Marcello Stanisci
+# @author Florian Dold
+
-from django.conf import settings
-from .errors import BadAmount
import re
import math
import logging
logger = logging.getLogger(__name__)
+FRACTION = 100000000
+
def floatify(amount_dict):
- return amount_dict['value'] + (float(amount_dict['fraction']) /
float(settings.FRACTION))
+ return amount_dict['value'] + (float(amount_dict['fraction']) /
float(FRACTION))
-def stringify_amount(amount_float):
- o = "".join(["%.", "%sf" % settings.NDIGITS])
+def stringify(amount_float, digits=2):
+ o = "".join(["%.", "%sf" % digits])
return o % amount_float
-# Return amount object from string like "x.yz CURRENCY".
-# We don't take "x CURRENCY", if no fractional part is needed;
-# use "x.00 CURRENCY" instead.
-
def parse_amount(amount_str):
- parsed = re.search('([0-9]+\.[0-9]+) ([-_*A-Za-z0-9]+)', amount_str)
+ """
+ Parse amount of return None if not a
+ valid amount string
+ """
+ parsed = re.search("^\s*([0-9]+)(\.[0-9]+)? ([-_*A-Za-z0-9]+)\s*$",
amount_str)
if not parsed:
- raise BadAmount(amount_str)
- amount = round(float(parsed.group(1)), settings.NDIGITS)
- amount_value = int(amount)
-
- if parsed.group(2) != settings.TALER_CURRENCY:
- raise BadAmount()
-
- return {'value': amount_value,
- 'fraction': int((amount - amount_value) * settings.FRACTION),
- 'currency': parsed.group(2)}
+ return None
+ value = int(parsed.group(1))
+ fraction = 0
+ if parsed.group(2) is not None:
+ for i, digit in enumerate(parsed.group(2)[1:]):
+ fraction += int(int(digit) * (FRACTION / 10 ** (i+1)))
+
+ return {'value': value,
+ 'fraction': fraction,
+ 'currency': parsed.group(3)}
diff --git a/talerbank/app/captcha.py b/talerbank/app/captcha.py
deleted file mode 100644
index 8caafdc..0000000
--- a/talerbank/app/captcha.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# 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 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/>
-#
-# @author Marcello Stanisci
-
-import hashlib
-import json
-import logging
-from urllib.parse import urlunparse, unquote, urlparse, ParseResult
-from simplemathcaptcha.fields import MathCaptchaField, MathCaptchaWidget
-from django.http import HttpResponse
-from django.shortcuts import render
-from django.conf import settings
-from django import forms
-from django.contrib.auth.decorators import login_required
-from django.views.decorators.http import require_POST, require_GET
-from .funds import Reserve, create_reserve_at_exchange
-from . import schemas
-from . import amounts
-from . import errors
-from .user import get_bank_account_from_username
-
-logger = logging.getLogger(__name__)
-
-
-class Pin(forms.Form):
- pin = MathCaptchaField(
- widget=MathCaptchaWidget(
- attrs=dict(autocomplete="off"),
- question_tmpl="<div lang=\"en\">What is %(num1)i %(operator)s
%(num2)i ?</div>"))
-
-
address@hidden
address@hidden
-def pin_tan_question(request):
- for param in ["amount_value",
- "amount_fraction",
- "amount_currency",
- "exchange",
- "reserve_pub",
- "wire_details"]:
- if param not in request.GET:
- raise errors.BadGetParameter(param)
- try:
- amount = {"value": int(request.GET["amount_value"]),
- "fraction": int(request.GET["amount_fraction"]),
- "currency": request.GET["amount_currency"]}
- except ValueError:
- raise errors.BadGetParameter()
- wiredetails = json.loads(unquote(request.GET["wire_details"]))
- schemas.validate_wiredetails(wiredetails)
- request.session["account_number"] = wiredetails["test"]["account_number"]
- request.session["wire_details"] = wiredetails["test"]
- schemas.validate_amount(amount)
- request.session["amount"] = amount
- request.session["exchange"] = request.GET["exchange"]
- request.session["reserve_pub"] = request.GET["reserve_pub"]
- previous_failed = False
- if "captcha_failed" in request.session:
- previous_failed = True
- del request.session["captcha_failed"]
- return render(request, "pin_tan.html", {"form": Pin(auto_id=False),
- "failed":
request.GET.get("failed"),
- "amount": amounts.floatify(amount),
- "currency":
settings.TALER_CURRENCY,
- "previous_failed": previous_failed,
- "exchange":
request.GET["exchange"]})
-
-
address@hidden
address@hidden
-def pin_tan_verify(request):
- try:
- given = request.POST["pin_0"]
- hashed_result = request.POST["pin_1"]
- except Exception: # FIXME narrow the Exception type
- raise errors.BadPostValue()
- hasher = hashlib.new("sha1")
- hasher.update(settings.SECRET_KEY.encode("utf-8"))
- hasher.update(given.encode("utf-8"))
- hashed_attempt = hasher.hexdigest()
- if hashed_attempt != hashed_result:
- request.session["captcha_failed"] = True
- return redirect("pin-question")
-
- for param in ["amount", "exchange", "reserve_pub"]:
- if param not in request.session:
- return HttpResponse("Not a withdraw session", status=400)
- settings.TALER_WIREDETAILS_COUNTER += 1
- sender_wiredetails = dict(
- type="TEST",
- bank_uri=urlunparse(ParseResult(scheme=request.scheme,
netloc=request.META['HTTP_HOST'],
- path="/", params="", query="", fragment="")),
- account_number=request.session['account_no']
- )
- reserve = Reserve(request.session["amount"],
- request.session["exchange"],
- request.session["account_number"],
- request.session["reserve_pub"],
- sender_wiredetails,
- wiredetails_counter=settings.TALER_WIREDETAILS_COUNTER)
- success_url = urlunparse([request.scheme,
- request.META["HTTP_HOST"],
- "/success.html", "", "", ""])
- return create_reserve_at_exchange(request, success_url, reserve)
diff --git a/talerbank/app/errors.py b/talerbank/app/errors.py
deleted file mode 100644
index 4619a1b..0000000
--- a/talerbank/app/errors.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# 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 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/>
-#
-# @author Marcello Stanisci
-
-from django.shortcuts import render
-from django.http import JsonResponse
-import logging
-
-logger = logging.getLogger(__name__)
-
-class RenderedException(Exception):
- """An exception that is rendered by us"""
- def render(self, request):
- raise Exception("RenderedException must override render()")
-
-
-class BadGetParameter(RenderedException):
- def __init__(self, name=None):
- self.name = name
- def render(self, request):
- # the bank has no REST service available on GET,
- # so no JSON returned in this case
- return bad_get_parameter_handler(request, name)
-
-# Raised either at withdrawal time (prettier rendering needed (?)),
-# or when the exchange does deposits (JSON object returned is fine)
-class BadAmount(RenderedException):
- def __init__(self, name=None):
- self.name = name
- def render(self, request):
- data = {"error": "bad amount"}
- if self.name:
- data["parameter"] = self.name
- return JsonResponse(data, status=400)
-
-
-class BadWireDetails(RenderedException):
- def __init__(self, name=None, hint=None):
- self.name = name
- self.hint = hint
- def render(self, request):
- data = {"error": "bad wire details"}
- if self.name:
- data["parameter"] = self.name
- if self.hint:
- data["hint"] = self.hint
- return JsonResponse(data, status=400)
-
-
-class BadIncomingRequest(RenderedException):
- """Thrown for an /admin/add/incoming call"""
- def __init__(self, name=None, hint=None):
- self.name = name
- self.hint = hint
- def render(self, request):
- data = {"error": "malformed JSON"}
- if self.hint:
- data["hint"] = self.hint
- return JsonResponse(data, status=400)
-
-
-class NonExistentAccount(RenderedException):
- """Thrown whenever a non existent account is referenced"""
- def __init__(self, name=None, hint=None):
- self.name = name
- self.hint = hint
- def render(self, request):
- data = {"error": "non existent account"}
- if self.hint:
- logger.error(self.hint)
- data["hint"] = self.hint
- return JsonResponse(data, status=400)
-
-class NoWireMethodMatch(RenderedException):
- def __init__(self):
- pass
- def render(self, request):
- return non_suppoerted_wire_method(request)
-
-
-class BadPostValue(RenderedException):
- def __init__(self):
- pass
- def render(self, request):
- return bad_post_value_handler(request)
-
-
-class WrongMethod(RenderedException):
- def __init__(self, allowed_method=None):
- self.allowed_method = allowed_method
- def render(self, request):
- data = {"error": "wrong method"}
- if self.allowed_method:
- data["parameter"] = self.allowed_method
- return JsonResponse(data)
-
-
-class ExchangeUnknown(RenderedException):
- def __init__(self):
- pass
- def render(self, request):
- return exchange_unknown_handler(request)
-
-
-class CurrencyMismatch(RenderedException):
- def __init__(self, allowed_method=None):
- pass
- def render(self, request):
- data = {"error": "currency mismatch"}
- return JsonResponse(data)
-
-
-def no_bank_account_handler(request):
- return internal_error_handler(
- request,
- "(The bank itself has no account,"
- " please run 'taler-bank-manage --preaccounts')")
-
-
-def non_supported_wire_method(request):
- return render(request, 'error.html', {'type': 'non_supported_method'},
status=400)
-
-
-def non_existent_db_handler(request):
- return internal_error_handler(request, "(db does not exist)")
-
-
-def internal_error_handler(request, hint=False):
- return render(request, 'error.html', {'type': 'internal_error', 'hint':
hint}, status=500)
-
-
-def user_not_logged_handler(request):
- return render(request, 'error.html', {'type': 'not_logged'}, status=401)
-
-
-def exchange_unknown_handler(request):
- return render(request, 'error.html', {'type': 'exchange_unknown'},
status=401)
-
-
-def wrong_method_handler(request, err):
- return render(request,
- 'error.html',
- {'type': 'custom',
- 'custom_title': "Wrong method",
- 'custom_message': "Only " + err.allowed_method + "
allowed"},
- status=405)
-
-
-def bad_get_parameter_handler(request, name):
- msg = "Invalid parameter {!s}in GET request"
- if name:
- msg = msg.format(name + " ")
- else:
- msg = msg.format("")
-
- return render(request,
- 'error.html',
- {'type': 'custom',
- 'custom_title': "Bad request",
- 'custom_message': msg},
- status=405)
-
-
-def bad_post_value_handler(request):
- return render(request,
- 'error.html',
- {'type': 'custom',
- 'custom_title': "Bad request",
- 'custom_message': "Bad value in POSTed data"},
- status=400)
diff --git a/talerbank/app/funds.py b/talerbank/app/funds.py
deleted file mode 100644
index ea232f4..0000000
--- a/talerbank/app/funds.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# 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 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/>
-#
-# @author Marcello Stanisci
-
-from . import schemas
-from .errors import WrongMethod, BadWireDetails, CurrencyMismatch,
NonExistentAccount
-from . import amounts
-from .models import BankAccount, History
-from django.views.decorators.csrf import csrf_exempt
-from django.http import JsonResponse, HttpResponse
-from django.shortcuts import render, redirect
-from django.http import HttpResponseServerError
-from django.contrib.auth.decorators import login_required
-from urllib.parse import urlparse, urljoin
-from django.conf import settings
-from django.core.urlresolvers import reverse
-import requests
-import time
-import json
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-
-def check_exchange_account_no(account_no):
- try:
- BankAccount.objects.get(account_no=account_no)
- except BankAccount.DoesNotExist:
- raise errors.ExchangeUnknown()
-
-
-class Reserve:
- def __init__(self, amount, exchange, exchange_account, reserve_pub,
sender_account_details, wiredetails_counter=0):
- schemas.validate_amount(amount)
- self.amount = amount
- self.exchange = exchange
- self.exchange_account = exchange_account
- self.reserve_pub = reserve_pub
- self.sender_account_details = sender_account_details
- self.transfer_details = wiredetails_counter
-
-# The CSRF exempt is due to the fact Django looks for an anti-CSRF token
-# In any POST it gets. Since the following function is meant to serve
exchanges,
-# And exchanges don't send any such token, it is necessary to disable it.
-# Those tokens are normally hidden fields in Django-generated HTML forms.
-
-
address@hidden
-def add_incoming(request):
- logger.info("handling /admin/add/incoming")
- if request.method != 'POST':
- raise WrongMethod('GET')
- data = json.loads(request.body.decode('utf-8'))
- logger.info("valid uploaded data")
- schemas.validate_incoming_request(data)
- logger.info("add_incoming for debit account %s and credit account %s, WTID
%s",
- data['debit_account'],
- data['credit_account'],
- data['wtid'])
-
- wire_transfer_in_out(data['amount'],
- data['debit_account'],
- data['credit_account'],
- data['wtid'])
- return JsonResponse({'outcome': 'ok'}, status=200)
-
-
address@hidden
-def withdraw_nojs(request):
- amount = amounts.parse_amount(request.POST['kudos_amount'])
- response = HttpResponse(status=202)
- response['X-Taler-CallBack-Url'] = reverse('pin-question')
- response['X-Taler-Wt-Types'] = '["TEST"]'
- response['X-Taler-Amount'] = json.dumps(amount)
- return response
-
-
-def create_reserve_at_exchange(request, success_url, reserve_set):
- if not isinstance(reserve_set, Reserve):
- return HttpResponseServerError
- o = urlparse(reserve_set.exchange)
- if o.scheme == '' or o.netloc == '' or not o.path.endswith('/'):
- return JsonResponse({'error': 'bad exchange base url',
- 'given_url': reserve_set.exchange}, status=400)
- amount = {'value': reserve_set.amount['value'],
- 'fraction': reserve_set.amount['fraction'],
- 'currency': reserve_set.amount['currency']}
- check_exchange_account_no(reserve_set.exchange_account)
- json_body = {'reserve_pub': reserve_set.reserve_pub,
- 'execution_date': "/Date(" + str(int(time.time())) + ")/",
- 'sender_account_details': reserve_set.sender_account_details,
- 'transfer_details': {'uuid': reserve_set.transfer_details},
- 'amount': amount}
- res = requests.post(urljoin(reserve_set.exchange, '/admin/add/incoming'),
json=json_body)
- logger.info("making request to /admin/add/incoming with %s", json_body)
- if res.status_code != 200:
- return render(request, "error_exchange.html", {
- "message": "Could not transfer funds to the exchange. The
exchange ({}) gave a bad response.".format(reserve_set.exchange),
- "response_text": res.text,
- "response_status": res.status_code
- })
-
- wire_transfer_in_out(amount,
- request.session['account_no'],
- reserve_set.exchange_account,
- reserve_set.reserve_pub)
- request.session['withdrawal_successful'] = True
- return redirect('profile')
-
-
-
-def wire_transfer_in_out(amount,
- debit,
- credit,
- wtid):
- if debit == credit:
- raise BadWireDetails(hint="debit and credit accounts are the same")
- try:
- debit_account = BankAccount.objects.get(account_no=debit)
- except BankAccount.DoesNotExist:
- raise NonExistentAccount(hint="account " + str(debit) + " unknown")
- try:
- credit_account = BankAccount.objects.get(account_no=credit)
- except BankAccount.DoesNotExist:
- raise NonExistentAccount(hint="account " + str(credit) + " unknown")
- wire_transfer(amount,
- debit_account,
- "OUT",
- credit_account,
- wtid)
- wire_transfer(amount,
- credit_account,
- "IN",
- debit_account,
- wtid)
-
-
-# transfer funds from/to 'account' (used as a subroutine)
-def wire_transfer(amount,
- account,
- direction,
- counterpart,
- wtid="not given"):
- if account.currency != amount['currency']:
- logger.error("currency %s and currency %s mismatch",
- account.currency,
- amount['currency'])
- raise CurrencyMismatch()
- float_amount = amounts.floatify(amount)
- if "IN" == direction:
- account.balance += float_amount
- else:
- account.balance -= float_amount
- account.save()
- history_item = History(amount=float_amount,
- currency=amount['currency'],
- direction=direction,
- counterpart=counterpart,
- subject=wtid,
- account=account)
- history_item.save()
diff --git a/talerbank/app/history.py b/talerbank/app/history.py
deleted file mode 100644
index db3d6da..0000000
--- a/talerbank/app/history.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# 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 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/>
-#
-# @author Marcello Stanisci
-
-from .models import BankAccount
-from .errors import internal_error_handler
-from django.contrib.auth.decorators import login_required
-from django.shortcuts import render, redirect
-from django.conf import settings
-from .user import get_bank_account_from_username
-from .amounts import stringify_amount
-import logging
-
-logger = logging.getLogger(__name__)
-
-def get_public_accounts():
- try:
- return BankAccount.objects.filter(is_public=True)
- except BankAccount.DoesNotExist:
- return []
-
-
-
-def extract_history(bank_account):
- ret = []
- for item in bank_account.history_set.all():
- entry = {'float_amount': stringify_amount(item.amount),
- 'float_currency': item.currency,
- 'direction': 'FROM' if item.direction == 'IN' else 'TO',
- 'counterpart': item.counterpart.account_no,
- 'subject': item.subject,
- 'date': item.date.strftime("%d/%m/%y %H:%M")} # Yes, ugly.
- logger.info(item.counterpart.user.username)
- # we don't make visible regular users' usernames
- if item.counterpart.user.username in
settings.TALER_PREDEFINED_ACCOUNTS + ["Bank", "Exchange"]:
- entry['counterpart_username'] = item.counterpart.user.username
- ret.append(entry)
- return ret
-
-
-def public_accounts(request, name):
- accounts = []
- for item in get_public_accounts():
- accounts.append({'account_name': item.user.username})
- sel_account_name = request.GET.get('account')
- if not sel_account_name:
- return redirect("public-accounts", account="Tor")
- sel_account = get_bank_account_from_username(sel_account_name)
- if sel_account is False:
- return internal_error_handler(request, "User '%s' does not exist" %
(sel_account_name,))
- history = extract_history(sel_account)
- return render(request,
- 'public_histories_reloaded.html',
- {'public_accounts': accounts,
- 'currency': settings.TALER_CURRENCY,
- 'selected_account':
- {'account_name': sel_account_name,
- 'account_no': sel_account.account_no,
- 'history': history}
- })
diff --git a/talerbank/app/middleware.py b/talerbank/app/middleware.py
deleted file mode 100644
index b2b2292..0000000
--- a/talerbank/app/middleware.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# This file is part of TALER
-# (C) 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 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/>
-#
-# @author Marcello Stanisci
-# @author Florian Dold
-
-from . import errors
-
-
-class ExpectedExceptionsMiddleware:
- def process_exception(self, request, exception):
- if not isinstance(exception, errors.RenderedException):
- # Leave handling up to django
- return None
- return exception.render(request)
diff --git a/talerbank/app/migrations/0001_initial.py
b/talerbank/app/migrations/0001_initial.py
index f4a5d0c..310fc0c 100644
--- a/talerbank/app/migrations/0001_initial.py
+++ b/talerbank/app/migrations/0001_initial.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Generated by Django 1.10.1 on 2016-09-30 20:07
+# Generated by Django 1.10.3 on 2016-11-29 14:35
from __future__ import unicode_literals
from django.conf import settings
@@ -27,16 +27,15 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
- name='History',
+ name='BankTransaction',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True,
serialize=False, verbose_name='ID')),
('amount', models.FloatField(default=0)),
('currency', models.CharField(max_length=12)),
- ('direction', models.CharField(max_length=4)),
- ('subject', models.CharField(default='not given',
max_length=200)),
+ ('subject', models.CharField(default='(no subject given)',
max_length=200)),
('date', models.DateTimeField(auto_now=True)),
- ('account',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='app.BankAccount')),
- ('counterpart',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='counterpart_account', to='app.BankAccount')),
+ ('credit_account',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='credit_account', to='app.BankAccount')),
+ ('debit_account',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
related_name='debit_account', to='app.BankAccount')),
],
),
]
diff --git a/talerbank/app/models.py b/talerbank/app/models.py
index 2c4d44d..1b84fe9 100644
--- a/talerbank/app/models.py
+++ b/talerbank/app/models.py
@@ -13,6 +13,7 @@
# TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
#
# @author Marcello Stanisci
+# @author Florian Dold
from __future__ import unicode_literals
from django.contrib.auth.models import User
@@ -27,14 +28,10 @@ class BankAccount(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
-class History(models.Model):
+class BankTransaction(models.Model):
amount = models.FloatField(default=0)
currency = models.CharField(max_length=12)
- direction = models.CharField(max_length=4)
- counterpart = models.ForeignKey(BankAccount,
- on_delete=models.CASCADE,
- related_name="counterpart_account")
- subject = models.CharField(default="not given", max_length=200)
+ debit_account = models.ForeignKey(BankAccount, on_delete=models.CASCADE,
related_name="debit_account")
+ credit_account = models.ForeignKey(BankAccount, on_delete=models.CASCADE,
related_name="credit_account")
+ subject = models.CharField(default="(no subject given)", max_length=200)
date = models.DateTimeField(auto_now=True)
- account = models.ForeignKey(BankAccount, on_delete=models.CASCADE)
- # NOTE: by default, UTC timezone is used.
diff --git a/talerbank/app/schemas.py b/talerbank/app/schemas.py
index fd71b14..5dfcbce 100644
--- a/talerbank/app/schemas.py
+++ b/talerbank/app/schemas.py
@@ -20,46 +20,49 @@ definitions of JSON schemas for validating data
"""
import validictory
-from .errors import BadIncomingRequest, BadWireDetails, BadAmount
+from django.core.exceptions import ValidationError
-wiredetails_schema = {"type": "object",
- "properties": {
- "test": {"type": "object",
- "properties": {
- "type": {"type": "string"},
- "account_number": {"type": "integer"},
- "bank_uri": {"type": "string"},
- "name": {"type": "string"},
- "salt": {"type": "string"},
- "sig": {"type": "string"}}}}}
+wiredetails_schema = {
+ "type": "object",
+ "properties": {
+ "test": {
+ "type": "object",
+ "properties": {
+ "type": {"type": "string"},
+ "account_number": {"type": "integer"},
+ "bank_uri": {"type": "string"},
+ "name": {"type": "string"},
+ "salt": {"type": "string"},
+ "sig": {"type": "string"}
+ }
+ }
+ }
+}
-amount_schema = {"type": "object",
- "properties": {
- "value": {"type": "integer"},
- "fraction": {"type": "integer"},
- "currency": {"type": "string"}}}
+amount_schema = {
+ "type": "object",
+ "properties": {
+ "value": {"type": "integer"},
+ "fraction": {"type": "integer"},
+ "currency": {"type": "string"}
+ }
+}
-incoming_request_schema = {"type": "object",
- "properties": {
- "amount": {"type": amount_schema},
- "wtid": {"type": "string"},
- "credit_account": {"type": "integer"},
- "debit_account": {"type": "integer"}}}
+incoming_request_schema = {
+ "type": "object",
+ "properties": {
+ "amount": {"type": amount_schema},
+ "wtid": {"type": "string"},
+ "credit_account": {"type": "integer"},
+ "debit_account": {"type": "integer"}
+ }
+}
def validate_amount(amount):
- try:
- validictory.validate(amount, amount_schema)
- except (ValueError, TypeError):
- raise BadAmount()
+ validictory.validate(amount, amount_schema)
def validate_wiredetails(wiredetails):
- try:
- validictory.validate(wiredetails, wiredetails_schema)
- except (ValueError, TypeError) as e:
- raise BadWireDetails(str(e))
+ validictory.validate(wiredetails, wiredetails_schema)
def validate_incoming_request(incoming_request):
- try:
- validictory.validate(incoming_request, incoming_request_schema)
- except (ValueError, TypeError):
- raise BadIncomingRequest()
+ validictory.validate(incoming_request, incoming_request_schema)
diff --git a/talerbank/app/static/profile-page.js
b/talerbank/app/static/profile-page.js
index 62f8eb3..f0791e3 100644
--- a/talerbank/app/static/profile-page.js
+++ b/talerbank/app/static/profile-page.js
@@ -43,23 +43,6 @@ function getConst(name) {
}
-function addOption(value, currency) {
- var s = document.getElementById("reserve-amount");
- var e = document.createElement("option");
- e.textContent = "".concat(value.toFixed(precision), " ", currency);
- e.value = value;
- s.appendChild(e);
-}
-
-
-function addOptions() {
- addOption(1, bank_currency);
- addOption(5, bank_currency);
- addOption(10, bank_currency);
- addOption(20, bank_currency);
-}
-
-
/**
* Parse fractional format (e.g. 42.12 EUR) into
* Taler amount.
@@ -77,9 +60,11 @@ function parseAmount(amount_str) {
amount_fraction = Number("0." + amount[2]) * 1000000;
if (amount[1] + amount_fraction == 0)
return null;
- return {value: Number(amount[1]),
- fraction: amount_fraction,
- currency: amount[amount.length - 1]};
+ return {
+ value: Number(amount[1]),
+ fraction: amount_fraction,
+ currency: amount[amount.length - 1]
+ };
};
@@ -92,13 +77,12 @@ function init() {
console.log("confirming reserve", reserve_pub);
taler.confirmReserve(reserve_pub);
}
- addOptions();
- document.getElementById("select-exchange").onclick = function() {
+ document.getElementById("select-exchange").onclick = function () {
var form = document.getElementById("reserve-form");
var amount = {
- value: parseInt(form.elements["reserve-amount"].value),
- fraction: 0,
- currency: bank_currency
+ value: parseInt(form.elements["reserve-amount"].value),
+ fraction: 0,
+ currency: bank_currency
};
console.log("callback_url", callback_url);
taler.createReserve(callback_url, amount, ["TEST"]);
@@ -111,4 +95,3 @@ if (document.readyState == "loading") {
} else {
init();
}
-
diff --git a/talerbank/app/templates/Makefile.am
b/talerbank/app/templates/Makefile.am
index 7be4a12..34751ff 100644
--- a/talerbank/app/templates/Makefile.am
+++ b/talerbank/app/templates/Makefile.am
@@ -5,7 +5,7 @@ EXTRA_DIST = \
account_disabled.html \
history.html \
profile_page.html \
- public_histories_reloaded.html \
+ public_accounts.html \
error.html \
home_page.html \
pin_tan.html \
diff --git a/talerbank/app/templates/account_disabled.html
b/talerbank/app/templates/account_disabled.html
index 776d24c..d2ebfb4 100644
--- a/talerbank/app/templates/account_disabled.html
+++ b/talerbank/app/templates/account_disabled.html
@@ -17,7 +17,6 @@
@author Marcello Stanisci
-->
{% extends "base.html" %}
-{% load staticfiles %}
{% block headermsg %}
<h1 class="nav">Account disabled<h1>
diff --git a/talerbank/app/templates/error_exchange.html
b/talerbank/app/templates/error_exchange.html
index 1bb1c4a..e3319a6 100644
--- a/talerbank/app/templates/error_exchange.html
+++ b/talerbank/app/templates/error_exchange.html
@@ -15,9 +15,9 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
@author Marcello Stanisci
+ @author Florian Dold
-->
{% extends "base.html" %}
-{% load staticfiles %}
{% block headermsg %}
<h1>Error</h1>
@@ -30,13 +30,13 @@
<p>
{{ message }}
</p>
-
+ <p>
Status: {{ response_status }}
-
+ </p>
+ <p>
Response body:
- <pre>
- {{ response_text }}
- </pre>
+ <pre>{{ response_text }}</pre>
+ </p>
</article>
</section>
{% endblock content %}
diff --git a/talerbank/app/templates/history.html
b/talerbank/app/templates/history.html
deleted file mode 100644
index 9af14ec..0000000
--- a/talerbank/app/templates/history.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<!--
- This file is part of GNU TALER.
- Copyright (C) 2014, 2015, 2016 INRIA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free
Software
- Foundation; either version 2.1, 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 Lesser General Public License for more
details.
-
- You should have received a copy of the GNU Lesser General Public License
along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-
- @author Marcello Stanisci
--->
-
-{% extends "base.html" %}
-
-{% block content %}
- {% if history %}
- <ul>
- {% for item in history %}
- <li> {{ item.float_amount }} {{ item.float_currency }} {{
item.direction }} {{ item.counterpart }}</li>
- {% endfor %}
- </ul>
- {% else %}
- No transactions made to/from this account
- {% endif %}
-{% endblock content %}
-
diff --git a/talerbank/app/templates/home_page.html
b/talerbank/app/templates/login.html
similarity index 51%
rename from talerbank/app/templates/home_page.html
rename to talerbank/app/templates/login.html
index 8fbfa49..b9d6020 100644
--- a/talerbank/app/templates/home_page.html
+++ b/talerbank/app/templates/login.html
@@ -15,69 +15,62 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
@author Marcello Stanisci
+ @author Florian Dold
-->
{% extends "base.html" %}
-{% load staticfiles %}
+{% load settings_value from settings %}
{% block headermsg %}
- <h1 lang="en" class="nav">Welcome to the {{ currency }} Bank!</h1>
- <h1 lang="it" class="nav">Banca {{ currency }} vi da il benvenuto!</h1>
+ <h1 lang="en" class="nav">Welcome to the {% settings_value "TALER_CURRENCY"
%} Bank!</h1>
{% endblock headermsg %}
{% block content %}
<aside class="sidebar" id="left">
</aside>
<section id="main">
- {% if js == 'use_js' %}
- You are using the JavaScript version, try out the
- <a href="{% url 'index' js='no_js' %}">JavaScript-less version!</a>
- {% else %}
- You are using the JavaScript-less version, try out the
- <a href="{% url 'index' js='use_js' %}">JavaScript version!</a>
- {% endif %}
- <br>
<article>
<div class="login-form">
- <h1 lang="en">Please login!</h1>
- <h1 lang="it">Accedi!</h1>
- {% if wrong %}
- <p class="informational informational-fail">
- Some fields were either not filled or filled incorrectly.
- Please try again.
+ <h1>Please login!</h1>
+ {% if form.errors %}
+ <p class="informational informational-fail">
+ Your username and password didn't match. Please try again.
</p>
{% endif %}
- {% if logged_out %}
- <p class="informational informational-ok">
- Successfully logged out!
+
+ {% if just_logged_out %}
+ <p class="informational informational-ok">
+ You were logged out successfully.
</p>
{% endif %}
+
+ {% if next %}
+ {% if user.is_authenticated %}
+ <p class="informational informational-fail">Your account doesn't
have access to this page. To proceed,
+ please login with an account that has access.</p>
+ {% else %}
+ <p>Please login to see this page.</p>
+ {% endif %}
+ {% endif %}
<table>
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
- <input type="text" name="username" placeholder="username"
autofocus></input>
+ {{ form.username }}
<input type="password" name="password"
placeholder="password"></input>
- <input type="submit" value="Ok"></input>
+ <input type="submit" value="login" />
+ <input type="hidden" name="next" value="{{ next }}" />
</form>
</table>
</div>
- <p lang="en">
+ <p>
If you are a new customer, please <a href="{% url 'register'
%}">register</a>.
Registration is fast and gratis, and it gives you a registration bonus
- of 100 {{ currency }}!
+ of 100 {% settings_value "TALER_CURRENCY" %}!
</p>
- <p lang="it">
- Se sei un nuovo cliente, puoi <a href="{% url 'register'
%}">registrarti</a>.
- La registrazione è semplice e veloce, e ti regala un bonus di 100
{{ currency }}!
- </p>
- <p lang="en">
- To view account histories for public accounts,
+ <p>
+ To view transactions of public accounts,
please <a href="{% url "public-accounts" %}">click here</a>.
</p>
- <p lang="it">
- Per vedere l'attività dei conti pubblici,
- <a href="{% url "public-accounts" %}">clicca qui</a>.
- </p>
</article>
</section>
{% endblock content %}
diff --git a/talerbank/app/templates/pin_tan.html
b/talerbank/app/templates/pin_tan.html
index 25de2d0..244cf9c 100644
--- a/talerbank/app/templates/pin_tan.html
+++ b/talerbank/app/templates/pin_tan.html
@@ -18,11 +18,13 @@
-->
{% extends "base.html" %}
-{% load staticfiles %}
+
+{% load settings_value from settings %}
{% block headermsg %}
<h1 class="nav">PIN/TAN: Confirm transaction</h1>
{% endblock %}
+
{% block content %}
<aside class="sidebar" id="left">
</aside>
@@ -34,8 +36,8 @@
</p>
{% endif %}
<p>
- {{ currency }} Bank needs to verify that you
- intend to withdraw <b>{{ amount }} {{ currency }}</b> from
+ {% settings_value "TALER_CURRENCY" %} Bank needs to verify that you
+ intend to withdraw <b>{{ amount }} {% settings_value "TALER_CURRENCY"
%}</b> from
<b>{{ exchange }}</b>.
To prove that you are the account owner, please answer the
following "security question" (*):
@@ -43,6 +45,7 @@
<form method="post" action="{% url "pin-verify" %}">
{% csrf_token %}
{{ form.pin }}
+ <input type="hidden" name="question_url" value="{{
request.get_full_path }}"></input>
<input type="submit" value="Ok"></input>
</form>
<small style="margin: 40px 0px">(*) A real bank should ask for
@@ -52,34 +55,4 @@
<small>
</article>
</section>
- <script>
- /*
- @licstart The following is the entire license notice for the
- JavaScript code in this page.
-
- Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
-
- The JavaScript code in this page is free software: you can
- redistribute it and/or modify it under the terms of the GNU
- General Public License (GNU GPL) as published by the Free Software
- Foundation, either version 3 of the License, or (at your option)
- any later version. The code is distributed WITHOUT ANY WARRANTY;
- without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
-
- As additional permission under GNU GPL version 3 section 7, you
- may distribute non-source (e.g., minimized or compacted) forms of
- that code without the copy of the GNU GPL normally required by
- section 4, provided you include this license notice and a URL
- through which recipients can access the Corresponding Source.
-
- @licend The above is the entire license notice
- for the JavaScript code in this page.
- */
- </script>
- <script type="application/javascript">
- var i = document.getElementsByName("pin_0")[0];
- i.setAttribute("autofocus", true);
- i.focus();
- </script>
{% endblock content %}
diff --git a/talerbank/app/templates/profile_page.html
b/talerbank/app/templates/profile_page.html
index 752432f..c3e6eda 100644
--- a/talerbank/app/templates/profile_page.html
+++ b/talerbank/app/templates/profile_page.html
@@ -17,7 +17,7 @@
@author Marcello Stanisci
-->
{% extends "base.html" %}
-{% load staticfiles %}
+{% load static from mystatic %}
{% block head %}
<meta name="currency" value="{{ currency }}">
@@ -28,7 +28,7 @@
{% endif %}
<link rel="stylesheet" type="text/css" href="{% static "disabled-button.css"
%}">
<script src="{% static "chrome-store-link.js" %}"
type="application/javascript"></script>
- {% if js == 'use_js' %}
+ {% if use_js %}
<script src="{% static "profile-page.js" %}"
type="application/javascript"></script>
{% endif %}
{% endblock head %}
@@ -53,24 +53,16 @@
<section id="main">
<article>
<div class="notification">
- {% if withdraw %}
- {% if withdraw == "success" %}
+ {% if just_withdrawn %}
<p class="informational informational-ok">
Withdrawal approved!
</p>
- {% else %}
- <p class="informational informational-fail">
- Withdrawal failed!
- </p>
{% endif %}
- {% endif %}
- {% if registration %}
- {% if registration == "success" %}
+ {% if just_registered %}
<p class="informational informational-ok">
Registration successful!
</p>
{% endif %}
- {% endif %}
</div>
</article>
<article>
@@ -104,20 +96,14 @@
{% csrf_token %}
Amount to withdraw:
<select id="reserve-amount" name="kudos_amount" autofocus>
- {% if js == 'use_js' %}
- <!-- programmatically filled -->
- {% else %}
- <!-- NOTE: the fractional part has to match settings.NDIGITS
- FIXME: provide values programmatically -->
<option value="1.00 {{ currency }}">1.00 {{ currency }}</option>
<option value="10.00 {{ currency }}">10.00 {{ currency
}}</option>
<option value="15.00 {{ currency }}">15.00 {{ currency
}}</option>
<option value="20.00 {{ currency }}">20.00 {{ currency
}}</option>
- {% endif %}
</select>
<input id="select-exchange"
class="taler-installed-show"
- {% if js == 'use_js' %}
+ {% if use_js %}
type="button"
{% else %}
type="submit"
@@ -130,6 +116,13 @@
</div>
</form>
</div>
+ <p>
+ {% if use_js %}
+ You're using the JavaScript version of the bank. You can <a href="{%
url 'profile' %}?use_js=false">switch</a> to the JS-free version.
+ {% else %}
+ You're using the JavaScript-free version of the bank. You can <a
href="{% url 'profile' %}?use_js=true">switch</a> to the JS version.
+ {% endif %}
+ </p>
</article>
<article>
<h2>Transaction history</h2>
@@ -147,8 +140,7 @@
<tr>
<td style="text-align:right">{{ item.date }}</td>
<td style="text-align:right">
- {% if item.direction == "FROM" %}+{% else %}-{% endif %}{{
item.float_amount }}
- {{ item.float_currency }}
+ {{ item.float_amount }} {{ item.float_currency }}
</td>
<td class="text-align:left">{% if item.counterpart_username %} {{
item.counterpart_username }} {% endif %} (account #{{ item.counterpart }})</td>
<td class="text-align:left">{{ item.subject }}</td>
@@ -167,6 +159,6 @@
</section>
<div class="copyright">
<p>Copyright © 2014—2016 INRIA</p>
- <a href="/javascript" data-jslicense="1" class="jslicenseinfo">JavaScript
license information</a>
+ <a href="{% url "javascript" %}" data-jslicense="1"
class="jslicenseinfo">JavaScript license information</a>
</div>
{% endblock %}
diff --git a/talerbank/app/templates/public_accounts.html
b/talerbank/app/templates/public_accounts.html
new file mode 100644
index 0000000..d6c2519
--- /dev/null
+++ b/talerbank/app/templates/public_accounts.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<!--
+ This file is part of GNU TALER.
+ Copyright (C) 2014, 2015, 2016 INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free
Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more
details.
+
+ You should have received a copy of the GNU Lesser General Public License
along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Marcello Stanisci
+-->
+{% extends "base.html" %}
+
+{% load static from mystatic %}
+
+{% block headermsg %}
+ <h1 class="nav">History of public accounts</h1>
+{% endblock headermsg %}
+
+{% block content %}
+ <aside class="sidebar" id="left">
+ </aside>
+ <section id="main">
+ <article>
+ <table bgcolor="#E0E0E0" width="100%" width="100%" border="0"
cellpadding="2" cellspacing="1">
+ <tr>
+ {% for account in public_accounts %}
+ <td width="12%" align="center">
+ <a id="{{ account.user.username }}"
+ href="{% url "public-accounts" name=account.user.username %}"
+ {% if account.account_no == selected_account.number %}
+ style="font-weight: bold"
+ {% endif %}
+ >
+ {{ account.user.username }}
+ </a>
+ </td>
+ {% endfor %}
+ </tr>
+ </table>
+ <div id="transactions-history">
+ {% if selected_account.history %}
+ <table class="history">
+ <tr>
+ <th style="text-align:center">Date</th>
+ <th style="text-align:center">Amount</th>
+ <th style="text-align:center">Counterpart</th>
+ <th style="text-align:center">Subject</th>
+ </tr>
+ {% for entry in selected_account.history %}
+ <tr>
+ <td style="text-align:right">{{entry.date}}</td>
+ <td style="text-align:right">
+ {{ entry.float_amount }} {{ entry.float_currency }}
+ </td>
+ <td style="text-align:left">{% if entry.counterpart_username %}
{{ entry.counterpart_username }} {% endif %} (account #{{ entry.counterpart
}})</td>
+ <td style="text-align:left">
+ {% if entry.counterpart_username %}
+ <a name="{{ entry.subject }}"></a>
+ <a href="public-accounts?account={{ entry.counterpart_username
}}#{{ entry.subject }}">{{ entry.subject }}</a>
+ {% else %}
+ {{ entry.subject }}
+ {% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ {% else %}
+ <p>No history for account #{{ selected_account.number }} ({{
selected_account.name}}) yet</p>
+ {% endif %}
+ </div>
+ </table>
+ </article>
+ </section>
+{% endblock content %}
diff --git a/talerbank/app/templates/public_histories_reloaded.html
b/talerbank/app/templates/public_histories_reloaded.html
deleted file mode 100644
index 2d6d5ff..0000000
--- a/talerbank/app/templates/public_histories_reloaded.html
+++ /dev/null
@@ -1,113 +0,0 @@
-<!DOCTYPE html>
-<!--
- This file is part of GNU TALER.
- Copyright (C) 2014, 2015, 2016 INRIA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free
Software
- Foundation; either version 2.1, 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 Lesser General Public License for more
details.
-
- You should have received a copy of the GNU Lesser General Public License
along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-
- @author Marcello Stanisci
--->
-{% extends "base.html" %}
-{% load staticfiles %}
-{% block headermsg %}
- <h1 lang="en" class="nav">History of public accounts</h1>
- <h1 lang="it" class="nav">Storico dei conti pubblici</h1>
-{% endblock headermsg %}
-{% block content %}
- <aside class="sidebar" id="left">
- </aside>
- <section id="main">
- <article>
- <table bgcolor="#E0E0E0" width="100%" width="100%" border="0"
cellpadding="2" cellspacing="1">
- <tr>
- {% for item in public_accounts %}
- <td width="12%" align="center">
- <a id="{{ item.account_name }}"
- href="/public-accounts?account={{ item.account_name }}">
- {{ item.account_name }}
- </a>
- </td>
- {% endfor %}
- </tr>
- </table>
- <div id="transactions-history">
- {% if selected_account.history %}
- <table class="history">
- <tr>
- <th lang="en" style="text-align:center">Date</th>
- <th lang="en" style="text-align:center">Amount</th>
- <th lang="en" style="text-align:center">Counterpart</th>
- <th lang="en" style="text-align:center">Subject</th>
- <th lang="it" style="text-align:center">Data</th>
- <th lang="it" style="text-align:center">Ammontare</th>
- <th lang="it" style="text-align:center">Controparte</th>
- <th lang="it" style="text-align:center">Causale</th>
- </tr>
- {% for entry in selected_account.history %}
- <tr>
- <td style="text-align:right">{{entry.date}}</td>
- <td style="text-align:right">
- {% if entry.direction == "FROM" %}+{% else %}-{% endif %}{{
entry.float_amount }} {{ entry.float_currency }}
- </td>
- <td style="text-align:left">{% if entry.counterpart_username %}
{{ entry.counterpart_username }} {% endif %} (account #{{ entry.counterpart
}})</td>
- <td style="text-align:left">
- {% if entry.counterpart_username %}
- <a name="{{ entry.subject }}"></a>
- <a href="public-accounts?account={{ entry.counterpart_username
}}#{{ entry.subject }}">{{ entry.subject }}</a>
- {% else %}
- {{ entry.subject }}
- {% endif %}
- </td>
- </tr>
- {% endfor %}
- {% else %}
- <p>No history for this account yet</p>
- {% endif %}
- </div>
- </table>
- </article>
- </section>
- <script>
- /*
- @licstart The following is the entire license notice for the
- JavaScript code in this page.
-
- Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
-
- The JavaScript code in this page is free software: you can
- redistribute it and/or modify it under the terms of the GNU
- General Public License (GNU GPL) as published by the Free Software
- Foundation, either version 3 of the License, or (at your option)
- any later version. The code is distributed WITHOUT ANY WARRANTY;
- without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
-
- As additional permission under GNU GPL version 3 section 7, you
- may distribute non-source (e.g., minimized or compacted) forms of
- that code without the copy of the GNU GPL normally required by
- section 4, provided you include this license notice and a URL
- through which recipients can access the Corresponding Source.
-
- @licend The above is the entire license notice
- for the JavaScript code in this page.
- */
- </script>
- <script type="application/javascript">
- function bold_selected(){
- var target = document.getElementById("{{ selected_account.account_name
}}");
- target.style.fontWeight = "bold";
- console.log ("selecting", target.parentNode);
- target.parentNode.setAttribute("class", "selected-item");
- };
- document.addEventListener('DOMContentLoaded', bold_selected);
- </script>
-{% endblock content %}
diff --git a/talerbank/app/templates/register.html
b/talerbank/app/templates/register.html
index a1ca54e..20aa8af 100644
--- a/talerbank/app/templates/register.html
+++ b/talerbank/app/templates/register.html
@@ -18,11 +18,10 @@
-->
{% extends "base.html" %}
-{% load staticfiles %}
+{% load settings_value from settings %}
{% block headermsg %}
- <h1 lang="en" class="nav">Register to the {{ currency }} bank!</h1>
- <h1 lang="it" class="nav">Registrati in banca {{ currency }}!</h1>
+ <h1 class="nav">Register to the {% settings_value "TALER_CURRENCY" %}
bank!</h1>
{% endblock headermsg %}
{% block content %}
@@ -52,7 +51,7 @@
<h1 lang="it">Form di registrazione</h1>
<form method="post" action="{% url 'register' %}">
{% csrf_token %}
- <input type="text" name="username" placeholder="username"></input>
+ <input type="text" name="username" placeholder="username"
autofocus></input>
<input type="password" name="password"
placeholder="password"></input>
<input type="submit" value="Ok"></input>
</form>
diff --git a/talerbank/app/templatetags/settings.py
b/talerbank/app/templatetags/settings.py
new file mode 100644
index 0000000..08fb084
--- /dev/null
+++ b/talerbank/app/templatetags/settings.py
@@ -0,0 +1,8 @@
+import django.template
+from django.conf import settings
+
+register = django.template.Library()
+
address@hidden
+def settings_value(name):
+ return getattr(settings, name, "")
diff --git a/talerbank/app/urls.py b/talerbank/app/urls.py
index 3b5e74c..752dd6a 100644
--- a/talerbank/app/urls.py
+++ b/talerbank/app/urls.py
@@ -16,24 +16,21 @@
from django.conf.urls import include, url
from django.views.generic.base import RedirectView
-from . import user
-from . import funds
from . import views
-from . import history
-from . import captcha
urlpatterns = [
url(r'^', include('talerbank.urls')),
- url(r'^(?P<js>(use_js|no_js))?$', views.home_page, name='index'),
+ url(r'^$', RedirectView.as_view(pattern_name="profile"), name="index"),
url(r'^favicon\.ico$', views.ignore),
- url(r'^accounts/register/$', user.register, name="register"),
- url(r'^accounts/login/$', views.home_page, name="login"),
- url(r'^accounts/logout/$', user.logout, name="logout"),
+ url(r'^javascript(.html)?/$', views.javascript_licensing,
name="javascript"),
+ url(r'^login/$', views.login_view, name="login"),
+ url(r'^logout/$', views.logout_view, name="logout"),
+ url(r'^accounts/register/$', views.register, name="register"),
url(r'^profile$', views.profile_page, name="profile"),
- url(r'^withdraw$', funds.withdraw_nojs, name="withdraw-nojs"),
- url(r'^public-accounts$', history.public_accounts, name="public-accounts"),
- url(r'^public-accounts?account=(P?<name>[a-zA-Z0-9 ]+)$',
history.public_accounts, name="public-accounts"),
- url(r'^pin/question$', captcha.pin_tan_question, name="pin-question"),
- url(r'^pin/verify$', captcha.pin_tan_verify, name="pin-verify"),
+ url(r'^withdraw$', views.withdraw_nojs, name="withdraw-nojs"),
+ url(r'^public-accounts$', views.public_accounts, name="public-accounts"),
+ url(r'^public-accounts/(?P<name>[a-zA-Z0-9 ]+)$', views.public_accounts,
name="public-accounts"),
+ url(r'^pin/question$', views.pin_tan_question, name="pin-question"),
+ url(r'^pin/verify$', views.pin_tan_verify, name="pin-verify"),
url(r'^javascript$', views.javascript_licensing)
]
diff --git a/talerbank/app/user.py b/talerbank/app/user.py
deleted file mode 100644
index d71a77b..0000000
--- a/talerbank/app/user.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# 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 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/>
-#
-# @author Marcello Stanisci
-
-import logging
-import django.db
-from django.contrib.auth import authenticate, login, logout
-from django.contrib.auth.models import User
-from django.shortcuts import render, redirect
-from django import forms
-from django.conf import settings
-from .models import BankAccount
-from .errors import bad_get_parameter_handler
-from .funds import wire_transfer_in_out
-
-logger = logging.getLogger(__name__)
-
-class UserReg(forms.Form):
- username = forms.CharField()
- password = forms.CharField(widget=forms.PasswordInput())
-
-
-def register(request):
- """
- register a new user giving 100 KUDOS bonus
- """
- wrong_field = False
- not_available = False
- if request.method == 'POST':
- form = UserReg(request.POST)
- if not form.is_valid():
- try:
- username = form.cleaned_data['username']
- password = form.cleaned_data['password']
- user = User.objects.create_user(username=username,
- password=password)
- account = BankAccount(user=user,
- currency=settings.TALER_CURRENCY)
- account.save()
- wire_transfer_in_out({'value': 100,
- 'fraction': 0,
- 'currency': settings.TALER_CURRENCY},
- 1,
- account.account_no,
- "Joining bonus")
- request.session['account_no'] = account.account_no
- request.session['registration_successful'] = True
- user = authenticate(username=username, password=password)
- login(request, user)
- return redirect("profile")
- except django.db.IntegrityError:
- not_available = True
- else:
- wrong_field = True
- return render(request,
- 'register.html',
- {'wrong': wrong_field,
- 'currency': settings.TALER_CURRENCY,
- 'not_available': not_available})
-
-
-def get_bank_account_from_username(username):
- try:
- user_account = User.objects.get(username=username)
- return user_account.bankaccount
- except User.DoesNotExist:
- logger.warn("user '%s' does not exist", username)
- return None
-
-
-def logout(request):
- """
- Log out the user and redirect to index page.
- """
- del request.session["account_no"]
- logout(request)
- request.session['logged_out'] = True
- return redirect("index")
-
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index 4901ff7..9a0e06d 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -15,98 +15,358 @@
# @author Marcello Stanisci
# @author Florian Dold
-from django.contrib.auth.decorators import login_required
-from django.http import HttpResponse
+import django.contrib.auth
+import django.contrib.auth.views
+import django.contrib.auth.forms
+from django import forms
from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, HttpResponseBadRequest,
HttpResponseServerError
from django.shortcuts import render, redirect
-from django.contrib.auth import authenticate, login, logout
-from .models import BankAccount
-from .history import extract_history
-from .amounts import stringify_amount
-from . import errors
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_POST, require_GET
+from simplemathcaptcha.fields import MathCaptchaField, MathCaptchaWidget
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.db.models import Q
+import json
import logging
+import time
+import hashlib
+import requests
+from urllib.parse import urljoin
+from . import amounts
+from . import schemas
+from .models import BankAccount, BankTransaction
logger = logging.getLogger(__name__)
+
+class MyAuthenticationForm(django.contrib.auth.forms.AuthenticationForm):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["username"].widget.attrs["autofocus"] = True
+ self.fields["username"].widget.attrs["placeholder"] = "Username"
+ self.fields["password"].widget.attrs["placeholder"] = "Password"
+
+
def ignore(request):
return HttpResponse()
def javascript_licensing(request):
- return render(request, 'javascript.html')
-
-def home_page(request, js=None):
- logger.info("js: %s" % js)
-
- if js:
- request.session['js'] = js
- else:
- js = request.session.get('js', 'no_js')
-
- if request.method == 'POST':
- username = request.POST['username']
- password = request.POST['password']
- user = authenticate(username=username, password=password)
- if user is None:
- request.session['wrong_login'] = True
- return redirect("index")
- if not user.is_active:
- return render(request, 'account_disabled.html', {'name':
user.username,
- 'currency':
settings.TALER_CURRENCY})
- login(request, user)
- request.session["account_no"] = user.bankaccount.account_no
- logger.info("Redirecting to /profile, js: %s" % js)
- return redirect("profile")
- wrong = False
- if "wrong_login" in request.session:
- wrong = request.session['wrong_login']
- del request.session['wrong_login']
- if "logged_out" in request.session:
- del request.session['logged_out']
- return render(request,
- 'home_page.html',
- {'currency': settings.TALER_CURRENCY,
- 'logged_out': True,
- 'js': js})
- if request.user.is_authenticated():
- return redirect("profile")
- return render(request,
- 'home_page.html',
- {'currency': settings.TALER_CURRENCY,
- 'wrong': wrong,
- 'js': js})
+ return render(request, "javascript.html")
+
+def login_view(request):
+ just_logged_out = get_session_flag(request, "just_logged_out")
+ response = django.contrib.auth.views.login(
+ request, authentication_form=MyAuthenticationForm,
template_name="login.html")
+ # sometimes the response is a redirect and not a template response
+ if hasattr(response, "context_data"):
+ response.context_data["just_logged_out"] = just_logged_out
+ return response
+
+def get_session_flag(request, name):
+ """
+ Get a flag from the session and clear it.
+ """
+ if name in request.session:
+ del request.session[name]
+ return True
+ return False
@login_required
def profile_page(request):
- withdraw = False
- registration = False
- if 'registration_successful' in request.session:
- del request.session['registration_successful']
- registration = "success"
- if 'withdrawal_successful' in request.session:
- del request.session['withdrawal_successful']
- withdraw = "success"
- user_account =
BankAccount.objects.get(account_no=request.session['account_no'])
+ just_withdrawn = get_session_flag(request, "just_withdrawn")
+ just_registered = get_session_flag(request, "just_registered")
+ user_account = BankAccount.objects.get(user=request.user)
history = extract_history(user_account)
- logger.info(str(history))
- reserve_pub = request.session.get('reserve_pub')
-
- # Should never hit an empty session['js']
- js = request.session.get('js', 'no_js')
- logger.info("js: %s" % js)
- response = render(request,
- 'profile_page.html',
- {'name': user_account.user.username,
- 'balance': stringify_amount(user_account.balance),
- 'currency': user_account.currency,
- 'precision': settings.NDIGITS,
- 'account_no': user_account.account_no,
- 'history': history,
- 'withdraw': withdraw,
- 'registration': registration,
- 'js': js})
- if js and withdraw == "success":
- response['X-Taler-Reserve-Pub'] = reserve_pub
+ reserve_pub = request.session.get("reserve_pub")
+ if "use_js" in request.GET:
+ print("use_js is in GET as '{}'".format(request.GET["use_js"]))
+ if request.GET["use_js"].lower() == "true":
+ request.session["use_js"] = True
+ else:
+ request.session["use_js"] = False
+ use_js = request.session.get("use_js", False)
+
+ context = dict(
+ name=user_account.user.username,
+ balance=amounts.stringify(user_account.balance),
+ currency=user_account.currency,
+ precision=settings.TALER_DIGITS,
+ account_no=user_account.account_no,
+ history=history,
+ just_withdrawn=just_withdrawn,
+ just_registered=just_registered,
+ use_js=use_js,
+ )
+
+ response = render(request, "profile_page.html", context)
+ if just_withdrawn and not use_js:
+ response["X-Taler-Operation"] = "confirm-reserve"
+ response["X-Taler-Reserve-Pub"] = reserve_pub
response.status_code = 202
+ return response
+
+
+class Pin(forms.Form):
+ pin = MathCaptchaField(
+ widget=MathCaptchaWidget(
+ attrs=dict(autocomplete="off", autofocus=True),
+ question_tmpl="<div lang=\"en\">What is %(num1)i %(operator)s
%(num2)i ?</div>"))
+
+
address@hidden
address@hidden
+def pin_tan_question(request):
+ for param in ("amount_value",
+ "amount_fraction",
+ "amount_currency",
+ "exchange",
+ "reserve_pub",
+ "wire_details"):
+ if param not in request.GET:
+ return HttpResponseBadRequest("parameter {} missing".format(param))
+ try:
+ amount = {"value": int(request.GET["amount_value"]),
+ "fraction": int(request.GET["amount_fraction"]),
+ "currency": request.GET["amount_currency"]}
+ except ValueError:
+ return HttpResponseBadRequest("invalid parameters")
+ user_account = BankAccount.objects.get(user=request.user)
+ wiredetails = json.loads(request.GET["wire_details"])
+ if not isinstance(wiredetails, dict) or "test" not in wiredetails:
+ return HttpResponseBadRequest(
+ "This bank only supports the test wire transfer method. "
+ "The exchange does not seem to support it.")
+ try:
+ schemas.validate_wiredetails(wiredetails)
+ schemas.validate_amount(amount)
+ except ValueError:
+ return HttpResponseBadRequest("invalid parameters")
+ # parameters we store in the session are (more or less) validated
+ request.session["exchange_account_number"] =
wiredetails["test"]["account_number"]
+ request.session["amount"] = amount
+ request.session["exchange_url"] = request.GET["exchange"]
+ 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")
+ context = dict(
+ form=Pin(auto_id=False),
+ amount=amounts.floatify(amount),
+ previous_failed=previous_failed,
+ exchange=request.GET["exchange"],
+ )
+ return render(request, "pin_tan.html", context)
+
+
address@hidden
address@hidden
+def pin_tan_verify(request):
+ try:
+ given = request.POST["pin_0"]
+ hashed_result = request.POST["pin_1"]
+ question_url = request.POST["question_url"]
+ except Exception: # FIXME narrow the Exception type
+ return redirect("profile")
+ hasher = hashlib.new("sha1")
+ hasher.update(settings.SECRET_KEY.encode("utf-8"))
+ hasher.update(given.encode("utf-8"))
+ hashed_attempt = hasher.hexdigest()
+ if hashed_attempt != hashed_result:
+ request.session["captcha_failed"] = True
+ return redirect(question_url)
+ # We recover the info about reserve creation from the session (and
+ # not from POST parameters), since we don't what the user to
+ # change it after we've verified it.
+ try:
+ amount = request.session["amount"]
+ exchange_url = request.session["exchange_url"]
+ reserve_pub = request.session["reserve_pub"]
+ exchange_account_number = request.session["exchange_account_number"]
+ sender_wiredetails = request.session["sender_wiredetails"]
+ except KeyError:
+ # This is not a withdraw session, we redirect the user to the
+ # profile page.
+ return redirect("profile")
+ return create_reserve_at_exchange(request, amount, exchange_url,
exchange_account_number, reserve_pub, sender_wiredetails)
+
+
+class UserReg(forms.Form):
+ username = forms.CharField()
+ password = forms.CharField(widget=forms.PasswordInput())
+
+
+def register(request):
+ """
+ register a new user giving 100 KUDOS bonus
+ """
+ if request.method != "POST":
+ return render(request, "register.html")
+ form = UserReg(request.POST)
+ if not form.is_valid():
+ return render(request, "register.html", dict(wrong_field=True))
+ 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))
+ user = User.objects.create_user(username=username, password=password)
+ user_account = BankAccount(user=user, currency=settings.TALER_CURRENCY)
+ user_account.save()
+ bank_internal_account = BankAccount.objects.get(account_no=1)
+ amount = dict(value=100, fraction=0, currency=settings.TALER_CURRENCY)
+ wire_transfer(amount, bank_internal_account, user_account, "Joining bonus")
+ request.session["just_registered"] = True
+ user = django.contrib.auth.authenticate(username=username,
password=password)
+ django.contrib.auth.login(request, user)
+ return redirect("profile")
+
+
+def logout_view(request):
+ """
+ Log out the user and redirect to index page.
+ """
+ django.contrib.auth.logout(request)
+ request.session["just_logged_out"] = True
+ return redirect("index")
+
+
+def extract_history(account):
+ history = []
+ related_transactions = BankTransaction.objects.filter(
+ Q(debit_account=account) | Q(credit_account=account))
+ for item in related_transactions:
+ if item.credit_account == account:
+ counterpart = item.debit_account
+ sign = 1
+ else:
+ counterpart = item.credit_account
+ sign = -1
+ entry = dict(
+ float_amount=amounts.stringify(item.amount * sign),
+ float_currency=item.currency,
+ counterpart=counterpart.account_no,
+ counterpart_username=counterpart.user.username,
+ subject=item.subject,
+ date=item.date.strftime("%d/%m/%y %H:%M"),
+ )
+ history.append(entry)
+ return history
+
+def public_accounts(request, name=None):
+ if not name:
+ name = settings.TALER_PREDEFINED_ACCOUNTS[0]
+ try:
+ user = User.objects.get(username=name)
+ account = BankAccount.objects.get(user=user, is_public=True)
+ except User.DoesNotExist:
+ return HttpResponse("account '{}' not found".format(name), status=404)
+ except BankAccount.DoesNotExist:
+ return HttpResponse("account '{}' not found".format(name), status=404)
+ public_accounts = BankAccount.objects.filter(is_public=True)
+ history = extract_history(account)
+ context = dict(
+ public_accounts=public_accounts,
+ selected_account=dict(
+ name=name,
+ number=account.account_no,
+ history=history,
+ )
+ )
+ return render(request, "public_accounts.html", context)
+
+
address@hidden
address@hidden
+def add_incoming(request):
+ """
+ Internal API used by exchanges to notify the bank
+ of incoming payments.
+
+ This view is CSRF exempt, since it is not used from
+ within the browser, and only over the private admin interface.
+ """
+ logger.info("handling /admin/add/incoming")
+ data = json.loads(request.body.decode("utf-8"))
+ try:
+ schemas.validate_incoming_request(data)
+ except ValueError:
+ return HttpResponseBadRequest()
+ logger.info("add_incoming for debit account %s and credit account %s, WTID
%s",
+ data["debit_account"],
+ data["credit_account"],
+ data["wtid"])
+ try:
+ debit_account = user_account =
BankAccount.objects.get(user=data["debit_account"])
+ credit_account = user_account =
BankAccount.objects.get(user=data["credit_account"])
+ except BankAccount.DoesNotExist:
+ return HttpResponse(status=404)
+ wire_transfer(data["amount"], debit_account, credit_account, data["wtid"])
+ return JsonResponse({"outcome": "ok"}, status=200)
+
+
address@hidden
address@hidden
+def withdraw_nojs(request):
+ amount = amounts.parse_amount(request.POST.get("kudos_amount", ""))
+ if amount is None:
+ return HttpResponseBadRequest()
+ response = HttpResponse(status=202)
+ response["X-Taler-Operation"] = "create-reserve"
+ response["X-Taler-Callback-Url"] = reverse("pin-question")
+ response["X-Taler-Wt-Types"] = '["TEST"]'
+ response["X-Taler-Amount"] = json.dumps(amount)
return response
+
+
+def create_reserve_at_exchange(request, amount, exchange_url,
exchange_account_no, reserve_pub, sender_account_details):
+ try:
+ BankAccount.objects.get(account_no=exchange_account_no)
+ except BankAccount.DoesNotExist:
+ raise HttpResponseBadRequest("The bank account #{} of exchange {} does
not exist".format(exchange_account_no, exchange_url))
+ json_body = dict(
+ reserve_pub=reserve_pub,
+ execution_date="/Date(" + str(int(time.time())) + ")/",
+ sender_account_details=sender_account_details,
+ # just something unique
+ transfer_details=dict(timestamp=int(time.time() * 1000)),
+ amount=amount,
+ )
+ request_url = urljoin(exchange_url, "admin/add/incoming")
+ res = requests.post(request_url, json=json_body)
+ if res.status_code != 200:
+ return render(request, "error_exchange.html", dict(
+ message="Could not transfer funds to the exchange. The exchange
({}) gave a bad response.".format(exchange_url),
+ response_text=res.text,
+ response_status=res.status_code,
+ ))
+ user_account = BankAccount.objects.get(user=request.user)
+ exchange_account = BankAccount.objects.get(user=exchange_account_no)
+ wire_transfer(amount, user_account, exchange_account_no, reserve_pub)
+ request.session["just_withdrawn"] = True
+ return redirect("profile")
+
+
+def wire_transfer(amount,
+ debit_account,
+ credit_account,
+ subject):
+ if debit_account.pk == credit_account.pk:
+ return
+ float_amount = amounts.floatify(amount)
+ transaction_item = BankTransaction(amount=float_amount,
+ currency=amount["currency"],
+ credit_account=credit_account,
+ debit_account=debit_account,
+ subject=subject)
+ debit_account.balance -= float_amount
+ credit_account.balance += float_amount
+ debit_account.save()
+ credit_account.save()
+ transaction_item.save()
diff --git a/talerbank/settings.py b/talerbank/settings.py
index 6f6a857..4625fe7 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings.py
@@ -39,6 +39,8 @@ ALLOWED_HOSTS = ["*"]
LOGIN_URL = "login"
+LOGIN_REDIRECT_URL = "index"
+
# Application definition
@@ -61,7 +63,6 @@ MIDDLEWARE_CLASSES = [
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'talerbank.app.middleware.ExpectedExceptionsMiddleware',
]
# To be dynamically set at launch time (by *.wsgi scripts)
@@ -89,33 +90,39 @@ WSGI_APPLICATION = 'talerbank.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+DATABASES = {}
+
# parse a database URL, django can't natively do this!
-dbname = tc.value_string("bank", "database", required=True)
+dbname = tc.value_string("bank", "database", required=False)
dbconfig = {}
-db_url = urllib.parse.urlparse(dbname)
-if db_url.scheme != "postgres":
- raise Exception("only postgres db is supported ('{}' not
understood)".format(dbname))
-dbconfig['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
-dbconfig['NAME'] = db_url.path.lstrip("/")
-
-if not db_url.netloc:
- p = urllib.parse.parse_qs(db_url.query)
- if ("host" not in p) or len(p["host"]) == 0:
- host = None
+if dbname:
+ db_url = urllib.parse.urlparse(dbname)
+ if db_url.scheme != "postgres":
+ raise Exception("only postgres db is supported ('{}' not
understood)".format(dbname))
+ dbconfig['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
+ dbconfig['NAME'] = db_url.path.lstrip("/")
+
+ if not db_url.netloc:
+ p = urllib.parse.parse_qs(db_url.query)
+ if ("host" not in p) or len(p["host"]) == 0:
+ host = None
+ else:
+ host = p["host"][0]
else:
- host = p["host"][0]
-else:
- host = db_url.netloc
+ host = db_url.netloc
-if host:
- dbconfig["HOST"] = host
+ if host:
+ dbconfig["HOST"] = host
-logger.info("db string '%s'", dbname)
-logger.info("db info '%s'", dbconfig)
+ logger.info("db string '%s'", dbname)
+ logger.info("db info '%s'", dbconfig)
-DATABASES = {
- 'default': dbconfig
-}
+ DATABASES["default"] = dbconfig
+else:
+ DATABASES["default"] = {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
# Password validation
@@ -168,16 +175,8 @@ STATIC_ROOT = '/tmp/talerbankstatic/'
-TALER_WIREDETAILS_COUNTER = 0
TALER_CURRENCY = tc.value_string("taler", "currency", required=True)
-# How many digits we want to be shown for amounts fractional part
-NDIGITS = tc.value_int("bank", "ndigits")
-if NDIGITS is None:
- NDIGITS = 2
-# Taler-compliant fractional part in amount objects, currently 1e8
-FRACTION = tc.value_int("bank", "fraction")
-if FRACTION is None:
- FRACTION = 100000000
+TALER_DIGITS = 2
TALER_PREDEFINED_ACCOUNTS = ['Tor', 'GNUnet', 'Taler', 'FSF', 'Tutorial']
TALER_EXPECTS_DONATIONS = ['Tor', 'GNUnet', 'Taler', 'FSF']
--
To stop receiving notification emails like this one, please contact
address@hidden