Skip to content

Commit f2aa31b

Browse files
committed
wip
1 parent 0789dff commit f2aa31b

File tree

8 files changed

+481
-0
lines changed

8 files changed

+481
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ test/lambda/env.json
9999

100100
# files generated by tooling in drivers-evergreen-tools
101101
secrets-export.sh
102+
secrets-export.fish
102103
mo-expansion.sh
103104
mo-expansion.yml
104105
expansions.sh

etc/bash_to_fish.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { createReadStream, promises as fs } from 'node:fs';
2+
import path from 'node:path';
3+
import readline from 'node:readline/promises';
4+
5+
/**
6+
* Takes an "exports" only bash script file
7+
* and converts it to fish syntax.
8+
* Will crash on any line that isn't:
9+
* - a comment
10+
* - an empty line
11+
* - a bash 'set' call
12+
* - export VAR=VAL
13+
*/
14+
15+
const fileName = process.argv[2];
16+
const outFileName = path.basename(fileName, '.sh') + '.fish';
17+
const input = createReadStream(process.argv[2]);
18+
const lines = readline.createInterface({ input });
19+
const output = await fs.open(outFileName, 'w');
20+
21+
for await (let line of lines) {
22+
line = line.trim();
23+
24+
if (!line.startsWith('export ')) {
25+
if (line.startsWith('#')) continue;
26+
if (line === '') continue;
27+
if (line.startsWith('set')) continue;
28+
throw new Error('Cannot translate: ' + line);
29+
}
30+
31+
const varVal = line.slice('export '.length);
32+
const variable = varVal.slice(0, varVal.indexOf('='));
33+
const value = varVal.slice(varVal.indexOf('=') + 1);
34+
output.appendFile(`set -x ${variable} ${value}\n`);
35+
}
36+
37+
output.close();
38+
input.close();
39+
lines.close();
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
import * as fs from 'node:fs/promises';
2+
import * as path from 'node:path';
3+
4+
import { expect } from 'chai';
5+
6+
import { getCSFLEKMSProviders } from '../../csfle-kms-providers';
7+
import { BSON, type Document, type MongoClient } from '../../mongodb';
8+
9+
const metadata: MongoDBMetadataUI = {
10+
requires: {
11+
clientSideEncryption: '>=6.1.0',
12+
mongodb: '>=8.1.0',
13+
topology: '!single'
14+
}
15+
};
16+
17+
const readFixture = async name =>
18+
BSON.EJSON.parse(
19+
await fs.readFile(
20+
path.resolve(__dirname, `../../spec/client-side-encryption/etc/data/lookup/${name}`),
21+
'utf8'
22+
)
23+
);
24+
25+
const newEncryptedClient = ctx =>
26+
ctx.configuration.newClient(
27+
{},
28+
{
29+
autoEncryption: {
30+
keyVaultNamespace: 'db.keyvault',
31+
kmsProviders: { local: getCSFLEKMSProviders().local }
32+
}
33+
}
34+
);
35+
36+
describe.only('$lookup support', function () {
37+
before(async function () {
38+
let client: MongoClient, encryptedClient: MongoClient;
39+
try {
40+
/** Create an unencrypted MongoClient. */
41+
client = this.configuration.newClient();
42+
/** Drop database db. */
43+
await client
44+
.db('db')
45+
.dropDatabase()
46+
.catch(() => null);
47+
/** Insert `key-doc.json` into db.keyvault. */
48+
const keyDoc = await readFixture('key-doc.json');
49+
await client.db('db').collection('keyvault').insertOne(keyDoc);
50+
51+
/**
52+
* Create the following collections:
53+
* ```
54+
* db.csfle with options: { "validator": { "$jsonSchema": "<schema-csfle.json>"}}.
55+
* db.csfle2 with options: { "validator": { "$jsonSchema": "<schema-csfle2.json>"}}.
56+
* db.qe with options: { "encryptedFields": "<schema-qe.json>"}.
57+
* db.qe2 with options: { "encryptedFields": "<schema-qe2.json>"}.
58+
* db.no_schema with no options.
59+
* db.no_schema2 with no options.
60+
* ```
61+
*/
62+
const collections = [
63+
{
64+
name: 'csfle',
65+
options: { validator: { $jsonSchema: await readFixture('schema-csfle.json') } },
66+
document: { csfle: 'csfle' }
67+
},
68+
{
69+
name: 'csfle2',
70+
options: { validator: { $jsonSchema: await readFixture('schema-csfle2.json') } },
71+
document: { csfle2: 'csfle2' }
72+
},
73+
{
74+
name: 'qe',
75+
options: { encryptedFields: await readFixture('schema-qe.json') },
76+
document: { qe: 'qe' }
77+
},
78+
{
79+
name: 'qe2',
80+
options: { encryptedFields: await readFixture('schema-qe2.json') },
81+
document: { qe2: 'qe2' }
82+
},
83+
{
84+
name: 'no_schema',
85+
options: {},
86+
document: { no_schema: 'no_schema' }
87+
},
88+
{
89+
name: 'no_schema2',
90+
options: {},
91+
document: { no_schema2: 'no_schema2' }
92+
}
93+
];
94+
95+
for (const { name, options } of collections)
96+
await client.db('db').createCollection(name, options);
97+
98+
/**
99+
* Create an encrypted MongoClient configured with:
100+
*
101+
* ```txt
102+
* AutoEncryptionOpts(
103+
* keyVaultNamespace="db.keyvault",
104+
* kmsProviders={"local": { "key": "<base64 decoding of LOCAL_MASTERKEY>" }}
105+
* )
106+
* ```
107+
*/
108+
encryptedClient = newEncryptedClient(this);
109+
110+
/**
111+
* ```
112+
* {"csfle": "csfle"} into db.csfle
113+
* Use the unencrypted client to retrieve it. Assert the csfle field is BSON binary.
114+
* {"csfle2": "csfle2"} into db.csfle2
115+
* Use the unencrypted client to retrieve it. Assert the csfle2 field is BSON binary.
116+
* {"qe": "qe"} into db.qe
117+
* Use the unencrypted client to retrieve it. Assert the qe field is BSON binary.
118+
* {"qe2": "qe2"} into db.qe2
119+
* Use the unencrypted client to retrieve it. Assert the qe2 field is BSON binary.
120+
* {"no_schema": "no_schema"} into db.no_schema
121+
* {"no_schema2": "no_schema2"} into db.no_schema2
122+
* ```
123+
*/
124+
for (const { name, document } of collections) {
125+
const { insertedId } = await encryptedClient.db('db').collection(name).insertOne(document);
126+
127+
if (name.startsWith('no_')) continue;
128+
129+
expect(await client.db('db').collection(name).findOne(insertedId))
130+
.to.have.property(Object.keys(document)[0])
131+
.that.has.property('_bsontype', 'Binary');
132+
}
133+
} finally {
134+
await client.close();
135+
await encryptedClient.close();
136+
}
137+
});
138+
139+
const test = function (
140+
title: string,
141+
collName: string,
142+
pipeline: Document[],
143+
expected: Document
144+
) {
145+
describe(title.slice(0, title.indexOf(':')), function () {
146+
let client: MongoClient;
147+
148+
beforeEach(async function () {
149+
client = newEncryptedClient(this);
150+
});
151+
152+
afterEach(async function () {
153+
await client.close();
154+
});
155+
156+
it(title.slice(title.indexOf(':') + 1), async () => {
157+
const collection = client.db('db').collection(collName);
158+
const actual = await collection
159+
.aggregate(pipeline)
160+
.toArray()
161+
.catch(error => error);
162+
163+
const expectedError = expected instanceof Error;
164+
165+
if (expectedError) {
166+
expect(actual).to.be.instanceOf(Error);
167+
expect(actual.message).to.match(new RegExp(expected.message, 'i'));
168+
} else if (actual instanceof Error) {
169+
throw actual;
170+
} else {
171+
expect(actual).to.have.lengthOf(1);
172+
expect(actual[0]).to.deep.equal(expected);
173+
}
174+
});
175+
});
176+
};
177+
178+
test(
179+
'Case 1: db.csfle joins db.no_schema',
180+
'csfle',
181+
[
182+
{ $match: { csfle: 'csfle' } },
183+
{
184+
$lookup: {
185+
from: 'no_schema',
186+
as: 'matched',
187+
pipeline: [{ $match: { no_schema: 'no_schema' } }, { $project: { _id: 0 } }]
188+
}
189+
},
190+
{ $project: { _id: 0 } }
191+
],
192+
{ csfle: 'csfle', matched: [{ no_schema: 'no_schema' }] }
193+
);
194+
195+
test(
196+
'Case 2: db.qe joins db.no_schema',
197+
'qe',
198+
[
199+
{ $match: { qe: 'qe' } },
200+
{
201+
$lookup: {
202+
from: 'no_schema',
203+
as: 'matched',
204+
pipeline: [
205+
{ $match: { no_schema: 'no_schema' } },
206+
{ $project: { _id: 0, __safeContent__: 0 } }
207+
]
208+
}
209+
},
210+
{ $project: { _id: 0, __safeContent__: 0 } }
211+
],
212+
{ qe: 'qe', matched: [{ no_schema: 'no_schema' }] }
213+
);
214+
215+
test(
216+
'Case 3: db.no_schema joins db.csfle',
217+
'no_schema',
218+
[
219+
{ $match: { no_schema: 'no_schema' } },
220+
{
221+
$lookup: {
222+
from: 'csfle',
223+
as: 'matched',
224+
pipeline: [{ $match: { csfle: 'csfle' } }, { $project: { _id: 0 } }]
225+
}
226+
},
227+
{ $project: { _id: 0 } }
228+
],
229+
{ no_schema: 'no_schema', matched: [{ csfle: 'csfle' }] }
230+
);
231+
232+
test(
233+
'Case 4: db.no_schema joins db.qe',
234+
'no_schema',
235+
[
236+
{ $match: { no_schema: 'no_schema' } },
237+
{
238+
$lookup: {
239+
from: 'qe',
240+
as: 'matched',
241+
pipeline: [{ $match: { qe: 'qe' } }, { $project: { _id: 0, __safeContent__: 0 } }]
242+
}
243+
},
244+
{ $project: { _id: 0 } }
245+
],
246+
{ no_schema: 'no_schema', matched: [{ qe: 'qe' }] }
247+
);
248+
249+
test(
250+
'Case 5: db.csfle joins db.csfle2',
251+
'csfle',
252+
[
253+
{ $match: { csfle: 'csfle' } },
254+
{
255+
$lookup: {
256+
from: 'csfle2',
257+
as: 'matched',
258+
pipeline: [{ $match: { csfle2: 'csfle2' } }, { $project: { _id: 0 } }]
259+
}
260+
},
261+
{ $project: { _id: 0 } }
262+
],
263+
{ csfle: 'csfle', matched: [{ csfle2: 'csfle2' }] }
264+
);
265+
266+
test(
267+
'Case 6: db.qe joins db.qe2',
268+
'qe',
269+
[
270+
{ $match: { qe: 'qe' } },
271+
{
272+
$lookup: {
273+
from: 'qe2',
274+
as: 'matched',
275+
pipeline: [{ $match: { qe2: 'qe2' } }, { $project: { _id: 0, __safeContent__: 0 } }]
276+
}
277+
},
278+
{ $project: { _id: 0, __safeContent__: 0 } }
279+
],
280+
{ qe: 'qe', matched: [{ qe2: 'qe2' }] }
281+
);
282+
283+
test(
284+
'Case 7: db.no_schema joins db.no_schema2',
285+
'no_schema',
286+
[
287+
{ $match: { no_schema: 'no_schema' } },
288+
{
289+
$lookup: {
290+
from: 'no_schema2',
291+
as: 'matched',
292+
pipeline: [{ $match: { no_schema2: 'no_schema2' } }, { $project: { _id: 0 } }]
293+
}
294+
},
295+
{ $project: { _id: 0 } }
296+
],
297+
{ no_schema: 'no_schema', matched: [{ no_schema2: 'no_schema2' }] }
298+
);
299+
300+
test(
301+
'Case 8: db.csfle joins db.qe',
302+
'csfle',
303+
[
304+
{ $match: { csfle: 'qe' } },
305+
{
306+
$lookup: {
307+
from: 'qe',
308+
as: 'matched',
309+
pipeline: [{ $match: { qe: 'qe' } }, { $project: { _id: 0 } }]
310+
}
311+
},
312+
{ $project: { _id: 0 } }
313+
],
314+
new Error('not supported')
315+
);
316+
317+
test(
318+
'Case 9: db.csfle joins db.qe',
319+
'csfle',
320+
[
321+
{ $match: { csfle: 'csfle' } },
322+
{
323+
$lookup: {
324+
from: 'no_schema',
325+
as: 'matched',
326+
pipeline: [{ $match: { no_schema: 'no_schema' } }, { $project: { _id: 0 } }]
327+
}
328+
},
329+
{ $project: { _id: 0 } }
330+
],
331+
new Error('Upgrade')
332+
);
333+
});

0 commit comments

Comments
 (0)