n5i 0.8.0

Common components for n5i
Documentation
// SPDX-FileCopyrightText: 2024-2026 The n5i Project
//
// SPDX-License-Identifier: AGPL-3.0-or-later

use serde::{Deserialize, Serialize};
use std::fmt::Debug;

#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[repr(u16)]
#[cfg_attr(feature = "graphql", derive(async_graphql::Enum))]
#[cfg_attr(feature = "graphql", graphql(name = "UserPermission"))]
pub enum Permission {
    ManageApps,
    ManageDomains,
    ManageUsers,
    ManageGroups,
    ManageProxies,
    ManageIssuers,
    ConfigureNetwork,
    ManageSysComponents,
    ManageHardware,
    Escalate,
    VendorReserved, // Free to be used by a distro
    ManageAppStores,
    ManageAppStorage,
    InstallRootApps,
    AddTrustedDomains,
}

#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct PermissionSet(u16);

impl Debug for PermissionSet {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut first = true;
        write!(f, "[")?;
        for perm in self.into_iter() {
            if first {
                first = false;
            } else {
                write!(f, ", ")?;
            }
            write!(f, "{perm:?}")?;
        }
        write!(f, "]")
    }
}

#[inline]
const fn perm_to_uint(value: Permission) -> u16 {
    match value {
        Permission::ManageApps => 0b0000000000000001,
        Permission::ManageDomains => 0b0000000000000010,
        Permission::ManageUsers => 0b0000000000000100,
        Permission::ManageGroups => 0b0000000000001000,
        Permission::ManageProxies => 0b0000000000010000,
        Permission::ManageIssuers => 0b0000000000100000,
        Permission::ConfigureNetwork => 0b0000000001000000,
        Permission::ManageSysComponents => 0b0000000010000000,
        Permission::ManageHardware => 0b0000000100000000,
        Permission::Escalate => 0b0000001000000000,
        Permission::VendorReserved => 0b0000010000000000,
        Permission::ManageAppStores => 0b0000100000000000,
        Permission::ManageAppStorage => 0b0001000000000000,
        Permission::InstallRootApps => 0b0010000000000000,
        Permission::AddTrustedDomains => 0b0100000000000000,
    }
}

impl PermissionSet {
    pub fn add(&mut self, perm: Permission) {
        self.0 |= perm_to_uint(perm);
    }

    pub fn remove(&mut self, perm: Permission) {
        self.0 &= !perm_to_uint(perm);
    }

    pub fn has(&self, perm: Permission) -> bool {
        self.0 & perm_to_uint(perm) != 0
    }

    pub const fn empty() -> Self {
        PermissionSet(0)
    }

    pub const fn from_perms_static(perms: &'static [Permission]) -> Self {
        let mut set: u16 = 0;
        let mut i = 0;
        while i < perms.len() {
            set |= perm_to_uint(perms[i]);
            i += 1;
        }
        PermissionSet(set)
    }

    pub const fn full() -> Self {
        // Right now, the first byte is unused
        PermissionSet(0b0111111111111111)
    }
}

impl From<&[Permission]> for PermissionSet {
    fn from(permissions: &[Permission]) -> Self {
        let mut set = PermissionSet(0);
        for perm in permissions {
            set.add(*perm);
        }
        set
    }
}

impl From<PermissionSet> for u16 {
    fn from(set: PermissionSet) -> u16 {
        set.0
    }
}

impl From<u16> for PermissionSet {
    fn from(value: u16) -> Self {
        PermissionSet(value)
    }
}

pub struct PermissionSetIter {
    value: u16,
    current: u16,
}

impl Iterator for PermissionSetIter {
    type Item = Permission;

    fn next(&mut self) -> Option<Self::Item> {
        while self.current != 0 {
            let bit = self.current.trailing_zeros();
            self.current &= !(1 << bit);
            if self.value & (1 << bit) != 0 {
                return Some(match bit {
                    0 => Permission::ManageApps,
                    1 => Permission::ManageDomains,
                    2 => Permission::ManageUsers,
                    3 => Permission::ManageGroups,
                    4 => Permission::ManageProxies,
                    5 => Permission::ManageIssuers,
                    6 => Permission::ConfigureNetwork,
                    7 => Permission::ManageSysComponents,
                    8 => Permission::ManageHardware,
                    9 => Permission::Escalate,
                    10 => Permission::VendorReserved,
                    11 => Permission::ManageAppStores,
                    12 => Permission::ManageAppStorage,
                    13 => Permission::InstallRootApps,
                    14 => Permission::AddTrustedDomains,
                    _ => return None,
                });
            }
        }
        None
    }
}

impl IntoIterator for PermissionSet {
    type Item = Permission;
    type IntoIter = PermissionSetIter;

    fn into_iter(self) -> Self::IntoIter {
        PermissionSetIter {
            value: self.0,
            current: self.0,
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Permission, PermissionSet};

    #[test]
    fn test_permission_set() {
        let mut set = PermissionSet(0);
        assert!(!set.has(Permission::ManageApps));
        set.add(Permission::ManageApps);
        assert!(set.has(Permission::ManageApps));
        set.add(Permission::ManageGroups);
        assert!(set.has(Permission::ManageApps));
        assert!(set.has(Permission::ManageGroups));
        set.remove(Permission::ManageApps);
        assert!(!set.has(Permission::ManageApps));
        assert!(set.has(Permission::ManageGroups));
    }

    #[test]
    fn test_permission_set_iter() {
        let mut set = PermissionSet(0);
        set.add(Permission::ManageApps);
        set.add(Permission::ManageGroups);
        let mut iter = set.into_iter();
        assert_eq!(iter.next(), Some(Permission::ManageApps));
        assert_eq!(iter.next(), Some(Permission::ManageGroups));
        assert_eq!(iter.next(), None);
    }

    #[test]
    fn test_format_permission_set() {
        let mut set = PermissionSet(0);
        set.add(Permission::ManageApps);
        set.add(Permission::ManageGroups);
        assert_eq!(format!("{set:?}"), "[ManageApps, ManageGroups]");
    }

    #[test]
    fn test_format_permission_set_fixed_order() {
        let mut set = PermissionSet(0);
        set.add(Permission::ManageGroups);
        set.add(Permission::ManageApps);
        assert_eq!(format!("{set:?}"), "[ManageApps, ManageGroups]");
    }
}