Browse Source

feat: use `cerberus` schemas to validate POST data

fee_issues
Eneko Illarramendi 5 years ago
parent
commit
4397a6cab3
  1. 1
      Pipfile
  2. 20
      lnbits/core/views/api.py
  3. 11
      lnbits/decorators.py
  4. 17
      lnbits/extensions/paywall/views_api.py
  5. 10
      lnbits/extensions/tpos/views_api.py
  6. 2
      lnbits/settings.py
  7. 5
      requirements.txt

1
Pipfile

@ -8,6 +8,7 @@ python_version = "3.7"
[packages] [packages]
bitstring = "*" bitstring = "*"
cerberus = "*"
lnd-grpc = "*" lnd-grpc = "*"
lnurl = "*" lnurl = "*"
flask = "*" flask = "*"

20
lnbits/core/views/api.py

@ -23,14 +23,13 @@ def api_payments():
@api_check_wallet_macaroon(key_type="invoice") @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(): 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: try:
ok, checking_id, payment_request, error_message = WALLET.create_invoice(g.data["amount"], g.data["memo"]) ok, checking_id, payment_request, error_message = WALLET.create_invoice(g.data["amount"], g.data["memo"])
except Exception as e: except Exception as e:
@ -46,11 +45,8 @@ def api_payments_create_invoice():
@api_check_wallet_macaroon(key_type="admin") @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(): 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: try:
invoice = bolt11.decode(g.data["bolt11"]) invoice = bolt11.decode(g.data["bolt11"])
@ -81,7 +77,7 @@ def api_payments_pay_invoice():
@core_app.route("/api/v1/payments", methods=["POST"]) @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(): def api_payments_create():
if g.data["out"] is True: if g.data["out"] is True:
return api_payments_pay_invoice() return api_payments_pay_invoice()

11
lnbits/decorators.py

@ -1,3 +1,4 @@
from cerberus import Validator
from flask import g, abort, jsonify, request from flask import g, abort, jsonify, request
from functools import wraps from functools import wraps
from typing import List, Union from typing import List, Union
@ -26,18 +27,18 @@ def api_check_wallet_macaroon(*, key_type: str = "invoice"):
return wrap return wrap
def api_validate_post_request(*, required_params: List[str] = []): def api_validate_post_request(*, schema: dict):
def wrap(view): def wrap(view):
@wraps(view) @wraps(view)
def wrapped_view(**kwargs): def wrapped_view(**kwargs):
if "application/json" not in request.headers["Content-Type"]: if "application/json" not in request.headers["Content-Type"]:
return jsonify({"message": "Content-Type must be `application/json`."}), Status.BAD_REQUEST 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 not v.validate(g.data):
if param not in g.data: return jsonify({"message": f"Errors in request data: {v.errors}"}), Status.BAD_REQUEST
return jsonify({"message": f"`{param}` is required."}), Status.BAD_REQUEST
return view(**kwargs) return view(**kwargs)

17
lnbits/extensions/paywall/views_api.py

@ -21,16 +21,13 @@ def api_paywalls():
@paywall_ext.route("/api/v1/paywalls", methods=["POST"]) @paywall_ext.route("/api/v1/paywalls", methods=["POST"])
@api_check_wallet_macaroon(key_type="invoice") @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(): def api_paywall_create():
if not isinstance(g.data["amount"], int) or g.data["amount"] < 0: paywall = create_paywall(wallet_id=g.wallet.id, **g.data)
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"])
return jsonify(paywall._asdict()), Status.CREATED return jsonify(paywall._asdict()), Status.CREATED
@ -48,4 +45,4 @@ def api_paywall_delete(paywall_id):
delete_paywall(paywall_id) delete_paywall(paywall_id)
return '', Status.NO_CONTENT return "", Status.NO_CONTENT

10
lnbits/extensions/tpos/views_api.py

@ -22,7 +22,12 @@ def api_tposs():
@tpos_ext.route("/api/v1/tposs", methods=["POST"]) @tpos_ext.route("/api/v1/tposs", methods=["POST"])
@api_check_wallet_macaroon(key_type="invoice") @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(): def api_tpos_create():
print("poo") print("poo")
@ -46,7 +51,7 @@ def api_tpos_delete(tpos_id):
return '', Status.NO_CONTENT return '', Status.NO_CONTENT
@tpos_ext.route("/api/v1/tposs/invoice/<tpos_id>", methods=["POST"]) @tpos_ext.route("/api/v1/tposs/invoice/<tpos_id>", 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): def api_tpos_create_invoice(tpos_id):
r = get_tpos(tpos_id) r = get_tpos(tpos_id)
print(r) print(r)
@ -55,4 +60,3 @@ def api_tpos_create_invoice(tpos_id):
# api_payments_create_invoice(memo=tpos_id.id, amount=amount, ) # api_payments_create_invoice(memo=tpos_id.id, amount=amount, )
return jsonify(rr), Status.CREATED return jsonify(rr), Status.CREATED

2
lnbits/settings.py

@ -2,7 +2,7 @@ import importlib
import os 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")) wallet_class = getattr(wallets_module, os.getenv("LNBITS_BACKEND_WALLET_CLASS", "LntxbotWallet"))
LNBITS_PATH = os.path.dirname(os.path.realpath(__file__)) LNBITS_PATH = os.path.dirname(os.path.realpath(__file__))

5
requirements.txt

@ -1,6 +1,7 @@
bech32==1.2.0 bech32==1.2.0
bitstring==3.1.6 bitstring==3.1.6
certifi==2019.11.28 cerberus==1.3.2
certifi==2020.4.5.1
chardet==3.0.4 chardet==3.0.4
click==7.1.1 click==7.1.1
flask-assets==2.0 flask-assets==2.0
@ -9,7 +10,7 @@ flask-limiter==1.2.1
flask-seasurf==0.2.2 flask-seasurf==0.2.2
flask-talisman==0.7.0 flask-talisman==0.7.0
flask==1.1.2 flask==1.1.2
gevent==1.4.0 gevent==1.5.0
googleapis-common-protos==1.51.0 googleapis-common-protos==1.51.0
greenlet==0.4.15 greenlet==0.4.15
grpcio-tools==1.28.1 grpcio-tools==1.28.1

Loading…
Cancel
Save