##
# This file is part of TALER
# (C) 2014, 2015, 2016 Taler Systems SA
#
# 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
#
#
# @author Marcello Stanisci
# @author Florian Dold
from functools import wraps
import math
import json
import logging
import hashlib
import random
import re
from urllib.parse import urlparse
import django.contrib.auth
import django.contrib.auth.views
import django.contrib.auth.forms
from django.db import transaction
from django import forms
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST, require_GET
from django.views.decorators.http import require_http_methods
from django.urls import reverse
from django.contrib.auth.models import User
from django.db.models import Q
from django.http import JsonResponse, HttpResponse
from django.shortcuts import render, redirect
from django.core.exceptions import ObjectDoesNotExist
from datetime import datetime
from .models import BankAccount, BankTransaction, TalerWithdrawOperation
from taler.util.amount import Amount
import qrcode
import qrcode.image.svg
import lxml
from .schemas import (
HistoryParams, HistoryRangeParams, URLParamValidationError, RejectData,
AddIncomingData, JSONFieldException, PinTanParams, InvalidSession,
WithdrawSessionData, WithdrawHeadless, WithdrawHeadlessUri
)
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
##
# Exception raised upon failing login.
#
class LoginFailed(Exception):
hint = "Wrong username/password"
http_status_code = 401
taler_error_code = 5109
class InvalidInputData(Exception):
def __init__(self, msg):
super().__init__(msg)
class UsernameUnavailable(Exception):
pass
##
# Exception raised when the public history from
# a ordinary user account is tried to be accessed.
class PrivateAccountException(Exception):
hint = "The selected account is private"
http_status_code = 402
##
# Exception raised when some financial operation goes
# beyond the limit threshold.
class DebitLimitException(Exception):
hint = "Insufficient credit, operation not acceptable."
http_status_code = 406
taler_error_code = 5103
##
# Exception raised when some financial operation is
# attempted and both parties are the same account number.
#
class SameAccountException(Exception):
hint = "Debit and credit account are the same."
http_status_code = 403
taler_error_code = 5102
##
# Exception raised when someone tries to reject a
# transaction, but they have no rights to accomplish
# such operation.
class RejectNoRightsException(Exception):
hint = "You weren't the transaction credit account, " \
"no rights to reject."
http_status_code = 403
taler_error_code = 5200
class UnhandledException(Exception):
hint = "Unhandled exception happened!"
http_status_code = 500
taler_error_code = 5300
##
# The authentication for users to log in the bank.
#
class TalerAuthenticationForm(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"
##
# Return a empty response. Used in "/favicon.ico" requests.
#
def ignore(request):
del request
return HttpResponse()
##
# Decode body, when it is expected to be UTF-8.
#
# @param request the HTTP request being served.
# @return the body as string.
def decode_body(request):
return request.body.decode("utf-8")
##
# Get a flag from the session and clear it.
#
# @param request the HTTP request being served.
# @param name name of the session value that should be retrieved.
# @return the value, if found; otherwise False.
def get_session_flag(request, name):
if name in request.session:
ret = request.session[name]
del request.session[name]
return ret
return False
##
# Get a hint from the session and clear it. A 'hint' is a
# "message" that different parts of the bank can send to each
# other - via the state - communicating what is the state of
# the HTTP session.
#
# @param request the HTTP request being served.
# @param name hint name
# @return the hint (a "null" one if none was found)
def get_session_hint(request, name):
if name in request.session:
ret = request.session[name]
del request.session[name]
return ret
# Fail message, success message, hint.
return False, False, None
##
# Build the list containing all the predefined accounts; the
# list contains, for example, the exchange, the bank itself, and
# all the public accounts (like GNUnet / Tor / FSF / ..)
def predefined_accounts_list():
account = 2
ret = []
for i in settings.TALER_PREDEFINED_ACCOUNTS[1:]:
ret.append((account, "%s (#%d)" % (i, account)))
account += 1
return ret
##
# Thanks to [1], this class provides a dropdown menu that
# can be used within a