selfprivacy-rest-api/selfprivacy_api/repositories/users/kanidm_user_repository.py

232 lines
7.5 KiB
Python
Raw Normal View History

2024-10-28 21:57:23 +00:00
from typing import Optional
2024-11-19 17:50:43 +00:00
import subprocess
import requests
2024-11-21 17:01:29 +00:00
import re
2024-11-25 11:24:05 +00:00
import logging
2024-11-27 12:52:05 +00:00
from selfprivacy_api.utils import get_domain, temporary_env_var
2024-11-19 17:50:43 +00:00
from selfprivacy_api.utils.redis_pool import RedisPool
2024-11-11 17:37:17 +00:00
from selfprivacy_api.models.user import UserDataUser, UserDataUserOrigin
2024-10-28 21:57:23 +00:00
from selfprivacy_api.repositories.users.abstract_user_repository import (
AbstractUserRepository,
)
2024-11-11 16:50:52 +00:00
KANIDM_URL = "https://127.0.0.1:3013"
2024-11-19 17:50:43 +00:00
redis = RedisPool().get_connection()
2024-11-25 11:24:05 +00:00
logger = logging.getLogger(__name__)
2024-11-19 17:50:43 +00:00
class KanidmAdminToken:
@staticmethod
def get() -> str:
kanidm_admin_token = redis.get("kanidm:token")
if kanidm_admin_token is None:
kanidm_admin_password = redis.get("kanidm:password") # type: ignore
if kanidm_admin_password is None:
kanidm_admin_password = (
KanidmAdminToken.reset_and_save_idm_admin_password()
)
kanidm_admin_token = KanidmAdminToken.create_and_save_token(
kanidm_admin_password=kanidm_admin_password
)
return kanidm_admin_token
@staticmethod
def create_and_save_token(kanidm_admin_password: str) -> str:
2024-11-25 11:24:05 +00:00
logging.error("create_and_save_token START")
2024-11-19 17:50:43 +00:00
with temporary_env_var(key="KANIDM_PASSWORD", value=kanidm_admin_password):
2024-11-27 12:52:05 +00:00
subprocess.run("kanidm login -D idm_admin")
kanidm_admin_token = subprocess.check_output(
[
"kanidm",
"service-account",
"api-token",
"generate",
"--rw",
"selfprivacy",
"token2",
],
text=True,
)
# except subprocess.CalledProcessError as e:
# logger.error(e)
2024-11-19 17:50:43 +00:00
2024-11-25 11:24:05 +00:00
kanidm_admin_token = kanidm_admin_token.splitlines()[-1]
2024-11-22 19:34:07 +00:00
2024-11-19 17:50:43 +00:00
redis.set("kanidm:token", kanidm_admin_token)
return kanidm_admin_token
@staticmethod
def reset_and_save_idm_admin_password() -> str:
2024-11-25 11:24:05 +00:00
logging.error("reset_and_save_idm_admin_password START")
2024-11-22 19:15:06 +00:00
output = subprocess.check_output(
[
"kanidmd",
"recover-account",
"-c",
"/etc/kanidm/server.toml",
"idm_admin",
"-o",
"json",
],
text=True,
)
2024-11-22 18:41:50 +00:00
match = re.search(r'"password":"([^"]+)"', output)
new_kanidm_admin_password = match.group(
1
) # we have many not json strings in output
2024-11-21 17:01:29 +00:00
redis.set("kanidm:password", new_kanidm_admin_password)
2024-11-19 17:50:43 +00:00
return new_kanidm_admin_password
class KanidmQueryError(Exception):
2024-11-11 08:41:30 +00:00
"""Error occurred during kanidm query"""
2024-10-28 21:57:23 +00:00
class KanidmUserRepository(AbstractUserRepository):
@staticmethod
2024-11-11 00:33:17 +00:00
def _send_query(endpoint: str, method: str = "GET", data=None):
request_method = getattr(requests, method.lower(), None)
2024-11-11 16:36:16 +00:00
full_endpoint = f"{KANIDM_URL}/v1/{endpoint}"
2024-11-22 19:18:33 +00:00
# try:
response = request_method(
full_endpoint,
json=data,
headers={
"Authorization": f"Bearer {KanidmAdminToken.get()}",
"Content-Type": "application/json",
},
timeout=0.8, # TODO: change timeout
verify=False, # TODO: REMOVE THIS NOTHALAL!!!!!
)
2024-11-22 19:18:33 +00:00
if response.status_code != 200:
raise KanidmQueryError(
f"Kanidm returned {response.status_code} unexpected HTTP status code. Endpoint: {full_endpoint}. Error: {response.text}."
)
return response.json()
2024-11-11 16:07:34 +00:00
2024-11-22 19:18:33 +00:00
# except Exception as error:
# raise KanidmQueryError(f"Kanidm request failed! Error: {str(error)}")
@staticmethod
2024-11-14 23:30:24 +00:00
def create_user(
username: str,
password: Optional[str] = None, # TODO legacy?
2024-11-14 23:30:24 +00:00
displayname: Optional[str] = None,
email: Optional[str] = None,
directmemberof: Optional[list[str]] = None,
memberof: Optional[list[str]] = None,
) -> None:
2024-11-11 00:33:17 +00:00
data = {
"attrs": {
"name": [username],
2024-11-14 23:30:24 +00:00
"displayname": [displayname if displayname else username],
"mail": [email if email else f"{username}@{get_domain()}"],
"class": ["user"], # TODO read more about it
2024-11-11 00:33:17 +00:00
}
}
2024-11-14 23:30:24 +00:00
if directmemberof:
data["attrs"]["directmemberof"] = directmemberof
if memberof:
data["attrs"]["memberof"] = memberof
return KanidmUserRepository._send_query(
2024-11-11 00:33:17 +00:00
endpoint="person",
method="POST",
data=data,
)
2024-10-28 21:57:23 +00:00
def get_users(
exclude_primary: bool = False,
exclude_root: bool = False,
) -> list[UserDataUser]:
2024-11-11 17:41:44 +00:00
users_data = KanidmUserRepository._send_query(endpoint="person", method="GET")
2024-11-11 17:37:17 +00:00
users = []
for user in users_data:
attrs = user.get("attrs", {})
user_type = UserDataUser(
uuid=attrs.get("uuid", [None])[0],
2024-11-11 17:49:51 +00:00
username=attrs.get("name", [None])[0],
2024-11-14 23:30:24 +00:00
ssh_keys=["test"], # TODO: подключить реальные SSH-ключи
2024-11-11 17:37:17 +00:00
displayname=attrs.get("displayname", [None])[0],
email=attrs.get("mail", [None])[0],
origin=UserDataUserOrigin.NORMAL, # TODO
2024-11-14 23:30:24 +00:00
directmemberof=attrs.get("directmemberof", []),
memberof=attrs.get("memberof", []),
2024-11-11 17:37:17 +00:00
)
users.append(user_type)
return users
2024-10-28 21:57:23 +00:00
def delete_user(username: str) -> None:
"""Deletes an existing user"""
2024-11-14 23:30:24 +00:00
return KanidmUserRepository._send_query(
endpoint=f"person/{username}", method="DELETE"
)
2024-10-28 21:57:23 +00:00
2024-11-14 23:30:24 +00:00
def update_user(
username: str,
password: Optional[str] = None, # TODO legacy?
2024-11-14 23:30:24 +00:00
displayname: Optional[str] = None,
email: Optional[str] = None,
directmemberof: Optional[list[str]] = None,
memberof: Optional[list[str]] = None,
) -> None:
2024-10-28 21:57:23 +00:00
"""Updates the password of an existing user"""
2024-11-14 23:30:24 +00:00
data = {
"attrs": {
"displayname": [displayname if displayname else username],
"mail": [email if email else f"{username}@{get_domain()}"],
"class": ["user"], # TODO read more about it
}
}
if directmemberof:
data["attrs"]["directmemberof"] = directmemberof
if memberof:
data["attrs"]["memberof"] = memberof
return KanidmUserRepository._send_query(
endpoint=f"person/{username}",
method="PATCH",
data=data,
)
2024-10-28 21:57:23 +00:00
def get_user_by_username(username: str) -> Optional[UserDataUser]:
"""Retrieves user data (UserDataUser) by username"""
2024-11-14 23:30:24 +00:00
user_data = KanidmUserRepository._send_query(
endpoint=f"person/{username}",
method="GET",
)
if not user_data or "attrs" not in user_data:
return None
attrs = user_data["attrs"]
return UserDataUser(
uuid=attrs.get("uuid", [None])[0],
username=attrs.get("name", [None])[0],
displayname=attrs.get("displayname", [None])[0],
email=attrs.get("mail", [None])[0],
ssh_keys=attrs.get("ssh_keys", []),
origin=UserDataUserOrigin.NORMAL, # TODO
directmemberof=attrs.get("directmemberof", []),
memberof=attrs.get("memberof", []),
)