From c161bb013e091c0bcc524f07d50c028d9daf8cce Mon Sep 17 00:00:00 2001
From: John Olheiser <john.olheiser@gmail.com>
Date: Tue, 25 Feb 2020 14:28:47 -0600
Subject: [PATCH] Change action GETs to POST (#10462)

* Change action GETs to POST

* submite = submit + smite

* No more # href

* Fix test

* Match other tests

* Explicit csrf

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
---
 integrations/release_test.go         |  2 +-
 routers/routes/routes.go             | 12 ++++-----
 templates/org/member/members.tmpl    |  4 +--
 templates/org/team/members.tmpl      |  5 +++-
 templates/org/team/repositories.tmpl |  5 +++-
 templates/org/team/sidebar.tmpl      | 10 ++++++--
 templates/org/team/teams.tmpl        | 10 ++++++--
 templates/repo/header.tmpl           | 38 ++++++++++++++++------------
 templates/repo/issue/milestones.tmpl |  4 +--
 templates/user/profile.tmpl          | 10 ++++++--
 web_src/js/index.js                  | 14 ++++++++++
 11 files changed, 79 insertions(+), 35 deletions(-)

diff --git a/integrations/release_test.go b/integrations/release_test.go
index 33badcb0a..be0abd5e3 100644
--- a/integrations/release_test.go
+++ b/integrations/release_test.go
@@ -20,7 +20,7 @@ func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title st
 	resp := session.MakeRequest(t, req, http.StatusOK)
 	htmlDoc := NewHTMLParser(t, resp.Body)
 
-	link, exists := htmlDoc.doc.Find("form").Attr("action")
+	link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action")
 	assert.True(t, exists, "The template has changed")
 
 	postData := map[string]string{
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 525115fb9..69745f6eb 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -502,7 +502,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 	}, reqSignIn)
 
 	m.Group("/:username", func() {
-		m.Get("/action/:action", user.Action)
+		m.Post("/action/:action", user.Action)
 	}, reqSignIn)
 
 	if macaron.Env == macaron.DEV {
@@ -534,7 +534,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 			m.Get("/^:type(issues|pulls)$", user.Issues)
 			m.Get("/milestones", reqMilestonesDashboardPageEnabled, user.Milestones)
 			m.Get("/members", org.Members)
-			m.Get("/members/action/:action", org.MembersAction)
+			m.Post("/members/action/:action", org.MembersAction)
 
 			m.Get("/teams", org.Teams)
 		}, context.OrgAssignment(true))
@@ -542,8 +542,8 @@ func RegisterRoutes(m *macaron.Macaron) {
 		m.Group("/:org", func() {
 			m.Get("/teams/:team", org.TeamMembers)
 			m.Get("/teams/:team/repositories", org.TeamRepositories)
-			m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
-			m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
+			m.Post("/teams/:team/action/:action", org.TeamsAction)
+			m.Post("/teams/:team/action/repo/:action", org.TeamsRepoAction)
 		}, context.OrgAssignment(true, false, true))
 
 		m.Group("/:org", func() {
@@ -681,7 +681,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 		})
 	}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef())
 
