Ejemplo n.º 1
0
def info(request: Request) -> Response:
    info_data = {
        "deposit": {},
        "withdraw": {},
        "fee": {"enabled": True, "authentication_required": True},
        "transactions": {"enabled": True, "authentication_required": True},
        "transaction": {"enabled": True, "authentication_required": True},
    }
    for asset in Asset.objects.filter(sep6_enabled=True):
        try:
            fields_and_types = registered_info_func(asset, request.GET.get("lang"))
        except ValueError:
            return render_error_response("unsupported 'lang'")
        try:
            validate_integration(fields_and_types)
        except ValueError as e:
            logger.error(f"info integration error: {str(e)}")
            return render_error_response(
                _("unable to process the request"), status_code=500
            )
        info_data["deposit"][asset.code] = get_asset_info(
            asset, "deposit", fields_and_types.get("fields", {})
        )
        info_data["withdraw"][asset.code] = get_asset_info(
            asset, "withdrawal", fields_and_types.get("types", {})
        )

    return Response(info_data)
def withdraw(account: str, request: Request) -> Response:
    """
    POST /transactions/withdraw/interactive

    Creates an `incomplete` withdraw Transaction object in the database and
    returns the URL entry-point for the interactive flow.
    """
    lang = request.POST.get("lang")
    asset_code = request.POST.get("asset_code")
    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)
    if not asset_code:
        return render_error_response(_("'asset_code' is required"))
    elif request.POST.get("memo"):
        # Polaris SEP-24 doesn't support custodial wallets that depend on memos
        # to disambiguate users using the same stellar account. Support would
        # require new or adjusted integration points.
        return render_error_response(_("`memo` parameter is not supported"))

    # Verify that the asset code exists in our database, with withdraw enabled.
    asset = Asset.objects.filter(code=asset_code).first()
    if not (asset and asset.withdrawal_enabled and asset.sep24_enabled):
        return render_error_response(
            _("invalid operation for asset %s") % asset_code)
    elif not asset.distribution_account:
        return render_error_response(
            _("unsupported asset type: %s") % asset_code)

    try:
        rwi.save_sep9_fields(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))

    transaction_id = create_transaction_id()
    Transaction.objects.create(
        id=transaction_id,
        stellar_account=account,
        asset=asset,
        kind=Transaction.KIND.withdrawal,
        status=Transaction.STATUS.incomplete,
        receiving_anchor_account=asset.distribution_account,
        memo_type=Transaction.MEMO_TYPES.hash,
        protocol=Transaction.PROTOCOL.sep24,
    )
    logger.info(f"Created withdrawal transaction {transaction_id}")

    url = interactive_url(request, str(transaction_id), account, asset_code,
                          settings.OPERATION_WITHDRAWAL)
    return Response({
        "type": "interactive_customer_info_needed",
        "url": url,
        "id": transaction_id
    })
Ejemplo n.º 3
0
def transaction(
    request: Request,
    account: str,
    sep6: bool = False,
) -> Response:
    try:
        request_transaction = _get_transaction_from_request(
            request,
            account=account,
            sep6=sep6,
        )
    except (AttributeError, ValidationError) as exc:
        return render_error_response(str(exc),
                                     status_code=status.HTTP_400_BAD_REQUEST)
    except Transaction.DoesNotExist:
        return render_error_response("transaction not found",
                                     status_code=status.HTTP_404_NOT_FOUND)
    serializer = TransactionSerializer(
        request_transaction,
        context={
            "request": request,
            "sep6": sep6
        },
    )
    return Response({"transaction": serializer.data})
