WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 197145d

Browse files
committed
Support Prefix/Suffix/Substring Indexes.
1 parent f2113d4 commit 197145d

File tree

7 files changed

+373
-10
lines changed

7 files changed

+373
-10
lines changed

internal/integration/client_side_encryption_prose_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,6 +3144,127 @@ func TestClientSideEncryptionProse(t *testing.T) {
31443144
})
31453145
}
31463146
})
3147+
3148+
mt.RunOpts("27. text Explicit Encryption", qeRunOpts.MinServerVersion("8.2"), func(mt *mtest.T) {
3149+
encryptedFields := readJSONFile(mt, "encryptedFields-prefix-suffix.json")
3150+
key1Document := readJSONFile(mt, "key1-document.json")
3151+
subtype, data := key1Document.Lookup("_id").Binary()
3152+
key1ID := bson.Binary{Subtype: subtype, Data: data}
3153+
3154+
testSetup := func() (*mongo.Client, *mongo.ClientEncryption) {
3155+
for _, collName := range []string{"prefix-suffix", "substring"} {
3156+
mtest.DropEncryptedCollection(mt, mt.Client.Database("db").Collection(collName), encryptedFields)
3157+
cco := options.CreateCollection().SetEncryptedFields(encryptedFields)
3158+
err := mt.Client.Database("db").CreateCollection(context.Background(), collName, cco)
3159+
require.NoError(mt, err, "error on CreateCollection: %v", err)
3160+
}
3161+
err := mt.Client.Database("keyvault").Collection("datakeys").Drop(context.Background())
3162+
require.NoError(mt, err, "error on Drop: %v", err)
3163+
opts := options.Client().ApplyURI(mtest.ClusterURI())
3164+
integtest.AddTestServerAPIVersion(opts)
3165+
keyVaultClient, err := mongo.Connect(opts)
3166+
require.NoError(mt, err, "error on Connect: %v", err)
3167+
datakeysColl := keyVaultClient.Database("keyvault").Collection("datakeys", options.Collection().SetWriteConcern(mtest.MajorityWc))
3168+
_, err = datakeysColl.InsertOne(context.Background(), key1Document)
3169+
require.NoError(mt, err, "error on InsertOne: %v", err)
3170+
kmsProvidersMap := map[string]map[string]any{
3171+
"local": {"key": localMasterKey},
3172+
}
3173+
// Create a ClientEncryption.
3174+
ceo := options.ClientEncryption().
3175+
SetKeyVaultNamespace("keyvault.datakeys").
3176+
SetKmsProviders(kmsProvidersMap)
3177+
clientEncryption, err := mongo.NewClientEncryption(keyVaultClient, ceo)
3178+
require.NoError(mt, err, "error on NewClientEncryption: %v", err)
3179+
3180+
// Create a MongoClient with AutoEncryptionOpts and bypassQueryAnalysis=true.
3181+
aeo := options.AutoEncryption().
3182+
SetKeyVaultNamespace("keyvault.datakeys").
3183+
SetKmsProviders(kmsProvidersMap).
3184+
SetBypassQueryAnalysis(true)
3185+
co := options.Client().SetAutoEncryptionOptions(aeo).ApplyURI(mtest.ClusterURI())
3186+
integtest.AddTestServerAPIVersion(co)
3187+
encryptedClient, err := mongo.Connect(co)
3188+
require.NoError(mt, err, "error on Connect: %v", err)
3189+
3190+
foobarbaz := bson.RawValue{Type: bson.TypeString, Value: bsoncore.AppendString(nil, "foobarbaz")}
3191+
for _, c := range []struct {
3192+
collection string
3193+
textOpts *options.TextOptionsBuilder
3194+
}{
3195+
{
3196+
collection: "prefix-suffix",
3197+
textOpts: options.Text().
3198+
SetPrefix(options.PrefixOptions{
3199+
StrMaxQueryLength: 10,
3200+
StrMinQueryLength: 2,
3201+
}).
3202+
SetSuffix(options.SuffixOptions{
3203+
StrMaxQueryLength: 10,
3204+
StrMinQueryLength: 2,
3205+
}).
3206+
SetCaseSensitive(true).
3207+
SetDiacriticSensitive(true),
3208+
},
3209+
{
3210+
collection: "substring",
3211+
textOpts: options.Text().
3212+
SetSubstring(options.SubstringOptions{
3213+
StrMaxLength: 10,
3214+
StrMaxQueryLength: 10,
3215+
StrMinQueryLength: 2,
3216+
}).
3217+
SetCaseSensitive(true).
3218+
SetDiacriticSensitive(true),
3219+
},
3220+
} {
3221+
coll := encryptedClient.Database("db").Collection(c.collection, options.Collection().SetWriteConcern(mtest.MajorityWc))
3222+
eo := options.Encrypt().
3223+
SetKeyID(key1ID).
3224+
SetAlgorithm("TextPreview").
3225+
SetContentionFactor(0).
3226+
SetTextOptions(c.textOpts)
3227+
insertPayload, err := clientEncryption.Encrypt(context.Background(), foobarbaz, eo)
3228+
require.NoError(mt, err, "error in Encrypt: %v", err)
3229+
_, err = coll.InsertOne(context.Background(), bson.D{{"_id", 0}, {"encryptedText", insertPayload}})
3230+
require.NoError(mt, err, "error in InsertOne: %v", err)
3231+
}
3232+
3233+
return encryptedClient, clientEncryption
3234+
}
3235+
3236+
mt.Run("Case 1: can decrypt a payload", func(mt *mtest.T) {
3237+
encryptedClient, clientEncryption := testSetup()
3238+
defer clientEncryption.Close(context.Background())
3239+
defer encryptedClient.Disconnect(context.Background())
3240+
3241+
foo := bson.RawValue{Type: bson.TypeString, Value: bsoncore.AppendString(nil, "foo")}
3242+
eo := options.Encrypt().
3243+
SetAlgorithm("TextPreview").
3244+
SetKeyID(key1ID).
3245+
SetContentionFactor(0).
3246+
SetTextOptions(options.Text().
3247+
SetPrefix(options.PrefixOptions{
3248+
StrMaxQueryLength: 10,
3249+
StrMinQueryLength: 2,
3250+
}).
3251+
SetCaseSensitive(true).
3252+
SetDiacriticSensitive(true))
3253+
payload, err := clientEncryption.Encrypt(context.Background(), foo, eo)
3254+
require.NoError(mt, err, "error in Encrypt: %v", err)
3255+
coll := encryptedClient.Database("db").Collection("prefix-suffix")
3256+
got, err := coll.FindOne(context.Background(), bson.D{
3257+
{"$expr", bson.D{
3258+
{"$encStrStartsWith", bson.D{
3259+
{"input", "$encryptedText"},
3260+
{"prefix", payload},
3261+
}},
3262+
}},
3263+
}).Raw()
3264+
require.NoError(mt, err, "error in FindOne: %v", err)
3265+
assert.FailNow(mt, "got: %v", got)
3266+
})
3267+
})
31473268
}
31483269

