feat(quota): Quota enforcement
The previous commit laid out the foundation of the quota engine, this one builds on top of it, and implements the actual enforcement. Enforcement happens at the route decoration level, whenever possible. In case of the API, when over quota, a 413 error is returned, with an appropriate JSON payload. In case of web routes, a 413 HTML page is rendered with similar information. This implementation is for a **soft quota**: quota usage is checked before an operation is to be performed, and the operation is *only* denied if the user is already over quota. This makes it possible to go over quota, but has the significant advantage of being practically implementable within the current Forgejo architecture. The goal of enforcement is to deny actions that can make the user go over quota, and allow the rest. As such, deleting things should - in almost all cases - be possible. A prime exemption is deleting files via the web ui: that creates a new commit, which in turn increases repo size, thus, is denied if the user is over quota. Limitations ----------- Because we generally work at a route decorator level, and rarely look *into* the operation itself, `size:repos:public` and `size:repos:private` are not enforced at this level, the engine enforces against `size:repos:all`. This will be improved in the future. AGit does not play very well with this system, because AGit PRs count toward the repo they're opened against, while in the GitHub-style fork + pull model, it counts against the fork. This too, can be improved in the future. There's very little done on the UI side to guard against going over quota. What this patch implements, is enforcement, not prevention. The UI will still let you *try* operations that *will* result in a denial. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
This commit is contained in:
parent
a414703c09
commit
67fa52dedb
33 changed files with 3172 additions and 66 deletions
|
@ -115,6 +115,7 @@ loading = Loading…
|
||||||
|
|
||||||
error = Error
|
error = Error
|
||||||
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.
|
error404 = The page you are trying to reach either <strong>does not exist</strong> or <strong>you are not authorized</strong> to view it.
|
||||||
|
error413 = You have exhausted your quota.
|
||||||
go_back = Go Back
|
go_back = Go Back
|
||||||
invalid_data = Invalid data: %v
|
invalid_data = Invalid data: %v
|
||||||
|
|
||||||
|
@ -2196,6 +2197,7 @@ settings.units.add_more = Add more...
|
||||||
|
|
||||||
settings.sync_mirror = Synchronize now
|
settings.sync_mirror = Synchronize now
|
||||||
settings.pull_mirror_sync_in_progress = Pulling changes from the remote %s at the moment.
|
settings.pull_mirror_sync_in_progress = Pulling changes from the remote %s at the moment.
|
||||||
|
settings.pull_mirror_sync_quota_exceeded = Quota exceeded, not pulling changes.
|
||||||
settings.push_mirror_sync_in_progress = Pushing changes to the remote %s at the moment.
|
settings.push_mirror_sync_in_progress = Pushing changes to the remote %s at the moment.
|
||||||
settings.site = Website
|
settings.site = Website
|
||||||
settings.update_settings = Save settings
|
settings.update_settings = Save settings
|
||||||
|
@ -2279,6 +2281,7 @@ settings.transfer_owner = New owner
|
||||||
settings.transfer_perform = Perform transfer
|
settings.transfer_perform = Perform transfer
|
||||||
settings.transfer_started = This repository has been marked for transfer and awaits confirmation from "%s"
|
settings.transfer_started = This repository has been marked for transfer and awaits confirmation from "%s"
|
||||||
settings.transfer_succeed = The repository has been transferred.
|
settings.transfer_succeed = The repository has been transferred.
|
||||||
|
settings.transfer_quota_exceeded = The new owner (%s) is over quota. The repository has not been transferred.
|
||||||
settings.signing_settings = Signing verification settings
|
settings.signing_settings = Signing verification settings
|
||||||
settings.trust_model = Signature trust model
|
settings.trust_model = Signature trust model
|
||||||
settings.trust_model.default = Default trust model
|
settings.trust_model.default = Default trust model
|
||||||
|
|
|
@ -71,6 +71,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/actions"
|
"code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -240,6 +241,18 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the owner's quota
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, ctx.ActionTask.OwnerID, quota_model.LimitSubjectSizeAssetsArtifacts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Error checking quota")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.Error(http.StatusRequestEntityTooLarge, "Quota exceeded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// get upload file size
|
// get upload file size
|
||||||
fileRealTotalSize, contentLength := getUploadFileSize(ctx)
|
fileRealTotalSize, contentLength := getUploadFileSize(ctx)
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/actions"
|
"code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
@ -290,6 +291,18 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the owner's quota
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, task.OwnerID, quota_model.LimitSubjectSizeAssetsArtifacts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Error checking quota")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.Error(http.StatusRequestEntityTooLarge, "Quota exceeded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
comp := ctx.Req.URL.Query().Get("comp")
|
comp := ctx.Req.URL.Query().Get("comp")
|
||||||
switch comp {
|
switch comp {
|
||||||
case "block", "appendBlock":
|
case "block", "appendBlock":
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -74,6 +75,21 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enforcePackagesQuota() func(ctx *context.Context) {
|
||||||
|
return func(ctx *context.Context) {
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, ctx.Doer.ID, quota_model.LimitSubjectSizeAssetsPackagesAll)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Error checking quota")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.Error(http.StatusRequestEntityTooLarge, "enforcePackagesQuota", "quota exceeded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func verifyAuth(r *web.Route, authMethods []auth.Method) {
|
func verifyAuth(r *web.Route, authMethods []auth.Method) {
|
||||||
if setting.Service.EnableReverseProxyAuth {
|
if setting.Service.EnableReverseProxyAuth {
|
||||||
authMethods = append(authMethods, &auth.ReverseProxy{})
|
authMethods = append(authMethods, &auth.ReverseProxy{})
|
||||||
|
@ -111,7 +127,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/alpine", func() {
|
r.Group("/alpine", func() {
|
||||||
r.Get("/key", alpine.GetRepositoryKey)
|
r.Get("/key", alpine.GetRepositoryKey)
|
||||||
r.Group("/{branch}/{repository}", func() {
|
r.Group("/{branch}/{repository}", func() {
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), alpine.UploadPackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), alpine.UploadPackageFile)
|
||||||
r.Group("/{architecture}", func() {
|
r.Group("/{architecture}", func() {
|
||||||
r.Get("/APKINDEX.tar.gz", alpine.GetRepositoryFile)
|
r.Get("/APKINDEX.tar.gz", alpine.GetRepositoryFile)
|
||||||
r.Group("/{filename}", func() {
|
r.Group("/{filename}", func() {
|
||||||
|
@ -124,12 +140,12 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/cargo", func() {
|
r.Group("/cargo", func() {
|
||||||
r.Group("/api/v1/crates", func() {
|
r.Group("/api/v1/crates", func() {
|
||||||
r.Get("", cargo.SearchPackages)
|
r.Get("", cargo.SearchPackages)
|
||||||
r.Put("/new", reqPackageAccess(perm.AccessModeWrite), cargo.UploadPackage)
|
r.Put("/new", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cargo.UploadPackage)
|
||||||
r.Group("/{package}", func() {
|
r.Group("/{package}", func() {
|
||||||
r.Group("/{version}", func() {
|
r.Group("/{version}", func() {
|
||||||
r.Get("/download", cargo.DownloadPackageFile)
|
r.Get("/download", cargo.DownloadPackageFile)
|
||||||
r.Delete("/yank", reqPackageAccess(perm.AccessModeWrite), cargo.YankPackage)
|
r.Delete("/yank", reqPackageAccess(perm.AccessModeWrite), cargo.YankPackage)
|
||||||
r.Put("/unyank", reqPackageAccess(perm.AccessModeWrite), cargo.UnyankPackage)
|
r.Put("/unyank", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cargo.UnyankPackage)
|
||||||
})
|
})
|
||||||
r.Get("/owners", cargo.ListOwners)
|
r.Get("/owners", cargo.ListOwners)
|
||||||
})
|
})
|
||||||
|
@ -147,7 +163,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/search", chef.EnumeratePackages)
|
r.Get("/search", chef.EnumeratePackages)
|
||||||
r.Group("/cookbooks", func() {
|
r.Group("/cookbooks", func() {
|
||||||
r.Get("", chef.EnumeratePackages)
|
r.Get("", chef.EnumeratePackages)
|
||||||
r.Post("", reqPackageAccess(perm.AccessModeWrite), chef.UploadPackage)
|
r.Post("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), chef.UploadPackage)
|
||||||
r.Group("/{name}", func() {
|
r.Group("/{name}", func() {
|
||||||
r.Get("", chef.PackageMetadata)
|
r.Get("", chef.PackageMetadata)
|
||||||
r.Group("/versions/{version}", func() {
|
r.Group("/versions/{version}", func() {
|
||||||
|
@ -167,7 +183,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/p2/{vendorname}/{projectname}~dev.json", composer.PackageMetadata)
|
r.Get("/p2/{vendorname}/{projectname}~dev.json", composer.PackageMetadata)
|
||||||
r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata)
|
r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata)
|
||||||
r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile)
|
r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), composer.UploadPackage)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), composer.UploadPackage)
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/conan", func() {
|
r.Group("/conan", func() {
|
||||||
r.Group("/v1", func() {
|
r.Group("/v1", func() {
|
||||||
|
@ -183,14 +199,14 @@ func CommonRoutes() *web.Route {
|
||||||
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV1)
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV1)
|
||||||
r.Get("/search", conan.SearchPackagesV1)
|
r.Get("/search", conan.SearchPackagesV1)
|
||||||
r.Get("/digest", conan.RecipeDownloadURLs)
|
r.Get("/digest", conan.RecipeDownloadURLs)
|
||||||
r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), conan.RecipeUploadURLs)
|
r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.RecipeUploadURLs)
|
||||||
r.Get("/download_urls", conan.RecipeDownloadURLs)
|
r.Get("/download_urls", conan.RecipeDownloadURLs)
|
||||||
r.Group("/packages", func() {
|
r.Group("/packages", func() {
|
||||||
r.Post("/delete", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV1)
|
r.Post("/delete", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV1)
|
||||||
r.Group("/{package_reference}", func() {
|
r.Group("/{package_reference}", func() {
|
||||||
r.Get("", conan.PackageSnapshot)
|
r.Get("", conan.PackageSnapshot)
|
||||||
r.Get("/digest", conan.PackageDownloadURLs)
|
r.Get("/digest", conan.PackageDownloadURLs)
|
||||||
r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), conan.PackageUploadURLs)
|
r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.PackageUploadURLs)
|
||||||
r.Get("/download_urls", conan.PackageDownloadURLs)
|
r.Get("/download_urls", conan.PackageDownloadURLs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -199,11 +215,11 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/files/{name}/{version}/{user}/{channel}/{recipe_revision}", func() {
|
r.Group("/files/{name}/{version}/{user}/{channel}/{recipe_revision}", func() {
|
||||||
r.Group("/recipe/{filename}", func() {
|
r.Group("/recipe/{filename}", func() {
|
||||||
r.Get("", conan.DownloadRecipeFile)
|
r.Get("", conan.DownloadRecipeFile)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadRecipeFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadRecipeFile)
|
||||||
})
|
})
|
||||||
r.Group("/package/{package_reference}/{package_revision}/{filename}", func() {
|
r.Group("/package/{package_reference}/{package_revision}/{filename}", func() {
|
||||||
r.Get("", conan.DownloadPackageFile)
|
r.Get("", conan.DownloadPackageFile)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadPackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadPackageFile)
|
||||||
})
|
})
|
||||||
}, conan.ExtractPathParameters)
|
}, conan.ExtractPathParameters)
|
||||||
})
|
})
|
||||||
|
@ -228,7 +244,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("", conan.ListRecipeRevisionFiles)
|
r.Get("", conan.ListRecipeRevisionFiles)
|
||||||
r.Group("/{filename}", func() {
|
r.Group("/{filename}", func() {
|
||||||
r.Get("", conan.DownloadRecipeFile)
|
r.Get("", conan.DownloadRecipeFile)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadRecipeFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadRecipeFile)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
r.Group("/packages", func() {
|
r.Group("/packages", func() {
|
||||||
|
@ -244,7 +260,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("", conan.ListPackageRevisionFiles)
|
r.Get("", conan.ListPackageRevisionFiles)
|
||||||
r.Group("/{filename}", func() {
|
r.Group("/{filename}", func() {
|
||||||
r.Get("", conan.DownloadPackageFile)
|
r.Get("", conan.DownloadPackageFile)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), conan.UploadPackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadPackageFile)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -281,7 +297,7 @@ func CommonRoutes() *web.Route {
|
||||||
conda.DownloadPackageFile(ctx)
|
conda.DownloadPackageFile(ctx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
|
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), func(ctx *context.Context) {
|
||||||
m := uploadPattern.FindStringSubmatch(ctx.Params("*"))
|
m := uploadPattern.FindStringSubmatch(ctx.Params("*"))
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
ctx.Status(http.StatusNotFound)
|
ctx.Status(http.StatusNotFound)
|
||||||
|
@ -301,7 +317,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages)
|
r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages)
|
||||||
r.Get("/{filename}", cran.DownloadSourcePackageFile)
|
r.Get("/{filename}", cran.DownloadSourcePackageFile)
|
||||||
})
|
})
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadSourcePackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cran.UploadSourcePackageFile)
|
||||||
})
|
})
|
||||||
r.Group("/bin", func() {
|
r.Group("/bin", func() {
|
||||||
r.Group("/{platform}/contrib/{rversion}", func() {
|
r.Group("/{platform}/contrib/{rversion}", func() {
|
||||||
|
@ -309,7 +325,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/PACKAGES{format}", cran.EnumerateBinaryPackages)
|
r.Get("/PACKAGES{format}", cran.EnumerateBinaryPackages)
|
||||||
r.Get("/{filename}", cran.DownloadBinaryPackageFile)
|
r.Get("/{filename}", cran.DownloadBinaryPackageFile)
|
||||||
})
|
})
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), cran.UploadBinaryPackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cran.UploadBinaryPackageFile)
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/debian", func() {
|
r.Group("/debian", func() {
|
||||||
|
@ -325,13 +341,13 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/pool/{distribution}/{component}", func() {
|
r.Group("/pool/{distribution}/{component}", func() {
|
||||||
r.Get("/{name}_{version}_{architecture}.deb", debian.DownloadPackageFile)
|
r.Get("/{name}_{version}_{architecture}.deb", debian.DownloadPackageFile)
|
||||||
r.Group("", func() {
|
r.Group("", func() {
|
||||||
r.Put("/upload", debian.UploadPackageFile)
|
r.Put("/upload", enforcePackagesQuota(), debian.UploadPackageFile)
|
||||||
r.Delete("/{name}/{version}/{architecture}", debian.DeletePackageFile)
|
r.Delete("/{name}/{version}/{architecture}", debian.DeletePackageFile)
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/go", func() {
|
r.Group("/go", func() {
|
||||||
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), goproxy.UploadPackage)
|
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), goproxy.UploadPackage)
|
||||||
r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
|
r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
|
||||||
ctx.Status(http.StatusNotFound)
|
ctx.Status(http.StatusNotFound)
|
||||||
})
|
})
|
||||||
|
@ -394,7 +410,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/{filename}", func() {
|
r.Group("/{filename}", func() {
|
||||||
r.Get("", generic.DownloadPackageFile)
|
r.Get("", generic.DownloadPackageFile)
|
||||||
r.Group("", func() {
|
r.Group("", func() {
|
||||||
r.Put("", generic.UploadPackage)
|
r.Put("", enforcePackagesQuota(), generic.UploadPackage)
|
||||||
r.Delete("", generic.DeletePackageFile)
|
r.Delete("", generic.DeletePackageFile)
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
})
|
})
|
||||||
|
@ -403,10 +419,10 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/helm", func() {
|
r.Group("/helm", func() {
|
||||||
r.Get("/index.yaml", helm.Index)
|
r.Get("/index.yaml", helm.Index)
|
||||||
r.Get("/{filename}", helm.DownloadPackageFile)
|
r.Get("/{filename}", helm.DownloadPackageFile)
|
||||||
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
|
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), helm.UploadPackage)
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/maven", func() {
|
r.Group("/maven", func() {
|
||||||
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
|
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), maven.UploadPackageFile)
|
||||||
r.Get("/*", maven.DownloadPackageFile)
|
r.Get("/*", maven.DownloadPackageFile)
|
||||||
r.Head("/*", maven.ProvidePackageFileHeader)
|
r.Head("/*", maven.ProvidePackageFileHeader)
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
@ -427,8 +443,8 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
|
r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
|
||||||
})
|
})
|
||||||
r.Group("", func() {
|
r.Group("", func() {
|
||||||
r.Put("/", nuget.UploadPackage)
|
r.Put("/", enforcePackagesQuota(), nuget.UploadPackage)
|
||||||
r.Put("/symbolpackage", nuget.UploadSymbolPackage)
|
r.Put("/symbolpackage", enforcePackagesQuota(), nuget.UploadSymbolPackage)
|
||||||
r.Delete("/{id}/{version}", nuget.DeletePackage)
|
r.Delete("/{id}/{version}", nuget.DeletePackage)
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
|
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
|
||||||
|
@ -450,7 +466,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/npm", func() {
|
r.Group("/npm", func() {
|
||||||
r.Group("/@{scope}/{id}", func() {
|
r.Group("/@{scope}/{id}", func() {
|
||||||
r.Get("", npm.PackageMetadata)
|
r.Get("", npm.PackageMetadata)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), npm.UploadPackage)
|
||||||
r.Group("/-/{version}/{filename}", func() {
|
r.Group("/-/{version}/{filename}", func() {
|
||||||
r.Get("", npm.DownloadPackageFile)
|
r.Get("", npm.DownloadPackageFile)
|
||||||
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
|
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
|
||||||
|
@ -463,7 +479,7 @@ func CommonRoutes() *web.Route {
|
||||||
})
|
})
|
||||||
r.Group("/{id}", func() {
|
r.Group("/{id}", func() {
|
||||||
r.Get("", npm.PackageMetadata)
|
r.Get("", npm.PackageMetadata)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), npm.UploadPackage)
|
||||||
r.Group("/-/{version}/{filename}", func() {
|
r.Group("/-/{version}/{filename}", func() {
|
||||||
r.Get("", npm.DownloadPackageFile)
|
r.Get("", npm.DownloadPackageFile)
|
||||||
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
|
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
|
||||||
|
@ -496,7 +512,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Group("/api/packages", func() {
|
r.Group("/api/packages", func() {
|
||||||
r.Group("/versions/new", func() {
|
r.Group("/versions/new", func() {
|
||||||
r.Get("", pub.RequestUpload)
|
r.Get("", pub.RequestUpload)
|
||||||
r.Post("/upload", pub.UploadPackageFile)
|
r.Post("/upload", enforcePackagesQuota(), pub.UploadPackageFile)
|
||||||
r.Get("/finalize/{id}/{version}", pub.FinalizePackage)
|
r.Get("/finalize/{id}/{version}", pub.FinalizePackage)
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
r.Group("/{id}", func() {
|
r.Group("/{id}", func() {
|
||||||
|
@ -507,7 +523,7 @@ func CommonRoutes() *web.Route {
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
r.Group("/pypi", func() {
|
r.Group("/pypi", func() {
|
||||||
r.Post("/", reqPackageAccess(perm.AccessModeWrite), pypi.UploadPackageFile)
|
r.Post("/", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), pypi.UploadPackageFile)
|
||||||
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
|
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
|
||||||
r.Get("/simple/{id}", pypi.PackageMetadata)
|
r.Get("/simple/{id}", pypi.PackageMetadata)
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
@ -556,6 +572,10 @@ func CommonRoutes() *web.Route {
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
enforcePackagesQuota()(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
||||||
rpm.UploadPackageFile(ctx)
|
rpm.UploadPackageFile(ctx)
|
||||||
return
|
return
|
||||||
|
@ -591,7 +611,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
||||||
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
||||||
r.Group("/api/v1/gems", func() {
|
r.Group("/api/v1/gems", func() {
|
||||||
r.Post("/", rubygems.UploadPackageFile)
|
r.Post("/", enforcePackagesQuota(), rubygems.UploadPackageFile)
|
||||||
r.Delete("/yank", rubygems.DeletePackage)
|
r.Delete("/yank", rubygems.DeletePackage)
|
||||||
}, reqPackageAccess(perm.AccessModeWrite))
|
}, reqPackageAccess(perm.AccessModeWrite))
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
@ -603,7 +623,7 @@ func CommonRoutes() *web.Route {
|
||||||
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
|
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
|
||||||
r.Group("/{version}", func() {
|
r.Group("/{version}", func() {
|
||||||
r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
|
r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), swift.UploadPackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), enforcePackagesQuota(), swift.UploadPackageFile)
|
||||||
r.Get("", func(ctx *context.Context) {
|
r.Get("", func(ctx *context.Context) {
|
||||||
// Can't use normal routes here: https://github.com/go-chi/chi/issues/781
|
// Can't use normal routes here: https://github.com/go-chi/chi/issues/781
|
||||||
|
|
||||||
|
@ -639,7 +659,7 @@ func CommonRoutes() *web.Route {
|
||||||
r.Get("", vagrant.EnumeratePackageVersions)
|
r.Get("", vagrant.EnumeratePackageVersions)
|
||||||
r.Group("/{version}/{provider}", func() {
|
r.Group("/{version}/{provider}", func() {
|
||||||
r.Get("", vagrant.DownloadPackageFile)
|
r.Get("", vagrant.DownloadPackageFile)
|
||||||
r.Put("", reqPackageAccess(perm.AccessModeWrite), vagrant.UploadPackageFile)
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), vagrant.UploadPackageFile)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}, reqPackageAccess(perm.AccessModeRead))
|
}, reqPackageAccess(perm.AccessModeRead))
|
||||||
|
|
|
@ -77,6 +77,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -973,7 +974,7 @@ func Routes() *web.Route {
|
||||||
|
|
||||||
// (repo scope)
|
// (repo scope)
|
||||||
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
|
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
|
||||||
Post(bind(api.CreateRepoOption{}), repo.Create)
|
Post(bind(api.CreateRepoOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetUser), repo.Create)
|
||||||
|
|
||||||
// (repo scope)
|
// (repo scope)
|
||||||
if !setting.Repository.DisableStars {
|
if !setting.Repository.DisableStars {
|
||||||
|
@ -1104,7 +1105,7 @@ func Routes() *web.Route {
|
||||||
m.Get("", repo.ListBranches)
|
m.Get("", repo.ListBranches)
|
||||||
m.Get("/*", repo.GetBranch)
|
m.Get("/*", repo.GetBranch)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.CreateBranch)
|
||||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||||
m.Group("/branch_protections", func() {
|
m.Group("/branch_protections", func() {
|
||||||
m.Get("", repo.ListBranchProtections)
|
m.Get("", repo.ListBranchProtections)
|
||||||
|
@ -1118,7 +1119,7 @@ func Routes() *web.Route {
|
||||||
m.Group("/tags", func() {
|
m.Group("/tags", func() {
|
||||||
m.Get("", repo.ListTags)
|
m.Get("", repo.ListTags)
|
||||||
m.Get("/*", repo.GetTag)
|
m.Get("/*", repo.GetTag)
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.CreateTag)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
||||||
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
||||||
m.Group("/tag_protections", func() {
|
m.Group("/tag_protections", func() {
|
||||||
|
@ -1152,10 +1153,10 @@ func Routes() *web.Route {
|
||||||
m.Group("/wiki", func() {
|
m.Group("/wiki", func() {
|
||||||
m.Combo("/page/{pageName}").
|
m.Combo("/page/{pageName}").
|
||||||
Get(repo.GetWikiPage).
|
Get(repo.GetWikiPage).
|
||||||
Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
|
Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeWiki, context.QuotaTargetRepo), repo.EditWikiPage).
|
||||||
Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
|
Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
|
||||||
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
|
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
|
||||||
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
|
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeWiki, context.QuotaTargetRepo), repo.NewWikiPage)
|
||||||
m.Get("/pages", repo.ListWikiPages)
|
m.Get("/pages", repo.ListWikiPages)
|
||||||
}, mustEnableWiki)
|
}, mustEnableWiki)
|
||||||
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
||||||
|
@ -1172,15 +1173,15 @@ func Routes() *web.Route {
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
m.Group("/releases", func() {
|
m.Group("/releases", func() {
|
||||||
m.Combo("").Get(repo.ListReleases).
|
m.Combo("").Get(repo.ListReleases).
|
||||||
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
|
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.CreateRelease)
|
||||||
m.Combo("/latest").Get(repo.GetLatestRelease)
|
m.Combo("/latest").Get(repo.GetLatestRelease)
|
||||||
m.Group("/{id}", func() {
|
m.Group("/{id}", func() {
|
||||||
m.Combo("").Get(repo.GetRelease).
|
m.Combo("").Get(repo.GetRelease).
|
||||||
Patch(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease).
|
Patch(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.EditRelease).
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease)
|
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease)
|
||||||
m.Group("/assets", func() {
|
m.Group("/assets", func() {
|
||||||
m.Combo("").Get(repo.ListReleaseAttachments).
|
m.Combo("").Get(repo.ListReleaseAttachments).
|
||||||
Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment)
|
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeAssetsAttachmentsReleases, context.QuotaTargetRepo), repo.CreateReleaseAttachment)
|
||||||
m.Combo("/{attachment_id}").Get(repo.GetReleaseAttachment).
|
m.Combo("/{attachment_id}").Get(repo.GetReleaseAttachment).
|
||||||
Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
|
Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment)
|
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment)
|
||||||
|
@ -1192,7 +1193,7 @@ func Routes() *web.Route {
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
|
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
|
||||||
})
|
})
|
||||||
}, reqRepoReader(unit.TypeReleases))
|
}, reqRepoReader(unit.TypeReleases))
|
||||||
m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.MirrorSync)
|
m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.MirrorSync)
|
||||||
m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), mustNotBeArchived, repo.PushMirrorSync)
|
m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), mustNotBeArchived, repo.PushMirrorSync)
|
||||||
m.Group("/push_mirrors", func() {
|
m.Group("/push_mirrors", func() {
|
||||||
m.Combo("").Get(repo.ListPushMirrors).
|
m.Combo("").Get(repo.ListPushMirrors).
|
||||||
|
@ -1211,11 +1212,11 @@ func Routes() *web.Route {
|
||||||
m.Combo("").Get(repo.GetPullRequest).
|
m.Combo("").Get(repo.GetPullRequest).
|
||||||
Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
|
Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
|
||||||
m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)
|
m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch)
|
||||||
m.Post("/update", reqToken(), repo.UpdatePullRequest)
|
m.Post("/update", reqToken(), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.UpdatePullRequest)
|
||||||
m.Get("/commits", repo.GetPullRequestCommits)
|
m.Get("/commits", repo.GetPullRequestCommits)
|
||||||
m.Get("/files", repo.GetPullRequestFiles)
|
m.Get("/files", repo.GetPullRequestFiles)
|
||||||
m.Combo("/merge").Get(repo.IsPullRequestMerged).
|
m.Combo("/merge").Get(repo.IsPullRequestMerged).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest).
|
Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.MergePullRequest).
|
||||||
Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge)
|
Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge)
|
||||||
m.Group("/reviews", func() {
|
m.Group("/reviews", func() {
|
||||||
m.Combo("").
|
m.Combo("").
|
||||||
|
@ -1270,15 +1271,15 @@ func Routes() *web.Route {
|
||||||
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
||||||
m.Get("/notes/{sha}", repo.GetNote)
|
m.Get("/notes/{sha}", repo.GetNote)
|
||||||
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
|
||||||
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
|
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.ApplyDiffPatch)
|
||||||
m.Group("/contents", func() {
|
m.Group("/contents", func() {
|
||||||
m.Get("", repo.GetContentsList)
|
m.Get("", repo.GetContentsList)
|
||||||
m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
|
m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.ChangeFiles)
|
||||||
m.Get("/*", repo.GetContents)
|
m.Get("/*", repo.GetContents)
|
||||||
m.Group("/*", func() {
|
m.Group("/*", func() {
|
||||||
m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
|
m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.CreateFile)
|
||||||
m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
|
m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.UpdateFile)
|
||||||
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
|
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.DeleteFile)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
}, reqRepoReader(unit.TypeCode))
|
}, reqRepoReader(unit.TypeCode))
|
||||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||||
|
@ -1335,7 +1336,7 @@ func Routes() *web.Route {
|
||||||
m.Group("/assets", func() {
|
m.Group("/assets", func() {
|
||||||
m.Combo("").
|
m.Combo("").
|
||||||
Get(repo.ListIssueCommentAttachments).
|
Get(repo.ListIssueCommentAttachments).
|
||||||
Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment)
|
Post(reqToken(), mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeAssetsAttachmentsIssues, context.QuotaTargetRepo), repo.CreateIssueCommentAttachment)
|
||||||
m.Combo("/{attachment_id}").
|
m.Combo("/{attachment_id}").
|
||||||
Get(repo.GetIssueCommentAttachment).
|
Get(repo.GetIssueCommentAttachment).
|
||||||
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
|
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
|
||||||
|
@ -1387,7 +1388,7 @@ func Routes() *web.Route {
|
||||||
m.Group("/assets", func() {
|
m.Group("/assets", func() {
|
||||||
m.Combo("").
|
m.Combo("").
|
||||||
Get(repo.ListIssueAttachments).
|
Get(repo.ListIssueAttachments).
|
||||||
Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment)
|
Post(reqToken(), mustNotBeArchived, context.EnforceQuotaAPI(quota_model.LimitSubjectSizeAssetsAttachmentsIssues, context.QuotaTargetRepo), repo.CreateIssueAttachment)
|
||||||
m.Combo("/{attachment_id}").
|
m.Combo("/{attachment_id}").
|
||||||
Get(repo.GetIssueAttachment).
|
Get(repo.GetIssueAttachment).
|
||||||
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
|
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
|
||||||
|
@ -1449,7 +1450,7 @@ func Routes() *web.Route {
|
||||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.Delete)
|
Delete(reqToken(), reqOrgOwnership(), org.Delete)
|
||||||
m.Combo("/repos").Get(user.ListOrgRepos).
|
m.Combo("/repos").Get(user.ListOrgRepos).
|
||||||
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
|
Post(reqToken(), bind(api.CreateRepoOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetOrg), repo.CreateOrgRepo)
|
||||||
m.Group("/members", func() {
|
m.Group("/members", func() {
|
||||||
m.Get("", reqToken(), org.ListMembers)
|
m.Get("", reqToken(), org.ListMembers)
|
||||||
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
m.Combo("/{username}").Get(reqToken(), org.IsMember).
|
||||||
|
|
|
@ -210,6 +210,8 @@ func CreateBranch(ctx *context.APIContext) {
|
||||||
// description: The old branch does not exist.
|
// description: The old branch does not exist.
|
||||||
// "409":
|
// "409":
|
||||||
// description: The branch with the same name already exists.
|
// description: The branch with the same name already exists.
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
|
|
@ -477,6 +477,8 @@ func ChangeFiles(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "423":
|
// "423":
|
||||||
|
@ -579,6 +581,8 @@ func CreateFile(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "423":
|
// "423":
|
||||||
|
@ -677,6 +681,8 @@ func UpdateFile(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "423":
|
// "423":
|
||||||
|
@ -842,6 +848,8 @@ func DeleteFile(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -105,6 +106,8 @@ func CreateFork(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
// "409":
|
// "409":
|
||||||
// description: The repository with the same name already exists.
|
// description: The repository with the same name already exists.
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
@ -134,6 +137,10 @@ func CreateFork(ctx *context.APIContext) {
|
||||||
forker = org.AsUser()
|
forker = org.AsUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, forker.ID, forker.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
if form.Name == nil {
|
if form.Name == nil {
|
||||||
name = repo.Name
|
name = repo.Name
|
||||||
|
|
|
@ -160,6 +160,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
|
@ -269,6 +271,8 @@ func EditIssueAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/Attachment"
|
// "$ref": "#/responses/Attachment"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
|
|
@ -157,6 +157,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
|
@ -274,6 +276,8 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/Attachment"
|
// "$ref": "#/responses/Attachment"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
attach := getIssueCommentAttachmentSafeWrite(ctx)
|
attach := getIssueCommentAttachmentSafeWrite(ctx)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
|
@ -54,6 +55,8 @@ func Migrate(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "409":
|
// "409":
|
||||||
// description: The repository with the same name already exists.
|
// description: The repository with the same name already exists.
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
@ -85,6 +88,10 @@ func Migrate(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, repoOwner.ID, repoOwner.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.Doer.IsAdmin {
|
if !ctx.Doer.IsAdmin {
|
||||||
if !repoOwner.IsOrganization() && ctx.Doer.ID != repoOwner.ID {
|
if !repoOwner.IsOrganization() && ctx.Doer.ID != repoOwner.ID {
|
||||||
ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
|
ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
|
||||||
|
|
|
@ -50,6 +50,8 @@ func MirrorSync(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
|
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
@ -103,6 +105,8 @@ func PushMirrorSync(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
|
|
||||||
if !setting.Mirror.Enabled {
|
if !setting.Mirror.Enabled {
|
||||||
ctx.Error(http.StatusBadRequest, "PushMirrorSync", "Mirror feature is disabled")
|
ctx.Error(http.StatusBadRequest, "PushMirrorSync", "Mirror feature is disabled")
|
||||||
|
@ -279,6 +283,8 @@ func AddPushMirror(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
|
|
||||||
if !setting.Mirror.Enabled {
|
if !setting.Mirror.Enabled {
|
||||||
ctx.Error(http.StatusBadRequest, "AddPushMirror", "Mirror feature is disabled")
|
ctx.Error(http.StatusBadRequest, "AddPushMirror", "Mirror feature is disabled")
|
||||||
|
|
|
@ -47,6 +47,8 @@ func ApplyDiffPatch(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/FileResponse"
|
// "$ref": "#/responses/FileResponse"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
|
apiOpts := web.GetForm(ctx).(*api.ApplyDiffPatchFileOptions)
|
||||||
|
|
|
@ -387,6 +387,8 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
// "409":
|
// "409":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
|
@ -857,6 +859,8 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
// "409":
|
// "409":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
@ -1218,6 +1222,8 @@ func UpdatePullRequest(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
// "409":
|
// "409":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,8 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
|
|
||||||
// Check if attachments are enabled
|
// Check if attachments are enabled
|
||||||
if !setting.Attachment.Enabled {
|
if !setting.Attachment.Enabled {
|
||||||
|
@ -348,6 +350,8 @@ func EditReleaseAttachment(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/Attachment"
|
// "$ref": "#/responses/Attachment"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
|
|
||||||
form := web.GetForm(ctx).(*api.EditAttachmentOptions)
|
form := web.GetForm(ctx).(*api.EditAttachmentOptions)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -302,6 +303,8 @@ func Create(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "409":
|
// "409":
|
||||||
// description: The repository with the same name already exists.
|
// description: The repository with the same name already exists.
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
opt := web.GetForm(ctx).(*api.CreateRepoOption)
|
opt := web.GetForm(ctx).(*api.CreateRepoOption)
|
||||||
|
@ -346,6 +349,8 @@ func Generate(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
// "409":
|
// "409":
|
||||||
// description: The repository with the same name already exists.
|
// description: The repository with the same name already exists.
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
form := web.GetForm(ctx).(*api.GenerateRepoOption)
|
form := web.GetForm(ctx).(*api.GenerateRepoOption)
|
||||||
|
@ -412,6 +417,10 @@ func Generate(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, ctxUser.ID, ctxUser.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, ctx.Repo.Repository, opts)
|
repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, ctx.Repo.Repository, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if repo_model.IsErrRepoAlreadyExist(err) {
|
if repo_model.IsErrRepoAlreadyExist(err) {
|
||||||
|
|
|
@ -208,6 +208,8 @@ func CreateTag(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/empty"
|
// "$ref": "#/responses/empty"
|
||||||
// "409":
|
// "409":
|
||||||
// "$ref": "#/responses/conflict"
|
// "$ref": "#/responses/conflict"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
// "423":
|
// "423":
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
@ -53,6 +54,8 @@ func Transfer(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
@ -76,6 +79,10 @@ func Transfer(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, newOwner.ID, newOwner.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var teams []*organization.Team
|
var teams []*organization.Team
|
||||||
if opts.TeamIDs != nil {
|
if opts.TeamIDs != nil {
|
||||||
if !newOwner.IsOrganization() {
|
if !newOwner.IsOrganization() {
|
||||||
|
@ -162,6 +169,8 @@ func AcceptTransfer(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
|
|
||||||
err := acceptOrRejectRepoTransfer(ctx, true)
|
err := acceptOrRejectRepoTransfer(ctx, true)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
@ -233,6 +242,11 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if accept {
|
if accept {
|
||||||
|
recipient := repoTransfer.Recipient
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, recipient.ID, recipient.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
|
return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,8 @@ func NewWikiPage(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
@ -131,6 +133,8 @@ func EditWikiPage(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/forbidden"
|
// "$ref": "#/responses/forbidden"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "413":
|
||||||
|
// "$ref": "#/responses/quotaExceeded"
|
||||||
// "423":
|
// "423":
|
||||||
// "$ref": "#/responses/repoArchivedError"
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,13 @@ import (
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
perm_model "code.gitea.io/gitea/models/perm"
|
perm_model "code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
gitea_context "code.gitea.io/gitea/services/context"
|
gitea_context "code.gitea.io/gitea/services/context"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
@ -47,6 +49,8 @@ type preReceiveContext struct {
|
||||||
|
|
||||||
opts *private.HookOptions
|
opts *private.HookOptions
|
||||||
|
|
||||||
|
isOverQuota bool
|
||||||
|
|
||||||
branchName string
|
branchName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +144,36 @@ func (ctx *preReceiveContext) assertPushOptions() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *preReceiveContext) checkQuota() error {
|
||||||
|
if !setting.Quota.Enabled {
|
||||||
|
ctx.isOverQuota = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.loadPusherAndPermission() {
|
||||||
|
ctx.isOverQuota = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, ctx.PrivateContext.Repo.Repository.OwnerID, quota_model.LimitSubjectSizeReposAll)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||||
|
UserMsg: "Error checking user quota",
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.isOverQuota = !ok
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *preReceiveContext) quotaExceeded() {
|
||||||
|
ctx.JSON(http.StatusRequestEntityTooLarge, private.Response{
|
||||||
|
UserMsg: "Quota exceeded",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// HookPreReceive checks whether a individual commit is acceptable
|
// HookPreReceive checks whether a individual commit is acceptable
|
||||||
func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
||||||
opts := web.GetForm(ctx).(*private.HookOptions)
|
opts := web.GetForm(ctx).(*private.HookOptions)
|
||||||
|
@ -156,6 +190,10 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
||||||
}
|
}
|
||||||
log.Trace("Git push options validation succeeded")
|
log.Trace("Git push options validation succeeded")
|
||||||
|
|
||||||
|
if err := ourCtx.checkQuota(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate across the provided old commit IDs
|
// Iterate across the provided old commit IDs
|
||||||
for i := range opts.OldCommitIDs {
|
for i := range opts.OldCommitIDs {
|
||||||
oldCommitID := opts.OldCommitIDs[i]
|
oldCommitID := opts.OldCommitIDs[i]
|
||||||
|
@ -170,6 +208,10 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
||||||
case git.SupportProcReceive && refFullName.IsFor():
|
case git.SupportProcReceive && refFullName.IsFor():
|
||||||
preReceiveFor(ourCtx, oldCommitID, newCommitID, refFullName)
|
preReceiveFor(ourCtx, oldCommitID, newCommitID, refFullName)
|
||||||
default:
|
default:
|
||||||
|
if ourCtx.isOverQuota {
|
||||||
|
ourCtx.quotaExceeded()
|
||||||
|
return
|
||||||
|
}
|
||||||
ourCtx.AssertCanWriteCode()
|
ourCtx.AssertCanWriteCode()
|
||||||
}
|
}
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
@ -211,6 +253,11 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||||
|
|
||||||
// Allow pushes to non-protected branches
|
// Allow pushes to non-protected branches
|
||||||
if protectBranch == nil {
|
if protectBranch == nil {
|
||||||
|
// ...unless the user is over quota, and the operation is not a delete
|
||||||
|
if newCommitID != objectFormat.EmptyObjectID().String() && ctx.isOverQuota {
|
||||||
|
ctx.quotaExceeded()
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
protectBranch.Repo = repo
|
protectBranch.Repo = repo
|
||||||
|
@ -452,6 +499,15 @@ func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID string, refF
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user is over quota, and the push isn't a tag deletion, deny it
|
||||||
|
if ctx.isOverQuota {
|
||||||
|
objectFormat := ctx.Repo.GetObjectFormat()
|
||||||
|
if newCommitID != objectFormat.EmptyObjectID().String() {
|
||||||
|
ctx.quotaExceeded()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func preReceiveFor(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) { //nolint:unparam
|
func preReceiveFor(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) { //nolint:unparam
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
admin_model "code.gitea.io/gitea/models/admin"
|
admin_model "code.gitea.io/gitea/models/admin"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
@ -170,6 +171,10 @@ func MigratePost(ctx *context.Context) {
|
||||||
|
|
||||||
tpl := base.TplName("repo/migrate/" + form.Service.Name())
|
tpl := base.TplName("repo/migrate/" + form.Service.Name())
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, ctxUser.ID, ctxUser.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
ctx.HTML(http.StatusOK, tpl)
|
ctx.HTML(http.StatusOK, tpl)
|
||||||
return
|
return
|
||||||
|
@ -260,6 +265,25 @@ func setMigrationContextData(ctx *context.Context, serviceType structs.GitServic
|
||||||
}
|
}
|
||||||
|
|
||||||
func MigrateRetryPost(ctx *context.Context) {
|
func MigrateRetryPost(ctx *context.Context) {
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, ctx.Repo.Repository.OwnerID, quota_model.LimitSubjectSizeReposAll)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
ctx.ServerError("quota_model.EvaluateForUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
if err := task.SetMigrateTaskMessage(ctx, ctx.Repo.Repository.ID, ctx.Locale.TrString("repo.settings.pull_mirror_sync_quota_exceeded")); err != nil {
|
||||||
|
log.Error("SetMigrateTaskMessage failed: %v", err)
|
||||||
|
ctx.ServerError("task.SetMigrateTaskMessage", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusRequestEntityTooLarge, map[string]any{
|
||||||
|
"ok": false,
|
||||||
|
"error": ctx.Tr("repo.settings.pull_mirror_sync_quota_exceeded"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := task.RetryMigrateTask(ctx, ctx.Repo.Repository.ID); err != nil {
|
if err := task.RetryMigrateTask(ctx, ctx.Repo.Repository.ID); err != nil {
|
||||||
log.Error("Retry task failed: %v", err)
|
log.Error("Retry task failed: %v", err)
|
||||||
ctx.ServerError("task.RetryMigrateTask", err)
|
ctx.ServerError("task.RetryMigrateTask", err)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
pull_model "code.gitea.io/gitea/models/pull"
|
pull_model "code.gitea.io/gitea/models/pull"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -250,6 +251,10 @@ func ForkPost(ctx *context.Context) {
|
||||||
|
|
||||||
ctx.Data["ContextUser"] = ctxUser
|
ctx.Data["ContextUser"] = ctxUser
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, ctxUser.ID, ctxUser.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
ctx.HTML(http.StatusOK, tplFork)
|
ctx.HTML(http.StatusOK, tplFork)
|
||||||
return
|
return
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -240,6 +241,10 @@ func CreatePost(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["ContextUser"] = ctxUser
|
ctx.Data["ContextUser"] = ctxUser
|
||||||
|
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, ctxUser.ID, ctxUser.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
ctx.HTML(http.StatusOK, tplCreate)
|
ctx.HTML(http.StatusOK, tplCreate)
|
||||||
return
|
return
|
||||||
|
@ -363,49 +368,56 @@ func ActionTransfer(accept bool) func(ctx *context.Context) {
|
||||||
action = "reject_transfer"
|
action = "reject_transfer"
|
||||||
}
|
}
|
||||||
|
|
||||||
err := acceptOrRejectRepoTransfer(ctx, accept)
|
ok, err := acceptOrRejectRepoTransfer(ctx, accept)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError(fmt.Sprintf("Action (%s)", action), err)
|
ctx.ServerError(fmt.Sprintf("Action (%s)", action), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.Repo.RepoLink)
|
ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.Repo.RepoLink)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
|
func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) (bool, error) {
|
||||||
repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
|
repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repoTransfer.LoadAttributes(ctx); err != nil {
|
if err := repoTransfer.LoadAttributes(ctx); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
|
if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
|
||||||
return errors.New("user does not have enough permissions")
|
return false, errors.New("user does not have enough permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if accept {
|
if accept {
|
||||||
|
if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, ctx.Doer.ID, ctx.Doer.Name) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Repo.GitRepo != nil {
|
if ctx.Repo.GitRepo != nil {
|
||||||
ctx.Repo.GitRepo.Close()
|
ctx.Repo.GitRepo.Close()
|
||||||
ctx.Repo.GitRepo = nil
|
ctx.Repo.GitRepo = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil {
|
if err := repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
|
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
|
||||||
} else {
|
} else {
|
||||||
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
|
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
|
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Redirect(ctx.Repo.Repository.Link())
|
ctx.Redirect(ctx.Repo.Repository.Link())
|
||||||
return nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedirectDownload return a file based on the following infos:
|
// RedirectDownload return a file based on the following infos:
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -518,6 +519,20 @@ func SettingsPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, repo.OwnerID, quota_model.LimitSubjectSizeReposAll)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("quota_model.EvaluateForUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// This section doesn't require repo_name/RepoName to be set in the form, don't show it
|
||||||
|
// as an error on the UI for this action
|
||||||
|
ctx.Data["Err_RepoName"] = nil
|
||||||
|
|
||||||
|
ctx.RenderWithErr(ctx.Tr("repo.settings.pull_mirror_sync_quota_exceeded"), tplSettingsOptions, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
mirror_service.AddPullMirrorToQueue(repo.ID)
|
mirror_service.AddPullMirrorToQueue(repo.ID)
|
||||||
|
|
||||||
ctx.Flash.Info(ctx.Tr("repo.settings.pull_mirror_sync_in_progress", repo.OriginalURL))
|
ctx.Flash.Info(ctx.Tr("repo.settings.pull_mirror_sync_in_progress", repo.OriginalURL))
|
||||||
|
@ -828,6 +843,17 @@ func SettingsPost(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the quota of the new owner
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, newOwner.ID, quota_model.LimitSubjectSizeReposAll)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("quota_model.EvaluateForUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_quota_exceeded", newOwner.Name), tplSettingsOptions, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Close the GitRepo if open
|
// Close the GitRepo if open
|
||||||
if ctx.Repo.GitRepo != nil {
|
if ctx.Repo.GitRepo != nil {
|
||||||
ctx.Repo.GitRepo.Close()
|
ctx.Repo.GitRepo.Close()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/metrics"
|
"code.gitea.io/gitea/modules/metrics"
|
||||||
|
@ -1196,7 +1197,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
|
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
|
||||||
m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues)
|
m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues)
|
||||||
m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.SetShowOutdatedComments, repo.UpdateResolveConversation)
|
m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.SetShowOutdatedComments, repo.UpdateResolveConversation)
|
||||||
m.Post("/attachments", repo.UploadIssueAttachment)
|
m.Post("/attachments", context.EnforceQuotaWeb(quota_model.LimitSubjectSizeAssetsAttachmentsIssues, context.QuotaTargetRepo), repo.UploadIssueAttachment)
|
||||||
m.Post("/attachments/remove", repo.DeleteAttachment)
|
m.Post("/attachments/remove", repo.DeleteAttachment)
|
||||||
m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin)
|
m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin)
|
||||||
m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove)
|
m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove)
|
||||||
|
@ -1244,9 +1245,9 @@ func registerRoutes(m *web.Route) {
|
||||||
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
|
Post(web.Bind(forms.EditRepoFileForm{}), repo.NewDiffPatchPost)
|
||||||
m.Combo("/_cherrypick/{sha:([a-f0-9]{4,64})}/*").Get(repo.CherryPick).
|
m.Combo("/_cherrypick/{sha:([a-f0-9]{4,64})}/*").Get(repo.CherryPick).
|
||||||
Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost)
|
Post(web.Bind(forms.CherryPickForm{}), repo.CherryPickPost)
|
||||||
}, repo.MustBeEditable, repo.CommonEditorData)
|
}, repo.MustBeEditable, repo.CommonEditorData, context.EnforceQuotaWeb(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo))
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
m.Post("/upload-file", repo.UploadFileToServer)
|
m.Post("/upload-file", context.EnforceQuotaWeb(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo), repo.UploadFileToServer)
|
||||||
m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
m.Post("/upload-remove", web.Bind(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
||||||
}, repo.MustBeEditable, repo.MustBeAbleToUpload)
|
}, repo.MustBeEditable, repo.MustBeAbleToUpload)
|
||||||
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived())
|
}, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived())
|
||||||
|
@ -1256,7 +1257,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch)
|
m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch)
|
||||||
m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch)
|
m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch)
|
||||||
m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch)
|
m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch)
|
||||||
}, web.Bind(forms.NewBranchForm{}))
|
}, web.Bind(forms.NewBranchForm{}), context.EnforceQuotaWeb(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo))
|
||||||
m.Post("/delete", repo.DeleteBranchPost)
|
m.Post("/delete", repo.DeleteBranchPost)
|
||||||
m.Post("/restore", repo.RestoreBranchPost)
|
m.Post("/restore", repo.RestoreBranchPost)
|
||||||
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
|
||||||
|
@ -1288,16 +1289,17 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
|
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
|
||||||
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
|
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
|
||||||
m.Group("/releases", func() {
|
m.Group("/releases", func() {
|
||||||
m.Get("/new", repo.NewRelease)
|
m.Combo("/new", context.EnforceQuotaWeb(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo)).
|
||||||
m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
|
Get(repo.NewRelease).
|
||||||
|
Post(web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
|
||||||
m.Post("/delete", repo.DeleteRelease)
|
m.Post("/delete", repo.DeleteRelease)
|
||||||
m.Post("/attachments", repo.UploadReleaseAttachment)
|
m.Post("/attachments", context.EnforceQuotaWeb(quota_model.LimitSubjectSizeAssetsAttachmentsReleases, context.QuotaTargetRepo), repo.UploadReleaseAttachment)
|
||||||
m.Post("/attachments/remove", repo.DeleteAttachment)
|
m.Post("/attachments/remove", repo.DeleteAttachment)
|
||||||
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
|
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
|
||||||
m.Group("/releases", func() {
|
m.Group("/releases", func() {
|
||||||
m.Get("/edit/*", repo.EditRelease)
|
m.Get("/edit/*", repo.EditRelease)
|
||||||
m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost)
|
m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost)
|
||||||
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache)
|
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache, context.EnforceQuotaWeb(quota_model.LimitSubjectSizeReposAll, context.QuotaTargetRepo))
|
||||||
}, ignSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoReleaseReader)
|
}, ignSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoReleaseReader)
|
||||||
|
|
||||||
// to maintain compatibility with old attachments
|
// to maintain compatibility with old attachments
|
||||||
|
@ -1410,10 +1412,10 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Group("/wiki", func() {
|
m.Group("/wiki", func() {
|
||||||
m.Combo("/").
|
m.Combo("/").
|
||||||
Get(repo.Wiki).
|
Get(repo.Wiki).
|
||||||
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
|
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), context.EnforceQuotaWeb(quota_model.LimitSubjectSizeWiki, context.QuotaTargetRepo), repo.WikiPost)
|
||||||
m.Combo("/*").
|
m.Combo("/*").
|
||||||
Get(repo.Wiki).
|
Get(repo.Wiki).
|
||||||
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
|
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), context.EnforceQuotaWeb(quota_model.LimitSubjectSizeWiki, context.QuotaTargetRepo), repo.WikiPost)
|
||||||
m.Get("/commit/{sha:[a-f0-9]{4,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
|
m.Get("/commit/{sha:[a-f0-9]{4,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
|
||||||
m.Get("/commit/{sha:[a-f0-9]{4,64}}.{ext:patch|diff}", repo.RawDiff)
|
m.Get("/commit/{sha:[a-f0-9]{4,64}}.{ext:patch|diff}", repo.RawDiff)
|
||||||
}, repo.MustEnableWiki, func(ctx *context.Context) {
|
}, repo.MustEnableWiki, func(ctx *context.Context) {
|
||||||
|
@ -1490,7 +1492,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
|
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
|
||||||
m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
|
m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
|
||||||
})
|
})
|
||||||
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
|
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), context.EnforceQuotaWeb(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.MergePullRequest)
|
||||||
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
|
||||||
m.Post("/update", repo.UpdatePullRequest)
|
m.Post("/update", repo.UpdatePullRequest)
|
||||||
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
|
||||||
|
|
|
@ -4,11 +4,30 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
quota_model "code.gitea.io/gitea/models/quota"
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type QuotaTargetType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuotaTargetUser QuotaTargetType = iota
|
||||||
|
QuotaTargetRepo
|
||||||
|
QuotaTargetOrg
|
||||||
|
)
|
||||||
|
|
||||||
|
// QuotaExceeded
|
||||||
|
// swagger:response quotaExceeded
|
||||||
|
type APIQuotaExceeded struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
UserName string `json:"username,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// QuotaGroupAssignmentAPI returns a middleware to handle context-quota-group assignment for api routes
|
// QuotaGroupAssignmentAPI returns a middleware to handle context-quota-group assignment for api routes
|
||||||
func QuotaGroupAssignmentAPI() func(ctx *APIContext) {
|
func QuotaGroupAssignmentAPI() func(ctx *APIContext) {
|
||||||
return func(ctx *APIContext) {
|
return func(ctx *APIContext) {
|
||||||
|
@ -42,3 +61,140 @@ func QuotaRuleAssignmentAPI() func(ctx *APIContext) {
|
||||||
ctx.QuotaRule = rule
|
ctx.QuotaRule = rule
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ctx.CheckQuota checks whether the user in question is within quota limits (web context)
|
||||||
|
func (ctx *Context) CheckQuota(subject quota_model.LimitSubject, userID int64, username string) bool {
|
||||||
|
ok, err := checkQuota(ctx.Base.originCtx, subject, userID, username, func(userID int64, username string) {
|
||||||
|
showHTML := false
|
||||||
|
for _, part := range ctx.Req.Header["Accept"] {
|
||||||
|
if strings.Contains(part, "text/html") {
|
||||||
|
showHTML = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !showHTML {
|
||||||
|
ctx.plainTextInternal(3, http.StatusRequestEntityTooLarge, []byte("Quota exceeded.\n"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
|
||||||
|
ctx.Data["Title"] = "Quota Exceeded"
|
||||||
|
ctx.HTML(http.StatusRequestEntityTooLarge, base.TplName("status/413"))
|
||||||
|
}, func(err error) {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "quota_model.EvaluateForUser")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ctx.CheckQuota checks whether the user in question is within quota limits (API context)
|
||||||
|
func (ctx *APIContext) CheckQuota(subject quota_model.LimitSubject, userID int64, username string) bool {
|
||||||
|
ok, err := checkQuota(ctx.Base.originCtx, subject, userID, username, func(userID int64, username string) {
|
||||||
|
ctx.JSON(http.StatusRequestEntityTooLarge, APIQuotaExceeded{
|
||||||
|
Message: "quota exceeded",
|
||||||
|
UserID: userID,
|
||||||
|
UserName: username,
|
||||||
|
})
|
||||||
|
}, func(err error) {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnforceQuotaWeb returns a middleware that enforces quota limits on the given web route.
|
||||||
|
func EnforceQuotaWeb(subject quota_model.LimitSubject, target QuotaTargetType) func(ctx *Context) {
|
||||||
|
return func(ctx *Context) {
|
||||||
|
ctx.CheckQuota(subject, target.UserID(ctx), target.UserName(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnforceQuotaWeb returns a middleware that enforces quota limits on the given API route.
|
||||||
|
func EnforceQuotaAPI(subject quota_model.LimitSubject, target QuotaTargetType) func(ctx *APIContext) {
|
||||||
|
return func(ctx *APIContext) {
|
||||||
|
ctx.CheckQuota(subject, target.UserID(ctx), target.UserName(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkQuota wraps quota checking into a single function
|
||||||
|
func checkQuota(ctx context.Context, subject quota_model.LimitSubject, userID int64, username string, quotaExceededHandler func(userID int64, username string), errorHandler func(err error)) (bool, error) {
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, userID, subject)
|
||||||
|
if err != nil {
|
||||||
|
errorHandler(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
quotaExceededHandler(userID, username)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuotaContext interface {
|
||||||
|
GetQuotaTargetUserID(target QuotaTargetType) int64
|
||||||
|
GetQuotaTargetUserName(target QuotaTargetType) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) GetQuotaTargetUserID(target QuotaTargetType) int64 {
|
||||||
|
switch target {
|
||||||
|
case QuotaTargetUser:
|
||||||
|
return ctx.Doer.ID
|
||||||
|
case QuotaTargetRepo:
|
||||||
|
return ctx.Repo.Repository.OwnerID
|
||||||
|
case QuotaTargetOrg:
|
||||||
|
return ctx.Org.Organization.ID
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) GetQuotaTargetUserName(target QuotaTargetType) string {
|
||||||
|
switch target {
|
||||||
|
case QuotaTargetUser:
|
||||||
|
return ctx.Doer.Name
|
||||||
|
case QuotaTargetRepo:
|
||||||
|
return ctx.Repo.Repository.Owner.Name
|
||||||
|
case QuotaTargetOrg:
|
||||||
|
return ctx.Org.Organization.Name
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *APIContext) GetQuotaTargetUserID(target QuotaTargetType) int64 {
|
||||||
|
switch target {
|
||||||
|
case QuotaTargetUser:
|
||||||
|
return ctx.Doer.ID
|
||||||
|
case QuotaTargetRepo:
|
||||||
|
return ctx.Repo.Repository.OwnerID
|
||||||
|
case QuotaTargetOrg:
|
||||||
|
return ctx.Org.Organization.ID
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *APIContext) GetQuotaTargetUserName(target QuotaTargetType) string {
|
||||||
|
switch target {
|
||||||
|
case QuotaTargetUser:
|
||||||
|
return ctx.Doer.Name
|
||||||
|
case QuotaTargetRepo:
|
||||||
|
return ctx.Repo.Repository.Owner.Name
|
||||||
|
case QuotaTargetOrg:
|
||||||
|
return ctx.Org.Organization.Name
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (target QuotaTargetType) UserID(ctx QuotaContext) int64 {
|
||||||
|
return ctx.GetQuotaTargetUserID(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (target QuotaTargetType) UserName(ctx QuotaContext) string {
|
||||||
|
return ctx.GetQuotaTargetUserName(target)
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -179,6 +180,18 @@ func BatchHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isUpload {
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, ctx.Doer.ID, quota_model.LimitSubjectSizeGitLFS)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
writeStatus(ctx, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
writeStatusMessage(ctx, http.StatusRequestEntityTooLarge, "quota exceeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contentStore := lfs_module.NewContentStore()
|
contentStore := lfs_module.NewContentStore()
|
||||||
|
|
||||||
var responseObjects []*lfs_module.ObjectResponse
|
var responseObjects []*lfs_module.ObjectResponse
|
||||||
|
@ -297,6 +310,18 @@ func UploadHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, ctx.Doer.ID, quota_model.LimitSubjectSizeGitLFS)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
writeStatus(ctx, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
writeStatusMessage(ctx, http.StatusRequestEntityTooLarge, "quota exceeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uploadOrVerify := func() error {
|
uploadOrVerify := func() error {
|
||||||
if exists {
|
if exists {
|
||||||
accessible, err := git_model.LFSObjectAccessible(ctx, ctx.Doer, p.Oid)
|
accessible, err := git_model.LFSObjectAccessible(ctx, ctx.Doer, p.Oid)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
@ -73,6 +74,19 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the repo's owner is over quota, for pull mirrors
|
||||||
|
if mirrorType == PullMirrorType {
|
||||||
|
ok, err := quota_model.EvaluateForUser(ctx, repo.OwnerID, quota_model.LimitSubjectSizeReposAll)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("quota_model.EvaluateForUser: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
log.Trace("Owner quota exceeded for %-v, not syncing", repo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Push to the Queue
|
// Push to the Queue
|
||||||
if err := PushToQueue(mirrorType, referenceID); err != nil {
|
if err := PushToQueue(mirrorType, referenceID); err != nil {
|
||||||
if err == queue.ErrAlreadyInQueue {
|
if err == queue.ErrAlreadyInQueue {
|
||||||
|
|
|
@ -152,3 +152,18 @@ func RetryMigrateTask(ctx context.Context, repoID int64) error {
|
||||||
|
|
||||||
return taskQueue.Push(migratingTask)
|
return taskQueue.Push(migratingTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetMigrateTaskMessage(ctx context.Context, repoID int64, message string) error {
|
||||||
|
migratingTask, err := admin_model.GetMigratingTask(ctx, repoID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetMigratingTask: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
migratingTask.Message = message
|
||||||
|
if err = migratingTask.UpdateCols(ctx, "message"); err != nil {
|
||||||
|
log.Error("task.UpdateCols failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
11
templates/status/413.tmpl
Normal file
11
templates/status/413.tmpl
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content ui center tw-w-screen {{if .IsRepo}}repository{{end}}">
|
||||||
|
{{if .IsRepo}}{{template "repo/header" .}}{{end}}
|
||||||
|
<div class="ui container center">
|
||||||
|
<h1 style="margin-top: 100px" class="error-code">413</h1>
|
||||||
|
<p>{{ctx.Locale.Tr "error413"}}</p>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
96
templates/swagger/v1_json.tmpl
generated
96
templates/swagger/v1_json.tmpl
generated
|
@ -4306,6 +4306,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"description": "The repository with the same name already exists."
|
"description": "The repository with the same name already exists."
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
}
|
}
|
||||||
|
@ -5612,6 +5615,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"description": "The branch with the same name already exists."
|
"description": "The branch with the same name already exists."
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -6348,6 +6354,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
@ -6458,6 +6467,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
@ -6519,6 +6531,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
@ -6583,6 +6598,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -6633,6 +6651,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -7034,6 +7055,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"description": "The repository with the same name already exists."
|
"description": "The repository with the same name already exists."
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
}
|
}
|
||||||
|
@ -8506,6 +8530,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
},
|
},
|
||||||
|
@ -8677,6 +8704,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -9135,6 +9165,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
},
|
},
|
||||||
|
@ -9306,6 +9339,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -11979,6 +12015,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12311,6 +12350,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
},
|
},
|
||||||
|
@ -12813,6 +12855,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -13671,6 +13716,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
}
|
}
|
||||||
|
@ -13777,6 +13825,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13819,6 +13870,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14443,6 +14497,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14605,6 +14662,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15359,6 +15419,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"$ref": "#/responses/conflict"
|
"$ref": "#/responses/conflict"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
},
|
},
|
||||||
|
@ -15991,6 +16054,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
}
|
}
|
||||||
|
@ -16032,6 +16098,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16121,6 +16190,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -16265,6 +16337,9 @@
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"423": {
|
"423": {
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
|
@ -16417,6 +16492,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"description": "The repository with the same name already exists."
|
"description": "The repository with the same name already exists."
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
}
|
}
|
||||||
|
@ -18514,6 +18592,9 @@
|
||||||
"409": {
|
"409": {
|
||||||
"description": "The repository with the same name already exists."
|
"description": "The repository with the same name already exists."
|
||||||
},
|
},
|
||||||
|
"413": {
|
||||||
|
"$ref": "#/responses/quotaExceeded"
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"$ref": "#/responses/validationError"
|
"$ref": "#/responses/validationError"
|
||||||
}
|
}
|
||||||
|
@ -28110,6 +28191,21 @@
|
||||||
"$ref": "#/definitions/SetUserQuotaGroupsOptions"
|
"$ref": "#/definitions/SetUserQuotaGroupsOptions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"quotaExceeded": {
|
||||||
|
"description": "QuotaExceeded",
|
||||||
|
"headers": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"redirect": {
|
"redirect": {
|
||||||
"description": "APIRedirect is a redirect response"
|
"description": "APIRedirect is a redirect response"
|
||||||
},
|
},
|
||||||
|
|
1436
tests/integration/api_quota_use_test.go
Normal file
1436
tests/integration/api_quota_use_test.go
Normal file
File diff suppressed because it is too large
Load diff
1099
tests/integration/quota_use_test.go
Normal file
1099
tests/integration/quota_use_test.go
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue