Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting max upload size per user #332

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ caching that is now supported properly by this release, or disable caching if no
### Added

* Added support for `HEAD` at the `/healthz` endpoint.
* Added support for setting maximum individual upload size per user
* Added `X-Content-Security-Policy: sandbox` in contexts where the normal CSP
header would be served. This is a limited, pre-standard form of CSP supported
by IE11, in order to have at least some mitigation of XSS attacks.
Expand Down
7 changes: 6 additions & 1 deletion api/r0/public_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/turt2live/matrix-media-repo/api/_apimeta"
"github.com/turt2live/matrix-media-repo/common/rcontext"
"github.com/turt2live/matrix-media-repo/controllers/upload_controller"
)

type PublicConfigResponse struct {
Expand All @@ -14,7 +15,11 @@ type PublicConfigResponse struct {
func PublicConfig(r *http.Request, rctx rcontext.RequestContext, user _apimeta.UserInfo) interface{} {
uploadSize := rctx.Config.Uploads.ReportedMaxSizeBytes
if uploadSize == 0 {
uploadSize = rctx.Config.Uploads.MaxSizeBytes
if !rctx.Config.Uploads.MaxBytesPerUser.Enabled {
uploadSize = rctx.Config.Uploads.MaxSizeBytes
} else {
uploadSize = upload_controller.GetUploadMaxBytesForUser(rctx, user.UserId)
}
}

if uploadSize < 0 {
Expand Down
5 changes: 5 additions & 0 deletions api/r0/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func UploadMedia(r *http.Request, rctx rcontext.RequestContext, user _apimeta.Us
contentType = "application/octet-stream" // binary
}

if upload_controller.IsRequestTooLargeForUser(r.ContentLength, r.Header.Get("Content-Length"), rctx, user.UserId) {
io.Copy(ioutil.Discard, r.Body) // Ditch the entire request
return api.RequestTooLarge()
}

if upload_controller.IsRequestTooLarge(r.ContentLength, r.Header.Get("Content-Length"), rctx) {
io.Copy(ioutil.Discard, r.Body) // Ditch the entire request
return _responses.RequestTooLarge()
Expand Down
4 changes: 4 additions & 0 deletions common/config/conf_min_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func NewDefaultMinimumRepoConfig() MinimumRepoConfig {
MaxSizeBytes: 104857600, // 100mb
MinSizeBytes: 100,
ReportedMaxSizeBytes: 0,
MaxBytesPerUser: MaxBytesPerUserConfig{
Enabled: false,
UserMaxBytes: []MaxBytesUserConfig{},
},
Quota: QuotasConfig{
Enabled: false,
UserQuotas: []QuotaUserConfig{},
Expand Down
19 changes: 15 additions & 4 deletions common/config/models_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@ type QuotasConfig struct {
UserQuotas []QuotaUserConfig `yaml:"users,flow"`
}

type MaxBytesUserConfig struct {
Glob string `yaml:"glob"`
MaxBytes int64 `yaml:"maxBytes"`
}

type MaxBytesPerUserConfig struct {
Enabled bool `yaml:"enabled"`
UserMaxBytes []MaxBytesUserConfig `yaml:"users,flow"`
}

type UploadsConfig struct {
MaxSizeBytes int64 `yaml:"maxBytes"`
MinSizeBytes int64 `yaml:"minBytes"`
ReportedMaxSizeBytes int64 `yaml:"reportedMaxBytes"`
Quota QuotasConfig `yaml:"quotas"`
MaxSizeBytes int64 `yaml:"maxBytes"`
MinSizeBytes int64 `yaml:"minBytes"`
ReportedMaxSizeBytes int64 `yaml:"reportedMaxBytes"`
MaxBytesPerUser MaxBytesPerUserConfig `yaml:"maxBytesPerUser"`
Quota QuotasConfig `yaml:"quotas"`
}

type DatastoreConfig struct {
Expand Down
10 changes: 10 additions & 0 deletions config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,16 @@ uploads:
# Set this to -1 to indicate that there is no limit. Zero will force the use of maxBytes.
#reportedMaxBytes: 104857600

# Per user maximum file size limits. Global maximum will also apply unless disabled.
# Rules are evaluated using an identical method to upload quotas.
maxBytesPerUser:
# Whether or not the per user maximum is enabled.
enabled: false
# Per user upload maximum rules.
users:
- glob: "@*:*"
maxBytes: 0

# Options for limiting how much content a user can upload. Quotas are applied to content
# associated with a user regardless of de-duplication. Quotas which affect remote servers
# or users will not take effect. When a user exceeds their quota they will be unable to
Expand Down
27 changes: 27 additions & 0 deletions controllers/upload_controller/upload_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/patrickmn/go-cache"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/ryanuber/go-glob"
"github.com/turt2live/matrix-media-repo/common"
"github.com/turt2live/matrix-media-repo/common/rcontext"
"github.com/turt2live/matrix-media-repo/internal_cache"
Expand All @@ -33,6 +34,32 @@ type AlreadyUploadedFile struct {
ObjectInfo *types.ObjectInfo
}

func GetUploadMaxBytesForUser(ctx rcontext.RequestContext, userId string) (int64) {
for _, q := range ctx.Config.Uploads.MaxBytesPerUser.UserMaxBytes {
if glob.Glob(q.Glob, userId) {
return q.MaxBytes
}
}
return ctx.Config.Uploads.MaxSizeBytes
}

func IsRequestTooLargeForUser(contentLength int64, contentLengthHeader string, ctx rcontext.RequestContext, userId string) bool {
if !ctx.Config.Uploads.MaxBytesPerUser.Enabled {
return false // per user max not enabled
}

for _, q := range ctx.Config.Uploads.MaxBytesPerUser.UserMaxBytes {
if glob.Glob(q.Glob, userId) {
if q.MaxBytes == 0 {
return false // no max for user
}
return contentLength > q.MaxBytes
}
}

return false // no rules == no max
}

func IsRequestTooLarge(contentLength int64, contentLengthHeader string, ctx rcontext.RequestContext) bool {
if ctx.Config.Uploads.MaxSizeBytes <= 0 {
return false
Expand Down