feat(packages): add global package access support
Add is_global flag to packages allowing them to be accessible at root URLs without owner prefix. Include database migration, package settings UI, admin bulk operations, and automatic repository matching. This enables cleaner package URLs for organization-wide packages.
This commit is contained in:
@@ -61,6 +61,10 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package
|
||||
pkg := &Package{
|
||||
Owner: ctx.ContextUser,
|
||||
}
|
||||
|
||||
// Handle global packages (when owner is nil, i.e., username was "_")
|
||||
isGlobalRequest := ctx.ContextUser == nil
|
||||
|
||||
var err error
|
||||
pkg.AccessMode, err = determineAccessMode(ctx.Base, pkg, ctx.Doer)
|
||||
if err != nil {
|
||||
@@ -72,14 +76,47 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package
|
||||
name := ctx.PathParam("name")
|
||||
version := ctx.PathParam("version")
|
||||
if packageType != "" && name != "" && version != "" {
|
||||
pv, err := packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
var pv *packages_model.PackageVersion
|
||||
|
||||
if isGlobalRequest {
|
||||
// Look up global package by name (no owner)
|
||||
p, err := packages_model.GetGlobalPackageByName(ctx, packages_model.Type(packageType), name)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetGlobalPackageByName: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetGlobalPackageByName: %w", err))
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
// Get the actual owner for the package
|
||||
owner, err := user_model.GetUserByID(ctx, p.OwnerID)
|
||||
if err != nil {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetUserByID: %w", err))
|
||||
return pkg
|
||||
}
|
||||
pkg.Owner = owner
|
||||
|
||||
// Get the version
|
||||
pv, err = packages_model.GetVersionByNameAndVersion(ctx, p.OwnerID, packages_model.Type(packageType), name, version)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
} else {
|
||||
pv, err = packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version)
|
||||
if err != nil {
|
||||
if err == packages_model.ErrPackageNotExist {
|
||||
errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
} else {
|
||||
errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err))
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv)
|
||||
@@ -101,6 +138,14 @@ func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.A
|
||||
return perm.AccessModeNone, nil
|
||||
}
|
||||
|
||||
// Global packages (no owner) - allow read access to everyone, write only to admins
|
||||
if pkg.Owner == nil {
|
||||
if doer != nil && doer.IsAdmin {
|
||||
return perm.AccessModeAdmin, nil
|
||||
}
|
||||
return perm.AccessModeRead, nil
|
||||
}
|
||||
|
||||
// TODO: ActionUser permission check
|
||||
accessMode := perm.AccessModeNone
|
||||
if pkg.Owner.IsOrganization() {
|
||||
|
||||
@@ -61,6 +61,11 @@ func UserAssignmentAPI() func(ctx *APIContext) {
|
||||
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (contextUser *user_model.User) {
|
||||
username := ctx.PathParam("username")
|
||||
|
||||
// Special case: "_" means global packages (no specific owner)
|
||||
if username == "_" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if doer != nil && strings.EqualFold(doer.LowerName, username) {
|
||||
contextUser = doer
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user