16 Commits
0.0.2 ... 0.1.5

Author SHA1 Message Date
309b4c6ba8 authe 2021-05-07 12:24:59 +02:00
a921fb6a0f changes 2021-05-07 12:15:30 +02:00
f56db65012 pubkey stuff, remove debug 2021-05-06 16:55:39 +02:00
ef0793be4e pubkey stuff 2021-05-06 16:52:16 +02:00
3f2442e259 pubkey stuff 2021-05-06 16:50:17 +02:00
78439a7ed8 pubkey stuff 2021-05-06 16:46:19 +02:00
0377278ea0 pubkey stuff 2021-05-06 16:37:32 +02:00
49e8aa43b4 use rs256 2021-05-06 15:42:46 +02:00
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
7 changed files with 131 additions and 74 deletions

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'

View File

@ -16,15 +16,11 @@ parser.add_argument('--password', '-p',
parser.add_argument('--application', '-a', parser.add_argument('--application', '-a',
help='Application', help='Application',
required=True) required=True)
parser.add_argument('--issuer', '-i',
help='Issuer',
required=True)
args = parser.parse_args() args = parser.parse_args()
user = args.user user = args.user
password = args.password password = args.password
application = args.application application = args.application
issuer = args.issuer
DB_USER = os.environ["DB_USER"] DB_USER = os.environ["DB_USER"]
@ -43,13 +39,9 @@ try:
cur = conn.cursor() cur = conn.cursor()
cur.execute(""" cur.execute("""
INSERT INTO users (issuer, login, password) INSERT INTO users (login, pwhash)
VALUES( VALUES(?, ?)
(SELECT id FROM issuers WHERE name = ?), """, [user, pwhash])
?,
?
)
""", [issuer, user, pwhash])
cur.execute(""" cur.execute("""
INSERT INTO user_applications_mapping (application, user) INSERT INTO user_applications_mapping (application, user)
VALUES( VALUES(

36
auth.py
View File

@ -12,6 +12,11 @@ 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"]
class NoUserException(Exception): class NoUserException(Exception):
pass pass
@ -23,8 +28,15 @@ class PasswordMismatchException(Exception):
pass pass
UserEntry = namedtuple('UserEntry', ['id', 'login', 'issuer', 'secret', 'expiry', 'claims']) UserEntry = namedtuple('UserEntry', ['id', 'login', 'expiry', 'claims'])
JWT_PRIV_KEY = ""
with open('/opt/app/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()
def getUserEntryFromDB(application: str, login: str): def getUserEntryFromDB(application: str, login: str):
@ -36,7 +48,7 @@ def getUserEntryFromDB(application: str, login: str):
conn.autocommit = False conn.autocommit = False
cur = conn.cursor(dictionary=True) cur = conn.cursor(dictionary=True)
cur.execute("SELECT id, password, issuer, secret, expiry FROM user_application_and_issuer " + cur.execute("SELECT id, pwhash, expiry FROM user_application" +
" WHERE application = ? AND login = ?", " WHERE application = ? AND login = ?",
[application, login]) [application, login])
resObj = cur.next() resObj = cur.next()
@ -54,7 +66,7 @@ def getUserEntryFromDB(application: str, login: str):
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"]] ]
@ -62,11 +74,9 @@ def getUserEntryFromDB(application: str, login: str):
else: else:
claims[claimObj["key"]] = claimObj["value"] claims[claimObj["key"]] = claimObj["value"]
userEntry = UserEntry(id=userId, login=login, userEntry = UserEntry(id=userId, login=login, expiry=resObj["expiry"], claims=claims)
secret=resObj["secret"], issuer=resObj["issuer"],
expiry=resObj["expiry"], claims=claims)
return userEntry, resObj["password"] 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:
@ -93,16 +103,16 @@ def generateToken(**args):
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_PRIV_KEY, algorithm='RS256')
except NoUserException: except NoUserException:
print("ERROR: generateToken: no user found, login or application wrong") print("ERROR: generateToken: no user found, login or application wrong")
raise werkzeug.exceptions.Unauthorized() raise werkzeug.exceptions.Unauthorized()
@ -118,3 +128,9 @@ def generateToken(**args):
except Exception as e: except Exception as e:
print("ERROR: generateToken: unspecific exception: {}".format(str(e))) print("ERROR: generateToken: unspecific exception: {}".format(str(e)))
raise werkzeug.exceptions.Unauthorized() raise werkzeug.exceptions.Unauthorized()
def generateTokenFromEnc(content):
return content
def getPubKey():
return JWT_PUB_KEY

View File

@ -1,14 +1,5 @@
CREATE TABLE `issuers` ( CREATE DATABASE `authservice`;
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, USE `authservice`;
`name` varchar(128) NOT NULL,
`secret` varchar(128) NOT NULL,
`max_expiry` int(10) NOT NULL,
CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_issuers_name` (`name`)
) ENGINE=InnoDB;
ALTER TABLE `issuers`
MODIFY COLUMN `max_expiry` int(10) unsigned NOT NULL;
CREATE TABLE `applications` ( CREATE TABLE `applications` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@ -20,36 +11,12 @@ CREATE TABLE `applications` (
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,
`expiry` int(10) unsigned NOT NULL DEFAULT 600,
CONSTRAINT PRIMARY KEY (`id`), CONSTRAINT PRIMARY KEY (`id`),
CONSTRAINT UNIQUE KEY `uk_users_login` (`login`) 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;
ALTER TABLE `users`
MODIFY COLUMN expiry int(10) unsigned NOT NULL DEFAULT 600;
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,
@ -88,19 +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_application_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,
a.name as application, u.expiry AS expiry,
i.name AS issuer, a.name as application
i.secret AS secret,
least(u.expiry, i.max_expiry) AS expiry
FROM users u, FROM users u,
issuers i,
applications a, applications a,
user_applications_mapping m user_applications_mapping m
WHERE u.issuer = i.id AND WHERE u.id = m.user AND
u.id = m.user AND
a.id = m.application; 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

@ -7,7 +7,7 @@ paths:
/auth: /auth:
post: post:
tags: [ "JWT" ] tags: [ "JWT" ]
summary: Return JWT token summary: Accept login and password, return JWT token
operationId: auth.generateToken operationId: auth.generateToken
requestBody: requestBody:
content: content:
@ -21,6 +21,23 @@ paths:
'text/plain': 'text/plain':
schema: schema:
type: string type: string
/authe:
post:
tags: [ "JWT" ]
summary: Accept encrypted set of credentials, return JWT token
operationId: auth.generateTokenFromEnc
requestBody:
content:
'text/plain':
schema:
type: string
responses:
'200':
description: JWT token
content:
'text/plain':
schema:
type: string
/secret: /secret:
get: get:
tags: [ "JWT" ] tags: [ "JWT" ]
@ -35,6 +52,19 @@ paths:
type: string type: string
security: security:
- jwt: ['secret'] - jwt: ['secret']
/pubkey:
get:
tags: [ "JWT" ]
summary: Get the public key of this issuer
operationId: auth.getPubKey
responses:
'200':
description: public key
content:
'text/plain':
schema:
type: string
components: components:
securitySchemes: securitySchemes:

13
readme.md Normal file
View File

@ -0,0 +1,13 @@
Generate the RSA key pair using:
Private key (keep it secret!):
openssl genrsa -out authservice.key 2048
Extract the public key (publish it):
openssl rsa -in authservice.pem -outform PEM -pubout -out authservice.pub

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)