31493270
func getWatcher(mt *mtest.T, streamType mongo.StreamType, cpt *cseProseTest) watcher {

internal/spectest/skip.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -822,15 +822,6 @@ var skipTests = map[string][]string{
822822
"TestUnifiedSpec/client-side-operations-timeout/tests/tailable-awaitData.json/error_on_watch_if_maxAwaitTimeMS_is_equal_to_timeoutMS",
823823
},
824824

825-
// TODO(GODRIVER-3620): Support text indexes with auto encryption.
826-
"Support text indexes with auto encryption (GODRIVER-3620)": {
827-
"TestUnifiedSpec/client-side-encryption/tests/unified/QE-Text-cleanupStructuredEncryptionData.json",
828-
"TestUnifiedSpec/client-side-encryption/tests/unified/QE-Text-compactStructuredEncryptionData.json",
829-
"TestUnifiedSpec/client-side-encryption/tests/unified/QE-Text-prefixPreview.json",
830-
"TestUnifiedSpec/client-side-encryption/tests/unified/QE-Text-substringPreview.json",
831-
"TestUnifiedSpec/client-side-encryption/tests/unified/QE-Text-suffixPreview.json",
832-
},
833-
834825
// TODO(GODRIVER-3403): Support queryable encryption in Client.BulkWrite.
835826
"Support queryable encryption in Client.BulkWrite (GODRIVER-3403)": {
836827
"TestUnifiedSpec/crud/tests/unified/client-bulkWrite-qe.json",

mongo/client_encryption.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,27 @@ func transformExplicitEncryptionOptions(opts ...options.Lister[options.EncryptOp
243243
}
244244
transformed.SetRangeOptions(transformedRange)
245245
}
246+
if args.TextOptions != nil {
247+
textArgs, _ := mongoutil.NewOptions[options.TextOptions](args.TextOptions)
248+
249+
transformedText := mcopts.ExplicitTextOptions{
250+
CaseSensitive: textArgs.CaseSensitive,
251+
DiacriticSensitive: textArgs.DiacriticSensitive,
252+
}
253+
if textArgs.Substring != nil {
254+
substringOpts := mcopts.SubstringOptions(*textArgs.Substring)
255+
transformedText.Substring = &substringOpts
256+
}
257+
if textArgs.Prefix != nil {
258+
prefixOpts := mcopts.PrefixOptions(*textArgs.Prefix)
259+
transformedText.Prefix = &prefixOpts
260+
}
261+
if textArgs.Suffix != nil {
262+
suffixOpts := mcopts.SuffixOptions(*textArgs.Suffix)
263+
transformedText.Suffix = &suffixOpts
264+
}
265+
transformed.SetTextOptions(transformedText)
266+
}
246267
return transformed
247268
}
248269

mongo/options/encryptoptions.go

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type RangeOptions struct {
2727
Precision *int32
2828
}
2929

30-
// RangeOptionsBuilder contains options to configure Rangeopts for queryeable
30+
// RangeOptionsBuilder contains options to configure RangeOptions for queryeable
3131
// encryption. Each option can be set through setter functions. See
3232
// documentation for each setter function for an explanation of the option.
3333
type RangeOptionsBuilder struct {
@@ -99,6 +99,105 @@ func (ro *RangeOptionsBuilder) SetPrecision(precision int32) *RangeOptionsBuilde
9999
return ro
100100
}
101101

102+
// TextOptions specifies index options for a Queryable Encryption field supporting "test" queries.
103+
//
104+
// See corresponding setter methods for documentation.
105+
type TextOptions struct {
106+
Substring *SubstringOptions
107+
Prefix *PrefixOptions
108+
Suffix *SuffixOptions
109+
CaseSensitive bool
110+
DiacriticSensitive bool
111+
}
112+
113+
type SubstringOptions struct {
114+
StrMaxLength int32
115+
StrMinQueryLength int32
116+
StrMaxQueryLength int32
117+
}
118+
119+
type PrefixOptions struct {
120+
StrMinQueryLength int32
121+
StrMaxQueryLength int32
122+
}
123+
124+
type SuffixOptions struct {
125+
StrMinQueryLength int32
126+
StrMaxQueryLength int32
127+
}
128+
129+
// TextOptionsBuilder contains options to configure TextOptions for queryeable
130+
// encryption. Each option can be set through setter functions. See
131+
// documentation for each setter function for an explanation of the option.
132+
type TextOptionsBuilder struct {
133+
Opts []func(*TextOptions) error
134+
}
135+
136+
// Text creates a new TextOptions instance.
137+
func Text() *TextOptionsBuilder {
138+
return &TextOptionsBuilder{}
139+
}
140+
141+
// List returns a list of TextOptions setter functions.
142+
func (to *TextOptionsBuilder) List() []func(*TextOptions) error {
143+
return to.Opts
144+
}
145+
146+
// SetSubstring sets the text index substring value.
147+
func (to *TextOptionsBuilder) SetSubstring(substring SubstringOptions) *TextOptionsBuilder {
148+
to.Opts = append(to.Opts, func(opts *TextOptions) error {
149+
opts.Substring = &substring
150+
151+
return nil
152+
})
153+
154+
return to
155+
}
156+
157+
// SetPrefix sets the text index prefix value.
158+
func (to *TextOptionsBuilder) SetPrefix(prefix PrefixOptions) *TextOptionsBuilder {
159+
to.Opts = append(to.Opts, func(opts *TextOptions) error {
160+
opts.Prefix = &prefix
161+
162+
return nil
163+
})
164+
165+
return to
166+
}
167+
168+
// SetSuffix sets the text index suffix value.
169+
func (to *TextOptionsBuilder) SetSuffix(suffix SuffixOptions) *TextOptionsBuilder {
170+
to.Opts = append(to.Opts, func(opts *TextOptions) error {
171+
opts.Suffix = &suffix
172+
173+
return nil
174+
})
175+
176+
return to
177+
}
178+
179+
// SetCaseSensitive sets the text index caseSensitive value.
180+
func (to *TextOptionsBuilder) SetCaseSensitive(caseSensitive bool) *TextOptionsBuilder {
181+
to.Opts = append(to.Opts, func(opts *TextOptions) error {
182+
opts.CaseSensitive = caseSensitive
183+
184+
return nil
185+
})
186+
187+
return to
188+
}
189+
190+
// SetDiacriticSensitive sets the text index diacriticSensitive value.
191+
func (to *TextOptionsBuilder) SetDiacriticSensitive(diacriticSensitive bool) *TextOptionsBuilder {
192+
to.Opts = append(to.Opts, func(opts *TextOptions) error {
193+
opts.DiacriticSensitive = diacriticSensitive
194+
195+
return nil
196+
})
197+
198+
return to
199+
}
200+
102201
// EncryptOptions represents arguments to explicitly encrypt a value.
103202
//
104203
// See corresponding setter methods for documentation.
@@ -109,6 +208,7 @@ type EncryptOptions struct {
109208
QueryType string
110209
ContentionFactor *int64
111210
RangeOptions *RangeOptionsBuilder
211+
TextOptions *TextOptionsBuilder
112212
}
113213

114214
// EncryptOptionsBuilder contains options to configure Encryptopts for
@@ -203,3 +303,14 @@ func (e *EncryptOptionsBuilder) SetRangeOptions(ro *RangeOptionsBuilder) *Encryp
203303

204304
return e
205305
}
306+
307+
// SetTextOptions specifies the options to use for text queries.
308+
func (e *EncryptOptionsBuilder) SetTextOptions(to *TextOptionsBuilder) *EncryptOptionsBuilder {
309+
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
310+
opts.TextOptions = to
311+
312+
return nil
313+
})
314+
315+
return e
316+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"fields": [
3+
{
4+
"keyId": {
5+
"$binary": {
6+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
7+
"subType": "04"
8+
}
9+
},
10+
"path": "encryptedText",
11+
"bsonType": "string",
12+
"queries": [
13+
{
14+
"queryType": "prefixPreview",
15+
"strMinQueryLength": {
16+
"$numberInt": "2"
17+
},
18+
"strMaxQueryLength": {
19+
"$numberInt": "10"
20+
},
21+
"caseSensitive": true,
22+
"diacriticSensitive": true
23+
},
24+
{
25+
"queryType": "suffixPreview",
26+
"strMinQueryLength": {
27+
"$numberInt": "2"
28+
},
29+
"strMaxQueryLength": {
30+
"$numberInt": "10"
31+
},
32+
"caseSensitive": true,
33+
"diacriticSensitive": true
34+
}
35+
]
36+
}
37+
]
38+
}

0 commit comments

Comments
 (0)