11 Commits
0.0.1 ... 0.0.9

Author SHA1 Message Date
35a997774f fix in claims handling 2021-05-06 15:22:43 +02:00
08734cb82c remove x from private claims 2021-01-27 13:31:34 +01:00
875301b437 fix 2021-01-27 12:40:27 +01:00
da06065959 enable ui 2021-01-27 12:06:21 +01:00
fe007cbfe7 forgotten fix 2021-01-27 11:02:19 +01:00
e2d5ed21ad schema fixes 2021-01-27 10:57:54 +01:00
003c83da92 ui user 2021-01-26 22:56:07 +01:00
ca17c556d6 disable ui 2021-01-26 22:36:11 +01:00
ef1b8ddf30 add module to dockerfile 2021-01-26 22:11:08 +01:00
e1b9597fdb crypt and adduser tool 2021-01-26 22:06:39 +01:00
ca9e0b81d3 application and pwhash 2021-01-26 21:27:17 +01:00
7 changed files with 176 additions and 69 deletions

View File

@ -24,7 +24,8 @@ RUN \
pip3 install uwsgi && \ pip3 install uwsgi && \
pip3 install flask-cors && \ pip3 install flask-cors && \
pip3 install six && \ pip3 install six && \
pip3 install python-jose[cryptography] pip3 install python-jose[cryptography] && \
pip3 install pbkdf2
RUN \ RUN \
mkdir -p ${APP_DIR} && \ mkdir -p ${APP_DIR} && \

View File

@ -1,9 +1,10 @@
# copy to ENV and adjust values # copy to ENV and adjust values
export DB_HOST="172.16.10.18" export DB_HOST="172.16.10.18"
export DB_USER="hausverwaltung-ui" export DB_USER="authservice-ui"
export DB_PASS="test123" export DB_PASS="test123"
export DB_NAME="authservice" export DB_NAME="authservice"
# only required for decoding, on client side # only required for decoding, on client side
export JWT_SECRET='streng_geheim' export JWT_SECRET='streng_geheim'
export JWT_ISSUER='de.hottis.authservice'

59
asadduser.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/python
import mariadb
from pbkdf2 import crypt
import argparse
import os
parser = argparse.ArgumentParser(description='asadduser')
parser.add_argument('--user', '-u',
help='Login',
required=True)
parser.add_argument('--password', '-p',
help='Password',
required=True)
parser.add_argument('--application', '-a',
help='Application',
required=True)
args = parser.parse_args()
user = args.user
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"]
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.autocommit = False
cur = conn.cursor()
cur.execute("""
INSERT INTO users (login, pwhash)
VALUES(?, ?)
""", [user, pwhash])
cur.execute("""
INSERT INTO user_applications_mapping (application, user)
VALUES(
(SELECT id FROM applications WHERE name = ?),
(SELECT id FROM users WHERE login = ?)
)
""", [application, user])
conn.commit()
finally:
if cur:
cur.close()
if conn:
conn.rollback()
conn.close()

62
auth.py
View File