-	m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
+	m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
 
 	m.Group("/:username/:reponame", func() {
 		m.Group("/issues", func() {
@@ -735,7 +735,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 				Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
 			m.Get("/:id/edit", repo.EditMilestone)
 			m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
-			m.Get("/:id/:action", repo.ChangeMilestonStatus)
+			m.Post("/:id/:action", repo.ChangeMilestonStatus)
 			m.Post("/delete", repo.DeleteMilestone)
 		}, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
 		m.Group("/milestone", func() {
diff --git a/templates/org/member/members.tmpl b/templates/org/member/members.tmpl
index 81cfcf51e..15af60d57 100644
--- a/templates/org/member/members.tmpl
+++ b/templates/org/member/members.tmpl
@@ -22,10 +22,10 @@
 							{{ $isPublic := index $.MembersIsPublicMember .ID}}
 							{{if $isPublic}}
 								<strong>{{$.i18n.Tr "org.members.public"}}</strong>
-								{{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}}(<a href="{{$.OrgLink}}/members/action/private?uid={{.ID}}">{{$.i18n.Tr "org.members.public_helper"}}</a>){{end}}
+								{{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}}(<a class="link-action" href data-url="{{$.OrgLink}}/members/action/private?uid={{.ID}}">{{$.i18n.Tr "org.members.public_helper"}}</a>){{end}}
 							{{else}}
 								<strong>{{$.i18n.Tr "org.members.private"}}</strong>
-								{{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}}(<a href="{{$.OrgLink}}/members/action/public?uid={{.ID}}">{{$.i18n.Tr "org.members.private_helper"}}</a>){{end}}
+								{{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}}(<a class="link-action" href data-url="{{$.OrgLink}}/members/action/public?uid={{.ID}}">{{$.i18n.Tr "org.members.private_helper"}}</a>){{end}}
 							{{end}}
 						</div>
 					</div>
diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl
index 74e5e1908..f3b08652c 100644
--- a/templates/org/team/members.tmpl
+++ b/templates/org/team/members.tmpl
@@ -27,7 +27,10 @@
 					{{range .Team.Members}}
 						<div class="item">
 							{{if $.IsOrganizationOwner}}
-								<a class="ui red small button right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/remove?uid={{.ID}}">{{$.i18n.Tr "org.members.remove"}}</a>
+								<form method="post" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/remove?uid={{.ID}}">
+									{{$.CsrfTokenHtml}}
+									<button type="submit" class="ui red small button right" >{{$.i18n.Tr "org.members.remove"}}</button>
+								</form>
 							{{end}}
 							<a href="{{.HomeLink}}">
 								<img class="ui avatar image" src="{{.RelAvatarLink}}">
diff --git a/templates/org/team/repositories.tmpl b/templates/org/team/repositories.tmpl
index e81ff889e..d6046f86a 100644
--- a/templates/org/team/repositories.tmpl
+++ b/templates/org/team/repositories.tmpl
@@ -35,7 +35,10 @@
 					{{range .Team.Repos}}
 						<div class="item">
 							{{if $canAddRemove}}
-								<a class="ui red small button right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove?repoid={{.ID}}">{{$.i18n.Tr "remove"}}</a>
+								<form method="post" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove?repoid={{.ID}}">
+									{{$.CsrfTokenHtml}}
+									<button type="submit" class="ui red small button right">{{$.i18n.Tr "remove"}}</button>
+								</form>
 							{{end}}
 							<a class="member" href="{{AppSubUrl}}/{{$.Org.Name}}/{{.Name}}">
 								{{if .IsPrivate}}
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl
index ee612069b..ff2474f00 100644
--- a/templates/org/team/sidebar.tmpl
+++ b/templates/org/team/sidebar.tmpl
@@ -3,9 +3,15 @@
 		<strong>{{.Team.Name}}</strong>
 		<div class="ui right">
 			{{if .Team.IsMember $.SignedUser.ID}}
-				<a class="ui red tiny button" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave?uid={{$.SignedUser.ID}}&page=home">{{$.i18n.Tr "org.teams.leave"}}</a>
+				<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave?uid={{$.SignedUser.ID}}&page=home">
+					{{$.CsrfTokenHtml}}
+					<button type="submit" class="ui red tiny button">{{$.i18n.Tr "org.teams.leave"}}</button>
+				</form>
 			{{else if .IsOrganizationOwner}}
-				<a class="ui blue tiny button" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join?uid={{$.SignedUser.ID}}&page=team">{{$.i18n.Tr "org.teams.join"}}</a>
+				<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join?uid={{$.SignedUser.ID}}&page=team">
+					{{$.CsrfTokenHtml}}
+					<button type="submit" class="ui blue tiny button">{{$.i18n.Tr "org.teams.join"}}</button>
+				</form>
 			{{end}}
 		</div>
 	</h4>
diff --git a/templates/org/team/teams.tmpl b/templates/org/team/teams.tmpl
index 9d4a46902..a042ef611 100644
--- a/templates/org/team/teams.tmpl
+++ b/templates/org/team/teams.tmpl
@@ -17,9 +17,15 @@
 						<a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a>
 						<div class="ui right">
 							{{if .IsMember $.SignedUser.ID}}
-								<a class="ui red small button" href="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave?uid={{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.leave"}}</a>
+								<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave?uid={{$.SignedUser.ID}}">
+									{{$.CsrfTokenHtml}}
+									<button type="submit" class="ui red small button">{{$.i18n.Tr "org.teams.leave"}}</button>
+								</form>
 							{{else if $.IsOrganizationOwner}}
-								<a class="ui blue small button" href="{{$.OrgLink}}/teams/{{.LowerName}}/action/join?uid={{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.join"}}</a>
+								<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/join?uid={{$.SignedUser.ID}}">
+									{{$.CsrfTokenHtml}}
+									<button type="submit" class="ui blue small button">{{$.i18n.Tr "org.teams.join"}}</button>
+								</form>
 							{{end}}
 						</div>
 					</div>
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index c92feb5a7..1fc298bcb 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -51,22 +51,28 @@
 			</div>
 			{{if not .IsBeingCreated}}
 				<div class="repo-buttons">
-					<div class="ui labeled button" tabindex="0">
-						<a class="ui compact basic button" href="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch?redirect_to={{$.Link}}">
-							<i class="icon fa-eye{{if not $.IsWatchingRepo}}-slash{{end}}"></i>{{if $.IsWatchingRepo}}{{$.i18n.Tr "repo.unwatch"}}{{else}}{{$.i18n.Tr "repo.watch"}}{{end}}
-						</a>
-						<a class="ui basic label" href="{{.Link}}/watchers">
-							{{.NumWatches}}
-						</a>
-					</div>
-					<div class="ui labeled button" tabindex="0">
-						<a class="ui compact basic button" href="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
-							<i class="icon star{{if not $.IsStaringRepo}} outline{{end}}"></i>{{if $.IsStaringRepo}}{{$.i18n.Tr "repo.unstar"}}{{else}}{{$.i18n.Tr "repo.star"}}{{end}}
-						</a>
-						<a class="ui basic label" href="{{.Link}}/stars">
-							{{.NumStars}}
-						</a>
-					</div>
+					<form method="post" action="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch?redirect_to={{$.Link}}">
+						{{$.CsrfTokenHtml}}
+						<div class="ui labeled button" tabindex="0">
+							<button type="submit" class="ui compact basic button">
+								<i class="icon fa-eye{{if not $.IsWatchingRepo}}-slash{{end}}"></i>{{if $.IsWatchingRepo}}{{$.i18n.Tr "repo.unwatch"}}{{else}}{{$.i18n.Tr "repo.watch"}}{{end}}
+							</button>
+							<a class="ui basic label" href="{{.Link}}/watchers">
+								{{.NumWatches}}
+							</a>
+						</div>
+					</form>
+					<form method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
+						{{$.CsrfTokenHtml}}
+						<div class="ui labeled button" tabindex="0">
+							<button type="submit" class="ui compact basic button">
+								<i class="icon star{{if not $.IsStaringRepo}} outline{{end}}"></i>{{if $.IsStaringRepo}}{{$.i18n.Tr "repo.unstar"}}{{else}}{{$.i18n.Tr "repo.star"}}{{end}}
+							</button>
+							<a class="ui basic label" href="{{.Link}}/stars">
+								{{.NumStars}}
+							</a>
+						</div>
+					</form>
 					{{if and (not .IsEmpty) ($.Permission.CanRead $.UnitTypeCode)}}
 						<div class="ui labeled button {{if and ($.IsSigned) (not $.CanSignedUserFork)}}disabled-repo-button{{end}}" tabindex="0">
 							<a class="ui compact basic button {{if or (not $.IsSigned) (not $.CanSignedUserFork)}}poping up{{end}}" {{if $.CanSignedUserFork}}href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else if $.IsSigned}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" {{ else }} data-content="{{$.i18n.Tr "repo.fork_guest_user" }}" rel="nofollow" href="{{AppSubUrl}}/user/login?redirect_to={{AppSubUrl}}/repo/fork/{{.ID}}" {{end}} data-position="top center" data-variation="tiny">
diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl
index e33124e66..bee1cee65 100644
--- a/templates/repo/issue/milestones.tmpl
+++ b/templates/repo/issue/milestones.tmpl
@@ -71,9 +71,9 @@
 						<div class="ui right operate">
 							<a href="{{$.Link}}/{{.ID}}/edit" data-id={{.ID}} data-title={{.Name}}>{{svg "octicon-pencil" 16}} {{$.i18n.Tr "repo.issues.label_edit"}}</a>
 							{{if .IsClosed}}
-								<a href="{{$.Link}}/{{.ID}}/open" data-id={{.ID}} data-title={{.Name}}>{{svg "octicon-check" 16}} {{$.i18n.Tr "repo.milestones.open"}}</a>
+								<a class="link-action" href data-url="{{$.Link}}/{{.ID}}/open">{{svg "octicon-check" 16}} {{$.i18n.Tr "repo.milestones.open"}}</a>
 							{{else}}
-								<a href="{{$.Link}}/{{.ID}}/close" data-id={{.ID}} data-title={{.Name}}>{{svg "octicon-x" 16}} {{$.i18n.Tr "repo.milestones.close"}}</a>
+								<a class="link-action" href data-url="{{$.Link}}/{{.ID}}/close">{{svg "octicon-x" 16}} {{$.i18n.Tr "repo.milestones.close"}}</a>
 							{{end}}
 							<a class="delete-button" href="#" data-url="{{$.RepoLink}}/milestones/delete" data-id="{{.ID}}">{{svg "octicon-trashcan" 16}} {{$.i18n.Tr "repo.issues.label_delete"}}</a>
 						</div>
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl
index 945cc90f0..f3cac7bef 100644
--- a/templates/user/profile.tmpl
+++ b/templates/user/profile.tmpl
@@ -65,9 +65,15 @@
 							{{if and .IsSigned (ne .SignedUserName .Owner.Name)}}
 							<li class="follow">
 								{{if .SignedUser.IsFollowing .Owner.ID}}
