From 4397a6cab372725e9859983639234b878fee966f Mon Sep 17 00:00:00 2001 From: Eneko Illarramendi Date: Sat, 11 Apr 2020 19:47:25 +0200 Subject: [PATCH] feat: use `cerberus` schemas to validate POST data --- Pipfile | 1 + lnbits/core/views/api.py | 20 ++++++++------------ lnbits/decorators.py | 11 ++++++----- lnbits/extensions/paywall/views_api.py | 17 +++++++---------- lnbits/extensions/tpos/views_api.py | 10 +++++++--- lnbits/settings.py | 2 +- requirements.txt | 5 +++-- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Pipfile b/Pipfile index 6f99e6c..ba0f00d 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ python_version = "3.7" [packages] bitstring = "*" +cerberus = "*" lnd-grpc = "*" lnurl = "*" flask = "*" diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index c39d86a..d23a9e1 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -23,14 +23,13 @@ def api_payments(): @api_check_wallet_macaroon(key_type="invoice") -@api_validate_post_request(required_params=["amount", "memo"]) +@api_validate_post_request( + schema={ + "amount": {"type": "integer", "min": 1, "required": True}, + "memo": {"type": "string", "empty": False, "required": True}, + } +) def api_payments_create_invoice(): - if not isinstance(g.data["amount"], int) or g.data["amount"] < 1: - return jsonify({"message": "`amount` needs to be a positive integer."}), Status.BAD_REQUEST - - if not isinstance(g.data["memo"], str) or not g.data["memo"].strip(): - return jsonify({"message": "`memo` needs to be a valid string."}), Status.BAD_REQUEST - try: ok, checking_id, payment_request, error_message = WALLET.create_invoice(g.data["amount"], g.data["memo"]) except Exception as e: @@ -46,11 +45,8 @@ def api_payments_create_invoice(): @api_check_wallet_macaroon(key_type="admin") -@api_validate_post_request(required_params=["bolt11"]) +@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}}) def api_payments_pay_invoice(): - if not isinstance(g.data["bolt11"], str) or not g.data["bolt11"].strip(): - return jsonify({"message": "`bolt11` needs to be a valid string."}), Status.BAD_REQUEST - try: invoice = bolt11.decode(g.data["bolt11"]) @@ -81,7 +77,7 @@ def api_payments_pay_invoice(): @core_app.route("/api/v1/payments", methods=["POST"]) -@api_validate_post_request(required_params=["out"]) +@api_validate_post_request(schema={"out": {"type": "boolean", "required": True}}) def api_payments_create(): if g.data["out"] is True: return api_payments_pay_invoice() diff --git a/lnbits/decorators.py b/lnbits/decorators.py index 7c0fe13..3697d38 100644 --- a/lnbits/decorators.py +++ b/lnbits/decorators.py @@ -1,3 +1,4 @@ +from cerberus import Validator from flask import g, abort, jsonify, request from functools import wraps from typing import List, Union @@ -26,18 +27,18 @@ def api_check_wallet_macaroon(*, key_type: str = "invoice"): return wrap -def api_validate_post_request(*, required_params: List[str] = []): +def api_validate_post_request(*, schema: dict): def wrap(view): @wraps(view) def wrapped_view(**kwargs): if "application/json" not in request.headers["Content-Type"]: return jsonify({"message": "Content-Type must be `application/json`."}), Status.BAD_REQUEST - g.data = request.json + v = Validator(schema) + g.data = {key: request.json[key] for key in schema.keys()} - for param in required_params: - if param not in g.data: - return jsonify({"message": f"`{param}` is required."}), Status.BAD_REQUEST + if not v.validate(g.data): + return jsonify({"message": f"Errors in request data: {v.errors}"}), Status.BAD_REQUEST return view(**kwargs) diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py index 11afd21..cea836d 100644 --- a/lnbits/extensions/paywall/views_api.py +++ b/lnbits/extensions/paywall/views_api.py @@ -21,16 +21,13 @@ def api_paywalls(): @paywall_ext.route("/api/v1/paywalls", methods=["POST"]) @api_check_wallet_macaroon(key_type="invoice") -@api_validate_post_request(required_params=["url", "memo", "amount"]) +@api_validate_post_request(schema={ + "url": {"type": "string", "empty": False, "required": True}, + "memo": {"type": "string", "empty": False, "required": True}, + "amount": {"type": "integer", "min": 0, "required": True}, +}) def api_paywall_create(): - if not isinstance(g.data["amount"], int) or g.data["amount"] < 0: - return jsonify({"message": "`amount` needs to be a positive integer, or zero."}), Status.BAD_REQUEST - - for var in ["url", "memo"]: - if not isinstance(g.data[var], str) or not g.data[var].strip(): - return jsonify({"message": f"`{var}` needs to be a valid string."}), Status.BAD_REQUEST - - paywall = create_paywall(wallet_id=g.wallet.id, url=g.data["url"], memo=g.data["memo"], amount=g.data["amount"]) + paywall = create_paywall(wallet_id=g.wallet.id, **g.data) return jsonify(paywall._asdict()), Status.CREATED @@ -48,4 +45,4 @@ def api_paywall_delete(paywall_id): delete_paywall(paywall_id) - return '', Status.NO_CONTENT + return "", Status.NO_CONTENT diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 74de267..5bbe9cd 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -22,7 +22,12 @@ def api_tposs(): @tpos_ext.route("/api/v1/tposs", methods=["POST"]) @api_check_wallet_macaroon(key_type="invoice") -@api_validate_post_request(required_params=["name", "currency"]) +@api_validate_post_request( + schema={ + "name": {"type": "string", "empty": False, "required": True}, + "currency": {"type": "string", "empty": False, "required": True}, + } +) def api_tpos_create(): print("poo") @@ -46,7 +51,7 @@ def api_tpos_delete(tpos_id): return '', Status.NO_CONTENT @tpos_ext.route("/api/v1/tposs/invoice/", methods=["POST"]) -@api_validate_post_request(required_params=["amount"]) +@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}}) def api_tpos_create_invoice(tpos_id): r = get_tpos(tpos_id) print(r) @@ -55,4 +60,3 @@ def api_tpos_create_invoice(tpos_id): # api_payments_create_invoice(memo=tpos_id.id, amount=amount, ) return jsonify(rr), Status.CREATED - diff --git a/lnbits/settings.py b/lnbits/settings.py index a57b061..c488796 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -2,7 +2,7 @@ import importlib import os -wallets_module = importlib.import_module(f"lnbits.wallets") +wallets_module = importlib.import_module("lnbits.wallets") wallet_class = getattr(wallets_module, os.getenv("LNBITS_BACKEND_WALLET_CLASS", "LntxbotWallet")) LNBITS_PATH = os.path.dirname(os.path.realpath(__file__)) diff --git a/requirements.txt b/requirements.txt index 885d61f..93d7665 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ bech32==1.2.0 bitstring==3.1.6 -certifi==2019.11.28 +cerberus==1.3.2 +certifi==2020.4.5.1 chardet==3.0.4 click==7.1.1 flask-assets==2.0 @@ -9,7 +10,7 @@ flask-limiter==1.2.1 flask-seasurf==0.2.2 flask-talisman==0.7.0 flask==1.1.2 -gevent==1.4.0 +gevent==1.5.0 googleapis-common-protos==1.51.0 greenlet==0.4.15 grpcio-tools==1.28.1