From 7c2767ee2b2abb23dc1fe4b2409c2aaed1433e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Berthier?= Date: Tue, 30 Jan 2024 09:34:09 +0100 Subject: [PATCH 1/6] hooks: add AccessInfo in Event and rejectAccess in Response to prepare pre-access hook --- docs/hooks.md | 16 ++ pkg/handler/hooks.go | 39 ++- pkg/handler/unrouted_handler.go | 18 +- pkg/hooks/grpc/grpc.go | 24 ++ pkg/hooks/grpc/proto/hook.pb.go | 374 +++++++++++++++++---------- pkg/hooks/grpc/proto/hook.proto | 18 ++ pkg/hooks/grpc/proto/hook_grpc.pb.go | 2 +- pkg/hooks/hooks.go | 10 +- 8 files changed, 352 insertions(+), 149 deletions(-) diff --git a/docs/hooks.md b/docs/hooks.md index 02be61ba9..2fde0b73e 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -104,6 +104,17 @@ Below you can find an annotated, JSON-ish encoded example of a hook request: ] // and more ... } + }, + + // Information about the files that are begin accessed, to check authorization for instance + "Access": { + // read (Head/Get/Upload-Concat) or write (Patch/Delete) + "Mode": "read" + // All files info that will be access by http request + // Use an array because of Upload-Concat that may target several files + "Files": [ + // same as Upload + ] } } } @@ -169,6 +180,11 @@ Below you can find an annotated, JSON-ish encoded example of a hook response: // it is ignored. Use the HTTPResponse field to send details about the stop // to the client. "StopUpload": true + + // In case of pre-access or pre-create (when Upload-Concat), reject access to uploads. + // When true, http request will end with 403 status code by default, changeable with + // HTTPResponse override + "RejectAccess": false, } ``` diff --git a/pkg/handler/hooks.go b/pkg/handler/hooks.go index c1c6f1133..5b34be8b6 100644 --- a/pkg/handler/hooks.go +++ b/pkg/handler/hooks.go @@ -26,23 +26,58 @@ type HookEvent struct { // HTTPRequest contains details about the HTTP request that reached // tusd. HTTPRequest HTTPRequest + // Only use by pre-access and pre-create (when Upload-Concat) hook, + // for uploads protection for instance + Access AccessInfo } -func newHookEvent(c *httpContext, info FileInfo) HookEvent { +type AccessMode = string + +const ( + AccessModeRead AccessMode = "read" + AccessModeWrite AccessMode = "write" +) + +type AccessInfo struct { + // read (Head/Get/Upload-Concat) or write (Patch/Delete) + Mode AccessMode + + // All files info that will be access by http request + // Use an array because of Upload-Concat that may target several files + Files []FileInfo +} + +func newHookEvent(c *httpContext, info *FileInfo, accessInfo *AccessInfo) HookEvent { // The Host header field is not present in the header map, see https://pkg.go.dev/net/http#Request: // > For incoming requests, the Host header is promoted to the // > Request.Host field and removed from the Header map. // That's why we add it back manually. c.req.Header.Set("Host", c.req.Host) + var upload FileInfo + if info != nil { + upload = *info + } + var access AccessInfo + if accessInfo != nil { + access = *accessInfo + } return HookEvent{ Context: c, - Upload: info, + Upload: upload, HTTPRequest: HTTPRequest{ Method: c.req.Method, URI: c.req.RequestURI, RemoteAddr: c.req.RemoteAddr, Header: c.req.Header, }, + Access: access, + } +} + +func newAccessInfo(mode AccessMode, files []FileInfo) AccessInfo { + return AccessInfo{ + Mode: mode, + Files: files, } } diff --git a/pkg/handler/unrouted_handler.go b/pkg/handler/unrouted_handler.go index 14b471d99..21d93fbdc 100644 --- a/pkg/handler/unrouted_handler.go +++ b/pkg/handler/unrouted_handler.go @@ -338,7 +338,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) } if handler.config.PreUploadCreateCallback != nil { - resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, info)) + resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info, nil)) if err != nil { handler.sendError(c, err) return @@ -388,7 +388,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) c.log.Info("UploadCreated", "id", id, "size", size, "url", url) if handler.config.NotifyCreatedUploads { - handler.CreatedUploads <- newHookEvent(c, info) + handler.CreatedUploads <- newHookEvent(c, &info, nil) } if isFinal { @@ -400,7 +400,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) info.Offset = size if handler.config.NotifyCompleteUploads { - handler.CompleteUploads <- newHookEvent(c, info) + handler.CompleteUploads <- newHookEvent(c, &info, nil) } } @@ -491,7 +491,7 @@ func (handler *UnroutedHandler) PostFileV2(w http.ResponseWriter, r *http.Reques // 1. Create upload resource if handler.config.PreUploadCreateCallback != nil { - resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, info)) + resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info, nil)) if err != nil { handler.sendError(c, err) return @@ -543,7 +543,7 @@ func (handler *UnroutedHandler) PostFileV2(w http.ResponseWriter, r *http.Reques c.log.Info("UploadCreated", "size", info.Size, "url", url) if handler.config.NotifyCreatedUploads { - handler.CreatedUploads <- newHookEvent(c, info) + handler.CreatedUploads <- newHookEvent(c, &info, nil) } // 2. Lock upload @@ -939,7 +939,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(c *httpContext, resp HTTP // ... allow the hook callback to run before sending the response if handler.config.PreFinishResponseCallback != nil { - resp2, err := handler.config.PreFinishResponseCallback(newHookEvent(c, info)) + resp2, err := handler.config.PreFinishResponseCallback(newHookEvent(c, &info, nil)) if err != nil { return resp, err } @@ -951,7 +951,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(c *httpContext, resp HTTP // ... send the info out to the channel if handler.config.NotifyCompleteUploads { - handler.CompleteUploads <- newHookEvent(c, info) + handler.CompleteUploads <- newHookEvent(c, &info, nil) } } @@ -1150,7 +1150,7 @@ func (handler *UnroutedHandler) terminateUpload(c *httpContext, upload Upload, i } if handler.config.NotifyTerminatedUploads { - handler.TerminatedUploads <- newHookEvent(c, info) + handler.TerminatedUploads <- newHookEvent(c, &info, nil) } c.log.Info("UploadTerminated") @@ -1206,7 +1206,7 @@ func (handler *UnroutedHandler) absFileURL(r *http.Request, id string) string { // indicating how much data has been transfered to the server. // It will stop sending these instances once the provided context is done. func (handler *UnroutedHandler) sendProgressMessages(c *httpContext, info FileInfo) { - hook := newHookEvent(c, info) + hook := newHookEvent(c, &info, nil) previousOffset := int64(0) originalOffset := hook.Upload.Offset diff --git a/pkg/hooks/grpc/grpc.go b/pkg/hooks/grpc/grpc.go index 92b0e1c45..4e7efa95d 100644 --- a/pkg/hooks/grpc/grpc.go +++ b/pkg/hooks/grpc/grpc.go @@ -9,6 +9,7 @@ import ( "time" grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" + "github.com/tus/tusd/v2/pkg/handler" "github.com/tus/tusd/v2/pkg/hooks" pb "github.com/tus/tusd/v2/pkg/hooks/grpc/proto" "google.golang.org/grpc" @@ -74,6 +75,10 @@ func marshal(hookReq hooks.HookRequest) *pb.HookRequest { RemoteAddr: event.HTTPRequest.RemoteAddr, Header: getHeader(event.HTTPRequest.Header), }, + Access: &pb.AccessInfo{ + Mode: event.Access.Mode, + Files: getAccessFiles(event.Access.Files), + }, }, } } @@ -88,9 +93,28 @@ func getHeader(httpHeader http.Header) (hookHeader map[string]string) { return hookHeader } +func getAccessFiles(files []handler.FileInfo) (hookFiles []*pb.FileInfo) { + hookFiles = make([]*pb.FileInfo, len(files)) + for i, file := range files { + hookFiles[i] = &pb.FileInfo{ + Id: file.ID, + Size: file.Size, + SizeIsDeferred: file.SizeIsDeferred, + Offset: file.Offset, + MetaData: file.MetaData, + IsPartial: file.IsPartial, + IsFinal: file.IsFinal, + PartialUploads: file.PartialUploads, + Storage: file.Storage, + } + } + return hookFiles +} + func unmarshal(res *pb.HookResponse) (hookRes hooks.HookResponse) { hookRes.RejectUpload = res.RejectUpload hookRes.StopUpload = res.StopUpload + hookRes.RejectAccess = res.RejectAccess httpRes := res.HttpResponse if httpRes != nil { diff --git a/pkg/hooks/grpc/proto/hook.pb.go b/pkg/hooks/grpc/proto/hook.pb.go index d130d6ac9..da11edcb5 100644 --- a/pkg/hooks/grpc/proto/hook.pb.go +++ b/pkg/hooks/grpc/proto/hook.pb.go @@ -8,7 +8,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc v4.25.2 // source: pkg/hooks/grpc/proto/hook.proto package proto @@ -98,6 +98,8 @@ type Event struct { // HTTPRequest contains details about the HTTP request that reached // tusd. HttpRequest *HTTPRequest `protobuf:"bytes,2,opt,name=httpRequest,proto3" json:"httpRequest,omitempty"` + // Only use by pre-access and pre-create (when Upload-Concat) hook + Access *AccessInfo `protobuf:"bytes,3,opt,name=access,proto3" json:"access,omitempty"` } func (x *Event) Reset() { @@ -146,6 +148,13 @@ func (x *Event) GetHttpRequest() *HTTPRequest { return nil } +func (x *Event) GetAccess() *AccessInfo { + if x != nil { + return x.Access + } + return nil +} + // FileInfo contains information about a single upload resource. type FileInfo struct { state protoimpl.MessageState @@ -272,6 +281,65 @@ func (x *FileInfo) GetStorage() map[string]string { return nil } +// For pre-access and pre-create (when Upload-Concat) hook to protect access for instance +type AccessInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // read (Head/Get/Upload-Concat) or write (Patch/Delete) + Mode string `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"` + // All files that will be access by http request + // Use an array because of Upload-Concat that may target seeral files + Files []*FileInfo `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"` +} + +func (x *AccessInfo) Reset() { + *x = AccessInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AccessInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccessInfo) ProtoMessage() {} + +func (x *AccessInfo) ProtoReflect() protoreflect.Message { + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccessInfo.ProtoReflect.Descriptor instead. +func (*AccessInfo) Descriptor() ([]byte, []int) { + return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{3} +} + +func (x *AccessInfo) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +func (x *AccessInfo) GetFiles() []*FileInfo { + if x != nil { + return x.Files + } + return nil +} + // FileInfoChanges collects changes the should be made to a FileInfo object. This // can be done using the PreUploadCreateCallback to modify certain properties before // an upload is created. Properties which should not be modified (e.g. Size or Offset) @@ -305,7 +373,7 @@ type FileInfoChanges struct { func (x *FileInfoChanges) Reset() { *x = FileInfoChanges{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[3] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -318,7 +386,7 @@ func (x *FileInfoChanges) String() string { func (*FileInfoChanges) ProtoMessage() {} func (x *FileInfoChanges) ProtoReflect() protoreflect.Message { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[3] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -331,7 +399,7 @@ func (x *FileInfoChanges) ProtoReflect() protoreflect.Message { // Deprecated: Use FileInfoChanges.ProtoReflect.Descriptor instead. func (*FileInfoChanges) Descriptor() ([]byte, []int) { - return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{3} + return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{4} } func (x *FileInfoChanges) GetId() string { @@ -374,7 +442,7 @@ type HTTPRequest struct { func (x *HTTPRequest) Reset() { *x = HTTPRequest{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[4] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -387,7 +455,7 @@ func (x *HTTPRequest) String() string { func (*HTTPRequest) ProtoMessage() {} func (x *HTTPRequest) ProtoReflect() protoreflect.Message { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[4] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -400,7 +468,7 @@ func (x *HTTPRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPRequest.ProtoReflect.Descriptor instead. func (*HTTPRequest) Descriptor() ([]byte, []int) { - return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{4} + return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{5} } func (x *HTTPRequest) GetMethod() string { @@ -463,12 +531,16 @@ type HookResponse struct { // it is ignored. Use the HTTPResponse field to send details about the stop // to the client. StopUpload bool `protobuf:"varint,3,opt,name=stopUpload,proto3" json:"stopUpload,omitempty"` + // In case of pre-access or pre-create (when Upload-Concat), reject access to upload + // When true, http request will end with 403 status code by default, changeable with + // HTTPResponse override + RejectAccess bool `protobuf:"varint,5,opt,name=rejectAccess,proto3" json:"rejectAccess,omitempty"` } func (x *HookResponse) Reset() { *x = HookResponse{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[5] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -481,7 +553,7 @@ func (x *HookResponse) String() string { func (*HookResponse) ProtoMessage() {} func (x *HookResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[5] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -494,7 +566,7 @@ func (x *HookResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HookResponse.ProtoReflect.Descriptor instead. func (*HookResponse) Descriptor() ([]byte, []int) { - return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{5} + return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{6} } func (x *HookResponse) GetHttpResponse() *HTTPResponse { @@ -525,6 +597,13 @@ func (x *HookResponse) GetStopUpload() bool { return false } +func (x *HookResponse) GetRejectAccess() bool { + if x != nil { + return x.RejectAccess + } + return false +} + // HTTPResponse contains basic details of an outgoing HTTP response. type HTTPResponse struct { state protoimpl.MessageState @@ -542,7 +621,7 @@ type HTTPResponse struct { func (x *HTTPResponse) Reset() { *x = HTTPResponse{} if protoimpl.UnsafeEnabled { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[6] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -555,7 +634,7 @@ func (x *HTTPResponse) String() string { func (*HTTPResponse) ProtoMessage() {} func (x *HTTPResponse) ProtoReflect() protoreflect.Message { - mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[6] + mi := &file_pkg_hooks_grpc_proto_hook_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -568,7 +647,7 @@ func (x *HTTPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead. func (*HTTPResponse) Descriptor() ([]byte, []int) { - return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{6} + return file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP(), []int{7} } func (x *HTTPResponse) GetStatusCode() int64 { @@ -602,102 +681,112 @@ var file_pkg_hooks_grpc_proto_hook_proto_rawDesc = []byte{ 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x66, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x06, 0x75, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x75, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x12, 0x34, 0x0a, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, - 0x54, 0x54, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, 0x03, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x69, 0x7a, 0x65, - 0x49, 0x73, 0x44, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0e, 0x73, 0x69, 0x7a, 0x65, 0x49, 0x73, 0x44, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, - 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x73, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x70, - 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x08, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x55, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x73, 0x12, 0x36, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x09, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, - 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x91, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x06, 0x75, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x75, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x34, 0x0a, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0b, 0x68, 0x74, 0x74, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x22, 0xba, 0x03, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x69, 0x7a, 0x65, 0x49, 0x73, 0x44, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x69, + 0x7a, 0x65, 0x49, 0x73, 0x44, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, + 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x1c, 0x0a, 0x09, 0x69, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x18, 0x0a, + 0x07, 0x69, 0x73, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x69, 0x73, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x61, 0x6c, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x12, + 0x36, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, + 0x6f, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x44, + 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x47, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, + 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, + 0x64, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x9b, 0x02, 0x0a, 0x0f, 0x46, 0x69, + 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x3d, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, + 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x3b, + 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xca, 0x01, 0x0a, 0x0b, 0x48, 0x54, 0x54, 0x50, + 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, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x69, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x12, 0x36, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9b, 0x02, 0x0a, 0x0f, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x72, 0x6f, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xef, 0x01, 0x0a, 0x0c, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x52, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, + 0x0a, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x3e, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3d, 0x0a, 0x07, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, - 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0xca, 0x01, 0x0a, 0x0b, 0x48, 0x54, 0x54, 0x50, 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, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1e, 0x0a, 0x0a, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x36, 0x0a, 0x06, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, + 0x65, 0x73, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x0c, 0x48, 0x54, 0x54, 0x50, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0xcb, 0x01, 0x0a, 0x0c, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x37, 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, - 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0c, 0x68, 0x74, 0x74, - 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6a, - 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3e, 0x0a, - 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, - 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x0e, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1e, 0x0a, - 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xb6, 0x01, - 0x0a, 0x0c, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, - 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x37, - 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x39, 0x0a, 0x0b, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x46, 0x0a, 0x0b, 0x48, 0x6f, 0x6f, 0x6b, 0x48, 0x61, - 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x48, - 0x6f, 0x6f, 0x6b, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x12, - 0x5a, 0x10, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, + 0x46, 0x0a, 0x0b, 0x48, 0x6f, 0x6f, 0x6b, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x12, 0x37, + 0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x12, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x12, 0x5a, 0x10, 0x68, 0x6f, 0x6f, 0x6b, 0x73, + 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -712,41 +801,44 @@ func file_pkg_hooks_grpc_proto_hook_proto_rawDescGZIP() []byte { return file_pkg_hooks_grpc_proto_hook_proto_rawDescData } -var file_pkg_hooks_grpc_proto_hook_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_pkg_hooks_grpc_proto_hook_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_pkg_hooks_grpc_proto_hook_proto_goTypes = []interface{}{ (*HookRequest)(nil), // 0: proto.HookRequest (*Event)(nil), // 1: proto.Event (*FileInfo)(nil), // 2: proto.FileInfo - (*FileInfoChanges)(nil), // 3: proto.FileInfoChanges - (*HTTPRequest)(nil), // 4: proto.HTTPRequest - (*HookResponse)(nil), // 5: proto.HookResponse - (*HTTPResponse)(nil), // 6: proto.HTTPResponse - nil, // 7: proto.FileInfo.MetaDataEntry - nil, // 8: proto.FileInfo.StorageEntry - nil, // 9: proto.FileInfoChanges.MetaDataEntry - nil, // 10: proto.FileInfoChanges.StorageEntry - nil, // 11: proto.HTTPRequest.HeaderEntry - nil, // 12: proto.HTTPResponse.HeaderEntry + (*AccessInfo)(nil), // 3: proto.AccessInfo + (*FileInfoChanges)(nil), // 4: proto.FileInfoChanges + (*HTTPRequest)(nil), // 5: proto.HTTPRequest + (*HookResponse)(nil), // 6: proto.HookResponse + (*HTTPResponse)(nil), // 7: proto.HTTPResponse + nil, // 8: proto.FileInfo.MetaDataEntry + nil, // 9: proto.FileInfo.StorageEntry + nil, // 10: proto.FileInfoChanges.MetaDataEntry + nil, // 11: proto.FileInfoChanges.StorageEntry + nil, // 12: proto.HTTPRequest.HeaderEntry + nil, // 13: proto.HTTPResponse.HeaderEntry } var file_pkg_hooks_grpc_proto_hook_proto_depIdxs = []int32{ 1, // 0: proto.HookRequest.event:type_name -> proto.Event 2, // 1: proto.Event.upload:type_name -> proto.FileInfo - 4, // 2: proto.Event.httpRequest:type_name -> proto.HTTPRequest - 7, // 3: proto.FileInfo.metaData:type_name -> proto.FileInfo.MetaDataEntry - 8, // 4: proto.FileInfo.storage:type_name -> proto.FileInfo.StorageEntry - 9, // 5: proto.FileInfoChanges.metaData:type_name -> proto.FileInfoChanges.MetaDataEntry - 10, // 6: proto.FileInfoChanges.storage:type_name -> proto.FileInfoChanges.StorageEntry - 11, // 7: proto.HTTPRequest.header:type_name -> proto.HTTPRequest.HeaderEntry - 6, // 8: proto.HookResponse.httpResponse:type_name -> proto.HTTPResponse - 3, // 9: proto.HookResponse.changeFileInfo:type_name -> proto.FileInfoChanges - 12, // 10: proto.HTTPResponse.header:type_name -> proto.HTTPResponse.HeaderEntry - 0, // 11: proto.HookHandler.InvokeHook:input_type -> proto.HookRequest - 5, // 12: proto.HookHandler.InvokeHook:output_type -> proto.HookResponse - 12, // [12:13] is the sub-list for method output_type - 11, // [11:12] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 5, // 2: proto.Event.httpRequest:type_name -> proto.HTTPRequest + 3, // 3: proto.Event.access:type_name -> proto.AccessInfo + 8, // 4: proto.FileInfo.metaData:type_name -> proto.FileInfo.MetaDataEntry + 9, // 5: proto.FileInfo.storage:type_name -> proto.FileInfo.StorageEntry + 2, // 6: proto.AccessInfo.files:type_name -> proto.FileInfo + 10, // 7: proto.FileInfoChanges.metaData:type_name -> proto.FileInfoChanges.MetaDataEntry + 11, // 8: proto.FileInfoChanges.storage:type_name -> proto.FileInfoChanges.StorageEntry + 12, // 9: proto.HTTPRequest.header:type_name -> proto.HTTPRequest.HeaderEntry + 7, // 10: proto.HookResponse.httpResponse:type_name -> proto.HTTPResponse + 4, // 11: proto.HookResponse.changeFileInfo:type_name -> proto.FileInfoChanges + 13, // 12: proto.HTTPResponse.header:type_name -> proto.HTTPResponse.HeaderEntry + 0, // 13: proto.HookHandler.InvokeHook:input_type -> proto.HookRequest + 6, // 14: proto.HookHandler.InvokeHook:output_type -> proto.HookResponse + 14, // [14:15] is the sub-list for method output_type + 13, // [13:14] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_pkg_hooks_grpc_proto_hook_proto_init() } @@ -792,7 +884,7 @@ func file_pkg_hooks_grpc_proto_hook_proto_init() { } } file_pkg_hooks_grpc_proto_hook_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FileInfoChanges); i { + switch v := v.(*AccessInfo); i { case 0: return &v.state case 1: @@ -804,7 +896,7 @@ func file_pkg_hooks_grpc_proto_hook_proto_init() { } } file_pkg_hooks_grpc_proto_hook_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTTPRequest); i { + switch v := v.(*FileInfoChanges); i { case 0: return &v.state case 1: @@ -816,7 +908,7 @@ func file_pkg_hooks_grpc_proto_hook_proto_init() { } } file_pkg_hooks_grpc_proto_hook_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HookResponse); i { + switch v := v.(*HTTPRequest); i { case 0: return &v.state case 1: @@ -828,6 +920,18 @@ func file_pkg_hooks_grpc_proto_hook_proto_init() { } } file_pkg_hooks_grpc_proto_hook_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HookResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_hooks_grpc_proto_hook_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HTTPResponse); i { case 0: return &v.state @@ -846,7 +950,7 @@ func file_pkg_hooks_grpc_proto_hook_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pkg_hooks_grpc_proto_hook_proto_rawDesc, NumEnums: 0, - NumMessages: 13, + NumMessages: 14, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/hooks/grpc/proto/hook.proto b/pkg/hooks/grpc/proto/hook.proto index b6f4adc47..d3ae2691d 100644 --- a/pkg/hooks/grpc/proto/hook.proto +++ b/pkg/hooks/grpc/proto/hook.proto @@ -29,6 +29,9 @@ message Event { // HTTPRequest contains details about the HTTP request that reached // tusd. HTTPRequest httpRequest = 2; + + // Only use by pre-access and pre-create (when Upload-Concat) hook + AccessInfo access = 3; } // FileInfo contains information about a single upload resource. @@ -58,6 +61,16 @@ message FileInfo { map storage = 9; } +// For pre-access and pre-create (when Upload-Concat) hook to protect access for instance +message AccessInfo { + // read (Head/Get/Upload-Concat) or write (Patch/Delete) + string mode = 1; + + // All files that will be access by http request + // Use an array because of Upload-Concat that may target seeral files + repeated FileInfo files = 2; +} + // FileInfoChanges collects changes the should be made to a FileInfo object. This // can be done using the PreUploadCreateCallback to modify certain properties before // an upload is created. Properties which should not be modified (e.g. Size or Offset) @@ -130,6 +143,11 @@ message HookResponse { // it is ignored. Use the HTTPResponse field to send details about the stop // to the client. bool stopUpload = 3; + + // In case of pre-access or pre-create (when Upload-Concat), reject access to upload + // When true, http request will end with 403 status code by default, changeable with + // HTTPResponse override + bool rejectAccess = 5; } // HTTPResponse contains basic details of an outgoing HTTP response. diff --git a/pkg/hooks/grpc/proto/hook_grpc.pb.go b/pkg/hooks/grpc/proto/hook_grpc.pb.go index d0c9dca16..3cf82ddcb 100644 --- a/pkg/hooks/grpc/proto/hook_grpc.pb.go +++ b/pkg/hooks/grpc/proto/hook_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.12 +// - protoc v4.25.2 // source: pkg/hooks/grpc/proto/hook.proto package proto diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index 1251f9198..d431317b6 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -80,6 +80,11 @@ type HookResponse struct { // it is ignored. Use the HTTPResponse field to send details about the stop // to the client. StopUpload bool + + // In case of pre-access or pre-create (when Upload-Concat), reject access to upload + // When true, http request will end with 403 status code by default, changeable with + // HTTPResponse override + RejectAccess bool } type HookType string @@ -218,8 +223,9 @@ func invokeHookSync(typ HookType, event handler.HookEvent, hookHandler HookHandl // // If you want to create an UnroutedHandler instead of the routed handler, you can first create a routed handler and then // extract an unrouted one: -// routedHandler := hooks.NewHandlerWithHooks(...) -// unroutedHandler := routedHandler.UnroutedHandler +// +// routedHandler := hooks.NewHandlerWithHooks(...) +// unroutedHandler := routedHandler.UnroutedHandler // // Note: NewHandlerWithHooks sets up a goroutine to consume the notfication channels (CompleteUploads, TerminatedUploads, // CreatedUploads, UploadProgress) on the created handler. These channels must not be consumed by the caller or otherwise From c9a7ce9a1e6c4e9aa29ea985bf5d48a3efda740b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Berthier?= Date: Tue, 30 Jan 2024 09:43:52 +0100 Subject: [PATCH 2/6] hooks: add pre-access blocking hook - on Head/Get/Patch/Delete request - use new AccessInfo in HookEvent with AccessMode and FileInfo list - use RejectAccess in response to return 403 by default if rejected --- docs/faq.md | 2 +- docs/hooks.md | 7 +++- pkg/handler/config.go | 6 +++ pkg/handler/get_test.go | 74 +++++++++++++++++++++++++++++++++ pkg/handler/head_test.go | 31 ++++++++++++++ pkg/handler/patch_test.go | 36 ++++++++++++++++ pkg/handler/terminate_test.go | 32 ++++++++++++++ pkg/handler/unrouted_handler.go | 39 ++++++++++++++++- pkg/hooks/hooks.go | 29 ++++++++++++- pkg/hooks/hooks_test.go | 71 ++++++++++++++++++++++++++++++- 10 files changed, 321 insertions(+), 6 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index aa9891bd3..78816c5bf 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -32,7 +32,7 @@ This error can occur when you are running tusd's disk storage on a file system w ### How can I prevent users from downloading the uploaded files? -tusd allows any user to retrieve a previously uploaded file by issuing a HTTP GET request to the corresponding upload URL. This is possible as long as the uploaded files on the datastore have not been deleted or moved to another location. While it is a handy feature for debugging and testing your setup, we know that there are situations where you don't want to allow downloads or where you want more control about who downloads what. In these scenarios we recommend to place a proxy in front of tusd which takes on the task of access control or even preventing HTTP GET requests entirely. tusd has no feature built in for controling or disabling downloads on its own because the main focus is on accepting uploads, not serving files. +tusd allows any user to retrieve a previously uploaded file by issuing a HTTP GET request to the corresponding upload URL. This is possible as long as the uploaded files on the datastore have not been deleted or moved to another location. While it is a handy feature for debugging and testing your setup, we know that there are situations where you don't want to allow downloads or where you want more control about who downloads what. In these scenarios you can use `-disable-download` option or a `pre-access` hook to control access to uploaded files based on http request headers. You can also place a proxy in front of tusd which takes on the task of access control or even preventing HTTP GET requests entirely. ### How can I keep the original filename for the uploads? diff --git a/docs/hooks.md b/docs/hooks.md index 2fde0b73e..e502bd88e 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -21,6 +21,7 @@ The table below provides an overview of all available hooks. | pre-finish | Yes | after all upload data has been received but before a response is sent. | sending custom data when an upload is finished | Yes | | post-finish | No | after all upload data has been received and after a response is sent. | post-processing of upload, logging of upload end | Yes | | post-terminate | No | after an upload has been terminated. | clean up of allocated resources | Yes | +| pre-access | Yes | before an existing upload is access (Head/Get/Patch/Delete). | validation of user authentication | No | Users should be aware of following things: - If a hook is _blocking_, tusd will wait with further processing until the hook is completed. This is useful for validation and authentication, where further processing should be stopped if the hook determines to do so. However, long execution time may impact the user experience because the upload processing is blocked while the hook executes. @@ -319,7 +320,7 @@ For example, assume that every upload must belong to a specific user project. Th ### Authenticating Users -User authentication can be achieved by two ways: Either, user tokens can be included in the upload meta data, as described in the above example. Alternatively, traditional header fields, such as `Authorization` or `Cookie` can be used to carry user-identifying information. These header values are also present for the hook requests and are accessible for the `pre-create` hook, where the authorization tokens or cookies can be validated to authenticate the user. +User authentication can be achieved by two ways: Either, user tokens can be included in the upload meta data, as described in the above example. Alternatively, traditional header fields, such as `Authorization` or `Cookie` can be used to carry user-identifying information. These header values are also present for the hook requests and are accessible for the `pre-create` and `pre-access` hooks, where the authorization tokens or cookies can be validated to authenticate the user. If the authentication is successful, the hook can return an empty hook response to indicate tusd that the upload should continue as normal. If the authentication fails, the hook can instruct tusd to reject the upload and return a custom error response to the client. For example, this is a possible hook response: @@ -337,7 +338,9 @@ If the authentication is successful, the hook can return an empty hook response } ``` -Note that this handles authentication during the initial POST request when creating an upload. When tusd responds, it sends a random upload URL to the client, which is used to transmit the remaining data via PATCH and resume the upload via HEAD requests. Currently, there is no mechanism to ensure that the upload is resumed by the same user that created it. We plan on addressing this in the future. However, since the upload URL is randomly generated and only short-lived, it is hard to guess for uninvolved parties. +Note that listen `pre-create` hook only handles authentication during the initial POST request when creating an upload. When tusd responds, it sends a random upload URL to the client, which is used to transmit the remaining data via PATCH and resume the upload via HEAD requests. Since the upload URL is randomly generated and only short-lived, it is hard to guess for uninvolved parties. + +To have full protection, consider adding `pre-access` hook too. ### Interrupting Uploads diff --git a/pkg/handler/config.go b/pkg/handler/config.go index 81660ae34..f8de142be 100644 --- a/pkg/handler/config.go +++ b/pkg/handler/config.go @@ -69,6 +69,12 @@ type Config struct { // that should be overwriten before the upload is create. See its type definition for // more details on its behavior. If you do not want to make any changes, return an empty struct. PreUploadCreateCallback func(hook HookEvent) (HTTPResponse, FileInfoChanges, error) + // PreUploadAccessCallback will be invoked before accessing an upload, if the + // property is supplied, on Get/Head/Patch/Delete requests. + // If the callback returns no error, the requests will continue. + // If the error is non-nil, the requests will be rejected. This can be used to implement + // authorization. + PreUploadAccessCallback func(hook HookEvent) error // PreFinishResponseCallback will be invoked after an upload is completed but before // a response is returned to the client. This can be used to implement post-processing validation. // If the callback returns no error, optional values from HTTPResponse will be contained in the HTTP response. diff --git a/pkg/handler/get_test.go b/pkg/handler/get_test.go index b3a26b548..ff1bd944f 100644 --- a/pkg/handler/get_test.go +++ b/pkg/handler/get_test.go @@ -164,4 +164,78 @@ func TestGet(t *testing.T) { ResBody: "", }).Run(handler, t) }) + + SubTest(t, "RejectAccess", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + Size: 20, + Offset: 20, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + PreUploadAccessCallback: func(event HookEvent) error { + return ErrAccessRejectedByServer + }, + }) + + (&httpTest{ + Method: "GET", + URL: "yes", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + }, + Code: ErrAccessRejectedByServer.HTTPResponse.StatusCode, + ResHeader: ErrAccessRejectedByServer.HTTPResponse.Header, + ResBody: ErrAccessRejectedByServer.HTTPResponse.Body, + }).Run(handler, t) + }) + + SubTest(t, "RejectAccessCustomResponse", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + Size: 20, + Offset: 20, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + PreUploadAccessCallback: func(event HookEvent) error { + err := ErrAccessRejectedByServer + err.HTTPResponse = HTTPResponse{ + StatusCode: 409, + Body: "Custom response", + Header: HTTPHeader{ + "X-Foo": "bar", + }, + } + return err + }, + }) + + (&httpTest{ + Method: "GET", + URL: "yes", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + }, + Code: 409, + ResHeader: map[string]string{ + "X-Foo": "bar", + }, + ResBody: "Custom response", + }).Run(handler, t) + }) } diff --git a/pkg/handler/head_test.go b/pkg/handler/head_test.go index e6c0d24a3..bc45e83cd 100644 --- a/pkg/handler/head_test.go +++ b/pkg/handler/head_test.go @@ -144,6 +144,37 @@ func TestHead(t *testing.T) { }).Run(handler, t) }) + SubTest(t, "RejectAccess", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + SizeIsDeferred: true, + Size: 0, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + PreUploadAccessCallback: func(event HookEvent) error { + return ErrAccessRejectedByServer + }, + }) + + (&httpTest{ + Method: "HEAD", + URL: "yes", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + }, + Code: ErrAccessRejectedByServer.HTTPResponse.StatusCode, + ResHeader: ErrAccessRejectedByServer.HTTPResponse.Header, + }).Run(handler, t) + }) + SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) { SubTest(t, "IncompleteUpload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { ctrl := gomock.NewController(t) diff --git a/pkg/handler/patch_test.go b/pkg/handler/patch_test.go index 36e8b2c97..b779f7c7b 100644 --- a/pkg/handler/patch_test.go +++ b/pkg/handler/patch_test.go @@ -706,6 +706,42 @@ func TestPatch(t *testing.T) { a.False(more) }) + SubTest(t, "RejectAccess", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + ID: "yes", + Offset: 0, + Size: 5, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + PreUploadAccessCallback: func(event HookEvent) error { + return ErrAccessRejectedByServer + }, + }) + + (&httpTest{ + Method: "PATCH", + URL: "yes", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + "Upload-Offset": "0", + "Content-Type": "application/offset+octet-stream", + }, + ReqBody: strings.NewReader("hello"), + Code: ErrAccessRejectedByServer.HTTPResponse.StatusCode, + ResHeader: ErrAccessRejectedByServer.HTTPResponse.Header, + ResBody: ErrAccessRejectedByServer.HTTPResponse.Body, + }).Run(handler, t) + }) + SubTest(t, "BodyReadError", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { // This test ensure that error that occurr from reading the request body are not forwarded to the // storage backend but are still causing an diff --git a/pkg/handler/terminate_test.go b/pkg/handler/terminate_test.go index 95131309e..11b71a304 100644 --- a/pkg/handler/terminate_test.go +++ b/pkg/handler/terminate_test.go @@ -100,4 +100,36 @@ func TestTerminate(t *testing.T) { Code: http.StatusNotImplemented, }).Run(http.HandlerFunc(handler.DelFile), t) }) + + SubTest(t, "RejectAccess", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + upload := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "yes").Return(upload, nil), + upload.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + Size: 20, + Offset: 20, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + PreUploadAccessCallback: func(event HookEvent) error { + return ErrAccessRejectedByServer + }, + }) + + (&httpTest{ + Method: "DELETE", + URL: "yes", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + }, + Code: ErrAccessRejectedByServer.HTTPResponse.StatusCode, + ResHeader: ErrAccessRejectedByServer.HTTPResponse.Header, + ResBody: ErrAccessRejectedByServer.HTTPResponse.Body, + }).Run(handler, t) + }) } diff --git a/pkg/handler/unrouted_handler.go b/pkg/handler/unrouted_handler.go index 21d93fbdc..3807b4ccb 100644 --- a/pkg/handler/unrouted_handler.go +++ b/pkg/handler/unrouted_handler.go @@ -50,6 +50,7 @@ var ( ErrUploadStoppedByServer = NewError("ERR_UPLOAD_STOPPED", "upload has been stopped by server", http.StatusBadRequest) ErrUploadRejectedByServer = NewError("ERR_UPLOAD_REJECTED", "upload creation has been rejected by server", http.StatusBadRequest) ErrUploadInterrupted = NewError("ERR_UPLOAD_INTERRUPTED", "upload has been interrupted by another request for this upload resource", http.StatusBadRequest) + ErrAccessRejectedByServer = NewError("ERR_ACCESS_REJECTED", "upload access has been rejected by server", http.StatusForbidden) ErrServerShutdown = NewError("ERR_SERVER_SHUTDOWN", "request has been interrupted because the server is shutting down", http.StatusServiceUnavailable) ErrOriginNotAllowed = NewError("ERR_ORIGIN_NOT_ALLOWED", "request origin is not allowed", http.StatusForbidden) @@ -627,6 +628,15 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) return } + if handler.config.PreUploadAccessCallback != nil { + access := newAccessInfo(AccessModeRead, []FileInfo{info}) + err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + if err != nil { + handler.sendError(c, err) + return + } + } + resp := HTTPResponse{ Header: HTTPHeader{ "Cache-Control": "no-store", @@ -729,6 +739,15 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request return } + if handler.config.PreUploadAccessCallback != nil { + access := newAccessInfo(AccessModeWrite, []FileInfo{info}) + err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + if err != nil { + handler.sendError(c, err) + return + } + } + // Modifying a final upload is not allowed if info.IsFinal { handler.sendError(c, ErrModifyFinal) @@ -992,6 +1011,15 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) return } + if handler.config.PreUploadAccessCallback != nil { + access := newAccessInfo(AccessModeRead, []FileInfo{info}) + err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + if err != nil { + handler.sendError(c, err) + return + } + } + contentType, contentDisposition := filterContentType(info) resp := HTTPResponse{ StatusCode: http.StatusOK, @@ -1117,7 +1145,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) } var info FileInfo - if handler.config.NotifyTerminatedUploads { + if handler.config.NotifyTerminatedUploads || handler.config.PreUploadAccessCallback != nil { info, err = upload.GetInfo(c) if err != nil { handler.sendError(c, err) @@ -1125,6 +1153,15 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) } } + if handler.config.PreUploadAccessCallback != nil { + access := newAccessInfo(AccessModeWrite, []FileInfo{info}) + err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + if err != nil { + handler.sendError(c, err) + return + } + } + err = handler.terminateUpload(c, upload, info) if err != nil { handler.sendError(c, err) diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index d431317b6..a2fe72c13 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -96,10 +96,11 @@ const ( HookPostCreate HookType = "post-create" HookPreCreate HookType = "pre-create" HookPreFinish HookType = "pre-finish" + HookPreAccess HookType = "pre-access" ) // AvailableHooks is a slice of all hooks that are implemented by tusd. -var AvailableHooks []HookType = []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish} +var AvailableHooks []HookType = []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish, HookPreAccess} func preCreateCallback(event handler.HookEvent, hookHandler HookHandler) (handler.HTTPResponse, handler.FileInfoChanges, error) { ok, hookRes, err := invokeHookSync(HookPreCreate, event, hookHandler) @@ -123,6 +124,25 @@ func preCreateCallback(event handler.HookEvent, hookHandler HookHandler) (handle return httpRes, changes, nil } +func preAccessCallback(event handler.HookEvent, hookHandler HookHandler) error { + ok, hookRes, err := invokeHookSync(HookPreAccess, event, hookHandler) + if !ok || err != nil { + return err + } + + httpRes := hookRes.HTTPResponse + + // If the hook response includes the instruction to reject access, reuse the error code + // and message from ErrAccessRejectedByServer, but also include custom HTTP response values. + if hookRes.RejectAccess { + err := handler.ErrAccessRejectedByServer + err.HTTPResponse = err.HTTPResponse.MergeWith(httpRes) + + return err + } + return nil +} + func preFinishCallback(event handler.HookEvent, hookHandler HookHandler) (handler.HTTPResponse, error) { ok, hookRes, err := invokeHookSync(HookPreFinish, event, hookHandler) if !ok || err != nil { @@ -171,12 +191,14 @@ func SetupHookMetrics() { MetricsHookErrorsTotal.WithLabelValues(string(HookPostCreate)).Add(0) MetricsHookErrorsTotal.WithLabelValues(string(HookPreCreate)).Add(0) MetricsHookErrorsTotal.WithLabelValues(string(HookPreFinish)).Add(0) + MetricsHookErrorsTotal.WithLabelValues(string(HookPreAccess)).Add(0) MetricsHookInvocationsTotal.WithLabelValues(string(HookPostFinish)).Add(0) MetricsHookInvocationsTotal.WithLabelValues(string(HookPostTerminate)).Add(0) MetricsHookInvocationsTotal.WithLabelValues(string(HookPostReceive)).Add(0) MetricsHookInvocationsTotal.WithLabelValues(string(HookPostCreate)).Add(0) MetricsHookInvocationsTotal.WithLabelValues(string(HookPreCreate)).Add(0) MetricsHookInvocationsTotal.WithLabelValues(string(HookPreFinish)).Add(0) + MetricsHookInvocationsTotal.WithLabelValues(string(HookPreAccess)).Add(0) } func invokeHookAsync(typ HookType, event handler.HookEvent, hookHandler HookHandler) { @@ -252,6 +274,11 @@ func NewHandlerWithHooks(config *handler.Config, hookHandler HookHandler, enable return preFinishCallback(event, hookHandler) } } + if slices.Contains(enabledHooks, HookPreAccess) { + config.PreUploadAccessCallback = func(event handler.HookEvent) error { + return preAccessCallback(event, hookHandler) + } + } // Create handler handler, err := handler.NewHandler(*config) diff --git a/pkg/hooks/hooks_test.go b/pkg/hooks/hooks_test.go index 693a8b6ad..260e6afc0 100644 --- a/pkg/hooks/hooks_test.go +++ b/pkg/hooks/hooks_test.go @@ -42,6 +42,27 @@ func TestNewHandlerWithHooks(t *testing.T) { }, } + preAccessEvent := handler.HookEvent{ + HTTPRequest: handler.HTTPRequest{ + Method: "POST", + URI: "/files/", + Header: http.Header{ + "X-Hello": []string{"there"}, + }, + }, + Access: handler.AccessInfo{ + Mode: handler.AccessModeRead, + Files: []handler.FileInfo{ + { + ID: "id", + MetaData: handler.MetaData{ + "hello": "world", + }, + }, + }, + }, + } + response := handler.HTTPResponse{ StatusCode: 200, Body: "foobar", @@ -89,6 +110,25 @@ func TestNewHandlerWithHooks(t *testing.T) { Type: HookPreFinish, Event: event, }).Return(HookResponse{}, error), + hookHandler.EXPECT().InvokeHook(HookRequest{ + Type: HookPreAccess, + Event: preAccessEvent, + }).Return(HookResponse{ + HTTPResponse: response, + }, nil), + hookHandler.EXPECT().InvokeHook(HookRequest{ + Type: HookPreAccess, + Event: preAccessEvent, + }).Return(HookResponse{ + RejectAccess: true, + }, nil), + hookHandler.EXPECT().InvokeHook(HookRequest{ + Type: HookPreAccess, + Event: preAccessEvent, + }).Return(HookResponse{ + HTTPResponse: response, + RejectAccess: true, + }, nil), ) // The hooks are executed asynchronously, so we don't know their execution order. @@ -112,7 +152,7 @@ func TestNewHandlerWithHooks(t *testing.T) { Event: event, }) - uploadHandler, err := NewHandlerWithHooks(&config, hookHandler, []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish}) + uploadHandler, err := NewHandlerWithHooks(&config, hookHandler, []HookType{HookPreCreate, HookPostCreate, HookPostReceive, HookPostTerminate, HookPostFinish, HookPreFinish, HookPreAccess}) a.NoError(err) // Successful pre-create hook @@ -148,6 +188,35 @@ func TestNewHandlerWithHooks(t *testing.T) { a.Equal(error, err) a.Equal(handler.HTTPResponse{}, resp_got) + // Successful pre-access hook + err = config.PreUploadAccessCallback(preAccessEvent) + a.NoError(err) + + // Pre-access hook with rejection + err = config.PreUploadAccessCallback(preAccessEvent) + a.Equal(handler.Error{ + ErrorCode: handler.ErrAccessRejectedByServer.ErrorCode, + Message: handler.ErrAccessRejectedByServer.Message, + HTTPResponse: handler.ErrAccessRejectedByServer.HTTPResponse, + }, err) + a.Equal(handler.HTTPResponse{}, resp_got) + + // Pre-access hook with rejection and http override + err = config.PreUploadAccessCallback(preAccessEvent) + a.Equal(handler.Error{ + ErrorCode: handler.ErrAccessRejectedByServer.ErrorCode, + Message: handler.ErrAccessRejectedByServer.Message, + HTTPResponse: handler.HTTPResponse{ + StatusCode: 200, + Body: "foobar", + Header: handler.HTTPHeader{ + "X-Hello": "here", + "Content-Type": "text/plain; charset=utf-8", + }, + }, + }, err) + a.Equal(handler.HTTPResponse{}, resp_got) + // Successful post-* hooks uploadHandler.CreatedUploads <- event uploadHandler.UploadProgress <- event From c0b520b7430511587979df55f5a35ed8b770a006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Berthier?= Date: Tue, 30 Jan 2024 09:47:24 +0100 Subject: [PATCH 3/6] handler: add POST UploadConcat unit-test --- pkg/handler/post_test.go | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/pkg/handler/post_test.go b/pkg/handler/post_test.go index 96b97c11d..cd5aba634 100644 --- a/pkg/handler/post_test.go +++ b/pkg/handler/post_test.go @@ -543,6 +543,62 @@ func TestPost(t *testing.T) { Code: http.StatusForbidden, }).Run(handler, t) }) + + SubTest(t, "UploadConcat", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + uploadA := NewMockFullUpload(ctrl) + uploadB := NewMockFullUpload(ctrl) + uploadC := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "a").Return(uploadA, nil), + uploadA.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + ID: "a", + Offset: 5, + Size: 5, + }, nil), + + store.EXPECT().GetUpload(gomock.Any(), "b").Return(uploadB, nil), + uploadB.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + ID: "b", + Offset: 10, + Size: 10, + }, nil), + + store.EXPECT().NewUpload(gomock.Any(), FileInfo{ + Size: 15, + MetaData: map[string]string{}, + IsFinal: true, + PartialUploads: []string{"a", "b"}, + }).Return(uploadC, nil), + uploadC.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + ID: "foo", + Size: 15, + MetaData: map[string]string{}, + }, nil), + + store.EXPECT().AsConcatableUpload(uploadC).Return(uploadC), + uploadC.EXPECT().ConcatUploads(gomock.Any(), []Upload{uploadA, uploadB}).Return(nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + BasePath: "/files/", + }) + + (&httpTest{ + Method: "POST", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + "Upload-Concat": "final;http://tus.io/files/a http://tus.io/files/b", + }, + Code: http.StatusCreated, + ResHeader: map[string]string{ + "Location": "http://tus.io/files/foo", + }, + }).Run(handler, t) + }) }) SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) { From d17a834101a62a16c88059bbe9103fa15768ddf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Berthier?= Date: Tue, 30 Jan 2024 09:50:31 +0100 Subject: [PATCH 4/6] kooks: fill AccessInfo in pre-create hook when Upload-Concat is used, to check authorization for instance --- pkg/handler/post_test.go | 42 +++++++++++++++++++++++++++++++++ pkg/handler/unrouted_handler.go | 16 ++++++++----- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/pkg/handler/post_test.go b/pkg/handler/post_test.go index cd5aba634..a5cd6b9d2 100644 --- a/pkg/handler/post_test.go +++ b/pkg/handler/post_test.go @@ -599,6 +599,48 @@ func TestPost(t *testing.T) { }, }).Run(handler, t) }) + + SubTest(t, "RejectAccessConcat", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + uploadA := NewMockFullUpload(ctrl) + uploadB := NewMockFullUpload(ctrl) + + gomock.InOrder( + store.EXPECT().GetUpload(gomock.Any(), "a").Return(uploadA, nil), + uploadA.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + ID: "a", + Offset: 5, + Size: 5, + }, nil), + + store.EXPECT().GetUpload(gomock.Any(), "b").Return(uploadB, nil), + uploadB.EXPECT().GetInfo(gomock.Any()).Return(FileInfo{ + ID: "b", + Offset: 10, + Size: 10, + }, nil), + ) + + handler, _ := NewHandler(Config{ + StoreComposer: composer, + BasePath: "/files/", + PreUploadCreateCallback: func(event HookEvent) (HTTPResponse, FileInfoChanges, error) { + return HTTPResponse{}, FileInfoChanges{}, ErrAccessRejectedByServer + }, + }) + + (&httpTest{ + Method: "POST", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + "Upload-Concat": "final;http://tus.io/files/a http://tus.io/files/b", + }, + Code: ErrAccessRejectedByServer.HTTPResponse.StatusCode, + ResHeader: ErrAccessRejectedByServer.HTTPResponse.Header, + ResBody: ErrAccessRejectedByServer.HTTPResponse.Body, + }).Run(handler, t) + }) }) SubTest(t, "ExperimentalProtocol", func(t *testing.T, _ *MockFullDataStore, _ *StoreComposer) { diff --git a/pkg/handler/unrouted_handler.go b/pkg/handler/unrouted_handler.go index 3807b4ccb..a151b81e6 100644 --- a/pkg/handler/unrouted_handler.go +++ b/pkg/handler/unrouted_handler.go @@ -293,6 +293,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) var size int64 var sizeIsDeferred bool var partialUploads []Upload + var partialFileInfos []FileInfo if isFinal { // A final upload must not contain a chunk within the creation request if containsChunk { @@ -300,7 +301,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) return } - partialUploads, size, err = handler.sizeOfUploads(c, partialUploadIDs) + partialUploads, partialFileInfos, size, err = handler.sizeOfUploads(c, partialUploadIDs) if err != nil { handler.sendError(c, err) return @@ -339,7 +340,8 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) } if handler.config.PreUploadCreateCallback != nil { - resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info, nil)) + access := newAccessInfo(AccessModeRead, partialFileInfos) + resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info, &access)) if err != nil { handler.sendError(c, err) return @@ -1310,27 +1312,29 @@ func getHostAndProtocol(r *http.Request, allowForwarded bool) (host, proto strin // The get sum of all sizes for a list of upload ids while checking whether // all of these uploads are finished yet. This is used to calculate the size // of a final resource. -func (handler *UnroutedHandler) sizeOfUploads(ctx context.Context, ids []string) (partialUploads []Upload, size int64, err error) { +func (handler *UnroutedHandler) sizeOfUploads(ctx context.Context, ids []string) (partialUploads []Upload, partialFileInfos []FileInfo, size int64, err error) { partialUploads = make([]Upload, len(ids)) + partialFileInfos = make([]FileInfo, len(ids)) for i, id := range ids { upload, err := handler.composer.Core.GetUpload(ctx, id) if err != nil { - return nil, 0, err + return nil, nil, 0, err } info, err := upload.GetInfo(ctx) if err != nil { - return nil, 0, err + return nil, nil, 0, err } if info.SizeIsDeferred || info.Offset != info.Size { err = ErrUploadNotFinished - return nil, 0, err + return nil, nil, 0, err } size += info.Size partialUploads[i] = upload + partialFileInfos[i] = info } return From fb21b4a68fddea529c67a600269561d9f823f326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Berthier?= Date: Tue, 30 Jan 2024 12:07:40 +0100 Subject: [PATCH 5/6] hooks: add pre-access examples - add some README.md to guide usage - fix grpc example Makefile proto path --- examples/hooks/file/README.md | 5 + examples/hooks/file/pre-access | 10 + examples/hooks/grpc/Makefile | 4 +- examples/hooks/grpc/README.md | 7 + examples/hooks/grpc/hook_pb2.py | 869 ++++++++++++++++++++++++-- examples/hooks/grpc/hook_pb2_grpc.py | 92 +-- examples/hooks/grpc/server.py | 8 + examples/hooks/http/README.md | 7 + examples/hooks/http/server.py | 6 + examples/hooks/plugin/README.md | 7 + examples/hooks/plugin/hook_handler.go | 9 + 11 files changed, 913 insertions(+), 111 deletions(-) create mode 100644 examples/hooks/file/README.md create mode 100755 examples/hooks/file/pre-access create mode 100644 examples/hooks/grpc/README.md create mode 100644 examples/hooks/http/README.md create mode 100644 examples/hooks/plugin/README.md diff --git a/examples/hooks/file/README.md b/examples/hooks/file/README.md new file mode 100644 index 000000000..b9b3ac90d --- /dev/null +++ b/examples/hooks/file/README.md @@ -0,0 +1,5 @@ +# Run file hook exemple + + tusd -hooks-dir=./ -hooks-enabled-events=pre-create,pre-finish,pre-access,post-create,post-receive,post-terminate,post-finish + +Adapt enabled-events hooks list for your needs. diff --git a/examples/hooks/file/pre-access b/examples/hooks/file/pre-access new file mode 100755 index 000000000..6da9f9151 --- /dev/null +++ b/examples/hooks/file/pre-access @@ -0,0 +1,10 @@ +#!/bin/sh + +# This example demonstrates how to read the hook event details +# from stdin, and output debug messages. + +# We use >&2 to write debugging output to stderr. tusd +# will forward these to its stderr. Any output from the +# hook on stdout will be captured by tusd and interpreted +# as a response. +cat /dev/stdin | jq . >&2 diff --git a/examples/hooks/grpc/Makefile b/examples/hooks/grpc/Makefile index 20053d369..c63adcd2a 100644 --- a/examples/hooks/grpc/Makefile +++ b/examples/hooks/grpc/Makefile @@ -1,2 +1,2 @@ -hook_pb2.py: ../../../cmd/tusd/cli/hooks/proto/v2/hook.proto - python3 -m grpc_tools.protoc --proto_path=../../../cmd/tusd/cli/hooks/proto/v2/ hook.proto --python_out=. --grpc_python_out=. +hook_pb2.py: ../../../pkg/hooks/grpc/proto/hook.proto + python3 -m grpc_tools.protoc --proto_path=../../../pkg/hooks/grpc/proto/ hook.proto --python_out=. --grpc_python_out=. diff --git a/examples/hooks/grpc/README.md b/examples/hooks/grpc/README.md new file mode 100644 index 000000000..52bf51e6d --- /dev/null +++ b/examples/hooks/grpc/README.md @@ -0,0 +1,7 @@ +# Run grpc hook exemple + + python3 server.py + + tusd -hooks-grpc=localhost:8000 -hooks-enabled-events=pre-create,pre-finish,pre-access,post-create,post-receive,post-terminate,post-finish + +Adapt enabled-events hooks list for your needs. diff --git a/examples/hooks/grpc/hook_pb2.py b/examples/hooks/grpc/hook_pb2.py index 634c7e95e..948213cc1 100644 --- a/examples/hooks/grpc/hook_pb2.py +++ b/examples/hooks/grpc/hook_pb2.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: hook.proto -"""Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,51 +15,816 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nhook.proto\x12\x02v2\"5\n\x0bHookRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x18\n\x05\x65vent\x18\x02 \x01(\x0b\x32\t.v2.Event\"K\n\x05\x45vent\x12\x1c\n\x06upload\x18\x01 \x01(\x0b\x32\x0c.v2.FileInfo\x12$\n\x0bhttpRequest\x18\x02 \x01(\x0b\x32\x0f.v2.HTTPRequest\"\xc3\x02\n\x08\x46ileInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x03\x12\x16\n\x0esizeIsDeferred\x18\x03 \x01(\x08\x12\x0e\n\x06offset\x18\x04 \x01(\x03\x12,\n\x08metaData\x18\x05 \x03(\x0b\x32\x1a.v2.FileInfo.MetaDataEntry\x12\x11\n\tisPartial\x18\x06 \x01(\x08\x12\x0f\n\x07isFinal\x18\x07 \x01(\x08\x12\x16\n\x0epartialUploads\x18\x08 \x03(\t\x12*\n\x07storage\x18\t \x03(\x0b\x32\x19.v2.FileInfo.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe6\x01\n\x0f\x46ileInfoChanges\x12\n\n\x02id\x18\x01 \x01(\t\x12\x33\n\x08metaData\x18\x02 \x03(\x0b\x32!.v2.FileInfoChanges.MetaDataEntry\x12\x31\n\x07storage\x18\x03 \x03(\x0b\x32 .v2.FileInfoChanges.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x9a\x01\n\x0bHTTPRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\x12\n\nremoteAddr\x18\x03 \x01(\t\x12+\n\x06header\x18\x04 \x03(\x0b\x32\x1b.v2.HTTPRequest.HeaderEntry\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8d\x01\n\x0cHookResponse\x12&\n\x0chttpResponse\x18\x01 \x01(\x0b\x32\x10.v2.HTTPResponse\x12\x14\n\x0crejectUpload\x18\x02 \x01(\x08\x12+\n\x0e\x63hangeFileInfo\x18\x04 \x01(\x0b\x32\x13.v2.FileInfoChanges\x12\x12\n\nstopUpload\x18\x03 \x01(\x08\"\x90\x01\n\x0cHTTPResponse\x12\x12\n\nstatusCode\x18\x01 \x01(\x03\x12.\n\x07headers\x18\x02 \x03(\x0b\x32\x1d.v2.HTTPResponse.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x03 \x01(\t\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32@\n\x0bHookHandler\x12\x31\n\nInvokeHook\x12\x0f.v2.HookRequest\x1a\x10.v2.HookResponse\"\x00\x62\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hook_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - _FILEINFO_METADATAENTRY._options = None - _FILEINFO_METADATAENTRY._serialized_options = b'8\001' - _FILEINFO_STORAGEENTRY._options = None - _FILEINFO_STORAGEENTRY._serialized_options = b'8\001' - _FILEINFOCHANGES_METADATAENTRY._options = None - _FILEINFOCHANGES_METADATAENTRY._serialized_options = b'8\001' - _FILEINFOCHANGES_STORAGEENTRY._options = None - _FILEINFOCHANGES_STORAGEENTRY._serialized_options = b'8\001' - _HTTPREQUEST_HEADERENTRY._options = None - _HTTPREQUEST_HEADERENTRY._serialized_options = b'8\001' - _HTTPRESPONSE_HEADERSENTRY._options = None - _HTTPRESPONSE_HEADERSENTRY._serialized_options = b'8\001' - _HOOKREQUEST._serialized_start=18 - _HOOKREQUEST._serialized_end=71 - _EVENT._serialized_start=73 - _EVENT._serialized_end=148 - _FILEINFO._serialized_start=151 - _FILEINFO._serialized_end=474 - _FILEINFO_METADATAENTRY._serialized_start=379 - _FILEINFO_METADATAENTRY._serialized_end=426 - _FILEINFO_STORAGEENTRY._serialized_start=428 - _FILEINFO_STORAGEENTRY._serialized_end=474 - _FILEINFOCHANGES._serialized_start=477 - _FILEINFOCHANGES._serialized_end=707 - _FILEINFOCHANGES_METADATAENTRY._serialized_start=379 - _FILEINFOCHANGES_METADATAENTRY._serialized_end=426 - _FILEINFOCHANGES_STORAGEENTRY._serialized_start=428 - _FILEINFOCHANGES_STORAGEENTRY._serialized_end=474 - _HTTPREQUEST._serialized_start=710 - _HTTPREQUEST._serialized_end=864 - _HTTPREQUEST_HEADERENTRY._serialized_start=819 - _HTTPREQUEST_HEADERENTRY._serialized_end=864 - _HOOKRESPONSE._serialized_start=867 - _HOOKRESPONSE._serialized_end=1008 - _HTTPRESPONSE._serialized_start=1011 - _HTTPRESPONSE._serialized_end=1155 - _HTTPRESPONSE_HEADERSENTRY._serialized_start=1109 - _HTTPRESPONSE_HEADERSENTRY._serialized_end=1155 - _HOOKHANDLER._serialized_start=1157 - _HOOKHANDLER._serialized_end=1221 +DESCRIPTOR = _descriptor.FileDescriptor( + name='hook.proto', + package='proto', + syntax='proto3', + serialized_pb=_b('\n\nhook.proto\x12\x05proto\"8\n\x0bHookRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x1b\n\x05\x65vent\x18\x02 \x01(\x0b\x32\x0c.proto.Event\"t\n\x05\x45vent\x12\x1f\n\x06upload\x18\x01 \x01(\x0b\x32\x0f.proto.FileInfo\x12\'\n\x0bhttpRequest\x18\x02 \x01(\x0b\x32\x12.proto.HTTPRequest\x12!\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\x0b\x32\x11.proto.AccessInfo\"\xc9\x02\n\x08\x46ileInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x03\x12\x16\n\x0esizeIsDeferred\x18\x03 \x01(\x08\x12\x0e\n\x06offset\x18\x04 \x01(\x03\x12/\n\x08metaData\x18\x05 \x03(\x0b\x32\x1d.proto.FileInfo.MetaDataEntry\x12\x11\n\tisPartial\x18\x06 \x01(\x08\x12\x0f\n\x07isFinal\x18\x07 \x01(\x08\x12\x16\n\x0epartialUploads\x18\x08 \x03(\t\x12-\n\x07storage\x18\t \x03(\x0b\x32\x1c.proto.FileInfo.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\":\n\nAccessInfo\x12\x0c\n\x04mode\x18\x01 \x01(\t\x12\x1e\n\x05\x66iles\x18\x02 \x03(\x0b\x32\x0f.proto.FileInfo\"\xec\x01\n\x0f\x46ileInfoChanges\x12\n\n\x02id\x18\x01 \x01(\t\x12\x36\n\x08metaData\x18\x02 \x03(\x0b\x32$.proto.FileInfoChanges.MetaDataEntry\x12\x34\n\x07storage\x18\x03 \x03(\x0b\x32#.proto.FileInfoChanges.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x9d\x01\n\x0bHTTPRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\x12\n\nremoteAddr\x18\x03 \x01(\t\x12.\n\x06header\x18\x04 \x03(\x0b\x32\x1e.proto.HTTPRequest.HeaderEntry\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa9\x01\n\x0cHookResponse\x12)\n\x0chttpResponse\x18\x01 \x01(\x0b\x32\x13.proto.HTTPResponse\x12\x14\n\x0crejectUpload\x18\x02 \x01(\x08\x12.\n\x0e\x63hangeFileInfo\x18\x04 \x01(\x0b\x32\x16.proto.FileInfoChanges\x12\x12\n\nstopUpload\x18\x03 \x01(\x08\x12\x14\n\x0crejectAccess\x18\x05 \x01(\x08\"\x90\x01\n\x0cHTTPResponse\x12\x12\n\nstatusCode\x18\x01 \x01(\x03\x12/\n\x06header\x18\x02 \x03(\x0b\x32\x1f.proto.HTTPResponse.HeaderEntry\x12\x0c\n\x04\x62ody\x18\x03 \x01(\t\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32\x46\n\x0bHookHandler\x12\x37\n\nInvokeHook\x12\x12.proto.HookRequest\x1a\x13.proto.HookResponse\"\x00\x42\x12Z\x10hooks/grpc/protob\x06proto3') +) + + + + +_HOOKREQUEST = _descriptor.Descriptor( + name='HookRequest', + full_name='proto.HookRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='proto.HookRequest.type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='event', full_name='proto.HookRequest.event', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=21, + serialized_end=77, +) + + +_EVENT = _descriptor.Descriptor( + name='Event', + full_name='proto.Event', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='upload', full_name='proto.Event.upload', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='httpRequest', full_name='proto.Event.httpRequest', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='access', full_name='proto.Event.access', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=79, + serialized_end=195, +) + + +_FILEINFO_METADATAENTRY = _descriptor.Descriptor( + name='MetaDataEntry', + full_name='proto.FileInfo.MetaDataEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.FileInfo.MetaDataEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='proto.FileInfo.MetaDataEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=432, + serialized_end=479, +) + +_FILEINFO_STORAGEENTRY = _descriptor.Descriptor( + name='StorageEntry', + full_name='proto.FileInfo.StorageEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.FileInfo.StorageEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='proto.FileInfo.StorageEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=481, + serialized_end=527, +) + +_FILEINFO = _descriptor.Descriptor( + name='FileInfo', + full_name='proto.FileInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='proto.FileInfo.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='size', full_name='proto.FileInfo.size', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='sizeIsDeferred', full_name='proto.FileInfo.sizeIsDeferred', index=2, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='offset', full_name='proto.FileInfo.offset', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='metaData', full_name='proto.FileInfo.metaData', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='isPartial', full_name='proto.FileInfo.isPartial', index=5, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='isFinal', full_name='proto.FileInfo.isFinal', index=6, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='partialUploads', full_name='proto.FileInfo.partialUploads', index=7, + number=8, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='storage', full_name='proto.FileInfo.storage', index=8, + number=9, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_FILEINFO_METADATAENTRY, _FILEINFO_STORAGEENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=198, + serialized_end=527, +) + + +_ACCESSINFO = _descriptor.Descriptor( + name='AccessInfo', + full_name='proto.AccessInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='mode', full_name='proto.AccessInfo.mode', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='files', full_name='proto.AccessInfo.files', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=529, + serialized_end=587, +) + + +_FILEINFOCHANGES_METADATAENTRY = _descriptor.Descriptor( + name='MetaDataEntry', + full_name='proto.FileInfoChanges.MetaDataEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.FileInfoChanges.MetaDataEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='proto.FileInfoChanges.MetaDataEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=432, + serialized_end=479, +) + +_FILEINFOCHANGES_STORAGEENTRY = _descriptor.Descriptor( + name='StorageEntry', + full_name='proto.FileInfoChanges.StorageEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.FileInfoChanges.StorageEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='proto.FileInfoChanges.StorageEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=481, + serialized_end=527, +) + +_FILEINFOCHANGES = _descriptor.Descriptor( + name='FileInfoChanges', + full_name='proto.FileInfoChanges', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='proto.FileInfoChanges.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='metaData', full_name='proto.FileInfoChanges.metaData', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='storage', full_name='proto.FileInfoChanges.storage', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_FILEINFOCHANGES_METADATAENTRY, _FILEINFOCHANGES_STORAGEENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=590, + serialized_end=826, +) + + +_HTTPREQUEST_HEADERENTRY = _descriptor.Descriptor( + name='HeaderEntry', + full_name='proto.HTTPRequest.HeaderEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.HTTPRequest.HeaderEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='proto.HTTPRequest.HeaderEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=941, + serialized_end=986, +) + +_HTTPREQUEST = _descriptor.Descriptor( + name='HTTPRequest', + full_name='proto.HTTPRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='method', full_name='proto.HTTPRequest.method', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='uri', full_name='proto.HTTPRequest.uri', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='remoteAddr', full_name='proto.HTTPRequest.remoteAddr', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='header', full_name='proto.HTTPRequest.header', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_HTTPREQUEST_HEADERENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=829, + serialized_end=986, +) + + +_HOOKRESPONSE = _descriptor.Descriptor( + name='HookResponse', + full_name='proto.HookResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='httpResponse', full_name='proto.HookResponse.httpResponse', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='rejectUpload', full_name='proto.HookResponse.rejectUpload', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='changeFileInfo', full_name='proto.HookResponse.changeFileInfo', index=2, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='stopUpload', full_name='proto.HookResponse.stopUpload', index=3, + number=3, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='rejectAccess', full_name='proto.HookResponse.rejectAccess', index=4, + number=5, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=989, + serialized_end=1158, +) + + +_HTTPRESPONSE_HEADERENTRY = _descriptor.Descriptor( + name='HeaderEntry', + full_name='proto.HTTPResponse.HeaderEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.HTTPResponse.HeaderEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='proto.HTTPResponse.HeaderEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=941, + serialized_end=986, +) + +_HTTPRESPONSE = _descriptor.Descriptor( + name='HTTPResponse', + full_name='proto.HTTPResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='statusCode', full_name='proto.HTTPResponse.statusCode', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='header', full_name='proto.HTTPResponse.header', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='body', full_name='proto.HTTPResponse.body', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_HTTPRESPONSE_HEADERENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1161, + serialized_end=1305, +) + +_HOOKREQUEST.fields_by_name['event'].message_type = _EVENT +_EVENT.fields_by_name['upload'].message_type = _FILEINFO +_EVENT.fields_by_name['httpRequest'].message_type = _HTTPREQUEST +_EVENT.fields_by_name['access'].message_type = _ACCESSINFO +_FILEINFO_METADATAENTRY.containing_type = _FILEINFO +_FILEINFO_STORAGEENTRY.containing_type = _FILEINFO +_FILEINFO.fields_by_name['metaData'].message_type = _FILEINFO_METADATAENTRY +_FILEINFO.fields_by_name['storage'].message_type = _FILEINFO_STORAGEENTRY +_ACCESSINFO.fields_by_name['files'].message_type = _FILEINFO +_FILEINFOCHANGES_METADATAENTRY.containing_type = _FILEINFOCHANGES +_FILEINFOCHANGES_STORAGEENTRY.containing_type = _FILEINFOCHANGES +_FILEINFOCHANGES.fields_by_name['metaData'].message_type = _FILEINFOCHANGES_METADATAENTRY +_FILEINFOCHANGES.fields_by_name['storage'].message_type = _FILEINFOCHANGES_STORAGEENTRY +_HTTPREQUEST_HEADERENTRY.containing_type = _HTTPREQUEST +_HTTPREQUEST.fields_by_name['header'].message_type = _HTTPREQUEST_HEADERENTRY +_HOOKRESPONSE.fields_by_name['httpResponse'].message_type = _HTTPRESPONSE +_HOOKRESPONSE.fields_by_name['changeFileInfo'].message_type = _FILEINFOCHANGES +_HTTPRESPONSE_HEADERENTRY.containing_type = _HTTPRESPONSE +_HTTPRESPONSE.fields_by_name['header'].message_type = _HTTPRESPONSE_HEADERENTRY +DESCRIPTOR.message_types_by_name['HookRequest'] = _HOOKREQUEST +DESCRIPTOR.message_types_by_name['Event'] = _EVENT +DESCRIPTOR.message_types_by_name['FileInfo'] = _FILEINFO +DESCRIPTOR.message_types_by_name['AccessInfo'] = _ACCESSINFO +DESCRIPTOR.message_types_by_name['FileInfoChanges'] = _FILEINFOCHANGES +DESCRIPTOR.message_types_by_name['HTTPRequest'] = _HTTPREQUEST +DESCRIPTOR.message_types_by_name['HookResponse'] = _HOOKRESPONSE +DESCRIPTOR.message_types_by_name['HTTPResponse'] = _HTTPRESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +HookRequest = _reflection.GeneratedProtocolMessageType('HookRequest', (_message.Message,), dict( + DESCRIPTOR = _HOOKREQUEST, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.HookRequest) + )) +_sym_db.RegisterMessage(HookRequest) + +Event = _reflection.GeneratedProtocolMessageType('Event', (_message.Message,), dict( + DESCRIPTOR = _EVENT, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.Event) + )) +_sym_db.RegisterMessage(Event) + +FileInfo = _reflection.GeneratedProtocolMessageType('FileInfo', (_message.Message,), dict( + + MetaDataEntry = _reflection.GeneratedProtocolMessageType('MetaDataEntry', (_message.Message,), dict( + DESCRIPTOR = _FILEINFO_METADATAENTRY, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.FileInfo.MetaDataEntry) + )) + , + + StorageEntry = _reflection.GeneratedProtocolMessageType('StorageEntry', (_message.Message,), dict( + DESCRIPTOR = _FILEINFO_STORAGEENTRY, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.FileInfo.StorageEntry) + )) + , + DESCRIPTOR = _FILEINFO, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.FileInfo) + )) +_sym_db.RegisterMessage(FileInfo) +_sym_db.RegisterMessage(FileInfo.MetaDataEntry) +_sym_db.RegisterMessage(FileInfo.StorageEntry) + +AccessInfo = _reflection.GeneratedProtocolMessageType('AccessInfo', (_message.Message,), dict( + DESCRIPTOR = _ACCESSINFO, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.AccessInfo) + )) +_sym_db.RegisterMessage(AccessInfo) + +FileInfoChanges = _reflection.GeneratedProtocolMessageType('FileInfoChanges', (_message.Message,), dict( + + MetaDataEntry = _reflection.GeneratedProtocolMessageType('MetaDataEntry', (_message.Message,), dict( + DESCRIPTOR = _FILEINFOCHANGES_METADATAENTRY, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.FileInfoChanges.MetaDataEntry) + )) + , + + StorageEntry = _reflection.GeneratedProtocolMessageType('StorageEntry', (_message.Message,), dict( + DESCRIPTOR = _FILEINFOCHANGES_STORAGEENTRY, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.FileInfoChanges.StorageEntry) + )) + , + DESCRIPTOR = _FILEINFOCHANGES, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.FileInfoChanges) + )) +_sym_db.RegisterMessage(FileInfoChanges) +_sym_db.RegisterMessage(FileInfoChanges.MetaDataEntry) +_sym_db.RegisterMessage(FileInfoChanges.StorageEntry) + +HTTPRequest = _reflection.GeneratedProtocolMessageType('HTTPRequest', (_message.Message,), dict( + + HeaderEntry = _reflection.GeneratedProtocolMessageType('HeaderEntry', (_message.Message,), dict( + DESCRIPTOR = _HTTPREQUEST_HEADERENTRY, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.HTTPRequest.HeaderEntry) + )) + , + DESCRIPTOR = _HTTPREQUEST, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.HTTPRequest) + )) +_sym_db.RegisterMessage(HTTPRequest) +_sym_db.RegisterMessage(HTTPRequest.HeaderEntry) + +HookResponse = _reflection.GeneratedProtocolMessageType('HookResponse', (_message.Message,), dict( + DESCRIPTOR = _HOOKRESPONSE, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.HookResponse) + )) +_sym_db.RegisterMessage(HookResponse) + +HTTPResponse = _reflection.GeneratedProtocolMessageType('HTTPResponse', (_message.Message,), dict( + + HeaderEntry = _reflection.GeneratedProtocolMessageType('HeaderEntry', (_message.Message,), dict( + DESCRIPTOR = _HTTPRESPONSE_HEADERENTRY, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.HTTPResponse.HeaderEntry) + )) + , + DESCRIPTOR = _HTTPRESPONSE, + __module__ = 'hook_pb2' + # @@protoc_insertion_point(class_scope:proto.HTTPResponse) + )) +_sym_db.RegisterMessage(HTTPResponse) +_sym_db.RegisterMessage(HTTPResponse.HeaderEntry) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('Z\020hooks/grpc/proto')) +_FILEINFO_METADATAENTRY.has_options = True +_FILEINFO_METADATAENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_FILEINFO_STORAGEENTRY.has_options = True +_FILEINFO_STORAGEENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_FILEINFOCHANGES_METADATAENTRY.has_options = True +_FILEINFOCHANGES_METADATAENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_FILEINFOCHANGES_STORAGEENTRY.has_options = True +_FILEINFOCHANGES_STORAGEENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_HTTPREQUEST_HEADERENTRY.has_options = True +_HTTPREQUEST_HEADERENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_HTTPRESPONSE_HEADERENTRY.has_options = True +_HTTPRESPONSE_HEADERENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) + +_HOOKHANDLER = _descriptor.ServiceDescriptor( + name='HookHandler', + full_name='proto.HookHandler', + file=DESCRIPTOR, + index=0, + options=None, + serialized_start=1307, + serialized_end=1377, + methods=[ + _descriptor.MethodDescriptor( + name='InvokeHook', + full_name='proto.HookHandler.InvokeHook', + index=0, + containing_service=None, + input_type=_HOOKREQUEST, + output_type=_HOOKRESPONSE, + options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_HOOKHANDLER) + +DESCRIPTOR.services_by_name['HookHandler'] = _HOOKHANDLER + # @@protoc_insertion_point(module_scope) diff --git a/examples/hooks/grpc/hook_pb2_grpc.py b/examples/hooks/grpc/hook_pb2_grpc.py index 241c0d019..ffdbd5309 100644 --- a/examples/hooks/grpc/hook_pb2_grpc.py +++ b/examples/hooks/grpc/hook_pb2_grpc.py @@ -1,74 +1,50 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" import grpc import hook_pb2 as hook__pb2 class HookHandlerStub(object): - """The hook service definition. - """ + """The hook service definition. + """ - def __init__(self, channel): - """Constructor. + def __init__(self, channel): + """Constructor. - Args: - channel: A grpc.Channel. - """ - self.InvokeHook = channel.unary_unary( - '/v2.HookHandler/InvokeHook', - request_serializer=hook__pb2.HookRequest.SerializeToString, - response_deserializer=hook__pb2.HookResponse.FromString, - ) + Args: + channel: A grpc.Channel. + """ + self.InvokeHook = channel.unary_unary( + '/proto.HookHandler/InvokeHook', + request_serializer=hook__pb2.HookRequest.SerializeToString, + response_deserializer=hook__pb2.HookResponse.FromString, + ) class HookHandlerServicer(object): - """The hook service definition. + """The hook service definition. + """ + + def InvokeHook(self, request, context): + """InvokeHook is invoked for every hook that is executed. HookRequest contains the + corresponding information about the hook type, the involved upload, and + causing HTTP request. + The return value HookResponse allows to stop or reject an upload, as well as modifying + the HTTP response. See the documentation for HookResponse for more details. """ - - def InvokeHook(self, request, context): - """InvokeHook is invoked for every hook that is executed. HookRequest contains the - corresponding information about the hook type, the involved upload, and - causing HTTP request. - The return value HookResponse allows to stop or reject an upload, as well as modifying - the HTTP response. See the documentation for HookResponse for more details. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') def add_HookHandlerServicer_to_server(servicer, server): - rpc_method_handlers = { - 'InvokeHook': grpc.unary_unary_rpc_method_handler( - servicer.InvokeHook, - request_deserializer=hook__pb2.HookRequest.FromString, - response_serializer=hook__pb2.HookResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'v2.HookHandler', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class HookHandler(object): - """The hook service definition. - """ - - @staticmethod - def InvokeHook(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/v2.HookHandler/InvokeHook', - hook__pb2.HookRequest.SerializeToString, - hook__pb2.HookResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + rpc_method_handlers = { + 'InvokeHook': grpc.unary_unary_rpc_method_handler( + servicer.InvokeHook, + request_deserializer=hook__pb2.HookRequest.FromString, + response_serializer=hook__pb2.HookResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'proto.HookHandler', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) diff --git a/examples/hooks/grpc/server.py b/examples/hooks/grpc/server.py index 15c8b3a4c..1e1040e91 100644 --- a/examples/hooks/grpc/server.py +++ b/examples/hooks/grpc/server.py @@ -37,6 +37,14 @@ def InvokeHook(self, hook_request, context): hook_response.changeFileInfo.metaData['filename'] = metaData['filename'] hook_response.changeFileInfo.metaData['creation_time'] = time.ctime() + + # Example: Use the pre-access hook to print each upload access + if hook_request.type == 'pre-access': + mode = hook_request.event.access.mode + id = hook_request.event.access.files[0].id + size = hook_request.event.access.files[0].size + print(f'Access {id} (mode={mode}, size={size} bytes)') + # Example: Use the post-finish hook to print information about a completed upload, # including its storage location. if hook_request.type == 'post-finish': diff --git a/examples/hooks/http/README.md b/examples/hooks/http/README.md new file mode 100644 index 000000000..ecf9ade8f --- /dev/null +++ b/examples/hooks/http/README.md @@ -0,0 +1,7 @@ +# Run http hook exemple + + python3 server.py + + tusd -hooks-http=http://localhost:8000 -hooks-enabled-events=pre-create,pre-finish,pre-access,post-create,post-receive,post-terminate,post-finish + +Adapt enabled-events hooks list for your needs. diff --git a/examples/hooks/http/server.py b/examples/hooks/http/server.py index 4f692126a..01b067383 100644 --- a/examples/hooks/http/server.py +++ b/examples/hooks/http/server.py @@ -50,6 +50,12 @@ def do_POST(self): 'creation_time': time.ctime(), } + # Example: Use the pre-access hook to print each upload access + if hook_request['Type'] == 'pre-access': + mode = hook_request['Event']['Access']['Mode'] + id = hook_request['Event']['Access']['Files'][0]['ID'] + size = hook_request['Event']['Access']['Files'][0]['Size'] + print(f'Access {id} (mode={mode}, size={size} bytes)') # Example: Use the post-finish hook to print information about a completed upload, # including its storage location. diff --git a/examples/hooks/plugin/README.md b/examples/hooks/plugin/README.md new file mode 100644 index 000000000..df91ef0f6 --- /dev/null +++ b/examples/hooks/plugin/README.md @@ -0,0 +1,7 @@ +# Run plugin hook exemple + + make + + tusd -hooks-http=./hook_handler -hooks-enabled-events=pre-create,pre-finish,pre-access,post-create,post-receive,post-terminate,post-finish + +Adapt enabled-events hooks list for your needs. diff --git a/examples/hooks/plugin/hook_handler.go b/examples/hooks/plugin/hook_handler.go index 0d359e4ee..8bbbd588e 100644 --- a/examples/hooks/plugin/hook_handler.go +++ b/examples/hooks/plugin/hook_handler.go @@ -41,6 +41,15 @@ func (g *MyHookHandler) InvokeHook(req hooks.HookRequest) (res hooks.HookRespons } } + // Example: Use the pre-access hook to print each upload access + if req.Type == hooks.HookPreAccess { + mode := req.Event.Access.Mode + id := req.Event.Access.Files[0].ID + size := req.Event.Access.Files[0].Size + + log.Printf("Access %s (mode=%s, size=%d bytes) \n", id, mode, size) + } + // Example: Use the post-finish hook to print information about a completed upload, // including its storage location. if req.Type == hooks.HookPreFinish { From 94336988b002f8474a36e755e4581897abc928d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Berthier?= Date: Fri, 16 Feb 2024 10:02:57 +0100 Subject: [PATCH 6/6] hooks: pre-access fix MR feedbacks --- docs/hooks.md | 6 +- examples/hooks/grpc/hook_pb2.py | 36 +++---- examples/hooks/grpc/server.py | 4 +- examples/hooks/http/server.py | 4 +- examples/hooks/plugin/hook_handler.go | 4 +- pkg/handler/config.go | 2 +- pkg/handler/hooks.go | 20 ++-- pkg/handler/post_test.go | 4 +- pkg/handler/unrouted_handler.go | 39 +++---- pkg/hooks/grpc/grpc.go | 4 +- pkg/hooks/grpc/proto/hook.pb.go | 140 +++++++++++++------------- pkg/hooks/grpc/proto/hook.proto | 2 +- pkg/hooks/hooks_test.go | 2 +- 13 files changed, 134 insertions(+), 133 deletions(-) diff --git a/docs/hooks.md b/docs/hooks.md index e502bd88e..a26b7dc49 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -21,7 +21,7 @@ The table below provides an overview of all available hooks. | pre-finish | Yes | after all upload data has been received but before a response is sent. | sending custom data when an upload is finished | Yes | | post-finish | No | after all upload data has been received and after a response is sent. | post-processing of upload, logging of upload end | Yes | | post-terminate | No | after an upload has been terminated. | clean up of allocated resources | Yes | -| pre-access | Yes | before an existing upload is access (Head/Get/Patch/Delete). | validation of user authentication | No | +| pre-access | Yes | before an existing upload is access (Head/Get/Patch/Delete/Upload-Concat). | validation of user authentication | No | Users should be aware of following things: - If a hook is _blocking_, tusd will wait with further processing until the hook is completed. This is useful for validation and authentication, where further processing should be stopped if the hook determines to do so. However, long execution time may impact the user experience because the upload processing is blocked while the hook executes. @@ -113,7 +113,7 @@ Below you can find an annotated, JSON-ish encoded example of a hook request: "Mode": "read" // All files info that will be access by http request // Use an array because of Upload-Concat that may target several files - "Files": [ + "Uploads": [ // same as Upload ] } @@ -320,7 +320,7 @@ For example, assume that every upload must belong to a specific user project. Th ### Authenticating Users -User authentication can be achieved by two ways: Either, user tokens can be included in the upload meta data, as described in the above example. Alternatively, traditional header fields, such as `Authorization` or `Cookie` can be used to carry user-identifying information. These header values are also present for the hook requests and are accessible for the `pre-create` and `pre-access` hooks, where the authorization tokens or cookies can be validated to authenticate the user. +User authentication can be achieved by two ways: Either, user tokens can be included in the upload meta data, as described in the above example. Alternatively, traditional header fields, such as `Authorization` or `Cookie` can be used to carry user-identifying information. These header values are also present for the hook requests and are accessible for the `pre-access` hook, where the authorization tokens or cookies can be validated to authenticate the user. If the authentication is successful, the hook can return an empty hook response to indicate tusd that the upload should continue as normal. If the authentication fails, the hook can instruct tusd to reject the upload and return a custom error response to the client. For example, this is a possible hook response: diff --git a/examples/hooks/grpc/hook_pb2.py b/examples/hooks/grpc/hook_pb2.py index 948213cc1..e76cae896 100644 --- a/examples/hooks/grpc/hook_pb2.py +++ b/examples/hooks/grpc/hook_pb2.py @@ -19,7 +19,7 @@ name='hook.proto', package='proto', syntax='proto3', - serialized_pb=_b('\n\nhook.proto\x12\x05proto\"8\n\x0bHookRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x1b\n\x05\x65vent\x18\x02 \x01(\x0b\x32\x0c.proto.Event\"t\n\x05\x45vent\x12\x1f\n\x06upload\x18\x01 \x01(\x0b\x32\x0f.proto.FileInfo\x12\'\n\x0bhttpRequest\x18\x02 \x01(\x0b\x32\x12.proto.HTTPRequest\x12!\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\x0b\x32\x11.proto.AccessInfo\"\xc9\x02\n\x08\x46ileInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x03\x12\x16\n\x0esizeIsDeferred\x18\x03 \x01(\x08\x12\x0e\n\x06offset\x18\x04 \x01(\x03\x12/\n\x08metaData\x18\x05 \x03(\x0b\x32\x1d.proto.FileInfo.MetaDataEntry\x12\x11\n\tisPartial\x18\x06 \x01(\x08\x12\x0f\n\x07isFinal\x18\x07 \x01(\x08\x12\x16\n\x0epartialUploads\x18\x08 \x03(\t\x12-\n\x07storage\x18\t \x03(\x0b\x32\x1c.proto.FileInfo.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\":\n\nAccessInfo\x12\x0c\n\x04mode\x18\x01 \x01(\t\x12\x1e\n\x05\x66iles\x18\x02 \x03(\x0b\x32\x0f.proto.FileInfo\"\xec\x01\n\x0f\x46ileInfoChanges\x12\n\n\x02id\x18\x01 \x01(\t\x12\x36\n\x08metaData\x18\x02 \x03(\x0b\x32$.proto.FileInfoChanges.MetaDataEntry\x12\x34\n\x07storage\x18\x03 \x03(\x0b\x32#.proto.FileInfoChanges.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x9d\x01\n\x0bHTTPRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\x12\n\nremoteAddr\x18\x03 \x01(\t\x12.\n\x06header\x18\x04 \x03(\x0b\x32\x1e.proto.HTTPRequest.HeaderEntry\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa9\x01\n\x0cHookResponse\x12)\n\x0chttpResponse\x18\x01 \x01(\x0b\x32\x13.proto.HTTPResponse\x12\x14\n\x0crejectUpload\x18\x02 \x01(\x08\x12.\n\x0e\x63hangeFileInfo\x18\x04 \x01(\x0b\x32\x16.proto.FileInfoChanges\x12\x12\n\nstopUpload\x18\x03 \x01(\x08\x12\x14\n\x0crejectAccess\x18\x05 \x01(\x08\"\x90\x01\n\x0cHTTPResponse\x12\x12\n\nstatusCode\x18\x01 \x01(\x03\x12/\n\x06header\x18\x02 \x03(\x0b\x32\x1f.proto.HTTPResponse.HeaderEntry\x12\x0c\n\x04\x62ody\x18\x03 \x01(\t\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32\x46\n\x0bHookHandler\x12\x37\n\nInvokeHook\x12\x12.proto.HookRequest\x1a\x13.proto.HookResponse\"\x00\x42\x12Z\x10hooks/grpc/protob\x06proto3') + serialized_pb=_b('\n\nhook.proto\x12\x05proto\"8\n\x0bHookRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x1b\n\x05\x65vent\x18\x02 \x01(\x0b\x32\x0c.proto.Event\"t\n\x05\x45vent\x12\x1f\n\x06upload\x18\x01 \x01(\x0b\x32\x0f.proto.FileInfo\x12\'\n\x0bhttpRequest\x18\x02 \x01(\x0b\x32\x12.proto.HTTPRequest\x12!\n\x06\x61\x63\x63\x65ss\x18\x03 \x01(\x0b\x32\x11.proto.AccessInfo\"\xc9\x02\n\x08\x46ileInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x03\x12\x16\n\x0esizeIsDeferred\x18\x03 \x01(\x08\x12\x0e\n\x06offset\x18\x04 \x01(\x03\x12/\n\x08metaData\x18\x05 \x03(\x0b\x32\x1d.proto.FileInfo.MetaDataEntry\x12\x11\n\tisPartial\x18\x06 \x01(\x08\x12\x0f\n\x07isFinal\x18\x07 \x01(\x08\x12\x16\n\x0epartialUploads\x18\x08 \x03(\t\x12-\n\x07storage\x18\t \x03(\x0b\x32\x1c.proto.FileInfo.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"<\n\nAccessInfo\x12\x0c\n\x04mode\x18\x01 \x01(\t\x12 \n\x07uploads\x18\x02 \x03(\x0b\x32\x0f.proto.FileInfo\"\xec\x01\n\x0f\x46ileInfoChanges\x12\n\n\x02id\x18\x01 \x01(\t\x12\x36\n\x08metaData\x18\x02 \x03(\x0b\x32$.proto.FileInfoChanges.MetaDataEntry\x12\x34\n\x07storage\x18\x03 \x03(\x0b\x32#.proto.FileInfoChanges.StorageEntry\x1a/\n\rMetaDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a.\n\x0cStorageEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x9d\x01\n\x0bHTTPRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\x12\n\nremoteAddr\x18\x03 \x01(\t\x12.\n\x06header\x18\x04 \x03(\x0b\x32\x1e.proto.HTTPRequest.HeaderEntry\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa9\x01\n\x0cHookResponse\x12)\n\x0chttpResponse\x18\x01 \x01(\x0b\x32\x13.proto.HTTPResponse\x12\x14\n\x0crejectUpload\x18\x02 \x01(\x08\x12.\n\x0e\x63hangeFileInfo\x18\x04 \x01(\x0b\x32\x16.proto.FileInfoChanges\x12\x12\n\nstopUpload\x18\x03 \x01(\x08\x12\x14\n\x0crejectAccess\x18\x05 \x01(\x08\"\x90\x01\n\x0cHTTPResponse\x12\x12\n\nstatusCode\x18\x01 \x01(\x03\x12/\n\x06header\x18\x02 \x03(\x0b\x32\x1f.proto.HTTPResponse.HeaderEntry\x12\x0c\n\x04\x62ody\x18\x03 \x01(\t\x1a-\n\x0bHeaderEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32\x46\n\x0bHookHandler\x12\x37\n\nInvokeHook\x12\x12.proto.HookRequest\x1a\x13.proto.HookResponse\"\x00\x42\x12Z\x10hooks/grpc/protob\x06proto3') ) @@ -284,7 +284,7 @@ is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='files', full_name='proto.AccessInfo.files', index=1, + name='uploads', full_name='proto.AccessInfo.uploads', index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -303,7 +303,7 @@ oneofs=[ ], serialized_start=529, - serialized_end=587, + serialized_end=589, ) @@ -421,8 +421,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=590, - serialized_end=826, + serialized_start=592, + serialized_end=828, ) @@ -459,8 +459,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=941, - serialized_end=986, + serialized_start=943, + serialized_end=988, ) _HTTPREQUEST = _descriptor.Descriptor( @@ -510,8 +510,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=829, - serialized_end=986, + serialized_start=831, + serialized_end=988, ) @@ -569,8 +569,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=989, - serialized_end=1158, + serialized_start=991, + serialized_end=1160, ) @@ -607,8 +607,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=941, - serialized_end=986, + serialized_start=943, + serialized_end=988, ) _HTTPRESPONSE = _descriptor.Descriptor( @@ -651,8 +651,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1161, - serialized_end=1305, + serialized_start=1163, + serialized_end=1307, ) _HOOKREQUEST.fields_by_name['event'].message_type = _EVENT @@ -663,7 +663,7 @@ _FILEINFO_STORAGEENTRY.containing_type = _FILEINFO _FILEINFO.fields_by_name['metaData'].message_type = _FILEINFO_METADATAENTRY _FILEINFO.fields_by_name['storage'].message_type = _FILEINFO_STORAGEENTRY -_ACCESSINFO.fields_by_name['files'].message_type = _FILEINFO +_ACCESSINFO.fields_by_name['uploads'].message_type = _FILEINFO _FILEINFOCHANGES_METADATAENTRY.containing_type = _FILEINFOCHANGES _FILEINFOCHANGES_STORAGEENTRY.containing_type = _FILEINFOCHANGES _FILEINFOCHANGES.fields_by_name['metaData'].message_type = _FILEINFOCHANGES_METADATAENTRY @@ -810,8 +810,8 @@ file=DESCRIPTOR, index=0, options=None, - serialized_start=1307, - serialized_end=1377, + serialized_start=1309, + serialized_end=1379, methods=[ _descriptor.MethodDescriptor( name='InvokeHook', diff --git a/examples/hooks/grpc/server.py b/examples/hooks/grpc/server.py index 1e1040e91..745d2cf63 100644 --- a/examples/hooks/grpc/server.py +++ b/examples/hooks/grpc/server.py @@ -41,8 +41,8 @@ def InvokeHook(self, hook_request, context): # Example: Use the pre-access hook to print each upload access if hook_request.type == 'pre-access': mode = hook_request.event.access.mode - id = hook_request.event.access.files[0].id - size = hook_request.event.access.files[0].size + id = hook_request.event.access.uploads[0].id + size = hook_request.event.access.uploads[0].size print(f'Access {id} (mode={mode}, size={size} bytes)') # Example: Use the post-finish hook to print information about a completed upload, diff --git a/examples/hooks/http/server.py b/examples/hooks/http/server.py index 01b067383..e0921b01a 100644 --- a/examples/hooks/http/server.py +++ b/examples/hooks/http/server.py @@ -53,8 +53,8 @@ def do_POST(self): # Example: Use the pre-access hook to print each upload access if hook_request['Type'] == 'pre-access': mode = hook_request['Event']['Access']['Mode'] - id = hook_request['Event']['Access']['Files'][0]['ID'] - size = hook_request['Event']['Access']['Files'][0]['Size'] + id = hook_request['Event']['Access']['Uploads'][0]['ID'] + size = hook_request['Event']['Access']['Uploads'][0]['Size'] print(f'Access {id} (mode={mode}, size={size} bytes)') # Example: Use the post-finish hook to print information about a completed upload, diff --git a/examples/hooks/plugin/hook_handler.go b/examples/hooks/plugin/hook_handler.go index 8bbbd588e..00c0839a8 100644 --- a/examples/hooks/plugin/hook_handler.go +++ b/examples/hooks/plugin/hook_handler.go @@ -44,8 +44,8 @@ func (g *MyHookHandler) InvokeHook(req hooks.HookRequest) (res hooks.HookRespons // Example: Use the pre-access hook to print each upload access if req.Type == hooks.HookPreAccess { mode := req.Event.Access.Mode - id := req.Event.Access.Files[0].ID - size := req.Event.Access.Files[0].Size + id := req.Event.Access.Uploads[0].ID + size := req.Event.Access.Uploads[0].Size log.Printf("Access %s (mode=%s, size=%d bytes) \n", id, mode, size) } diff --git a/pkg/handler/config.go b/pkg/handler/config.go index f8de142be..ba40731b0 100644 --- a/pkg/handler/config.go +++ b/pkg/handler/config.go @@ -70,7 +70,7 @@ type Config struct { // more details on its behavior. If you do not want to make any changes, return an empty struct. PreUploadCreateCallback func(hook HookEvent) (HTTPResponse, FileInfoChanges, error) // PreUploadAccessCallback will be invoked before accessing an upload, if the - // property is supplied, on Get/Head/Patch/Delete requests. + // property is supplied, on Get/Head/Patch/Delete/Upload-Concat requests. // If the callback returns no error, the requests will continue. // If the error is non-nil, the requests will be rejected. This can be used to implement // authorization. diff --git a/pkg/handler/hooks.go b/pkg/handler/hooks.go index 5b34be8b6..1e4956c2c 100644 --- a/pkg/handler/hooks.go +++ b/pkg/handler/hooks.go @@ -44,10 +44,10 @@ type AccessInfo struct { // All files info that will be access by http request // Use an array because of Upload-Concat that may target several files - Files []FileInfo + Uploads []FileInfo } -func newHookEvent(c *httpContext, info *FileInfo, accessInfo *AccessInfo) HookEvent { +func newHookEvent(c *httpContext, info *FileInfo) HookEvent { // The Host header field is not present in the header map, see https://pkg.go.dev/net/http#Request: // > For incoming requests, the Host header is promoted to the // > Request.Host field and removed from the Header map. @@ -58,10 +58,6 @@ func newHookEvent(c *httpContext, info *FileInfo, accessInfo *AccessInfo) HookEv if info != nil { upload = *info } - var access AccessInfo - if accessInfo != nil { - access = *accessInfo - } return HookEvent{ Context: c, Upload: upload, @@ -71,13 +67,15 @@ func newHookEvent(c *httpContext, info *FileInfo, accessInfo *AccessInfo) HookEv RemoteAddr: c.req.RemoteAddr, Header: c.req.Header, }, - Access: access, + Access: AccessInfo{}, } } -func newAccessInfo(mode AccessMode, files []FileInfo) AccessInfo { - return AccessInfo{ - Mode: mode, - Files: files, +func newHookAccessEvent(c *httpContext, mode AccessMode, uploads []FileInfo) HookEvent { + event := newHookEvent(c, nil) + event.Access = AccessInfo{ + Mode: mode, + Uploads: uploads, } + return event } diff --git a/pkg/handler/post_test.go b/pkg/handler/post_test.go index a5cd6b9d2..120af07f8 100644 --- a/pkg/handler/post_test.go +++ b/pkg/handler/post_test.go @@ -625,8 +625,8 @@ func TestPost(t *testing.T) { handler, _ := NewHandler(Config{ StoreComposer: composer, BasePath: "/files/", - PreUploadCreateCallback: func(event HookEvent) (HTTPResponse, FileInfoChanges, error) { - return HTTPResponse{}, FileInfoChanges{}, ErrAccessRejectedByServer + PreUploadAccessCallback: func(event HookEvent) error { + return ErrAccessRejectedByServer }, }) diff --git a/pkg/handler/unrouted_handler.go b/pkg/handler/unrouted_handler.go index a151b81e6..13ffa7f47 100644 --- a/pkg/handler/unrouted_handler.go +++ b/pkg/handler/unrouted_handler.go @@ -306,6 +306,14 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) handler.sendError(c, err) return } + + if handler.config.PreUploadAccessCallback != nil { + err := handler.config.PreUploadAccessCallback(newHookAccessEvent(c, AccessModeRead, partialFileInfos)) + if err != nil { + handler.sendError(c, err) + return + } + } } else { uploadLengthHeader := r.Header.Get("Upload-Length") uploadDeferLengthHeader := r.Header.Get("Upload-Defer-Length") @@ -340,8 +348,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) } if handler.config.PreUploadCreateCallback != nil { - access := newAccessInfo(AccessModeRead, partialFileInfos) - resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info, &access)) + resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info)) if err != nil { handler.sendError(c, err) return @@ -391,7 +398,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) c.log.Info("UploadCreated", "id", id, "size", size, "url", url) if handler.config.NotifyCreatedUploads { - handler.CreatedUploads <- newHookEvent(c, &info, nil) + handler.CreatedUploads <- newHookEvent(c, &info) } if isFinal { @@ -403,7 +410,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) info.Offset = size if handler.config.NotifyCompleteUploads { - handler.CompleteUploads <- newHookEvent(c, &info, nil) + handler.CompleteUploads <- newHookEvent(c, &info) } } @@ -494,7 +501,7 @@ func (handler *UnroutedHandler) PostFileV2(w http.ResponseWriter, r *http.Reques // 1. Create upload resource if handler.config.PreUploadCreateCallback != nil { - resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info, nil)) + resp2, changes, err := handler.config.PreUploadCreateCallback(newHookEvent(c, &info)) if err != nil { handler.sendError(c, err) return @@ -546,7 +553,7 @@ func (handler *UnroutedHandler) PostFileV2(w http.ResponseWriter, r *http.Reques c.log.Info("UploadCreated", "size", info.Size, "url", url) if handler.config.NotifyCreatedUploads { - handler.CreatedUploads <- newHookEvent(c, &info, nil) + handler.CreatedUploads <- newHookEvent(c, &info) } // 2. Lock upload @@ -631,8 +638,7 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) } if handler.config.PreUploadAccessCallback != nil { - access := newAccessInfo(AccessModeRead, []FileInfo{info}) - err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + err := handler.config.PreUploadAccessCallback(newHookAccessEvent(c, AccessModeRead, []FileInfo{info})) if err != nil { handler.sendError(c, err) return @@ -742,8 +748,7 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request } if handler.config.PreUploadAccessCallback != nil { - access := newAccessInfo(AccessModeWrite, []FileInfo{info}) - err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + err := handler.config.PreUploadAccessCallback(newHookAccessEvent(c, AccessModeWrite, []FileInfo{info})) if err != nil { handler.sendError(c, err) return @@ -960,7 +965,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(c *httpContext, resp HTTP // ... allow the hook callback to run before sending the response if handler.config.PreFinishResponseCallback != nil { - resp2, err := handler.config.PreFinishResponseCallback(newHookEvent(c, &info, nil)) + resp2, err := handler.config.PreFinishResponseCallback(newHookEvent(c, &info)) if err != nil { return resp, err } @@ -972,7 +977,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(c *httpContext, resp HTTP // ... send the info out to the channel if handler.config.NotifyCompleteUploads { - handler.CompleteUploads <- newHookEvent(c, &info, nil) + handler.CompleteUploads <- newHookEvent(c, &info) } } @@ -1014,8 +1019,7 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) } if handler.config.PreUploadAccessCallback != nil { - access := newAccessInfo(AccessModeRead, []FileInfo{info}) - err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + err := handler.config.PreUploadAccessCallback(newHookAccessEvent(c, AccessModeRead, []FileInfo{info})) if err != nil { handler.sendError(c, err) return @@ -1156,8 +1160,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) } if handler.config.PreUploadAccessCallback != nil { - access := newAccessInfo(AccessModeWrite, []FileInfo{info}) - err := handler.config.PreUploadAccessCallback(newHookEvent(c, nil, &access)) + err := handler.config.PreUploadAccessCallback(newHookAccessEvent(c, AccessModeWrite, []FileInfo{info})) if err != nil { handler.sendError(c, err) return @@ -1189,7 +1192,7 @@ func (handler *UnroutedHandler) terminateUpload(c *httpContext, upload Upload, i } if handler.config.NotifyTerminatedUploads { - handler.TerminatedUploads <- newHookEvent(c, &info, nil) + handler.TerminatedUploads <- newHookEvent(c, &info) } c.log.Info("UploadTerminated") @@ -1245,7 +1248,7 @@ func (handler *UnroutedHandler) absFileURL(r *http.Request, id string) string { // indicating how much data has been transfered to the server. // It will stop sending these instances once the provided context is done. func (handler *UnroutedHandler) sendProgressMessages(c *httpContext, info FileInfo) { - hook := newHookEvent(c, &info, nil) + hook := newHookEvent(c, &info) previousOffset := int64(0) originalOffset := hook.Upload.Offset diff --git a/pkg/hooks/grpc/grpc.go b/pkg/hooks/grpc/grpc.go index 4e7efa95d..d78a50d9d 100644 --- a/pkg/hooks/grpc/grpc.go +++ b/pkg/hooks/grpc/grpc.go @@ -76,8 +76,8 @@ func marshal(hookReq hooks.HookRequest) *pb.HookRequest { Header: getHeader(event.HTTPRequest.Header), }, Access: &pb.AccessInfo{ - Mode: event.Access.Mode, - Files: getAccessFiles(event.Access.Files), + Mode: event.Access.Mode, + Uploads: getAccessFiles(event.Access.Uploads), }, }, } diff --git a/pkg/hooks/grpc/proto/hook.pb.go b/pkg/hooks/grpc/proto/hook.pb.go index da11edcb5..4c168a2c9 100644 --- a/pkg/hooks/grpc/proto/hook.pb.go +++ b/pkg/hooks/grpc/proto/hook.pb.go @@ -7,7 +7,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.32.0 // protoc v4.25.2 // source: pkg/hooks/grpc/proto/hook.proto @@ -291,7 +291,7 @@ type AccessInfo struct { Mode string `protobuf:"bytes,1,opt,name=mode,proto3" json:"mode,omitempty"` // All files that will be access by http request // Use an array because of Upload-Concat that may target seeral files - Files []*FileInfo `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"` + Uploads []*FileInfo `protobuf:"bytes,2,rep,name=uploads,proto3" json:"uploads,omitempty"` } func (x *AccessInfo) Reset() { @@ -333,9 +333,9 @@ func (x *AccessInfo) GetMode() string { return "" } -func (x *AccessInfo) GetFiles() []*FileInfo { +func (x *AccessInfo) GetUploads() []*FileInfo { if x != nil { - return x.Files + return x.Uploads } return nil } @@ -718,75 +718,75 @@ var file_pkg_hooks_grpc_proto_hook_proto_rawDesc = []byte{ 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x47, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, + 0x22, 0x4b, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, - 0x64, 0x65, 0x12, 0x25, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x9b, 0x02, 0x0a, 0x0f, 0x46, 0x69, - 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x3d, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x1a, 0x3b, - 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x64, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x22, 0x9b, 0x02, + 0x0a, 0x0f, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x40, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x3d, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, + 0x3a, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3a, 0x0a, 0x0c, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xca, 0x01, 0x0a, 0x0b, 0x48, 0x54, 0x54, 0x50, - 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, - 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, - 0x69, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, - 0x72, 0x12, 0x36, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0xef, 0x01, 0x0a, 0x0c, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xca, 0x01, 0x0a, 0x0b, + 0x48, 0x54, 0x54, 0x50, 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, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, + 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x36, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, + 0x54, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x39, 0x0a, + 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xef, 0x01, 0x0a, 0x0c, 0x48, 0x6f, 0x6f, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x68, 0x74, 0x74, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, + 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3e, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, + 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x74, 0x6f, 0x70, + 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, + 0x6a, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x0c, 0x48, + 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x52, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, - 0x0a, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x55, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x12, 0x3e, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6c, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x55, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x41, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, - 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x0c, 0x48, 0x54, 0x54, 0x50, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x48, 0x54, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, - 0x46, 0x0a, 0x0b, 0x48, 0x6f, 0x6f, 0x6b, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x12, 0x37, - 0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x12, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x12, 0x5a, 0x10, 0x68, 0x6f, 0x6f, 0x6b, 0x73, - 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x32, 0x46, 0x0a, 0x0b, 0x48, 0x6f, 0x6f, 0x6b, 0x48, 0x61, 0x6e, 0x64, 0x6c, + 0x65, 0x72, 0x12, 0x37, 0x0a, 0x0a, 0x49, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x48, 0x6f, 0x6f, 0x6b, + 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x6f, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x12, 0x5a, 0x10, 0x68, + 0x6f, 0x6f, 0x6b, 0x73, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -825,7 +825,7 @@ var file_pkg_hooks_grpc_proto_hook_proto_depIdxs = []int32{ 3, // 3: proto.Event.access:type_name -> proto.AccessInfo 8, // 4: proto.FileInfo.metaData:type_name -> proto.FileInfo.MetaDataEntry 9, // 5: proto.FileInfo.storage:type_name -> proto.FileInfo.StorageEntry - 2, // 6: proto.AccessInfo.files:type_name -> proto.FileInfo + 2, // 6: proto.AccessInfo.uploads:type_name -> proto.FileInfo 10, // 7: proto.FileInfoChanges.metaData:type_name -> proto.FileInfoChanges.MetaDataEntry 11, // 8: proto.FileInfoChanges.storage:type_name -> proto.FileInfoChanges.StorageEntry 12, // 9: proto.HTTPRequest.header:type_name -> proto.HTTPRequest.HeaderEntry diff --git a/pkg/hooks/grpc/proto/hook.proto b/pkg/hooks/grpc/proto/hook.proto index d3ae2691d..6f309f058 100644 --- a/pkg/hooks/grpc/proto/hook.proto +++ b/pkg/hooks/grpc/proto/hook.proto @@ -68,7 +68,7 @@ message AccessInfo { // All files that will be access by http request // Use an array because of Upload-Concat that may target seeral files - repeated FileInfo files = 2; + repeated FileInfo uploads = 2; } // FileInfoChanges collects changes the should be made to a FileInfo object. This diff --git a/pkg/hooks/hooks_test.go b/pkg/hooks/hooks_test.go index 260e6afc0..b89afa8a2 100644 --- a/pkg/hooks/hooks_test.go +++ b/pkg/hooks/hooks_test.go @@ -52,7 +52,7 @@ func TestNewHandlerWithHooks(t *testing.T) { }, Access: handler.AccessInfo{ Mode: handler.AccessModeRead, - Files: []handler.FileInfo{ + Uploads: []handler.FileInfo{ { ID: "id", MetaData: handler.MetaData{