diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go
index 58f158bd17..8ac2fb63f2 100644
--- a/models/forgejo_migrations/migrate.go
+++ b/models/forgejo_migrations/migrate.go
@@ -10,6 +10,7 @@ import (
 
 	"code.gitea.io/gitea/models/forgejo/semver"
 	forgejo_v1_20 "code.gitea.io/gitea/models/forgejo_migrations/v1_20"
+	forgejo_v1_22 "code.gitea.io/gitea/models/forgejo_migrations/v1_22"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -43,6 +44,8 @@ var migrations = []*Migration{
 	NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable),
 	// v2 -> v3
 	NewMigration("create the forgejo_auth_token table", forgejo_v1_20.CreateAuthorizationTokenTable),
+	// v3 -> v4
+	NewMigration("Add default_permissions to repo_unit", forgejo_v1_22.AddDefaultPermissionsToRepoUnit),
 }
 
 // GetCurrentDBVersion returns the current Forgejo database version.
diff --git a/models/forgejo_migrations/v1_22/v4.go b/models/forgejo_migrations/v1_22/v4.go
new file mode 100644
index 0000000000..f1195f5f66
--- /dev/null
+++ b/models/forgejo_migrations/v1_22/v4.go
@@ -0,0 +1,17 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_22 //nolint
+
+import (
+	"xorm.io/xorm"
+)
+
+func AddDefaultPermissionsToRepoUnit(x *xorm.Engine) error {
+	type RepoUnit struct {
+		ID                 int64
+		DefaultPermissions int `xorm:"NOT NULL DEFAULT 0"`
+	}
+
+	return x.Sync(&RepoUnit{})
+}
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go
index 395ecdf1a5..0b66e62d7d 100644
--- a/models/perm/access/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -33,6 +33,16 @@ func (p *Permission) IsAdmin() bool {
 	return p.AccessMode >= perm_model.AccessModeAdmin
 }
 
+// IsGloballyWriteable returns true if the unit is writeable by all users of the instance.
+func (p *Permission) IsGloballyWriteable(unitType unit.Type) bool {
+	for _, u := range p.Units {
+		if u.Type == unitType {
+			return u.DefaultPermissions == repo_model.UnitAccessModeWrite
+		}
+	}
+	return false
+}
+
 // HasAccess returns true if the current user has at least read access to any unit of this repository
 func (p *Permission) HasAccess() bool {
 	if p.UnitsMode == nil {
@@ -198,7 +208,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
 	if err := repo.LoadOwner(ctx); err != nil {
 		return perm, err
 	}
+
 	if !repo.Owner.IsOrganization() {
+		// for a public repo, different repo units may have different default
+		// permissions for non-restricted users.
+		if !repo.IsPrivate && !user.IsRestricted && len(repo.Units) > 0 {
+			perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
+			for _, u := range repo.Units {
+				if _, ok := perm.UnitsMode[u.Type]; !ok {
+					perm.UnitsMode[u.Type] = u.DefaultPermissions.ToAccessMode(perm.AccessMode)
+				}
+			}
+		}
+
 		return perm, nil
 	}
 
@@ -239,10 +261,12 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
 			}
 		}
 
-		// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
+		// for a public repo on an organization, a non-restricted user should
+		// have the same permission on non-team defined units as the default
+		// permissions for the repo unit.
 		if !found && !repo.IsPrivate && !user.IsRestricted {
 			if _, ok := perm.UnitsMode[u.Type]; !ok {
-				perm.UnitsMode[u.Type] = perm_model.AccessModeRead
+				perm.UnitsMode[u.Type] = u.DefaultPermissions.ToAccessMode(perm_model.AccessModeRead)
 			}
 		}
 	}
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index 89f28b8fca..3df5236ea7 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/perm"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/setting"
@@ -39,13 +40,43 @@ func (err ErrUnitTypeNotExist) Unwrap() error {
 	return util.ErrNotExist
 }
 
