permission.rs (8529B)
1 //! Canonical NIP-46 permissions and bounded permission sets. 2 3 use crate::error::RadrootsNostrConnectError; 4 use crate::method::Method; 5 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 6 use std::fmt; 7 use std::str::FromStr; 8 9 /// Maximum UTF-8 byte length of one permission parameter. 10 pub const PERMISSION_PARAMETER_MAX_BYTES: usize = 64; 11 /// Maximum number of permissions accepted from one wire value. 12 pub const PERMISSION_COUNT_MAX: usize = 64; 13 /// Maximum UTF-8 byte length of the comma-separated permission wire value. 14 pub const PERMISSIONS_MAX_BYTES: usize = 4_096; 15 16 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 pub struct Permission { 18 #[doc(hidden)] 19 pub method: Method, 20 #[doc(hidden)] 21 pub parameter: Option<String>, 22 } 23 24 impl Permission { 25 #[must_use] 26 pub fn new(method: Method) -> Self { 27 Self { 28 method, 29 parameter: None, 30 } 31 } 32 33 /// Creates a permission with a bounded canonical parameter. 34 pub fn try_with_parameter( 35 method: Method, 36 parameter: impl Into<String>, 37 ) -> Result<Self, RadrootsNostrConnectError> { 38 let parameter = parameter.into(); 39 validate_parameter(¶meter)?; 40 Ok(Self { 41 method, 42 parameter: Some(parameter), 43 }) 44 } 45 46 /// Compatibility constructor retained until the Step 141 consumer cutover. 47 #[doc(hidden)] 48 #[must_use] 49 pub fn with_parameter(method: Method, parameter: impl Into<String>) -> Self { 50 Self { 51 method, 52 parameter: Some(parameter.into()), 53 } 54 } 55 56 /// Returns the permission method. 57 #[must_use] 58 pub fn method(&self) -> &Method { 59 &self.method 60 } 61 62 /// Returns the optional method-specific parameter. 63 #[must_use] 64 pub fn parameter(&self) -> Option<&str> { 65 self.parameter.as_deref() 66 } 67 68 pub fn matches_request(&self, method: &Method, parameter: Option<&str>) -> bool { 69 if self.method != *method { 70 return false; 71 } 72 match (&self.method, self.parameter.as_deref(), parameter) { 73 (Method::SignEvent, None, _) => true, 74 (Method::SignEvent, Some(configured), Some(requested)) => { 75 match ( 76 sign_event_kind_parameter(configured), 77 sign_event_kind_parameter(requested), 78 ) { 79 (Some(configured), Some(requested)) => configured == requested, 80 _ => false, 81 } 82 } 83 (_, None, None) => true, 84 (_, Some(configured), Some(requested)) => configured == requested, 85 _ => false, 86 } 87 } 88 89 pub fn matches_sign_event_kind(&self, event_kind: u32) -> bool { 90 let event_kind = event_kind.to_string(); 91 self.matches_request(&Method::SignEvent, Some(event_kind.as_str())) 92 } 93 } 94 95 impl fmt::Display for Permission { 96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 97 match self.parameter.as_deref() { 98 Some(parameter) => write!(f, "{}:{parameter}", self.method), 99 None => write!(f, "{}", self.method), 100 } 101 } 102 } 103 104 impl FromStr for Permission { 105 type Err = RadrootsNostrConnectError; 106 107 fn from_str(value: &str) -> Result<Self, Self::Err> { 108 let trimmed = value.trim(); 109 if trimmed.is_empty() { 110 return Err(RadrootsNostrConnectError::InvalidPermission( 111 value.to_owned(), 112 )); 113 } 114 115 let (method, parameter) = match trimmed.split_once(':') { 116 Some((method, parameter)) if !parameter.is_empty() => (method, Some(parameter)), 117 Some(_) => { 118 return Err(RadrootsNostrConnectError::InvalidPermission( 119 value.to_owned(), 120 )); 121 } 122 None => (trimmed, None), 123 }; 124 125 let method = Method::from_str(method)?; 126 match parameter { 127 Some(parameter) => Self::try_with_parameter(method, parameter), 128 None => Ok(Self::new(method)), 129 } 130 } 131 } 132 133 #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 134 pub struct Permissions(Vec<Permission>); 135 136 impl Permissions { 137 pub fn new() -> Self { 138 Self::default() 139 } 140 141 pub fn as_slice(&self) -> &[Permission] { 142 self.0.as_slice() 143 } 144 145 pub fn into_vec(self) -> Vec<Permission> { 146 self.0 147 } 148 149 pub fn is_empty(&self) -> bool { 150 self.0.is_empty() 151 } 152 153 /// Validates and canonicalizes a permission collection. 154 pub fn try_from_vec(value: Vec<Permission>) -> Result<Self, RadrootsNostrConnectError> { 155 let value = canonicalize(value); 156 validate_permissions(&value)?; 157 Ok(Self(value)) 158 } 159 160 pub fn allows_request(&self, method: &Method, parameter: Option<&str>) -> bool { 161 self.0 162 .iter() 163 .any(|permission| permission.matches_request(method, parameter)) 164 } 165 166 pub fn allows_sign_event_kind(&self, event_kind: u32) -> bool { 167 self.0 168 .iter() 169 .any(|permission| permission.matches_sign_event_kind(event_kind)) 170 } 171 } 172 173 fn sign_event_kind_parameter(value: &str) -> Option<u32> { 174 let value = value.strip_prefix("kind:").unwrap_or(value); 175 if value.is_empty() || !value.chars().all(|character| character.is_ascii_digit()) { 176 return None; 177 } 178 value.parse().ok() 179 } 180 181 impl From<Vec<Permission>> for Permissions { 182 fn from(value: Vec<Permission>) -> Self { 183 Self(canonicalize(value)) 184 } 185 } 186 187 impl fmt::Display for Permissions { 188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 189 let rendered = self 190 .0 191 .iter() 192 .map(ToString::to_string) 193 .collect::<Vec<_>>() 194 .join(","); 195 f.write_str(&rendered) 196 } 197 } 198 199 impl FromStr for Permissions { 200 type Err = RadrootsNostrConnectError; 201 202 fn from_str(value: &str) -> Result<Self, Self::Err> { 203 let trimmed = value.trim(); 204 if trimmed.is_empty() { 205 return Ok(Self::default()); 206 } 207 if trimmed.len() > PERMISSIONS_MAX_BYTES { 208 return Err(invalid_permissions( 209 "serialized permission set exceeds its byte limit", 210 )); 211 } 212 213 let permissions = trimmed 214 .split(',') 215 .map(Permission::from_str) 216 .collect::<Result<Vec<_>, _>>()?; 217 Self::try_from_vec(permissions) 218 } 219 } 220 221 impl Serialize for Permissions { 222 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 223 where 224 S: Serializer, 225 { 226 validate_permissions(&self.0).map_err(serde::ser::Error::custom)?; 227 serializer.serialize_str(&self.to_string()) 228 } 229 } 230 231 impl<'de> Deserialize<'de> for Permissions { 232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 233 where 234 D: Deserializer<'de>, 235 { 236 let value = String::deserialize(deserializer)?; 237 Self::from_str(&value).map_err(serde::de::Error::custom) 238 } 239 } 240 241 fn canonicalize(mut permissions: Vec<Permission>) -> Vec<Permission> { 242 permissions.sort_by_key(ToString::to_string); 243 permissions.dedup(); 244 permissions 245 } 246 247 fn validate_permissions(permissions: &[Permission]) -> Result<(), RadrootsNostrConnectError> { 248 if permissions.len() > PERMISSION_COUNT_MAX { 249 return Err(invalid_permissions("permission count exceeds its limit")); 250 } 251 for permission in permissions { 252 Method::from_str(permission.method.as_str())?; 253 if let Some(parameter) = permission.parameter.as_deref() { 254 validate_parameter(parameter)?; 255 } 256 } 257 let rendered = permissions 258 .iter() 259 .map(ToString::to_string) 260 .collect::<Vec<_>>() 261 .join(","); 262 if rendered.len() > PERMISSIONS_MAX_BYTES { 263 return Err(invalid_permissions( 264 "serialized permission set exceeds its byte limit", 265 )); 266 } 267 Ok(()) 268 } 269 270 fn validate_parameter(value: &str) -> Result<(), RadrootsNostrConnectError> { 271 if value.is_empty() 272 || value.len() > PERMISSION_PARAMETER_MAX_BYTES 273 || value.trim() != value 274 || value.contains(',') 275 || value.chars().any(char::is_control) 276 { 277 return Err(RadrootsNostrConnectError::InvalidPermission( 278 value.to_owned(), 279 )); 280 } 281 Ok(()) 282 } 283 284 fn invalid_permissions(reason: &str) -> RadrootsNostrConnectError { 285 RadrootsNostrConnectError::InvalidPermission(reason.to_owned()) 286 }