Removed BRE/mBank support. Good riddance. ~enleth

master
Kasownik 2018-02-27 18:44:05 +01:00
parent a49283cbe3
commit 3f7ef801f9
7 changed files with 17 additions and 313 deletions

View File

@ -1,238 +0,0 @@
#!/usr/bin/env/python2
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Sergiusz Bazanski <q3k@q3k.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import csv
import datetime
import re
import hashlib
import StringIO
import requests
import bs4
import time
from webapp import app
class BREParseError(Exception):
pass
class BRERow(object):
BRE_IN = [772, 773, 770, 968, 958, 963, 592]
SECRET = app.config["SECRET"]
def parse_data(self):
datar = self.data_raw.split(";")
data = {}
for d in datar[1:]:
kv = d.split(":")
k = kv[0].strip()
v = ":".join(kv[1:]).strip()
data[k] = v
if self._type in self.BRE_IN:
# in
try:
# internal transfers use the same type ID for both in and out
if "dla" in data:
return
self.type = "IN"
self.from_name = data["od"]
self.from_account = data["z rach."]
if "tyt." in data:
self.title = data["tyt."].lower()
elif "ref. zlec." in data:
self.title = data["ref. zlec."].lower()
else:
self.title = "[no transfer title]"
self.tnr = int(data["TNR"].split(".")[0])
except KeyError:
print "Error parsing transfer data ", data
raise
self.olduid = hashlib.sha256(self.SECRET + ','.join(self.raw).encode("utf-8")).hexdigest()
self.uid = hashlib.sha256(self.SECRET + data["TNR"]).hexdigest()
def __init__(self, row):
self.time = datetime.datetime.strptime(row[1], "%d/%m/%Y")
self.account = row[2]
# is this secure?
self.amount = int(float(row[3].replace(",", ".").replace(" ", "")) * 100)
self._type = int(row[6])
self.data_raw = row[5]
self.type = ""
self.raw = row
class BREParser(object):
def __init__(self):
self.rows = []
def parse(self, snapshot):
c = csv.reader(snapshot, delimiter="|")
for row in c:
r = BRERow([r.decode("iso-8859-2") for r in row])
r.parse_data()
self.rows.append(r)
def get_by_type(self, y):
return [row for row in self.rows if row.type == y]
def guess_title(title):
m = re.match(ur"^([a-z0-9\-_\.]+) *\- *(fatty|starving) *z\- *([0-9a-z\-_ąężźćóżłśń]+$)", title.strip().lower())
if not m:
return None, None, None
member, _type, title = m.group(1), m.group(2), m.group(3)
if title in [u"składka", u"opłata", u"opłata miesięczna", "skladka"]:
return member, _type, None
return member, _type, title
class BREFetcher(object):
BASE = "https://companynet.mbank.pl/mt/"
def __init__(self):
self.uid = app.config["BRE_UID"]
self.logging_token = None
self.token = None
self.s = requests.Session()
def _get(self, page):
url = self.BASE + page
#r = self.s.get(url,verify=app.config["BRE_CA_PATH"])
r = self.s.get(url)
print "[i] GET {} -> {}".format(page, r.status_code)
if r.status_code != 200:
raise Exception("return code %i" % r.status_code)
return bs4.BeautifulSoup(r.text)
def _post(self, page, data):
url = self.BASE + page
mdata = {}
mdata["screenWidth"] = 1337
mdata["screenHeight"] = 1337
mdata["LOGGING_TOKEN"] = self.logging_token
mdata["lang"] = ""
mdata.update(data)
r = self.s.post(url, mdata)
print "[i] POST {} -> {}".format(page, r.status_code)
if r.status_code != 200:
raise Exception("return code %i" % r.status_code)
return bs4.BeautifulSoup(r.text)
def _gettoken(self, soup):
# print soup
menulinks = soup.findAll("a", "menulink")
onclick = menulinks[0]["onclick"]
self.token = re.search(r"TOKEN=([a-z0-9]+)", onclick).group(1)
print "[i] Token: {}".format(self.token)
def login(self, username, token):
main = self._get("fragments/cua/login.jsp")
self.logging_token = main.find("input", type="hidden", attrs={"name": "LOGGING_TOKEN"})["value"]
data = {}
data["TARGET"] = "/cualogin.do"
data["RAWPASSWORD"] = token
data["loginType"] = "token"
data["LOGIN_OR_ALIAS"] = username
logged = self._post("fragments/cua/login.fcc", data)
self._gettoken(logged)
def create_report(self):
reportpage = self._get("main/navigate.do?templateId={}&to=newReport&org.apache.struts.taglib.html.TOKEN={}".format(self.uid, self.token))
self._gettoken(reportpage)
def generate_command(command, commandlist):
data = {
"synchronous": "on",
"pagerOffset": 0,
"pager.page": 0,
"pager.newPage": 0,
"org.apache.struts.taglib.html.TOKEN": self.token,
"filter.orderByColumn": None,
"filter.orderByAscending": "false",
"commandlist": commandlist,
"command": command,
}
return data
def setparameter(item, value, extra=None):
data = generate_command("editItem", "generate")
data["selectedItemKey"] = item
generate = self._post("report/generate/submitParameterList.do", data)
self._gettoken(generate)
data = {}
data["value"] = value
data["org.apache.struts.taglib.html.TOKEN"] = self.token
data["commandlist"] = "ok"
data["command"] = "ok"
if extra:
data.update(extra)
specify = self._post("report/specify/submitParameter.do", data)
self._gettoken(specify)
setparameter("{}.0[account]".format(self.uid), 1060690633)
setparameter("{}.0[from_date]".format(self.uid), "01.01.2001 00:00", {"predefiniedValue.selectedKey": "@empty", "valueTimePart": "00:00", "valueDatePart": "01.01.2001"})
setparameter("{}.0[to_date]".format(self.uid), "", {"predefiniedValue.selectedKey": "@currentday", "valueTimePart": "", "valueDatePart": ""})
data = generate_command("generate", "generate")
data["selectedItemKey"] = "{}.0[to_date]".format(self.uid)
submit_parameter_list = self._post("report/generate/submitParameterList.do", data)
self._gettoken(submit_parameter_list)
print "[i] Waiting..."
time.sleep(3)
data = {}
data["org.apache.struts.taglib.html.TOKEN"] = self.token
data["commandlist"] = "showReport"
data["command"] = "showReport"
report = self._post("report/generate/submitPleaseWait.do", data)
self._gettoken(report)
data = generate_command("export", "export")
report_ready = self._post("report/submitReportReady.do", data)
self._gettoken(report_ready)
data = generate_command("processExport", "processExport")
data["format.selectedKey"] = "dat"
data["fileName"] = "raport"
data["encoding.selectedKey"] = "iso"
export = self._post("report/submitExport.do", data)
self._gettoken(export)
fileurl = "report/downloadExport.do?command=download&FileNumber=0"
f = self.s.post(self.BASE + fileurl, dict(FileNumber=0), stream=True)
return f.raw
if __name__ == "__main__":
f = BREFetcher()
f.login(raw_input("[?] ID: "), raw_input("[?] Token: "))
print f.create_report().read()

