Browse Source

Do not allow commiting to protected branch from online editor (#1502)

* Do not allow commiting to protected branch from online editor

* Add editor integration tests for adding new file and not allowing to add new file to protected branch
Lauris BH 3 years ago
parent
commit
0144817971

+ 3 - 3
Makefile

@@ -86,15 +86,15 @@ test-vendor:
86 86
 
87 87
 .PHONY: test-sqlite
88 88
 test-sqlite: integrations.test
89
-	GITEA_CONF=integrations/sqlite.ini ./integrations.test
89
+	GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/sqlite.ini ./integrations.test
90 90
 
91 91
 .PHONY: test-mysql
92 92
 test-mysql: integrations.test
93
-	GITEA_CONF=integrations/mysql.ini ./integrations.test
93
+	GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/mysql.ini ./integrations.test
94 94
 
95 95
 .PHONY: test-pgsql
96 96
 test-pgsql: integrations.test
97
-	GITEA_CONF=integrations/pgsql.ini ./integrations.test
97
+	GITEA_ROOT=${CURDIR} GITEA_CONF=integrations/pgsql.ini ./integrations.test
98 98
 
99 99
 integrations.test: $(SOURCES)
100 100
 	go test -c code.gitea.io/gitea/integrations -tags 'sqlite'

+ 106 - 0
integrations/editor_test.go

@@ -0,0 +1,106 @@
1
+// Copyright 2017 The Gitea Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package integrations
6
+
7
+import (
8
+	"bytes"
9
+	"net/http"
10
+	"net/url"
11
+	"testing"
12
+
13
+	"github.com/stretchr/testify/assert"
14
+)
15
+
16
+func TestCreateFile(t *testing.T) {
17
+	prepareTestEnv(t)
18
+
19
+	session := loginUser(t, "user2", "password")
20
+
21
+	// Request editor page
22
+	req, err := http.NewRequest("GET", "/user2/repo1/_new/master/", nil)
23
+	assert.NoError(t, err)
24
+	resp := session.MakeRequest(t, req)
25
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
26
+
27
+	doc, err := NewHtmlParser(resp.Body)
28
+	assert.NoError(t, err)
29
+	lastCommit := doc.GetInputValueByName("last_commit")
30
+	assert.NotEmpty(t, lastCommit)
31
+
32
+	// Save new file to master branch
33
+	req, err = http.NewRequest("POST", "/user2/repo1/_new/master/",
34
+		bytes.NewBufferString(url.Values{
35
+			"_csrf":         []string{doc.GetInputValueByName("_csrf")},
36
+			"last_commit":   []string{lastCommit},
37
+			"tree_path":     []string{"test.txt"},
38
+			"content":       []string{"Content"},
39
+			"commit_choice": []string{"direct"},
40
+		}.Encode()),
41
+	)
42
+	assert.NoError(t, err)
43
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
44
+	resp = session.MakeRequest(t, req)
45
+	assert.EqualValues(t, http.StatusFound, resp.HeaderCode)
46
+}
47
+
48
+func TestCreateFileOnProtectedBranch(t *testing.T) {
49
+	prepareTestEnv(t)
50
+
51
+	session := loginUser(t, "user2", "password")
52
+
53
+	// Open repository branch settings
54
+	req, err := http.NewRequest("GET", "/user2/repo1/settings/branches", nil)
55
+	assert.NoError(t, err)
56
+	resp := session.MakeRequest(t, req)
57
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
58
+
59
+	doc, err := NewHtmlParser(resp.Body)
60
+	assert.NoError(t, err)
61
+
62
+	// Change master branch to protected
63
+	req, err = http.NewRequest("POST", "/user2/repo1/settings/branches?action=protected_branch",
64
+		bytes.NewBufferString(url.Values{
65
+			"_csrf":      []string{doc.GetInputValueByName("_csrf")},
66
+			"branchName": []string{"master"},
67
+			"canPush":    []string{"true"},
68
+		}.Encode()),
69
+	)
70
+	assert.NoError(t, err)
71
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
72
+	resp = session.MakeRequest(t, req)
73
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
74
+	// Check if master branch has been locked successfully
75
+	flashCookie := session.GetCookie("macaron_flash")
76
+	assert.NotNil(t, flashCookie)
77
+	assert.EqualValues(t, flashCookie.Value, "success%3Dmaster%2BLocked%2Bsuccessfully")
78
+
79
+	// Request editor page
80
+	req, err = http.NewRequest("GET", "/user2/repo1/_new/master/", nil)
81
+	assert.NoError(t, err)
82
+	resp = session.MakeRequest(t, req)
83
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
84
+
85
+	doc, err = NewHtmlParser(resp.Body)
86
+	assert.NoError(t, err)
87
+	lastCommit := doc.GetInputValueByName("last_commit")
88
+	assert.NotEmpty(t, lastCommit)
89
+
90
+	// Save new file to master branch
91
+	req, err = http.NewRequest("POST", "/user2/repo1/_new/master/",
92
+		bytes.NewBufferString(url.Values{
93
+			"_csrf":         []string{doc.GetInputValueByName("_csrf")},
94
+			"last_commit":   []string{lastCommit},
95
+			"tree_path":     []string{"test.txt"},
96
+			"content":       []string{"Content"},
97
+			"commit_choice": []string{"direct"},
98
+		}.Encode()),
99
+	)
100
+	assert.NoError(t, err)
101
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
102
+	resp = session.MakeRequest(t, req)
103
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
104
+	// Check body for error message
105
+	assert.Contains(t, string(resp.Body), "Can not commit to protected branch 'master'.")
106
+}

+ 110 - 0
integrations/html_helper.go

@@ -0,0 +1,110 @@
1
+// Copyright 2017 The Gitea Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package integrations
6
+
7
+import (
8
+	"bytes"
9
+
10
+	"golang.org/x/net/html"
11
+)
12
+
13
+type HtmlDoc struct {
14
+	doc  *html.Node
15
+	body *html.Node
16
+}
17
+
18
+func NewHtmlParser(content []byte) (*HtmlDoc, error) {
19
+	doc, err := html.Parse(bytes.NewReader(content))
20
+	if err != nil {
21
+		return nil, err
22
+	}
23
+
24
+	return &HtmlDoc{doc: doc}, nil
25
+}
26
+
27
+func (doc *HtmlDoc) GetBody() *html.Node {
28
+	if doc.body == nil {
29
+		var b *html.Node
30
+		var f func(*html.Node)
31
+		f = func(n *html.Node) {
32
+			if n.Type == html.ElementNode && n.Data == "body" {
33
+				b = n
34
+				return
35
+			}
36
+			for c := n.FirstChild; c != nil; c = c.NextSibling {
37
+				f(c)
38
+			}
39
+		}
40
+		f(doc.doc)
41
+		if b != nil {
42
+			doc.body = b
43
+		} else {
44
+			doc.body = doc.doc
45
+		}
46
+	}
47
+	return doc.body
48
+}
49
+
50
+func (doc *HtmlDoc) GetAttribute(n *html.Node, key string) (string, bool) {
51
+	for _, attr := range n.Attr {
52
+		if attr.Key == key {
53
+			return attr.Val, true
54
+		}
55
+	}
56
+	return "", false
57
+}
58
+
59
+func (doc *HtmlDoc) checkAttr(n *html.Node, attr, val string) bool {
60
+	if n.Type == html.ElementNode {
61
+		s, ok := doc.GetAttribute(n, attr)
62
+		if ok && s == val {
63
+			return true
64
+		}
65
+	}
66
+	return false
67
+}
68
+
69
+func (doc *HtmlDoc) traverse(n *html.Node, attr, val string) *html.Node {
70
+	if doc.checkAttr(n, attr, val) {
71
+		return n
72
+	}
73
+
74
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
75
+		result := doc.traverse(c, attr, val)
76
+		if result != nil {
77
+			return result
78
+		}
79
+	}
80
+
81
+	return nil
82
+}
83
+
84
+func (doc *HtmlDoc) GetElementById(id string) *html.Node {
85
+	return doc.traverse(doc.GetBody(), "id", id)
86
+}
87
+
88
+func (doc *HtmlDoc) GetInputValueById(id string) string {
89
+	inp := doc.GetElementById(id)
90
+	if inp == nil {
91
+		return ""
92
+	}
93
+
94
+	val, _ := doc.GetAttribute(inp, "value")
95
+	return val
96
+}
97
+
98
+func (doc *HtmlDoc) GetElementByName(name string) *html.Node {
99
+	return doc.traverse(doc.GetBody(), "name", name)
100
+}
101
+
102
+func (doc *HtmlDoc) GetInputValueByName(name string) string {
103
+	inp := doc.GetElementByName(name)
104
+	if inp == nil {
105
+		return ""
106
+	}
107
+
108
+	val, _ := doc.GetAttribute(inp, "value")
109
+	return val
110
+}

