4 Commits

Author SHA1 Message Date
d91cd9a3da a&a 2021-01-27 14:13:49 +01:00
e0f57e9de6 security stuff 2021-01-27 11:58:47 +01:00
4731b64780 jwt variables 2021-01-24 23:59:59 +01:00
e362b1484f seems to work 2021-01-24 23:53:28 +01:00
15 changed files with 118 additions and 152 deletions

6
.gitignore vendored
View File

@ -1,6 +1,2 @@
__pycache__/ __pycache__/
ENV ENV
*~
~*
.*~

View File

@ -1,37 +0,0 @@
stages:
- check
- dockerize
variables:
IMAGE_NAME: $CI_REGISTRY/$CI_PROJECT_PATH
check:
image: registry.hottis.de/dockerized/base-build-env:latest
stage: check
tags:
- hottis
- linux
- docker
rules:
- if: $CI_COMMIT_TAG
script:
- checksemver.py -v
--versionToValidate "$CI_COMMIT_TAG"
--validateMessage
--messageToValidate "$CI_COMMIT_MESSAGE"
dockerize:
image: registry.hottis.de/dockerized/docker-bash:latest
stage: dockerize
tags:
- hottis
- linux
- docker
rules:
- if: $CI_COMMIT_TAG
script:
- docker build --tag $IMAGE_NAME:latest --tag $IMAGE_NAME:$CI_COMMIT_TAG .
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker push $IMAGE_NAME:latest
- docker push $IMAGE_NAME:$CI_COMMIT_TAG

View File

@ -10,6 +10,10 @@ ENV DB_HOST="172.16.10.18"
ENV DB_NAME="hausverwaltung" ENV DB_NAME="hausverwaltung"
ENV DB_USER="hausverwaltung-ui" ENV DB_USER="hausverwaltung-ui"
ENV DB_PASS="test123" ENV DB_PASS="test123"
ENV JWT_ISSUER='de.hottis.hausverwaltung'
ENV JWT_SECRET='streng_geheim'
ENV JWT_LIFETIME_SECONDS=60
ENV JWT_ALGORITHM='HS256'
RUN \ RUN \

View File

@ -5,3 +5,4 @@ export DB_USER="hausverwaltung-ui"
export DB_PASS="test123" export DB_PASS="test123"
export DB_NAME="hausverwaltung" export DB_NAME="hausverwaltung"
export JWT_SECRET='streng_geheim'

View File

