Files
silverwindandwxiaoguang 5264304a34 refactor: carry the API token on the doer (#39690)
The token a request authenticated with now lives on the doer, so
permission and visibility checks apply its scope from the doer alone.
Replaces https://github.com/go-gitea/gitea/pull/39167, following
https://github.com/go-gitea/gitea/pull/39167#issuecomment-6063839619.

1. Public-only tokens get 404 instead of 403 for hidden users, orgs and
repos, like GitHub.
2. Org ownership, site admin, repo and org creation, generation and
transfer endpoints reject public-only tokens.
3. API pull request responses set `head.repo` to `null` and
`head.repo_id` to `-1` when the caller cannot see the head repository.
4. Public-only tokens see org memberships like anonymous callers, so
concealed members stay hidden.
5. Site admin access in activity feeds, follower lists, org members,
Actions run lists and packages needs a session or an `all`-scoped token.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-19 00:34:01 +00:00

135 lines
3.1 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"errors"
"strconv"
"strings"
"gitea.dev/models/auth"
)
type ExtDoerData interface {
EncodeToString() string
DecodeFromString(string) error
}
type extDoerGiteaActions struct {
TaskID int64
}
var _ ExtDoerData = (*extDoerGiteaActions)(nil)
func (e *extDoerGiteaActions) EncodeToString() string {
return "gitea-actions:" + strconv.FormatInt(e.TaskID, 10)
}
func (e *extDoerGiteaActions) DecodeFromString(s string) (err error) {
idStr, _ := strings.CutPrefix(s, "gitea-actions:")
e.TaskID, err = strconv.ParseInt(idStr, 10, 64)
return err
}
type extDoerDeployKey struct {
DeployKeyID int64
}
var _ ExtDoerData = (*extDoerDeployKey)(nil)
func (e *extDoerDeployKey) EncodeToString() string {
return "deploy-key:" + strconv.FormatInt(e.DeployKeyID, 10)
}
func (e *extDoerDeployKey) DecodeFromString(s string) (err error) {
idStr, _ := strings.CutPrefix(s, "deploy-key:")
e.DeployKeyID, err = strconv.ParseInt(idStr, 10, 64)
return err
}
const (
CredentialAccessToken = "access-token"
CredentialOAuth2Grant = "oauth2-grant"
)
type extDoerToken struct {
credential string // empty for package registry tokens
scope auth.AccessTokenScope
publicOnly bool
}
var _ ExtDoerData = (*extDoerToken)(nil)
func NewTokenExtDoerData(credentialKind string, credentialID int64, scope auth.AccessTokenScope) ExtDoerData {
publicOnly, err := scope.PublicOnly()
e := &extDoerToken{scope: scope, publicOnly: publicOnly || err != nil}
if credentialKind != "" {
e.credential = credentialKind + ":" + strconv.FormatInt(credentialID, 10)
}
return e
}
func (e *extDoerToken) EncodeToString() string {
return e.credential + "|" + string(e.scope)
}
func (e *extDoerToken) DecodeFromString(s string) error {
credential, scope, ok := strings.Cut(s, "|")
if !ok {
return errors.New("invalid token doer data")
}
publicOnly, err := auth.AccessTokenScope(scope).PublicOnly()
if err != nil {
return err
}
e.credential, e.scope, e.publicOnly = credential, auth.AccessTokenScope(scope), publicOnly
return nil
}
func getDoerToken(u *User) *extDoerToken {
if u == nil {
return nil
}
token, _ := u.ExtDoerData.(*extDoerToken)
return token
}
func GetDoerTokenScope(u *User) (auth.AccessTokenScope, bool) {
if token := getDoerToken(u); token != nil {
return token.scope, true
}
return "", false
}
func IsPublicOnlyDoer(u *User) bool {
token := getDoerToken(u)
return token != nil && token.publicOnly
}
func DoerCanViewPrivate(doer *User) bool {
return doer != nil && !IsPublicOnlyDoer(doer)
}
func DoerIsSiteAdminFullAccess(doer *User) bool {
if !DoerCanViewPrivate(doer) || !doer.IsAdmin {
return false
}
scope, hasScope := GetDoerTokenScope(doer)
return !hasScope || scope == auth.AccessTokenScopeAll
}
func DoerTokenAllowsOwner(doer, owner *User) bool {
return !IsPublicOnlyDoer(doer) || owner.Visibility.IsPublic()
}
func GetDoerCredential(u *User) string {
if u == nil || u.ExtDoerData == nil {
return ""
}
if token := getDoerToken(u); token != nil {
return token.credential
}
return u.ExtDoerData.EncodeToString()
}