1use crate::api::tables::{
4 DirectRead as IssuedDirectRead, StorageAccess, StorageBinding as IssuedBinding, StorageMode,
5 VendedCredential,
6};
7use crate::scandal::{
8 DirectRead, GcsSession, S3Session, ScandalError, StorageBinding, StorageCredential,
9};
10
11impl StorageBinding {
12 pub fn from_storage(root: String, binding: IssuedBinding) -> Result<Self, ScandalError> {
18 let expires_at = Some(binding.expires_at);
19 match binding.access {
20 StorageAccess::Signed(access) => {
21 let direct_read = match access.direct_read {
22 Some(direct) if binding.mode == StorageMode::ReadOnly => {
23 Some(direct_read(direct, &root)?)
24 }
25 _ => None,
26 };
27 Ok(Self {
28 root_alias: (root != binding.root_uri).then_some(binding.root_uri),
29 root_uri: root,
30 credential: Some(StorageCredential(access.token.0)),
31 endpoint: (!access.endpoint.is_empty()).then_some(access.endpoint),
33 expires_at,
34 direct_read,
35 })
36 }
37 StorageAccess::Vended(access) => {
38 let credential = session(access.credential, &root)?;
39 let root_alias = (root != binding.root_uri).then_some(root);
40 Ok(Self {
41 root_uri: binding.root_uri,
42 root_alias,
43 credential: Some(credential),
44 endpoint: None,
45 expires_at,
46 direct_read: None,
47 })
48 }
49 StorageAccess::Unknown { .. } => Err(unsupported(&root)),
50 }
51 }
52
53 pub fn for_resource(binding: IssuedBinding) -> Result<Self, ScandalError> {
56 Self::from_storage(binding.root_uri.clone(), binding)
57 }
58}
59
60fn direct_read(direct: IssuedDirectRead, root: &str) -> Result<DirectRead, ScandalError> {
61 Ok(DirectRead {
62 root_uri: direct.uri,
63 credential: session(direct.credential, root)?,
64 })
65}
66
67fn session(credential: VendedCredential, root: &str) -> Result<StorageCredential, ScandalError> {
69 let json = match credential {
70 VendedCredential::AwsSession(credential) => serde_json::to_string(&S3Session {
71 access_key_id: credential.access_key_id,
72 secret_access_key: credential.secret_access_key.0,
73 session_token: credential.session_token.0,
74 region: credential.region,
75 endpoint: credential.endpoint,
76 expires_at_micros: credential.expires_at_micros.0,
77 }),
78 VendedCredential::GcsAccessToken(credential) => serde_json::to_string(&GcsSession {
79 access_token: credential.access_token.0,
80 expires_at_micros: credential.expires_at_micros.0,
81 }),
82 _ => return Err(unsupported(root)),
83 };
84 json.map(StorageCredential)
85 .map_err(|error| ScandalError::Internal {
86 message: error.to_string(),
87 })
88}
89
90fn unsupported(root: &str) -> ScandalError {
91 ScandalError::InvalidArgument {
92 message: format!("storage at {root} is neither signed by SPFS nor in object storage"),
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::api::tables::{
100 GcsAccessToken, HuggingFaceToken, SecretToken, SignedAccess, VendedAccess,
101 };
102
103 fn vended_binding(credential: VendedCredential) -> IssuedBinding {
104 IssuedBinding {
105 root_uri: "gs://acme/lake/tables/t1/".to_owned(),
106 mode: StorageMode::ReadOnly,
107 expires_at: 0.into(),
108 access: StorageAccess::Vended(VendedAccess { credential }),
109 }
110 }
111
112 #[test]
116 fn a_vended_binding_carries_its_session_to_scandal() {
117 let root = "gs://acme/lake/tables/t1/".to_owned();
118 let gcs = || {
119 vended_binding(VendedCredential::GcsAccessToken(GcsAccessToken {
120 access_token: SecretToken("ya29.vended".into()),
121 expires_at_micros: 7.into(),
122 }))
123 };
124 let binding = StorageBinding::from_storage(root.clone(), gcs()).unwrap();
125 assert_eq!(binding.root_uri, root);
126 assert_eq!(binding.root_alias, None);
127 let session: GcsSession = serde_json::from_str(binding.credential().unwrap()).unwrap();
128 assert_eq!(session.access_token, "ya29.vended");
129 assert_eq!(session.expires_at_micros, 7);
130
131 let aliased =
132 StorageBinding::from_storage("spfsu://4b44-alias/".to_owned(), gcs()).unwrap();
133 assert_eq!(aliased.root_uri, root);
134 assert_eq!(aliased.root_alias.as_deref(), Some("spfsu://4b44-alias/"));
135
136 let refused = StorageBinding::from_storage(
137 root,
138 vended_binding(VendedCredential::HuggingFaceToken(HuggingFaceToken {
139 token: SecretToken("hf".into()),
140 expires_at_micros: 7.into(),
141 endpoint: "https://huggingface.co".to_owned(),
142 })),
143 );
144 assert!(refused.is_err());
145 }
146
147 fn signed_binding(mode: StorageMode, endpoint: &str) -> IssuedBinding {
148 IssuedBinding {
149 root_uri: "spfsu://4b44-alias/".to_owned(),
150 mode,
151 expires_at: 1_900_000_000.into(),
152 access: StorageAccess::Signed(SignedAccess {
153 endpoint: endpoint.to_owned(),
154 token: SecretToken("capability".into()),
155 direct_read: Some(IssuedDirectRead {
156 uri: "gs://acme/lake/tables/t1/".to_owned(),
157 credential: VendedCredential::GcsAccessToken(GcsAccessToken {
158 access_token: SecretToken("ya29.vended".into()),
159 expires_at_micros: 7.into(),
160 }),
161 }),
162 }),
163 }
164 }
165
166 #[test]
169 fn a_signed_binding_carries_its_terms_to_scandal() {
170 let binding = StorageBinding::for_resource(signed_binding(
171 StorageMode::ReadOnly,
172 "https://spfs.example/",
173 ))
174 .unwrap();
175 assert_eq!(binding.endpoint.as_deref(), Some("https://spfs.example/"));
176 assert_eq!(binding.expires_at.map(u64::from), Some(1_900_000_000));
177 let direct = binding.direct_read.unwrap();
178 assert_eq!(direct.root_uri, "gs://acme/lake/tables/t1/");
179 let session: GcsSession = serde_json::from_str(&direct.credential.0).unwrap();
180 assert_eq!(session.access_token, "ya29.vended");
181
182 let writable =
183 StorageBinding::for_resource(signed_binding(StorageMode::ReadWrite, "")).unwrap();
184 assert_eq!(writable.direct_read, None);
185 assert_eq!(writable.endpoint, None);
186 }
187}