21 Commits

Author SHA1 Message Date
d521e146cf consider x-forwarded-for 2021-09-12 14:17:43 +02:00
0d698568d4 consider client ip 2021-09-12 13:58:14 +02:00
3541226964 use postgres 2021-09-11 23:32:25 +02:00
216e1c0684 fix 2021-09-06 21:23:54 +02:00
f8afd95a82 fix typo 2021-09-06 21:12:59 +02:00
e29ce48971 refresh token expiry check 2021-09-06 21:06:12 +02:00
7163db9ce9 used instead of valid, token expiry check 2021-09-06 18:19:39 +02:00
629a85fc3e expiry in token table 2021-09-03 19:35:30 +02:00
b2e1ecab2b refreshable tokens 2021-09-03 19:06:04 +02:00
5b1209679b test output 2021-06-17 18:50:05 +02:00
9929b38db7 changed test build script 2021-06-15 23:36:49 +02:00
91178b1fa7 support handover of cleartext and encrypted credentials, refactor naming of methods 2021-06-15 23:13:18 +02:00
1f55ef0a80 jwe finally works 2021-06-15 21:26:36 +02:00
19b8aab8c2 test 2021-05-11 16:52:41 +02:00
e0bc8371ac adjust for postgres 2021-05-11 16:48:02 +02:00
7346da8419 move to psycopg2 2021-05-11 15:16:13 +02:00
fd9b673df9 change to postgres 2021-05-11 14:31:22 +02:00
c7dbaeabbb add application as aud in token 2021-05-07 14:40:40 +02:00
0911a73085 fix 2021-05-07 14:06:18 +02:00
1de73e99e3 message 2021-05-07 14:05:57 +02:00
b44af0658a jwe 2021-05-07 13:28:12 +02:00
9 changed files with 509 additions and 227 deletions

6
.gitignore vendored
View File

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

View File

@ -16,8 +16,9 @@ ENV JWT_SECRET='streng_geheim'
RUN \
apt update && \
apt install -y libmariadbclient-dev && \
pip3 install mariadb && \
apt install -y postgresql-client-common && \
pip3 install psycopg2 && \
pip3 install loguru && \
pip3 install dateparser && \
pip3 install connexion && \
pip3 install connexion[swagger-ui] && \

View File

@ -1,6 +1,6 @@
#!/usr/bin/python
import mariadb
import psycopg2
from pbkdf2 import crypt
import argparse
import os
@ -23,30 +23,26 @@ password = args.password
application = args.application
DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"]
DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
DB_NAME = "authservice"
pwhash = crypt(password, iterations=100000)
conn = None
cur = None
try:
conn = mariadb.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn = psycopg2.connect(database = DB_NAME)
conn.autocommit = False
cur = conn.cursor()
cur.execute("""
INSERT INTO users (login, pwhash)
VALUES(?, ?)
INSERT INTO user_t (login, pwhash)
VALUES(%s, %s)
""", [user, pwhash])
cur.execute("""
INSERT INTO user_applications_mapping (application, user)
INSERT INTO user_application_mapping_t (application,"user")
VALUES(
(SELECT id FROM applications WHERE name = ?),
(SELECT id FROM users WHERE login = ?)
(SELECT id FROM application_t WHERE name = %s),
(SELECT id FROM user_t WHERE login = %s)
)
""", [application, user])
conn.commit()

403
auth.py
View File