Ejemplo n.º 4
0
def transactions(
    request: Request,
    account: str,
    sep6: bool = False,
) -> Response:
    try:
        limit = _validate_limit(request.GET.get("limit"))
    except ValueError:
        return render_error_response("invalid limit",
                                     status_code=status.HTTP_400_BAD_REQUEST)

    protocol_filter = {
        "sep6_enabled": True
    } if sep6 else {
        "sep24_enabled": True
    }
    if not request.GET.get("asset_code"):
        return render_error_response("asset_code is required")
    elif not Asset.objects.filter(code=request.GET.get("asset_code"),
                                  **protocol_filter).exists():
        return render_error_response("invalid asset_code")

    translation_dict = {
        "asset_code": "asset__code",
        "no_older_than": "started_at__gte",
        "kind": "kind",
    }

    qset_filter = _compute_qset_filters(request.GET, translation_dict)
    qset_filter["stellar_account"] = account

    # Since the Transaction IDs are UUIDs, rather than in the chronological
    # order of their creation, we map the paging ID (if provided) to the
    # started_at field of a Transaction.
    paging_id = request.GET.get("paging_id")
    if paging_id:
        try:
            start_transaction = Transaction.objects.get(id=paging_id)
        except Transaction.DoesNotExist:
            return render_error_response(
                "invalid paging_id", status_code=status.HTTP_400_BAD_REQUEST)
        qset_filter["started_at__lt"] = start_transaction.started_at

    protocol = Transaction.PROTOCOL.sep6 if sep6 else Transaction.PROTOCOL.sep24
    transactions_qset = Transaction.objects.filter(protocol=protocol,
                                                   **qset_filter)
    if limit:
        transactions_qset = transactions_qset[:limit]

    serializer = TransactionSerializer(
        transactions_qset,
        many=True,
        context={
            "request": request,
            "same_asset": True,
            "sep6": sep6
        },
    )

    return Response({"transactions": serializer.data})
Ejemplo n.º 5
0
 def get(
     account: str,
     _client_domain: Optional[str],
     _request: Request,
     transaction_id: str = None,
 ) -> Response:
     if not transaction_id:
         return render_error_response(
             _("GET requests must include a transaction ID in the URI"), )
     elif not registered_sep31_receiver_integration.valid_sending_anchor(
             account):
         return render_error_response(_("invalid sending account."),
                                      status_code=403)
     elif not transaction_id:
         return render_error_response(_("missing 'id' in URI"))
     try:
         t = Transaction.objects.filter(
             id=transaction_id,
             stellar_account=account,
         ).first()
     except ValidationError:  # bad id parameter
         return render_error_response(_("transaction not found"),
                                      status_code=404)
     if not t:
         return render_error_response(_("transaction not found"),
                                      status_code=404)
     return Response({"transaction": SEP31TransactionSerializer(t).data})
Ejemplo n.º 6
0
    def post(account: str, request: Request, **kwargs):
        if kwargs:
            return render_error_response(
                _("POST requests should not specify subresources in the URI"))
        elif not registered_sep31_receiver_integration.valid_sending_anchor(
                account):
            return render_error_response("invalid sending account",
                                         status_code=403)

        try:
            params = validate_post_request(request)
        except ValueError as e:
            return render_error_response(str(e))

        # validate fields separately since error responses need different format
        missing_fields = validate_post_fields(params.get("fields"),
                                              params.get("asset"),
                                              params.get("lang"))
        if missing_fields:
            return Response(
                {
                    "error": "transaction_info_needed",
                    "fields": missing_fields
                },
                status=400,
            )

        transaction_id = create_transaction_id()
        # create memo
        transaction_id_hex = transaction_id.hex
        padded_hex_memo = "0" * (64 -
                                 len(transaction_id_hex)) + transaction_id_hex
        transaction_memo = memo_hex_to_base64(padded_hex_memo)
        # create transaction object without saving to the DB
        transaction = Transaction(
            id=transaction_id,
            protocol=Transaction.PROTOCOL.sep31,
            kind=Transaction.KIND.send,
            status=Transaction.STATUS.pending_sender,
            stellar_account=account,
            asset=params["asset"],
            amount_in=params["amount"],
            memo=transaction_memo,
            memo_type=Transaction.MEMO_TYPES.hash,
            receiving_anchor_account=params["asset"].distribution_account,
        )

        error_data = registered_sep31_receiver_integration.process_post_request(
            params, transaction)
        try:
            response_data = process_post_response(error_data, transaction)
        except ValueError as e:
            logger.error(str(e))
            return render_error_response(_("unable to process the request"),
                                         status_code=500)
        else:
            transaction.save()

        return Response(response_data,
                        status=400 if "error" in response_data else 200)