+// RepoUnitAccessMode specifies the users access mode to a repo unit
+type UnitAccessMode int
+
+const (
+	// UnitAccessModeUnset - no unit mode set
+	UnitAccessModeUnset UnitAccessMode = iota // 0
+	// UnitAccessModeNone no access
+	UnitAccessModeNone // 1
+	// UnitAccessModeRead read access
+	UnitAccessModeRead // 2
+	// UnitAccessModeWrite write access
+	UnitAccessModeWrite // 3
+)
+
+func (mode UnitAccessMode) ToAccessMode(modeIfUnset perm.AccessMode) perm.AccessMode {
+	switch mode {
+	case UnitAccessModeUnset:
+		return modeIfUnset
+	case UnitAccessModeNone:
+		return perm.AccessModeNone
+	case UnitAccessModeRead:
+		return perm.AccessModeRead
+	case UnitAccessModeWrite:
+		return perm.AccessModeWrite
+	default:
+		return perm.AccessModeNone
+	}
+}
+
 // RepoUnit describes all units of a repository
 type RepoUnit struct { //revive:disable-line:exported
-	ID          int64
-	RepoID      int64              `xorm:"INDEX(s)"`
-	Type        unit.Type          `xorm:"INDEX(s)"`
-	Config      convert.Conversion `xorm:"TEXT"`
-	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
+	ID                 int64
+	RepoID             int64              `xorm:"INDEX(s)"`
+	Type               unit.Type          `xorm:"INDEX(s)"`
+	Config             convert.Conversion `xorm:"TEXT"`
+	CreatedUnix        timeutil.TimeStamp `xorm:"INDEX CREATED"`
+	DefaultPermissions UnitAccessMode     `xorm:"NOT NULL DEFAULT 0"`
 }
 
 func init() {
diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go
index a760594013..27a34fd0eb 100644
--- a/models/repo/repo_unit_test.go
+++ b/models/repo/repo_unit_test.go
@@ -6,6 +6,8 @@ package repo
 import (
 	"testing"
 
+	"code.gitea.io/gitea/models/perm"
+
 	"github.com/stretchr/testify/assert"
 )
 
@@ -28,3 +30,10 @@ func TestActionsConfig(t *testing.T) {
 	cfg.DisableWorkflow("test3.yaml")
 	assert.EqualValues(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString())
 }
+
+func TestRepoUnitAccessMode(t *testing.T) {
+	assert.Equal(t, UnitAccessModeNone.ToAccessMode(perm.AccessModeAdmin), perm.AccessModeNone)
+	assert.Equal(t, UnitAccessModeRead.ToAccessMode(perm.AccessModeAdmin), perm.AccessModeRead)
+	assert.Equal(t, UnitAccessModeWrite.ToAccessMode(perm.AccessModeAdmin), perm.AccessModeWrite)
+	assert.Equal(t, UnitAccessModeUnset.ToAccessMode(perm.AccessModeRead), perm.AccessModeRead)
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index a5c82bfcf6..40e1a3ecbe 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2036,6 +2036,7 @@ settings.branches.update_default_branch = Update Default Branch
 settings.branches.add_new_rule = Add New Rule
 settings.advanced_settings = Advanced Settings
 settings.wiki_desc = Enable Repository Wiki
+settings.wiki_globally_editable = Allow anyone to edit the Wiki
 settings.use_internal_wiki = Use Built-In Wiki
 settings.use_external_wiki = Use External Wiki
 settings.external_wiki_url = External Wiki URL
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 989ada816f..6d294d1f26 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -473,10 +473,17 @@ func SettingsPost(ctx *context.Context) {
 			})
 			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
 		} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
