use super::ContentType;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsePermissionActionError {
pub input: String,
}
impl std::fmt::Display for ParsePermissionActionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid permission action: '{}'", self.input)
}
}
impl std::error::Error for ParsePermissionActionError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PermissionAction {
View,
Add,
Change,
Delete,
}
impl PermissionAction {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::View => "view",
Self::Add => "add",
Self::Change => "change",
Self::Delete => "delete",
}
}
#[must_use]
pub const fn all() -> [Self; 4] {
[Self::View, Self::Add, Self::Change, Self::Delete]
}
}
impl FromStr for PermissionAction {
type Err = ParsePermissionActionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"view" => Ok(Self::View),
"add" => Ok(Self::Add),
"change" => Ok(Self::Change),
"delete" => Ok(Self::Delete),
_ => Err(ParsePermissionActionError {
input: s.to_string(),
}),
}
}
}
impl std::fmt::Display for PermissionAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub struct ContentTypePermission;
impl ContentTypePermission {
#[must_use]
pub fn format(ct: &ContentType, action: PermissionAction) -> String {
format!("{}.{}_{}", ct.app_label, action.as_str(), ct.model)
}
#[must_use]
pub fn format_custom(ct: &ContentType, action: &str) -> String {
format!("{}.{}_{}", ct.app_label, action, ct.model)
}
#[must_use]
pub fn parse(permission: &str) -> Option<(String, String, String)> {
let dot_pos = permission.find('.')?;
let app_label = &permission[..dot_pos];
let action_model = &permission[dot_pos + 1..];
let underscore_pos = action_model.find('_')?;
let action = &action_model[..underscore_pos];
let model = &action_model[underscore_pos + 1..];
if app_label.is_empty() || action.is_empty() || model.is_empty() {
return None;
}
Some((app_label.to_string(), action.to_string(), model.to_string()))
}
#[must_use]
pub fn default_permissions(ct: &ContentType) -> Vec<String> {
PermissionAction::all()
.iter()
.map(|action| Self::format(ct, *action))
.collect()
}
#[must_use]
pub fn matches(permission: &str, ct: &ContentType, action: PermissionAction) -> bool {
permission == Self::format(ct, action)
}
#[must_use]
pub fn extract_content_type(permission: &str) -> Option<ContentType> {
let (app_label, _, model) = Self::parse(permission)?;
Some(ContentType::new(&app_label, &model))
}
#[must_use]
pub fn extract_action(permission: &str) -> Option<PermissionAction> {
let (_, action, _) = Self::parse(permission)?;
action.parse().ok()
}
}
#[derive(Debug, Clone, Default)]
pub struct PermissionContext<'a> {
pub username: Option<&'a str>,
pub is_authenticated: bool,
pub is_staff: bool,
pub is_superuser: bool,
}
impl<'a> PermissionContext<'a> {
#[must_use]
pub fn authenticated(username: &'a str) -> Self {
Self {
username: Some(username),
is_authenticated: true,
is_staff: false,
is_superuser: false,
}
}
#[must_use]
pub fn staff(username: &'a str) -> Self {
Self {
username: Some(username),
is_authenticated: true,
is_staff: true,
is_superuser: false,
}
}
#[must_use]
pub fn superuser(username: &'a str) -> Self {
Self {
username: Some(username),
is_authenticated: true,
is_staff: true,
is_superuser: true,
}
}
#[must_use]
pub fn anonymous() -> Self {
Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permission_action_as_str() {
assert_eq!(PermissionAction::View.as_str(), "view");
assert_eq!(PermissionAction::Add.as_str(), "add");
assert_eq!(PermissionAction::Change.as_str(), "change");
assert_eq!(PermissionAction::Delete.as_str(), "delete");
}
#[test]
fn test_permission_action_all() {
let actions = PermissionAction::all();
assert_eq!(actions.len(), 4);
assert!(actions.contains(&PermissionAction::View));
assert!(actions.contains(&PermissionAction::Add));
assert!(actions.contains(&PermissionAction::Change));
assert!(actions.contains(&PermissionAction::Delete));
}
#[test]
fn test_permission_action_from_str() {
assert_eq!(
PermissionAction::from_str("view"),
Ok(PermissionAction::View)
);
assert_eq!(
PermissionAction::from_str("VIEW"),
Ok(PermissionAction::View)
);
assert_eq!(PermissionAction::from_str("add"), Ok(PermissionAction::Add));
assert_eq!(
PermissionAction::from_str("change"),
Ok(PermissionAction::Change)
);
assert_eq!(
PermissionAction::from_str("delete"),
Ok(PermissionAction::Delete)
);
assert!(PermissionAction::from_str("unknown").is_err());
assert_eq!(
"view".parse::<PermissionAction>(),
Ok(PermissionAction::View)
);
assert!("invalid".parse::<PermissionAction>().is_err());
}
#[test]
fn test_permission_action_display() {
assert_eq!(format!("{}", PermissionAction::View), "view");
assert_eq!(format!("{}", PermissionAction::Add), "add");
}
#[test]
fn test_format_permission() {
let ct = ContentType::new("blog", "article");
assert_eq!(
ContentTypePermission::format(&ct, PermissionAction::View),
"blog.view_article"
);
assert_eq!(
ContentTypePermission::format(&ct, PermissionAction::Add),
"blog.add_article"
);
assert_eq!(
ContentTypePermission::format(&ct, PermissionAction::Change),
"blog.change_article"
);
assert_eq!(
ContentTypePermission::format(&ct, PermissionAction::Delete),
"blog.delete_article"
);
}
#[test]
fn test_format_custom_permission() {
let ct = ContentType::new("blog", "article");
assert_eq!(
ContentTypePermission::format_custom(&ct, "publish"),
"blog.publish_article"
);
assert_eq!(
ContentTypePermission::format_custom(&ct, "archive"),
"blog.archive_article"
);
}
#[test]
fn test_parse_permission() {
let parsed = ContentTypePermission::parse("blog.add_article");
assert_eq!(
parsed,
Some(("blog".to_string(), "add".to_string(), "article".to_string()))
);
let parsed = ContentTypePermission::parse("auth.view_user");
assert_eq!(
parsed,
Some(("auth".to_string(), "view".to_string(), "user".to_string()))
);
assert_eq!(ContentTypePermission::parse("invalid"), None);
assert_eq!(ContentTypePermission::parse("app.invalid"), None);
assert_eq!(ContentTypePermission::parse(".add_model"), None);
assert_eq!(ContentTypePermission::parse("app._model"), None);
assert_eq!(ContentTypePermission::parse("app.action_"), None);
}
#[test]
fn test_default_permissions() {
let ct = ContentType::new("blog", "article");
let perms = ContentTypePermission::default_permissions(&ct);
assert_eq!(perms.len(), 4);
assert!(perms.contains(&"blog.view_article".to_string()));
assert!(perms.contains(&"blog.add_article".to_string()));
assert!(perms.contains(&"blog.change_article".to_string()));
assert!(perms.contains(&"blog.delete_article".to_string()));
}
#[test]
fn test_matches_permission() {
let ct = ContentType::new("blog", "article");
assert!(ContentTypePermission::matches(
"blog.add_article",
&ct,
PermissionAction::Add
));
assert!(ContentTypePermission::matches(
"blog.view_article",
&ct,
PermissionAction::View
));
assert!(!ContentTypePermission::matches(
"blog.view_article",
&ct,
PermissionAction::Add
));
assert!(!ContentTypePermission::matches(
"auth.add_user",
&ct,
PermissionAction::Add
));
}
#[test]
fn test_extract_content_type() {
let ct = ContentTypePermission::extract_content_type("blog.add_article");
assert!(ct.is_some());
let ct = ct.unwrap();
assert_eq!(ct.app_label, "blog");
assert_eq!(ct.model, "article");
assert!(ContentTypePermission::extract_content_type("invalid").is_none());
}
#[test]
fn test_extract_action() {
assert_eq!(
ContentTypePermission::extract_action("blog.view_article"),
Some(PermissionAction::View)
);
assert_eq!(
ContentTypePermission::extract_action("blog.add_article"),
Some(PermissionAction::Add)
);
assert_eq!(
ContentTypePermission::extract_action("blog.change_article"),
Some(PermissionAction::Change)
);
assert_eq!(
ContentTypePermission::extract_action("blog.delete_article"),
Some(PermissionAction::Delete)
);
assert_eq!(
ContentTypePermission::extract_action("blog.custom_article"),
None
);
}
#[test]
fn test_permission_context_authenticated() {
let ctx = PermissionContext::authenticated("alice");
assert_eq!(ctx.username, Some("alice"));
assert!(ctx.is_authenticated);
assert!(!ctx.is_staff);
assert!(!ctx.is_superuser);
}
#[test]
fn test_permission_context_staff() {
let ctx = PermissionContext::staff("bob");
assert_eq!(ctx.username, Some("bob"));
assert!(ctx.is_authenticated);
assert!(ctx.is_staff);
assert!(!ctx.is_superuser);
}
#[test]
fn test_permission_context_superuser() {
let ctx = PermissionContext::superuser("admin");
assert_eq!(ctx.username, Some("admin"));
assert!(ctx.is_authenticated);
assert!(ctx.is_staff);
assert!(ctx.is_superuser);
}
#[test]
fn test_permission_context_anonymous() {
let ctx = PermissionContext::anonymous();
assert_eq!(ctx.username, None);
assert!(!ctx.is_authenticated);
assert!(!ctx.is_staff);
assert!(!ctx.is_superuser);
}
#[test]
fn test_permission_context_default() {
let ctx = PermissionContext::default();
assert_eq!(ctx.username, None);
assert!(!ctx.is_authenticated);
}
}