2
0
Files
logikonline baaa8803a9 fix(packages): allow read access for private packages
Changes permission check from write to read access for viewing private packages. Organization members with read permissions can now view private packages, not just those with write access.
2026-01-24 14:37:19 -05:00

216 lines
6.9 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"errors"
"fmt"
"net/http"
"code.gitcaddy.com/server/v3/models/organization"
packages_model "code.gitcaddy.com/server/v3/models/packages"
"code.gitcaddy.com/server/v3/models/perm"
"code.gitcaddy.com/server/v3/models/unit"
user_model "code.gitcaddy.com/server/v3/models/user"
"code.gitcaddy.com/server/v3/modules/setting"
"code.gitcaddy.com/server/v3/modules/structs"
"code.gitcaddy.com/server/v3/modules/templates"
)
// Package contains owner, access mode and optional the package descriptor
type Package struct {
Owner *user_model.User
AccessMode perm.AccessMode
Descriptor *packages_model.PackageDescriptor
}
type packageAssignmentCtx struct {
*Base
Doer *user_model.User
ContextUser *user_model.User
}
// PackageAssignment returns a middleware to handle Context.Package assignment
func PackageAssignment() func(ctx *Context) {
return func(ctx *Context) {
errorFn := func(status int, obj any) {
err, ok := obj.(error)
if !ok {
err = fmt.Errorf("%s", obj)
}
if status == http.StatusNotFound {
ctx.NotFound(err)
} else {
ctx.ServerError("PackageAssignment", err)
}
}
paCtx := &packageAssignmentCtx{Base: ctx.Base, Doer: ctx.Doer, ContextUser: ctx.ContextUser}
ctx.Package = packageAssignment(paCtx, errorFn)
}
}
// PackageAssignmentAPI returns a middleware to handle Context.Package assignment
func PackageAssignmentAPI() func(ctx *APIContext) {
return func(ctx *APIContext) {
paCtx := &packageAssignmentCtx{Base: ctx.Base, Doer: ctx.Doer, ContextUser: ctx.ContextUser}
ctx.Package = packageAssignment(paCtx, ctx.APIError)
}
}
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 {
errCb(http.StatusInternalServerError, fmt.Errorf("determineAccessMode: %w", err))
return pkg
}
packageType := ctx.PathParam("type")
name := ctx.PathParam("name")
version := ctx.PathParam("version")
if packageType != "" && name != "" && version != "" {
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
}
}
pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv)
if err != nil {
errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageDescriptor: %w", err))
return pkg
}
// Check if package is private and user doesn't have read access
if pkg.Descriptor.Package.IsPrivate && pkg.AccessMode < perm.AccessModeRead {
errCb(http.StatusNotFound, errors.New("package is private"))
return pkg
}
}
return pkg
}
func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) {
if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) {
return perm.AccessModeNone, nil
}
if doer != nil && !doer.IsGhost() && (!doer.IsActive || doer.ProhibitLogin) {
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() {
org := organization.OrgFromUser(pkg.Owner)
if doer != nil && !doer.IsGhost() {
// 1. If user is logged in, check all team packages permissions
var err error
accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx, doer.ID)
if err != nil {
return accessMode, err
}
// If access mode is less than write check every team for more permissions
// The minimum possible access mode is read for org members
if accessMode < perm.AccessModeWrite {
teams, err := organization.GetUserOrgTeams(ctx, org.ID, doer.ID)
if err != nil {
return accessMode, err
}
for _, t := range teams {
perm := t.UnitAccessMode(ctx, unit.TypePackages)
if accessMode < perm {
accessMode = perm
}
}
}
}
if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, pkg.Owner, doer) {
// 2. If user is unauthorized or no org member, check if org is visible
accessMode = perm.AccessModeRead
}
} else {
if doer != nil && !doer.IsGhost() {
// 1. Check if user is package owner
if doer.ID == pkg.Owner.ID {
accessMode = perm.AccessModeOwner
} else if pkg.Owner.Visibility == structs.VisibleTypePublic || pkg.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
accessMode = perm.AccessModeRead
}
} else if pkg.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
accessMode = perm.AccessModeRead
}
}
return accessMode, nil
}
// PackageContexter initializes a package context for a request.
func PackageContexter() func(next http.Handler) http.Handler {
renderer := templates.HTMLRenderer()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base := NewBaseContext(resp, req)
// FIXME: web Context is still needed when rendering 500 page in a package handler
// It should be refactored to use new error handling mechanisms
ctx := NewWebContext(base, renderer, nil)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
}