Add sftp test server code

This commit is contained in:
Daniel Egger 2024-05-31 16:42:42 +02:00
parent f8c6e135e1
commit c89914107f
13 changed files with 88 additions and 131 deletions

3
.gitignore vendored
View File

@ -278,10 +278,11 @@ cypress/test-reports
git-crypt-encrypted-files-check.txt git-crypt-encrypted-files-check.txt
/server/vbv_lernwelt/static/css/tailwind.css /server/vbv_lernwelt/static/css/tailwind.css
/server/vbv_lernwelt/static/vue/ /server/vbv_lernwelt/static/vue/
/server/vbv_lernwelt/static/storybook /server/vbv_lernwelt/static/storybook
/server/vbv_lernwelt/templates/vue/index.html /server/vbv_lernwelt/templates/vue/index.html
/server/vbv_lernwelt/media /server/vbv_lernwelt/media
/client/src/gql/dist/minifiedSchema.json /client/src/gql/dist/minifiedSchema.json
/sftptest/

View File

@ -682,10 +682,13 @@ else:
"DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com" "DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com"
) )
# Only for debugging the webhook (locally)
DATATRANS_DEBUG_WEBHOOK_OVERWRITE = env( # default settings for python sftpserver test-server
"DATATRANS_DEBUG_WEBHOOK_OVERWRITE", default=None ABACUS_EXPORT_SFTP_HOST = env("ABACUS_EXPORT_SFTP_HOST", default="localhost")
) ABACUS_EXPORT_SFTP_PORT = env("ABACUS_EXPORT_SFTP_PORT", default="3373")
ABACUS_EXPORT_SFTP_USERNAME = env("ABACUS_EXPORT_SFTP_USERNAME", default="admin")
ABACUS_EXPORT_SFTP_PASSWORD = env("ABACUS_EXPORT_SFTP_PASSWORD", default="admin")
# S3 BUCKET CONFIGURATION # S3 BUCKET CONFIGURATION
FILE_UPLOAD_STORAGE = env("FILE_UPLOAD_STORAGE", default="s3") # local | s3 FILE_UPLOAD_STORAGE = env("FILE_UPLOAD_STORAGE", default="s3") # local | s3

View File

@ -14,8 +14,8 @@ from .base import * # noqa
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
DATABASES["default"]["NAME"] = "vbv_lernwelt_cypress" DATABASES["default"]["NAME"] = "vbv_lernwelt_cypress"
DATATRANS_API_ENDPOINT = 'http://localhost:8001/server/fakeapi/datatrans/api' DATATRANS_API_ENDPOINT = "http://localhost:8001/server/fakeapi/datatrans/api"
DATATRANS_PAY_URL = 'http://localhost:8001/server/fakeapi/datatrans/pay' DATATRANS_PAY_URL = "http://localhost:8001/server/fakeapi/datatrans/pay"
# EMAIL # EMAIL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -64,14 +64,14 @@ from vbv_lernwelt.importer.views import (
) )
from vbv_lernwelt.media_files.views import user_image from vbv_lernwelt.media_files.views import user_image
from vbv_lernwelt.notify.views import email_notification_settings from vbv_lernwelt.notify.views import email_notification_settings
from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as media_library_urls
from vbv_lernwelt.shop.datatrans_fake_server import ( from vbv_lernwelt.shop.datatrans_fake_server import (
fake_datatrans_api_view, fake_datatrans_api_view,
fake_datatrans_pay_view, fake_datatrans_pay_view,
) )
from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as media_library_urls
class SignedIntConverter(IntConverter): class SignedIntConverter(IntConverter):

View File

@ -41,3 +41,6 @@ truffleHog
# deployement and CI # deployement and CI
git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530 git+https://github.com/iterativ/Caprover-API.git@5013f8fc929e8e3281b9d609e968a782e8e99530
# sftpserver for tests
git+https://github.com/lonetwin/sftpserver.git@1d16896d3f0f90d63d1caaf4e199f2a9dde6456f

View File