+			var wikiPermissions repo_model.UnitAccessMode
+			if form.GloballyWriteableWiki {
+				wikiPermissions = repo_model.UnitAccessModeWrite
+			} else {
+				wikiPermissions = repo_model.UnitAccessModeRead
+			}
 			units = append(units, repo_model.RepoUnit{
-				RepoID: repo.ID,
-				Type:   unit_model.TypeWiki,
-				Config: new(repo_model.UnitConfig),
+				RepoID:             repo.ID,
+				Type:               unit_model.TypeWiki,
+				Config:             new(repo_model.UnitConfig),
+				DefaultPermissions: wikiPermissions,
 			})
 			deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
 		} else {
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index ed4d82e1e5..c1dfce0a87 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -140,6 +140,7 @@ type RepoSettingForm struct {
 	// Advanced settings
 	EnableCode                            bool
 	EnableWiki                            bool
+	GloballyWriteableWiki                 bool
 	EnableExternalWiki                    bool
 	ExternalWikiURL                       string
 	EnableIssues                          bool
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 80aec2b252..5c2da5ebbd 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -318,6 +318,16 @@
 							<label>{{ctx.Locale.Tr "repo.settings.use_internal_wiki"}}</label>
 						</div>
 					</div>
+					{{if (not .Repository.IsPrivate)}}
+					<div class="field {{if (.Repository.UnitEnabled $.Context $.UnitTypeExternalWiki)}}disabled{{end}}">
+						<div class="field">
+							<div class="ui checkbox">
+								<input name="globally_writeable_wiki" type="checkbox" {{if .Permission.IsGloballyWriteable $.UnitTypeWiki}}checked{{end}}>
+								<label>{{ctx.Locale.Tr "repo.settings.wiki_globally_editable"}}</label>
+							</div>
+						</div>
+					</div>
+					{{end}}
 					<div class="field">
 						<div class="ui radio checkbox{{if $isExternalWikiGlobalDisabled}} disabled{{end}}"{{if $isExternalWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
 							<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="true" data-target="#external_wiki_box" {{if .Repository.UnitEnabled $.Context $.UnitTypeExternalWiki}}checked{{end}}>
diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go
index 05d90fc4e3..3b5469a1a5 100644
--- a/tests/integration/api_wiki_test.go
+++ b/tests/integration/api_wiki_test.go
@@ -4,12 +4,16 @@
 package integration
 
 import (
+	"context"
 	"encoding/base64"
 	"fmt"
 	"net/http"
 	"testing"
 
 	auth_model "code.gitea.io/gitea/models/auth"
+	repo_model "code.gitea.io/gitea/models/repo"
+	unit_model "code.gitea.io/gitea/models/unit"
+	"code.gitea.io/gitea/models/unittest"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/tests"
 
@@ -209,6 +213,53 @@ func TestAPIEditWikiPage(t *testing.T) {
 	MakeRequest(t, req, http.StatusOK)
 }
 
+func TestAPIEditOtherWikiPage(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	// (drive-by-user) user, session, and token for a drive-by wiki editor
+	username := "drive-by-user"
+	req := NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{
+		"user_name": username,
+		"email":     "drive-by@example.com",
+		"password":  "examplePassword!1",
+		"retype":    "examplePassword!1",
+	})
+	MakeRequest(t, req, http.StatusSeeOther)
+	session := loginUserWithPassword(t, username, "examplePassword!1")
+	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+
+	// (user2) user for the user whose wiki we're going to edit (as drive-by-user)
+	otherUsername := "user2"
+
+	// Creating a new Wiki page on user2's repo as user1 fails
+	testCreateWiki := func(expectedStatusCode int) {
+		urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new?token=%s", otherUsername, "repo1", token)
+		req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{
+			Title:         "Globally Edited Page",
+			ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")),
+			Message:       "",
+		})
+		session.MakeRequest(t, req, expectedStatusCode)
+	}
+	testCreateWiki(http.StatusForbidden)
+
+	// Update the repo settings for user2's repo to enable globally writeable wiki
+	ctx := context.Background()
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	var units []repo_model.RepoUnit
+	units = append(units, repo_model.RepoUnit{
+		RepoID:             repo.ID,
+		Type:               unit_model.TypeWiki,
+		Config:             new(repo_model.UnitConfig),
+		DefaultPermissions: repo_model.UnitAccessModeWrite,
+	})
+	err := repo_model.UpdateRepositoryUnits(ctx, repo, units, nil)
+	assert.NoError(t, err)
+
+	// Creating a new Wiki page on user2's repo works now
+	testCreateWiki(http.StatusCreated)
+}
+
 func TestAPIListPageRevisions(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
 	username := "user2"