@@ -135,6 +135,22 @@ func signedDefaultBucketMultiURL(t *testing.T, method, server, bucket, key strin
135135 return signed
136136}
137137
138+ func signedDefaultBucketSingleURL (t * testing.T , method , bucket , key string , now time.Time ) string {
139+ t .Helper ()
140+ signed , err := signing .SignURLForModeWithOptions (signing.SignInput {
141+ Method : method ,
142+ BaseURL : "https://files.example" ,
143+ Bucket : bucket ,
144+ Key : key ,
145+ Expires : now .Add (time .Minute ),
146+ Secret : "secret" ,
147+ }, publicpath .ModeSingle , signing.SignOptions {DefaultBucketPath : true })
148+ if err != nil {
149+ t .Fatalf ("SignURLForModeWithOptions() error = %v" , err )
150+ }
151+ return signed
152+ }
153+
138154func testMultiEdge (pub * fakePublisher , cfg config.EdgeConfig , fetchers map [string ]objectFetcher ) (* edgeServer , * pending.Registry ) {
139155 reg := pending .NewRegistry (pending.Options {})
140156 cfg .IngestURL = "https://edge.internal/_ingest"
@@ -620,6 +636,136 @@ func TestSingleServerPublishesDefaultSubjectAndEmptyServer(t *testing.T) {
620636 }
621637}
622638
639+ func TestSingleServerDefaultBucketShortPathPublishesTicket (t * testing.T ) {
640+ pub := & fakePublisher {err : errors .New ("stop after publish" )}
641+ reg := pending .NewRegistry (pending.Options {})
642+ edge := newEdgeServer (config.EdgeConfig {
643+ IngestURL : "https://edge.internal/_ingest" ,
644+ DefaultBucket : "demo" ,
645+ AllowedBuckets : []string {"demo" },
646+ NATS : config.NATSConfig {Subject : "air3.tickets" },
647+ Signing : config.SigningConfig {Disabled : true },
648+ Timeouts : config.TimeoutConfig {PendingRequestTTL : time .Second },
649+ }, reg , pub , nil )
650+ edge .newToken = func () (string , error ) { return "single-default-token" , nil }
651+
652+ resp := httptest .NewRecorder ()
653+ edge .ServeHTTP (resp , httptest .NewRequest (http .MethodGet , "/file.txt" , nil ))
654+
655+ published := pub .snapshot ()
656+ if len (published ) != 1 {
657+ t .Fatalf ("published tickets = %#v, want one" , published )
658+ }
659+ if published [0 ].Server != "" || published [0 ].Bucket != "demo" || published [0 ].Key != "file.txt" {
660+ t .Fatalf ("published ticket = %#v, want empty server bucket demo key file.txt" , published [0 ])
661+ }
662+ }
663+
664+ func TestSingleServerDefaultBucketShortFormWinsOverBucketPrefix (t * testing.T ) {
665+ pub := & fakePublisher {err : errors .New ("stop after publish" )}
666+ reg := pending .NewRegistry (pending.Options {})
667+ edge := newEdgeServer (config.EdgeConfig {
668+ IngestURL : "https://edge.internal/_ingest" ,
669+ DefaultBucket : "demo" ,
670+ AllowedBuckets : []string {"demo" },
671+ Signing : config.SigningConfig {Disabled : true },
672+ Timeouts : config.TimeoutConfig {PendingRequestTTL : time .Second },
673+ }, reg , pub , nil )
674+ edge .newToken = func () (string , error ) { return "single-explicit-token" , nil }
675+
676+ resp := httptest .NewRecorder ()
677+ edge .ServeHTTP (resp , httptest .NewRequest (http .MethodGet , "/demo/file.txt" , nil ))
678+
679+ published := pub .snapshot ()
680+ if len (published ) != 1 {
681+ t .Fatalf ("published tickets = %#v, want one" , published )
682+ }
683+ if published [0 ].Server != "" || published [0 ].Bucket != "demo" || published [0 ].Key != "demo/file.txt" {
684+ t .Fatalf ("published ticket = %#v, want empty server bucket demo key demo/file.txt" , published [0 ])
685+ }
686+ }
687+
688+ func TestSingleServerDefaultBucketShortPathAllowlistUsesResolvedBucket (t * testing.T ) {
689+ pub := & fakePublisher {}
690+ reg := pending .NewRegistry (pending.Options {})
691+ edge := newEdgeServer (config.EdgeConfig {
692+ IngestURL : "https://edge.internal/_ingest" ,
693+ DefaultBucket : "demo" ,
694+ AllowedBuckets : []string {"other" },
695+ Signing : config.SigningConfig {Disabled : true },
696+ Timeouts : config.TimeoutConfig {PendingRequestTTL : time .Second },
697+ }, reg , pub , nil )
698+
699+ resp := httptest .NewRecorder ()
700+ edge .ServeHTTP (resp , httptest .NewRequest (http .MethodGet , "/file.txt" , nil ))
701+ if got := resp .Result ().StatusCode ; got != http .StatusForbidden {
702+ t .Fatalf ("status = %d, want %d" , got , http .StatusForbidden )
703+ }
704+ if pub .count () != 0 {
705+ t .Fatalf ("published %d tickets, want 0" , pub .count ())
706+ }
707+ }
708+
709+ func TestSignedSingleServerDefaultBucketShortURLAcceptedAndTamperingRejected (t * testing.T ) {
710+ now := time .Now ()
711+ signed := signedDefaultBucketSingleURL (t , http .MethodGet , "demo" , "file.txt" , now )
712+
713+ t .Run ("accepted" , func (t * testing.T ) {
714+ pub := & fakePublisher {err : errors .New ("stop after publish" )}
715+ reg := pending .NewRegistry (pending.Options {})
716+ edge := newEdgeServer (config.EdgeConfig {
717+ IngestURL : "https://edge.internal/_ingest" ,
718+ DefaultBucket : "demo" ,
719+ AllowedBuckets : []string {"demo" },
720+ Signing : config.SigningConfig {Secret : "secret" },
721+ Timeouts : config.TimeoutConfig {PendingRequestTTL : time .Second },
722+ }, reg , pub , nil )
723+ edge .now = func () time.Time { return now }
724+ edge .newToken = func () (string , error ) { return "signed-single-default-token" , nil }
725+
726+ resp := httptest .NewRecorder ()
727+ edge .ServeHTTP (resp , httptest .NewRequest (http .MethodGet , signed , nil ))
728+
729+ published := pub .snapshot ()
730+ if len (published ) != 1 {
731+ t .Fatalf ("published tickets = %#v, want one" , published )
732+ }
733+ if published [0 ].Server != "" || published [0 ].Bucket != "demo" || published [0 ].Key != "file.txt" {
734+ t .Fatalf ("published ticket = %#v, want empty server bucket demo key file.txt" , published [0 ])
735+ }
736+ })
737+
738+ for _ , tt := range []struct {
739+ name string
740+ raw string
741+ }{
742+ {name : "path" , raw : strings .Replace (signed , "/file.txt" , "/other.txt" , 1 )},
743+ {name : "signature" , raw : strings .Replace (signed , "sig=" , "sig=bad" , 1 )},
744+ } {
745+ t .Run ("tampered " + tt .name , func (t * testing.T ) {
746+ pub := & fakePublisher {}
747+ reg := pending .NewRegistry (pending.Options {})
748+ edge := newEdgeServer (config.EdgeConfig {
749+ IngestURL : "https://edge.internal/_ingest" ,
750+ DefaultBucket : "demo" ,
751+ AllowedBuckets : []string {"demo" },
752+ Signing : config.SigningConfig {Secret : "secret" },
753+ Timeouts : config.TimeoutConfig {PendingRequestTTL : time .Second },
754+ }, reg , pub , nil )
755+ edge .now = func () time.Time { return now }
756+
757+ resp := httptest .NewRecorder ()
758+ edge .ServeHTTP (resp , httptest .NewRequest (http .MethodGet , tt .raw , nil ))
759+ if got := resp .Result ().StatusCode ; got != http .StatusForbidden {
760+ t .Fatalf ("status = %d, want %d" , got , http .StatusForbidden )
761+ }
762+ if pub .count () != 0 {
763+ t .Fatalf ("published %d tickets, want 0" , pub .count ())
764+ }
765+ })
766+ }
767+ }
768+
623769func TestMultiServerConnectorPublishesRoutedSubjectAndTicket (t * testing.T ) {
624770 pub := & fakePublisher {err : errors .New ("stop after publish" )}
625771 edge , _ := testMultiEdge (pub , config.EdgeConfig {
0 commit comments