1use axum::{extract::FromRequestParts, http::request::Parts};
4use serde::{Deserialize, Serialize, de::DeserializeOwned};
5use std::{borrow::Cow, fmt::Debug, marker::PhantomData};
6
7use crate::{Site, auth::{AuthError, AuthUser}};
8
9pub use uxar_macros::BitRole;
11
12pub type RoleType = u64;
13
14pub trait BitRole: Sized + Debug + Copy + Clone + 'static{
15
16 fn role_value(self) -> u8;
20
21 fn role_pairs() -> &'static [(u8, &'static str)];
23
24 #[inline]
25 fn role_name(self) -> Option<&'static str> {
26 for (val, name) in Self::role_pairs() {
27 if *val == self.role_value() {
28 return Some(name);
29 }
30 }
31 None
32 }
33
34 #[inline]
35 fn to_role_type(self) -> RoleType {
36 (1 as RoleType)
37 .checked_shl(self.role_value() as u32)
38 .unwrap_or(0)
39 }
40}
41
42
43pub fn format_roles<R: BitRole>(mask: RoleType) -> Vec<String> {
44 let mut roles = Vec::new();
45 for (val, name) in R::role_pairs() {
46 let Some(role_bit) = (1 as RoleType).checked_shl(*val as u32) else {
47 continue;
48 };
49 if mask & role_bit != 0 {
50 roles.push(name.to_string());
51 }
52 }
53 roles
54}
55
56
57#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
58pub struct PermitAny;
59
60#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
61pub struct PermitAll;
62
63pub struct Permit<const MASK: RoleType, R: BitRole, O = PermitAny>(
64 pub AuthUser,
65 pub PhantomData<R>,
66 pub PhantomData<O>,
67);
68
69impl<const MASK: RoleType, R: BitRole> Permit<MASK, R> {
70 pub fn into_user(self) -> AuthUser {
71 self.0
72 }
73}
74
75pub trait HasPerm {
76 fn has_permission(role_mask: RoleType, perm_mask: RoleType) -> bool;
77
78 fn join_all() -> bool {
79 false
80 }
81}
82
83impl HasPerm for PermitAny {
84 fn has_permission(role_mask: RoleType, perm_mask: RoleType) -> bool {
85 role_mask & perm_mask != 0
86 }
87}
88
89impl HasPerm for PermitAll {
90 fn has_permission(role_mask: RoleType, perm_mask: RoleType) -> bool {
91 role_mask & perm_mask == perm_mask
92 }
93
94 fn join_all() -> bool {
95 true
96 }
97}
98
99impl<const MASK: RoleType, R: BitRole, O: HasPerm> FromRequestParts<Site> for Permit<MASK, R, O> {
100 type Rejection = AuthError;
101
102 async fn from_request_parts(parts: &mut Parts, site: &Site) -> Result<Self, Self::Rejection> {
103 let user = AuthUser::from_request_parts(parts, site).await?;
104 if !O::has_permission(user.roles, MASK) {
105 return Err(AuthError::Forbidden);
106 }
107 Ok(Permit(user, PhantomData, PhantomData))
108 }
109}
110
111impl<const MASK: RoleType, R: BitRole, O: HasPerm> crate::callables::IntoArgPart for Permit<MASK, R, O> {
112 fn into_arg_part() -> crate::callables::ArgPart {
113 let scopes = R::role_pairs()
114 .iter()
115 .filter_map(|(bit, name)| {
116 let role_bit = (1 as RoleType).checked_shl(*bit as u32)?;
117 (MASK & role_bit != 0).then(|| Cow::Borrowed(*name))
118 })
119 .collect();
120 crate::callables::ArgPart::Security {
121 scheme: Cow::Borrowed("bearerAuth"),
122 scopes,
123 join_all: O::join_all(),
124 }
125 }
126}
127
128
129
130#[macro_export]
131macro_rules! permit {
132 (@mask $role_ty:ty, $role:ident) => {
134 (<$role_ty>::__uxar_mask(<$role_ty>::$role))
135 };
136
137 ($role_ty:ty, $role:ident $(,)?) => {
139 $crate::auth::Permit::<{
140 $crate::permit!(@mask $role_ty, $role)
141 }, $role_ty, $crate::auth::PermitAny>
142 };
143
144 ($role_ty:ty, $first:ident $( & $rest:ident )+ $(,)?) => {
146 $crate::auth::Permit::<{
147 $crate::permit!(@mask $role_ty, $first)
148 $( | $crate::permit!(@mask $role_ty, $rest) )+
149 }, $role_ty, $crate::auth::PermitAll>
150 };
151
152 ($role_ty:ty, $first:ident $( | $rest:ident )+ $(,)?) => {
154 $crate::auth::Permit::<{
155 $crate::permit!(@mask $role_ty, $first)
156 $( | $crate::permit!(@mask $role_ty, $rest) )+
157 }, $role_ty, $crate::auth::PermitAny>
158 };
159}