From e8a62151c72a43b14ab3e1e9c1ed509fbc5b4c42 Mon Sep 17 00:00:00 2001 From: Lukas Schreiner Date: Wed, 5 Dec 2018 21:18:48 +0100 Subject: [PATCH] Chipcard now working. Added examples so that the people don't need to figure out by them own how to do these things. --- README.md | 4 +- aqbanking/aqbanking.cpp | 23 +++++---- aqbanking/pyaqhandler.cpp | 100 ++++++++++++++++++++++++++++++++++++++ aqbanking/pyaqhandler.hpp | 4 ++ examples/chkiban.py | 3 ++ examples/getbalance.py | 50 +++++++++++++++++++ examples/getjobs.py | 47 ++++++++++++++++++ examples/listaccs.py | 44 +++++++++++++++++ examples/transactions.py | 51 +++++++++++++++++++ examples/transfer.py | 46 ++++++++++++++++++ setup.py | 12 +++-- 11 files changed, 370 insertions(+), 14 deletions(-) create mode 100644 examples/chkiban.py create mode 100644 examples/getbalance.py create mode 100644 examples/getjobs.py create mode 100644 examples/listaccs.py create mode 100644 examples/transactions.py create mode 100644 examples/transfer.py diff --git a/README.md b/README.md index 1e291fe..44babfd 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,11 @@ Implemented is: `nationalTransfer` and `sepaTransfer`. Furthermore if you're doing some transfer you're partially asked to enter three times the password. Now you can build your PIN cache with help of the `set_callbackPasswordStatus` function. This calls the python callback with parameters `token`, `pin` and `status` whereas the status field can be 9 = reset, 1 = Bad password, 2 = Remove password and 0 = all went fine. +You can find some examples inside of the `examples/` folder. + Known Bugs/Missing features =========================== -Right now, this extension does not support the Smartcard/Chipcard procedure for authentication. This is planned for the future, but will need time in order to write the corresponding backend. +Smartcard/Chipcard support meanwhile integrated. But no "text" that user has to enter something on the readers panel is provided. The server certificate of the HTTPS connection is not validated at the moment, so do not use it for sensitive data, as man in the middle attack is possible without notice. diff --git a/aqbanking/aqbanking.cpp b/aqbanking/aqbanking.cpp index e5e8310..c9136dc 100644 --- a/aqbanking/aqbanking.cpp +++ b/aqbanking/aqbanking.cpp @@ -659,6 +659,7 @@ static PyObject *aqbanking_Account_balance(aqbanking_Account* self, PyObject *ar double balance; const char *bank_code; const char *account_no; + PyObject *currency; // Check if all necessary data in account object is given! if (self->no == NULL) @@ -708,19 +709,23 @@ static PyObject *aqbanking_Account_balance(aqbanking_Account* self, PyObject *ar job = AB_JobGetBalance_new(a); AB_Job_List2_PushBack(jl, job); rv = AB_Banking_ExecuteJobs(self->ab, jl, ctx); - - if (rv) + if (rv > 0) { PyErr_SetString(ExecutionFailed, "Could not get the balance!"); return NULL; } // With success. No process the result. - ai = AB_ImExporterContext_GetFirstAccountInfo (ctx); - status = AB_ImExporterAccountInfo_GetFirstAccountStatus (ai); - bal = AB_AccountStatus_GetBookedBalance (status); - v = AB_Balance_GetValue (bal); + ai = AB_ImExporterContext_GetFirstAccountInfo(ctx); + if (ai == NULL) { + PyErr_SetString(ExecutionFailed, "Could not retrieve balance."); + return NULL; + } + status = AB_ImExporterAccountInfo_GetFirstAccountStatus(ai); + bal = AB_AccountStatus_GetBookedBalance(status); + v = AB_Balance_GetValue(bal); balance = AB_Value_GetValueAsDouble(v); + currency = PyUnicode_FromString(AB_Value_GetCurrency(v)); // Free jobs. AB_Job_List2_free(jl); @@ -730,11 +735,11 @@ static PyObject *aqbanking_Account_balance(aqbanking_Account* self, PyObject *ar rv = AB_free(self); if (rv > 0) { + PyErr_SetString(ExecutionFailed, "Could not free up aqbanking."); return NULL; } - // FIXME: currency! - return Py_BuildValue("(d,s)", balance, "EUR"); + return Py_BuildValue("(d,O)", balance, currency); } static PyObject *aqbanking_Account_available_jobs(aqbanking_Account* self, PyObject *args, PyObject *keywds) @@ -1066,7 +1071,7 @@ static PyObject *aqbanking_Account_transactions(aqbanking_Account* self, PyObjec } trans->value = PyFloat_FromDouble(AB_Value_GetValueAsDouble(v)); - trans->currency = PyUnicode_FromString("EUR"); + trans->currency = PyUnicode_FromString(AB_Value_GetCurrency(v)); trans->uniqueId = PyLong_FromLong(AB_Transaction_GetUniqueId(t)); if (AB_Transaction_GetTransactionText(t) == NULL) { trans->transactionText = PyUnicode_FromString(""); diff --git a/aqbanking/pyaqhandler.cpp b/aqbanking/pyaqhandler.cpp index 1094d0a..65e4a55 100644 --- a/aqbanking/pyaqhandler.cpp +++ b/aqbanking/pyaqhandler.cpp @@ -5,18 +5,118 @@ #include #include #include +#include +#include +#include #include "pyaqhandler.hpp" PyAqHandler::PyAqHandler() : CppGui() { GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Verbous); GWEN_Gui_SetGui(this->_gui); + GWEN_Gui_AddFlags(_gui, GWEN_GUI_FLAGS_DIALOGSUPPORTED); + GWEN_Gui_SetName(_gui, "pyaq-gui"); this->callbackLog = NULL; this->callbackPassword = NULL; this->callbackCheckCert = NULL; this->callbackPasswordStatus = NULL; } +int PyAqHandler::setupDialog(GWEN_WIDGET *w) { + CppWidget *xw=NULL; + printf("GWEN Dialog Widget: %d (%s)\n\n", + GWEN_Widget_GetType(w), + GWEN_Widget_Type_toString(GWEN_Widget_GetType(w))); + + + switch(GWEN_Widget_GetType(w)) { + case GWEN_Widget_TypeLabel: + xw = new CppWidget(w); + const char *s; + const char *name; + name=xw->getName(); + s=xw->getText(0); + printf("Got text in label %s: %s\n", name, s); + break; + case GWEN_Widget_TypeDialog: + case GWEN_Widget_TypeVLayout: + case GWEN_Widget_TypeHLayout: + case GWEN_Widget_TypeGridLayout: + case GWEN_Widget_TypeLineEdit: + case GWEN_Widget_TypeVSpacer: + case GWEN_Widget_TypeHSpacer: + case GWEN_Widget_TypePushButton: + case GWEN_Widget_TypeHLine: + case GWEN_Widget_TypeVLine: + case GWEN_Widget_TypeTextEdit: + case GWEN_Widget_TypeComboBox: + case GWEN_Widget_TypeTabBook: + case GWEN_Widget_TypeTabPage: + case GWEN_Widget_TypeCheckBox: + case GWEN_Widget_TypeGroupBox: + case GWEN_Widget_TypeWidgetStack: + case GWEN_Widget_TypeTextBrowser: + case GWEN_Widget_TypeScrollArea: + case GWEN_Widget_TypeProgressBar: + case GWEN_Widget_TypeListBox: + case GWEN_Widget_TypeRadioButton: + case GWEN_Widget_TypeSpinBox: + default: + DBG_ERROR(GWEN_LOGDOMAIN, "Unhandled widget type %d (%s)", + GWEN_Widget_GetType(w), GWEN_Widget_Type_toString(GWEN_Widget_GetType(w))); + break; + } + + GWEN_WIDGET *wChild; + wChild=GWEN_Widget_Tree_GetFirstChild(w); + while(wChild) { + setupDialog(wChild); + wChild=GWEN_Widget_Tree_GetNext(wChild); + } + + return 0; +} + +int PyAqHandler::openDialog(GWEN_DIALOG *dlg, uint32_t guiid) { + printf("PyAqHandler::OpenDialog\n"); + // Finally this should be passed sometime to python. + // For now we need also to extract data. + CppDialog *cppDlg = new CppDialog(dlg); + GWEN_DIALOG *gwd = cppDlg->getCInterface(); + GWEN_WIDGET *w; + GWEN_WIDGET_TREE *wtree = GWEN_Dialog_GetWidgets(gwd); + // We need a tree + if (wtree==NULL) { + DBG_ERROR(GWEN_LOGDOMAIN, "No widget tree in dialog"); + return GWEN_ERROR_GENERIC; + } + + // First child. + w=GWEN_Widget_Tree_GetFirst(wtree); + if (w==NULL) { + DBG_ERROR(GWEN_LOGDOMAIN, "No widgets in dialog"); + return GWEN_ERROR_GENERIC; + } + printf("GWEN Dialog Widget: %d (%s)\n\n", + GWEN_Widget_GetType(w), + GWEN_Widget_Type_toString(GWEN_Widget_GetType(w))); + + w=GWEN_Widget_Tree_GetFirstChild(w); + while(w) { + setupDialog(w); + w=GWEN_Widget_Tree_GetNext(w); + } + + return 0; +} + + + +int PyAqHandler::closeDialog(GWEN_DIALOG *dlg) { + printf("PyAqHandler::CloseDialog\n"); + return 0; +} + int PyAqHandler::setPasswordStatus(const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS status, uint32_t guiid) { int pystat = 0; diff --git a/aqbanking/pyaqhandler.hpp b/aqbanking/pyaqhandler.hpp index 4fded34..e4d8a39 100644 --- a/aqbanking/pyaqhandler.hpp +++ b/aqbanking/pyaqhandler.hpp @@ -14,6 +14,7 @@ static PyObject *AqBankingDeInitializeError; * This handles all request and so it also gives the opportunity to have handlers per account! */ class PyAqHandler : public CppGui { + public: PyAqHandler(); @@ -30,6 +31,9 @@ class PyAqHandler : public CppGui { virtual int getPassword(uint32_t flags, const char *token, const char *title, const char *text, char *buffer, int minLen, int maxLen, uint32_t guiid); virtual int checkCert(const GWEN_SSLCERTDESCR *cd, GWEN_SYNCIO *sio, uint32_t guiid); virtual int setPasswordStatus(const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS status, uint32_t guiid); + virtual int setupDialog(GWEN_WIDGET *w); + virtual int openDialog(GWEN_DIALOG *dlg, uint32_t guiid); + virtual int closeDialog(GWEN_DIALOG *dlg); }; #endif /* PYAQHANDLER_HPP */ \ No newline at end of file diff --git a/examples/chkiban.py b/examples/chkiban.py new file mode 100644 index 0000000..d9ffae8 --- /dev/null +++ b/examples/chkiban.py @@ -0,0 +1,3 @@ +import aqbanking +d = aqbanking.chkiban('') +print(d) diff --git a/examples/getbalance.py b/examples/getbalance.py new file mode 100644 index 0000000..b2d4276 --- /dev/null +++ b/examples/getbalance.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import aqbanking +import getpass + +cachedPasswords = {} + +def callback(domain, prio, msg): + print('[LOG]: %r' % (msg,)) + +# const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS enumStatus, uint32_t guiid +# 0 = OK +# 1 = Bad +# 2 = Remove +# 4 = Used +# 8 = Unused +# 16 = Unknown +def passwordStatus_cb(token, pin, status): + print('cb_pw_status: %s / / %d' % (token, status)) + if status == 2 or status == 1: + try: + del(cachedPasswords[token]) + except: + pass + elif status == 0: + cachedPasswords[token] = pin + +def password_cb(flags, token, title, text, minLen, maxLen): + # Ask only, if we didn't asked already for a PIN. + try: + return cachedPasswords[token] + except: + plainText = text if '' not in text else text[:text.find('')].replace('\r', '').replace('\n', '') + pin = getpass.getpass('%s: ' % (plainText,)) + return pin + +for f in aqbanking.listacc(): + print('Available configured banks: ', f.bank_name) + +acc = aqbanking.Account(no='100254687', bank_code='35468754') +acc.set_callbackLog(callback) +acc.set_callbackPassword(password_cb) +acc.set_callbackPasswordStatus(passwordStatus_cb) +ret = acc.balance() +if ret is not None: + try: + print(ret[0], ' ', ret[1]) + except TypeError: + pass diff --git a/examples/getjobs.py b/examples/getjobs.py new file mode 100644 index 0000000..77de125 --- /dev/null +++ b/examples/getjobs.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import aqbanking +import getpass + +cachedPasswords = {} + +def callback(domain, prio, msg): + print('[LOG]: %r' % (msg,)) + +# const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS enumStatus, uint32_t guiid +# 0 = OK +# 1 = Bad +# 2 = Remove +# 4 = Used +# 8 = Unused +# 16 = Unknown +def passwordStatus_cb(token, pin, status): + print('cb_pw_status: %s / / %d' % (token, status)) + if status == 2 or status == 1: + try: + del(cachedPasswords[token]) + except: + pass + elif status == 0: + cachedPasswords[token] = pin + +def password_cb(flags, token, title, text, minLen, maxLen): + # Ask only, if we didn't asked already for a PIN. + try: + return cachedPasswords[token] + except: + plainText = text if '' not in text else text[:text.find('')].replace('\r', '').replace('\n', '') + pin = getpass.getpass('%s: ' % (plainText,)) + return pin + +acc = aqbanking.Account(no='100254687', bank_code='35468754') +acc.set_callbackLog(callback) +acc.set_callbackPassword(password_cb) +acc.set_callbackPasswordStatus(passwordStatus_cb) +ret = acc.availableJobs() +if ret is not None: + try: + print(ret[0]) + except TypeError: + pass diff --git a/examples/listaccs.py b/examples/listaccs.py new file mode 100644 index 0000000..76963b6 --- /dev/null +++ b/examples/listaccs.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import aqbanking +import getpass + +cachedPasswords = {} + +def callback(domain, prio, msg): + print('[LOG]: %r' % (msg,)) + +# const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS enumStatus, uint32_t guiid +# 0 = OK +# 1 = Bad +# 2 = Remove +# 4 = Used +# 8 = Unused +# 16 = Unknown +def passwordStatus_cb(token, pin, status): + print('cb_pw_status: %s / / %d' % (token, status)) + if status == 2 or status == 1: + try: + del(cachedPasswords[token]) + except: + pass + elif status == 0: + cachedPasswords[token] = pin + +def password_cb(flags, token, title, text, minLen, maxLen): + # Ask only, if we didn't asked already for a PIN. + try: + return cachedPasswords[token] + except: + plainText = text if '' not in text else text[:text.find('')].replace('\r', '').replace('\n', '') + pin = getpass.getpass('%s: ' % (plainText,)) + return pin + +accs = aqbanking.listacc() +print(accs) +# you can then access directly the account and can continue on it... +acc = accs[0] +acc.set_callbackLog(callback) +acc.set_callbackPassword(password_cb) +acc.set_callbackPasswordStatus(passwordStatus_cb) diff --git a/examples/transactions.py b/examples/transactions.py new file mode 100644 index 0000000..de19d4c --- /dev/null +++ b/examples/transactions.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import aqbanking +import datetime +import getpass + +cachedPasswords = {} + +def callback(domain, prio, msg): + print('[LOG]: %r' % (msg,)) + +# const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS enumStatus, uint32_t guiid +# 0 = OK +# 1 = Bad +# 2 = Remove +# 4 = Used +# 8 = Unused +# 16 = Unknown +def passwordStatus_cb(token, pin, status): + print('cb_pw_status: %s / / %d' % (token, status)) + if status == 2 or status == 1: + try: + del(cachedPasswords[token]) + except: + pass + elif status == 0: + cachedPasswords[token] = pin + +def password_cb(flags, token, title, text, minLen, maxLen): + # Ask only, if we didn't asked already for a PIN. + try: + return cachedPasswords[token] + except: + plainText = text if '' not in text else text[:text.find('')].replace('\r', '').replace('\n', '') + pin = getpass.getpass('%s: ' % (plainText,)) + return pin + + +acc = aqbanking.Account(no='100254687', bank_code='35468754') +acc.set_callbackLog(callback) +acc.set_callbackPassword(password_cb) +acc.set_callbackPasswordStatus(passwordStatus_cb) +now = datetime.datetime.now() +dateFrom = (now - datetime.timedelta(days=30)).strftime('%Y%m%d') +dateTo = now.strftime('%Y%m%d') +ret = acc.transactions(dateFrom, dateTo) +import pprint +pprint.pprint(ret) +if len(ret) > 0: + print(ret[0].value, ret[0].currency) diff --git a/examples/transfer.py b/examples/transfer.py new file mode 100644 index 0000000..f4b8fec --- /dev/null +++ b/examples/transfer.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import aqbanking +import getpass + +cachedPasswords = {} + +def callback(domain, prio, msg): + print('[LOG]: %r' % (msg,)) + +# const char *token, const char *pin, GWEN_GUI_PASSWORD_STATUS enumStatus, uint32_t guiid +# 0 = OK +# 1 = Bad +# 2 = Remove +# 4 = Used +# 8 = Unused +# 16 = Unknown +def passwordStatus_cb(token, pin, status): + print('cb_pw_status: %s / / %d' % (token, status)) + if status == 2 or status == 1: + try: + del(cachedPasswords[token]) + except: + pass + elif status == 0: + cachedPasswords[token] = pin + +def password_cb(flags, token, title, text, minLen, maxLen): + # Ask only, if we didn't asked already for a PIN. + try: + return cachedPasswords[token] + except: + plainText = text if '' not in text else text[:text.find('')].replace('\r', '').replace('\n', '') + pin = getpass.getpass('%s: ' % (plainText,)) + return pin + +acc = aqbanking.Account(no='100254687', bank_code='35468754') +acc.set_callbackLog(callback) +acc.set_callbackPassword(password_cb) +ret = acc.enqueJob('Max Mustermann', 'ibannumber', 'bic', 'purpose of transfer', '', '', 0.01) +if ret is not None: + try: + print(ret[0]) + except TypeError: + pass diff --git a/setup.py b/setup.py index 46cddbb..eca9eae 100644 --- a/setup.py +++ b/setup.py @@ -7,16 +7,20 @@ # retrieve the README def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() + f = open(os.path.join(os.path.dirname(__file__), fname)) + cnt = f.read() + f.close() + return cnt module1 = Extension('aqbanking', libraries = ['gwenhywfar', 'aqbanking', 'gwengui-cpp'], include_dirs = ['/usr/include/gwenhywfar4', '/usr/include/aqbanking5', '/usr/local/include/gwenhywfar4', '/usr/local/include/aqbanking5'], # for compiling debug with python debug: - extra_compile_args=['-O0', '-g', '-Wunused-variable', '-std=gnu++11', '-DPy_DEBUG', '-Wunused-function', '-DDEBUG', '-DDEBUGSTDERR', '-DFENQUEJOB'], + #extra_compile_args=['-O0', '-g', '-Wunused-variable', '-std=gnu++11', '-DPy_DEBUG', '-Wunused-function', '-DDEBUG', '-DDEBUGSTDERR', '-DFENQUEJOB'], # for compiling debug without python debug - #extra_compile_args=['-O0', '-g', '-Wunused-variable', '-std=gnu++11', '-Wunused-function', '-DDEBUGSTDERR', '-DFENQUEJOB'], - #extra_compile_args=['-Wunused-variable', '-Wunused-function'], + extra_compile_args=['-O0', '-g', '-Wunused-variable', '-std=gnu++11', '-Wunused-function', '-DDEBUGSTDERR', '-DFENQUEJOB'], + # RELEASE parameter for compilation: + #extra_compile_args=['-Wunused-variable', '-Wunused-function', '-DFENQUEJOB'], sources = ['aqbanking/pyaqhandler.cpp', 'aqbanking/aqbanking.cpp'] )