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

137 lines
3.5 KiB
Go

// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"context"
"strings"
"gitea.dev/modules/structs"
)
const (
GhostUserID int64 = -1
GhostUserName = "Ghost"
)
// NewGhostUser creates and returns a fake user for someone has deleted their account.
func NewGhostUser() *User {
return &User{
ID: GhostUserID,
Name: GhostUserName,
LowerName: strings.ToLower(GhostUserName),
}
}
// IsGhost check if user is fake user for a deleted account
func (u *User) IsGhost() bool {
if u == nil {
return false
}
return u.ID == GhostUserID && u.Name == GhostUserName
}
// newSystemUser creates and returns a fake user for system use.
// The builtin username can be wrapped in parentheses to avoid conflicts with real usernames.
func newSystemUser(id int64, name, fullName string) *User {
return &User{
ID: id,
Name: name,
LowerName: strings.ToLower(name),
IsActive: true,
FullName: fullName,
Type: UserTypeBot,
Visibility: structs.VisibleTypePublic,
}
}
const (
ActionsUserID int64 = -2
DeployKeyUserID int64 = -3
CliUserID int64 = -4
AuthSourceUserID int64 = -5
)
// NewActionsUser creates and returns a fake user for running the actions.
func NewActionsUser() *User {
return newSystemUser(ActionsUserID, "gitea-actions", "Gitea Actions")
}
func GetActionsUserTaskID(u *User) (int64, bool) {
if u == nil || u.ExtDoerData == nil || u.ID != ActionsUserID {
return 0, false
}
extData := u.ExtDoerData.(*extDoerGiteaActions) //nolint:forcetypeassert // must be valid
return extData.TaskID, true
}
func NewActionsUserWithTaskID(id int64) *User {
u := NewActionsUser()
u.ExtDoerData = &extDoerGiteaActions{TaskID: id}
return u
}
func NewDeployKeyUser() *User {
return newSystemUser(DeployKeyUserID, "gitea:deploy-key", "Deploy Key")
}
func GetDeployKeyUserDeployKeyID(u *User) (int64, bool) {
// ok, the function name seems wordy, it is intentionally to distinguish from other "keys" like "public key id"
// it was a mess in the "pre-receive" hook code
if u == nil || u.ExtDoerData == nil || u.ID != DeployKeyUserID {
return 0, false
}
extData := u.ExtDoerData.(*extDoerDeployKey) //nolint:forcetypeassert // must be valid
return extData.DeployKeyID, true
}
func NewDeployKeyUserWithKeyID(id int64) *User {
u := NewDeployKeyUser()
u.ExtDoerData = &extDoerDeployKey{DeployKeyID: id}
return u
}
func NewCliUser() *User {
// for audit log only
return newSystemUser(CliUserID, "gitea:cli", "Gitea CLI")
}
func NewAuthSourceUser() *User {
// for audit log only
return newSystemUser(AuthSourceUserID, "gitea:auth-source", "Gitea Auth Source")
}
func GetSystemUserByName(name string) *User {
lowerName := strings.ToLower(name)
uid := globalVars().systemUserLowerNameIdMap[lowerName]
return GetSystemUserByID(uid)
}
func GetSystemUserByID(uid int64) *User {
if fn := globalVars().systemUserNewFuncs[uid]; fn != nil {
return fn()
}
return nil
}
func GetDoerPermissionUser(ctx context.Context, id int64, extDoerData string) (u *User, err error) {
switch {
case id > 0:
if u, err = GetUserByID(ctx, id); err != nil || extDoerData == "" {
return u, err
}
u.ExtDoerData = &extDoerToken{}
case id == ActionsUserID:
u = NewActionsUser()
u.ExtDoerData = &extDoerGiteaActions{}
case id == DeployKeyUserID:
u = NewDeployKeyUser()
u.ExtDoerData = &extDoerDeployKey{}
default:
// other system users are not real doers for the permission system
return nil, ErrUserNotExist{UID: id}
}
return u, u.ExtDoerData.DecodeFromString(extDoerData)
}