+ 81 - 2
integrations/integration_test.go

@@ -11,7 +11,10 @@ import (
11 11
 	"io"
12 12
 	"log"
13 13
 	"net/http"
14
+	"net/http/cookiejar"
15
+	"net/url"
14 16
 	"os"
17
+	"strings"
15 18
 	"testing"
16 19
 
17 20
 	"code.gitea.io/gitea/models"
@@ -60,6 +63,10 @@ func initIntegrationTest() {
60 63
 		fmt.Println("Environment variable $GITEA_CONF not set")
61 64
 		os.Exit(1)
62 65
 	}
66
+	if os.Getenv("GITEA_ROOT") == "" {
67
+		fmt.Println("Environment variable $GITEA_ROOT not set")
68
+		os.Exit(1)
69
+	}
63 70
 
64 71
 	setting.NewContext()
65 72
 	models.LoadConfigs()
@@ -103,13 +110,82 @@ func prepareTestEnv(t *testing.T) {
103 110
 	assert.NoError(t, com.CopyDir("integrations/gitea-integration-meta", "integrations/gitea-integration"))
104 111
 }
105 112
 
113
+type TestSession struct {
114
+	jar http.CookieJar
115
+}
116
+
117
+func (s *TestSession) GetCookie(name string) *http.Cookie {
118
+	baseURL, err := url.Parse(setting.AppURL)
119
+	if err != nil {
120
+		return nil
121
+	}
122
+
123
+	for _, c := range s.jar.Cookies(baseURL) {
124
+		if c.Name == name {
125
+			return c
126
+		}
127
+	}
128
+	return nil
129
+}
130
+
131
+func (s *TestSession) MakeRequest(t *testing.T, req *http.Request) *TestResponse {
132
+	baseURL, err := url.Parse(setting.AppURL)
133
+	assert.NoError(t, err)
134
+	for _, c := range s.jar.Cookies(baseURL) {
135
+		req.AddCookie(c)
136
+	}
137
+	resp := MakeRequest(req)
138
+
139
+	ch := http.Header{}
140
+	ch.Add("Cookie", strings.Join(resp.Headers["Set-Cookie"], ";"))
141
+	cr := http.Request{Header: ch}
142
+	s.jar.SetCookies(baseURL, cr.Cookies())
143
+
144
+	return resp
145
+}
146
+
147
+func loginUser(t *testing.T, userName, password string) *TestSession {
148
+	req, err := http.NewRequest("GET", "/user/login", nil)
149
+	assert.NoError(t, err)
150
+	resp := MakeRequest(req)
151
+	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
152
+
153
+	doc, err := NewHtmlParser(resp.Body)
154
+	assert.NoError(t, err)
155
+
156
+	req, err = http.NewRequest("POST", "/user/login",
157
+		bytes.NewBufferString(url.Values{
158
+			"_csrf":     []string{doc.GetInputValueByName("_csrf")},
159
+			"user_name": []string{userName},
160
+			"password":  []string{password},
161
+		}.Encode()),
162
+	)
163
+	assert.NoError(t, err)
164
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
165
+	resp = MakeRequest(req)
166
+	assert.EqualValues(t, http.StatusFound, resp.HeaderCode)
167
+
168
+	ch := http.Header{}
169
+	ch.Add("Cookie", strings.Join(resp.Headers["Set-Cookie"], ";"))
170
+	cr := http.Request{Header: ch}
171
+
172
+	jar, err := cookiejar.New(nil)
173
+	assert.NoError(t, err)
174
+	baseURL, err := url.Parse(setting.AppURL)
175
+	assert.NoError(t, err)
176
+	jar.SetCookies(baseURL, cr.Cookies())
177
+
178
+	return &TestSession{jar: jar}
179
+}
180
+
106 181
 type TestResponseWriter struct {
107 182
 	HeaderCode int
108 183
 	Writer     io.Writer
184
+	Headers    http.Header
109 185
 }
110 186
 
111 187
 func (w *TestResponseWriter) Header() http.Header {
112
-	return make(map[string][]string)
188
+	return w.Headers
113 189
 }
114 190
 
115 191
 func (w *TestResponseWriter) Write(b []byte) (int, error) {
@@ -123,16 +199,19 @@ func (w *TestResponseWriter) WriteHeader(n int) {
123 199
 type TestResponse struct {
124 200
 	HeaderCode int
125 201
 	Body       []byte
202
+	Headers    http.Header
126 203
 }
127 204
 
128 205
 func MakeRequest(req *http.Request) *TestResponse {
129 206
 	buffer := bytes.NewBuffer(nil)
130 207
 	respWriter := &TestResponseWriter{
131
-		Writer: buffer,
208
+		Writer:  buffer,
209
+		Headers: make(map[string][]string),
132 210
 	}
133 211
 	mac.ServeHTTP(respWriter, req)
134 212
 	return &TestResponse{
135 213
 		HeaderCode: respWriter.HeaderCode,
136 214
 		Body:       buffer.Bytes(),
215
+		Headers:    respWriter.Headers,
137 216
 	}
138 217
 }

+ 17 - 0
models/branches.go

@@ -63,6 +63,23 @@ func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
63 63
 	return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})
64 64
 }