Ejemplo n.º 7
0
def complete_interactive_withdraw(request: Request) -> Response:
    """
    GET /transactions/withdraw/interactive/complete

    Updates the transaction status to pending_user_transfer_start and
    redirects to GET /more_info. A `callback` can be passed in the URL
    to be used by the more_info template javascript.
    """
    transaction_id = request.GET.get("transaction_id")
    callback = request.GET.get("callback")
    if not transaction_id:
        return render_error_response(_("Missing id parameter in URL"),
                                     content_type="text/html")
    try:
        Transaction.objects.filter(id=transaction_id).update(
            status=Transaction.STATUS.pending_user_transfer_start)
    except ValidationError:
        return render_error_response(
            _("ID passed is not a valid transaction ID"),
            content_type="text/html")
    logger.info(
        f"Hands-off interactive flow complete for transaction {transaction_id}"
    )
    url, args = (
        reverse("more_info"),
        urlencode({
            "id": transaction_id,
            "callback": callback
        }),
    )
    return redirect(f"{url}?{args}")
Ejemplo n.º 8
0
def complete_interactive_deposit(request: Request) -> Response:
    """
    GET /transactions/deposit/interactive/complete

    Updates the transaction status to pending_user_transfer_start and
    redirects to GET /more_info. A `callback` can be passed in the URL
    to be used by the more_info template javascript.
    """
    transaction_id = request.GET.get("transaction_id")
    callback = request.GET.get("callback")
    if not transaction_id:
        return render_error_response(
            _("Missing id parameter in URL"), content_type="text/html"
        )
    try:
        Transaction.objects.filter(id=transaction_id).update(
            status=Transaction.STATUS.pending_user_transfer_start
        )
    except ValidationError:
        return render_error_response(
            _("ID passed is not a valid transaction ID"), content_type="text/html"
        )
    logger.info(f"Hands-off interactive flow complete for transaction {transaction_id}")
    args = {"id": transaction_id, "initialLoad": "true"}
    if callback:
        args["callback"] = callback
    return redirect(f"{reverse('more_info')}?{urlencode(args)}")
Ejemplo n.º 9
0
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),
    }
Ejemplo n.º 10
0
def withdraw(
    account: str,
    client_domain: Optional[str],
    request: Request,
) -> Response:
    args = parse_request_args(request)
    if "error" in args:
        return args["error"]
    args["account"] = account

    transaction_id = create_transaction_id()
    transaction_id_hex = transaction_id.hex
    padded_hex_memo = "0" * (64 - len(transaction_id_hex)) + transaction_id_hex
    transaction_memo = memo_hex_to_base64(padded_hex_memo)
    transaction = Transaction(
        id=transaction_id,
        stellar_account=account,
        asset=args["asset"],
        kind=Transaction.KIND.withdrawal,
        status=Transaction.STATUS.pending_user_transfer_start,
        receiving_anchor_account=args["asset"].distribution_account,
        memo=transaction_memo,
        memo_type=Transaction.MEMO_TYPES.hash,
        protocol=Transaction.PROTOCOL.sep6,
        more_info_url=request.build_absolute_uri(
            f"{SEP6_MORE_INFO_PATH}?id={transaction_id}"),
        on_change_callback=args["on_change_callback"],
        client_domain=client_domain,
    )

    # All request arguments are validated in parse_request_args()
    # except 'type', 'dest', and 'dest_extra'. Since Polaris doesn't know
    # which argument was invalid, the anchor is responsible for raising
    # an exception with a translated message.
    try:
        integration_response = rwi.process_sep6_request(args, transaction)
    except ValueError as e:
        return render_error_response(str(e))
    try:
        response, status_code = validate_response(args, integration_response,
                                                  transaction)
    except ValueError as e:
        logger.error(str(e))
        return render_error_response(_("unable to process the request"),
                                     status_code=500)

    if status_code == 200:
        response["memo"] = transaction.memo
        response["memo_type"] = transaction.memo_type
        logger.info(f"Created withdraw transaction {transaction.id}")
        transaction.save()
    elif Transaction.objects.filter(id=transaction.id).exists():
        logger.error(
            "Do not save transaction objects for invalid SEP-6 requests")
        return render_error_response(_("unable to process the request"),
                                     status_code=500)

    return Response(response, status=status_code)
Ejemplo n.º 11
0
def validate_language(
        lang: str,
        content_type: str = "application/json") -> Optional[Response]:
    if not lang:
        return render_error_response(_("missing language code in request"),
                                     content_type=content_type)
    elif not _is_supported_language(lang):
        return render_error_response(_("unsupported language: %s" % lang),
                                     content_type=content_type)
