Skip to content

Move organization's visibility change to danger zone. #34814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,16 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error {
return err
}

// UpdateUserColsWithNoAutotime update user according special columns
func UpdateUserColsWithNoAutotime(ctx context.Context, u *User, cols ...string) error {
if err := ValidateUser(u, cols...); err != nil {
return err
}

_, err := db.GetEngine(ctx).ID(u.ID).Cols(cols...).NoAutoTime().Update(u)
return err
}

// GetInactiveUsers gets all inactive users
func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) {
cond := builder.And(
Expand Down
6 changes: 5 additions & 1 deletion modules/indexer/issues/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,14 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
return nil, false, err
}

if err := issue.Repo.LoadOwner(ctx); err != nil {
return nil, false, fmt.Errorf("issue.Repo.LoadOwner: %w", err)
}

return &internal.IndexerData{
ID: issue.ID,
RepoID: issue.RepoID,
IsPublic: !issue.Repo.IsPrivate,
IsPublic: !issue.Repo.IsPrivate && issue.Repo.Owner.Visibility.IsPublic(),
Title: issue.Title,
Content: issue.Content,
Comments: comments,
Expand Down
11 changes: 11 additions & 0 deletions modules/structs/visible_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func (vt VisibleType) IsPrivate() bool {
return vt == VisibleTypePrivate
}

func (vt VisibleType) IsValid() bool {
return vt.String() != ""
}

// VisibilityString provides the mode string of the visibility type (public, limited, private)
func (vt VisibleType) String() string {
for k, v := range VisibilityModes {
Expand All @@ -56,3 +60,10 @@ func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) {
}
return keys
}

func ConvertStringToVisibleType(s string) VisibleType {
if vt, ok := VisibilityModes[s]; ok {
return vt
}
return VisibleType(-1) // Invalid type
}
8 changes: 8 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2826,6 +2826,14 @@ settings.location = Location
settings.permission = Permissions
settings.repoadminchangeteam = Repository admin can add and remove access for teams
settings.visibility = Visibility
settings.change_visibility = Change Visibility
settings.invalid_visibility = The new visibility is not valid.
settings.change_visibility_notices_1 = This operation <strong>CANNOT</strong> be undone.
settings.change_visibility_notices_2 = Some users will not visit the repositories of the orgniazation.
settings.change_visibility_no_change = The visibility is no change.
settings.change_visibility_failed = Change visibility of %s failed because of internal error
settings.change_visibility_success = Organization %s visibility has been changed successfully.
settings.visibility_desc = Changing the organisation visibility
settings.visibility.public = Public
settings.visibility.limited = Limited (Visible to authenticated users only)
settings.visibility.limited_shortname = Limited
Expand Down
46 changes: 24 additions & 22 deletions routers/web/org/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
Expand All @@ -25,7 +26,6 @@ import (
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
org_service "code.gitea.io/gitea/services/org"
repo_service "code.gitea.io/gitea/services/repository"
user_service "code.gitea.io/gitea/services/user"
)

Expand Down Expand Up @@ -83,38 +83,17 @@ func SettingsPost(ctx *context.Context) {
Description: optional.Some(form.Description),
Website: optional.Some(form.Website),
Location: optional.Some(form.Location),
Visibility: optional.Some(form.Visibility),
RepoAdminChangeTeamAccess: optional.Some(form.RepoAdminChangeTeamAccess),
}
if ctx.Doer.IsAdmin {
opts.MaxRepoCreation = optional.Some(form.MaxRepoCreation)
}

visibilityChanged := org.Visibility != form.Visibility

if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil {
ctx.ServerError("UpdateUser", err)
return
}

// update forks visibility
if visibilityChanged {
repos, _, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: org.AsUser(), Private: true, ListOptions: db.ListOptions{Page: 1, PageSize: org.NumRepos},
})
if err != nil {
ctx.ServerError("GetRepositories", err)
return
}
for _, repo := range repos {
repo.OwnerName = org.Name
if err := repo_service.UpdateRepository(ctx, repo, true); err != nil {
ctx.ServerError("UpdateRepository", err)
return
}
}
}

log.Trace("Organization setting updated: %s", org.Name)
ctx.Flash.Success(ctx.Tr("org.settings.update_setting_success"))
ctx.Redirect(ctx.Org.OrgLink + "/settings")
Expand Down Expand Up @@ -251,3 +230,26 @@ func SettingsRenamePost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("org.settings.rename_success", oldOrgName, newOrgName))
ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(newOrgName) + "/settings")
}