@ -1,6 +1,8 @@
from dbpool import getConnection, getMany, getOne from dbpool import getConnection, getMany, getOne
from auth import check_scope
def get_mieters(): def get_mieters(token_info):
check_scope(token_info, [ "mieter/read", "wohnung/read", "objekt/read" ])
return getMany(""" return getMany("""
SELECT m.id as id, SELECT m.id as id,
o.id as objekt, o.id as objekt,
@ -21,30 +23,8 @@ SELECT m.id as id,
w.id = m.wohnung w.id = m.wohnung
""", [], "Mieter") """, [], "Mieter")
def get_mieters_active(): def get_mieter(id, token_info):
return getMany(""" check_scope(token_info, [ "mieter/read", "wohnung/read", "objekt/read" ])
SELECT m.id as id,
o.id as objekt,
w.id as wohnung,
w.shortname as wohnung_shortname,
o.shortname as objekt_shortname,
COALESCE(m.anrede, '-') as anrede,
COALESCE(m.vorname, '-') as vorname,
m.nachname as nachname,
COALESCE(m.strasse, '-') as strasse,
COALESCE(m.plz, '-') as plz,
COALESCE(m.ort, '-') as ort,
COALESCE(m.telefon, '-') as telefon,
m.einzug as einzug,
COALESCE(m.auszug, '-') as auszug
FROM wohnung w, objekt o, mieter m
WHERE o.id = w.objekt AND
w.id = m.wohnung AND
m.einzug <= curdate() and
(m.auszug is null or m.auszug > curdate())
""", [], "Mieter")
def get_mieter(id=None):
return getOne(""" return getOne("""
SELECT m.id as id, SELECT m.id as id,
o.id as objekt, o.id as objekt,

View File

@ -1,8 +1,11 @@
from dbpool import getConnection, getMany, getOne from dbpool import getConnection, getMany, getOne
from auth import check_scope
def get_objekte(): def get_objekte(token_info):
check_scope(token_info, "objekt/read")
return getMany("SELECT id, shortname, flaeche FROM objekt", [], "Objekt") return getMany("SELECT id, shortname, flaeche FROM objekt", [], "Objekt")
def get_objekt(id=None): def get_objekt(id, token_info):
check_scope(token_info, "objekt/read")
return getOne("SELECT id, shortname, flaeche FROM objekt WHERE id = ?", return getOne("SELECT id, shortname, flaeche FROM objekt WHERE id = ?",
(id,), "Objekt") (id,), "Objekt")

View File

@ -1,6 +1,9 @@
from dbpool import getConnection, getOne, getMany from dbpool import getConnection, getOne, getMany
from auth import check_scope
def get_wohnungen():
def get_wohnungen(token_info):
check_scope(token_info, "wohnung/read")
return getMany(""" return getMany("""
SELECT w.id as id, SELECT w.id as id,
w.objekt as objekt, w.objekt as objekt,
@ -11,7 +14,8 @@ SELECT w.id as id,
WHERE o.id = w.objekt WHERE o.id = w.objekt
""", [], "Wohnung") """, [], "Wohnung")
def get_wohnung(id=None): def get_wohnung(id, token_info):
check_scope(token_info, [ "wohnung/read", "objekt/read" ])
return getOne(""" return getOne("""
SELECT w.id as id, SELECT w.id as id,
w.objekt as objekt, w.objekt as objekt,
@ -23,7 +27,8 @@ SELECT w.id as id,
w.id = ? w.id = ?
""", (id, ), "Wohnung") """, (id, ), "Wohnung")
def get_wohnungen_by_objekt(id): def get_wohnungen_by_objekt(id, token_info):
check_scope(token_info, [ "wohnung/read", "objekt/read" ])
return getMany(""" return getMany("""
SELECT w.id as id, SELECT w.id as id,
w.objekt as objekt, w.objekt as objekt,

View File

@ -1,9 +1,11 @@
from dbpool import getConnection, getOne, getMany, putOne, call from dbpool import getConnection, getOne, getMany, putOne
import datetime import datetime
import decimal import decimal
import dateparser import dateparser
from auth import check_scope
def get_zahlungen_by_mieter(mieter_id): def get_zahlungen_by_mieter(mieter_id, token_info):
check_scope(token_info, "zahlung/read")
return getMany(""" return getMany("""
SELECT id, SELECT id,
mieter, mieter,
@ -28,7 +30,8 @@ SELECT id,
""", [ id ], "Zahlung") """, [ id ], "Zahlung")
def get_forderungen_by_mieter(mieter_id): def get_forderungen_by_mieter(mieter_id, token_info):
check_scope(token_info, "forderung/read")
return getMany(""" return getMany("""
SELECT id, SELECT id,
mieter, mieter,
@ -40,7 +43,8 @@ SELECT id,
WHERE mieter = ? WHERE mieter = ?
""", [ mieter_id ], "Forderung") """, [ mieter_id ], "Forderung")
def get_forderung(id): def get_forderung(id, token_info):
check_scope(token_info, "forderung/read")
return getOne(""" return getOne("""
SELECT id, SELECT id,
mieter, mieter,
@ -52,7 +56,8 @@ SELECT id,
WHERE id = ? WHERE id = ?
""", [ id ], "Forderung") """, [ id ], "Forderung")
def get_zahlungforderung_by_mieter_and_year(mieter_id, year): def get_zahlungforderung_by_mieter_and_year(mieter_id, year, token_info):
check_scope(token_info, [ "forderung/read", "zahlung/read", "mieter/read" ])
if year == 0: if year == 0:
year = datetime.datetime.now().year year = datetime.datetime.now().year
start_date = "{}-01-01".format(year) start_date = "{}-01-01".format(year)
@ -72,7 +77,8 @@ WHERE mieter = ? AND
datum_soll BETWEEN ? AND ? datum_soll BETWEEN ? AND ?
""", [mieter_id, start_date, end_date], "ZahlungForderung") """, [mieter_id, start_date, end_date], "ZahlungForderung")
def get_saldo_by_mieter_and_year(mieter_id, year): def get_saldo_by_mieter_and_year(mieter_id, year, token_info):
check_scope(token_info, [ "forderung/read", "zahlung/read", "mieter/read" ])
if year == 0: if year == 0:
year = datetime.datetime.now().year year = datetime.datetime.now().year
start_date = "{}-01-01".format(year) start_date = "{}-01-01".format(year)
@ -106,22 +112,12 @@ WHERE mieter = ? AND
"zahlungen": float(sumZ) "zahlungen": float(sumZ)
} }
def put_zahlung(**args): def put_zahlung(zahlung, token_info):
try: check_scope(token_info, "zahlung/write")
body = args["body"] print("Input of put_zahlung: {} {}".format(type(zahlung), zahlung))
datum_soll_raw = body["datum_soll"] datum_soll = dateparser.parse(zahlung["datum_soll"], languages=["de"])
datum_ist_raw = body["datum_ist"] datum_ist = dateparser.parse(zahlung["datum_ist"], languages=["de"])
return putOne("""
print("Input of put_zahlung: {}".format(body))
datum_soll = dateparser.parse(datum_soll_raw, languages=["de"])
datum_ist = dateparser.parse(datum_ist_raw, languages=["de"])
return putOne("""
INSERT INTO zahlung (datum_soll, datum_ist, mieter, betrag, kommentar) INSERT INTO zahlung (datum_soll, datum_ist, mieter, betrag, kommentar)
VALUES(?, ?, ?, ?, ?) VALUES(?, ?, ?, ?, ?)
""", [ datum_soll, datum_ist, body["mieter"], body["betrag"], body["kommentar"] ], "Zahlung") """, [ datum_soll, datum_ist, zahlung["mieter"], zahlung["betrag"], zahlung["kommentar"] ], "Zahlung")
except KeyError as e:
print("Some parameter missing: {}".format(e))
return str(e), 500
def insertAllForMonth():
return call("insert_monatl_miet_forderung")