@ -1,136 +1,393 @@
import time
import connexion
from jose import JWTError, jwt
from jose import JWTError, jwt, jwe
import json
from jose.exceptions import ExpiredSignatureError
import werkzeug
import os
import mariadb
import psycopg2
from collections import namedtuple
from pbkdf2 import crypt
DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"]
DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
JWT_ISSUER = os.environ["JWT_ISSUER"]
from loguru import logger
import configparser
import random
import string
from flask import request
DB_USER = ""
DB_PASS = ""
DB_HOST = ""
DB_NAME = ""
JWT_ISSUER = ""
try:
DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"]
DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
JWT_ISSUER = os.environ["JWT_ISSUER"]
except KeyError:
config = configparser.ConfigParser()
config.read('./config/config.ini')
DB_USER = config["database"]["user"]
DB_PASS = config["database"]["pass"]
DB_HOST = config["database"]["host"]
DB_NAME = config["database"]["name"]
JWT_ISSUER = config["jwt"]["issuer"]
class NoUserException(Exception):
pass
class RefreshTokenExpiredException(Exception):
pass
class NoTokenException(Exception):
pass
class NoValidTokenException(Exception):
pass
class ManyUsersException(Exception):
pass
class ManyTokensException(Exception):
pass
class PasswordMismatchException(Exception):
pass
class RefreshTokenExpiredException(Exception):
pass
UserEntry = namedtuple('UserEntry', ['id', 'login', 'expiry', 'claims'])
UserEntry = namedtuple('UserEntry', ['id', 'login', 'pwhash', 'expiry', 'claims'])
RefreshTokenEntry = namedtuple('RefreshTokenEntry', ['id', 'salt', 'login', 'app', 'expiry'])
JWT_PRIV_KEY = ""
with open('/opt/app/config/authservice.key', 'r') as f:
JWT_PRIV_KEY = f.read()
try:
JWT_PRIV_KEY = os.environ["JWT_PRIV_KEY"]
except KeyError:
with open('./config/authservice.key', 'r') as f:
JWT_PRIV_KEY = f.read()
JWT_PUB_KEY = ""
with open('/opt/app/config/authservice.pub', 'r') as f:
JWT_PUB_KEY = f.read()
try:
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
except KeyError:
with open('./config/authservice.pub', 'r') as f:
JWT_PUB_KEY = f.read()
def getUserEntryFromDB(application: str, login: str):
conn = None
cur = None
try:
conn = mariadb.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn = psycopg2.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn.autocommit = False
cur = conn.cursor(dictionary=True)
cur.execute("SELECT id, pwhash, expiry FROM user_application" +
" WHERE application = ? AND login = ?",
[application, login])
resObj = cur.next()
print("DEBUG: getUserEntryFromDB: resObj: {}".format(resObj))
if not resObj:
raise NoUserException()
invObj = cur.next()
if invObj:
raise ManyUsersException()
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()
logger.debug("userObj: {}".format(userObj))
if not userObj:
raise NoUserException()
invObj = cur.fetchone()
if invObj:
raise ManyUsersException()
userId = resObj["id"]
cur.execute("SELECT user, `key`, `value` FROM claims_for_user where user = ?",
[userId])
claims = {}
for claimObj in cur:
print("DEBUG: getUserEntryFromDB: add claim {} -> {}".format(claimObj["key"], claimObj["value"]))
if claimObj["key"] in claims:
if isinstance(claims[claimObj["key"]], list):
claims[claimObj["key"]].append(claimObj["value"])
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:
logger.debug("add claim {} -> {}".format(claimObj[0], claimObj[1]))
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])
else:
claims[claimObj["key"]] = [ claims[claimObj["key"]] ]
claims[claimObj["key"]].append(claimObj["value"])
else:
claims[claimObj["key"]] = claimObj["value"]
claims[claimObj[0]] = claimObj[1]
userEntry = UserEntry(id=userId, login=login, expiry=resObj["expiry"], claims=claims)
userEntry = UserEntry(id=userObj[0], login=login, pwhash=userObj[1], expiry=userObj[2], claims=claims)
return userEntry, resObj["pwhash"]
except mariadb.Error as err:
return userEntry
except psycopg2.Error as err:
raise Exception("Error when connecting to database: {}".format(err))
finally:
if conn:
conn.close()
def getRefreshTokenFromDB(application, login, httpClientIp):
conn = None
cur = None
try:
conn = psycopg2.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn.autocommit = False
with conn:
with conn.cursor() as cur:
cur.execute('SELECT u.id, u.expiry, a.name FROM user_t u, application_t a, user_application_mapping_t m' +
' WHERE u.login = %s AND ' +
' a.name = %s AND ' +
' a.id = m.application AND u.id = m."user"',
(login, application))
userObj = cur.fetchone()
logger.debug("userObj: {}".format(userObj))
if not userObj:
raise NoUserException()
invObj = cur.fetchone()
if invObj:
raise ManyUsersException()
with conn.cursor() as cur:
salt = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(64))
cur.execute('INSERT INTO token_t ("user", salt, expiry, client_ip) VALUES (%s, %s, %s, %s) RETURNING id',
(userObj[0], salt, userObj[1], httpClientIp))
tokenObj = cur.fetchone()
logger.debug("tokenObj: {}".format(tokenObj))
if not tokenObj:
raise NoTokenException()
invObj = cur.fetchone()
if invObj:
raise ManyTokensException()
refreshTokenEntry = RefreshTokenEntry(id=tokenObj[0], salt=salt, login=login, app=userObj[2], expiry=userObj[1])
return refreshTokenEntry
except psycopg2.Error as err:
raise Exception("Error when connecting to database: {}".format(err))
finally:
if cur:
cur.close()
if conn:
conn.rollback()
conn.close()
def getUserEntry(application, login, password):
userEntry, pwhash = getUserEntryFromDB(application, login)
if pwhash != crypt(password, pwhash):
userEntry = getUserEntryFromDB(application, login)
if userEntry.pwhash != crypt(password, userEntry.pwhash):
raise PasswordMismatchException()
return userEntry
def generateToken(**args):
def generateToken(func, **args):
try:
body = args["body"]
application = body["application"]
login = body["login"]
password = body["password"]
userEntry = getUserEntry(application, login, password)
timestamp = int(time.time())
payload = {
"iss": JWT_ISSUER,
"iat": int(timestamp),
"exp": int(timestamp + userEntry.expiry),
"sub": str(userEntry.id)
}
for claim in userEntry.claims.items():
# print("DEBUG: generateToken: add claim {} -> {}".format(claim[0], claim[1]))
payload[claim[0]] = claim[1]
return jwt.encode(payload, JWT_PRIV_KEY, algorithm='RS256')
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")
if request.headers.getlist("X-Forwarded-For"):
httpClientIp = request.headers.getlist("X-Forwarded-For")[0]
else:
httpClientIp = request.remote_addr
logger.debug(f"Tuple: {application} {login} {password} {httpClientIp}")
return func(application, login, password, httpClientIp)
except NoTokenException:
logger.error("no token created")
raise werkzeug.exceptions.Unauthorized()
except ManyTokensException:
logger.error("too many tokens created")
raise werkzeug.exceptions.Unauthorized()
except NoUserException:
print("ERROR: generateToken: no user found, login or application wrong")
logger.error("no user found, login or application wrong")
raise werkzeug.exceptions.Unauthorized()
except ManyUsersException:
print("ERROR: generateToken: too many users found")
logger.error("too many users found")
raise werkzeug.exceptions.Unauthorized()
except PasswordMismatchException:
print("ERROR: generateToken: wrong password")
logger.error("wrong password")
raise werkzeug.exceptions.Unauthorized()
except KeyError:
print("ERROR: generateToken: application, login or password missing")
logger.error("application, login or password missing")
raise werkzeug.exceptions.Unauthorized()
except Exception as e:
print("ERROR: generateToken: unspecific exception: {}".format(str(e)))
logger.error("unspecific exception: {}".format(str(e)))
raise werkzeug.exceptions.Unauthorized()
def generateTokenFromEnc(content):
return content
def _makeSimpleToken(application, login, password, httpClientIp, refresh=False):
userEntry = getUserEntry(application, login, password) if not refresh else getUserEntryFromDB(application, login)
timestamp = int(time.time())
payload = {
"iss": JWT_ISSUER,
"iat": int(timestamp),
"exp": int(timestamp + userEntry.expiry),
"sub": str(userEntry.id),
"aud": application
}
logger.debug("claims: {}".format(userEntry.claims))
for claim in userEntry.claims.items():
logger.debug("add claim {}".format(claim))
payload[claim[0]] = claim[1]
return jwt.encode(payload, JWT_PRIV_KEY, algorithm='RS256')
def _makeRefreshToken(application, login, password, httpClientIp):
refreshTokenEntry = getRefreshTokenFromDB(application, login, httpClientIp)
timestamp = int(time.time())
payload = {
"iss": JWT_ISSUER,
"iat": int(timestamp),
"exp": int(timestamp + refreshTokenEntry.expiry),
"sub": str(refreshTokenEntry.login),
"xap": str(refreshTokenEntry.app),
"xid": str(refreshTokenEntry.id),
"xal": str(refreshTokenEntry.salt)
}
return jwt.encode(payload, JWT_PRIV_KEY, algorithm='RS256')
def _makeRefreshableTokens(application, login, password, httpClientIp):
authToken = _makeSimpleToken(application, login, password, httpClientIp)
refreshToken = _makeRefreshToken(application, login, password, httpClientIp)
return {
"authToken": authToken,
"refreshToken": refreshToken
}
def generateSimpleToken(**args):
return generateToken(_makeSimpleToken, **args)
def generateRefreshableTokens(**args):
return generateToken(_makeRefreshableTokens, **args)
def getPubKey():
return JWT_PUB_KEY
def decodeToken(token):
try:
return jwt.decode(token, JWT_PUB_KEY, audience="test")
except JWTError as e:
logger.error("{}".format(e))
raise werkzeug.exceptions.Unauthorized()
def testToken(user, token_info):
return {
"message": f"You are user_id {user} and the provided token has been signed by this issuers. Fine.",
"details": token_info
}
def checkAndInvalidateRefreshToken(login, xid, xal, httpClientIp):
try:
validTokenFound = False
conn = psycopg2.connect(user = DB_USER, password = DB_PASS,
host = DB_HOST, database = DB_NAME)
conn.autocommit = False
with conn:
with conn.cursor() as cur:
cur.execute('SELECT t.id, t.client_ip FROM token_t t, user_t u' +
' WHERE t.valid = true AND ' +
' t.id = %s AND ' +
' t.salt = %s AND ' +
' t."user" = u.id AND ' +
' u.login = %s',
(xid, xal, login))
tokenObj = cur.fetchone()
logger.debug("tokenObj: {}".format(tokenObj))
if not tokenObj:
raise NoTokenException()
invObj = cur.fetchone()
if invObj:
raise ManyTokensException()
if (tokenObj[1] == httpClientIp):
with conn.cursor() as cur:
cur.execute('UPDATE token_t SET used = used + 1 WHERE id = %s',
[ xid ])
validTokenFound = True
else:
logger.warning(f"Client IP in token {tokenObj[1]} and current one {httpClientIp} does not match")
with conn.cursor() as cur:
cur.execute('UPDATE token_t SET valid = false WHERE id = %s',
[ xid ])
if (not validTokenFound):
raise NoValidTokenException()
except psycopg2.Error as err:
raise Exception("Error when connecting to database: {}".format(err))
finally:
if conn:
conn.close()
def refreshTokens(**args):
try:
refreshToken = args["body"]
refreshTokenObj = jwt.decode(refreshToken, JWT_PUB_KEY)
logger.info(str(refreshTokenObj))
if request.headers.getlist("X-Forwarded-For"):
httpClientIp = request.headers.getlist("X-Forwarded-For")[0]
else:
httpClientIp = request.remote_addr
if refreshTokenObj["exp"] < int(time.time()):
raise RefreshTokenExpiredException()
checkAndInvalidateRefreshToken(refreshTokenObj["sub"], refreshTokenObj["xid"], refreshTokenObj["xal"], httpClientIp)
authToken = _makeSimpleToken(refreshTokenObj["xap"], refreshTokenObj["sub"], "", httpClientIp, refresh=True)
refreshToken = _makeRefreshToken(refreshTokenObj["xap"], refreshTokenObj["sub"], "", httpClientIp)
return {
"authToken": authToken,
"refreshToken": refreshToken
}
except JWTError as e:
logger.error("jwt.decode failed: {}".format(e))
raise werkzeug.exceptions.Unauthorized()
except RefreshTokenExpiredException:
logger.error("refresh token expired")
raise werkzeug.exceptions.Unauthorized()
except NoTokenException:
logger.error("no token created/found")
raise werkzeug.exceptions.Unauthorized()
except NoValidTokenException:
logger.error("no valid token found")
raise werkzeug.exceptions.Unauthorized()
except ManyTokensException:
logger.error("too many tokens created/found")
raise werkzeug.exceptions.Unauthorized()
except NoUserException:
logger.error("no user found, login or application wrong")
raise werkzeug.exceptions.Unauthorized()
except ManyUsersException:
logger.error("too many users found")
raise werkzeug.exceptions.Unauthorized()
except PasswordMismatchException:
logger.error("wrong password")
raise werkzeug.exceptions.Unauthorized()
except KeyError:
logger.error("application, login or password missing")
raise werkzeug.exceptions.Unauthorized()
except Exception as e:
logger.error("unspecific exception: {}".format(str(e)))
raise werkzeug.exceptions.Unauthorized()