View File

@ -37,10 +37,6 @@ class ContactEmailSettingsForm(Form):
ldap = BooleanField("")
custom = TextField("Custom address:")
class BREFetchForm(Form):
identifier = TextField("Identifier", [validators.Required()])
token = PasswordField("Identifier", [validators.Required()])
class LDAPSyncForm(Form):
fatty_to_add = MultiCheckboxField("Fatty to add", choices=[])
fatty_to_remove = MultiCheckboxField("Fatty to remove", choices=[])

View File

@ -22,33 +22,10 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""The 'business' logic of the whole thing.
It updates database transfer rows based on BRE snapshots, and it also
tries to match transfers onto members, updating their payment status."""
"""The 'business' logic of the whole thing."""
from webapp import app, db
import banking
import models
def update_transfer_rows():
f = open(app.config["BRE_SNAPSHOT_PATH"])
parser = banking.BREParser()
parser.parse(f)
f.close()
for row in parser.get_by_type("IN"):
transfer = models.Transfer.query.filter_by(uid=row.uid).first()
if not transfer:
transfer = models.Transfer(None, row.uid,
row.from_account,
row.from_name,
row.amount, row.title,
row.time,
False)
db.session.add(transfer)
db.session.commit()
def get_unmatched_transfers():
return models.Transfer.query.filter_by(member_transfers=None,ignore=False).order_by(models.Transfer.date.asc()).all()

View File

@ -9,13 +9,7 @@
<h4>Active operations:</h4>
<h4>Available operations:</h4>
<p>
<!--<form action="/fetch" method="post">
<button type="button" class="btn btn-primary">Fetch transfer data</button>
</form>
<form action="/spam" method="post">
<button type="button" class="btn btn-primary">Send reminders</button>
</form>-->
<a href="/admin/fetch"><b>Fetch transfer data</b></a>
<a href="/admin/match"><b>Match transfers</b></a>
<a href="/admin/ldapsync"><b>Synchronize LDAP groups</b></a>
<a href="/admin/spam"><b>Spam members</b></a>
</p>

View File

@ -9,13 +9,7 @@
<h4>Active operations:</h4>
<h4>Available operations:</h4>
<p>
<!--<form action="/fetch" method="post">
<button type="button" class="btn btn-primary">Fetch transfer data</button>
</form>
<form action="/spam" method="post">
<button type="button" class="btn btn-primary">Send reminders</button>
</form>-->
<a href="/admin/fetch"><b>Fetch transfer data</b></a>
<a href="/admin/match"><b>Match transfers</b></a>
<a href="/admin/ldapsync"><b>Synchronize LDAP groups</b></a>
</p>
</div>

View File

@ -1,14 +1,9 @@
{% extends "root.html" %}
{% block title %}brefetch{% endblock %}
{% block title %}Match transfers{% endblock %}
{% block content %}
<div class="container">
<h2>Fetch banking data from BRE</h2>
<form method="post" action="/admin/fetch">
{{ form.identifier()|safe }}
{{ form.token()|safe }}
<input type="submit" value="Fetch" />
</form>
<h2>Match transfers</h2>
<h2>Matching operations</h2>
<a href="/admin/match/auto">Match all easily matchable transfers</a><br />
<a href="/admin/match/manual">Match manually all unmatched transfers</a><br />

View File

@ -35,7 +35,6 @@ from subprocess import Popen, PIPE
from webapp import app, forms, User, db, models, mc, cache_enabled, admin_required
from flask.ext.login import login_user, login_required, logout_user, current_user
from flask import Response, request, redirect, flash, render_template, url_for, abort, g
import banking
import logic
import directory
import traceback
@ -214,33 +213,12 @@ def add_member(membershiptype, username):
db.session.commit()
return "ok"
@app.route("/admin/fetch", methods=["GET", "POST"])
@app.route("/admin/match")
@login_required
@admin_required
def admin_fetch():
form = forms.BREFetchForm(request.form)
if request.method == "POST" and form.validate():
identifier = form.identifier.data
token = form.token.data
try:
b = banking.BREFetcher()
b.login(identifier, token)
data = b.create_report().read()
flash("Fetched data from BRE ({} rows)".format(data.count("\n")))
f = open(app.config["BRE_SNAPSHOT_PATH"], "w")
f.write(data)
f.close()
return redirect(url_for("admin_fetch"))
except Exception as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
flash("Error when fetching data. <pre>%s</pre>" % traceback.format_exception(exc_type, exc_value,exc_traceback))
return redirect(url_for("admin_fetch"))
logic.update_transfer_rows()
def admin_match():
transfers_unmatched = logic.get_unmatched_transfers()
return render_template("fetch.html", form=form, transfers_unmatched=transfers_unmatched)
return render_template("match.html", transfers_unmatched=transfers_unmatched)
@app.route("/admin/match/auto", methods=["GET"])
@ -280,7 +258,7 @@ def admin_match_auto():
for member in affected_members:
member.get_status(force_refresh=True)
flash("Matched %i, %i left" % (matched, left))
return redirect(url_for("admin_fetch"))
return redirect(url_for("admin_match"))
@app.route("/admin/match/manual", methods=["GET"])
@login_required
@ -349,3 +327,11 @@ def logout():
return redirect(url_for("stats"))
from prometheus_client import multiprocess
from prometheus_client import generate_latest, CollectorRegistry, CONTENT_TYPE_LATEST
@app.route("/varz")
def metrics():
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)
data = generate_latest(registry)
return Response(data, mimetype=CONTENT_TYPE_LATEST)