// SettingsChangeVisibilityPost response for change organization visibility
func SettingsChangeVisibilityPost(ctx *context.Context) {
visibility := structs.VisibleType(ctx.FormInt("visibility"))
if !visibility.IsValid() {
ctx.JSONError(ctx.Tr("org.settings.invalid_visibility"))
return
}

if ctx.Org.Organization.Visibility == visibility {
ctx.JSONError(ctx.Tr("org.settings.change_visibility_no_change"))
return
}

if err := org_service.ChangeOrganizationVisibility(ctx, ctx.Org.Organization, visibility); err != nil {
log.Error("ChangeOrganizationVisibility: %v", err)
ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.change_visibility_failed", ctx.Org.Organization.Name))))
return
}

ctx.Flash.Success(ctx.Tr("org.settings.change_visibility_success", ctx.Org.Organization.Name))
ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(ctx.Org.Organization.Name) + "/settings")
}
1 change: 1 addition & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,7 @@ func registerWebRoutes(m *web.Router) {

m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRenamePost)
m.Post("/delete", org.SettingsDeleteOrgPost)
m.Post("/visibility", org.SettingsChangeVisibilityPost)

m.Group("/packages", func() {
m.Get("", org.Packages)
Expand Down
1 change: 0 additions & 1 deletion services/forms/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ type UpdateOrgSettingForm struct {
Description string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
Visibility structs.VisibleType
MaxRepoCreation int
RepoAdminChangeTeamAccess bool
}
Expand Down
71 changes: 71 additions & 0 deletions services/org/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
"fmt"

actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
secret_model "code.gitea.io/gitea/models/secret"
user_model "code.gitea.io/gitea/models/user"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
)
Expand Down Expand Up @@ -102,3 +106,70 @@

return nil
}

func updateOrgRepoForVisibilityChanged(ctx context.Context, repo *repo_model.Repository, makePrivate bool) error {
// Organization repository need to recalculate access table when visibility is changed.
if err := access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
return fmt.Errorf("recalculateTeamAccesses: %w", err)
}

if makePrivate {
if _, err := db.GetEngine(ctx).Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
IsPrivate: true,
}); err != nil {
return err
}

if err := repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
return err
}
}

// Create/Remove git-daemon-export-ok for git-daemon...
if err := repo_service.CheckDaemonExportOK(ctx, repo); err != nil {
return err
}

// If visibility is changed, we need to update the issue indexer.
// Since the data in the issue indexer have field to indicate if the repo is public or not.
// FIXME: it should check organization visibility instead of repository visibility only.
issue_indexer.UpdateRepoIndexer(ctx, repo.ID)

forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
if err != nil {
return fmt.Errorf("getRepositoriesByForkID: %w", err)
}
for i := range forkRepos {
if err := updateOrgRepoForVisibilityChanged(ctx, forkRepos[i], makePrivate); err != nil {
return fmt.Errorf("updateRepoForVisibilityChanged[%d]: %w", forkRepos[i], err)

Check failure on line 144 in services/org/org.go

View workflow job for this annotation

GitHub Actions / test-unit

fmt.Errorf format %d has arg forkRepos[i] of wrong type *code.gitea.io/gitea/models/repo.Repository

Check failure on line 144 in services/org/org.go

View workflow job for this annotation

GitHub Actions / lint-go-windows

printf: fmt.Errorf format %d has arg forkRepos[i] of wrong type *code.gitea.io/gitea/models/repo.Repository (govet)

Check failure on line 144 in services/org/org.go

View workflow job for this annotation

GitHub Actions / lint-backend

printf: fmt.Errorf format %d has arg forkRepos[i] of wrong type *code.gitea.io/gitea/models/repo.Repository (govet)

Check failure on line 144 in services/org/org.go

View workflow job for this annotation

GitHub Actions / lint-go-gogit

printf: fmt.Errorf format %d has arg forkRepos[i] of wrong type *code.gitea.io/gitea/models/repo.Repository (govet)
}
}
return nil
}

