Files
zeekayandHanzo Dev aa87442fdd Record upstream authorship centrally in NOTICE instead of in every file header
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the
Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in
NOTICE, alongside LICENSE. The header lint accepts any copyright line above the
SPDX line, so it no longer demands an upstream one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:50:35 -07:00

286 lines
8.1 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"strings"
"github.com/hanzoai/git/models/organization"
"github.com/hanzoai/git/models/perm"
"github.com/hanzoai/git/models/unit"
user_model "github.com/hanzoai/git/models/user"
"github.com/hanzoai/git/modules/markup"
"github.com/hanzoai/git/modules/markup/markdown"
"github.com/hanzoai/git/modules/setting"
"github.com/hanzoai/git/modules/structs"
)
// Organization contains organization context
type Organization struct {
IsOwner bool
IsMember bool
IsTeamMember bool // Is member of team.
IsTeamAdmin bool // In owner team or team that has admin permission level.
Organization *organization.Organization
OrgLink string
CanCreateOrgRepo bool
Team *organization.Team
Teams []*organization.Team
}
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeWrite
}
func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead
}
func GetOrganizationByParams(ctx *Context) {
orgName := ctx.PathParam("org")
var err error
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil {
if organization.IsErrOrgNotExist(err) {
redirectUserID, err := user_model.LookupUserRedirect(ctx, orgName)
if err == nil {
RedirectToUser(ctx.Base, ctx.Doer, orgName, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
ctx.NotFound(err)
} else {
ctx.ServerError("LookupUserRedirect", err)
}
} else {
ctx.ServerError("GetUserByName", err)
}
return
}
}
type OrgAssignmentOptions struct {
RequireMember bool
RequireOwner bool
RequireTeamMember bool
RequireTeamAdmin bool
}
// OrgAssignment returns a middleware to handle organization assignment
func OrgAssignment(orgAssignmentOpts OrgAssignmentOptions) func(ctx *Context) {
return func(ctx *Context) {
opts := orgAssignmentOpts // it must be a copy, because the values will be changed
var err error
if ctx.ContextUser == nil {
// if Organization is not defined, get it from params
if ctx.Org.Organization == nil {
GetOrganizationByParams(ctx)
if ctx.Written() {
return
}
}
} else if ctx.ContextUser.IsOrganization() {
ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
} else {
// ContextUser is an individual User
return
}
org := ctx.Org.Organization
// Handle Visibility
if org.Visibility != structs.VisibleTypePublic && !ctx.IsSigned {
// We must be signed in to see limited or private organizations
ctx.NotFound(err)
return
}
if org.Visibility == structs.VisibleTypePrivate {
opts.RequireMember = true
} else if ctx.IsSigned && ctx.Doer.IsRestricted {
opts.RequireMember = true
}
ctx.ContextUser = org.AsUser()
ctx.Data["Org"] = org
// Admin has super access.
if ctx.IsSigned && ctx.Doer.IsAdmin {
ctx.Org.IsOwner = true
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
ctx.Org.CanCreateOrgRepo = true
} else if ctx.IsSigned {
ctx.Org.IsOwner, err = org.IsOwnedBy(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsOwnedBy", err)
return
}
if ctx.Org.IsOwner {
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
ctx.Org.CanCreateOrgRepo = true
} else {
ctx.Org.IsMember, err = org.IsOrgMember(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsOrgMember", err)
return
}
if ctx.Org.IsMember {
ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("CanCreateOrgRepo", err)
return
}
}
}
} else {
// Fake data.
ctx.Data["SignedUser"] = &user_model.User{}
}
if (opts.RequireMember && !ctx.Org.IsMember) || (opts.RequireOwner && !ctx.Org.IsOwner) {
ctx.NotFound(err)
return
}
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["IsPublicMember"] = func(uid int64) bool {
is, _ := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, uid)
return is
}
ctx.Data["CanCreateOrgRepo"] = ctx.Org.CanCreateOrgRepo
ctx.Org.OrgLink = org.AsUser().OrganisationLink()
ctx.Data["OrgLink"] = ctx.Org.OrgLink
// Member
findMembersOpts := &organization.FindOrgMembersOpts{
Doer: ctx.Doer,
OrgID: org.ID,
IsDoerMember: ctx.Org.IsMember,
}
ctx.Data["NumMembers"], err = organization.CountOrgMembers(ctx, findMembersOpts)
if err != nil {
ctx.ServerError("CountOrgMembers", err)
return
}
// Team.
shouldSeeAllTeams, err := UserShouldSeeAllOrgTeams(ctx)
if err != nil {
ctx.ServerError("UserShouldSeeAllOrgTeams", err)
return
}
switch {
case shouldSeeAllTeams:
ctx.Org.Teams, err = org.LoadTeams(ctx)
if err != nil {
ctx.ServerError("LoadTeams", err)
return
}
case ctx.IsSigned:
// Signed-in non-members still see teams whose visibility tier
// includes them (public for any signed-in user, plus limited
// for org members), and any team they directly belong to.
ctx.Org.Teams, _, err = organization.SearchTeam(ctx, &organization.SearchTeamOptions{
OrgID: org.ID,
UserID: ctx.Doer.ID,
IncludeVisibilities: organization.VisibleTeamVisibilitiesFor(ctx.Org.IsMember, true),
})
if err != nil {
ctx.ServerError("SearchTeam", err)
return
}
}
if ctx.Org.IsMember {
ctx.Data["NumTeams"] = len(ctx.Org.Teams)
}
teamName := ctx.PathParam("team")
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {
if strings.EqualFold(team.LowerName, teamName) {
teamExists = true
ctx.Org.Team = team
ctx.Data["Team"] = ctx.Org.Team
break
}
}
if !teamExists {
ctx.NotFound(err)
return
}
// Membership in a visible team is not implied by its presence in
// ctx.Org.Teams; admins/org owners keep the privileged flag set
// earlier in this function.
if !ctx.Org.IsOwner {
ctx.Org.IsTeamMember, err = organization.IsTeamMember(ctx, org.ID, ctx.Org.Team.ID, ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsTeamMember", err)
return
}
}
ctx.Data["IsTeamMember"] = ctx.Org.IsTeamMember
if opts.RequireTeamMember && !ctx.Org.IsTeamMember {
ctx.NotFound(err)
return
}
isTeamOwnerOrAdmin := ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.HasAdminAccess()
ctx.Org.IsTeamAdmin = ctx.Org.IsOwner || (ctx.Org.IsTeamMember && isTeamOwnerOrAdmin)
ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin
if opts.RequireTeamAdmin && !ctx.Org.IsTeamAdmin {
ctx.NotFound(err)
return
}
}
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(markup.NewRenderContext(ctx), ctx.ContextUser.Description)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
ctx.Data["RenderedDescription"] = content
}
}
}
// UserShouldSeeAllOrgTeams tells if a user has permission to view all teams in the org.
func UserShouldSeeAllOrgTeams(ctx *Context) (bool, error) {
if !ctx.Org.IsMember {
return false, nil
}
if ctx.Org.IsOwner {
return true, nil
}
teams, err := ctx.Org.Organization.GetUserTeams(ctx, ctx.Doer.ID)
if err != nil {
return false, err
}
for _, team := range teams {
if team.IncludesAllRepositories && team.HasAdminAccess() {
return true, nil
}
}
return false, nil
}