@ -5,22 +5,32 @@ import werkzeug
import os import os
import mariadb import mariadb
from collections import namedtuple from collections import namedtuple
from pbkdf2 import crypt
DB_USER = os.environ["DB_USER"] DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"] DB_PASS = os.environ["DB_PASS"]
DB_HOST = os.environ["DB_HOST"] DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"] DB_NAME = os.environ["DB_NAME"]
JWT_ISSUER = os.environ["JWT_ISSUER"]
JWT_SECRET = os.environ["JWT_SECRET"]
class NoUserException(Exception): pass
class ManyUsersException(Exception): pass
UserEntry = namedtuple('UserEntry', ['id', 'login', 'issuer', 'secret', 'expiry', 'claims']) class NoUserException(Exception):
pass
class ManyUsersException(Exception):
pass
class PasswordMismatchException(Exception):
pass
UserEntry = namedtuple('UserEntry', ['id', 'login', 'expiry', 'claims'])
def getUserEntryFromDB(login: str, password: str) -> UserEntry: def getUserEntryFromDB(application: str, login: str):
conn = None conn = None
cur = None cur = None
try: try:
@ -29,9 +39,9 @@ def getUserEntryFromDB(login: str, password: str) -> UserEntry:
conn.autocommit = False conn.autocommit = False
cur = conn.cursor(dictionary=True) cur = conn.cursor(dictionary=True)
# print("DEBUG: getUserEntryFromDB: login: <{}>, password: <{}>".format(login, password)) cur.execute("SELECT id, pwhash, expiry FROM user_application" +
cur.execute("SELECT id, issuer, secret, expiry FROM user_and_issuer WHERE login = ? AND password = ?", " WHERE application = ? AND login = ?",
[login, password]) [application, login])
resObj = cur.next() resObj = cur.next()
print("DEBUG: getUserEntryFromDB: resObj: {}".format(resObj)) print("DEBUG: getUserEntryFromDB: resObj: {}".format(resObj))
if not resObj: if not resObj:
@ -47,7 +57,7 @@ def getUserEntryFromDB(login: str, password: str) -> UserEntry:
for claimObj in cur: for claimObj in cur:
print("DEBUG: getUserEntryFromDB: add claim {} -> {}".format(claimObj["key"], claimObj["value"])) print("DEBUG: getUserEntryFromDB: add claim {} -> {}".format(claimObj["key"], claimObj["value"]))
if claimObj["key"] in claims: if claimObj["key"] in claims:
if isinstance(claimObj["key"], list): if isinstance(claims[claimObj["key"]], list):
claims[claimObj["key"]].append(claimObj["value"]) claims[claimObj["key"]].append(claimObj["value"])
else: else:
claims[claimObj["key"]] = [ claims[claimObj["key"]] ] claims[claimObj["key"]] = [ claims[claimObj["key"]] ]
@ -55,11 +65,9 @@ def getUserEntryFromDB(login: str, password: str) -> UserEntry:
else: else:
claims[claimObj["key"]] = claimObj["value"] claims[claimObj["key"]] = claimObj["value"]
userEntry = UserEntry(id=userId, login=login, secret=resObj["secret"], userEntry = UserEntry(id=userId, login=login, expiry=resObj["expiry"], claims=claims)
issuer=resObj["issuer"], expiry=resObj["expiry"],
claims=claims)
return userEntry return userEntry, resObj["pwhash"]
except mariadb.Error as err: except mariadb.Error as err:
raise Exception("Error when connecting to database: {}".format(err)) raise Exception("Error when connecting to database: {}".format(err))
finally: finally:
@ -69,38 +77,44 @@ def getUserEntryFromDB(login: str, password: str) -> UserEntry:
conn.rollback() conn.rollback()
conn.close() conn.close()
def getUserEntry(application, login, password):
def getUserEntry(login: str, password: str) -> UserEntry: userEntry, pwhash = getUserEntryFromDB(application, login)
return getUserEntryFromDB(login, password) if pwhash != crypt(password, pwhash):
raise PasswordMismatchException()
return userEntry
def generateToken(**args): def generateToken(**args):
try: try:
body = args["body"] body = args["body"]
application = body["application"]
login = body["login"] login = body["login"]
password = body["password"] password = body["password"]
userEntry = getUserEntryFromDB(login, password)
userEntry = getUserEntry(application, login, password)
timestamp = int(time.time()) timestamp = int(time.time())
payload = { payload = {
"iss": userEntry.issuer, "iss": JWT_ISSUER,
"iat": int(timestamp), "iat": int(timestamp),
"exp": int(timestamp + userEntry.expiry), "exp": int(timestamp + userEntry.expiry),
"sub": str(userEntry.id) "sub": str(userEntry.id)
} }
for claim in userEntry.claims.items(): for claim in userEntry.claims.items():
# print("DEBUG: generateToken: add claim {} -> {}".format(claim[0], claim[1])) # print("DEBUG: generateToken: add claim {} -> {}".format(claim[0], claim[1]))
payload["x-{}".format(claim[0])] = claim[1] payload[claim[0]] = claim[1]
return jwt.encode(payload, userEntry.secret) return jwt.encode(payload, JWT_SECRET)
except NoUserException: except NoUserException:
print("ERROR: generateToken: no user found, login or password wrong") print("ERROR: generateToken: no user found, login or application wrong")
raise werkzeug.exceptions.Unauthorized() raise werkzeug.exceptions.Unauthorized()
except ManyUsersException: except ManyUsersException:
print("ERROR: generateToken: too many users found") print("ERROR: generateToken: too many users found")
raise werkzeug.exceptions.Unauthorized() raise werkzeug.exceptions.Unauthorized()
except PasswordMismatchException:
print("ERROR: generateToken: wrong password")
raise werkzeug.exceptions.Unauthorized()
except KeyError: except KeyError:
print("ERROR: generateToken: login or password missing") print("ERROR: generateToken: application, login or password missing")
raise werkzeug.exceptions.Unauthorized() raise werkzeug.exceptions.Unauthorized()
except Exception as e: except Exception as e:
print("ERROR: generateToken: unspecific exception: {}".format(str(e))) print("ERROR: generateToken: unspecific exception: {}".format(str(e)))

