Add Swift login endpoint (#32693)

Fix #32683

This PR adds the login endpoint and fixes the documentation links.

(cherry picked from commit 136408307c6de7aac2ab5476f8cddf90f39355dc)

Conflicts:
	routers/api/packages/api.go
  trivial context conflicts
This commit is contained in:
KN4CK3R 2024-12-03 17:24:16 +01:00 committed by Earl Warren
parent 48131547a1
commit 0786ddc5de
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
3 changed files with 76 additions and 42 deletions

View file

@ -693,40 +693,46 @@ func CommonRoutes() *web.Route {
}, reqPackageAccess(perm.AccessModeWrite)) }, reqPackageAccess(perm.AccessModeWrite))
}, reqPackageAccess(perm.AccessModeRead)) }, reqPackageAccess(perm.AccessModeRead))
r.Group("/swift", func() { r.Group("/swift", func() {
r.Group("/{scope}/{name}", func() { r.Group("", func() { // Needs to be unauthenticated.
r.Group("", func() { r.Post("", swift.CheckAuthenticate)
r.Get("", swift.EnumeratePackageVersions) r.Post("/login", swift.CheckAuthenticate)
r.Get(".json", swift.EnumeratePackageVersions) })
}, swift.CheckAcceptMediaType(swift.AcceptJSON)) r.Group("", func() {
r.Group("/{version}", func() { r.Group("/{scope}/{name}", func() {
r.Get("/Package.swift", swift.CheckAcceptMediaType(swift.AcceptSwift), swift.DownloadManifest) r.Group("", func() {
r.Put("", reqPackageAccess(perm.AccessModeWrite), swift.CheckAcceptMediaType(swift.AcceptJSON), enforcePackagesQuota(), swift.UploadPackageFile) r.Get("", swift.EnumeratePackageVersions)
r.Get("", func(ctx *context.Context) { r.Get(".json", swift.EnumeratePackageVersions)
// Can't use normal routes here: https://github.com/go-chi/chi/issues/781 }, 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") version := ctx.Params("version")
if strings.HasSuffix(version, ".zip") { if strings.HasSuffix(version, ".zip") {
swift.CheckAcceptMediaType(swift.AcceptZip)(ctx) swift.CheckAcceptMediaType(swift.AcceptZip)(ctx)
if ctx.Written() { if ctx.Written() {
return 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)
} }
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)
}
}) })
}) })
}) r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers)
r.Get("/identifiers", swift.CheckAcceptMediaType(swift.AcceptJSON), swift.LookupPackageIdentifiers) }, reqPackageAccess(perm.AccessModeRead))
}, reqPackageAccess(perm.AccessModeRead)) })
r.Group("/vagrant", func() { r.Group("/vagrant", func() {
r.Group("/authenticate", func() { r.Group("/authenticate", func() {
r.Get("", vagrant.CheckAuthenticate) r.Get("", vagrant.CheckAuthenticate)

View file

@ -27,7 +27,7 @@ import (
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
) )
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#35-api-versioning // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#35-api-versioning
const ( const (
AcceptJSON = "application/vnd.swift.registry.v1+json" AcceptJSON = "application/vnd.swift.registry.v1+json"
AcceptSwift = "application/vnd.swift.registry.v1+swift" AcceptSwift = "application/vnd.swift.registry.v1+swift"
@ -35,9 +35,9 @@ const (
) )
var ( var (
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#361-package-scope // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#361-package-scope
scopePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\z`) scopePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-]{0,38}\z`)
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#362-package-name // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#362-package-name
namePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-_]{0,99}\z`) namePattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9-_]{0,99}\z`)
) )
@ -49,7 +49,7 @@ type headers struct {
Link string Link string
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#35-api-versioning // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#35-api-versioning
func setResponseHeaders(resp http.ResponseWriter, h *headers) { func setResponseHeaders(resp http.ResponseWriter, h *headers) {
if h.ContentType != "" { if h.ContentType != "" {
resp.Header().Set("Content-Type", h.ContentType) resp.Header().Set("Content-Type", h.ContentType)
@ -69,7 +69,7 @@ func setResponseHeaders(resp http.ResponseWriter, h *headers) {
} }
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#33-error-handling // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#33-error-handling
func apiError(ctx *context.Context, status int, obj any) { func apiError(ctx *context.Context, status int, obj any) {
// https://www.rfc-editor.org/rfc/rfc7807 // https://www.rfc-editor.org/rfc/rfc7807
type Problem struct { type Problem struct {
@ -91,7 +91,7 @@ func apiError(ctx *context.Context, status int, obj any) {
}) })
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#35-api-versioning // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#35-api-versioning
func CheckAcceptMediaType(requiredAcceptHeader string) func(ctx *context.Context) { func CheckAcceptMediaType(requiredAcceptHeader string) func(ctx *context.Context) {
return func(ctx *context.Context) { return func(ctx *context.Context) {
accept := ctx.Req.Header.Get("Accept") accept := ctx.Req.Header.Get("Accept")
@ -101,6 +101,16 @@ func CheckAcceptMediaType(requiredAcceptHeader string) func(ctx *context.Context
} }
} }
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md#registry-authentication
func CheckAuthenticate(ctx *context.Context) {
if ctx.Doer == nil {
apiError(ctx, http.StatusUnauthorized, nil)
return
}
ctx.Status(http.StatusOK)
}
func buildPackageID(scope, name string) string { func buildPackageID(scope, name string) string {
return scope + "." + name return scope + "." + name
} }
@ -113,7 +123,7 @@ type EnumeratePackageVersionsResponse struct {
Releases map[string]Release `json:"releases"` Releases map[string]Release `json:"releases"`
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#41-list-package-releases // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#41-list-package-releases
func EnumeratePackageVersions(ctx *context.Context) { func EnumeratePackageVersions(ctx *context.Context) {
packageScope := ctx.Params("scope") packageScope := ctx.Params("scope")
packageName := ctx.Params("name") packageName := ctx.Params("name")
@ -170,7 +180,7 @@ type PackageVersionMetadataResponse struct {
Metadata *swift_module.SoftwareSourceCode `json:"metadata"` Metadata *swift_module.SoftwareSourceCode `json:"metadata"`
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-2 // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-2
func PackageVersionMetadata(ctx *context.Context) { func PackageVersionMetadata(ctx *context.Context) {
id := buildPackageID(ctx.Params("scope"), ctx.Params("name")) id := buildPackageID(ctx.Params("scope"), ctx.Params("name"))
@ -228,7 +238,7 @@ func PackageVersionMetadata(ctx *context.Context) {
}) })
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#43-fetch-manifest-for-a-package-release // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#43-fetch-manifest-for-a-package-release
func DownloadManifest(ctx *context.Context) { func DownloadManifest(ctx *context.Context) {
packageScope := ctx.Params("scope") packageScope := ctx.Params("scope")
packageName := ctx.Params("name") packageName := ctx.Params("name")
@ -280,7 +290,7 @@ func DownloadManifest(ctx *context.Context) {
}) })
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-6 // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
func UploadPackageFile(ctx *context.Context) { func UploadPackageFile(ctx *context.Context) {
packageScope := ctx.Params("scope") packageScope := ctx.Params("scope")
packageName := ctx.Params("name") packageName := ctx.Params("name")
@ -379,7 +389,7 @@ func UploadPackageFile(ctx *context.Context) {
ctx.Status(http.StatusCreated) ctx.Status(http.StatusCreated)
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-4 // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-4
func DownloadPackageFile(ctx *context.Context) { func DownloadPackageFile(ctx *context.Context) {
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(ctx.Params("scope"), ctx.Params("name")), ctx.Params("version")) pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeSwift, buildPackageID(ctx.Params("scope"), ctx.Params("name")), ctx.Params("version"))
if err != nil { if err != nil {
@ -420,7 +430,7 @@ type LookupPackageIdentifiersResponse struct {
Identifiers []string `json:"identifiers"` Identifiers []string `json:"identifiers"`
} }
// https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-5 // https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-5
func LookupPackageIdentifiers(ctx *context.Context) { func LookupPackageIdentifiers(ctx *context.Context) {
url := ctx.FormTrim("url") url := ctx.FormTrim("url")
if url == "" { if url == "" {

View file

@ -43,6 +43,24 @@ func TestPackageSwift(t *testing.T) {
url := fmt.Sprintf("/api/packages/%s/swift", user.Name) url := fmt.Sprintf("/api/packages/%s/swift", user.Name)
t.Run("CheckLogin", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithBody(t, "POST", url, strings.NewReader(""))
MakeRequest(t, req, http.StatusUnauthorized)
req = NewRequestWithBody(t, "POST", url, strings.NewReader("")).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
req = NewRequestWithBody(t, "POST", url+"/login", strings.NewReader(""))
MakeRequest(t, req, http.StatusUnauthorized)
req = NewRequestWithBody(t, "POST", url+"/login", strings.NewReader("")).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusOK)
})
t.Run("CheckAcceptMediaType", func(t *testing.T) { t.Run("CheckAcceptMediaType", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()