Merge pull request 'Anmeldung per LDAP' (#35) from feature-ldap into main

Reviewed-on: kompetenzinventar/ki-backend#35
This commit is contained in:
weeman 2021-07-26 19:44:19 +02:00
commit 730878847b
7 changed files with 116 additions and 24 deletions

View File

@ -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
View File

@ -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": {

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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():