HEX
Server: Apache/2.4.63 (Unix)
System: Linux Synopilou92 4.4.302+ #72806 SMP Mon Jul 21 23:16:00 CST 2025 x86_64
User: pilou92 (1026)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: /volume1/@appstore/SynologyPhotos/migration/python/dump_photo_share_permission.py
#!/usr/bin/env python3

from collections import namedtuple
import json
import sys

import pg8000

CONFIG_KEYS = ["allow_user_download", "allow_guest_download"]
USER_PERMISSION_KEYS = ["access", "upload", "manage"]
USER_PERMISSION_TABLE_TEMPLATE = "photo_{}_right_for_dsm_account"
Config = namedtuple("Config", CONFIG_KEYS, defaults=[False] * len(CONFIG_KEYS))


def init_cursor():
    user = "postgres"
    unix_sock = "/var/run/postgresql/.s.PGSQL.5432"
    database = "photo"
    conn = pg8000.connect(user, unix_sock=unix_sock, database=database)

    cursor = conn.cursor()
    cursor.execute("SET CLIENT_ENCODING TO 'UTF8'")

    return cursor


def get_config(cursor):
    cursor.execute(
        """
        SELECT config_key, config_value
        FROM photo_config
        WHERE config_key IN (%s)
    """
        % ",".join(["%s"] * len(CONFIG_KEYS)),
        tuple(CONFIG_KEYS),
    )

    d = {}
    for key, value in cursor:
        d[key] = True if value == "on" else False

    return Config(**d)


def get_user_permission_by_table(cursor, table):
    cursor.execute(
        """
        SELECT userid, shareid
        FROM {}
    """.format(
            table
        )
    )

    share_map = {}
    for uid, id_share in cursor:
        share_map.setdefault(id_share, []).append(uid)
    return share_map


def get_group_permission(cursor):
    cursor.execute(
        """
        SELECT groupid, shareid, permission
        FROM photo_group_permission_for_dsm_account
    """
    )

    share_map = {}
    for gid, id_share, permission in cursor:
        role = ""
        if permission & 4 > 0:
            role = "manage"
        elif permission & 2 > 0:
            role = "upload"
        elif permission & 1 > 0:
            role = "access"

        if role:
            share_map.setdefault(id_share, {})[gid] = role

    return share_map


def get_user_permission(cursor):
    share_map = {}

    for key in USER_PERMISSION_KEYS:
        table = USER_PERMISSION_TABLE_TEMPLATE.format(key)
        permissions = get_user_permission_by_table(cursor, table)

        for id_share, uids in permissions.items():
            for uid in uids:
                share_map.setdefault(id_share, {})[uid] = key

    return share_map


def get_permission_map(cursor):
    config = get_config(cursor)

    user_share_map = get_user_permission(cursor)

    group_share_map = get_group_permission(cursor)

    access_role = "view"
    if config.allow_user_download:
        access_role = "download"

    role_map = {
        "manage": "manage",
        "upload": "upload",
        "access": access_role,
    }

    permission_map = {}

    for id_share in user_share_map:
        for uid, role in user_share_map[id_share].items():
            permission_map.setdefault(id_share, []).append(
                {
                    "member": {
                        "type": "user",
                        "id": uid,
                    },
                    "role": role_map[role],
                }
            )

    for id_share in group_share_map:
        for gid, role in group_share_map[id_share].items():
            permission_map.setdefault(id_share, []).append(
                {
                    "member": {
                        "type": "group",
                        "id": gid,
                    },
                    "role": role_map[role],
                }
            )

    return permission_map


def get_shares(cursor):
    config = get_config(cursor)
    public_role = "view"
    if config.allow_guest_download:
        public_role = "download"
    # there is no public-upload in PhotoStation

    permission_map = get_permission_map(cursor)

    cursor.execute(
        """
        SELECT shareid, sharename, public, password
        FROM photo_share
        WHERE sharename NOT LIKE '%/%/%'
        """
    )

    folders = []
    for id_share, name, is_public, md5ed_password in cursor:
        # print(id_share, name, is_public, md5ed_password, is_subdir)

        normalized_name = "/{}".format(name) if name != "/" else name

        folder_type = "private"
        if is_public:
            folder_type = "public"
        if len(md5ed_password) > 0:
            folder_type = "password"

        permission = permission_map.get(id_share, [])
        if folder_type != "private":
            permission.append(
                {
                    "member": {
                        "type": "public",
                    },
                    "role": public_role,
                }
            )
        folder = {
            "shareid": id_share,
            "sharename": name,
            "name": normalized_name,
            "type": folder_type,
            "permission": permission,
        }
        if folder_type != "private":
            folder["public_role"] = public_role
        if folder_type == "password":
            folder["md5ed_password"] = md5ed_password
        folders.append(folder)

    return folders


def get_users(cursor):
    users = set()
    for key in USER_PERMISSION_KEYS:
        table = USER_PERMISSION_TABLE_TEMPLATE.format(key)

        cursor.execute(
            """
            SELECT DISTINCT(userid)
            FROM {}
        """.format(
                table
            )
        )

        for [uid] in cursor:
            users.add(uid)

    # convert back to list to be JSON serializable
    return list(users)


def get_groups(cursor):
    cursor.execute(
        """
        SELECT DISTINCT(groupid)
        FROM photo_group_permission_for_dsm_account
    """
    )

    groups = set()
    for [gid] in cursor:
        groups.add(gid)

    # convert back to list to be JSON serializable
    return list(groups)


def main():
    cursor = init_cursor()

    result = {}
    result["shares"] = get_shares(cursor)
    result["users"] = get_users(cursor)
    result["groups"] = get_groups(cursor)

    return result


if __name__ == "__main__":
    result = main()

    if len(sys.argv) >= 2:
        path = sys.argv[1]
        json.dump(result, open(path, "w"))
    else:
        print(json.dumps(result))