5 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
4f4b959e9e successfully moved to openapi 3.0.0 2021-01-24 23:24:20 +01:00
10 changed files with 290 additions and 240 deletions

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 \
@ -20,9 +24,7 @@ RUN \
pip3 install connexion && \ pip3 install connexion && \
pip3 install connexion[swagger-ui] && \ pip3 install connexion[swagger-ui] && \
pip3 install uwsgi && \ pip3 install uwsgi && \
pip3 install flask-cors && \ pip3 install flask-cors
pip3 install python-jose[cryptography] && \
pip3 install six
RUN \ RUN \
mkdir -p ${APP_DIR} && \ mkdir -p ${APP_DIR} && \

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,7 +23,8 @@ SELECT m.id as id,
w.id = m.wohnung w.id = m.wohnung
""", [], "Mieter") """, [], "Mieter")
def get_mieter(id=None): def get_mieter(id, token_info):
check_scope(token_info, [ "mieter/read", "wohnung/read", "objekt/read" ])
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

@ -2,8 +2,10 @@ 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,7 +112,8 @@ WHERE mieter = ? AND
"zahlungen": float(sumZ) "zahlungen": float(sumZ)
} }
def put_zahlung(zahlung): def put_zahlung(zahlung, token_info):
check_scope(token_info, "zahlung/write")
print("Input of put_zahlung: {} {}".format(type(zahlung), zahlung)) print("Input of put_zahlung: {} {}".format(type(zahlung), zahlung))
datum_soll = dateparser.parse(zahlung["datum_soll"], languages=["de"]) datum_soll = dateparser.parse(zahlung["datum_soll"], languages=["de"])
datum_ist = dateparser.parse(zahlung["datum_ist"], languages=["de"]) datum_ist = dateparser.parse(zahlung["datum_ist"], languages=["de"])

44
auth.py Normal file → Executable file
View File

