use crate::core::Deserializer;
use crate::{
core::PubNubError,
lib::{
alloc::{
format,
string::{String, ToString},
},
collections::HashMap,
},
};
use base64::{engine::general_purpose, Engine};
#[cfg(feature = "serde")]
use ciborium::de::from_reader;
#[cfg(feature = "serde")]
struct CiboriumDeserializer;
#[cfg(feature = "serde")]
impl Deserializer for CiboriumDeserializer {
fn deserialize<Token: for<'de> serde::Deserialize<'de>>(
&self,
bytes: &[u8],
) -> Result<Token, PubNubError> {
from_reader(bytes).map_err(|e| PubNubError::TokenDeserialization {
details: e.to_string(),
})
}
}
#[cfg(feature = "serde")]
pub fn parse_token(token: &str) -> Result<Token, PubNubError> {
parse_token_with(token, CiboriumDeserializer)
}
pub fn parse_token_with<D>(token: &str, deserializer: D) -> Result<Token, PubNubError>
where
D: Deserializer,
{
let token_bytes = general_purpose::URL_SAFE
.decode(format!("{token}{}", "=".repeat(token.len() % 4)).as_bytes())
.map_err(|e| PubNubError::TokenDeserialization {
details: e.to_string(),
})?;
deserializer.deserialize(&token_bytes)
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub enum Token {
V2(TokenV2),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct TokenV2 {
#[cfg_attr(feature = "serde", serde(rename = "v"))]
pub version: u8,
#[cfg_attr(feature = "serde", serde(rename = "t"))]
pub timestamp: u32,
pub ttl: u32,
#[cfg_attr(feature = "serde", serde(rename = "uuid"))]
pub authorized_user_id: Option<String>,
#[cfg_attr(feature = "serde", serde(rename = "res"))]
pub resources: TokenResources,
#[cfg_attr(feature = "serde", serde(rename = "pat"))]
pub patterns: TokenResources,
pub meta: HashMap<String, MetaValue>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct TokenResources {
#[cfg_attr(feature = "serde", serde(rename = "chan"))]
pub channels: HashMap<String, ResourcePermissions>,
#[cfg_attr(feature = "serde", serde(rename = "grp"))]
pub groups: HashMap<String, ResourcePermissions>,
#[cfg_attr(feature = "serde", serde(rename = "uuid"))]
pub users: HashMap<String, ResourcePermissions>,
}
impl From<u8> for ResourcePermissions {
fn from(int: u8) -> Self {
ResourcePermissions {
read: int & 1 != 0,
write: int & 2 != 0,
manage: int & 4 != 0,
delete: int & 8 != 0,
create: int & 16 != 0,
get: int & 32 != 0,
update: int & 64 != 0,
join: int & 128 != 0,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "serde", serde(from = "u8"))]
pub struct ResourcePermissions {
pub read: bool,
pub write: bool,
pub manage: bool,
pub delete: bool,
pub create: bool,
pub get: bool,
pub update: bool,
pub join: bool,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum MetaValue {
String(String),
Integer(i64),
Float(f64),
Bool(bool),
Null,
}
#[cfg(test)]
mod should {
use super::*;
use crate::{
dx::parse_token::MetaValue::{Float, Integer, Null, String},
lib::core::ops::Deref,
};
impl PartialEq for MetaValue {
fn eq(&self, other: &Self) -> bool {
use MetaValue::*;
match (self.clone(), other.clone()) {
(String(v1), String(v2)) => v1.deref() == v2.deref(),
(Integer(v1), Integer(v2)) => v1 == v2,
(Float(v1), Float(v2)) => (v1 - v2).abs() < 0.001,
(Bool(v1), Bool(v2)) => v1 == v2,
(Null, Null) => true,
_ => false,
}
}
}
impl Eq for MetaValue {}
#[test]
fn test_parse_token() {
let base64_token = "qEF2AkF0GmQ1YSpDdHRsGQU5Q3Jlc6VEY2hhbqFvY2hhbm5lbFJlc291cmNlGP9DZ3JwoWxjaGFubmVsR3JvdXABQ3NwY6BDdXNyoER1dWlkoENwYXSlRGNoYW6haWNoYW5uZWwuKgJDZ3JwoW5jaGFubmVsR3JvdXAuKgRDc3BjoEN1c3KgRHV1aWShZnV1aWQuKhhoRG1ldGGkZG1ldGFkZGF0YWdpbnRlZ2VyGQU5ZW90aGVy9mVmbG9hdPtAKr1wo9cKPUR1dWlkZHV1aWRDc2lnWCAbOhXPSWx05l4c3Iuf-SWVOVpLM6xyto3lVPdMKdhJ2A";
let token = parse_token(base64_token).unwrap();
assert_eq!(
Token::V2(TokenV2 {
version: 2,
ttl: 1337,
timestamp: 1681219882,
patterns: TokenResources {
channels: [("channel.*".into(), 2.into())].into(),
groups: [("channelGroup.*".into(), 4.into())].into(),
users: [("uuid.*".into(), 104.into())].into(),
},
resources: TokenResources {
users: [].into(),
groups: [("channelGroup".into(), 1.into())].into(),
channels: [("channelResource".into(), 255.into())].into(),
},
authorized_user_id: Some("uuid".into()),
meta: HashMap::from([
("meta".into(), String("data".into())),
("other".into(), Null),
("integer".into(), Integer(1337)),
("float".into(), Float(13.37))
])
}),
token
);
}
}