forked from kompetenzinventar/ki-backend
Merge pull request 'Anmeldung per LDAP' (#35) from feature-ldap into main
Reviewed-on: kompetenzinventar/ki-backend#35
This commit is contained in:
commit
730878847b
1
Pipfile
1
Pipfile
@ -16,6 +16,7 @@ sqlalchemy = "~=1.4.18"
|
|||||||
waitress = "~=2.0.0"
|
waitress = "~=2.0.0"
|
||||||
pyyaml = "~=5.4.1"
|
pyyaml = "~=5.4.1"
|
||||||
flask-cors = "~=3.0.10"
|
flask-cors = "~=3.0.10"
|
||||||
|
ldap3 = "~=2.9"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
flake8 = "~=3.9.2"
|
flake8 = "~=3.9.2"
|
||||||
|
51
Pipfile.lock
generated
51
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "128a3b90d2e3d5876c5942fb256dba2d5e3d0b3a545cae6d51aeda6925a0f593"
|
"sha256": "9dfe9cdb37253c770013fcf80c413f9b0ffdfc1cc16210fb9d80cdac760c2b93"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
|
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
|
||||||
"sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
|
"sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==8.0.1"
|
"version": "==8.0.1"
|
||||||
},
|
},
|
||||||
"flask": {
|
"flask": {
|
||||||
@ -124,7 +124,7 @@
|
|||||||
"sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c",
|
"sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c",
|
||||||
"sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"
|
"sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==2.0.1"
|
"version": "==2.0.1"
|
||||||
},
|
},
|
||||||
"jinja2": {
|
"jinja2": {
|
||||||
@ -132,9 +132,20 @@
|
|||||||
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
|
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
|
||||||
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
|
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==3.0.1"
|
"version": "==3.0.1"
|
||||||
},
|
},
|
||||||
|
"ldap3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
|
||||||
|
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
|
||||||
|
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
|
||||||
|
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
|
||||||
|
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.9"
|
||||||
|
},
|
||||||
"mako": {
|
"mako": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab",
|
"sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab",
|
||||||
@ -180,9 +191,27 @@
|
|||||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
||||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==2.0.1"
|
"version": "==2.0.1"
|
||||||
},
|
},
|
||||||
|
"pyasn1": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
|
||||||
|
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
|
||||||
|
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
|
||||||
|
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
|
||||||
|
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
|
||||||
|
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
|
||||||
|
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
|
||||||
|
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
|
||||||
|
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
|
||||||
|
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
|
||||||
|
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
|
||||||
|
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
|
||||||
|
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
|
||||||
|
],
|
||||||
|
"version": "==0.4.8"
|
||||||
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||||
@ -301,7 +330,7 @@
|
|||||||
"sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42",
|
"sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42",
|
||||||
"sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"
|
"sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==2.0.1"
|
"version": "==2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -374,11 +403,11 @@
|
|||||||
},
|
},
|
||||||
"identify": {
|
"identify": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421",
|
"sha256:7abaecbb414e385752e8ce02d8c494f4fbc780c975074b46172598a28f1ab839",
|
||||||
"sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"
|
"sha256:a0e700637abcbd1caae58e0463861250095dfe330a8371733a471af706a4a29a"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.1'",
|
"markers": "python_full_version >= '3.6.1'",
|
||||||
"version": "==2.2.10"
|
"version": "==2.2.11"
|
||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -393,7 +422,7 @@
|
|||||||
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
|
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
|
||||||
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
|
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==3.0.1"
|
"version": "==3.0.1"
|
||||||
},
|
},
|
||||||
"license-expression": {
|
"license-expression": {
|
||||||
@ -440,7 +469,7 @@
|
|||||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
||||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==2.0.1"
|
"version": "==2.0.1"
|
||||||
},
|
},
|
||||||
"mccabe": {
|
"mccabe": {
|
||||||
|
@ -68,9 +68,18 @@ flake8
|
|||||||
|
|
||||||
### Testbenutzer
|
### Testbenutzer
|
||||||
|
|
||||||
|
#### Lokal ohne LDAP
|
||||||
|
|
||||||
Für ein Login ohne LDAP werden die Benutzer aus der [`auth.yml`](./data/auth.yml) benutzt.
|
Für ein Login ohne LDAP werden die Benutzer aus der [`auth.yml`](./data/auth.yml) benutzt.
|
||||||
|
|
||||||
|
|
||||||
|
#### Lokal mit LDAP
|
||||||
|
|
||||||
|
Einen LDAP Server aufsetzen. Z.B. https://directory.apache.org/apacheds/
|
||||||
|
|
||||||
|
In der `.env` die LDAP Dinge ausfüllen (siehe [`env.dev`](./env.dev)).
|
||||||
|
|
||||||
|
|
||||||
### Beispiel-Requests
|
### Beispiel-Requests
|
||||||
|
|
||||||
Beispiele brauchen curl und jq.
|
Beispiele brauchen curl und jq.
|
||||||
|
19
app.py
19
app.py
@ -8,25 +8,36 @@ import os
|
|||||||
from dotenv import load_dotenv, find_dotenv
|
from dotenv import load_dotenv, find_dotenv
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask.logging import default_handler
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from ldap3.utils.log import logger as ldap3_logger
|
||||||
|
from ldap3.utils.log import set_library_log_detail_level, BASIC
|
||||||
|
|
||||||
load_dotenv(find_dotenv())
|
load_dotenv(find_dotenv())
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
loglevel = os.getenv("KI_LOGLEVEL", logging.WARNING)
|
loglevel = os.getenv("KI_LOGLEVEL", logging.WARNING)
|
||||||
loglevel = int(loglevel)
|
loglevel = int(loglevel)
|
||||||
|
app.logger.setLevel(loglevel)
|
||||||
logging.basicConfig(level=loglevel)
|
logging.basicConfig(level=loglevel)
|
||||||
logging.debug("Hello from KI")
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
set_library_log_detail_level(BASIC)
|
||||||
|
ldap3_logger.addHandler(default_handler)
|
||||||
|
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI")
|
app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI")
|
||||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||||
app.config["KI_DATA_DIR"] = os.path.dirname(__file__) + "/data"
|
app.config["KI_DATA_DIR"] = os.path.dirname(__file__) + "/data"
|
||||||
app.config["KI_AUTH"] = os.getenv("KI_AUTH")
|
|
||||||
app.config["CORS_ORIGINS"] = os.getenv("CORS_ORIGINS", "*")
|
app.config["CORS_ORIGINS"] = os.getenv("CORS_ORIGINS", "*")
|
||||||
|
|
||||||
|
app.config["KI_AUTH"] = os.getenv("KI_AUTH")
|
||||||
|
app.config["KI_LDAP_URL"] = os.getenv("KI_LDAP_URL")
|
||||||
|
app.config["KI_LDAP_ROOT_DN"] = os.getenv("KI_LDAP_ROOT_DN")
|
||||||
|
|
||||||
CORS(app)
|
CORS(app)
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
migrate = Migrate(app, db)
|
migrate = Migrate(app, db)
|
||||||
|
|
||||||
|
logging.debug("Hello from KI")
|
||||||
|
|
||||||
from ki import module # noqa
|
from ki import module # noqa
|
||||||
|
5
env.dev
5
env.dev
@ -9,7 +9,12 @@ CORS_ORIGINS=*
|
|||||||
FLASK_APP=app.py
|
FLASK_APP=app.py
|
||||||
FLASK_ENV=development
|
FLASK_ENV=development
|
||||||
|
|
||||||
|
# auth method: file or ldap
|
||||||
KI_AUTH=file
|
KI_AUTH=file
|
||||||
|
|
||||||
|
# ldap auth only
|
||||||
|
KI_LDAP_URL=ldap://localhost:10389
|
||||||
|
KI_LDAP_ROOT_DN=dc=example,dc=com
|
||||||
|
|
||||||
# 10 = debug
|
# 10 = debug
|
||||||
KI_LOGLEVEL=10
|
KI_LOGLEVEL=10
|
||||||
|
53
ki/auth.py
53
ki/auth.py
@ -5,11 +5,28 @@
|
|||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from ldap3 import Server, Connection, ALL
|
||||||
|
|
||||||
from app import app, db
|
from app import app, db
|
||||||
from ki.models import User, Token
|
from ki.models import User, Token
|
||||||
|
|
||||||
|
|
||||||
def auth(username, password):
|
def create_user_token(username):
|
||||||
|
user = User.query.filter(User.auth_id.__eq__(username)).first()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
user = User(auth_id=username)
|
||||||
|
db.session.add(user)
|
||||||
|
|
||||||
|
token = Token(token=str(uuid.uuid4()), user=user)
|
||||||
|
db.session.add(token)
|
||||||
|
db.session.commit()
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def file_auth(username, password):
|
||||||
|
app.logger.debug("performing file authentication")
|
||||||
|
|
||||||
auth_file_path = app.config["KI_DATA_DIR"] + "/auth.yml"
|
auth_file_path = app.config["KI_DATA_DIR"] + "/auth.yml"
|
||||||
|
|
||||||
with open(auth_file_path, "r") as auth_file_stream:
|
with open(auth_file_path, "r") as auth_file_stream:
|
||||||
@ -23,14 +40,32 @@ def auth(username, password):
|
|||||||
if auth_user["password"] != password:
|
if auth_user["password"] != password:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
user = User.query.filter(User.auth_id.__eq__(username)).first()
|
return create_user_token(username)
|
||||||
|
|
||||||
if user is None:
|
|
||||||
user = User(auth_id=username)
|
|
||||||
db.session.add(user)
|
|
||||||
|
|
||||||
token = Token(token=str(uuid.uuid4()), user=user)
|
def ldap_auth(username, password):
|
||||||
db.session.add(token)
|
app.logger.debug("performing LDAP authentication")
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return token
|
server = Server(app.config['KI_LDAP_URL'], get_info=ALL)
|
||||||
|
root_dn = app.config['KI_LDAP_ROOT_DN']
|
||||||
|
ldap_user = f"cn={username},{root_dn}"
|
||||||
|
|
||||||
|
app.logger.debug(f"server: {server}")
|
||||||
|
connection = Connection(server, user=ldap_user, password=password)
|
||||||
|
|
||||||
|
if connection.bind():
|
||||||
|
connection.unbind()
|
||||||
|
return create_user_token(username)
|
||||||
|
|
||||||
|
connection.unbind()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def auth(username, password):
|
||||||
|
if app.config['KI_AUTH'] == 'file':
|
||||||
|
return file_auth(username, password)
|
||||||
|
|
||||||
|
if app.config['KI_AUTH'] == 'ldap':
|
||||||
|
return ldap_auth(username, password)
|
||||||
|
|
||||||
|
raise RuntimeError('unknown auth method')
|
||||||
|
@ -15,8 +15,10 @@ class ApiTest(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
app.debug = True
|
app.debug = True
|
||||||
|
app.config["KI_AUTH"] = "file"
|
||||||
app.config["TESTING"] = True
|
app.config["TESTING"] = True
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///:memory:"
|
||||||
|
|
||||||
self.client = app.test_client()
|
self.client = app.test_client()
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
Loading…
Reference in New Issue
Block a user