Ejemplo n.º 12
0
def fee(request: Request, sep6: bool = False) -> Response:
    """
    Definition of the /fee endpoint, in accordance with SEP-0024.
    See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#fee
    """
    deposit_op = polaris_settings.OPERATION_DEPOSIT
    withdrawal_op = polaris_settings.OPERATION_WITHDRAWAL

    operation = request.GET.get("operation")
    op_type = request.GET.get("type")
    asset_code = request.GET.get("asset_code")
    amount_str = request.GET.get("amount")

    # Verify that the asset code exists in our database:
    protocol_filter = {
        "sep6_enabled": True
    } if sep6 else {
        "sep24_enabled": True
    }
    asset = Asset.objects.filter(code=asset_code, **protocol_filter).first()
    if not asset_code or not asset:
        return render_error_response("invalid 'asset_code'")

    # Verify that amount is provided, and that can be parsed into a decimal:
    try:
        amount = Decimal(amount_str)
    except (DecimalException, TypeError):
        return render_error_response("invalid 'amount'")

    error_resp = None
    # Verify that the requested operation is valid:
    if operation not in (deposit_op, withdrawal_op):
        error_resp = render_error_response(
            f"'operation' should be either '{deposit_op}' or '{withdrawal_op}'"
        )
    # Verify asset is enabled and within the specified limits
    elif operation == deposit_op:
        error_resp = verify_valid_asset_operation(asset, amount,
                                                  Transaction.KIND.deposit)
    elif operation == withdrawal_op:
        error_resp = verify_valid_asset_operation(asset, amount,
                                                  Transaction.KIND.withdrawal)

    if error_resp:
        return error_resp
    else:
        return Response({
            "fee":
            registered_fee_func({
                "operation": operation,
                "type": op_type,
                "asset_code": asset_code,
                "amount": amount,
            })
        })
Ejemplo n.º 13
0
def more_info(request: Request, sep6: bool = False) -> Response:
    try:
        request_transaction = _get_transaction_from_request(request, sep6=sep6)
    except (AttributeError, ValidationError) as exc:
        return render_error_response(str(exc), content_type="text/html")
    except Transaction.DoesNotExist:
        return render_error_response(
            _("transaction not found"),
            status_code=status.HTTP_404_NOT_FOUND,
            content_type="text/html",
        )

    serializer = TransactionSerializer(request_transaction,
                                       context={
                                           "request": request,
                                           "sep6": sep6
                                       })
    tx_json = json.dumps({"transaction": serializer.data})
    context = {
        "tx_json": tx_json,
        "amount_in": serializer.data.get("amount_in"),
        "amount_out": serializer.data.get("amount_out"),
        "amount_fee": serializer.data.get("amount_fee"),
        "transaction": request_transaction,
        "asset_code": request_transaction.asset.code,
    }
    if request_transaction.kind == Transaction.KIND.deposit:
        content = rdi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)
        if request_transaction.status == Transaction.STATUS.pending_user_transfer_start:
            context.update(instructions=rdi.instructions_for_pending_deposit(
                request_transaction))
    else:
        content = rwi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)
    if content:
        context.update(content)

    if registered_scripts_func is not scripts:
        logger.warning(
            "DEPRECATED: the `scripts` Polaris integration function will be "
            "removed in Polaris 2.0 in favor of allowing the anchor to override "
            "and extend Polaris' Django templates. See the Template Extensions "
            "documentation for more information.")
    context["scripts"] = registered_scripts_func(content)

    # more_info.html will update the 'callback' parameter value to 'success' after
    # making the callback. If the page is then reloaded, the callback is not included
    # in the rendering context, ensuring only one successful callback request is made.
    callback = request.GET.get("callback")
    if callback and callback != "success":
        context["callback"] = callback

    return Response(context, template_name="polaris/more_info.html")
