Skip to content

Commit 8a5ad94

Browse files
Add using cached-permissions and cached-permissions-path CLI features to FSRT
1 parent e94f3bf commit 8a5ad94

File tree

9 files changed

+541
-56
lines changed

9 files changed

+541
-56
lines changed

Cargo.lock

+44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ Arguments:
1616
1717
Options:
1818
-d, --debug
19-
--dump-ir <DUMP_IR> Dump the IR for the specified function.
19+
--dump-ir <DUMP_IR> Dump the IR for the specified function
2020
-dt, --dump-dt <DUMP_DOM_TREE> Dump the Dominator Tree for the specified app
21-
-f, --function <FUNCTION> A specific function to scan. Must be an entrypoint specified in `manifest.yml`
21+
-f, --function <FUNCTION> A specific function to scan, must be an entrypoint specified in `manifest.yml`
2222
-h, --help Print help information
2323
-V, --version Print version information
2424
--check-permissions Runs the permission checker
25+
--cached-permissions Uses cached swagger permissions to avoid redownloading them
26+
--cached-permissions-path User designated cache location, if not specified defaults to ~/.cache
2527
--graphql-schema-path <LOCATION> Uses the graphql schema in location; othwerwise selects ~/.config dir
2628
```
2729

crates/forge_analyzer/src/utils.rs

-14
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,6 @@ pub fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Vec<String>) {
9595
}
9696
}
9797

98-
pub fn trnaslate_request_type(request_type: Option<&str>) -> RequestType {
99-
if let Some(request_type) = request_type {
100-
match request_type {
101-
"PATCH" => RequestType::Patch,
102-
"PUT" => RequestType::Put,
103-
"DELETE" => RequestType::Delete,
104-
"POST" => RequestType::Post,
105-
_ => RequestType::Get,
106-
}
107-
} else {
108-
RequestType::Get
109-
}
110-
}
111-
11298
pub fn return_combinations_phi(exprs: Vec<Value>) -> Value {
11399
let exprs: Vec<Vec<String>> = exprs
114100
.iter()

crates/forge_permission_resolver/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ ureq = { version = "2.9.6", features = ["json", "charset"] }
2121

2222
[dev-dependencies]
2323
pretty_assertions.workspace = true
24+
filetime = "0.2"
25+
tempfile = "3.2"
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
pub mod permissions_cache;
12
pub mod permissions_resolver;
23
pub mod permissions_resolver_compass;
4+
pub mod serde;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
use regex::Regex;
2+
use std::collections::HashMap;
3+
use std::env;
4+
use std::path::PathBuf;
5+
use std::time::Duration;
6+
7+
use crate::permissions_resolver::PermissionHashMap;
8+
use crate::serde::{SerializablePermissionHashMap, ToStringMap};
9+
use std::fs::{self, File};
10+
use std::io::{Read, Write};
11+
use tracing::debug;
12+
13+
const CACHE_EXPIRATION: Duration = Duration::from_secs(60 * 60 * 24 * 7); // 1 week
14+
15+
fn default_cache_path() -> PathBuf {
16+
let home = env::var("HOME").unwrap_or_else(|_| ".".to_string());
17+
PathBuf::from(home).join(".cache/fsrt")
18+
}
19+
20+
/// Struct to hold cache-related settings
21+
#[derive(Default, Debug, Clone, PartialEq, Eq)]
22+
pub struct CacheConfig {
23+
use_cache: bool,
24+
cache_path: Option<PathBuf>,
25+
ttl: Duration,
26+
}
27+
28+
impl CacheConfig {
29+
pub fn new(use_cache: bool, cache_path: Option<PathBuf>) -> Self {
30+
CacheConfig {
31+
use_cache,
32+
cache_path: Some(cache_path.unwrap_or_else(default_cache_path)),
33+
ttl: CACHE_EXPIRATION,
34+
}
35+
}
36+
37+
pub fn use_cache(&self) -> bool {
38+
self.use_cache
39+
}
40+
41+
pub fn cache_path(&self) -> &PathBuf {
42+
self.cache_path.as_ref().unwrap()
43+
}
44+
}
45+
46+
pub struct PermissionsCache {
47+
config: CacheConfig,
48+
}
49+
50+
impl PermissionsCache {
51+
pub fn new(config: CacheConfig) -> Self {
52+
PermissionsCache { config }
53+
}
54+
55+
fn get_cache_path(&self, key: &str) -> PathBuf {
56+
let mut path = self.config.cache_path().clone();
57+
path.push(key);
58+
path
59+
}
60+
61+
fn is_cache_valid(&self, path: &PathBuf) -> bool {
62+
if let Ok(metadata) = fs::metadata(path) {
63+
if let Ok(modified) = metadata.modified() {
64+
if let Ok(duration) = modified.elapsed() {
65+
if duration < self.config.ttl {
66+
return true;
67+
} else {
68+
println!(
69+
"Cache file expired: {:?}, duration {:?} is greater than ttl {:?}",
70+
path, duration, self.config.ttl
71+
);
72+
}
73+
} else {
74+
println!("Failed to get elapsed time for cache file: {:?}", path);
75+
}
76+
} else {
77+
println!("Failed to get modified time for cache file: {:?}", path);
78+
}
79+
} else {
80+
println!("Failed to get metadata for cache file: {:?}", path);
81+
}
82+
false
83+
}
84+
85+
pub fn read(
86+
&self,
87+
key: &str,
88+
permission_map: &mut PermissionHashMap,
89+
regex_map: &mut HashMap<String, Regex>,
90+
) -> bool {
91+
if !self.config.use_cache() {
92+
return false;
93+
}
94+
95+
let cache_path = self.get_cache_path(key).with_extension("json");
96+
97+
if !self.is_cache_valid(&cache_path) {
98+
return false;
99+
}
100+
debug!("cache_path: {:?}", cache_path);
101+
102+
let mut file = match File::open(&cache_path) {
103+
Ok(file) => file,
104+
Err(_) => return false,
105+
};
106+
107+
let mut contents = String::new();
108+
if file.read_to_string(&mut contents).is_err() {
109+
return false;
110+
}
111+
112+
match serde_json::from_str::<(SerializablePermissionHashMap, HashMap<String, String>)>(
113+
&contents,
114+
) {
115+
Ok((perm_map, reg_map)) => {
116+
*permission_map = perm_map.into();
117+
*regex_map = HashMap::<String, Regex>::from_string_map(reg_map);
118+
true
119+
}
120+
Err(_) => false,
121+
}
122+
}
123+
124+
pub fn set(
125+
&self,
126+
key: &str,
127+
permission_map: &PermissionHashMap,
128+
regex_map: &HashMap<String, Regex>,
129+
) -> bool {
130+
if !self.config.use_cache() {
131+
return false;
132+
}
133+
134+
let cache_path = self.get_cache_path(key).with_extension("json");
135+
136+
let cache_dir = cache_path.parent().unwrap();
137+
if fs::create_dir_all(cache_dir).is_err() {
138+
return false;
139+
}
140+
141+
let serializable_perm_map: SerializablePermissionHashMap = permission_map.into();
142+
let serializable_regex_map = regex_map.to_string_map();
143+
let data =
144+
serde_json::to_string_pretty(&(serializable_perm_map, serializable_regex_map)).unwrap();
145+
146+
let mut file = match File::create(&cache_path) {
147+
Ok(file) => file,
148+
Err(_) => return false,
149+
};
150+
151+
if file.write_all(data.as_bytes()).is_err() {
152+
return false;
153+
}
154+
155+
true
156+
}
157+
}
158+
159+
#[cfg(test)]
160+
mod tests {
161+
use super::*;
162+
use crate::permissions_resolver::{PermissionHashMap, RequestType};
163+
use filetime;
164+
use tempfile::tempdir;
165+
166+
#[test]
167+
fn test_cache_read_write() {
168+
let temp_dir = tempdir().unwrap();
169+
let cache_path = temp_dir.path().to_path_buf();
170+
let config = CacheConfig::new(true, Some(cache_path.clone()));
171+
172+
let mut permission_map: PermissionHashMap = HashMap::new();
173+
permission_map.insert(
174+
("/test/path".to_string(), RequestType::Get),
175+
vec!["read:test".to_string()],
176+
);
177+
178+
let mut regex_map: HashMap<String, Regex> = HashMap::new();
179+
regex_map.insert("test".to_string(), Regex::new(r"^\d+$").unwrap());
180+
181+
let cache = PermissionsCache::new(config.clone());
182+
183+
// Test writing to cache
184+
assert!(cache.set("test_key", &permission_map, &regex_map));
185+
186+
// Test reading from cache
187+
let mut read_permission_map: PermissionHashMap = HashMap::new();
188+
let mut read_regex_map: HashMap<String, Regex> = HashMap::new();
189+
assert!(cache.read("test_key", &mut read_permission_map, &mut read_regex_map));
190+
191+
assert_eq!(permission_map, read_permission_map);
192+
assert_eq!(regex_map.len(), read_regex_map.len());
193+
for (key, regex) in regex_map {
194+
assert_eq!(regex.as_str(), read_regex_map.get(&key).unwrap().as_str());
195+
}
196+
}
197+
198+
#[test]
199+
fn test_cache_expiration() {
200+
let temp_dir = tempdir().unwrap();
201+
let cache_path = temp_dir.path().to_path_buf();
202+
let config = CacheConfig::new(true, Some(cache_path.clone()));
203+
204+
let mut permission_map: PermissionHashMap = HashMap::new();
205+
permission_map.insert(
206+
("/test/path".to_string(), RequestType::Get),
207+
vec!["read:test".to_string()],
208+
);
209+
210+
let mut regex_map: HashMap<String, Regex> = HashMap::new();
211+
regex_map.insert("test".to_string(), Regex::new(r"^\d+$").unwrap());
212+
213+
let cache = PermissionsCache::new(config.clone());
214+
215+
// Test writing to cache
216+
assert!(cache.set("test_key", &permission_map, &regex_map));
217+
218+
// Manually set the cache file's modified time to be expired
219+
let cache_file_path = cache.get_cache_path("test_key").with_extension("json");
220+
let one_week_ago = std::time::SystemTime::now() - CACHE_EXPIRATION - Duration::from_secs(1);
221+
filetime::set_file_mtime(
222+
&cache_file_path,
223+
filetime::FileTime::from_system_time(one_week_ago),
224+
)
225+
.unwrap();
226+
227+
// Test reading from cache should fail due to expiration
228+
let mut read_permission_map: PermissionHashMap = HashMap::new();
229+
let mut read_regex_map: HashMap<String, Regex> = HashMap::new();
230+
assert!(!cache.read("test_key", &mut read_permission_map, &mut read_regex_map));
231+
}
232+
233+
#[test]
234+
fn test_cache_disabled() {
235+
let temp_dir = tempdir().unwrap();
236+
let cache_path = temp_dir.path().to_path_buf();
237+
let config = CacheConfig::new(false, Some(cache_path.clone()));
238+
239+
let mut permission_map: PermissionHashMap = HashMap::new();
240+
permission_map.insert(
241+
("/test/path".to_string(), RequestType::Get),
242+
vec!["read:test".to_string()],
243+
);
244+
245+
let mut regex_map: HashMap<String, Regex> = HashMap::new();
246+
regex_map.insert("test".to_string(), Regex::new(r"^\d+$").unwrap());
247+
248+
let cache = PermissionsCache::new(config.clone());
249+
250+
// Test writing to cache should fail because cache is disabled
251+
assert!(!cache.set("test_key", &permission_map, &regex_map));
252+
253+
// Test reading from cache should fail because cache is disabled
254+
let mut read_permission_map: PermissionHashMap = HashMap::new();
255+
let mut read_regex_map: HashMap<String, Regex> = HashMap::new();
256+
assert!(!cache.read("test_key", &mut read_permission_map, &mut read_regex_map));
257+
}
258+
}

0 commit comments

Comments
 (0)