44
auth.py Executable file
View File

@ -0,0 +1,44 @@
import time
import connexion
from werkzeug.exceptions import Unauthorized, Forbidden
from jose import JWTError, jwt
import os
JWT_SECRET = os.environ['JWT_SECRET']
def decode_token(token):
try:
print("DEBUG decode_token: try to decode")
token_info = jwt.decode(token, JWT_SECRET)
print("DEBUG decode_token: token_info: {}".format(token_info))
return token_info
except JWTError as e:
print("ERROR decode_token: error when decoding token: {}".format(e))
raise Unauthorized()
def check_token(token, key, value):
if (key in token) and ((token[key] == value) or (isinstance(token[key], list) and (value in token[key]))):
return True
print("WARN: check_token: {} -> {} required but not granted".format(key, value))
raise Forbidden()
def check_scope(token, value):
if isinstance(value, list):
for v in value:
check_token(token, "x-scope", v)
else:
check_token(token, "x-scope", value)
def get_secret(user, token_info) -> str:
return '''
You are user_id {user} and the secret is 'wbevuec'.
Decoded token claims: {token_info}.
'''.format(user=user, token_info=token_info)
def _current_timestamp() -> int:
return int(time.time())

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
IMAGE_NAME="registry.hottis.de/hv/hv-service" IMAGE_NAME="registry.hottis.de/hv/hv-service"
VERSION=0.0.4 VERSION=0.0.3
docker build -t ${IMAGE_NAME}:${VERSION} . docker build -t ${IMAGE_NAME}:${VERSION} .
docker push ${IMAGE_NAME}:${VERSION} docker push ${IMAGE_NAME}:${VERSION}

View File

