## # 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 # @brief definitions of JSON schemas for validating data import json from django.conf import settings from django.core.exceptions import ValidationError from django import forms from django.core.validators import RegexValidator from urllib.parse import urlparse ## # 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). # FIXME: also defined in views.py. Need a common.py to contain # such definitions ? UINT64_MAX = (2**64) - 1 ## # Pattern for amounts, plain RegEx. AMOUNT_REGEX = "^[A-Za-z0-9_-]+:([0-9]+)\.?([0-9]+)?$" ## # Exception class to be raised when a expected URL parameter # is not found. class InvalidSession(ValueError): ## # Init method. # # @param self the object itself. # @param http_status_code the HTTP response code to return # to the caller (client). def __init__(self, http_status_code): self.hint = "Landed on a broken session" self.http_status_code = http_status_code super().__init__() ## # Exception class to be raised when a JSON # object does not respect a specification. class JSONFieldException(ValueError): ## # Init method. # # @param self the object itself. # @param error object containing the hint. # @param http_status_code the HTTP response code to return # to the caller (client). def __init__(self, error, http_status_code): self.hint = json.dumps(error.as_json()) self.http_status_code = http_status_code super().__init__() ## # Exception class to be raised when at least one expected URL # parameter is either not found or malformed. class URLParamValidationError(ValueError): ## # Init method. # # @param self the object itself. # @param error object containing the hint. # @param http_status_code the HTTP response code to return # to the caller (client). def __init__(self, error, http_status_code): self.hint = json.stringify(error.as_json()) self.http_status_code = http_status_code super().__init__() class AuthForm(forms.Form): type = forms.CharField( validators=[ RegexValidator( "^basic$", message="Only 'basic' method provided for now" ) ] ) # Just any value is good here. data = forms.Field(required=False) class AuthField(forms.Field): ## # No need to touch the input. Dict is good # and gets validated by the "validate()" method. def to_python(self, value): return value ## # Validate input. def validate(self, value): af = AuthForm(value) if not af.is_valid(): raise ValidationError(json.dumps(af.errors.as_json())) class RejectData(forms.Form): auth = AuthField() # FIXME: adjust min/max values. row_id = forms.IntegerField() account_number = forms.IntegerField() class AddIncomingData(forms.Form): auth = AuthField() amount = forms.CharField( validators=[ RegexValidator( AMOUNT_REGEX, message="Format CURRENCY:X[.Y] not respected" ) ] ) subject = forms.CharField() credit_account = forms.IntegerField(min_value=1) exchange_url = forms.URLField() ## # Form specification that validates GET parameters from a # /history request. class HistoryParamsBase(forms.Form): auth = forms.CharField( validators=[ RegexValidator("^basic$", message="Only 'basic' is allowed") ] ) cancelled = forms.CharField( required=False, empty_value="show", validators=[ RegexValidator( "^(omit|show)$", message="Only 'omit' or 'show' are valid" ) ] ) ordering = forms.CharField( required=False, empty_value="descending", validators=[ RegexValidator( "^(ascending|descending)$", message="Only 'ascending' or 'descending' are valid" ) ] ) direction = forms.CharField( validators=[ RegexValidator( "^(debit|credit|both|cancel\+|cancel-)$", message="Only: debit/credit/both/cancel+/cancel-" ) ] ) # FIXME: adjust min/max values. account_number = forms.IntegerField(required=False) class HistoryParams(HistoryParamsBase): def clean_start(self): delta = self.cleaned_data.get("delta") start = self.cleaned_data.get("start") if None == start: return 0 if 0 <= delta else UINT64_MAX return start # FIXME: adjust min/max values. delta = forms.IntegerField() start = forms.IntegerField(required=False) class HistoryRangeParams(HistoryParamsBase): # FIXME: adjust min/max values. end = forms.IntegerField() start = forms.IntegerField() class PaytoField(forms.Field): def __init__(self, **kwargs): super().__init__(**kwargs) def to_python(self, value): return value def validate(self, value): # The request misses this, default exchange # will be used. NOTE: experience showed that the # "required=False" argument given when init the object # does NOT prevent this function from being called! if not value: return wire_uri = urlparse(value) if "payto" != wire_uri.scheme: raise ValidationError("URL is not 'payto'") class WithdrawHeadless(forms.Form): auth = AuthField() amount = forms.CharField( validators=[ RegexValidator( AMOUNT_REGEX, message="Format CURRENCY:X[.Y] not respected" ) ] ) reserve_pub = forms.CharField() exchange_wire_details = PaytoField(required=False) class PinTanParams(forms.Form): amount_currency = forms.CharField( validators=[ RegexValidator("^[A-Z]+$", message="Currency is all upper case") ] ) amount_value = forms.IntegerField(min_value=0) amount_fraction = forms.IntegerField(min_value=0) exchange = forms.URLField() reserve_pub = forms.CharField() exchange_wire_details = PaytoField() class SenderWireDetails(forms.Form): # FIXME: must be changed to 'payto' format. type = forms.CharField() bank_url = forms.URLField() account_number = forms.IntegerField(min_value=1) class SenderWireDetailsField(forms.Field): def to_python(self, value): return value def validate(self, value): swd = SenderWireDetails(value) if not swd.is_valid(): raise ValidationError(json.dumps(swd.errors.as_json())) class WithdrawSessionData(forms.Form): amount = forms.CharField( validators=[ RegexValidator( AMOUNT_REGEX, message="Could not find valid amount in state.." ) ] ) reserve_pub = forms.CharField() exchange_account_number = forms.IntegerField(min_value=1) sender_wiredetails = SenderWireDetailsField()