affine_core 0.0.2

AFFiNE primitive core.
Documentation
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WorkspaceDatabaseTable {
  Folders,
  DocProperties,
  DocCustomPropertyInfo,
  PinnedCollections,
  ExplorerIcon,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UserdataTable {
  Favorite,
  Settings,
  DocIntegrationRef,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReservedDocumentClassification<'a> {
  Ordinary,
  WorkspaceDatabase { table: WorkspaceDatabaseTable },
  Userdata { owner_id: &'a str, table: UserdataTable },
  InvalidReserved,
}

impl ReservedDocumentClassification<'_> {
  pub fn is_valid_reserved(self) -> bool {
    matches!(self, Self::WorkspaceDatabase { .. } | Self::Userdata { .. })
  }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReservedDocumentAccessDecision {
  NotApplicable,
  Allowed,
  Denied,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum ReservedDocumentError {
  #[error("invalid userdata owner")]
  InvalidOwner,
  #[error("invalid workspace")]
  InvalidWorkspace,
}

pub fn classify_reserved_document<'a>(workspace_id: &str, doc_id: &'a str) -> ReservedDocumentClassification<'a> {
  if let Some(rest) = doc_id.strip_prefix("db$") {
    let mut parts = rest.split('$');
    let (Some(encoded_workspace_id), Some(table), None) = (parts.next(), parts.next(), parts.next()) else {
      return ReservedDocumentClassification::InvalidReserved;
    };
    if workspace_id.is_empty() || encoded_workspace_id != workspace_id {
      return ReservedDocumentClassification::InvalidReserved;
    }
    return parse_workspace_table(table).map_or(ReservedDocumentClassification::InvalidReserved, |table| {
      ReservedDocumentClassification::WorkspaceDatabase { table }
    });
  }

  if let Some(rest) = doc_id.strip_prefix("userdata$") {
    let mut parts = rest.split('$');
    let (Some(owner_id), Some(encoded_workspace_id), Some(table), None) =
      (parts.next(), parts.next(), parts.next(), parts.next())
    else {
      return ReservedDocumentClassification::InvalidReserved;
    };
    if !valid_owner(owner_id) || workspace_id.is_empty() || encoded_workspace_id != workspace_id {
      return ReservedDocumentClassification::InvalidReserved;
    }
    return parse_userdata_table(table).map_or(ReservedDocumentClassification::InvalidReserved, |table| {
      ReservedDocumentClassification::Userdata { owner_id, table }
    });
  }

  ReservedDocumentClassification::Ordinary
}

pub fn authorize_reserved_document(
  actor_user_id: &str,
  classification: ReservedDocumentClassification<'_>,
) -> ReservedDocumentAccessDecision {
  match classification {
    ReservedDocumentClassification::Ordinary => ReservedDocumentAccessDecision::NotApplicable,
    ReservedDocumentClassification::WorkspaceDatabase { .. } => ReservedDocumentAccessDecision::Allowed,
    ReservedDocumentClassification::Userdata { owner_id, .. } if owner_id == actor_user_id => {
      ReservedDocumentAccessDecision::Allowed
    }
    ReservedDocumentClassification::Userdata { .. } | ReservedDocumentClassification::InvalidReserved => {
      ReservedDocumentAccessDecision::Denied
    }
  }
}

pub fn userdata_document_id(
  user_id: &str,
  workspace_id: &str,
  table: UserdataTable,
) -> Result<String, ReservedDocumentError> {
  if !valid_owner(user_id) {
    return Err(ReservedDocumentError::InvalidOwner);
  }
  if workspace_id.is_empty() || workspace_id.contains('$') {
    return Err(ReservedDocumentError::InvalidWorkspace);
  }
  Ok(format!(
    "userdata${user_id}${workspace_id}${}",
    userdata_table_name(table)
  ))
}

fn valid_owner(owner_id: &str) -> bool {
  !owner_id.is_empty() && owner_id != "__local__" && !owner_id.contains('$')
}

fn parse_workspace_table(table: &str) -> Option<WorkspaceDatabaseTable> {
  match table {
    "folders" => Some(WorkspaceDatabaseTable::Folders),
    "docProperties" => Some(WorkspaceDatabaseTable::DocProperties),
    "docCustomPropertyInfo" => Some(WorkspaceDatabaseTable::DocCustomPropertyInfo),
    "pinnedCollections" => Some(WorkspaceDatabaseTable::PinnedCollections),
    "explorerIcon" => Some(WorkspaceDatabaseTable::ExplorerIcon),
    _ => None,
  }
}

fn parse_userdata_table(table: &str) -> Option<UserdataTable> {
  match table {
    "favorite" => Some(UserdataTable::Favorite),
    "settings" => Some(UserdataTable::Settings),
    "docIntegrationRef" => Some(UserdataTable::DocIntegrationRef),
    _ => None,
  }
}

fn userdata_table_name(table: UserdataTable) -> &'static str {
  match table {
    UserdataTable::Favorite => "favorite",
    UserdataTable::Settings => "settings",
    UserdataTable::DocIntegrationRef => "docIntegrationRef",
  }
}

#[cfg(test)]
#[path = "../tests/access_control/reserved_document/tests.rs"]
mod tests;