@@ -11,7 +11,10 @@ import (
11
11
12
12
func ExampleNewConverter () {
13
13
// Remeber to use `filter.WithArrayDriver(pg.Array)` when using github.com/lib/pq
14
- converter := filter .NewConverter (filter .WithNestedJSONB ("meta" , "created_at" , "updated_at" ))
14
+ converter , err := filter .NewConverter (filter .WithNestedJSONB ("meta" , "created_at" , "updated_at" ))
15
+ if err != nil {
16
+ // handle error
17
+ }
15
18
16
19
mongoFilterQuery := `{
17
20
"name": "John",
@@ -33,7 +36,7 @@ func ExampleNewConverter() {
33
36
func TestConverter_Convert (t * testing.T ) {
34
37
tests := []struct {
35
38
name string
36
- option filter.Option
39
+ option [] filter.Option
37
40
input string
38
41
conditions string
39
42
values []any
@@ -73,15 +76,15 @@ func TestConverter_Convert(t *testing.T) {
73
76
},
74
77
{
75
78
"nested jsonb single value" ,
76
- filter .WithNestedJSONB ("meta" ),
79
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
77
80
`{"name": "John"}` ,
78
81
`("meta"->>'name' = $1)` ,
79
82
[]any {"John" },
80
83
nil ,
81
84
},
82
85
{
83
86
"nested jsonb multi value" ,
84
- filter .WithNestedJSONB ("meta" , "created_at" , "updated_at" ),
87
+ [] filter.Option { filter . WithNestedJSONB ("meta" , "created_at" , "updated_at" )} ,
85
88
`{"created_at": {"$gte": "2020-01-01T00:00:00Z"}, "name": "John", "role": "admin"}` ,
86
89
`(("created_at" >= $1) AND ("meta"->>'name' = $2) AND ("meta"->>'role' = $3))` ,
87
90
[]any {"2020-01-01T00:00:00Z" , "John" , "admin" },
@@ -296,7 +299,7 @@ func TestConverter_Convert(t *testing.T) {
296
299
},
297
300
{
298
301
"null jsonb column" ,
299
- filter .WithNestedJSONB ("meta" ),
302
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
300
303
`{"name": null}` ,
301
304
`(jsonb_path_match(meta, 'exists($.name)') AND "meta"->>'name' IS NULL)` ,
302
305
nil ,
@@ -312,15 +315,15 @@ func TestConverter_Convert(t *testing.T) {
312
315
},
313
316
{
314
317
"not $exists jsonb column" ,
315
- filter .WithNestedJSONB ("meta" ),
318
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
316
319
`{"name": {"$exists": false}}` ,
317
320
`(NOT jsonb_path_match(meta, 'exists($.name)'))` ,
318
321
nil ,
319
322
nil ,
320
323
},
321
324
{
322
325
"$exists jsonb column" ,
323
- filter .WithNestedJSONB ("meta" ),
326
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
324
327
`{"name": {"$exists": true}}` ,
325
328
`(jsonb_path_match(meta, 'exists($.name)'))` ,
326
329
nil ,
@@ -344,31 +347,31 @@ func TestConverter_Convert(t *testing.T) {
344
347
},
345
348
{
346
349
"$elemMatch on jsonb column" ,
347
- filter .WithNestedJSONB ("meta" ),
350
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
348
351
`{"name": {"$elemMatch": {"$eq": "John"}}}` ,
349
352
`EXISTS (SELECT 1 FROM jsonb_array_elements("meta"->'name') AS __filter_placeholder WHERE ("__filter_placeholder"::text = $1))` ,
350
353
[]any {"John" },
351
354
nil ,
352
355
},
353
356
{
354
357
"$elemMatch with $gt" ,
355
- filter .WithPlaceholderName ("__placeholder" ),
358
+ [] filter.Option { filter . WithAllowAllColumns (), filter . WithPlaceholderName ("__placeholder" )} ,
356
359
`{"age": {"$elemMatch": {"$gt": 18}}}` ,
357
360
`EXISTS (SELECT 1 FROM unnest("age") AS __placeholder WHERE ("__placeholder"::text > $1))` ,
358
361
[]any {float64 (18 )},
359
362
nil ,
360
363
},
361
364
{
362
365
"numeric comparison bug with jsonb column" ,
363
- filter .WithNestedJSONB ("meta" ),
366
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
364
367
`{"foo": {"$gt": 0}}` ,
365
368
`(("meta"->>'foo')::numeric > $1)` ,
366
369
[]any {float64 (0 )},
367
370
nil ,
368
371
},
369
372
{
370
373
"numeric comparison against null with jsonb column" ,
371
- filter .WithNestedJSONB ("meta" ),
374
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
372
375
`{"foo": {"$gt": null}}` ,
373
376
`("meta"->>'foo' > $1)` ,
374
377
[]any {nil },
@@ -392,23 +395,23 @@ func TestConverter_Convert(t *testing.T) {
392
395
},
393
396
{
394
397
"compare two jsonb fields" ,
395
- filter .WithNestedJSONB ("meta" ),
398
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
396
399
`{"foo": {"$eq": {"$field": "bar"}}}` ,
397
400
`("meta"->>'foo' = "meta"->>'bar')` ,
398
401
nil ,
399
402
nil ,
400
403
},
401
404
{
402
405
"compare two jsonb fields with numeric comparison" ,
403
- filter .WithNestedJSONB ("meta" ),
406
+ [] filter.Option { filter . WithNestedJSONB ("meta" )} ,
404
407
`{"foo": {"$lt": {"$field": "bar"}}}` ,
405
408
`(("meta"->>'foo')::numeric < ("meta"->>'bar')::numeric)` ,
406
409
nil ,
407
410
nil ,
408
411
},
409
412
{
410
413
"compare two fields with simple expression" ,
411
- filter .WithNestedJSONB ("meta" , "foo" ),
414
+ [] filter.Option { filter . WithNestedJSONB ("meta" , "foo" )} ,
412
415
`{"foo": {"$field": "bar"}}` ,
413
416
`("foo" = "meta"->>'bar')` ,
414
417
nil ,
@@ -426,7 +429,13 @@ func TestConverter_Convert(t *testing.T) {
426
429
427
430
for _ , tt := range tests {
428
431
t .Run (tt .name , func (t * testing.T ) {
429
- c := filter .NewConverter (tt .option )
432
+ if tt .option == nil {
433
+ tt .option = []filter.Option {filter .WithAllowAllColumns ()}
434
+ }
435
+ c , err := filter .NewConverter (tt .option ... )
436
+ if err != nil {
437
+ t .Fatal (err )
438
+ }
430
439
conditions , values , err := c .Convert ([]byte (tt .input ), 1 )
431
440
if err != nil && (tt .err == nil || err .Error () != tt .err .Error ()) {
432
441
t .Errorf ("Converter.Convert() error = %v, wantErr %v" , err , tt .err )
@@ -447,7 +456,7 @@ func TestConverter_Convert(t *testing.T) {
447
456
}
448
457
449
458
func TestConverter_Convert_startAtParameterIndex (t * testing.T ) {
450
- c := filter .NewConverter ()
459
+ c , _ := filter .NewConverter (filter . WithAllowAllColumns () )
451
460
conditions , values , err := c .Convert ([]byte (`{"name": "John", "password": "secret"}` ), 10 )
452
461
if err != nil {
453
462
t .Fatal (err )
@@ -476,7 +485,7 @@ func TestConverter_Convert_startAtParameterIndex(t *testing.T) {
476
485
}
477
486
478
487
func TestConverter_WithEmptyCondition (t * testing.T ) {
479
- c := filter .NewConverter (filter .WithEmptyCondition ("TRUE" ))
488
+ c , _ := filter .NewConverter (filter . WithAllowAllColumns (), filter .WithEmptyCondition ("TRUE" ))
480
489
conditions , values , err := c .Convert ([]byte (`{}` ), 1 )
481
490
if err != nil {
482
491
t .Fatal (err )
@@ -490,6 +499,8 @@ func TestConverter_WithEmptyCondition(t *testing.T) {
490
499
}
491
500
492
501
func TestConverter_NoConstructor (t * testing.T ) {
502
+ t .Skip () // this is currently not supported since we introduced the access control options
503
+
493
504
c := & filter.Converter {}
494
505
conditions , values , err := c .Convert ([]byte (`{"name": "John"}` ), 1 )
495
506
if err != nil {
@@ -524,3 +535,85 @@ func TestConverter_CopyReference(t *testing.T) {
524
535
t .Errorf ("Converter.Convert() conditions = %v, want %v" , conditions , want )
525
536
}
526
537
}
538
+
539
+ func TestConverter_RequireAccessControl (t * testing.T ) {
540
+ if _ , err := filter .NewConverter (); err != filter .ErrNoAccessOption {
541
+ t .Errorf ("NewConverter() error = %v, want %v" , err , filter .ErrNoAccessOption )
542
+ }
543
+ if _ , err := filter .NewConverter (filter .WithPlaceholderName ("___test___" )); err != filter .ErrNoAccessOption {
544
+ t .Errorf ("NewConverter() error = %v, want %v" , err , filter .ErrNoAccessOption )
545
+ }
546
+ if _ , err := filter .NewConverter (filter .WithAllowAllColumns ()); err != nil {
547
+ t .Errorf ("NewConverter() error = %v, want no error" , err )
548
+ }
549
+ if _ , err := filter .NewConverter (filter .WithAllowColumns ("name" , "map" )); err != nil {
550
+ t .Errorf ("NewConverter() error = %v, want no error" , err )
551
+ }
552
+ if _ , err := filter .NewConverter (filter .WithAllowColumns ()); err != nil {
553
+ t .Errorf ("NewConverter() error = %v, want no error" , err )
554
+ }
555
+ if _ , err := filter .NewConverter (filter .WithNestedJSONB ("meta" , "created_at" , "updated_at" )); err != nil {
556
+ t .Errorf ("NewConverter() error = %v, want no error" , err )
557
+ }
558
+ if _ , err := filter .NewConverter (filter .WithDisallowColumns ("password" )); err != nil {
559
+ t .Errorf ("NewConverter() error = %v, want no error" , err )
560
+ }
561
+ }
562
+
563
+ func TestConverter_AccessControl (t * testing.T ) {
564
+ f := func (in string , wantErr error , options ... filter.Option ) func (t * testing.T ) {
565
+ t .Helper ()
566
+ return func (t * testing.T ) {
567
+ t .Helper ()
568
+ c := & filter.Converter {}
569
+ if options != nil {
570
+ c , _ = filter .NewConverter (options ... )
571
+ // requirement of access control is tested above.
572
+ }
573
+ q , _ , err := c .Convert ([]byte (in ), 1 )
574
+ t .Log (in , "->" , q , err )
575
+ if wantErr == nil && err != nil {
576
+ t .Fatalf ("no error returned, expected error: %v" , err )
577
+ } else if wantErr != nil && err == nil {
578
+ t .Fatalf ("expected error: %v" , wantErr )
579
+ } else if wantErr != nil && wantErr .Error () != err .Error () {
580
+ t .Fatalf ("error mismatch: %v != %v" , err , wantErr )
581
+ }
582
+ }
583
+ }
584
+
585
+ no := func (c string ) error { return filter.ColumnNotAllowedError {Column : c } }
586
+
587
+ t .Run ("allow all, single root field" ,
588
+ f (`{"name":"John"}` , nil , filter .WithAllowAllColumns ()))
589
+ t .Run ("allow name, single allowed root field" ,
590
+ f (`{"name":"John"}` , nil , filter .WithAllowColumns ("name" )))
591
+ t .Run ("allow name, single disallowed root field" ,
592
+ f (`{"password":"hacks"}` , no ("password" ), filter .WithAllowColumns ("name" )))
593
+ t .Run ("allowed meta, single allowed nested field" ,
594
+ f (`{"map":"de_dust"}` , nil , filter .WithNestedJSONB ("meta" , "created_at" )))
595
+ t .Run ("allowed nested excemption, single allowed field" ,
596
+ f (`{"created_at":"de_dust"}` , nil , filter .WithNestedJSONB ("meta" , "created_at" )))
597
+ t .Run ("multi allow, single allowed root field" ,
598
+ f (`{"name":"John"}` , nil , filter .WithAllowColumns ("name" , "email" )))
599
+ t .Run ("multi allow, two allowed root fields" ,
600
+ f (
`{"name":"John", "email":"[email protected] "}` ,
nil ,
filter .
WithAllowColumns (
"name" ,
"email" )))
601
+ t .Run ("multi allow, mixes access" ,
602
+ f (`{"name":"John", "password":"hacks"}` , no ("password" ), filter .WithAllowColumns ("name" , "email" )))
603
+ t .Run ("multi allow, mixes access" ,
604
+ f (`{"name":"John", "password":"hacks"}` , no ("password" ), filter .WithAllowColumns ("name" , "email" )))
605
+ t .Run ("allowed basic $and" ,
606
+ f (`{"$and": [{"name": "John"}, {"version": 3}]}` , nil , filter .WithAllowColumns ("name" , "version" )))
607
+ t .Run ("disallowed basic $and" ,
608
+ f (`{"$and": [{"name": "John"}, {"version": 3}]}` , no ("version" ), filter .WithAllowColumns ("name" )))
609
+ t .Run ("allow all but one" ,
610
+ f (`{"name": "John"}` , nil , filter .WithAllowAllColumns (), filter .WithDisallowColumns ("password" )))
611
+ t .Run ("allow all but one, failing" ,
612
+ f (`{"$and": [{"name": "John"}, {"password": "hacks"}]}` , no ("password" ), filter .WithAllowAllColumns (), filter .WithDisallowColumns ("password" )))
613
+ t .Run ("nested but disallow password, allow exception" ,
614
+ f (`{"created_at": "1"}` , nil , filter .WithNestedJSONB ("meta" , "created_at" ), filter .WithDisallowColumns ("password" )))
615
+ t .Run ("nested but disallow password, allow nested" ,
616
+ f (`{"map": "de_dust"}` , nil , filter .WithNestedJSONB ("meta" , "created_at" ), filter .WithDisallowColumns ("password" )))
617
+ t .Run ("nested but disallow password, disallow" ,
618
+ f (`{"password": "hacks"}` , no ("password" ), filter .WithNestedJSONB ("meta" , "created_at" ), filter .WithDisallowColumns ("password" )))
619
+ }
0 commit comments