View File

@ -1,45 +1,22 @@
CREATE TABLE `issuers` ( CREATE DATABASE `authservice`;
USE `authservice`;
CREATE TABLE `applications` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL, `name` varchar(128) NOT NULL,
`secret` varchar(128) NOT NULL,
`max_expiry` int(10) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`), CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_issuers_name` (`name`) CONSTRAINT UNIQUE KEY `uk_applications_name` (`name`)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
ALTER TABLE `issuers`
MODIFY COLUMN `max_expiry` int(10) unsigned NOT NULL;
CREATE TABLE `users` ( CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`login` varchar(64) NOT NULL, `login` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL, `pwhash` varchar(64) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`), `expiry` int(10) unsigned NOT NULL DEFAULT 600,
CONSTRAINT UNIQUE KEY `uk_users_login` (`login`) CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_users_login` (`login`)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
ALTER TABLE `users`
ADD COLUMN issuer int(10) unsigned;
ALTER TABLE `users`
MODIFY COLUMN issuer int(10) unsigned NOT NULL;
ALTER TABLE `users`
ADD CONSTRAINT FOREIGN KEY `fk_users_issuer` (`issuer`)
REFERENCES `issuers` (`id`);
ALTER TABLE `users`
DROP CONSTRAINT `uk_users_login`;
ALTER TABLE `users`
ADD CONSTRAINT UNIQUE KEY `uk_users_login_issuer` (`login`, `issuer`);
ALTER TABLE `users`
ADD COLUMN expiry int(10) unsigned;
ALTER TABLE `users`
MODIFY COLUMN expiry int(10) unsigned NOT NULL;
CREATE TABLE `claims` ( CREATE TABLE `claims` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL, `key` varchar(64) NOT NULL,
@ -58,6 +35,16 @@ CREATE TABLE `user_claims_mapping` (
REFERENCES `claims`(`id`) REFERENCES `claims`(`id`)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
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 OR REPLACE VIEW claims_for_user AS CREATE OR REPLACE VIEW claims_for_user AS
SELECT u.id AS user, SELECT u.id AS user,
c.`key` AS `key`, c.`key` AS `key`,
@ -68,14 +55,57 @@ CREATE OR REPLACE VIEW claims_for_user AS
WHERE m.user = u.id AND WHERE m.user = u.id AND
m.claim = c.id; m.claim = c.id;
CREATE OR REPLACE VIEW user_and_issuer AS CREATE OR REPLACE VIEW user_application AS
SELECT u.login AS login, SELECT u.login AS login,
u.password AS password, u.pwhash AS pwhash,
u.id AS id, u.id AS id,
i.name AS issuer, u.expiry AS expiry,
i.secret AS secret, a.name as application
least(u.expiry, i.max_expiry) AS expiry
FROM users u, FROM users u,
issuers i applications a,
WHERE u.issuer = i.id; user_applications_mapping 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

@ -45,9 +45,11 @@ components:
x-bearerInfoFunc: test.decodeToken x-bearerInfoFunc: test.decodeToken
schemas: schemas:
User: User:
description: Login/Password tuple description: Application/Login/Password tuple
type: object type: object
properties: properties:
application:
type: string
login: login:
type: string type: string
password: password:

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('openapi.yaml') app.add_api('openapi.yaml', options = {"swagger_ui": True})
# CORSify it - otherwise Angular won't accept it # CORSify it - otherwise Angular won't accept it
CORS(app.app) CORS(app.app)