@ -341,7 +341,9 @@ packaging==23.1
# pytest # pytest
# pytest-sugar # pytest-sugar
paramiko==3.3.1 paramiko==3.3.1
# via -r requirements.in # via
# -r requirements.in
# sftpserver
parso==0.8.3 parso==0.8.3
# via jedi # via jedi
pathspec==0.11.2 pathspec==0.11.2
@ -397,9 +399,7 @@ pyflakes==3.1.0
pygments==2.16.1 pygments==2.16.1
# via ipython # via ipython
pyjwt[crypto]==2.8.0 pyjwt[crypto]==2.8.0
# via # via msal
# msal
# pyjwt
pylint==2.17.5 pylint==2.17.5
# via # via
# pylint-django # pylint-django
@ -480,6 +480,8 @@ sendgrid==6.10.0
# via -r requirements.in # via -r requirements.in
sentry-sdk==1.29.2 sentry-sdk==1.29.2
# via -r requirements.in # via -r requirements.in
sftpserver @ git+https://github.com/lonetwin/sftpserver.git@1d16896d3f0f90d63d1caaf4e199f2a9dde6456f
# via -r requirements-dev.in
six==1.16.0 six==1.16.0
# via # via
# asttokens # asttokens
@ -621,9 +623,7 @@ wheel==0.41.1
whitenoise[brotli]==6.5.0 whitenoise[brotli]==6.5.0
# via -r requirements.in # via -r requirements.in
willow[heif]==1.6.1 willow[heif]==1.6.1
# via # via wagtail
# wagtail
# willow
wrapt==1.15.0 wrapt==1.15.0
# via astroid # via astroid

View File

@ -107,9 +107,10 @@ def fake_datatrans_pay_view(request, api_url=""):
def call_transaction_complete_webhook( def call_transaction_complete_webhook(
webhook_url, transaction_id, datatrans_status="settled" webhook_url, transaction_id, datatrans_status="settled"
): ):
import requests
import time import time
import requests
time.sleep(1) time.sleep(1)
payload = { payload = {
@ -119,14 +120,14 @@ def fake_datatrans_pay_view(request, api_url=""):
key_hex_bytes = bytes.fromhex(settings.DATATRANS_HMAC_KEY) key_hex_bytes = bytes.fromhex(settings.DATATRANS_HMAC_KEY)
# Create sign with timestamp and payload # Create sign with timestamp and payload
sign = hmac.new(key_hex_bytes, bytes(str(1) + json.dumps(payload), "utf-8"), hashlib.sha256) sign = hmac.new(
key_hex_bytes, bytes(str(1) + json.dumps(payload), "utf-8"), hashlib.sha256
)
response = requests.post( response = requests.post(
url=webhook_url, url=webhook_url,
json=payload, json=payload,
headers={ headers={"Datatrans-Signature": f"t=1,s0={sign.hexdigest()}"},
"Datatrans-Signature": f"t=1,s0={sign.hexdigest()}"
},
) )
print(response) print(response)

View File

@ -1,47 +1,28 @@
import datetime import datetime
from uuid import uuid4 from io import BytesIO
from xml.dom import minidom from xml.dom import minidom
from xml.etree.ElementTree import Element, SubElement, tostring from xml.etree.ElementTree import Element, SubElement, tostring
from vbv_lernwelt.shop.invoice.creator import InvoiceCreator, Item import structlog
from vbv_lernwelt.shop.invoice.repositories import InvoiceRepository from django.conf import settings
from paramiko.client import AutoAddPolicy, SSHClient
from vbv_lernwelt.shop.models import CheckoutInformation from vbv_lernwelt.shop.models import CheckoutInformation
class AbacusInvoiceCreator(InvoiceCreator): logger = structlog.get_logger(__name__)
def __init__(self, repository: InvoiceRepository):
self.repository = repository
def create_invoice(
self,
checkout_information: CheckoutInformation,
filename: str = None,
):
customer_number = checkout_information.transaction_id
order_date = checkout_information.created_at.date()
reference_purchase_order = str(checkout_information.id)
unic_id = checkout_information.transaction_id
items = [ def abacus_ssh_upload(checkout_information: CheckoutInformation):
Item( invoice_xml_filename, invoice_xml_content = create_invoice_xml(checkout_information)
product_number=checkout_information.product_sku, customer_xml_filename, customer_xml_content = create_customer_xml(
quantity=1, checkout_information
description=checkout_information.product_description, )
)
]
invoice = self.render_invoice_xml( abacus_ssh_upload_invoice(
customer_number, customer_xml_filename, customer_xml_content, folder="debitor"
order_date, )
reference_purchase_order, abacus_ssh_upload_invoice(invoice_xml_filename, invoice_xml_content, folder="order")
unic_id,
items,
)
if filename is None:
filename = f"vbv-vv-{uuid4().hex}.xml"
self.repository.upload_invoice(invoice, filename)
def create_invoice_xml(checkout_information: CheckoutInformation): def create_invoice_xml(checkout_information: CheckoutInformation):
@ -223,3 +204,33 @@ def create_xml_string(container: Element, encoding: str = "utf-8") -> str:
indent=" ", encoding=encoding indent=" ", encoding=encoding
) )
return xml_pretty_str.decode(encoding) return xml_pretty_str.decode(encoding)
def abacus_ssh_upload_invoice(
filename: str, content_xml: str, folder: str = ""
) -> None:
invoice_io = BytesIO(content_xml.encode("utf-8"))
ssh_client = SSHClient()
print(invoice_io)
try:
ssh_client.set_missing_host_key_policy(AutoAddPolicy())
ssh_client.connect(
hostname=settings.ABACUS_EXPORT_SFTP_HOST,
port=settings.ABACUS_EXPORT_SFTP_PORT,
username=settings.ABACUS_EXPORT_SFTP_USERNAME,
password=settings.ABACUS_EXPORT_SFTP_PASSWORD,
)
with ssh_client.open_sftp() as sftp_client:
path = filename
if folder:
path = f"{folder}/{filename}"
sftp_client.putfo(invoice_io, path)
except Exception as e:
logger.error("Could not upload invoice", exc_info=e)
finally:
ssh_client.close()

