typesec_integrations/jwt/
engine.rs1use std::collections::HashSet;
4
5use serde_json::Value;
6use tracing::debug;
7use typesec_core::{
8 ResourceId, SubjectId,
9 policy::{PolicyEngine, PolicyResult},
10};
11
12use super::claims::VerifiedSubject;
13
14pub struct JwtClaimsEngine {
20 subject: String,
21 permissions: HashSet<String>,
22 org_id: Option<String>,
23}
24
25impl JwtClaimsEngine {
26 pub fn new(subject: VerifiedSubject) -> Self {
28 Self {
29 subject: subject.subject,
30 permissions: subject.permissions.into_iter().collect(),
31 org_id: subject.org_id,
32 }
33 }
34
35 pub fn from_permissions(
37 subject: impl Into<String>,
38 permissions: impl IntoIterator<Item = String>,
39 ) -> Self {
40 Self {
41 subject: subject.into(),
42 permissions: permissions.into_iter().collect(),
43 org_id: None,
44 }
45 }
46
47 fn permission_matches(&self, action: &str, resource: &str) -> bool {
48 if self.permissions.contains(action) {
49 return true;
50 }
51
52 let resource_type = resource.split(['/', ':']).next().unwrap_or(resource);
53 self.permissions
54 .contains(&format!("{resource_type}:{action}"))
55 }
56}
57
58impl PolicyEngine for JwtClaimsEngine {
59 fn check(&self, subject: &SubjectId, action: &str, resource: &ResourceId) -> PolicyResult {
60 let subject = subject.as_str();
61 let resource = resource.as_str();
62 debug!(subject, action, resource, org_id = ?self.org_id, "jwt claims check");
63
64 if subject != self.subject {
65 return PolicyResult::delegate(
66 "jwt",
67 format!("jwt claims are for '{}', not '{subject}'", self.subject),
68 );
69 }
70
71 if self.permission_matches(action, resource) {
72 PolicyResult::Allow
73 } else {
74 PolicyResult::delegate(
75 "jwt",
76 format!("permission '{action}' not present in jwt claims"),
77 )
78 }
79 }
80}
81
82#[allow(dead_code)]
83fn _assert_value_send_sync(_: Value) {}