def parse_request_args(request: Request) -> Dict: asset = Asset.objects.filter(code=request.GET.get("asset_code"), sep6_enabled=True, deposit_enabled=True).first() if not asset: return {"error": render_error_response(_("invalid 'asset_code'"))} lang = request.GET.get("lang") if lang: err_resp = validate_language(lang) if err_resp: return {"error": err_resp} activate_lang_for_request(lang) memo_type = request.GET.get("memo_type") if memo_type and memo_type not in Transaction.MEMO_TYPES: return {"error": render_error_response(_("invalid 'memo_type'"))} try: memo = memo_str(request.GET.get("memo"), memo_type) except (ValueError, MemoInvalidException): return { "error": render_error_response(_("invalid 'memo' for 'memo_type'")) } return { "asset": asset, "memo_type": memo_type, "memo": memo, "lang": lang, "type": request.GET.get("type"), **extract_sep9_fields(request.GET), }
def test_memo_str_hash_memo(): raw_bytes = token_bytes(32) memo_str = utils.memo_hex_to_base64(raw_bytes.hex()) assert utils.memo_str(HashMemo(raw_bytes)) == ( memo_str, Transaction.MEMO_TYPES.hash, )
def get(account: str, request: Request) -> Response: if request.GET.get( "account") and account != request.GET.get("account"): return render_error_response( _("The account specified does not match authorization token"), status_code=403, ) elif not (request.GET.get("id") or request.GET.get("account")): return render_error_response( _("unable to identify a user without 'id' or 'account'")) elif request.GET.get("memo_type") and not request.GET.get("memo"): return render_error_response(_("missing 'memo' for 'memo_type'")) try: # validate memo and memo_type memo_str(request.GET.get("memo"), request.GET.get("memo_type")) except ValueError: return render_error_response(_("invalid 'memo' for 'memo_type'")) try: response_data = rci.get({ "id": request.GET.get("id"), "sep10_client_account": account, "account": request.GET.get("account"), "memo": request.GET.get("memo"), "memo_type": request.GET.get("memo_type"), "type": request.GET.get("type"), "lang": request.GET.get("lang"), }) except ValueError as e: return render_error_response(str(e), status_code=400) except ObjectDoesNotExist as e: return render_error_response(str(e), status_code=404) try: validate_response_data(response_data) except ValueError: logger.exception( _("An exception was raised validating GET /customer response")) return render_error_response(_("unable to process request."), status_code=500) return Response(response_data)
def put(account: str, request: Request) -> Response: if request.data.get("id") and not request.data.get("account"): if not isinstance(request.data.get("id"), str): return render_error_response(_("bad ID value, expected str")) elif account != request.data.get("account"): return render_error_response( _("The account specified does not match authorization token"), status_code=403, ) elif request.data.get("memo_type") and not request.data.get("memo"): return render_error_response(_("missing 'memo' for 'memo_type'")) if request.data.get("memo"): try: # validate memo and memo_type memo_str(request.data.get("memo"), request.data.get("memo_type")) except ValueError: return render_error_response( _("invalid 'memo' for 'memo_type'")) try: customer_id = rci.put({ "id": request.data.get("id"), "account": account, "memo": request.data.get("memo"), "memo_type": request.data.get("memo_type"), **extract_sep9_fields(request.data), }) except ValueError as e: return render_error_response(str(e), status_code=400) except ObjectDoesNotExist as e: return render_error_response(str(e), status_code=404) if not isinstance(customer_id, str): logger.error( "Invalid customer ID returned from put() integration. Must be str." ) return render_error_response(_("unable to process request")) return Response({"id": customer_id}, status=202)
def delete(account_from_auth: str, request: Request, account: str) -> Response: if account_from_auth != account: return render_error_response("account not found", status_code=404) try: memo = memo_str(request.data.get("memo"), request.data.get("memo_type")) except ValueError as e: return render_error_response("invalid 'memo' for 'memo_type'") try: rci.delete(account, memo, request.data.get("memo_type")) except ValueError: return render_error_response("account not found", status_code=404) else: return Response({}, status=200)
def parse_request_args(request: Request) -> Dict: asset = Asset.objects.filter(code=request.GET.get("asset_code"), sep6_enabled=True, withdrawal_enabled=True).first() if not asset: return {"error": render_error_response(_("invalid 'asset_code'"))} lang = request.GET.get("lang") if lang: err_resp = validate_language(lang) if err_resp: return {"error": err_resp} activate_lang_for_request(lang) memo_type = request.GET.get("memo_type") if memo_type and memo_type not in Transaction.MEMO_TYPES: return {"error": render_error_response(_("invalid 'memo_type'"))} try: memo = memo_str(request.GET.get("memo"), memo_type) except (ValueError, MemoInvalidException): return { "error": render_error_response(_("invalid 'memo' for 'memo_type'")) } if not request.GET.get("type"): return {"error": render_error_response(_("'type' is required"))} if not request.GET.get("dest"): return {"error": render_error_response(_("'dest' is required"))} args = { "asset": asset, "memo_type": memo_type, "memo": memo, "lang": request.GET.get("lang"), "type": request.GET.get("type"), "dest": request.GET.get("dest"), "dest_extra": request.GET.get("dest_extra"), **extract_sep9_fields(request.GET), } # add remaining extra params, it's on the anchor to validate them for param, value in request.GET.items(): if param not in args: args[param] = value return args
def deposit(account: str, request: Request) -> Response: """ POST /transactions/deposit/interactive Creates an `incomplete` deposit Transaction object in the database and returns the URL entry-point for the interactive flow. """ asset_code = request.POST.get("asset_code") stellar_account = request.POST.get("account") lang = request.POST.get("lang") sep9_fields = extract_sep9_fields(request.POST) if lang: err_resp = validate_language(lang) if err_resp: return err_resp activate_lang_for_request(lang) # Verify that the request is valid. if not all([asset_code, stellar_account]): return render_error_response( _("`asset_code` and `account` are required parameters")) # Ensure memo won't cause stellar transaction to fail when submitted try: memo = memo_str(request.POST.get("memo"), request.POST.get("memo_type")) except ValueError: return render_error_response(_("invalid 'memo' for 'memo_type'")) amount = None if request.POST.get("amount"): try: amount = Decimal(request.POST.get("amount")) except DecimalException as e: return render_error_response(_("Invalid 'amount'")) # Verify that the asset code exists in our database, with deposit enabled. asset = Asset.objects.filter(code=asset_code).first() if not asset: return render_error_response(_("unknown asset: %s") % asset_code) elif not (asset.deposit_enabled and asset.sep24_enabled): return render_error_response( _("invalid operation for asset %s") % asset_code) try: Keypair.from_public_key(stellar_account) except Ed25519PublicKeyInvalidError: return render_error_response(_("invalid 'account'")) try: rdi.save_sep9_fields(stellar_account, sep9_fields, lang) except ValueError as e: # The anchor found a validation error in the sep-9 fields POSTed by # the wallet. The error string returned should be in the language # specified in the request. return render_error_response(str(e)) # Construct interactive deposit pop-up URL. transaction_id = create_transaction_id() Transaction.objects.create( id=transaction_id, stellar_account=account, asset=asset, kind=Transaction.KIND.deposit, status=Transaction.STATUS.incomplete, to_address=account, protocol=Transaction.PROTOCOL.sep24, memo=memo, memo_type=request.POST.get("memo_type") or Transaction.MEMO_TYPES.hash, ) logger.info(f"Created deposit transaction {transaction_id}") url = interactive_url( request, str(transaction_id), account, asset_code, settings.OPERATION_DEPOSIT, amount, ) return Response( { "type": "interactive_customer_info_needed", "url": url, "id": transaction_id }, status=status.HTTP_200_OK, )
def test_memo_str_text_memo(): assert utils.memo_str(TextMemo("test")) == ("test", Transaction.MEMO_TYPES.text)
def test_memo_str_bad_input_type(): with pytest.raises(ValueError): utils.memo_str(Mock())
def test_memo_str_none(): assert utils.memo_str(None) == (None, None)
def test_memo_str_id_memo(): assert utils.memo_str(IdMemo(123)) == ("123", Transaction.MEMO_TYPES.id)