diff --git a/Zero-K.info/AppCode/PostLinkExtensions.cs b/Zero-K.info/AppCode/PostLinkExtensions.cs new file mode 100644 index 0000000000..976e2d5b91 --- /dev/null +++ b/Zero-K.info/AppCode/PostLinkExtensions.cs @@ -0,0 +1,69 @@ +using System.Web.Mvc.Html; +using System.Web.Routing; + +namespace System.Web.Mvc +{ + /// + /// Renders a link-looking control that submits via POST and carries an anti-forgery token. + /// Used for actions that change state: a plain <a> leaves them reachable by GET, which means + /// any third-party page can trigger them with the visitor's cookies attached. + /// Pass cssClass "js_confirm" to reuse the site's existing confirmation dialog. + /// + public static class PostLinkExtensions + { + /// Text link that POSTs to an action. + public static MvcHtmlString PostLink(this HtmlHelper html, + string linkText, + string action, + string controller = null, + object routeValues = null, + string cssClass = null, + string nicetitle = null) { + return BuildPostForm(html, HttpUtility.HtmlEncode(linkText ?? ""), action, controller, routeValues, cssClass, nicetitle); + } + + /// Image link that POSTs to an action. imageHeight of 0 omits the attribute. + public static MvcHtmlString PostImageLink(this HtmlHelper html, + string imageSrc, + int imageHeight, + string action, + string controller = null, + object routeValues = null, + string cssClass = null, + string nicetitle = null) { + var img = new TagBuilder("img"); + img.Attributes["src"] = imageSrc; + if (imageHeight > 0) img.Attributes["height"] = imageHeight.ToString(); + img.Attributes["alt"] = ""; + return BuildPostForm(html, img.ToString(TagRenderMode.SelfClosing), action, controller, routeValues, cssClass, nicetitle); + } + + static MvcHtmlString BuildPostForm(HtmlHelper html, + string innerHtml, + string action, + string controller, + object routeValues, + string cssClass, + string nicetitle) { + var urlHelper = new UrlHelper(html.ViewContext.RequestContext); + var values = routeValues == null ? new RouteValueDictionary() : new RouteValueDictionary(routeValues); + var url = controller == null ? urlHelper.Action(action, values) : urlHelper.Action(action, controller, values); + + var form = new TagBuilder("form"); + form.Attributes["method"] = "post"; + form.Attributes["action"] = url; + form.AddCssClass("postlink"); + + var button = new TagBuilder("button"); + button.Attributes["type"] = "submit"; + // site_main.js skips .postlink-button when it buttonifies :submit with jQuery UI + button.AddCssClass("postlink-button"); + if (!string.IsNullOrEmpty(cssClass)) button.AddCssClass(cssClass); + if (!string.IsNullOrEmpty(nicetitle)) button.Attributes["nicetitle"] = nicetitle; + button.InnerHtml = innerHtml; + + form.InnerHtml = html.AntiForgeryToken().ToHtmlString() + button.ToString(TagRenderMode.Normal); + return new MvcHtmlString(form.ToString(TagRenderMode.Normal)); + } + } +} diff --git a/Zero-K.info/Controllers/ContributionsController.cs b/Zero-K.info/Controllers/ContributionsController.cs index a5c8fe96b8..677335b4dc 100644 --- a/Zero-K.info/Controllers/ContributionsController.cs +++ b/Zero-K.info/Controllers/ContributionsController.cs @@ -44,6 +44,7 @@ public ActionResult ThankYou() { /// /// Manually input a contribution /// + [HttpPost] [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult AddContribution(int accountID,int kudos, string item, string currency, double gross, double grossEur, double netEur, string email, string comment, bool isSpring, DateTime date) { diff --git a/Zero-K.info/Controllers/FactionsController.cs b/Zero-K.info/Controllers/FactionsController.cs index b916df5a86..9fb6903ec4 100644 --- a/Zero-K.info/Controllers/FactionsController.cs +++ b/Zero-K.info/Controllers/FactionsController.cs @@ -116,6 +116,9 @@ public ActionResult NewTreaty(int? acceptingFactionID) { /// Delete the specified ? /// If not null or empty, this is a newly proposed treaty /// + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult ModifyTreaty(int factionTreatyID, int? turns, int? acceptingFactionID, @@ -203,6 +206,9 @@ public ActionResult ModifyTreaty(int factionTreatyID, + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult CancelTreaty(int id) { var db = new ZkDataContext(); var treaty = db.FactionTreaties.Single(x => x.FactionTreatyID == id); @@ -218,6 +224,9 @@ public ActionResult CancelTreaty(int id) { return Content("Cannot cancel"); } + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult CounterProposal(int id) { var db = new ZkDataContext(); var treaty = db.FactionTreaties.Single(x => x.FactionTreatyID == id); @@ -292,6 +301,9 @@ public ActionResult AcceptTreaty(int id) { /// /// Set faction secret topic (applied to lobby channel as well) /// + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult SetTopic(int factionID, string secretTopic) { var db = new ZkDataContext(); var fac = db.Factions.Single(x => x.FactionID == factionID); diff --git a/Zero-K.info/Controllers/ForumController.cs b/Zero-K.info/Controllers/ForumController.cs index c034a6de63..28c68dc164 100644 --- a/Zero-K.info/Controllers/ForumController.cs +++ b/Zero-K.info/Controllers/ForumController.cs @@ -538,6 +538,7 @@ public ActionResult Thread(int? id, int? postID) { return View(res); } + [HttpPost] [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult AdminThread(int threadID, int newcat, bool isPinned, bool isLocked) { diff --git a/Zero-K.info/Controllers/LobbyController.cs b/Zero-K.info/Controllers/LobbyController.cs index eebc2fe74e..fd78950bb0 100644 --- a/Zero-K.info/Controllers/LobbyController.cs +++ b/Zero-K.info/Controllers/LobbyController.cs @@ -94,6 +94,8 @@ public async Task AddBlockedHost(string hostname, string comment) } //[ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public async Task RemoveBlockedCompany(int companyID) { @@ -108,6 +110,8 @@ public async Task RemoveBlockedCompany(int companyID) } //[ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public async Task RemoveBlockedHost(int hostID) { diff --git a/Zero-K.info/Controllers/LobbyNewsController.cs b/Zero-K.info/Controllers/LobbyNewsController.cs index 2dd7bd02e1..8acac7ee0a 100644 --- a/Zero-K.info/Controllers/LobbyNewsController.cs +++ b/Zero-K.info/Controllers/LobbyNewsController.cs @@ -28,6 +28,8 @@ public ActionResult Edit(int? id) return View("LobbyNewsEdit", db.LobbyNews.Find(id)); } + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult Delete(int id) { @@ -45,6 +47,7 @@ public ActionResult Delete(int id) /// /// The existing item, if editing /// Also makes or edits a and its starting + [HttpPost] [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] [ValidateInput(false)] diff --git a/Zero-K.info/Controllers/MapBansController.cs b/Zero-K.info/Controllers/MapBansController.cs index 1f37e23e17..fe56ac2879 100644 --- a/Zero-K.info/Controllers/MapBansController.cs +++ b/Zero-K.info/Controllers/MapBansController.cs @@ -46,6 +46,9 @@ public ActionResult Index() /// /// + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult Update(List resources) { if (resources == null) return Content("No input given"); diff --git a/Zero-K.info/Controllers/MapsController.cs b/Zero-K.info/Controllers/MapsController.cs index 0b19a2d424..28fc4ae6b1 100644 --- a/Zero-K.info/Controllers/MapsController.cs +++ b/Zero-K.info/Controllers/MapsController.cs @@ -207,6 +207,7 @@ public JsonResult JsonSearch(string search, /// Brings up the planet image selector page /// /// The ID of the map to assign a planet image to + [Auth(Role = AdminLevel.Moderator)] public ActionResult PlanetImageSelect(int resourceID) { var res = new PlanetImageSelectData(); var db = new ZkDataContext(); @@ -239,6 +240,9 @@ public ActionResult Rate(int id, int rating) { return Content(""); } + [Auth(Role = AdminLevel.Moderator)] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult RemovePlanetIcon(int resourceID) { var db = new ZkDataContext(); var res = db.Resources.Single(x => x.ResourceID == resourceID); @@ -247,6 +251,9 @@ public ActionResult RemovePlanetIcon(int resourceID) { return RedirectToAction("Detail", new { id = res.ResourceID }); } + [Auth(Role = AdminLevel.Moderator)] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult SubmitPlanetIcon(int resourceID, string icon) { var db = new ZkDataContext(); var res = db.Resources.Single(x => x.ResourceID == resourceID); diff --git a/Zero-K.info/Controllers/MissionsController.cs b/Zero-K.info/Controllers/MissionsController.cs index ee4a7ef658..644ac4a27a 100644 --- a/Zero-K.info/Controllers/MissionsController.cs +++ b/Zero-K.info/Controllers/MissionsController.cs @@ -13,6 +13,7 @@ public class MissionsController : Controller // GET: /Missions/ + [HttpPost] [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult ChangeFeaturedOrder(int id, float? featuredOrder, string script) @@ -31,6 +32,8 @@ public ActionResult ChangeFeaturedOrder(int id, float? featuredOrder, string scr } //[ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult Delete(int id) { @@ -153,6 +156,8 @@ public ActionResult Script(int id) } //[ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult Undelete(int id) { diff --git a/Zero-K.info/Controllers/MyController.cs b/Zero-K.info/Controllers/MyController.cs index 71cf2a5f97..05a7d2ab26 100644 --- a/Zero-K.info/Controllers/MyController.cs +++ b/Zero-K.info/Controllers/MyController.cs @@ -252,6 +252,9 @@ public ActionResult Index() /// /// Reset all the user's unlocks /// + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult Reset() { var db = new ZkDataContext(); diff --git a/Zero-K.info/Controllers/NewsController.cs b/Zero-K.info/Controllers/NewsController.cs index dcddc22d48..12c61a37cc 100644 --- a/Zero-K.info/Controllers/NewsController.cs +++ b/Zero-K.info/Controllers/NewsController.cs @@ -34,6 +34,7 @@ public ActionResult Detail(int id) { /// /// The existing item, if editing /// Also makes or edits a and its starting + [HttpPost] [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] [ValidateInput(false)] diff --git a/Zero-K.info/Controllers/PlanetwarsAdminController.cs b/Zero-K.info/Controllers/PlanetwarsAdminController.cs index 20799cb461..46efb701e6 100644 --- a/Zero-K.info/Controllers/PlanetwarsAdminController.cs +++ b/Zero-K.info/Controllers/PlanetwarsAdminController.cs @@ -149,6 +149,8 @@ private static void PurgeGalaxy(int galaxyID, bool unassignFactions, bool resetR } + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult SetDefault(int galaxyid) { var db = new ZkDataContext(); @@ -158,6 +160,8 @@ public ActionResult SetDefault(int galaxyid) return RedirectToAction("Index"); } + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.SuperAdmin)] public ActionResult Delete(int galaxyid) { @@ -168,6 +172,8 @@ public ActionResult Delete(int galaxyid) return RedirectToAction("Index"); } + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult SetPlanetTeamSizes(int galaxyID) { var db = new ZkDataContext(); @@ -189,6 +195,8 @@ public ActionResult SetPlanetTeamSizes(int galaxyID) return RedirectToAction("Index"); } + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult RandomizeMaps(int galaxyID) { using (var db = new ZkDataContext()) @@ -232,6 +240,8 @@ public ActionResult ResetRatings() return RedirectToAction("Index"); } + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult AddWormholes(int galaxyID) { var db = new ZkDataContext(); @@ -246,6 +256,8 @@ public ActionResult AddWormholes(int galaxyID) return RedirectToAction("Index"); } + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult OwnPlanets(int galaxyID) { var db = new ZkDataContext(); @@ -290,6 +302,8 @@ public ActionResult OwnPlanets(int galaxyID) return RedirectToAction("Index"); } + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult StartGalaxy(int galaxyID) { AddWormholes(galaxyID); diff --git a/Zero-K.info/Controllers/PlanetwarsController.cs b/Zero-K.info/Controllers/PlanetwarsController.cs index b2b1b9dcd7..3010f21985 100644 --- a/Zero-K.info/Controllers/PlanetwarsController.cs +++ b/Zero-K.info/Controllers/PlanetwarsController.cs @@ -439,6 +439,8 @@ public ActionResult RunSetPlanetOwners() + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult SubmitRenamePlanet(int planetID, string newName, int teamSize, string map) { @@ -461,6 +463,9 @@ public ActionResult SubmitRenamePlanet(int planetID, string newName, int teamSiz } + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult RecallRole(int accountID, int roletypeID) { var db = new ZkDataContext(); @@ -482,6 +487,9 @@ public ActionResult RecallRole(int accountID, int roletypeID) else return Content("Cannot recall"); } + [Auth] + [HttpPost] + [ValidateAntiForgeryToken] public ActionResult AppointRole(int accountID, int roletypeID) { var db = new ZkDataContext(); diff --git a/Zero-K.info/Controllers/PollController.cs b/Zero-K.info/Controllers/PollController.cs index 33b76afbf9..9624a0a628 100644 --- a/Zero-K.info/Controllers/PollController.cs +++ b/Zero-K.info/Controllers/PollController.cs @@ -21,6 +21,7 @@ public ActionResult Index(int pollID) return null; } + [HttpPost] [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult NewPoll(string question, string answers, bool? isAnonymous) @@ -195,6 +196,8 @@ public ActionResult PollVote(int pollID) } //[ValidateAntiForgeryToken] + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult SwapHeadline(int pollid) { @@ -217,6 +220,8 @@ public ActionResult UserVotes(int? id) return View("PollUserVotes", acc); } + [HttpPost] + [ValidateAntiForgeryToken] [Auth(Role = AdminLevel.Moderator)] public ActionResult SwapVisible(int pollid) { diff --git a/Zero-K.info/Scripts/site_main.js b/Zero-K.info/Scripts/site_main.js index 2e14582fd2..3dca355ffc 100644 --- a/Zero-K.info/Scripts/site_main.js +++ b/Zero-K.info/Scripts/site_main.js @@ -205,8 +205,8 @@ function GlobalPageInit(root) { s.find(".js_datetimepicker").datetimepicker(); // buttonification - s.find(":submit").button(); - s.find(":button").button(); + s.find(":submit").not(".postlink-button").button(); + s.find(":button").not(".postlink-button").button(); s.find(".js_button").button(); s.find(".js_accordion").accordion(); diff --git a/Zero-K.info/Styles/style.css b/Zero-K.info/Styles/style.css index 0219d0988b..678a680083 100644 --- a/Zero-K.info/Styles/style.css +++ b/Zero-K.info/Styles/style.css @@ -640,4 +640,37 @@ div#busy .video-container { max-width: 100%; -} \ No newline at end of file +} + +/* Action links that POST instead of GET so they can carry an anti-forgery token. + Renders