View File

@ -1,8 +1,8 @@
#!/bin/bash
IMAGE_NAME="registry.hottis.de/wolutator/authservice"
VERSION=0.0.1
VERSION=0.3.x
docker build -t ${IMAGE_NAME}:${VERSION} .
docker push ${IMAGE_NAME}:${VERSION}
# docker push ${IMAGE_NAME}:${VERSION}

View File

@ -1,111 +1,69 @@
CREATE DATABASE `authservice`;
USE `authservice`;
create sequence application_s start with 1 increment by 1;
create table application_t (
id integer primary key not null default nextval('application_s'),
name varchar(128) not null unique
);
CREATE TABLE `applications` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_applications_name` (`name`)
) ENGINE=InnoDB;
create sequence user_s start with 1 increment by 1;
create table user_t (
id integer primary key not null default nextval('user_s'),
login varchar(64) not null unique,
pwhash varchar(64) not null,
expiry integer not null default 600
);
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`login` varchar(64) NOT NULL,
`pwhash` varchar(64) NOT NULL,
`expiry` int(10) unsigned NOT NULL DEFAULT 600,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_users_login` (`login`)
) ENGINE=InnoDB;
create sequence token_s start with 1 increment by 1;
create table token_t (
id integer primary key not null default nextval('token_s'),
"user" integer not null references user_t (id),
salt varchar(64) not null,
valid boolean not null default true
);
CREATE TABLE `claims` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL,
`value` varchar(1024) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_claims_key_value` (`key`, `value`)
) ENGINE=InnoDB;
create sequence claim_s start with 1 increment by 1;
create table claim_t (
id integer primary key not null default nextval('claim_s'),
key varchar(64) not null,
value varchar(64) not null,
application integer not null references application(id),
unique (key, value)
);
CREATE TABLE `user_claims_mapping` (
`user` int(10) unsigned NOT NULL,
`claim` int(10) unsigned NOT NULL,
CONSTRAINT UNIQUE KEY `uk_user_claims_mapping` (`user`, `claim` ),
CONSTRAINT FOREIGN KEY `fk_user_claims_mapping_user` (`user`)
REFERENCES `users`(`id`),
CONSTRAINT FOREIGN KEY `fk_user_claims_mapping_claim` (`claim`)
REFERENCES `claims`(`id`)
) ENGINE=InnoDB;
create table user_claim_mapping_t (
"user" integer not null references user_t(id),
claim integer not null references claim_t(id),
unique ("user", claim)
);
CREATE TABLE `user_applications_mapping` (
`user` int(10) unsigned NOT NULL,
`application` int(10) unsigned NOT NULL,
CONSTRAINT UNIQUE KEY `uk_user_applications_mapping` (`user`, `application` ),
CONSTRAINT FOREIGN KEY `fk_user_applications_mapping_user` (`user`)
REFERENCES `users`(`id`),
CONSTRAINT FOREIGN KEY `fk_user_applications_mapping_application` (`application`)
REFERENCES `applications`(`id`)
) ENGINE=InnoDB;
create table user_application_mapping_t (
"user" integer not null references user_t(id),
application integer not null references application_t(id),
unique ("user", application)
);
CREATE OR REPLACE VIEW claims_for_user AS
SELECT u.id AS user,
c.`key` AS `key`,
c.`value` AS `value`
FROM users u,
claims c,
user_claims_mapping m
WHERE m.user = u.id AND
m.claim = c.id;
CREATE OR REPLACE VIEW user_application AS
SELECT u.login AS login,
u.pwhash AS pwhash,
u.id AS id,
u.expiry AS expiry,
create or replace view claims_for_user_v as
select u.id as "user",
a.name as application,
c.key as key,
c.value as value
from user_t u,
claim_t c,
user_claim_mapping_t m,
application_t a
where m.user = u.id and
m.claim = c.id and
a.id = c.application;
create or replace view user_application_v as
select u.login as login,
u.pwhash as pwhash,
u.id as id,
u.expiry as expiry,
a.name as application
FROM users u,
applications a,
user_applications_mapping m
WHERE u.id = m.user AND
a.id = m.application;
from user_t u,
application_t a,
user_application_mapping_t m
where u.id = m.user and
a.id = m.application;
CREATE USER 'authservice-ui'@'%' IDENTIFIED BY 'test123';
GRANT SELECT ON `user_application` TO 'authservice-ui'@'%';
GRANT SELECT ON `claims_for_user` TO 'authservice-ui'@'%';
CREATE USER 'authservice-cli'@'%' IDENTIFIED BY 'test123';
GRANT INSERT ON `users` TO 'authservice-cli'@'%';
GRANT INSERT ON `user_applications_mapping` TO 'authservice-cli'@'%';
FLUSH PRIVILEGES;
INSERT INTO `applications` (`name`) VALUES ('hv');
INSERT INTO `claims` (`key`, `value`) VALUES ('accesslevel', 'r');
INSERT INTO `claims` (`key`, `value`) VALUES ('accesslevel', 'rw');
-- password is 'test123'
INSERT INTO `users` (`login`, `pwhash`) VALUES ('wn', '$p5k2$186a0$dJXL0AjF$0HualDF92nyilDXPgSbaUn/UpFzSrpPx');
INSERT INTO `user_applications_mapping` (`user`, `application`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'wn'),
(SELECT `id` FROM `applications` WHERE `name` = 'hv')
);
INSERT INTO `user_claims_mapping` (`user`, `claim`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'wn'),
(SELECT `id` FROM `claims` WHERE `key` = 'accesslevel' AND `value` = 'rw')
);
-- password is 'geheim'
INSERT INTO `users` (`login`, `pwhash`) VALUES ('gregor', '$p5k2$186a0$Tcwps8Ar$TsypGB.y1dCB9pWOPz2X2SsxYqrTn3Fv');
INSERT INTO `user_applications_mapping` (`user`, `application`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'gregor'),
(SELECT `id` FROM `applications` WHERE `name` = 'hv')
);
INSERT INTO `user_claims_mapping` (`user`, `claim`)
VALUES(
(SELECT `id` FROM `users` WHERE `login` = 'gregor'),
(SELECT `id` FROM `claims` WHERE `key` = 'accesslevel' AND `value` = 'rw')
);

View File

@ -4,16 +4,18 @@ info:
version: "0.1"
paths:
/auth:
/token:
post:
tags: [ "JWT" ]
summary: Accept login and password, return JWT token
operationId: auth.generateToken
summary: Accepts encrypted or clear set of credentials, returns JWT token
operationId: auth.generateSimpleToken
requestBody:
content:
'application/json':
schema:
$ref: '#/components/schemas/User'
anyOf:
- $ref: '#/components/schemas/User'
- $ref: '#/components/schemas/EncUser'
responses:
'200':
description: JWT token
@ -21,35 +23,54 @@ paths:
'text/plain':
schema:
type: string
/authe:
/refreshable:
post:
tags: [ "JWT" ]
summary: Accept encrypted set of credentials, return JWT token
operationId: auth.generateTokenFromEnc
summary: Accepts encrypted or clear set of credentials, returns tuple of AuthToken and RefreshToken
operationId: auth.generateRefreshableTokens
requestBody:
content:
'application/json':
schema:
anyOf:
- $ref: '#/components/schemas/User'
- $ref: '#/components/schemas/EncUser'
responses:
'200':
description: Token tuple
content:
'application/json':
schema:
$ref: '#/components/schemas/TokenTuple'
/refresh:
post:
tags: [ "JWT" ]
summary: Accepts refresh token, returns tuple of AuthToken and RefreshToken
operationId: auth.refreshTokens
requestBody:
content:
'text/plain':
schema:
type: string
$ref: '#/components/schemas/RefreshToken'
responses:
'200':
description: JWT token
description: Token tuple
content:
'text/plain':
'application/json':
schema:
type: string
/secret:
$ref: '#/components/schemas/TokenTuple'
/test:
get:
tags: [ "JWT" ]
tags: [ "Test" ]
summary: Return secret string
operationId: test.getSecret
operationId: auth.testToken
responses:
'200':
description: secret response
content:
'text/plain':
'application/json':
schema:
type: string
$ref: '#/components/schemas/TestOutput'
security:
- jwt: ['secret']
/pubkey:
@ -72,7 +93,7 @@ components:
type: http
scheme: bearer
bearerFormat: JWT
x-bearerInfoFunc: test.decodeToken
x-bearerInfoFunc: auth.decodeToken
schemas:
User:
description: Application/Login/Password tuple
@ -84,3 +105,32 @@ components:
type: string
password:
type: string
EncUser:
description: Encrypted Application/Login/Password tuple
type: object
properties:
encAleTuple:
type: string
TestOutput:
description: Test Output
type: object
properties:
message:
type: string
details:
type: object
AuthToken:
description: Token for authentication purposes, just a string
type: string
RefreshToken:
description: Token for refresh purposes, just a string
type: string
TokenTuple:
description: Test Output
type: object
properties:
authToken:
$ref: '#/components/schemas/AuthToken'
refreshToken:
$ref: '#/components/schemas/RefreshToken'

24
test.py
View File

@ -1,20 +1,8 @@
from jose import JWTError, jwt
import os
import werkzeug
import connexion
import logging
logging.basicConfig(level=logging.DEBUG)
JWT_SECRET = os.environ['JWT_SECRET']
def decodeToken(token):
try:
return jwt.decode(token, JWT_SECRET)
except JWTError as e:
print("ERROR: decodeToken: {}".format(e))
raise werkzeug.exceptions.Unauthorized()
def getSecret(user, token_info):
return '''
You are user_id {user} and the secret is 'wbevuec'.
Decoded token claims: {token_info}.
'''.format(user=user, token_info=token_info)
app = connexion.App('authservice')
app.add_api('./openapi.yaml')
app.run(port=8080)

28
testjwe.py Normal file
View File

@ -0,0 +1,28 @@
import unittest
from jose import jwe
import os
import json
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
JWT_PRIV_KEY = os.environ["JWT_PRIV_KEY"]
class JweTestMethods(unittest.TestCase):
def test_encryptDecrypt(self):
inObj = {"application":"hv2", "login":"wn", "password":"joshua"}
plainText = json.dumps(inObj)
cryptText = jwe.encrypt(plainText, JWT_PUB_KEY, "A256GCM", "RSA-OAEP")
print(cryptText)
clearText = jwe.decrypt(cryptText, JWT_PRIV_KEY)
print(clearText)
outObj = json.loads(clearText)
print(outObj)
self.assertEqual(outObj, inObj)
if __name__ == '__main__':
unittest.main()