Skip to content

Commit 1e39c11

Browse files
author
Bennett Hardwick
committed
Split up querying
1 parent 0e7a61c commit 1e39c11

File tree

1 file changed

+85
-46
lines changed

1 file changed

+85
-46
lines changed

src/encrypted_table/query.rs

Lines changed: 85 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use aws_sdk_dynamodb::{primitives::Blob, types::AttributeValue};
2-
use cipherstash_client::encryption::{
3-
compound_indexer::{ComposableIndex, ComposablePlaintext},
4-
Plaintext,
2+
use cipherstash_client::{
3+
credentials::{service_credentials::ServiceToken, Credentials},
4+
encryption::{
5+
compound_indexer::{ComposableIndex, ComposablePlaintext},
6+
Encryption, Plaintext,
7+
},
58
};
69
use itertools::Itertools;
7-
use std::marker::PhantomData;
10+
use std::{collections::HashMap, marker::PhantomData};
811

912
use crate::{
1013
traits::{Decryptable, Searchable},
@@ -26,6 +29,71 @@ pub struct QueryBuilder<'t, S, D = Dynamo> {
2629
__table: PhantomData<S>,
2730
}
2831

32+
pub struct PreparedQuery {
33+
index_name: String,
34+
type_name: String,
35+
composed_index: Box<dyn ComposableIndex>,
36+
plaintext: ComposablePlaintext,
37+
}
38+
39+
impl PreparedQuery {
40+
pub async fn encrypt(
41+
self,
42+
cipher: &Encryption<impl Credentials<Token = ServiceToken>>,
43+
) -> Result<AttributeValue, QueryError> {
44+
let PreparedQuery {
45+
index_name,
46+
composed_index,
47+
plaintext,
48+
type_name,
49+
} = self;
50+
51+
let index_term = cipher.compound_query(
52+
&CompoundIndex::new(composed_index),
53+
plaintext,
54+
Some(format!("{}#{}", type_name, index_name)),
55+
12,
56+
)?;
57+
58+
// With DynamoDB queries must always return a single term
59+
let term = if let IndexTerm::Binary(x) = index_term {
60+
AttributeValue::B(Blob::new(x))
61+
} else {
62+
Err(QueryError::Other(format!(
63+
"Returned IndexTerm had invalid type: {index_term:?}"
64+
)))?
65+
};
66+
67+
Ok(term)
68+
}
69+
70+
pub async fn send(
71+
self,
72+
table: &EncryptedTable<Dynamo>,
73+
) -> Result<Vec<HashMap<String, AttributeValue>>, QueryError> {
74+
let term = self.encrypt(&table.cipher).await?;
75+
76+
let query = table
77+
.db
78+
.query()
79+
.table_name(&table.db.table_name)
80+
.index_name("TermIndex")
81+
.key_condition_expression("term = :term")
82+
.expression_attribute_values(":term", term);
83+
84+
let result = query
85+
.send()
86+
.await
87+
.map_err(|e| QueryError::AwsError(format!("{e:?}")))?;
88+
89+
let items = result
90+
.items
91+
.ok_or_else(|| QueryError::AwsError("Expected items entry on aws response".into()))?;
92+
93+
Ok(items)
94+
}
95+
}
96+
2997
impl<'t, S, D> QueryBuilder<'t, S, D>
3098
where
3199
S: Searchable,
@@ -50,9 +118,7 @@ where
50118
self
51119
}
52120

53-
fn build(
54-
self,
55-
) -> Result<(String, Box<dyn ComposableIndex>, ComposablePlaintext, Self), QueryError> {
121+
fn build(self) -> Result<PreparedQuery, QueryError> {
56122
let items_len = self.parts.len();
57123

58124
// this is the simplest way to brute force the index names but relies on some gross
@@ -61,7 +127,7 @@ where
61127
let (indexes, plaintexts): (Vec<(&String, &SingleIndex)>, Vec<&Plaintext>) =
62128
perm.into_iter().map(|x| ((&x.0, &x.1), &x.2)).unzip();
63129

64-
let name = indexes.iter().map(|(index_name, _)| index_name).join("#");
130+
let index_name = indexes.iter().map(|(index_name, _)| index_name).join("#");
65131

66132
let mut indexes_iter = indexes.iter().map(|(_, index)| **index);
67133

@@ -92,7 +158,7 @@ where
92158
}
93159
};
94160

95-
if let Some(index) = S::index_by_name(name.as_str(), index_type) {
161+
if let Some(composed_index) = S::index_by_name(index_name.as_str(), index_type) {
96162
let mut plaintext = ComposablePlaintext::new(plaintexts[0].clone());
97163

98164
for p in plaintexts[1..].iter() {
@@ -101,7 +167,12 @@ where
101167
.expect("Failed to compose");
102168
}
103169

104-
return Ok((name, index, plaintext, self));
170+
return Ok(PreparedQuery {
171+
index_name,
172+
type_name: S::type_name().to_string(),
173+
plaintext,
174+
composed_index,
175+
});
105176
}
106177
}
107178

@@ -118,43 +189,11 @@ where
118189
S: Searchable + Identifiable,
119190
{
120191
pub async fn load<T: Decryptable>(self) -> Result<Vec<T>, QueryError> {
121-
let (index_name, index, plaintext, builder) = self.build()?;
122-
123-
let index_term = builder.table.cipher.compound_query(
124-
&CompoundIndex::new(index),
125-
plaintext,
126-
Some(format!("{}#{}", S::type_name(), index_name)),
127-
12,
128-
)?;
129-
130-
// With DynamoDB queries must always return a single term
131-
let term = if let IndexTerm::Binary(x) = index_term {
132-
AttributeValue::B(Blob::new(x))
133-
} else {
134-
Err(QueryError::Other(format!(
135-
"Returned IndexTerm had invalid type: {index_term:?}"
136-
)))?
137-
};
138-
139-
let query = builder
140-
.table
141-
.db
142-
.query()
143-
.table_name(&builder.table.db.table_name)
144-
.index_name("TermIndex")
145-
.key_condition_expression("term = :term")
146-
.expression_attribute_values(":term", term);
147-
148-
let result = query
149-
.send()
150-
.await
151-
.map_err(|e| QueryError::AwsError(format!("{e:?}")))?;
152-
153-
let items = result
154-
.items
155-
.ok_or_else(|| QueryError::AwsError("Expected items entry on aws response".into()))?;
192+
let table = self.table;
193+
let query = self.build()?;
156194

157-
let results = builder.table.decrypt_all(items).await?;
195+
let items = query.send(table).await?;
196+
let results = table.decrypt_all(items).await?;
158197

159198
Ok(results)
160199
}

0 commit comments

Comments
 (0)