mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
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]>
286 lines
8.1 KiB
Go
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
|
|
}
|