You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
6.5 KiB

//! Information about users, perms, etc
use super::*;
use std::collections::HashMap;
use std::borrow::Cow;
use bitflags::bitflags;
/// The ID type used for backing user/group IDs
type GenericID = uuid::Uuid;
/// A user group ID
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct GroupID(GenericID);
/// A unique user ID
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct UserID(GenericID);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct User
{
id: UserID,
name: String,
hidden: bool,
desc: Option<String>,
comment: Option<String>,
/// Corresponds to a Unix `superuser`.
///
/// All permissions checks are bypassed for a user with this set.
is_god: bool,
groups: Vec<GroupID>,
}
/// A group is a way of setting permissions for a whole set of users.
///
/// Users have groups, not the other way around.
/// A user can have multiple groups or none.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Group
{
id: GroupID,
hidden: bool,
/// Corresponds to a Unix `superuser`.
///
/// All permissions checks are bypassed for a user in a group with this set.
is_god: bool,
name: String,
desc: Option<String>,
comment: Option<String>,
inherits: Option<Vec<GroupID>>,
}
impl Group
{
/*
//TODO: Create bespoke iterator type for this, it's not possible here.
pub fn inherits<'b, 'a>(&'b self, space: &'a Userspace) -> impl Iterator<Item = GroupID> +'a
where 'b: 'a
{
match &self.inherits {
Some(inherits) => inherits.iter()
.filter_map(|group| group.find_in_space(space).map(|parent| parent.inherits(space))).flatten(),
None => todo!()
}
}*/
}
pub trait AsEntityId
{
fn entity_id(&self) -> Cow<'_, EntityID>;
}
impl AsEntityId for EntityID
{
#[inline(always)] fn entity_id(&self) -> Cow<'_, EntityID> {
Cow::Borrowed(self)
}
}
impl AsEntityId for UserID
{
fn entity_id(&self) -> Cow<'_, EntityID> {
Cow::Owned(EntityID::User(self.clone()))
}
}
impl AsEntityId for GroupID
{
fn entity_id(&self) -> Cow<'_, EntityID> {
Cow::Owned(EntityID::Group(self.clone()))
}
}
impl<'a, T> AsEntityId for &'a T
where T: AsEntityId
{
#[inline(always)] fn entity_id(&self) -> Cow<'_, EntityID> {
T::entity_id(self)
}
}
/// A trait for items that can have permissions bits set for them.
/// Users and groups implement this.
pub trait Entity
{
fn generic_id(&self) -> &GenericID;
fn name(&self) -> &str;
fn desc(&self) -> Option<&str>;
fn comment(&self) -> Option<&str>;
fn superuser(&self) -> bool;
/// # Note
/// This does not flatten inherited groups, that needs to be performed later
fn groups(&self) -> &[GroupID];
}
impl Entity for User
{
#[inline] fn generic_id(&self) -> &GenericID
{
&self.id.0
}
#[inline] fn name(&self) -> &str
{
&self.name[..]
}
#[inline] fn desc(&self) -> Option<&str>
{
self.desc.as_ref().map(String::as_str)
}
#[inline] fn comment(&self) -> Option<&str>
{
self.comment.as_ref().map(String::as_str)
}
#[inline] fn superuser(&self) -> bool
{
self.is_god
}
/// # Note
/// This does not flatten inherited groups, that needs to be performed later
#[inline] fn groups(&self) -> &[GroupID]
{
&self.groups[..]
}
}
impl Entity for Group
{
#[inline] fn generic_id(&self) -> &GenericID
{
&self.id.0
}
#[inline] fn name(&self) -> &str
{
&self.name[..]
}
#[inline] fn desc(&self) -> Option<&str>
{
self.desc.as_ref().map(String::as_str)
}
#[inline] fn comment(&self) -> Option<&str>
{
self.comment.as_ref().map(String::as_str)
}
#[inline] fn superuser(&self) -> bool
{
self.is_god
}
/// # Note
/// This does not flatten inherited groups, that needs to be performed later
#[inline] fn groups(&self) -> &[GroupID]
{
self.inherits.as_ref().map(Vec::as_slice).unwrap_or(&[])
}
}
/// Either a single user or a group of users
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum EntityID
{
User(UserID),
Group(GroupID),
}
impl UserID
{
/// Attempt to find this user in this userspace
pub fn find_in_space<'a>(&self, space: &'a Userspace) -> Option<&'a User>
{
space.users.get(self)
}
/// Attempt to find a mutable reference to this user in this userspace
pub fn find_in_space_mut<'a>(&self, space: &'a mut Userspace) -> Option<&'a mut User>
{
space.users.get_mut(self)
}
}
impl GroupID
{
/// Attempt to find this group in this userspace
pub fn find_in_space<'a>(&self, space: &'a Userspace) -> Option<&'a Group>
{
space.groups.get(self)
}
/// Attempt to find a mutable reference to this group in this userspace
pub fn find_in_space_mut<'a>(&self, space: &'a mut Userspace) -> Option<&'a mut Group>
{
space.groups.get_mut(self)
}
}
impl EntityID
{
/// Attempt to find the entity that implements this ID in this space
pub fn find_in_space<'a>(&self, space: &'a Userspace) -> Option<&'a (dyn Entity + 'static)>
{
match self {
Self::User(id) => space.users.get(id).map(|x| x as &dyn Entity),
Self::Group(id) => space.groups.get(id).map(|x| x as &dyn Entity),
}
}
}
bitflags! {
/// A permission a user or group has for a data item.
#[derive(Serialize, Deserialize)]
struct Permission: u16 {
/// Cannot read or write the item.
///
/// However they can see it, unless it is marked "hidden".
const NONE = 0;
/// Can read from the item but cannot write to it
const READ = 1;
/// Can write to the item
///
/// This does not imply `READ`.
const WRITE = 2;
/// Full access
const FULL = Self::READ.bits | Self::WRITE.bits;
}
}
/// A set of permissions informations for users and/or groups
#[derive(Debug, Serialize, Deserialize)]
pub struct Permissions
{
blanket: Permission,
spec: smallmap::Map<EntityID, Permission>,
}
/// Contains all users and groups
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Userspace
{
// use maps here for easier lookup, despite duplicating ID data
users: HashMap<UserID, User>,
groups: HashMap<GroupID, Group>,
}
impl Userspace
{
/// Is this `User` or `Group` ID present in this space?
pub fn contains_id(&self, ent: &impl AsEntityId) -> bool
{
ent.entity_id().find_in_space(self).is_some()
}
}