2025-09-23 17:14:11 +03:00
|
|
|
from io import DEFAULT_BUFFER_SIZE
|
2025-09-23 15:53:51 +03:00
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import requests
|
|
|
|
import psycopg2 as ps
|
|
|
|
|
|
|
|
def read_file(path):
|
|
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
|
|
return f.read()
|
|
|
|
|
|
|
|
def getenv(name):
|
|
|
|
try:
|
|
|
|
return os.environ[name]
|
|
|
|
except KeyError:
|
2025-09-23 17:14:11 +03:00
|
|
|
print(f"[ERROR] Missing environment variable {name}. You should NOT run this script by hand, please use systemd mastodon-kanidm-sync.service.")
|
2025-09-23 15:53:51 +03:00
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
2025-09-23 17:14:11 +03:00
|
|
|
# Import configuration
|
2025-09-23 15:53:51 +03:00
|
|
|
KANIDM_URL = getenv("KANIDM_URL")
|
|
|
|
KANIDM_TOKEN = read_file(getenv("KANIDM_TOKEN_PATH")).strip()
|
2025-09-23 23:11:25 +03:00
|
|
|
OWNER_USERNAME = getenv("OWNER_USERNAME")
|
2025-09-23 17:14:11 +03:00
|
|
|
|
2025-09-23 22:49:51 +03:00
|
|
|
# Fetch kanidm users list from userdata file
|
2025-09-23 17:14:11 +03:00
|
|
|
# Userdata file is json list with information about what users are configured by kanidm
|
2025-09-23 22:49:51 +03:00
|
|
|
try:
|
2025-09-24 17:05:07 +03:00
|
|
|
USERDATA = read_file(getenv("USERDATA_FILE_PATH")).strip()
|
|
|
|
userdata = json.loads(USERDATA)
|
|
|
|
print("[INFO] ")
|
2025-09-23 22:49:51 +03:00
|
|
|
except FileNotFoundError:
|
|
|
|
userdata = []
|
2025-09-23 15:53:51 +03:00
|
|
|
|
2025-09-23 17:14:11 +03:00
|
|
|
# Load database
|
2025-09-23 15:53:51 +03:00
|
|
|
conn = ps.connect(
|
|
|
|
dbname=getenv("POSTGRES_DBNAME"),
|
|
|
|
user=getenv("POSTGRES_USER"),
|
|
|
|
host=getenv("POSTGRES_HOST")
|
|
|
|
)
|
|
|
|
|
2025-09-23 17:14:11 +03:00
|
|
|
# Fetch current userdata from database
|
2025-09-23 15:53:51 +03:00
|
|
|
cur = conn.cursor()
|
|
|
|
cur.execute('''
|
|
|
|
SELECT identities.uid, users.id, user_roles.name
|
|
|
|
FROM users
|
2025-09-24 17:05:07 +03:00
|
|
|
JOIN identities
|
|
|
|
ON users.id = identities.id
|
|
|
|
LEFT JOIN user_roles
|
2025-09-23 15:53:51 +03:00
|
|
|
ON users.role_id = user_roles.id;
|
|
|
|
'''
|
|
|
|
)
|
|
|
|
|
|
|
|
state = cur.fetchall()
|
|
|
|
|
2025-09-23 17:14:11 +03:00
|
|
|
users = {}
|
|
|
|
for i in state:
|
|
|
|
users[i[0]] = {
|
|
|
|
"id": i[1],
|
|
|
|
"role": i[2],
|
|
|
|
"isKanidmUser": False
|
|
|
|
}
|
|
|
|
|
|
|
|
# Fetch Kanidm userdata
|
2025-09-23 15:53:51 +03:00
|
|
|
kanidm_users_raw = requests.get(
|
|
|
|
f"{KANIDM_URL}/v1/person",
|
|
|
|
headers={
|
|
|
|
"Authorization": f"Bearer {KANIDM_TOKEN}",
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
},
|
|
|
|
timeout=5,
|
|
|
|
).json()
|
|
|
|
|
2025-09-24 17:05:07 +03:00
|
|
|
def give_role(uid, role, putUserdata = True):
|
|
|
|
if (uid not in userdata) and (putUserdata):
|
|
|
|
userdata.append(uid)
|
|
|
|
users[uid]["isKanidmUser"] = True
|
|
|
|
users[uid]["role"] = role
|
|
|
|
print(f"[INFO] {uid} is marked as {role}")
|
|
|
|
|
|
|
|
|
2025-09-23 15:53:51 +03:00
|
|
|
for i in kanidm_users_raw:
|
|
|
|
i = i["attrs"]
|
2025-09-23 17:14:11 +03:00
|
|
|
for uid in i["name"]: # [user].attrs.name is a list
|
2025-09-23 23:07:05 +03:00
|
|
|
if uid in users: # Don't apply anything for users who have no mastodon access (sp.mastodon.users) or didn't register
|
2025-09-23 23:11:25 +03:00
|
|
|
if uid == OWNER_USERNAME:
|
2025-09-24 17:05:07 +03:00
|
|
|
give_role(uid, "Owner", False)
|
2025-09-23 23:11:25 +03:00
|
|
|
continue
|
|
|
|
|
2025-09-23 22:54:10 +03:00
|
|
|
for group in i["memberof"]:
|
2025-09-23 23:07:05 +03:00
|
|
|
if group.startswith("sp.mastodon.admins@") or group.startswith("sp.admins@"):
|
2025-09-24 17:05:07 +03:00
|
|
|
give_role(uid, "Admin")
|
2025-09-23 17:14:11 +03:00
|
|
|
break
|
2025-09-24 17:05:07 +03:00
|
|
|
|
2025-09-23 23:07:05 +03:00
|
|
|
elif group.startswith("sp.mastodon.moderators@"):
|
2025-09-24 17:05:07 +03:00
|
|
|
give_role(uid, "Moderator")
|
2025-09-23 17:14:11 +03:00
|
|
|
break
|
2025-09-24 17:05:07 +03:00
|
|
|
|
2025-09-23 17:14:11 +03:00
|
|
|
elif uid in userdata:
|
|
|
|
# If user, who previously had a role, has no roles set by Kanidm, delete them from userdata list so allow setting roles directly by mastodon
|
2025-09-24 17:05:07 +03:00
|
|
|
give_role(uid, None, False)
|
2025-09-23 17:14:11 +03:00
|
|
|
userdata.remove(uid)
|
|
|
|
|
|
|
|
print("[DEBUG] ", users) # DEBUG
|
2025-09-23 15:53:51 +03:00
|
|
|
|
2025-09-24 17:52:44 +03:00
|
|
|
# Fetch RoleIDs
|
|
|
|
cur = conn.cursor()
|
|
|
|
cur.execute("SELECT id, name FROM user_roles;")
|
|
|
|
|
|
|
|
roles_raw = cur.fetchall()
|
|
|
|
roles = {}
|
|
|
|
for i in roles_raw:
|
|
|
|
roles[i[1]] = i[0]
|
|
|
|
|
2025-09-25 17:23:58 +03:00
|
|
|
print("[debug]", roles)
|
|
|
|
|
2025-09-24 17:52:44 +03:00
|
|
|
# Give roles
|
2025-09-24 17:05:07 +03:00
|
|
|
for uid in users:
|
|
|
|
if not users[uid]["isKanidmUser"]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if users[uid]["role"]:
|
2025-09-24 17:52:44 +03:00
|
|
|
rolename = users[uid]["role"]
|
|
|
|
roleid = roles[rolename]
|
2025-09-24 17:05:07 +03:00
|
|
|
else:
|
|
|
|
roleid = "NULL"
|
|
|
|
|
2025-09-25 17:14:45 +03:00
|
|
|
sqlcommand = f"UPDATE users SET role_id = {roleid} WHERE id = {users[uid]["id"]};"
|
2025-09-25 22:59:59 +03:00
|
|
|
print("[debug] SQL:", sqlcommand)
|
2025-09-25 17:14:45 +03:00
|
|
|
cur.execute(sqlcommand)
|
2025-09-24 17:05:07 +03:00
|
|
|
|
2025-09-25 22:59:59 +03:00
|
|
|
conn.commit()
|
2025-09-23 15:53:51 +03:00
|
|
|
cur.close()
|
|
|
|
conn.close()
|
2025-09-23 17:14:11 +03:00
|
|
|
|
2025-09-24 17:05:07 +03:00
|
|
|
print("[INFO] Final userdata.json file content: ", userdata)
|
2025-09-23 22:46:49 +03:00
|
|
|
|
|
|
|
def write_userdata(mode):
|
|
|
|
with open(getenv("USERDATA_FILE_PATH"), mode) as f:
|
|
|
|
f.write(json.dumps(userdata))
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
try:
|
|
|
|
write_userdata("w")
|
|
|
|
except FileNotFoundError:
|
2025-09-24 17:05:07 +03:00
|
|
|
print("[INFO] userdata.json file doesn't exist. Creating it")
|
2025-09-23 22:46:49 +03:00
|
|
|
write_userdata("x")
|