65 65
 
66
+// IsProtectedBranch checks if branch is protected
67
+func (repo *Repository) IsProtectedBranch(branchName string) (bool, error) {
68
+	protectedBranch := &ProtectedBranch{
69
+		RepoID:     repo.ID,
70
+		BranchName: branchName,
71
+	}
72
+
73
+	has, err := x.Get(protectedBranch)
74
+	if err != nil {
75
+		return true, err
76
+	} else if has {
77
+		return true, nil
78
+	}
79
+
80
+	return false, nil
81
+}
82
+
66 83
 // AddProtectedBranch add protection to branch
67 84
 func (repo *Repository) AddProtectedBranch(branchName string, canPush bool) error {
68 85
 	protectedBranch := &ProtectedBranch{

+ 3 - 2
models/fixtures/user.yml

@@ -18,15 +18,16 @@
18 18
   name: user2
19 19
   full_name: User Two
20 20
   email: user2@example.com
21
-  passwd: password
21
+  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
22 22
   type: 0 # individual
23
-  salt: salt
23
+  salt: ZogKvWdyEx
24 24
   is_admin: false
25 25
   avatar: avatar2
26 26
   avatar_email: user2@example.com
27 27
   num_repos: 2
28 28
   num_stars: 2
29 29
   num_followers: 1
30
+  is_active: true
30 31
 
31 32
 -
32 33
   id: 3

+ 10 - 0
modules/context/repo.go

@@ -75,6 +75,16 @@ func (r *Repository) CanEnableEditor() bool {
75 75
 	return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter()
76 76
 }
77 77
 
78
+// CanCommitToBranch returns true if repository is editable and user has proper access level
79
+//   and branch is not protected
80
+func (r *Repository) CanCommitToBranch() (bool, error) {
81
+	protectedBranch, err := r.Repository.IsProtectedBranch(r.BranchName)
82
+	if err != nil {
83
+		return false, err
84
+	}
85
+	return r.CanEnableEditor() && !protectedBranch, nil
86
+}
87
+
78 88
 // GetEditorconfig returns the .editorconfig definition if found in the
79 89
 // HEAD of the default repo branch.
80 90
 func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {

+ 1 - 0
options/locale/locale_en-US.ini

@@ -560,6 +560,7 @@ editor.fail_to_update_file = Failed to update/create file '%s' with error: %v
560 560
 editor.add_subdir = Add subdirectory...
561 561
 editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v
562 562
 editor.upload_files_to_dir = Upload files to '%s'
563
+editor.cannot_commit_to_protected_branch = Can not commit to protected branch '%s'.
563 564
 
564 565
 commits.commits = Commits
565 566
 commits.search = Search commits

+ 53 - 6
routers/repo/editor.go

@@ -26,13 +26,26 @@ const (
26 26
 	tplEditDiffPreview base.TplName = "repo/editor/diff_preview"
27 27
 	tplDeleteFile      base.TplName = "repo/editor/delete"
28 28
 	tplUploadFile      base.TplName = "repo/editor/upload"
29
+
30
+	frmCommitChoiceDirect    string = "direct"
31
+	frmCommitChoiceNewBranch string = "commit-to-new-branch"
29 32
 )
30 33
 
34
+func renderCommitRights(ctx *context.Context) bool {
35
+	canCommit, err := ctx.Repo.CanCommitToBranch()
36
+	if err != nil {
37
+		log.Error(4, "CanCommitToBranch: %v", err)
38
+	}
39
+	ctx.Data["CanCommitToBranch"] = canCommit
40
+	return canCommit
41
+}
42
+
31 43
 func editFile(ctx *context.Context, isNewFile bool) {
32 44
 	ctx.Data["PageIsEdit"] = true
33 45
 	ctx.Data["IsNewFile"] = isNewFile
34 46
 	ctx.Data["RequireHighlightJS"] = true
35 47
 	ctx.Data["RequireSimpleMDE"] = true
48
+	canCommit := renderCommitRights(ctx)
36 49
 
37 50
 	var treeNames []string
38 51
 	if len(ctx.Repo.TreePath) > 0 {
@@ -90,7 +103,11 @@ func editFile(ctx *context.Context, isNewFile bool) {
90 103
 	ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName
91 104
 	ctx.Data["commit_summary"] = ""
92 105
 	ctx.Data["commit_message"] = ""
93
-	ctx.Data["commit_choice"] = "direct"
106
+	if canCommit {
107
+		ctx.Data["commit_choice"] = frmCommitChoiceDirect
108
+	} else {
109
+		ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
110
+	}
94 111
 	ctx.Data["new_branch_name"] = ""
95 112
 	ctx.Data["last_commit"] = ctx.Repo.Commit.ID
96 113
 	ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
@@ -116,6 +133,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
116 133
 	ctx.Data["IsNewFile"] = isNewFile
117 134
 	ctx.Data["RequireHighlightJS"] = true
118 135
 	ctx.Data["RequireSimpleMDE"] = true
136
+	canCommit := renderCommitRights(ctx)
119 137
 
120 138
 	oldBranchName := ctx.Repo.BranchName
121 139
 	branchName := oldBranchName
@@ -123,7 +141,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
123 141
 	lastCommit := form.LastCommit
124 142
 	form.LastCommit = ctx.Repo.Commit.ID.String()
125 143
 
126
-	if form.CommitChoice == "commit-to-new-branch" {
144
+	if form.CommitChoice == frmCommitChoiceNewBranch {
127 145
 		branchName = form.NewBranchName
128 146
 	}
129 147
 
@@ -164,6 +182,11 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
164 182
 			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplEditFile, &form)
165 183
 			return
166 184
 		}
185
+	} else if !canCommit {
186
+		ctx.Data["Err_NewBranchName"] = true
187
+		ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
188
+		ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplEditFile, &form)
189
+		return
167 190
 	}
168 191
 
169 192
 	var newTreePath string
@@ -317,10 +340,17 @@ func DeleteFile(ctx *context.Context) {
317 340
 	ctx.Data["PageIsDelete"] = true
318 341
 	ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName
319 342
 	ctx.Data["TreePath"] = ctx.Repo.TreePath
343
+	canCommit := renderCommitRights(ctx)
344
+
320 345
 	ctx.Data["commit_summary"] = ""
321 346
 	ctx.Data["commit_message"] = ""
322
-	ctx.Data["commit_choice"] = "direct"
347
+	if canCommit {
348
+		ctx.Data["commit_choice"] = frmCommitChoiceDirect
349
+	} else {
350
+		ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
351
+	}
323 352
 	ctx.Data["new_branch_name"] = ""
353
+
324 354
 	ctx.HTML(200, tplDeleteFile)
325 355
 }
326 356
 
@@ -329,11 +359,12 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
329 359
 	ctx.Data["PageIsDelete"] = true
330 360
 	ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName
331 361
 	ctx.Data["TreePath"] = ctx.Repo.TreePath
362
+	canCommit := renderCommitRights(ctx)
332 363
 
333 364
 	oldBranchName := ctx.Repo.BranchName
334 365
 	branchName := oldBranchName
335 366
 
336
-	if form.CommitChoice == "commit-to-new-branch" {
367
+	if form.CommitChoice == frmCommitChoiceNewBranch {
337 368
 		branchName = form.NewBranchName
338 369
 	}
339 370
 	ctx.Data["commit_summary"] = form.CommitSummary
@@ -352,6 +383,11 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
352 383
 			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplDeleteFile, &form)
353 384
 			return
354 385
 		}
386
+	} else if !canCommit {
387
+		ctx.Data["Err_NewBranchName"] = true
388
+		ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
389
+		ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplDeleteFile, &form)
390
+		return
355 391
 	}