Ejemplo n.º 14
0
def deposit(account: str, client_domain: Optional[str], request: Request,) -> Response:
    args = parse_request_args(request)
    if "error" in args:
        return args["error"]
    args["account"] = account

    transaction_id = create_transaction_id()
    transaction = Transaction(
        id=transaction_id,
        stellar_account=account,
        asset=args["asset"],
        kind=Transaction.KIND.deposit,
        status=Transaction.STATUS.pending_user_transfer_start,
        memo=args["memo"],
        memo_type=args["memo_type"] or Transaction.MEMO_TYPES.text,
        to_address=account,
        protocol=Transaction.PROTOCOL.sep6,
        more_info_url=request.build_absolute_uri(
            f"{SEP6_MORE_INFO_PATH}?id={transaction_id}"
        ),
        claimable_balance_supported=args["claimable_balance_supported"],
        on_change_callback=args["on_change_callback"],
        client_domain=client_domain,
    )

    try:
        integration_response = rdi.process_sep6_request(args, transaction)
    except ValueError as e:
        return render_error_response(str(e))
    except APIException as e:
        return render_error_response(str(e), status_code=e.status_code)

    try:
        response, status_code = validate_response(
            args, integration_response, transaction
        )
    except (ValueError, KeyError) as e:
        logger.error(str(e))
        return render_error_response(
            _("unable to process the request"), status_code=500
        )

    if status_code == 200:
        logger.info(f"Created deposit transaction {transaction.id}")
        transaction.save()
    elif Transaction.objects.filter(id=transaction.id).exists():
        logger.error("Do not save transaction objects for invalid SEP-6 requests")
        return render_error_response(
            _("unable to process the request"), status_code=500
        )

    return Response(response, status=status_code)
Ejemplo n.º 15
0
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:
        make_memo(request.data.get("memo"), request.data.get("memo_type"))
    except ValueError:
        return render_error_response(_("invalid 'memo' for 'memo_type'"))
    try:
        rci.delete(account, request.data.get("memo"), request.data.get("memo_type"))
    except ObjectDoesNotExist:
        return render_error_response(_("account not found"), status_code=404)
    else:
        return Response({"status": "success"}, status=200)
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)
Ejemplo n.º 17
0
def callback(account: str, request: Request) -> Response:
    if request.data.get("id"):
        if not isinstance(request.data.get("id"), str):
            return render_error_response(_("bad ID value, expected str"))
        elif (
            request.data.get("account")
            or request.data.get("memo")
            or request.data.get("memo_type")
        ):
            return render_error_response(
                _(
                    "requests with 'id' cannot also have 'account', 'memo', or 'memo_type'"
                )
            )
    elif account != request.data.get("account"):
        return render_error_response(
            _("The account specified does not match authorization token"),
            status_code=403,
        )

    try:
        # validate memo and memo_type
        make_memo(request.data.get("memo"), request.data.get("memo_type"))
    except ValueError:
        return render_error_response(_("invalid 'memo' for 'memo_type'"))

    callback_url = request.data.get("url")
    if not callback_url:
        return render_error_response(_("callback 'url' required"))
    schemes = ["https"] if not settings.LOCAL_MODE else ["https", "http"]
    try:
        URLValidator(schemes=schemes)(request.data.get("url"))
    except ValidationError:
        return render_error_response(_("'url' must be a valid URL"))

    try:
        rci.callback(
            {
                "id": request.data.get("id"),
                "account": account,
                "memo": request.data.get("memo"),
                "memo_type": request.data.get("memo_type"),
                "url": callback_url,
            }
        )
    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)
    except NotImplementedError:
        return render_error_response(_("not implemented"), status_code=501)

    return Response({"success": True})
Ejemplo n.º 18
0
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
Ejemplo n.º 19
0
def interactive_args_validation(request: Request) -> Dict:
    """
    Validates the arguments passed to the /webapp endpoints

    Returns a dictionary, either containing an 'error' response
    object or the transaction and asset objects specified by the
    incoming request.
    """
    transaction_id = request.GET.get("transaction_id")
    asset_code = request.GET.get("asset_code")
    callback = request.GET.get("callback")
    amount_str = request.GET.get("amount")
    asset = Asset.objects.filter(code=asset_code, sep24_enabled=True).first()
    if not transaction_id:
        return dict(error=render_error_response(
            _("no 'transaction_id' provided"), content_type="text/html"))
    elif not (asset_code and asset):
        return dict(error=render_error_response(_("invalid 'asset_code'"),
                                                content_type="text/html"))
    try:
        transaction = Transaction.objects.get(id=transaction_id, asset=asset)
    except (Transaction.DoesNotExist, ValidationError):
        return dict(error=render_error_response(
            _("Transaction with ID and asset_code not found"),
            content_type="text/html",
            status_code=status.HTTP_404_NOT_FOUND,
        ))

    # Verify that amount is provided, and that can be parsed into a decimal:
    amount = None
    if amount_str:
        try:
            amount = Decimal(amount_str)
        except (DecimalException, TypeError):
            return dict(error=render_error_response("invalid 'amount'"))

        err_resp = verify_valid_asset_operation(asset,
                                                amount,
                                                transaction.kind,
                                                content_type="text/html")
        if err_resp:
            return dict(error=err_resp)

    return dict(transaction=transaction,
                asset=asset,
                callback=callback,
                amount=amount)
