89 lines
3.0 KiB
Python
89 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
import bcrypt
|
|
from sqlalchemy import select
|
|
|
|
from app.db.engine import get_session
|
|
from app.db.models import User
|
|
from app.security.audit import log_event
|
|
from app.security.ldap_client import LdapClient
|
|
from app.security.ldap_sync import sync_user_ldap_roles
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
|
|
|
|
|
|
def verify_password(password: str, hashed: str) -> bool:
|
|
if not hashed:
|
|
return False
|
|
try:
|
|
return bcrypt.checkpw(password.encode("utf-8"), hashed.encode("utf-8"))
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def get_user_by_username(session, username: str) -> User | None:
|
|
return session.scalar(select(User).where(User.username == username))
|
|
|
|
|
|
def authenticate_local(username: str, password: str, request=None) -> int | None:
|
|
db = get_session()
|
|
try:
|
|
user = get_user_by_username(db, username)
|
|
if not user or not user.is_active or user.is_ldap:
|
|
if user:
|
|
log_event(db, action="login.failed", target=username, detail={"reason": "local_denied"}, request=request)
|
|
return None
|
|
if not verify_password(password, user.password_hash):
|
|
log_event(db, action="login.failed", target=username, detail={"reason": "password"}, request=request)
|
|
return None
|
|
user.last_login_at = datetime.utcnow()
|
|
db.add(user)
|
|
db.commit()
|
|
log_event(db, action="login.success", target=username, detail={"provider": "local"}, request=request, actor=user)
|
|
return int(user.id)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def authenticate_ldap(username: str, password: str, request=None) -> int | None:
|
|
client = LdapClient()
|
|
result = client.authenticate(username, password)
|
|
if not result:
|
|
db = get_session()
|
|
try:
|
|
log_event(db, action="login.failed", target=username, detail={"reason": "ldap"}, request=request)
|
|
finally:
|
|
db.close()
|
|
return None
|
|
|
|
user_dn = result["user_dn"]
|
|
db = get_session()
|
|
try:
|
|
user = get_user_by_username(db, username)
|
|
if not user:
|
|
user = User(username=username, is_active=True, is_superuser=False, is_ldap=True, password_hash="")
|
|
db.add(user)
|
|
db.commit()
|
|
db.refresh(user)
|
|
user.is_ldap = True
|
|
user.is_active = True
|
|
user.last_login_at = datetime.utcnow()
|
|
sync_user_ldap_roles(session=db, user=user, username=username, user_dn=user_dn)
|
|
db.add(user)
|
|
db.commit()
|
|
log_event(db, action="login.success", target=username, detail={"provider": "ldap"}, request=request, actor=user)
|
|
return int(user.id)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def authenticate(username: str, password: str, request=None) -> int | None:
|
|
user_id = authenticate_local(username, password, request=request)
|
|
if user_id:
|
|
return user_id
|
|
return authenticate_ldap(username, password, request=request)
|