356 392
 
357 393
 	message := strings.TrimSpace(form.CommitSummary)
@@ -390,6 +426,7 @@ func renderUploadSettings(ctx *context.Context) {
390 426
 func UploadFile(ctx *context.Context) {
391 427
 	ctx.Data["PageIsUpload"] = true
392 428
 	renderUploadSettings(ctx)
429
+	canCommit := renderCommitRights(ctx)
393 430
 
394 431
 	// We must at least have one element for user to input.
395 432
 	treeNames := []string{""}
@@ -401,7 +438,11 @@ func UploadFile(ctx *context.Context) {
401 438
 	ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName
402 439
 	ctx.Data["commit_summary"] = ""
403 440
 	ctx.Data["commit_message"] = ""
404
-	ctx.Data["commit_choice"] = "direct"
441
+	if canCommit {
442
+		ctx.Data["commit_choice"] = frmCommitChoiceDirect
443
+	} else {
444
+		ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
445
+	}
405 446
 	ctx.Data["new_branch_name"] = ""
406 447
 
407 448
 	ctx.HTML(200, tplUploadFile)
@@ -411,11 +452,12 @@ func UploadFile(ctx *context.Context) {
411 452
 func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
412 453
 	ctx.Data["PageIsUpload"] = true
413 454
 	renderUploadSettings(ctx)
455
+	canCommit := renderCommitRights(ctx)
414 456
 
415 457
 	oldBranchName := ctx.Repo.BranchName
416 458
 	branchName := oldBranchName
417 459
 
418
-	if form.CommitChoice == "commit-to-new-branch" {
460
+	if form.CommitChoice == frmCommitChoiceNewBranch {
419 461
 		branchName = form.NewBranchName
420 462
 	}
421 463
 
@@ -446,6 +488,11 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
446 488
 			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplUploadFile, &form)
447 489
 			return
448 490
 		}
491
+	} else if !canCommit {
492
+		ctx.Data["Err_NewBranchName"] = true
493
+		ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
494
+		ctx.RenderWithErr(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName), tplUploadFile, &form)
495
+		return
449 496
 	}
450 497
 
451 498
 	var newTreePath string

+ 2 - 2
templates/repo/editor/commit_form.tmpl

@@ -10,7 +10,7 @@
10 10
 		</div>
11 11
 		<div class="quick-pull-choice js-quick-pull-choice">
12 12
 			<div class="field">
13
-		 		<div class="ui radio checkbox">
13
+		 		<div class="ui radio checkbox {{if not .CanCommitToBranch}}disabled{{end}}">
14 14
 					<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" {{if eq .commit_choice "direct"}}checked{{end}}>
15 15
 					<label>
16 16
 						<i class="octicon octicon-git-commit" height="16" width="14"></i>
@@ -40,4 +40,4 @@
40 40
 		{{.i18n.Tr "repo.editor.commit_changes"}}
41 41
 	</button>
42 42
 	<a class="ui button red" href="{{EscapePound $.BranchLink}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.editor.cancel"}}</a>
43
-</div>
43
+</div>