View File

@ -1,21 +0,0 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from vbv_lernwelt.shop.models import CheckoutInformation
@dataclass
class Item:
product_number: str
quantity: int
description: str
class InvoiceCreator(ABC):
@abstractmethod
def create_invoice(
self,
checkout_information: CheckoutInformation,
filename: str = None,
):
pass

View File

@ -1,44 +0,0 @@
from abc import ABC, abstractmethod
import structlog
logger = structlog.get_logger(__name__)
class InvoiceRepository(ABC):
@abstractmethod
def upload_invoice(self, invoice: str, filename: str):
pass
class SFTPInvoiceRepository(InvoiceRepository):
def __init__(self, hostname: str, username: str, password: str, port: int = 22):
self.hostname = hostname
self.username = username
self.password = password
self.port = port
def upload_invoice(self, invoice: str, filename: str) -> None:
from io import BytesIO
from paramiko import AutoAddPolicy, SSHClient
invoice_io = BytesIO(invoice.encode("utf-8"))
ssh_client = SSHClient()
try:
ssh_client.set_missing_host_key_policy(AutoAddPolicy())
ssh_client.connect(
self.hostname,
port=self.port,
username=self.username,
password=self.password,
)
with ssh_client.open_sftp() as sftp_client:
sftp_client.putfo(invoice_io, f"uploads/{filename}")
except Exception as e:
logger.error("Could not upload invoice", exc_info=e)
finally:
ssh_client.close()

View File

@ -105,6 +105,6 @@ class Migration(migrations.Migration):
name="old_company_country", name="old_company_country",
), ),
migrations.DeleteModel( migrations.DeleteModel(
name='BillingAddress', name="BillingAddress",
), ),
] ]

View File

@ -53,13 +53,6 @@ def init_datatrans_transaction(
redirect_url_cancel: str, redirect_url_cancel: str,
webhook_url: str, webhook_url: str,
): ):
if overwrite := settings.DATATRANS_DEBUG_WEBHOOK_OVERWRITE:
logger.warning(
"APPLYING DEBUG DATATRANS WEBHOOK OVERWRITE!",
webhook_url=overwrite,
)
webhook_url = overwrite
payload = { payload = {
# We use autoSettle=True, so that we don't have to settle the transaction: # We use autoSettle=True, so that we don't have to settle the transaction:
# -> Be aware that autoSettle has implications of the possible transaction states # -> Be aware that autoSettle has implications of the possible transaction states
@ -77,8 +70,8 @@ def init_datatrans_transaction(
} }
# add testing configuration data # add testing configuration data
if 'fakeapi' in settings.DATATRANS_API_ENDPOINT: if "fakeapi" in settings.DATATRANS_API_ENDPOINT:
payload['user_id'] = str(user.id) payload["user_id"] = str(user.id)
logger.info("Initiating transaction", payload=payload) logger.info("Initiating transaction", payload=payload)

10
start_sftpserver.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# set location to script directory
cd "${0%/*}"
# start python sftp test server (for abacus exports)
rm -rf sftptest
mkdir -p sftptest/debitor
mkdir -p sftptest/order
(cd sftptest && sftpserver -p 3373)