Ejemplo n.º 20
0
def deposit(account: str, request: Request) -> Response:
    args = parse_request_args(request)
    if "error" in args:
        return args["error"]
    args["account"] = account

    transaction_id = create_transaction_id()
    transaction = Transaction(
        id=transaction_id,
        stellar_account=account,
        asset=args["asset"],
        kind=Transaction.KIND.deposit,
        status=Transaction.STATUS.pending_user_transfer_start,
        memo=args["memo"],
        memo_type=args["memo_type"] or Transaction.MEMO_TYPES.text,
        to_address=account,
        protocol=Transaction.PROTOCOL.sep6,
    )

    try:
        integration_response = rdi.process_sep6_request(args, transaction)
    except ValueError as e:
        return render_error_response(str(e))
    except APIException as e:
        return render_error_response(str(e), status_code=e.status_code)

    try:
        response, status_code = validate_response(
            args, integration_response, transaction
        )
    except (ValueError, KeyError):
        return render_error_response(
            _("unable to process the request"), status_code=500
        )

    if status_code == 200:
        logger.info(f"Created deposit transaction {transaction.id}")
        transaction.save()
    elif Transaction.objects.filter(id=transaction.id).exists():
        logger.error("Do not save transaction objects for invalid SEP-6 requests")
        return render_error_response(
            _("unable to process the request"), status_code=500
        )

    return Response(response, status=status_code)
Ejemplo n.º 21
0
def more_info(request: Request, sep6: bool = False) -> Response:
    try:
        request_transaction = _get_transaction_from_request(request, sep6=sep6)
    except (AttributeError, ValidationError) as exc:
        return render_error_response(str(exc), content_type="text/html")
    except Transaction.DoesNotExist:
        return render_error_response(
            _("transaction not found"),
            status_code=status.HTTP_404_NOT_FOUND,
            content_type="text/html",
        )

    serializer = TransactionSerializer(request_transaction,
                                       context={
                                           "request": request,
                                           "sep6": sep6
                                       })
    tx_json = json.dumps({"transaction": serializer.data})
    resp_data = {
        "tx_json": tx_json,
        "amount_in": serializer.data.get("amount_in"),
        "amount_out": serializer.data.get("amount_out"),
        "amount_fee": serializer.data.get("amount_fee"),
        "transaction": request_transaction,
        "asset_code": request_transaction.asset.code,
    }
    if request_transaction.kind == Transaction.KIND.deposit:
        content = rdi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)
        if request_transaction.status == Transaction.STATUS.pending_user_transfer_start:
            resp_data["instructions"] = rdi.instructions_for_pending_deposit(
                request_transaction)
    else:
        content = rwi.content_for_template(Template.MORE_INFO,
                                           transaction=request_transaction)

    resp_data["scripts"] = registered_scripts_func(content)
    if content:
        resp_data.update(content)

    callback = request.GET.get("callback")
    if callback:
        resp_data["callback"] = callback

    return Response(resp_data, template_name="transaction/more_info.html")
Ejemplo n.º 22
0
 def wrapper(request: Request, *args, **kwargs) -> Response:
     try:
         authenticate_session_helper(request)
     except ValueError as e:
         return render_error_response(str(e),
                                      content_type=content_type,
                                      status_code=403)
     else:
         return view(request)
Ejemplo n.º 23
0
def validate_url(url) -> Optional[Dict]:
    schemes = ["https"] if not settings.LOCAL_MODE else ["https", "http"]
    try:
        URLValidator(schemes=schemes)(url)
    except ValidationError:
        return dict(error=render_error_response(
            _("invalid callback URL provided"),
            content_type="text/html",
        ))
Ejemplo n.º 24
0
def confirm_email(request: Request) -> Response:
    if not (request.GET.get("token") and request.GET.get("email")):
        return render_error_response("email and token arguments required.",
                                     content_type="text/html")

    try:
        account = PolarisStellarAccount.objects.get(
            user__email=request.GET.get("email"),
            confirmation_token=request.GET.get("token"),
        )
    except PolarisStellarAccount.DoesNotExist:
        return render_error_response(
            "User with email and token does not exist",
            content_type="text/html")

    account.confirmed = True
    account.save()

    return Response(template_name="email_confirmed.html")
