realm.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. use std::{
  2. collections::HashMap,
  3. sync::{Arc, RwLock},
  4. };
  5. use microrm::prelude::*;
  6. use crate::{
  7. config::Config,
  8. key::{HMacType, KeyType},
  9. schema, UIDCError,
  10. };
  11. #[derive(serde::Serialize, serde::Deserialize)]
  12. pub struct AccessTokenClaims<'l> {
  13. pub sub: &'l str,
  14. pub iss: &'l str,
  15. pub aud: &'l str,
  16. pub iat: u64,
  17. pub exp: u64,
  18. pub scopes: Vec<&'l str>,
  19. pub roles: Vec<String>,
  20. }
  21. #[derive(serde::Serialize, serde::Deserialize)]
  22. pub struct RefreshTokenClaims {
  23. pub sub: String,
  24. pub iss: String,
  25. pub aud: String,
  26. pub iat: u64,
  27. pub exp: u64,
  28. #[serde(rename = "use")]
  29. pub use_: String,
  30. pub scopes: Vec<String>,
  31. }
  32. pub struct RealmCache {
  33. config: Config,
  34. db: schema::UIDCDatabase,
  35. realms: RwLock<HashMap<schema::RealmID, Arc<RealmHelper>>>,
  36. }
  37. impl RealmCache {
  38. pub fn new(config: Config, db: schema::UIDCDatabase) -> Self {
  39. Self {
  40. config,
  41. db,
  42. realms: Default::default(),
  43. }
  44. }
  45. pub fn get_helper(&self, id: schema::RealmID) -> Option<Arc<RealmHelper>> {
  46. if let Some(rh) = self.realms.read().unwrap().get(&id) {
  47. return Some(rh.clone());
  48. }
  49. let realm = self.db.realms.by_id(id).ok().flatten()?;
  50. self.realms
  51. .write()
  52. .unwrap()
  53. .insert(id, RealmHelper::new(self.config.clone(), realm).into());
  54. self.get_helper(id)
  55. }
  56. }
  57. pub struct RealmHelper {
  58. config: Config,
  59. realm: microrm::Stored<schema::Realm>,
  60. // cached for later
  61. issuer: String,
  62. encoding_key_cache: RwLock<HashMap<schema::KeyID, jsonwebtoken::EncodingKey>>,
  63. decoding_key_cache: RwLock<HashMap<schema::KeyID, jsonwebtoken::DecodingKey>>,
  64. }
  65. impl RealmHelper {
  66. pub fn new(config: Config, realm: microrm::Stored<schema::Realm>) -> Self {
  67. Self {
  68. issuer: format!("{}/{}", config.base_url, realm.shortname),
  69. config,
  70. realm,
  71. encoding_key_cache: Default::default(),
  72. decoding_key_cache: Default::default(),
  73. }
  74. }
  75. fn determine_roles<'a>(
  76. &self,
  77. client: &microrm::Stored<schema::Client>,
  78. user: &microrm::Stored<schema::User>,
  79. scopes: impl Iterator<Item = &'a str>,
  80. ) -> Result<Vec<String>, UIDCError> {
  81. let mut requested_roles = vec![];
  82. for scope_name in scopes {
  83. if scope_name.is_empty() {
  84. continue;
  85. }
  86. let Some(scope) = client.scopes.keyed((self.realm.id(), scope_name)).get()? else {
  87. // requested nonexistent scope
  88. return Err(UIDCError::Abort("requested nonexistent scope!"));
  89. };
  90. requested_roles.extend(scope.roles.get_ids()?.into_iter());
  91. }
  92. let mut available_roles = vec![]; // HashSet::<schema::RoleID>::new();
  93. for group in user.groups.get()? {
  94. available_roles.extend(group.roles.get_ids()?.into_iter());
  95. }
  96. requested_roles.sort();
  97. requested_roles.dedup();
  98. available_roles.sort();
  99. available_roles.dedup();
  100. let roles = requested_roles
  101. .into_iter()
  102. .filter(|x| available_roles.contains(x))
  103. .flat_map(|v| {
  104. self.realm
  105. .roles
  106. .with(schema::RoleID::default(), v)
  107. .first()
  108. .get()
  109. })
  110. .flatten()
  111. .map(|v| v.wrapped().shortname)
  112. .collect();
  113. Ok(roles)
  114. }
  115. fn with_encoding_key<R>(
  116. &self,
  117. key: &microrm::Stored<schema::Key>,
  118. f: impl FnOnce(&jsonwebtoken::EncodingKey) -> R,
  119. ) -> R {
  120. // check to see if the cache will work
  121. if let Some(v) = self.encoding_key_cache.read().unwrap().get(&key.id()) {
  122. return f(v);
  123. }
  124. // parse an EncodingKey out of the stored key
  125. let ekey = match key.key_type.as_ref() {
  126. KeyType::HMac(_) => jsonwebtoken::EncodingKey::from_secret(&key.secret_data),
  127. KeyType::RSA2048 | KeyType::RSA4096 => {
  128. jsonwebtoken::EncodingKey::from_rsa_der(&key.secret_data)
  129. }
  130. KeyType::Ed25519 => jsonwebtoken::EncodingKey::from_ed_der(&key.secret_data),
  131. };
  132. f(self
  133. .encoding_key_cache
  134. .write()
  135. .unwrap()
  136. .entry(key.id())
  137. .or_insert(ekey))
  138. }
  139. fn with_decoding_key<R>(
  140. &self,
  141. key: &microrm::Stored<schema::Key>,
  142. f: impl FnOnce(&jsonwebtoken::DecodingKey) -> R,
  143. ) -> R {
  144. // check to see if the cache will work
  145. if let Some(v) = self.decoding_key_cache.read().unwrap().get(&key.id()) {
  146. return f(v);
  147. }
  148. // parse an EncodingKey out of the stored key
  149. let ekey = match key.key_type.as_ref() {
  150. KeyType::HMac(_) => jsonwebtoken::DecodingKey::from_secret(&key.secret_data),
  151. KeyType::RSA2048 | KeyType::RSA4096 => {
  152. jsonwebtoken::DecodingKey::from_rsa_der(&key.secret_data)
  153. }
  154. KeyType::Ed25519 => jsonwebtoken::DecodingKey::from_ed_der(&key.secret_data),
  155. };
  156. f(self
  157. .decoding_key_cache
  158. .write()
  159. .unwrap()
  160. .entry(key.id())
  161. .or_insert(ekey))
  162. }
  163. pub fn generate_access_token<'a>(
  164. &self,
  165. client: &microrm::Stored<schema::Client>,
  166. user: &microrm::Stored<schema::User>,
  167. scopes: impl Iterator<Item = &'a str> + Clone,
  168. ) -> Result<String, UIDCError> {
  169. let iat = std::time::SystemTime::now();
  170. let exp = iat + std::time::Duration::from_secs(self.config.auth_token_expiry);
  171. let resulting_roles = self.determine_roles(client, user, scopes.clone())?;
  172. let atclaims = AccessTokenClaims {
  173. sub: user.username.as_str(),
  174. iss: self.issuer.as_str(),
  175. aud: client.shortname.as_str(),
  176. iat: iat.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
  177. exp: exp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
  178. scopes: scopes.collect(),
  179. roles: resulting_roles,
  180. };
  181. // find an active key that matches the required key type
  182. let Some(ekey) = self
  183. .realm
  184. .keys
  185. .with(
  186. schema::Key::KeyState,
  187. schema::KeyState::Active.into_serialized(),
  188. )
  189. .with(schema::Key::KeyType, &client.access_key_type)
  190. .first()
  191. .get()?
  192. else {
  193. return Err(UIDCError::Abort("no matching signing key"));
  194. };
  195. let mut hdr = jsonwebtoken::Header::new(match ekey.key_type.as_ref() {
  196. KeyType::Ed25519 => jsonwebtoken::Algorithm::EdDSA,
  197. KeyType::RSA2048 | KeyType::RSA4096 => jsonwebtoken::Algorithm::RS256,
  198. KeyType::HMac(HMacType::Sha256) => {
  199. Err(UIDCError::Abort("cannot sign access token with HS256"))?
  200. }
  201. KeyType::HMac(HMacType::Sha512) => {
  202. Err(UIDCError::Abort("cannot sign access token with HS512"))?
  203. }
  204. });
  205. hdr.kid = Some(ekey.key_id.clone());
  206. self.with_encoding_key(&ekey, |ekey| {
  207. jsonwebtoken::encode(&hdr, &atclaims, ekey)
  208. .map_err(|e| UIDCError::AbortString(format!("failed to sign token: {e}")))
  209. })
  210. }
  211. pub fn generate_refresh_token<'a>(
  212. &self,
  213. client: &microrm::Stored<schema::Client>,
  214. user: &microrm::Stored<schema::User>,
  215. scopes: impl Iterator<Item = &'a str> + Clone,
  216. ) -> Result<String, UIDCError> {
  217. let iat = std::time::SystemTime::now();
  218. let exp = iat + std::time::Duration::from_secs(self.config.refresh_token_expiry);
  219. let atclaims = RefreshTokenClaims {
  220. sub: user.username.clone(),
  221. iss: self.issuer.clone(),
  222. aud: client.shortname.clone(),
  223. iat: iat.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
  224. exp: exp.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
  225. scopes: scopes.map(str::to_string).collect(),
  226. use_: "refresh".to_string(),
  227. };
  228. // find an active key that matches the required key type
  229. let Some(ekey) = self
  230. .realm
  231. .keys
  232. .with(
  233. schema::Key::KeyState,
  234. schema::KeyState::Active.into_serialized(),
  235. )
  236. .with(schema::Key::KeyType, &client.refresh_key_type)
  237. .first()
  238. .get()?
  239. else {
  240. return Err(UIDCError::Abort("no matching signing key"));
  241. };
  242. let mut hdr = jsonwebtoken::Header::new(match ekey.key_type.as_ref() {
  243. KeyType::Ed25519 => jsonwebtoken::Algorithm::EdDSA,
  244. KeyType::RSA2048 | KeyType::RSA4096 => jsonwebtoken::Algorithm::RS256,
  245. KeyType::HMac(HMacType::Sha256) => jsonwebtoken::Algorithm::HS256,
  246. KeyType::HMac(HMacType::Sha512) => jsonwebtoken::Algorithm::HS512,
  247. });
  248. hdr.kid = Some(ekey.key_id.clone());
  249. self.with_encoding_key(&ekey, |ekey| {
  250. jsonwebtoken::encode(&hdr, &atclaims, ekey)
  251. .map_err(|_| UIDCError::Abort("failed to sign token"))
  252. })
  253. }
  254. pub fn trade_refresh_token(
  255. &self,
  256. client: &microrm::Stored<schema::Client>,
  257. rtoken: &str,
  258. ) -> Result<(String, String), UIDCError> {
  259. let header = jsonwebtoken::decode_header(rtoken)
  260. .map_err(|e| UIDCError::AbortString(format!("invalid JWT header: {e}")))?;
  261. let Some(kid) = header.kid else {
  262. return Err(UIDCError::Abort("no kid in header"));
  263. };
  264. let Some(key) = self
  265. .realm
  266. .keys
  267. .with(schema::Key::KeyId, kid)
  268. .first()
  269. .get()?
  270. else {
  271. return Err(UIDCError::Abort("no matching key"));
  272. };
  273. if *key.key_state.as_ref() == schema::KeyState::Retired {
  274. return Err(UIDCError::Abort("signing key retired"));
  275. }
  276. let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
  277. validation.set_issuer(&[self.issuer.as_str()]);
  278. validation.set_audience(&[client.shortname.as_str()]);
  279. let Ok(rt) = self.with_decoding_key(&key, |dkey| {
  280. jsonwebtoken::decode::<RefreshTokenClaims>(rtoken, dkey, &validation)
  281. }) else {
  282. return Err(UIDCError::Abort("token validation failed"));
  283. };
  284. if rt.claims.use_ != "refresh" {
  285. return Err(UIDCError::Abort("mismatching token use"));
  286. }
  287. let Some(user) = self
  288. .realm
  289. .users
  290. .keyed((self.realm.id(), rt.claims.sub.as_str()))
  291. .get()?
  292. else {
  293. return Err(UIDCError::Abort("user no longer exists or was renamed"));
  294. };
  295. let scopes = rt.claims.scopes.iter().map(String::as_str);
  296. let access_token = self.generate_access_token(client, &user, scopes.clone())?;
  297. let refresh_token = self.generate_refresh_token(client, &user, scopes)?;
  298. Ok((access_token, refresh_token))
  299. }
  300. }