From bd4325b75bc7eb479fef9adf7d5e8f85ce6cf052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 5 Nov 2026 15:28:49 +0100 Subject: [PATCH] feat(policies): pass request lengths to the proxy policies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The proxy policies middleware now passes the Content-Length of the request body and the TUS Upload-Length header to the policies engine as input.request.content_length and input.request.upload_length. This allows rejecting uploads that are too big before any data is transferred, instead of only in postprocessing after the upload has completed. We add them to the request instead of setting resource.size, because the resource describes the target of the request (the middleware may even stat it) and in postprocessing resource.size is the actual file size. This can only be an early check. The TUS concatenation extension allows merging several smaller uploads into one file, so the file size still has to be checked in postprocessing. protojson encodes uint64 values as strings with camelCase keys, so NewEnvironmentFromPB copies the lengths explicitly. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Jörn Friedrich Dreyer --- .../messages/policies/v0/policies.pb.go | 79 ++++++++++++------- .../policies/v0/policies.swagger.json | 10 +++ .../messages/policies/v0/policies.proto | 4 + services/policies/README.md | 25 ++++++ services/policies/pkg/engine/engine.go | 8 ++ services/policies/pkg/engine/engine_test.go | 19 +++++ .../policies/pkg/engine/opa/engine_test.go | 23 ++++++ services/proxy/pkg/middleware/policies.go | 9 +++ .../proxy/pkg/middleware/policies_test.go | 46 +++++++++++ 9 files changed, 195 insertions(+), 28 deletions(-) diff --git a/protogen/gen/opencloud/messages/policies/v0/policies.pb.go b/protogen/gen/opencloud/messages/policies/v0/policies.pb.go index 2d95ac1574..95021478e4 100644 --- a/protogen/gen/opencloud/messages/policies/v0/policies.pb.go +++ b/protogen/gen/opencloud/messages/policies/v0/policies.pb.go @@ -223,6 +223,10 @@ type Request struct { Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + // Content-Length of the request body, 0 if unknown + ContentLength uint64 `protobuf:"varint,3,opt,name=content_length,json=contentLength,proto3" json:"content_length,omitempty"` + // TUS Upload-Length header, the declared total size of an upload, 0 if absent + UploadLength uint64 `protobuf:"varint,4,opt,name=upload_length,json=uploadLength,proto3" json:"upload_length,omitempty"` } func (x *Request) Reset() { @@ -271,6 +275,20 @@ func (x *Request) GetPath() string { return "" } +func (x *Request) GetContentLength() uint64 { + if x != nil { + return x.ContentLength + } + return 0 +} + +func (x *Request) GetUploadLength() uint64 { + if x != nil { + return x.UploadLength + } + return 0 +} + type Environment struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -487,35 +505,40 @@ var file_opencloud_messages_policies_v0_policies_proto_rawDesc = []byte{ 0x75, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, - 0x22, 0x35, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x8d, 0x02, 0x0a, 0x0b, 0x45, 0x6e, 0x76, 0x69, - 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, - 0x2e, 0x76, 0x30, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x41, - 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x30, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x44, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x22, 0x81, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, + 0x23, 0x0a, 0x0d, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4c, 0x65, + 0x6e, 0x67, 0x74, 0x68, 0x22, 0x8d, 0x02, 0x0a, 0x0b, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, - 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2a, 0x25, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, - 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x50, 0x50, 0x10, 0x00, 0x12, 0x0e, - 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x42, 0x4f, - 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, - 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, - 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x73, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2f, 0x76, 0x30, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, + 0x65, 0x12, 0x38, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x30, + 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, + 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x28, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x76, + 0x30, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2a, 0x25, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x0c, 0x0a, + 0x08, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x50, 0x50, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x53, + 0x54, 0x41, 0x47, 0x45, 0x5f, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x42, 0x4f, 0x5a, 0x4d, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2d, 0x65, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x70, + 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protogen/gen/opencloud/services/policies/v0/policies.swagger.json b/protogen/gen/opencloud/services/policies/v0/policies.swagger.json index 7b514d5346..6e4f432931 100644 --- a/protogen/gen/opencloud/services/policies/v0/policies.swagger.json +++ b/protogen/gen/opencloud/services/policies/v0/policies.swagger.json @@ -134,6 +134,16 @@ }, "path": { "type": "string" + }, + "contentLength": { + "type": "string", + "format": "uint64", + "title": "Content-Length of the request body, 0 if unknown" + }, + "uploadLength": { + "type": "string", + "format": "uint64", + "title": "TUS Upload-Length header, the declared total size of an upload, 0 if absent" } } }, diff --git a/protogen/proto/opencloud/messages/policies/v0/policies.proto b/protogen/proto/opencloud/messages/policies/v0/policies.proto index 2d49a02d3c..2da19ac1f6 100644 --- a/protogen/proto/opencloud/messages/policies/v0/policies.proto +++ b/protogen/proto/opencloud/messages/policies/v0/policies.proto @@ -32,6 +32,10 @@ message Resource { message Request { string method = 1; string path = 2; + // Content-Length of the request body, 0 if unknown + uint64 content_length = 3; + // TUS Upload-Length header, the declared total size of an upload, 0 if absent + uint64 upload_length = 4; } enum Stage { diff --git a/services/policies/README.md b/services/policies/README.md index 8d210022f9..bcc3cafd45 100644 --- a/services/policies/README.md +++ b/services/policies/README.md @@ -185,6 +185,31 @@ To identify available keys for OPA, you need to look at [`engine.go`](https://gi Note that which keys are available depends on the module it is used in. +### Request Lengths + +In the proxy module, `input.request.content_length` contains the `Content-Length` of the request body and `input.request.upload_length` the total size of an upload as declared by the TUS `Upload-Length` header. Both are `0` if unknown. They allow rejecting uploads that are too big before any data is transferred: + +```rego +package proxy + +import future.keywords.if + +default granted := true + +# TUS creation declares the total size of the upload +granted = false if { + input.request.upload_length > 10737418240 +} + +# a plain PUT carries the whole file, a PATCH a chunk +granted = false if { + input.request.method == ["PUT", "PATCH"][_] + input.request.content_length > 10737418240 +} +``` + +This is only an early check. The TUS concatenation extension can merge several smaller uploads into one file, so the actual file size has to be checked in postprocessing using `input.resource.size`. + ## Rego Extensions Besides the standard rego built-in functions, the following functions are added on top: diff --git a/services/policies/pkg/engine/engine.go b/services/policies/pkg/engine/engine.go index c40a81ac97..923afa049f 100644 --- a/services/policies/pkg/engine/engine.go +++ b/services/policies/pkg/engine/engine.go @@ -40,6 +40,10 @@ type Resource struct { type Request struct { Method string `json:"method"` Path string `json:"path"` + // ContentLength is the length of the request body, 0 if unknown. + ContentLength uint64 `json:"content_length"` + // UploadLength is the total size of an upload as declared by the TUS Upload-Length header, 0 if absent. + UploadLength uint64 `json:"upload_length"` } // Environment contains every data that is needed to decide if the request should pass or not @@ -63,6 +67,10 @@ func NewEnvironmentFromPB(pEnv *v0.Environment) (Environment, error) { return env, err } + // protojson encodes uint64 as strings with camelCase keys, so copy the lengths explicitly + env.Request.ContentLength = pEnv.GetRequest().GetContentLength() + env.Request.UploadLength = pEnv.GetRequest().GetUploadLength() + switch pEnv.Stage { case v0.Stage_STAGE_HTTP: env.Stage = StageHTTP diff --git a/services/policies/pkg/engine/engine_test.go b/services/policies/pkg/engine/engine_test.go index ea3095d418..dab6caf9c0 100644 --- a/services/policies/pkg/engine/engine_test.go +++ b/services/policies/pkg/engine/engine_test.go @@ -22,4 +22,23 @@ var _ = Describe("Engine", func() { Entry("http stage", pMessage.Stage_STAGE_HTTP, engine.StageHTTP), Entry("pp stage", pMessage.Stage_STAGE_PP, engine.StagePP), ) + + It("NewEnvironmentFromPB keeps the request lengths", func() { + pEnv := &pMessage.Environment{ + Stage: pMessage.Stage_STAGE_HTTP, + Request: &pMessage.Request{ + Method: "POST", + Path: "/remote.php/dav/spaces/some-space-id", + ContentLength: 1024, + UploadLength: 20000000000, + }, + } + + env, err := engine.NewEnvironmentFromPB(pEnv) + Expect(err).ToNot(HaveOccurred()) + + Expect(env.Request.Method).To(Equal("POST")) + Expect(env.Request.ContentLength).To(Equal(uint64(1024))) + Expect(env.Request.UploadLength).To(Equal(uint64(20000000000))) + }) }) diff --git a/services/policies/pkg/engine/opa/engine_test.go b/services/policies/pkg/engine/opa/engine_test.go index c7e33d330e..2d731acc56 100644 --- a/services/policies/pkg/engine/opa/engine_test.go +++ b/services/policies/pkg/engine/opa/engine_test.go @@ -150,5 +150,28 @@ var _ = Describe("engine", func() { Expect(granted).To(Equal(tc.want), "for %s", tc.name) } }) + + It("compares the request lengths as numbers", func() { + e, _ := newEngine("package size\n\nimport future.keywords.if\n\ndefault granted := true\n\ngranted = false if {\n input.request.upload_length > 10737418240\n}\n\ngranted = false if {\n input.request.method == [\"PUT\", \"PATCH\"][_]\n input.request.content_length > 10737418240\n}\n") + + for _, tc := range []struct { + name string + request engine.Request + want bool + }{ + {"no lengths", engine.Request{Method: "POST"}, true}, + {"tus creation below limit", engine.Request{Method: "POST", UploadLength: 10737418240}, true}, + {"tus creation above limit", engine.Request{Method: "POST", UploadLength: 20000000000}, false}, + {"put above limit", engine.Request{Method: "PUT", ContentLength: 20000000000}, false}, + {"patch above limit", engine.Request{Method: "PATCH", ContentLength: 20000000000}, false}, + {"patch below limit", engine.Request{Method: "PATCH", ContentLength: 104857600}, true}, + } { + granted, err := e.Evaluate(context.Background(), "data.size.granted", engine.Environment{ + Request: tc.request, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(granted).To(Equal(tc.want), "for %s", tc.name) + } + }) }) }) diff --git a/services/proxy/pkg/middleware/policies.go b/services/proxy/pkg/middleware/policies.go index 04aec89714..8e06c60f3a 100644 --- a/services/proxy/pkg/middleware/policies.go +++ b/services/proxy/pkg/middleware/policies.go @@ -5,6 +5,7 @@ import ( "net/http" "path" "path/filepath" + "strconv" "strings" "time" @@ -72,6 +73,14 @@ func Policies(qs string, opts ...Option) func(next http.Handler) http.Handler { }, } + // the declared lengths allow policies to reject uploads before the data is transferred + if r.ContentLength > 0 { + req.Environment.Request.ContentLength = uint64(r.ContentLength) + } + if uploadLength, err := strconv.ParseUint(r.Header.Get(net.HeaderUploadLength), 10, 64); err == nil { + req.Environment.Request.UploadLength = uploadLength + } + resource := &pMessage.Resource{} // tus diff --git a/services/proxy/pkg/middleware/policies_test.go b/services/proxy/pkg/middleware/policies_test.go index 363d7ff9d1..176b127408 100644 --- a/services/proxy/pkg/middleware/policies_test.go +++ b/services/proxy/pkg/middleware/policies_test.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/http/httptest" + "strings" "testing" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" @@ -109,6 +110,51 @@ func TestPolicies_EvaluationEnvironment_Request(t *testing.T) { policiesMiddleware.ServeHTTP(responseRecorder, httptest.NewRequest(http.MethodDelete, "/whatever", nil)) } +func TestPolicies_EvaluationEnvironment_RequestLengths(t *testing.T) { + tests := []struct { + name string + method string + body string + uploadLength string + expectedContentLength uint64 + expectedUploadLength uint64 + }{ + {name: "tus creation", method: http.MethodPost, uploadLength: "20000000000", expectedUploadLength: 20000000000}, + {name: "put", method: http.MethodPut, body: "some content", expectedContentLength: 12}, + {name: "patch", method: http.MethodPatch, body: "a chunk", expectedContentLength: 7}, + {name: "invalid upload length", method: http.MethodPost, uploadLength: "-1"}, + {name: "no lengths", method: http.MethodGet}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var g = NewWithT(t) + + policiesMiddleware, policiesProviderService, _ := prepare("any") + policiesProviderService.On("Evaluate", mock.Anything, mock.Anything, mock.Anything).Return( + func(_ context.Context, in *policiesPG.EvaluateRequest, _ ...client.CallOption) (*policiesPG.EvaluateResponse, error) { + g.Expect(in.Environment.Request.ContentLength).To(Equal(tt.expectedContentLength)) + g.Expect(in.Environment.Request.UploadLength).To(Equal(tt.expectedUploadLength)) + + return &policiesPG.EvaluateResponse{Result: false}, nil + }, + ).Once() + + var body io.Reader + if tt.body != "" { + body = strings.NewReader(tt.body) + } + request := httptest.NewRequest(tt.method, "/remote.php/dav/spaces/some-space-id/file.txt", body) + if tt.uploadLength != "" { + request.Header.Set(net.HeaderUploadLength, tt.uploadLength) + } + + policiesMiddleware.ServeHTTP(httptest.NewRecorder(), request) + policiesProviderService.AssertExpectations(t) + }) + } +} + func TestPolicies_EvaluationEnvironment_Resource(t *testing.T) { var g = NewWithT(t)