2021-01-25 21:52:52 +01:00
|
|
|
import time
|
|
|
|
import connexion
|
2021-06-15 23:13:18 +02:00
|
|
|
from jose import JWTError, jwt, jwe
|
|
|
|
import json
|
2021-01-26 13:43:09 +01:00
|
|
|
import werkzeug
|
2021-01-25 21:52:52 +01:00
|
|
|
import os
|
2021-05-11 15:16:13 +02:00
|
|
|
import psycopg2
|
2021-01-26 13:43:09 +01:00
|
|
|
from collections import namedtuple
|
2021-01-26 21:27:17 +01:00
|
|
|
from pbkdf2 import crypt
|
2021-05-11 16:48:02 +02:00
|
|
|
from loguru import logger
|
2021-01-25 21:52:52 +01:00
|
|
|
|
|
|
|
DB_USER = os.environ["DB_USER"]
|
|
|
|
DB_PASS = os.environ["DB_PASS"]
|
|
|
|
DB_HOST = os.environ["DB_HOST"]
|
|
|
|
DB_NAME = os.environ["DB_NAME"]
|
|
|
|
|
2021-01-27 10:57:54 +01:00
|
|
|
JWT_ISSUER = os.environ["JWT_ISSUER"]
|
2021-05-06 16:37:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
2021-01-27 10:57:54 +01:00
|
|
|
|
2021-01-25 21:52:52 +01:00
|
|
|
|
2021-01-26 21:27:17 +01:00
|
|
|
class NoUserException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class ManyUsersException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class PasswordMismatchException(Exception):
|
|
|
|
pass
|
|
|
|
|
2021-01-26 13:43:09 +01:00
|
|
|
|
2021-05-11 16:48:02 +02:00
|
|
|
UserEntry = namedtuple('UserEntry', ['id', 'login', 'pwhash', 'expiry', 'claims'])
|
2021-05-07 13:28:12 +02:00
|
|
|
|
2021-05-06 16:37:32 +02:00
|
|
|
JWT_PRIV_KEY = ""
|
2021-05-07 13:28:12 +02:00
|
|
|
try:
|
|
|
|
JWT_PRIV_KEY = os.environ["JWT_PRIV_KEY"]
|
|
|
|
except KeyError:
|
|
|
|
with open('/opt/app/config/authservice.key', 'r') as f:
|
|
|
|
JWT_PRIV_KEY = f.read()
|
2021-05-06 16:37:32 +02:00
|
|
|
|
|
|
|
JWT_PUB_KEY = ""
|
2021-05-07 13:28:12 +02:00
|
|
|
try:
|
|
|
|
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
|
|
|
|
except KeyError:
|
|
|
|
with open('/opt/app/config/authservice.pub', 'r') as f:
|
|
|
|
JWT_PUB_KEY = f.read()
|
2021-01-26 13:43:09 +01:00
|
|
|
|
|
|
|
|
2021-01-26 22:06:39 +01:00
|
|
|
def getUserEntryFromDB(application: str, login: str):
|
2021-01-25 21:52:52 +01:00
|
|
|
conn = None
|
|
|
|
cur = None
|
|
|
|
try:
|
2021-05-11 15:16:13 +02:00
|
|
|
conn = psycopg2.connect(user = DB_USER, password = DB_PASS,
|
|
|
|
host = DB_HOST, database = DB_NAME)
|
2021-01-25 21:52:52 +01:00
|
|
|
conn.autocommit = False
|
|
|
|
|
2021-05-11 16:48:02 +02:00
|
|
|
userObj = None
|
|
|
|
with conn.cursor() as cur:
|
|
|
|
cur.execute("SELECT id, pwhash, expiry FROM user_application_v" +
|
|
|
|
" WHERE application = %s AND login = %s",
|
|
|
|
(application, login))
|
|
|
|
userObj = cur.fetchone()
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.debug("userObj: {}".format(userObj))
|
2021-05-11 16:48:02 +02:00
|
|
|
if not userObj:
|
|
|
|
raise NoUserException()
|
|
|
|
invObj = cur.fetchone()
|
|
|
|
if invObj:
|
|
|
|
raise ManyUsersException()
|
|
|
|
|
2021-01-26 13:43:09 +01:00
|
|
|
claims = {}
|
2021-05-11 16:48:02 +02:00
|
|
|
with conn.cursor() as cur:
|
|
|
|
cur.execute('SELECT key, value FROM claims_for_user_v where "user" = %s and application = %s',
|
|
|
|
(userObj[0], application))
|
|
|
|
for claimObj in cur:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.debug("add claim {} -> {}".format(claimObj[0], claimObj[1]))
|
2021-05-11 16:48:02 +02:00
|
|
|
if claimObj[0] in claims:
|
|
|
|
if isinstance(claims[claimObj[0]], list):
|
|
|
|
claims[claimObj[0]].append(claimObj[1])
|
|
|
|
else:
|
|
|
|
claims[claimObj[0]] = [ claims[claimObj[0]] ]
|
|
|
|
claims[claimObj[0]].append(claimObj[1])
|
2021-01-26 13:43:09 +01:00
|
|
|
else:
|
2021-05-11 16:48:02 +02:00
|
|
|
claims[claimObj[0]] = claimObj[1]
|
2021-01-26 13:43:09 +01:00
|
|
|
|
2021-05-11 16:48:02 +02:00
|
|
|
userEntry = UserEntry(id=userObj[0], login=login, pwhash=userObj[1], expiry=userObj[2], claims=claims)
|
2021-01-25 21:52:52 +01:00
|
|
|
|
2021-05-11 16:48:02 +02:00
|
|
|
return userEntry
|
|
|
|
except psycopg2.Error as err:
|
2021-01-25 21:52:52 +01:00
|
|
|
raise Exception("Error when connecting to database: {}".format(err))
|
|
|
|
finally:
|
|
|
|
if conn:
|
|
|
|
conn.close()
|
|
|
|
|
2021-01-26 22:06:39 +01:00
|
|
|
def getUserEntry(application, login, password):
|
2021-05-11 16:48:02 +02:00
|
|
|
userEntry = getUserEntryFromDB(application, login)
|
|
|
|
if userEntry.pwhash != crypt(password, userEntry.pwhash):
|
2021-01-26 21:27:17 +01:00
|
|
|
raise PasswordMismatchException()
|
2021-01-26 22:06:39 +01:00
|
|
|
return userEntry
|
2021-01-25 21:52:52 +01:00
|
|
|
|
2021-01-26 13:43:09 +01:00
|
|
|
def generateToken(**args):
|
2021-01-25 21:52:52 +01:00
|
|
|
try:
|
2021-01-26 13:43:09 +01:00
|
|
|
body = args["body"]
|
2021-06-15 23:13:18 +02:00
|
|
|
|
|
|
|
application = ""
|
|
|
|
login = ""
|
|
|
|
password = ""
|
|
|
|
if (("application" in body) and
|
|
|
|
("login" in body) and
|
|
|
|
("password" in body)):
|
|
|
|
application = body["application"]
|
|
|
|
login = body["login"]
|
|
|
|
password = body["password"]
|
|
|
|
elif ("encAleTuple" in body):
|
|
|
|
clearContent = jwe.decrypt(body["encAleTuple"], JWT_PRIV_KEY)
|
|
|
|
clearObj = json.loads(clearContent)
|
|
|
|
application = clearObj["application"]
|
|
|
|
login = clearObj["login"]
|
|
|
|
password = clearObj["password"]
|
|
|
|
else:
|
|
|
|
raise KeyError("Neither application, login and password nor encAleTuple given")
|
|
|
|
|
|
|
|
logger.debug(f"Tuple: {application} {login} {password}")
|
2021-01-26 21:27:17 +01:00
|
|
|
|
2021-01-26 22:06:39 +01:00
|
|
|
userEntry = getUserEntry(application, login, password)
|
2021-01-26 21:27:17 +01:00
|
|
|
|
2021-01-26 13:43:09 +01:00
|
|
|
timestamp = int(time.time())
|
|
|
|
payload = {
|
2021-01-27 10:57:54 +01:00
|
|
|
"iss": JWT_ISSUER,
|
2021-01-26 13:43:09 +01:00
|
|
|
"iat": int(timestamp),
|
|
|
|
"exp": int(timestamp + userEntry.expiry),
|
2021-05-07 14:40:40 +02:00
|
|
|
"sub": str(userEntry.id),
|
|
|
|
"aud": application
|
2021-01-26 13:43:09 +01:00
|
|
|
}
|
2021-05-11 16:48:02 +02:00
|
|
|
logger.debug("claims: {}".format(userEntry.claims))
|
2021-01-26 13:43:09 +01:00
|
|
|
for claim in userEntry.claims.items():
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.debug("add claim {}".format(claim))
|
2021-01-27 13:31:34 +01:00
|
|
|
payload[claim[0]] = claim[1]
|
2021-01-26 13:43:09 +01:00
|
|
|
|
2021-05-06 16:37:32 +02:00
|
|
|
return jwt.encode(payload, JWT_PRIV_KEY, algorithm='RS256')
|
2021-01-26 13:43:09 +01:00
|
|
|
except NoUserException:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.error("no user found, login or application wrong")
|
2021-01-26 13:43:09 +01:00
|
|
|
raise werkzeug.exceptions.Unauthorized()
|
|
|
|
except ManyUsersException:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.error("too many users found")
|
2021-01-26 13:43:09 +01:00
|
|
|
raise werkzeug.exceptions.Unauthorized()
|
2021-01-26 21:27:17 +01:00
|
|
|
except PasswordMismatchException:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.error("wrong password")
|
2021-01-26 21:27:17 +01:00
|
|
|
raise werkzeug.exceptions.Unauthorized()
|
2021-01-26 13:43:09 +01:00
|
|
|
except KeyError:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.error("application, login or password missing")
|
2021-01-26 13:43:09 +01:00
|
|
|
raise werkzeug.exceptions.Unauthorized()
|
|
|
|
except Exception as e:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.error("unspecific exception: {}".format(str(e)))
|
2021-01-26 13:43:09 +01:00
|
|
|
raise werkzeug.exceptions.Unauthorized()
|
2021-05-06 16:37:32 +02:00
|
|
|
|
|
|
|
def getPubKey():
|
|
|
|
return JWT_PUB_KEY
|
2021-05-11 16:48:02 +02:00
|
|
|
|
|
|
|
def decodeToken(token):
|
|
|
|
try:
|
|
|
|
return jwt.decode(token, JWT_PUB_KEY, audience="test")
|
|
|
|
except JWTError as e:
|
2021-06-15 23:13:18 +02:00
|
|
|
logger.error("{}".format(e))
|
2021-05-11 16:48:02 +02:00
|
|
|
raise werkzeug.exceptions.Unauthorized()
|
|
|
|
|
2021-06-15 23:13:18 +02:00
|
|
|
def testToken(user, token_info):
|
2021-05-11 16:48:02 +02:00
|
|
|
return '''
|
2021-06-15 23:13:18 +02:00
|
|
|
You are user_id {user} and the provided token has been signed by this issuers. Fine.'.
|
2021-05-11 16:48:02 +02:00
|
|
|
Decoded token claims: {token_info}.
|
|
|
|
'''.format(user=user, token_info=token_info)
|
|
|
|
|