-								<a class="ui basic red button" href="{{.Link}}/action/unfollow?redirect_to={{$.Link}}">{{svg "octicon-person" 16}} {{.i18n.Tr "user.unfollow"}}</a>
+									<form method="post" action="{{.Link}}/action/unfollow?redirect_to={{$.Link}}">
+										{{$.CsrfTokenHtml}}
+										<button type="submit" class="ui basic red button">{{svg "octicon-person" 16}} {{.i18n.Tr "user.unfollow"}}</button>
+									</form>
 								{{else}}
-								<a class="ui basic green button" href="{{.Link}}/action/follow?redirect_to={{$.Link}}">{{svg "octicon-person" 16}} {{.i18n.Tr "user.follow"}}</a>
+									<form method="post" action="{{.Link}}/action/follow?redirect_to={{$.Link}}">
+										{{$.CsrfTokenHtml}}
+										<button type="submit" class="ui basic green button">{{svg "octicon-person" 16}} {{.i18n.Tr "user.follow"}}</button>
+									</form>
 								{{end}}
 							</li>
 							{{end}}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index d4f8bb40e..ec39a1c40 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -2469,6 +2469,7 @@ $(document).ready(async () => {
   // Helpers.
   $('.delete-button').click(showDeletePopup);
   $('.add-all-button').click(showAddAllPopup);
+  $('.link-action').click(linkAction);
 
   $('.delete-branch-button').click(showDeletePopup);
 
@@ -2735,6 +2736,19 @@ function showAddAllPopup() {
   return false;
 }
 
+function linkAction() {
+  const $this = $(this);
+  $.post($this.data('url'), {
+    _csrf: csrf
+  }).done((data) => {
+    if (data.redirect) {
+      window.location.href = data.redirect;
+    } else {
+      window.location.reload();
+    }
+  });
+}
+
 function initVueComponents() {
   const vueDelimeters = ['${', '}'];