@ -1,36 +1,35 @@
import time import time
import connexion import connexion
import six from werkzeug.exceptions import Unauthorized, Forbidden
from werkzeug.exceptions import Unauthorized
from jose import JWTError, jwt from jose import JWTError, jwt
import os
JWT_ISSUER = 'de.hottis.hausverwaltung' JWT_SECRET = os.environ['JWT_SECRET']
JWT_SECRET = 'streng_geheim'
JWT_LIFETIME_SECONDS = 600
JWT_ALGORITHM = 'HS256'
def generate_token(user_id):
timestamp = _current_timestamp()
payload = {
"iss": JWT_ISSUER,
"iat": int(timestamp),
"exp": int(timestamp + JWT_LIFETIME_SECONDS),
"sub": str(user_id),
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def decode_token(token): def decode_token(token):
try: try:
return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM]) 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: except JWTError as e:
six.raise_from(Unauthorized, 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: def get_secret(user, token_info) -> str:
return ''' return '''
@ -39,6 +38,7 @@ def get_secret(user, token_info) -> str:
'''.format(user=user, token_info=token_info) '''.format(user=user, token_info=token_info)
def _current_timestamp() -> int: def _current_timestamp() -> int:
return int(time.time()) return int(time.time())

View File

@ -1,7 +1,10 @@
openapi: 3.0.0 openapi: 3.0.0
info: info:
title: Hausverwaltung-JWT title: Hausverwaltung
version: "0.2" version: "0.1"
security:
- jwt: []
paths: paths:
/hv/objekte: /hv/objekte:
@ -12,10 +15,12 @@ paths:
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/Objekt' type: array
items:
$ref: '#/components/schemas/Objekt'
404: 404:
description: No Objekte available description: No Objekte available
500: 500:
@ -28,13 +33,16 @@ paths:
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
$ref: '#/components/Objekt' 'application/json':
schema:
$ref: '#/components/schemas/Objekt'
404: 404:
description: Objekt not found description: Objekt not found
500: 500:
@ -47,10 +55,12 @@ paths:
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/Wohnung' type: array
items:
$ref: '#/components/schemas/Wohnung'
404: 404:
description: No Wohnung available description: No Wohnung available
500: 500:
@ -63,15 +73,18 @@ paths:
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/Wohnung' type: array
items:
$ref: '#/components/schemas/Wohnung'
404: 404:
description: No Wohnung available description: No Wohnung available
500: 500:
@ -84,13 +97,16 @@ paths:
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
$ref: '#/components/Wohnung' 'application/json':
schema:
$ref: '#/components/schemas/Wohnung'
404: 404:
description: Wohnung not found description: Wohnung not found
500: 500:
@ -103,14 +119,19 @@ paths:
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/Mieter' type: array
items:
$ref: '#/components/schemas/Mieter'
404: 404:
description: No Mieter available description: No Mieter available
500: 500:
description: Some server error description: Some server error
security:
- jwt: ['secret']
/hv/mieter/{id}: /hv/mieter/{id}:
get: get:
tags: [ "Mieter" ] tags: [ "Mieter" ]
@ -119,13 +140,16 @@ paths:
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
$ref: '#/components/Mieter' 'application/json':
schema:
$ref: '#/components/schemas/Mieter'
404: 404:
description: Mieter not found description: Mieter not found
500: 500:
@ -138,13 +162,16 @@ paths:
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
$ref: '#/components/Forderung' 'application/json':
schema:
$ref: '#/components/schemas/Forderung'
404: 404:
description: Forderung not found description: Forderung not found
500: 500:
@ -157,15 +184,18 @@ paths:
parameters: parameters:
- name: mieter_id - name: mieter_id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/Forderung' type: array
items:
$ref: '#/components/schemas/Forderung'
404: 404:
description: No Forderung available description: No Forderung available
500: 500:
@ -178,13 +208,16 @@ paths:
parameters: parameters:
- name: id - name: id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
$ref: '#/components/Zahlung' 'application/json':
schema:
$ref: '#/components/schemas/Zahlung'
404: 404:
description: Zahlung not found description: Zahlung not found
500: 500:
@ -197,15 +230,18 @@ paths:
parameters: parameters:
- name: mieter_id - name: mieter_id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response. description: Successful response.
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/Zahlung' type: array
items:
$ref: '#/components/schemas/Zahlung'
404: 404:
description: No Zahlung available description: No Zahlung available
500: 500:
@ -218,19 +254,23 @@ paths:
parameters: parameters:
- name: mieter_id - name: mieter_id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
- name: year - name: year
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response description: Successful response
schema: content:
type: array 'application/json':
items: schema:
$ref: '#/components/ZahlungForderung' type: array
items:
$ref: '#/components/schemas/ZahlungForderung'
404: 404:
description: No ZahlungForderung available description: No ZahlungForderung available
500: 500:
@ -243,17 +283,21 @@ paths:
parameters: parameters:
- name: mieter_id - name: mieter_id
in: path in: path
type: integer
required: true required: true
schema:
type: integer
- name: year - name: year
in: path in: path
type: integer
required: true required: true
schema:
type: integer
responses: responses:
200: 200:
description: Successful response description: Successful response
schema: content:
$ref: '#/components/Saldo' 'application/json':
schema:
$ref: '#/components/schemas/Saldo'
404: 404:
description: Neither Forderungen nor Zahlungen available description: Neither Forderungen nor Zahlungen available
500: 500:
@ -263,39 +307,19 @@ paths:
tags: [ "Zahlung" ] tags: [ "Zahlung" ]
operationId: ZahlungenForderungen.put_zahlung operationId: ZahlungenForderungen.put_zahlung
summary: Inserts a new Zahlung summary: Inserts a new Zahlung
parameters: requestBody:
- name: zahlung content:
in: body 'application/json':
schema: schema:
$ref: '#/components/Zahlung' $ref: '#/components/schemas/Zahlung'
responses: responses:
202: 202:
description: Zahlung successfully inserted description: Zahlung successfully inserted
500: 500:
description: Some server or database error description: Some server or database error
/auth/{user_id}:
get:
tags: [ "jwt" ]
summary: Return JWT token
operationId: auth.generate_token
parameters:
- name: user_id
description: User unique identifier
in: path
required: true
example: 12
schema:
type: integer
responses:
'200':
description: JWT token
content:
'text/plain':
schema:
type: string
/secret: /secret:
get: get:
tags: [ "jwt" ] tags: [ "JWT" ]
summary: Return secret string summary: Return secret string
operationId: auth.get_secret operationId: auth.get_secret
responses: responses:
@ -305,131 +329,132 @@ paths:
'text/plain': 'text/plain':
schema: schema:
type: string type: string
security:
- jwt: ['secret']
components: components:
Objekt:
description: Objekt type
type: object
properties:
id:
type: integer
shortname:
type: string
flaeche:
type: number
Wohnung:
description: Wohnung type
type: object
properties:
id:
type: integer
objekt:
type: integer
shortname:
type: string
flaeche:
type: number
objekt_shortname:
type: string
Mieter:
description: Mieter type
type: object
properties:
id:
type: integer
objekt:
type: integer
wohnung:
type: integer
wohnung_shortname:
type: string
objekt_shortname:
type: string
anrede:
type: string
vorname:
type: string
nachname:
type: string
strasse:
type: string
plz:
type: string
ort:
type: string
telefon:
type: string
einzug:
type: string
auszug:
type: string
Forderung:
description: Forderung type
type: object
properties:
id:
type: integer
mieter:
type: integer
datum:
type: string
betrag:
type: number
kommentar:
type: string
ref_wohnung:
type: number
Zahlung:
description: Zahlung type
type: object
properties:
id:
type: integer
mieter:
type: integer
datum_ist:
type: string
datum_soll:
type: string
betrag:
type: number
kommentar:
type: string
ZahlungForderung:
description: ZahlungForderung type
type: object
properties:
zf_type:
type: string
id:
type: integer
datum_soll:
type: string
datum_ist:
type: string
betrag_zahlung:
type: number
betrag_forderung:
type: number
kommentar:
type: string
mieter:
type: number
Saldo:
description: Saldo type
type: object
properties:
forderungen:
type: number
zahlungen:
type: number
saldo:
type: number
securitySchemes: securitySchemes:
jwt: jwt:
type: http type: http
scheme: bearer scheme: bearer
bearerFormat: JWT bearerFormat: JWT
x-bearerInfoFunc: auth.decode_token x-bearerInfoFunc: auth.decode_token
schemas:
Objekt:
description: Objekt type
type: object
properties:
id:
type: integer
shortname:
type: string
flaeche:
type: number
Wohnung:
description: Wohnung type
type: object
properties:
id:
type: integer
objekt:
type: integer
shortname:
type: string
flaeche:
type: number
objekt_shortname:
type: string
Mieter:
description: Mieter type
type: object
properties:
id:
type: integer
objekt:
type: integer
wohnung:
type: integer
wohnung_shortname:
type: string
objekt_shortname:
type: string
anrede:
type: string
vorname:
type: string
nachname:
type: string
strasse:
type: string
plz:
type: string
ort:
type: string
telefon:
type: string
einzug:
type: string
auszug:
type: string
Forderung:
description: Forderung type
type: object
properties:
id:
type: integer
mieter:
type: integer
datum:
type: string
betrag:
type: number
kommentar:
type: string
ref_wohnung:
type: number
Zahlung:
description: Zahlung type
type: object
properties:
id:
type: integer
mieter:
type: integer
datum_ist:
type: string
datum_soll:
type: string
betrag:
type: number
kommentar:
type: string
ZahlungForderung:
description: ZahlungForderung type
type: object
properties:
zf_type:
type: string
id:
type: integer
datum_soll:
type: string
datum_ist:
type: string
betrag_zahlung:
type: number
betrag_forderung:
type: number
kommentar:
type: string
mieter:
type: number
Saldo:
description: Saldo type
type: object
properties:
forderungen:
type: number
zahlungen:
type: number
saldo:
type: number

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

@ -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)