2022-03-30 10:42:47 +02:00
|
|
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
2022-11-27 13:20:29 -05:00
|
|
|
// SPDX-License-Identifier: MIT
|
2022-03-30 10:42:47 +02:00
|
|
|
|
|
|
|
package packages
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
2025-03-27 19:40:14 +00:00
|
|
|
auth_model "forgejo.org/models/auth"
|
|
|
|
"forgejo.org/models/perm"
|
|
|
|
quota_model "forgejo.org/models/quota"
|
|
|
|
"forgejo.org/modules/log"
|
|
|
|
"forgejo.org/modules/setting"
|
|
|
|
"forgejo.org/modules/web"
|
|
|
|
"forgejo.org/routers/api/packages/alpine"
|
|
|
|
"forgejo.org/routers/api/packages/alt"
|
|
|
|
"forgejo.org/routers/api/packages/arch"
|
|
|
|
"forgejo.org/routers/api/packages/cargo"
|
|
|
|
"forgejo.org/routers/api/packages/chef"
|
|
|
|
"forgejo.org/routers/api/packages/composer"
|
|
|
|
"forgejo.org/routers/api/packages/conan"
|
|
|
|
"forgejo.org/routers/api/packages/conda"
|
|
|
|
"forgejo.org/routers/api/packages/container"
|
|
|
|
"forgejo.org/routers/api/packages/cran"
|
|
|
|
"forgejo.org/routers/api/packages/debian"
|
|
|
|
"forgejo.org/routers/api/packages/generic"
|
|
|
|
"forgejo.org/routers/api/packages/goproxy"
|
|
|
|
"forgejo.org/routers/api/packages/helm"
|
|
|
|
"forgejo.org/routers/api/packages/maven"
|
|
|
|
"forgejo.org/routers/api/packages/npm"
|
|
|
|
"forgejo.org/routers/api/packages/nuget"
|
|
|
|
"forgejo.org/routers/api/packages/pub"
|
|
|
|
"forgejo.org/routers/api/packages/pypi"
|
|
|
|
"forgejo.org/routers/api/packages/rpm"
|
|
|
|
"forgejo.org/routers/api/packages/rubygems"
|
|
|
|
"forgejo.org/routers/api/packages/swift"
|
|
|
|
"forgejo.org/routers/api/packages/vagrant"
|
|
|
|
"forgejo.org/services/auth"
|
|
|
|
"forgejo.org/services/context"
|
2022-03-30 10:42:47 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
|
|
|
return func(ctx *context.Context) {
|
2023-04-26 19:24:03 -05:00
|
|
|
if ctx.Data["IsApiToken"] == true {
|
|
|
|
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
|
|
|
if ok { // it's a personal access token but not oauth2 token
|
|
|
|
scopeMatched := false
|
|
|
|
var err error
|
2025-03-28 22:22:21 +00:00
|
|
|
switch accessMode {
|
|
|
|
case perm.AccessModeRead:
|
2023-04-26 19:24:03 -05:00
|
|
|
scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeReadPackage)
|
|
|
|
if err != nil {
|
|
|
|
ctx.Error(http.StatusInternalServerError, "HasScope", err.Error())
|
|
|
|
return
|
|
|
|
}
|
2025-03-28 22:22:21 +00:00
|
|
|
case perm.AccessModeWrite:
|
2023-04-26 19:24:03 -05:00
|
|
|
scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeWritePackage)
|
|
|
|
if err != nil {
|
|
|
|
ctx.Error(http.StatusInternalServerError, "HasScope", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !scopeMatched {
|
|
|
|
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
|
|
|
|
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
|
|
|
|
return
|
|
|
|
}
|
2024-10-08 17:51:09 +08:00
|
|
|
|
|
|
|
// check if scope only applies to public resources
|
|
|
|
publicOnly, err := scope.PublicOnly()
|
|
|
|
if err != nil {
|
|
|
|
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if publicOnly {
|
|
|
|
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
|
|
|
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2023-04-26 19:24:03 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-30 10:42:47 +02:00
|
|
|
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
|
|
|
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`)
|
|
|
|
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 16:21:03 +09:00
|
|
|
func verifyAuth(r *web.Route, authMethods []auth.Method) {
|
2022-03-30 10:42:47 +02:00
|
|
|
if setting.Service.EnableReverseProxyAuth {
|
|
|
|
authMethods = append(authMethods, &auth.ReverseProxy{})
|
|
|
|
}
|
|
|
|
authGroup := auth.NewGroup(authMethods...)
|
2023-04-10 16:21:03 +09:00
|
|
|
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Use(func(ctx *context.Context) {
|
refactor auth interface to return error when verify failure (#22119)
This PR changed the Auth interface signature from
`Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess
SessionStore) *user_model.User`
to
`Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess
SessionStore) (*user_model.User, error)`.
There is a new return argument `error` which means the verification
condition matched but verify process failed, we should stop the auth
process.
Before this PR, when return a `nil` user, we don't know the reason why
it returned `nil`. If the match condition is not satisfied or it
verified failure? For these two different results, we should have
different handler. If the match condition is not satisfied, we should
try next auth method and if there is no more auth method, it's an
anonymous user. If the condition matched but verify failed, the auth
process should be stop and return immediately.
This will fix #20563
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Jason Song <i@wolfogre.com>
2022-12-28 13:53:28 +08:00
|
|
|
var err error
|
|
|
|
ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
|
|
|
|
if err != nil {
|
2023-04-10 16:21:03 +09:00
|
|
|
log.Error("Failed to verify user: %v", err)
|
refactor auth interface to return error when verify failure (#22119)
This PR changed the Auth interface signature from
`Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess
SessionStore) *user_model.User`
to
`Verify(http *http.Request, w http.ResponseWriter, store DataStore, sess
SessionStore) (*user_model.User, error)`.
There is a new return argument `error` which means the verification
condition matched but verify process failed, we should stop the auth
process.
Before this PR, when return a `nil` user, we don't know the reason why
it returned `nil`. If the match condition is not satisfied or it
verified failure? For these two different results, we should have
different handler. If the match condition is not satisfied, we should
try next auth method and if there is no more auth method, it's an
anonymous user. If the condition matched but verify failed, the auth
process should be stop and return immediately.
This will fix #20563
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Jason Song <i@wolfogre.com>
2022-12-28 13:53:28 +08:00
|
|
|
ctx.Error(http.StatusUnauthorized, "authGroup.Verify")
|
|
|
|
return
|
|
|
|
}
|
2022-10-24 21:23:25 +02:00
|
|
|
ctx.IsSigned = ctx.Doer != nil
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
2023-04-10 16:21:03 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// CommonRoutes provide endpoints for most package managers (except containers - see below)
|
|
|
|
// These are mounted on `/api/packages` (not `/api/v1/packages`)
|
2023-06-18 15:59:09 +08:00
|
|
|
func CommonRoutes() *web.Route {
|
2023-04-10 16:21:03 +09:00
|
|
|
r := web.NewRoute()
|
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
r.Use(context.PackageContexter())
|
2023-04-10 16:21:03 +09:00
|
|
|
|
|
|
|
verifyAuth(r, []auth.Method{
|
|
|
|
&auth.OAuth2{},
|
|
|
|
&auth.Basic{},
|
|
|
|
&nuget.Auth{},
|
|
|
|
&conan.Auth{},
|
|
|
|
&chef.Auth{},
|
|
|
|
})
|
2022-03-30 10:42:47 +02:00
|
|
|
|
|
|
|
r.Group("/{username}", func() {
|
2023-05-12 19:27:50 +02:00
|
|
|
r.Group("/alpine", func() {
|
|
|
|
r.Get("/key", alpine.GetRepositoryKey)
|
|
|
|
r.Group("/{branch}/{repository}", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), alpine.UploadPackageFile)
|
2023-05-12 19:27:50 +02:00
|
|
|
r.Group("/{architecture}", func() {
|
|
|
|
r.Get("/APKINDEX.tar.gz", alpine.GetRepositoryFile)
|
|
|
|
r.Group("/{filename}", func() {
|
|
|
|
r.Get("", alpine.DownloadPackageFile)
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), alpine.DeletePackageFile)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2024-08-04 06:16:29 +00:00
|
|
|
r.Group("/arch", func() {
|
2024-12-06 14:51:49 +08:00
|
|
|
r.Methods("HEAD,GET", "/repository.key", arch.GetRepositoryKey)
|
|
|
|
r.Methods("HEAD,GET", "*", arch.GetPackageOrDB)
|
|
|
|
r.Methods("PUT", "*", reqPackageAccess(perm.AccessModeWrite), arch.PushPackage)
|
|
|
|
r.Methods("DELETE", "*", reqPackageAccess(perm.AccessModeWrite), arch.RemovePackage)
|
2024-08-04 06:16:29 +00:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-02-05 11:12:31 +01:00
|
|
|
r.Group("/cargo", func() {
|
|
|
|
r.Group("/api/v1/crates", func() {
|
|
|
|
r.Get("", cargo.SearchPackages)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/new", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cargo.UploadPackage)
|
2023-02-05 11:12:31 +01:00
|
|
|
r.Group("/{package}", func() {
|
|
|
|
r.Group("/{version}", func() {
|
|
|
|
r.Get("/download", cargo.DownloadPackageFile)
|
|
|
|
r.Delete("/yank", reqPackageAccess(perm.AccessModeWrite), cargo.YankPackage)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/unyank", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cargo.UnyankPackage)
|
2023-02-05 11:12:31 +01:00
|
|
|
})
|
|
|
|
r.Get("/owners", cargo.ListOwners)
|
|
|
|
})
|
|
|
|
})
|
2023-05-03 22:58:43 +02:00
|
|
|
r.Get("/config.json", cargo.RepositoryConfig)
|
|
|
|
r.Get("/1/{package}", cargo.EnumeratePackageVersions)
|
|
|
|
r.Get("/2/{package}", cargo.EnumeratePackageVersions)
|
|
|
|
// Use dummy placeholders because these parts are not of interest
|
|
|
|
r.Get("/3/{_}/{package}", cargo.EnumeratePackageVersions)
|
|
|
|
r.Get("/{_}/{__}/{package}", cargo.EnumeratePackageVersions)
|
2023-02-05 11:12:31 +01:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-02-06 02:49:21 +01:00
|
|
|
r.Group("/chef", func() {
|
|
|
|
r.Group("/api/v1", func() {
|
|
|
|
r.Get("/universe", chef.PackagesUniverse)
|
|
|
|
r.Get("/search", chef.EnumeratePackages)
|
|
|
|
r.Group("/cookbooks", func() {
|
|
|
|
r.Get("", chef.EnumeratePackages)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), chef.UploadPackage)
|
2023-02-06 02:49:21 +01:00
|
|
|
r.Group("/{name}", func() {
|
|
|
|
r.Get("", chef.PackageMetadata)
|
|
|
|
r.Group("/versions/{version}", func() {
|
|
|
|
r.Get("", chef.PackageVersionMetadata)
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), chef.DeletePackageVersion)
|
|
|
|
r.Get("/download", chef.DownloadPackage)
|
|
|
|
})
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), chef.DeletePackage)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/composer", func() {
|
|
|
|
r.Get("/packages.json", composer.ServiceIndex)
|
|
|
|
r.Get("/search.json", composer.SearchPackages)
|
|
|
|
r.Get("/list.json", composer.EnumeratePackages)
|
|
|
|
r.Get("/p2/{vendorname}/{projectname}~dev.json", composer.PackageMetadata)
|
|
|
|
r.Get("/p2/{vendorname}/{projectname}.json", composer.PackageMetadata)
|
|
|
|
r.Get("/files/{package}/{version}/{filename}", composer.DownloadPackageFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), composer.UploadPackage)
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/conan", func() {
|
|
|
|
r.Group("/v1", func() {
|
|
|
|
r.Get("/ping", conan.Ping)
|
|
|
|
r.Group("/users", func() {
|
|
|
|
r.Get("/authenticate", conan.Authenticate)
|
|
|
|
r.Get("/check_credentials", conan.CheckCredentials)
|
|
|
|
})
|
|
|
|
r.Group("/conans", func() {
|
|
|
|
r.Get("/search", conan.SearchRecipes)
|
|
|
|
r.Group("/{name}/{version}/{user}/{channel}", func() {
|
|
|
|
r.Get("", conan.RecipeSnapshot)
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV1)
|
|
|
|
r.Get("/search", conan.SearchPackagesV1)
|
|
|
|
r.Get("/digest", conan.RecipeDownloadURLs)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.RecipeUploadURLs)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Get("/download_urls", conan.RecipeDownloadURLs)
|
|
|
|
r.Group("/packages", func() {
|
|
|
|
r.Post("/delete", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV1)
|
|
|
|
r.Group("/{package_reference}", func() {
|
|
|
|
r.Get("", conan.PackageSnapshot)
|
|
|
|
r.Get("/digest", conan.PackageDownloadURLs)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("/upload_urls", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.PackageUploadURLs)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Get("/download_urls", conan.PackageDownloadURLs)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, conan.ExtractPathParameters)
|
|
|
|
})
|
|
|
|
r.Group("/files/{name}/{version}/{user}/{channel}/{recipe_revision}", func() {
|
|
|
|
r.Group("/recipe/{filename}", func() {
|
|
|
|
r.Get("", conan.DownloadRecipeFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadRecipeFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
r.Group("/package/{package_reference}/{package_revision}/{filename}", func() {
|
|
|
|
r.Get("", conan.DownloadPackageFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadPackageFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
}, conan.ExtractPathParameters)
|
|
|
|
})
|
|
|
|
r.Group("/v2", func() {
|
|
|
|
r.Get("/ping", conan.Ping)
|
|
|
|
r.Group("/users", func() {
|
|
|
|
r.Get("/authenticate", conan.Authenticate)
|
|
|
|
r.Get("/check_credentials", conan.CheckCredentials)
|
|
|
|
})
|
|
|
|
r.Group("/conans", func() {
|
|
|
|
r.Get("/search", conan.SearchRecipes)
|
|
|
|
r.Group("/{name}/{version}/{user}/{channel}", func() {
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV2)
|
|
|
|
r.Get("/search", conan.SearchPackagesV2)
|
|
|
|
r.Get("/latest", conan.LatestRecipeRevision)
|
|
|
|
r.Group("/revisions", func() {
|
|
|
|
r.Get("", conan.ListRecipeRevisions)
|
|
|
|
r.Group("/{recipe_revision}", func() {
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeleteRecipeV2)
|
|
|
|
r.Get("/search", conan.SearchPackagesV2)
|
|
|
|
r.Group("/files", func() {
|
|
|
|
r.Get("", conan.ListRecipeRevisionFiles)
|
|
|
|
r.Group("/{filename}", func() {
|
|
|
|
r.Get("", conan.DownloadRecipeFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadRecipeFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
r.Group("/packages", func() {
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV2)
|
|
|
|
r.Group("/{package_reference}", func() {
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV2)
|
|
|
|
r.Get("/latest", conan.LatestPackageRevision)
|
|
|
|
r.Group("/revisions", func() {
|
|
|
|
r.Get("", conan.ListPackageRevisions)
|
|
|
|
r.Group("/{package_revision}", func() {
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), conan.DeletePackageV2)
|
|
|
|
r.Group("/files", func() {
|
|
|
|
r.Get("", conan.ListPackageRevisionFiles)
|
|
|
|
r.Group("/{filename}", func() {
|
|
|
|
r.Get("", conan.DownloadPackageFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), conan.UploadPackageFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, conan.ExtractPathParameters)
|
|
|
|
})
|
|
|
|
})
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-02-01 19:30:39 +01:00
|
|
|
r.Group("/conda", func() {
|
|
|
|
var (
|
|
|
|
downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
|
|
|
|
uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
|
|
|
|
)
|
|
|
|
|
|
|
|
r.Get("/*", func(ctx *context.Context) {
|
|
|
|
m := downloadPattern.FindStringSubmatch(ctx.Params("*"))
|
|
|
|
if len(m) == 0 {
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("channel", strings.TrimSuffix(m[1], "/"))
|
|
|
|
ctx.SetParams("architecture", m[2])
|
|
|
|
ctx.SetParams("filename", m[3])
|
|
|
|
|
|
|
|
switch m[3] {
|
|
|
|
case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
|
|
|
|
conda.EnumeratePackages(ctx)
|
|
|
|
default:
|
|
|
|
conda.DownloadPackageFile(ctx)
|
|
|
|
}
|
|
|
|
})
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), func(ctx *context.Context) {
|
2023-02-01 19:30:39 +01:00
|
|
|
m := uploadPattern.FindStringSubmatch(ctx.Params("*"))
|
|
|
|
if len(m) == 0 {
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("channel", strings.TrimSuffix(m[1], "/"))
|
|
|
|
ctx.SetParams("filename", m[2])
|
|
|
|
|
|
|
|
conda.UploadPackageFile(ctx)
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-05-22 04:57:49 +02:00
|
|
|
r.Group("/cran", func() {
|
|
|
|
r.Group("/src", func() {
|
|
|
|
r.Group("/contrib", func() {
|
|
|
|
r.Get("/PACKAGES", cran.EnumerateSourcePackages)
|
|
|
|
r.Get("/PACKAGES{format}", cran.EnumerateSourcePackages)
|
|
|
|
r.Get("/{filename}", cran.DownloadSourcePackageFile)
|
2024-12-11 17:20:04 +01:00
|
|
|
r.Get("/Archive/{packagename}/{filename}", cran.DownloadSourcePackageFile)
|
2023-05-22 04:57:49 +02:00
|
|
|
})
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cran.UploadSourcePackageFile)
|
2023-05-22 04:57:49 +02:00
|
|
|
})
|
|
|
|
r.Group("/bin", func() {
|
|
|
|
r.Group("/{platform}/contrib/{rversion}", func() {
|
|
|
|
r.Get("/PACKAGES", cran.EnumerateBinaryPackages)
|
|
|
|
r.Get("/PACKAGES{format}", cran.EnumerateBinaryPackages)
|
|
|
|
r.Get("/{filename}", cran.DownloadBinaryPackageFile)
|
|
|
|
})
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), cran.UploadBinaryPackageFile)
|
2023-05-22 04:57:49 +02:00
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-05-02 18:31:35 +02:00
|
|
|
r.Group("/debian", func() {
|
|
|
|
r.Get("/repository.key", debian.GetRepositoryKey)
|
|
|
|
r.Group("/dists/{distribution}", func() {
|
|
|
|
r.Get("/{filename}", debian.GetRepositoryFile)
|
|
|
|
r.Get("/by-hash/{algorithm}/{hash}", debian.GetRepositoryFileByHash)
|
|
|
|
r.Group("/{component}/{architecture}", func() {
|
|
|
|
r.Get("/{filename}", debian.GetRepositoryFile)
|
|
|
|
r.Get("/by-hash/{algorithm}/{hash}", debian.GetRepositoryFileByHash)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
r.Group("/pool/{distribution}/{component}", func() {
|
|
|
|
r.Get("/{name}_{version}_{architecture}.deb", debian.DownloadPackageFile)
|
|
|
|
r.Group("", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/upload", enforcePackagesQuota(), debian.UploadPackageFile)
|
2023-05-02 18:31:35 +02:00
|
|
|
r.Delete("/{name}/{version}/{architecture}", debian.DeletePackageFile)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-05-14 17:38:40 +02:00
|
|
|
r.Group("/go", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), goproxy.UploadPackage)
|
2023-05-14 17:38:40 +02:00
|
|
|
r.Get("/sumdb/sum.golang.org/supported", func(ctx *context.Context) {
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Manual mapping of routes because the package name contains slashes which chi does not support
|
|
|
|
// https://go.dev/ref/mod#goproxy-protocol
|
|
|
|
r.Get("/*", func(ctx *context.Context) {
|
|
|
|
path := ctx.Params("*")
|
|
|
|
|
|
|
|
if strings.HasSuffix(path, "/@latest") {
|
|
|
|
ctx.SetParams("name", path[:len(path)-len("/@latest")])
|
|
|
|
ctx.SetParams("version", "latest")
|
|
|
|
|
|
|
|
goproxy.PackageVersionMetadata(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := strings.SplitN(path, "/@v/", 2)
|
|
|
|
if len(parts) != 2 {
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("name", parts[0])
|
|
|
|
|
|
|
|
// <package/name>/@v/list
|
|
|
|
if parts[1] == "list" {
|
|
|
|
goproxy.EnumeratePackageVersions(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// <package/name>/@v/<version>.zip
|
|
|
|
if strings.HasSuffix(parts[1], ".zip") {
|
|
|
|
ctx.SetParams("version", parts[1][:len(parts[1])-len(".zip")])
|
|
|
|
|
|
|
|
goproxy.DownloadPackageFile(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// <package/name>/@v/<version>.info
|
|
|
|
if strings.HasSuffix(parts[1], ".info") {
|
|
|
|
ctx.SetParams("version", parts[1][:len(parts[1])-len(".info")])
|
|
|
|
|
|
|
|
goproxy.PackageVersionMetadata(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// <package/name>/@v/<version>.mod
|
|
|
|
if strings.HasSuffix(parts[1], ".mod") {
|
|
|
|
ctx.SetParams("version", parts[1][:len(parts[1])-len(".mod")])
|
|
|
|
|
|
|
|
goproxy.PackageVersionGoModContent(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/generic", func() {
|
2022-08-09 06:39:24 +02:00
|
|
|
r.Group("/{packagename}/{packageversion}", func() {
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage)
|
|
|
|
r.Group("/{filename}", func() {
|
|
|
|
r.Get("", generic.DownloadPackageFile)
|
|
|
|
r.Group("", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", enforcePackagesQuota(), generic.UploadPackage)
|
2022-08-09 06:39:24 +02:00
|
|
|
r.Delete("", generic.DeletePackageFile)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
|
|
|
})
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-04-19 18:55:35 +02:00
|
|
|
r.Group("/helm", func() {
|
|
|
|
r.Get("/index.yaml", helm.Index)
|
|
|
|
r.Get("/{filename}", helm.DownloadPackageFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), helm.UploadPackage)
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/maven", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), maven.UploadPackageFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Get("/*", maven.DownloadPackageFile)
|
2022-11-24 15:25:13 +01:00
|
|
|
r.Head("/*", maven.ProvidePackageFileHeader)
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/nuget", func() {
|
2022-10-13 12:19:39 +02:00
|
|
|
r.Group("", func() { // Needs to be unauthenticated for the NuGet client.
|
|
|
|
r.Get("/", nuget.ServiceIndexV2)
|
|
|
|
r.Get("/index.json", nuget.ServiceIndexV3)
|
|
|
|
r.Get("/$metadata", nuget.FeedCapabilityResource)
|
|
|
|
})
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("", func() {
|
2022-10-13 12:19:39 +02:00
|
|
|
r.Get("/query", nuget.SearchServiceV3)
|
2022-09-24 17:17:08 +02:00
|
|
|
r.Group("/registration/{id}", func() {
|
|
|
|
r.Get("/index.json", nuget.RegistrationIndex)
|
2022-10-13 12:19:39 +02:00
|
|
|
r.Get("/{version}", nuget.RegistrationLeafV3)
|
2022-09-24 17:17:08 +02:00
|
|
|
})
|
|
|
|
r.Group("/package/{id}", func() {
|
2022-10-13 12:19:39 +02:00
|
|
|
r.Get("/index.json", nuget.EnumeratePackageVersionsV3)
|
2022-09-24 17:17:08 +02:00
|
|
|
r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
|
|
|
|
})
|
|
|
|
r.Group("", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("/", enforcePackagesQuota(), nuget.UploadPackage)
|
|
|
|
r.Put("/symbolpackage", enforcePackagesQuota(), nuget.UploadSymbolPackage)
|
2022-09-24 17:17:08 +02:00
|
|
|
r.Delete("/{id}/{version}", nuget.DeletePackage)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
2022-10-12 08:53:56 +02:00
|
|
|
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
|
2022-10-13 12:19:39 +02:00
|
|
|
r.Get("/Packages(Id='{id:[^']+}',Version='{version:[^']+}')", nuget.RegistrationLeafV2)
|
2023-02-11 12:30:44 +01:00
|
|
|
r.Group("/Packages()", func() {
|
|
|
|
r.Get("", nuget.SearchServiceV2)
|
|
|
|
r.Get("/$count", nuget.SearchServiceV2Count)
|
|
|
|
})
|
|
|
|
r.Group("/FindPackagesById()", func() {
|
|
|
|
r.Get("", nuget.EnumeratePackageVersionsV2)
|
|
|
|
r.Get("/$count", nuget.EnumeratePackageVersionsV2Count)
|
|
|
|
})
|
|
|
|
r.Group("/Search()", func() {
|
|
|
|
r.Get("", nuget.SearchServiceV2)
|
|
|
|
r.Get("/$count", nuget.SearchServiceV2Count)
|
|
|
|
})
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
r.Group("/npm", func() {
|
|
|
|
r.Group("/@{scope}/{id}", func() {
|
|
|
|
r.Get("", npm.PackageMetadata)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), npm.UploadPackage)
|
2022-08-09 09:23:43 +02:00
|
|
|
r.Group("/-/{version}/{filename}", func() {
|
|
|
|
r.Get("", npm.DownloadPackageFile)
|
|
|
|
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
|
|
|
|
})
|
2022-10-24 08:50:22 -05:00
|
|
|
r.Get("/-/{filename}", npm.DownloadPackageFileByName)
|
2022-08-09 09:23:43 +02:00
|
|
|
r.Group("/-rev/{revision}", func() {
|
|
|
|
r.Delete("", npm.DeletePackage)
|
|
|
|
r.Put("", npm.DeletePreview)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
r.Group("/{id}", func() {
|
|
|
|
r.Get("", npm.PackageMetadata)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), npm.UploadPackage)
|
2022-08-09 09:23:43 +02:00
|
|
|
r.Group("/-/{version}/{filename}", func() {
|
|
|
|
r.Get("", npm.DownloadPackageFile)
|
|
|
|
r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
|
|
|
|
})
|
2022-10-24 08:50:22 -05:00
|
|
|
r.Get("/-/{filename}", npm.DownloadPackageFileByName)
|
2022-08-09 09:23:43 +02:00
|
|
|
r.Group("/-rev/{revision}", func() {
|
|
|
|
r.Delete("", npm.DeletePackage)
|
|
|
|
r.Put("", npm.DeletePreview)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
r.Group("/-/package/@{scope}/{id}/dist-tags", func() {
|
|
|
|
r.Get("", npm.ListPackageTags)
|
|
|
|
r.Group("/{tag}", func() {
|
|
|
|
r.Put("", npm.AddPackageTag)
|
|
|
|
r.Delete("", npm.DeletePackageTag)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
|
|
|
})
|
|
|
|
r.Group("/-/package/{id}/dist-tags", func() {
|
|
|
|
r.Get("", npm.ListPackageTags)
|
|
|
|
r.Group("/{tag}", func() {
|
|
|
|
r.Put("", npm.AddPackageTag)
|
|
|
|
r.Delete("", npm.DeletePackageTag)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
|
|
|
})
|
2022-09-24 20:54:33 +09:30
|
|
|
r.Group("/-/v1/search", func() {
|
|
|
|
r.Get("", npm.PackageSearch)
|
|
|
|
})
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-08-07 12:09:54 +02:00
|
|
|
r.Group("/pub", func() {
|
|
|
|
r.Group("/api/packages", func() {
|
|
|
|
r.Group("/versions/new", func() {
|
|
|
|
r.Get("", pub.RequestUpload)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("/upload", enforcePackagesQuota(), pub.UploadPackageFile)
|
2022-08-07 12:09:54 +02:00
|
|
|
r.Get("/finalize/{id}/{version}", pub.FinalizePackage)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
|
|
|
r.Group("/{id}", func() {
|
|
|
|
r.Get("", pub.EnumeratePackageVersions)
|
|
|
|
r.Get("/files/{version}", pub.DownloadPackageFile)
|
|
|
|
r.Get("/{version}", pub.PackageVersionMetadata)
|
|
|
|
})
|
|
|
|
})
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/pypi", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("/", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), pypi.UploadPackageFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
|
|
|
|
r.Get("/simple/{id}", pypi.PackageMetadata)
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2024-01-19 12:37:10 +01:00
|
|
|
r.Group("/rpm", func() {
|
|
|
|
r.Group("/repository.key", func() {
|
|
|
|
r.Head("", rpm.GetRepositoryKey)
|
|
|
|
r.Get("", rpm.GetRepositoryKey)
|
|
|
|
})
|
|
|
|
|
|
|
|
var (
|
|
|
|
repoPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
|
|
|
|
uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
|
|
|
|
filePattern = regexp.MustCompile(`\A(.*?)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
|
|
|
|
repoFilePattern = regexp.MustCompile(`\A(.*?)/repodata/([^/]+)\z`)
|
|
|
|
)
|
|
|
|
|
|
|
|
r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
|
|
|
|
path := ctx.Params("*")
|
|
|
|
isHead := ctx.Req.Method == "HEAD"
|
|
|
|
isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
|
|
|
|
isPut := ctx.Req.Method == "PUT"
|
|
|
|
isDelete := ctx.Req.Method == "DELETE"
|
|
|
|
|
|
|
|
m := repoPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 2 && isGetHead {
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
rpm.GetRepositoryConfig(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = repoFilePattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 3 && isGetHead {
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
ctx.SetParams("filename", m[2])
|
|
|
|
if isHead {
|
|
|
|
rpm.CheckRepositoryFileExistence(ctx)
|
|
|
|
} else {
|
|
|
|
rpm.GetRepositoryFile(ctx)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = uploadPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 2 && isPut {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
enforcePackagesQuota()(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
2024-01-19 12:37:10 +01:00
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
rpm.UploadPackageFile(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = filePattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 6 && (isGetHead || isDelete) {
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
ctx.SetParams("name", m[2])
|
|
|
|
ctx.SetParams("version", m[3])
|
|
|
|
ctx.SetParams("architecture", m[4])
|
|
|
|
if isGetHead {
|
|
|
|
rpm.DownloadPackageFile(ctx)
|
|
|
|
} else {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rpm.DeletePackageFile(ctx)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2025-01-22 14:01:49 +00:00
|
|
|
r.Group("/alt", func() {
|
|
|
|
var (
|
|
|
|
baseURLPattern = regexp.MustCompile(`\A(.*?)\.repo\z`)
|
|
|
|
uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`)
|
|
|
|
baseRepoPattern = regexp.MustCompile(`(\S+)\.repo/(\S+)\/base/(\S+)`)
|
several fixes of ALT Package registry (#8475)
closes #7946
- The `rpmsRepoPattern` regex has been fixed to handle releases with dots correctly. For example, the version `0.9.0-alt1.git.17.g2ba905d` is valid, just like `0.1.0-1.n1` mentioned in the issue (https://codeberg.org/forgejo/forgejo/issues/7946#issue-1628991)
- getEntries now returns entry names. In the integration tests, there were lines like:
```go
assert.Equal(t, []string{"", ""}, result.ProvideNames)
```
and it’s unclear how such test logic could have ever worked correctly (fixes problems with deps https://codeberg.org/forgejo/forgejo/issues/7946#issuecomment-5109795)
- ALT is an acronym for ALT Linux Team, so `Alt` was replaced with `ALT`. Strictly speaking, it should probably be `ALT Linux`, but since we use `Arch` instead of `Arch Linux`, this seems fine. Also, Distrowatch shows `Arch`/`ALT` in its dropdown, so it’s consistent.
- The strings `"Alt Linux Team"` and `"Sisyphus"` in the `Origin` and `Suite` fields have been replaced with `setting.AppName` and `"Unknown"`. `Unknown` is a valid value and is set by default, so this won’t cause any issues.
- The documentation link has been fixed: (404 docs.gitea.com/usage/packages/alt/ -> 200 forgejo.org/docs/latest/user/packages/alt/)
---
## Checklist
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
### Tests
- I added test coverage for Go changes...
- [ ] in their respective `*_test.go` for unit tests.
- [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
- [ ] in `web_src/js/*.test.js` if it can be unit tested.
- [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).
### Documentation
- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.
### Release notes
- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8475
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Maxim Slipenko <maks1ms@altlinux.org>
Co-committed-by: Maxim Slipenko <maks1ms@altlinux.org>
2025-07-10 17:12:07 +02:00
|
|
|
rpmsRepoPattern = regexp.MustCompile(`(\S+)\.repo/(\S+)\.(\S+)\/([a-zA-Z0-9_-]+)-([\d.]+-[a-zA-Z0-9_.-]+)\.(\S+)\.rpm`)
|
2025-01-22 14:01:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
|
|
|
|
path := ctx.Params("*")
|
|
|
|
isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
|
|
|
|
isPut := ctx.Req.Method == "PUT"
|
|
|
|
isDelete := ctx.Req.Method == "DELETE"
|
|
|
|
|
|
|
|
m := baseURLPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 2 && isGetHead {
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
alt.GetRepositoryConfig(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = baseRepoPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 4 {
|
|
|
|
if strings.Trim(m[1], "/") != "alt" {
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
}
|
|
|
|
ctx.SetParams("filename", m[3])
|
|
|
|
if isGetHead {
|
|
|
|
alt.GetRepositoryFile(ctx, m[2])
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = uploadPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 2 && isPut {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
alt.UploadPackageFile(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = rpmsRepoPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 7 && (isGetHead || isDelete) {
|
|
|
|
if strings.Trim(m[1], "/") != "alt" {
|
|
|
|
ctx.SetParams("group", strings.Trim(m[1], "/"))
|
|
|
|
}
|
|
|
|
ctx.SetParams("name", m[4])
|
|
|
|
ctx.SetParams("version", m[5])
|
|
|
|
ctx.SetParams("architecture", m[6])
|
|
|
|
if isGetHead {
|
|
|
|
alt.DownloadPackageFile(ctx)
|
|
|
|
} else {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
alt.DeletePackageFile(ctx)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/rubygems", func() {
|
|
|
|
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
|
|
|
|
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
|
|
|
|
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
2024-05-19 23:30:41 +00:00
|
|
|
r.Get("/info/{package}", rubygems.ServePackageInfo)
|
|
|
|
r.Get("/versions", rubygems.ServeVersionsFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
|
|
|
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
|
|
|
r.Group("/api/v1/gems", func() {
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Post("/", enforcePackagesQuota(), rubygems.UploadPackageFile)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Delete("/yank", rubygems.DeletePackage)
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2023-03-13 21:28:39 +01:00
|
|
|
r.Group("/swift", func() {
|
2024-12-03 17:24:16 +01:00
|
|
|
r.Group("", func() { // Needs to be unauthenticated.
|
|
|
|
r.Post("", swift.CheckAuthenticate)
|
|
|
|
r.Post("/login", swift.CheckAuthenticate)
|
|
|
|
})
|
|
|
|
r.Group("", func() {
|
|
|
|
r.Group("/{scope}/{name}", func() {
|
|
|
|
r.Group("", func() {
|
|
|
|
r.Get("", swift.EnumeratePackageVersions)
|
|
|
|
r.Get(".json", swift.EnumeratePackageVersions)
|
|
|
|
}, swift.CheckAcceptMediaType(swift.AcceptJSON))
|
|
|
|
r.Group("/{version}", func() {
|
|
|
|
r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest)
|
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), enforcePackagesQuota(), swift.UploadPackageFile)
|
|
|
|
r.Get("", func(ctx *context.Context) {
|
|
|
|
// Can't use normal routes here: https://github.com/go-chi/chi/issues/781
|
|
|
|
|
|
|
|
version := ctx.Params("version")
|
|
|
|
if strings.HasSuffix(version, ".zip") {
|
|
|
|
swift.CheckAcceptMediaType(swift.AcceptZip)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.SetParams("version", version[:len(version)-4])
|
|
|
|
swift.DownloadPackageFile(ctx)
|
|
|
|
} else {
|
|
|
|
swift.CheckAcceptMediaType(swift.AcceptJSON)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if strings.HasSuffix(version, ".json") {
|
|
|
|
ctx.SetParams("version", version[:len(version)-5])
|
|
|
|
}
|
|
|
|
swift.PackageVersionMetadata(ctx)
|
2023-03-13 21:28:39 +01:00
|
|
|
}
|
2024-12-03 17:24:16 +01:00
|
|
|
})
|
2023-03-13 21:28:39 +01:00
|
|
|
})
|
|
|
|
})
|
2024-12-03 17:24:16 +01:00
|
|
|
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
|
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
|
|
|
})
|
2022-08-29 09:04:45 +02:00
|
|
|
r.Group("/vagrant", func() {
|
|
|
|
r.Group("/authenticate", func() {
|
|
|
|
r.Get("", vagrant.CheckAuthenticate)
|
|
|
|
})
|
|
|
|
r.Group("/{name}", func() {
|
|
|
|
r.Head("", vagrant.CheckBoxAvailable)
|
|
|
|
r.Get("", vagrant.EnumeratePackageVersions)
|
|
|
|
r.Group("/{version}/{provider}", func() {
|
|
|
|
r.Get("", vagrant.DownloadPackageFile)
|
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>
2024-07-06 10:30:16 +02:00
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), enforcePackagesQuota(), vagrant.UploadPackageFile)
|
2022-08-29 09:04:45 +02:00
|
|
|
})
|
|
|
|
})
|
2022-09-24 17:17:08 +02:00
|
|
|
}, reqPackageAccess(perm.AccessModeRead))
|
2024-02-27 15:12:22 +08:00
|
|
|
}, context.UserAssignmentWeb(), context.PackageAssignment())
|
2022-03-30 10:42:47 +02:00
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2022-11-12 18:59:15 +00:00
|
|
|
// ContainerRoutes provides endpoints that implement the OCI API to serve containers
|
|
|
|
// These have to be mounted on `/v2/...` to comply with the OCI spec:
|
|
|
|
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
|
2023-06-18 15:59:09 +08:00
|
|
|
func ContainerRoutes() *web.Route {
|
2022-03-30 10:42:47 +02:00
|
|
|
r := web.NewRoute()
|
|
|
|
|
2023-05-21 09:50:53 +08:00
|
|
|
r.Use(context.PackageContexter())
|
2022-03-30 10:42:47 +02:00
|
|
|
|
2023-04-10 16:21:03 +09:00
|
|
|
verifyAuth(r, []auth.Method{
|
2022-03-30 10:42:47 +02:00
|
|
|
&auth.Basic{},
|
|
|
|
&container.Auth{},
|
|
|
|
})
|
|
|
|
|
|
|
|
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
|
2023-12-13 21:23:53 +01:00
|
|
|
r.Group("/token", func() {
|
|
|
|
r.Get("", container.Authenticate)
|
|
|
|
r.Post("", container.AuthenticateNotImplemented)
|
|
|
|
})
|
2022-07-28 05:59:39 +02:00
|
|
|
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Group("/{username}", func() {
|
|
|
|
r.Group("/{image}", func() {
|
|
|
|
r.Group("/blobs/uploads", func() {
|
|
|
|
r.Post("", container.InitiateUploadBlob)
|
|
|
|
r.Group("/{uuid}", func() {
|
2022-10-07 17:30:59 +02:00
|
|
|
r.Get("", container.GetUploadBlob)
|
2022-03-30 10:42:47 +02:00
|
|
|
r.Patch("", container.UploadBlob)
|
|
|
|
r.Put("", container.EndUploadBlob)
|
2022-10-07 17:30:59 +02:00
|
|
|
r.Delete("", container.CancelUploadBlob)
|
2022-03-30 10:42:47 +02:00
|
|
|
})
|
|
|
|
}, reqPackageAccess(perm.AccessModeWrite))
|
|
|
|
r.Group("/blobs/{digest}", func() {
|
|
|
|
r.Head("", container.HeadBlob)
|
|
|
|
r.Get("", container.GetBlob)
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), container.DeleteBlob)
|
|
|
|
})
|
|
|
|
r.Group("/manifests/{reference}", func() {
|
|
|
|
r.Put("", reqPackageAccess(perm.AccessModeWrite), container.UploadManifest)
|
|
|
|
r.Head("", container.HeadManifest)
|
|
|
|
r.Get("", container.GetManifest)
|
|
|
|
r.Delete("", reqPackageAccess(perm.AccessModeWrite), container.DeleteManifest)
|
|
|
|
})
|
|
|
|
r.Get("/tags/list", container.GetTagList)
|
|
|
|
}, container.VerifyImageName)
|
|
|
|
|
|
|
|
var (
|
|
|
|
blobsUploadsPattern = regexp.MustCompile(`\A(.+)/blobs/uploads/([a-zA-Z0-9-_.=]+)\z`)
|
|
|
|
blobsPattern = regexp.MustCompile(`\A(.+)/blobs/([^/]+)\z`)
|
|
|
|
manifestsPattern = regexp.MustCompile(`\A(.+)/manifests/([^/]+)\z`)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Manual mapping of routes because {image} can contain slashes which chi does not support
|
2023-07-21 06:43:49 +08:00
|
|
|
r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "/*", func(ctx *context.Context) {
|
2022-03-30 10:42:47 +02:00
|
|
|
path := ctx.Params("*")
|
|
|
|
isHead := ctx.Req.Method == "HEAD"
|
|
|
|
isGet := ctx.Req.Method == "GET"
|
|
|
|
isPost := ctx.Req.Method == "POST"
|
|
|
|
isPut := ctx.Req.Method == "PUT"
|
|
|
|
isPatch := ctx.Req.Method == "PATCH"
|
|
|
|
isDelete := ctx.Req.Method == "DELETE"
|
|
|
|
|
|
|
|
if isPost && strings.HasSuffix(path, "/blobs/uploads") {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("image", path[:len(path)-14])
|
|
|
|
container.VerifyImageName(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
container.InitiateUploadBlob(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if isGet && strings.HasSuffix(path, "/tags/list") {
|
|
|
|
ctx.SetParams("image", path[:len(path)-10])
|
|
|
|
container.VerifyImageName(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
container.GetTagList(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m := blobsUploadsPattern.FindStringSubmatch(path)
|
2022-10-07 17:30:59 +02:00
|
|
|
if len(m) == 3 && (isGet || isPut || isPatch || isDelete) {
|
2022-03-30 10:42:47 +02:00
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("image", m[1])
|
|
|
|
container.VerifyImageName(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("uuid", m[2])
|
|
|
|
|
2022-10-07 17:30:59 +02:00
|
|
|
if isGet {
|
|
|
|
container.GetUploadBlob(ctx)
|
|
|
|
} else if isPatch {
|
2022-03-30 10:42:47 +02:00
|
|
|
container.UploadBlob(ctx)
|
2022-10-07 17:30:59 +02:00
|
|
|
} else if isPut {
|
2022-03-30 10:42:47 +02:00
|
|
|
container.EndUploadBlob(ctx)
|
2022-10-07 17:30:59 +02:00
|
|
|
} else {
|
|
|
|
container.CancelUploadBlob(ctx)
|
2022-03-30 10:42:47 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m = blobsPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 3 && (isHead || isGet || isDelete) {
|
|
|
|
ctx.SetParams("image", m[1])
|
|
|
|
container.VerifyImageName(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("digest", m[2])
|
|
|
|
|
|
|
|
if isHead {
|
|
|
|
container.HeadBlob(ctx)
|
|
|
|
} else if isGet {
|
|
|
|
container.GetBlob(ctx)
|
|
|
|
} else {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
container.DeleteBlob(ctx)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
m = manifestsPattern.FindStringSubmatch(path)
|
|
|
|
if len(m) == 3 && (isHead || isGet || isPut || isDelete) {
|
|
|
|
ctx.SetParams("image", m[1])
|
|
|
|
container.VerifyImageName(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.SetParams("reference", m[2])
|
|
|
|
|
|
|
|
if isHead {
|
|
|
|
container.HeadManifest(ctx)
|
|
|
|
} else if isGet {
|
|
|
|
container.GetManifest(ctx)
|
|
|
|
} else {
|
|
|
|
reqPackageAccess(perm.AccessModeWrite)(ctx)
|
|
|
|
if ctx.Written() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if isPut {
|
|
|
|
container.UploadManifest(ctx)
|
|
|
|
} else {
|
|
|
|
container.DeleteManifest(ctx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Status(http.StatusNotFound)
|
|
|
|
})
|
2024-02-27 15:12:22 +08:00
|
|
|
}, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
|
2022-03-30 10:42:47 +02:00
|
|
|
|
|
|
|
return r
|
|
|
|
}
|