func ChangeOrganizationVisibility(ctx context.Context, org *org_model.Organization, visibility structs.VisibleType) error {
if org.Visibility == visibility {
return nil
}

org.Visibility = visibility
// FIXME: If it's a big forks network(forks and sub forks), the database transaction will be too long to fail.
return db.WithTx(ctx, func(ctx context.Context) error {
if err := user_model.UpdateUserColsWithNoAutotime(ctx, org.AsUser(), "visibility"); err != nil {
return err
}

repos, _, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
Actor: org.AsUser(), Private: true, ListOptions: db.ListOptionsAll,
})
if err != nil {
return err
}
for _, repo := range repos {
if err := updateOrgRepoForVisibilityChanged(ctx, repo, visibility == structs.VisibleTypePrivate); err != nil {
return fmt.Errorf("updateOrgRepoForVisibilityChanged: %w", err)
}
}
return nil
})
}
2 changes: 1 addition & 1 deletion services/repository/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ func cleanupRepository(repoID int64) {
}

func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) error {
if err := checkDaemonExportOK(ctx, repo); err != nil {
if err := CheckDaemonExportOK(ctx, repo); err != nil {
return fmt.Errorf("checkDaemonExportOK: %w", err)
}

Expand Down
10 changes: 5 additions & 5 deletions services/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error
}

// Create/Remove git-daemon-export-ok for git-daemon...
if err := checkDaemonExportOK(ctx, repo); err != nil {
if err := CheckDaemonExportOK(ctx, repo); err != nil {
return err
}

Expand Down Expand Up @@ -197,7 +197,7 @@ func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err erro
}

// Create/Remove git-daemon-export-ok for git-daemon...
if err := checkDaemonExportOK(ctx, repo); err != nil {
if err := CheckDaemonExportOK(ctx, repo); err != nil {
return err
}

Expand Down Expand Up @@ -243,8 +243,8 @@ func LinkedRepository(ctx context.Context, a *repo_model.Attachment) (*repo_mode
return nil, -1, nil
}

// checkDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
func checkDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error {
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error {
if err := repo.LoadOwner(ctx); err != nil {
return err
}
Expand Down Expand Up @@ -314,7 +314,7 @@ func updateRepository(ctx context.Context, repo *repo_model.Repository, visibili
}

// Create/Remove git-daemon-export-ok for git-daemon...
if err := checkDaemonExportOK(ctx, repo); err != nil {
if err := CheckDaemonExportOK(ctx, repo); err != nil {
return err
}

Expand Down
23 changes: 0 additions & 23 deletions templates/org/settings/options.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,6 @@
<input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
</div>

<div class="divider"></div>
<div class="field" id="visibility_box">
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>

<div class="field" id="permission_box">
<label>{{ctx.Locale.Tr "org.settings.permission"}}</label>
<div class="field">
Expand Down
52 changes: 52 additions & 0 deletions templates/org/settings/options_dangerzone.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
</h4>
<div class="ui attached error danger segment">
<div class="flex-list">
<div class="flex-item tw-items-center">
<div class="flex-item-main">
<div class="flex-item-title">{{ctx.Locale.Tr "org.settings.visibility"}}</div>
<div class="flex-item-body">{{ctx.Locale.Tr "org.settings.visibility_desc"}}</div>
</div>
<div class="flex-item-trailing">
<button class="ui basic red show-modal button" data-modal="#change-visibility-org-modal">{{ctx.Locale.Tr "org.settings.change_visibility"}}</button>
</div>
</div>

<div class="flex-item tw-items-center">
<div class="flex-item-main">
<div class="flex-item-title">{{ctx.Locale.Tr "org.settings.rename"}}</div>
Expand All @@ -25,6 +35,48 @@
</div>
</div>

<div class="ui small modal" id="change-visibility-org-modal">
<div class="header">
{{ctx.Locale.Tr "org.settings.change_visibility"}}
</div>
<div class="content">
<ul class="ui warning message">
<li>{{ctx.Locale.Tr "org.settings.change_visibility_notices_1"}}</li>
<li>{{ctx.Locale.Tr "org.settings.change_visibility_notices_2"}}</li>
</ul>
<form class="ui form form-fetch-action" action="{{.Link}}/visibility" method="post">
{{.CsrfTokenHtml}}

<div class="field">
<label>{{ctx.Locale.Tr "org.settings.visibility"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>

<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "org.settings.change_visibility"}}</button>
</div>
</form>
</div>
</div>

<div class="ui small modal" id="rename-org-modal">
<div class="header">
{{ctx.Locale.Tr "org.settings.rename"}}
Expand Down
Loading