Ejemplo n.º 25
0
def transactions(account: str, request: Request) -> Response:
    """
    Definition of the /transactions endpoint, in accordance with SEP-0006.
    See: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0006.md#transaction-history
    """
    if account != request.GET.get("account"):
        return render_error_response(
            _("The account specified does not match authorization token"),
            status_code=403,
        )
    return endpoints.transactions(request, account, sep6=True)
Ejemplo n.º 26
0
 def post(self, request: Request, *_args, **_kwargs) -> Response:
     envelope_xdr = request.data.get("transaction")
     if not envelope_xdr:
         return render_error_response(gettext("'transaction' is required"))
     client_domain, error_response = self._validate_challenge_xdr(
         envelope_xdr)
     if error_response:
         return error_response
     else:
         return Response(
             {"token": self._generate_jwt(envelope_xdr, client_domain)})
Ejemplo n.º 27
0
 def post(self, request: Request, *_args, **_kwargs) -> Response:
     envelope_xdr = request.data.get("transaction")
     if not envelope_xdr:
         return render_error_response(_("'transaction' is required"))
     try:
         self._validate_challenge_xdr(envelope_xdr)
     except ValueError as e:
         return Response({"error": str(e)},
                         status=status.HTTP_400_BAD_REQUEST)
     else:
         return Response({"token": self._generate_jwt(envelope_xdr)})
Ejemplo n.º 28
0
def put_verification(account: str, _client_domain: Optional[str],
                     request) -> Response:
    if not request.data.get("id") or not isinstance(request.data.get("id"),
                                                    str):
        return render_error_response(_("bad ID value, expected str"))
    for key, value in request.data.items():
        if key == "id":
            continue
        sep9_field, verification = key.rsplit("_", 1)
        if verification != "verification" or sep9_field not in SEP_9_FIELDS:
            return render_error_response(
                _("all request attributes other than 'id' must be a SEP-9 field followed "
                  "by '_verification'"))

    try:
        response_data = rci.put_verification(account, dict(request.data))
    except ObjectDoesNotExist:
        return render_error_response(_("customer not found"), status_code=404)
    except ValueError as e:
        return render_error_response(str(e), status_code=400)
    except NotImplementedError:
        return render_error_response(_("not implemented"), status_code=501)

    try:
        validate_response_data(response_data)
    except ValueError:
        logger.exception(
            _("An exception was raised validating PUT /customer/verification response"
              ))
        return render_error_response(_("unable to process request."),
                                     status_code=500)

    return Response(response_data)
Ejemplo n.º 29
0
def patch_transaction(
    account: str, _client_domain: Optional[str], request: Request, transaction_id: str
):
    try:
        t = Transaction.objects.get(
            id=transaction_id,
            stellar_account=account,
            protocol=Transaction.PROTOCOL.sep6,
        )
    except (ValidationError, ObjectDoesNotExist):
        return render_error_response(_("transaction not found"), status_code=404)
    if t.status != Transaction.STATUS.pending_transaction_info_update:
        return render_error_response(_("update not required"))
    try:
        validate_patch_request_fields(request.data, t)
    except ValueError as e:
        return render_error_response(str(e))
    except RuntimeError as e:
        logger.exception(str(e))
        return render_error_response(_("unable to process request"), status_code=500)
    integration = rdi if t.kind == Transaction.KIND.deposit else rwi
    try:
        integration.patch_transaction(params=request.data, transaction=t)
    except ValueError as e:
        return render_error_response(str(e))
    except NotImplementedError:
        return render_error_response(_("not implemented"), status_code=501)
    t.status = Transaction.STATUS.pending_anchor
    t.required_info_updates = None
    t.required_info_message = None
    t.save()
    return Response(status=200)
Ejemplo n.º 30
0
def info(request: Request) -> Response:
    info_data = {
        "receive": {},
    }
    for asset in Asset.objects.filter(sep31_enabled=True):
        try:
            fields_and_types = registered_sep31_receiver_integration.info(
                asset, request.GET.get("lang"))
        except ValueError:
            return render_error_response("unsupported 'lang'")
        try:
            validate_info_response(fields_and_types)
        except ValueError as e:
            logger.error(f"info integration error: {str(e)}")
            return render_error_response(
                _("unable to process the request"),
                status_code=500,
            )
        info_data["receive"][asset.code] = get_asset_info(
            asset, fields_and_types)

    return Response(info_data)