def test_reject(self):
        tx, _ = _generate_tx(False)
        resp = SignTransactionResponse(tx)
        assert not resp.rejected

        resp.reject()
        assert resp.rejected
Example #2
0
    def handle_sign_transaction(self, f: Callable[
        [SignTransactionRequest, SignTransactionResponse],
        None], signature: str, req_body: str) -> Tuple[int, str]:
        """A hook for handling a sign transaction request from Agora.

        :param f: A function to call with the received event. Implementations can raise
            :exc:`InvoiceError <agora.error.InvoiceError>` to return a 403 response with invoice error details or
            :exc:`WebhookRequestError <agora.error.WebhookRequestError>` to return a specific HTTP status code and body.
        :param signature: The Agora HMAC signature included in the request headers.
        :param req_body: The request body.
        :return: A Tuple of the status code (int) and the request body (str).
        """

        if self.secret and not self.is_valid_signature(req_body, signature):
            return 401, ''

        try:
            json_req_body = json.loads(req_body)
        except JSONDecodeError:
            return 400, 'invalid request body'

        req = SignTransactionRequest.from_json(json_req_body, self.environment)
        resp = SignTransactionResponse(req.envelope)
        try:
            f(req, resp)
        except WebhookRequestError as e:
            return e.status_code, e.response_body
        except Exception as e:
            return 500, str(e)

        data = resp.to_json()
        if resp.rejected:
            return 403, json.dumps(data)

        return 200, json.dumps(data)
Example #3
0
    def test_mark_invoice_error(self):
        resp = SignTransactionResponse(_generate_envelope())
        resp.mark_invoice_error(5, InvoiceErrorReason.SKU_NOT_FOUND)

        assert resp.rejected
        assert len(resp.invoice_errors) == 1
        assert resp.invoice_errors[0].op_index == 5
        assert resp.invoice_errors[0].reason == InvoiceErrorReason.SKU_NOT_FOUND
Example #4
0
    def test_sign(self):
        resp = SignTransactionResponse(_generate_envelope())

        private_key = PrivateKey.random()
        resp.sign(private_key)

        # kp.verify throws an error if the signature doesn't match
        private_key.public_key.verify(resp.envelope.hash_meta(), resp.envelope.signatures[-1].signature)
Example #5
0
def _sign_transaction(req: SignTransactionRequest,
                      resp: SignTransactionResponse):
    for idx, payment in enumerate(req.payments):
        # Double check that the transaction crafter isn't trying to impersonate us
        if payment.sender == webhook_private_key.public_key():
            logging.warning("rejecting: payment sender is webhook address")
            resp.reject()
            return

        # In this example, we don't want to sign transactions that are not sending Kin to the webhook account. Other
        # application use cases may not have this restrictions
        if payment.dest != webhook_private_key.public_key():
            logging.warning(
                "rejecting: bad destination {}, expected {}".format(
                    payment.dest.stellar_address,
                    webhook_private_key.public_key().stellar_address))
            resp.mark_invoice_error(idx, InvoiceErrorReason.WRONG_DESTINATION)

        # If the transaction crafter submitted an invoice, make sure the line item SKUs are set.
        #
        # Note: the SKU is optional, but we simulate a rejection here for testing.
        # Applications may wish to cross-check their own databases for the item being purchased. If the user has already
        # purchased said 'item', they may wish to use mark the invoice error as InvoiceErrorReason.ALREADY_PAID.
        if payment.invoice:
            for line_item in payment.invoice.items:
                if not line_item.sku:
                    logging.warning("rejecting: invoice missing sku")
                    resp.mark_invoice_error(idx,
                                            InvoiceErrorReason.SKU_NOT_FOUND)

    tx_hash_str = req.get_tx_hash().hex()
    if resp.rejected:
        logging.warning("transaction rejected: {} ({} payments)".format(
            tx_hash_str, len(req.payments)))
        return

    logging.debug("transaction approved: {} ({} payments)".format(
        tx_hash_str, len(req.payments)))

    # Note: This allows Agora to forward the transaction to the blockchain. However, it does not indicate that it will
    # be submitted successfully, or that the transaction will be successful. For example, the sender may have
    # insufficient funds.
    #
    # Backends may keep track of the transaction themselves using SignTransactionRequest.get_tx_hash() and rely on
    # either the Events webhook or polling to get the transaction status.
    resp.sign(webhook_private_key)
    return
    def test_to_json(self):
        env = _generate_envelope()
        # not rejected
        resp = SignTransactionResponse(env)
        assert resp.to_json() == {"envelope_xdr": env.xdr().decode()}

        # rejected
        resp.reject()
        assert resp.to_json() == {}

        # rejected with invoice errors
        resp.mark_invoice_error(0, InvoiceErrorReason.ALREADY_PAID)
        assert resp.to_json() == {
            "invoice_errors": [{
                "operation_index":
                0,
                "reason":
                InvoiceErrorReason.ALREADY_PAID.to_lowercase()
            }]
        }
    def test_sign(self):
        tx, _ = _generate_tx(False)
        resp = SignTransactionResponse(tx)
        resp.sign(_SIGNING_KEY)

        _SIGNING_KEY.public_key.verify(resp.transaction.message.marshal(), resp.transaction.signatures[0])
    def test_reject(self):
        resp = SignTransactionResponse(_generate_envelope())
        assert not resp.rejected

        resp.reject()
        assert resp.rejected
Example #9
0
 def _sign_tx_return_rejected(req: SignTransactionRequest,
                              resp: SignTransactionResponse):
     resp.mark_invoice_error(0, InvoiceErrorReason.UNKNOWN)
Example #10
0
 def _sign_tx_success(req: SignTransactionRequest,
                      resp: SignTransactionResponse):
     resp.sign(_TEST_PRIVATE_KEY)