@ -83,7 +83,6 @@ def putOne(stmt, params, objName):
except mariadb.Error as err: except mariadb.Error as err:
dbh.rollback() dbh.rollback()
print("Database error in putOne({}): {}".format(objName, err)) print("Database error in putOne({}): {}".format(objName, err))
return str(err), 500
except Exception as err: except Exception as err:
dbh.rollback() dbh.rollback()
print("Error in putOne({}): {}".format(objName, err)) print("Error in putOne({}): {}".format(objName, err))
@ -94,27 +93,4 @@ def putOne(stmt, params, objName):
cur.close() cur.close()
if dbh: if dbh:
dbh.close() dbh.close()
def call(procName):
dbh = None
cur = None
try:
dbh = getConnection()
cur = dbh.cursor(dictionary=True)
cur.execute("CALL {}(null)".format(procName))
dbh.commit()
return "{} successfully called".format(procName), 202
except mariadb.Error as err:
dbh.rollback()
print("Database error in call {}: {}".format(procName, err))
return str(err), 500
except Exception as err:
dbh.rollback()
print("Some error in call {}: {}".format(procName, err))
return str(err), 500
finally:
print("return connection in call {}".format(procName))
if cur:
cur.close()
if dbh:
dbh.close()

View File

@ -3,6 +3,9 @@ info:
title: Hausverwaltung title: Hausverwaltung
version: "0.1" version: "0.1"
security:
- jwt: []
paths: paths:
/hv/objekte: /hv/objekte:
get: get:
@ -126,24 +129,9 @@ paths:
description: No Mieter available description: No Mieter available
500: 500:
description: Some server error description: Some server error
/hv/mieters/active: security:
get: - jwt: ['secret']
tags: [ "Mieter" ]
operationId: Mieter.get_mieters_active
summary: Returns all currently active Mieters
responses:
200:
description: Successful response.
content:
'application/json':
schema:
type: array
items:
$ref: '#/components/schemas/Mieter'
404:
description: No Mieter available
500:
description: Some server error
/hv/mieter/{id}: /hv/mieter/{id}:
get: get:
tags: [ "Mieter" ] tags: [ "Mieter" ]
@ -320,9 +308,8 @@ paths:
operationId: ZahlungenForderungen.put_zahlung operationId: ZahlungenForderungen.put_zahlung
summary: Inserts a new Zahlung summary: Inserts a new Zahlung
requestBody: requestBody:
description: Zahlung
content: content:
application/json: 'application/json':
schema: schema:
$ref: '#/components/schemas/Zahlung' $ref: '#/components/schemas/Zahlung'
responses: responses:
@ -330,20 +317,28 @@ paths:
description: Zahlung successfully inserted description: Zahlung successfully inserted
500: 500:
description: Some server or database error description: Some server or database error
/hv/forderung/insertAllForMonth: /secret:
post: get:
tags: [ "Forderung" ] tags: [ "JWT" ]
operationId: ZahlungenForderungen.insertAllForMonth summary: Return secret string
summary: Insert the Forderungen for the insertAllForMonth operationId: auth.get_secret
responses: responses:
202: '200':
description: Forderungen successfully inserted description: secret response
500: content:
description: Some server or database error 'text/plain':
schema:
type: string
components: components:
securitySchemes:
jwt:
type: http
scheme: bearer
bearerFormat: JWT
x-bearerInfoFunc: auth.decode_token
schemas: schemas:
Objekt: Objekt:
description: Objekt type description: Objekt type

4
run.sh
View File

@ -15,4 +15,8 @@ docker run \
-e DB_USER=$DB_USER \ -e DB_USER=$DB_USER \
-e DB_PASS=$DB_PASS \ -e DB_PASS=$DB_PASS \
-e DB_NAME=$DB_NAME \ -e DB_NAME=$DB_NAME \
-e JWT_ISSUER=$JWT_ISSUER \
-e JWT_SECRET=$JWT_SECRET \
-e JWT_LIFETIME_SECONDS=$JWT_LIFETIME_SECONDS \
-e JWT_ALGORITHM=$JWT_ALGORITHM
${IMAGE_NAME}:${VERSION} ${IMAGE_NAME}:${VERSION}

View File

@ -4,4 +4,3 @@ wsgi-file = server.py
processes = 4 processes = 4
stats = :9191 stats = :9191

View File

@ -3,7 +3,7 @@ from flask_cors import CORS
# instantiate the webservice # instantiate the webservice
app = connexion.App(__name__) app = connexion.App(__name__)
app.add_api('swagger.yaml') app.add_api('openapi.yaml')
# CORSify it - otherwise Angular won't accept it # CORSify it - otherwise Angular won't accept it
CORS(app.app) CORS(app.app)