From 5fa937620f845dfb1fc65c73f14d1adbeff593e2 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sun, 22 Jul 2012 19:37:38 +0200 Subject: [PATCH] implement forum IP ban --- app/controllers/ForumPost.scala | 2 +- app/controllers/ForumTopic.scala | 2 +- app/controllers/LilaController.scala | 2 ++ app/controllers/Mod.scala | 7 ++++++- app/forum/Post.scala | 3 +++ app/forum/PostApi.scala | 7 ++++--- app/forum/TopicApi.scala | 7 ++++--- app/http/Context.scala | 2 ++ app/mod/ModApi.scala | 11 ++++++++--- app/mod/Modlog.scala | 4 ++++ app/mod/ModlogApi.scala | 6 +++++- app/templating/AssetHelper.scala | 2 +- app/views/forum/topic/show.scala.html | 5 +++++ app/views/mod/log.scala.html | 1 + conf/routes | 1 + public/javascripts/ctrl.js | 9 +++++++++ public/stylesheets/forum.css | 3 ++- todo | 3 --- 18 files changed, 59 insertions(+), 18 deletions(-) diff --git a/app/controllers/ForumPost.scala b/app/controllers/ForumPost.scala index c5cf013f6e..4de4c3e786 100644 --- a/app/controllers/ForumPost.scala +++ b/app/controllers/ForumPost.scala @@ -24,7 +24,7 @@ object ForumPost extends LilaController with forum.Controller { err ⇒ BadRequest(html.forum.topic.show( categ, topic, posts, Some(err -> forms.captchaCreate))), data ⇒ Firewall { - val post = postApi.makePost(categ, topic, data, ctx.me).unsafePerformIO + val post = postApi.makePost(categ, topic, data).unsafePerformIO Redirect("%s#%d".format( routes.ForumTopic.show( categ.slug, diff --git a/app/controllers/ForumTopic.scala b/app/controllers/ForumTopic.scala index dfb0b988ba..9110d675d5 100644 --- a/app/controllers/ForumTopic.scala +++ b/app/controllers/ForumTopic.scala @@ -27,7 +27,7 @@ object ForumTopic extends LilaController with forum.Controller { forms.topic.bindFromRequest.fold( err ⇒ BadRequest(html.forum.topic.form(categ, err, forms.captchaCreate)), data ⇒ Firewall { - val topic = topicApi.makeTopic(categ, data, ctx.me).unsafePerformIO + val topic = topicApi.makeTopic(categ, data).unsafePerformIO Redirect(routes.ForumTopic.show(categ.slug, topic.slug, 1)) } ) diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index e1b0564d3b..d4c988d80c 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -110,6 +110,8 @@ trait LilaController def IORedirect(op: IO[Call]) = Redirect(op.unsafePerformIO) + def IORedirectUrl(op: IO[String]) = Redirect(op.unsafePerformIO) + def OptionOk[A, B](oa: Option[A])(op: A ⇒ B)( implicit writer: Writeable[B], ctype: ContentTypeOf[B], diff --git a/app/controllers/Mod.scala b/app/controllers/Mod.scala index 817aa8727b..3a9e8cfe46 100644 --- a/app/controllers/Mod.scala +++ b/app/controllers/Mod.scala @@ -31,10 +31,15 @@ object Mod extends LilaController { def ban(username: String) = Secure(Permission.IpBan) { implicit ctx ⇒ me ⇒ IORedirect { - modApi.ipban(me, username) map { _ ⇒ routes.User show username } + modApi.ban(me, username) map { _ ⇒ routes.User show username } } } + def ipban(ip: String) = Secure(Permission.IpBan) { implicit ctx ⇒ + me ⇒ + IOk(modApi.ipban(me, ip)) + } + val log = Auth { implicit ctx ⇒ me => IOk(modLogApi.recent map { html.mod.log(_) }) diff --git a/app/forum/Post.scala b/app/forum/Post.scala index 65b46e4f3f..5f2ed143bc 100644 --- a/app/forum/Post.scala +++ b/app/forum/Post.scala @@ -12,6 +12,7 @@ case class Post( topicId: String, author: Option[String], userId: Option[String], + ip: Option[String], text: String, number: Int, createdAt: DateTime) { @@ -27,12 +28,14 @@ object Post { topicId: String, author: Option[String], userId: Option[String], + ip: Option[String], text: String, number: Int): Post = Post( id = OrnicarRandom nextString idSize, topicId = topicId, author = author, userId = userId, + ip = ip, text = text, number = number, createdAt = DateTime.now) diff --git a/app/forum/PostApi.scala b/app/forum/PostApi.scala index c603af1f42..a0f9d2becf 100644 --- a/app/forum/PostApi.scala +++ b/app/forum/PostApi.scala @@ -2,6 +2,7 @@ package lila package forum import user.User +import http.Context import scalaz.effects._ import com.github.ornicar.paginator._ @@ -20,13 +21,13 @@ final class PostApi(env: ForumEnv, maxPerPage: Int) { def makePost( categ: Categ, topic: Topic, - data: DataForm.PostData, - user: Option[User]): IO[Post] = for { + data: DataForm.PostData)(implicit ctx: Context): IO[Post] = for { number ← lastNumberOf(topic) post = Post( topicId = topic.id, author = data.author, - userId = user map (_.id), + userId = ctx.me map (_.id), + ip = ctx.isAnon option ctx.req.remoteAddress, text = data.text, number = number + 1) _ ← env.postRepo saveIO post diff --git a/app/forum/TopicApi.scala b/app/forum/TopicApi.scala index 12c258473b..611acdcd36 100644 --- a/app/forum/TopicApi.scala +++ b/app/forum/TopicApi.scala @@ -2,6 +2,7 @@ package lila package forum import user.User +import http.Context import scalaz.effects._ import com.github.ornicar.paginator._ @@ -20,8 +21,7 @@ final class TopicApi(env: ForumEnv, maxPerPage: Int) { def makeTopic( categ: Categ, - data: DataForm.TopicData, - user: Option[User]): IO[Topic] = for { + data: DataForm.TopicData)(implicit ctx: Context): IO[Topic] = for { slug ← env.topicRepo.nextSlug(categ, data.name) topic = Topic( categId = categ.slug, @@ -30,7 +30,8 @@ final class TopicApi(env: ForumEnv, maxPerPage: Int) { post = Post( topicId = topic.id, author = data.post.author, - userId = user map (_.id), + userId = ctx.me map (_.id), + ip = ctx.isAnon option ctx.req.remoteAddress, text = data.post.text, number = 1) _ ← env.postRepo saveIO post diff --git a/app/http/Context.scala b/app/http/Context.scala index 141f2f7560..ad1b0d1aa4 100644 --- a/app/http/Context.scala +++ b/app/http/Context.scala @@ -10,6 +10,8 @@ sealed abstract class Context(val req: RequestHeader, val me: Option[User]) { def isAuth = me.isDefined + def isAnon = !isAuth + def canSeeChat = me.fold(m ⇒ !m.isChatBan, false) def isGranted(permission: Permission): Boolean = diff --git a/app/mod/ModApi.scala b/app/mod/ModApi.scala index d67c306c9e..f465becd33 100644 --- a/app/mod/ModApi.scala +++ b/app/mod/ModApi.scala @@ -4,7 +4,7 @@ package mod import user.{ User, UserRepo } import elo.EloUpdater import lobby.Messenger -import security.{ Firewall, Store => SecurityStore } +import security.{ Firewall, Store ⇒ SecurityStore } import scalaz.effects._ @@ -35,15 +35,20 @@ final class ModApi( io()) } yield () - def ipban(mod: User, username: String): IO[Unit] = withUser(username) { user ⇒ + def ban(mod: User, username: String): IO[Unit] = withUser(username) { user ⇒ for { spy ← securityStore userSpy username _ ← io(spy.ips foreach firewall.blockIp) _ ← lobbyMessenger mute user.username doUnless user.isChatBan - _ ← logApi.ipban(mod, user) + _ ← logApi.ban(mod, user) } yield () } + def ipban(mod: User, ip: String): IO[Unit] = for { + _ ← io(firewall blockIp ip) + _ ← logApi.ipban(mod, ip) + } yield () + private def withUser(username: String)(userIo: User ⇒ IO[Unit]) = for { userOption ← userRepo byId username _ ← userOption.fold(userIo, io()) diff --git a/app/mod/Modlog.scala b/app/mod/Modlog.scala index 015bbff910..b380167f1e 100644 --- a/app/mod/Modlog.scala +++ b/app/mod/Modlog.scala @@ -10,12 +10,15 @@ case class Modlog( mod: String, user: Option[String], action: String, + details: Option[String] = None, date: DateTime = DateTime.now) { def showAction = action match { case Modlog.engine ⇒ "mark as engine" case Modlog.unengine ⇒ "un-mark as engine" case Modlog.deletePost ⇒ "delete forum post" + case Modlog.ban ⇒ "ban user" + case Modlog.ipban ⇒ "ban IP" case a ⇒ a } } @@ -26,6 +29,7 @@ object Modlog { val unengine = "unengine" val mute = "mute" val unmute = "unmute" + val ban = "ban" val ipban = "ipban" val deletePost = "deletePost" } diff --git a/app/mod/ModlogApi.scala b/app/mod/ModlogApi.scala index 31086ffd65..a6918f569c 100644 --- a/app/mod/ModlogApi.scala +++ b/app/mod/ModlogApi.scala @@ -15,10 +15,14 @@ final class ModlogApi(repo: ModlogRepo) { Modlog(mod.id, user.id.some, v.fold(Modlog.mute, Modlog.unmute)) } - def ipban(mod: User, user: User) = add { + def ban(mod: User, user: User) = add { Modlog(mod.id, user.id.some, Modlog.ipban) } + def ipban(mod: User, ip: String) = add { + Modlog(mod.id, none, Modlog.ipban, ip.some) + } + def recent = repo recent 200 private def add(modLog: Modlog): IO[Unit] = repo saveIO modLog diff --git a/app/templating/AssetHelper.scala b/app/templating/AssetHelper.scala index 267832fe9d..cd6efc0c52 100644 --- a/app/templating/AssetHelper.scala +++ b/app/templating/AssetHelper.scala @@ -7,7 +7,7 @@ import play.api.templates.Html trait AssetHelper { - val assetVersion = 62 + val assetVersion = 63 def cssTag(name: String) = css("stylesheets/" + name) diff --git a/app/views/forum/topic/show.scala.html b/app/views/forum/topic/show.scala.html index e5fcda5caa..8df49c6666 100644 --- a/app/views/forum/topic/show.scala.html +++ b/app/views/forum/topic/show.scala.html @@ -21,6 +21,11 @@ title = topic.name) { @authorLink(post, "author".some) @showDate(post.createdAt) #@post.number + @if(isGranted(Permission.SuperAdmin)) { + @post.ip.map { ip => + Ban @ip + } + } @if(isGranted(Permission.ModerateForum)) { Delete } diff --git a/app/views/mod/log.scala.html b/app/views/mod/log.scala.html index 4f118864dd..bc36d073c8 100644 --- a/app/views/mod/log.scala.html +++ b/app/views/mod/log.scala.html @@ -19,6 +19,7 @@ @userIdLink(log.mod.some) @log.showAction.capitalize @userIdLink(log.user) + @log.details } diff --git a/conf/routes b/conf/routes index c1b65a7f23..63387143aa 100644 --- a/conf/routes +++ b/conf/routes @@ -88,6 +88,7 @@ POST /account/closeConfirm controllers.User.closeConfirm POST /mod/:username/engine controllers.Mod.engine(username: String) POST /mod/:username/mute controllers.Mod.mute(username: String) POST /mod/:username/ban controllers.Mod.ban(username: String) +POST /mod/:ip/ipban controllers.Mod.ipban(ip: String) GET /mod/log controllers.Mod.log # Wiki diff --git a/public/javascripts/ctrl.js b/public/javascripts/ctrl.js index 16553e2fb4..a45e75f149 100644 --- a/public/javascripts/ctrl.js +++ b/public/javascripts/ctrl.js @@ -184,6 +184,15 @@ $(function() { $('input.confirm').click(function() { return confirm('Confirm this action?'); }); + $('a.ipban').one("click", function() { + var $a = $(this); + if (confirm($a.text() + "?")) { + $.post($a.attr('href'), function() { + $a.text('Done').attr('href', '#'); + }); + } + return false; + }); function bookmarks() { $('span.bookmark a.icon:not(.jsed)').each(function() { diff --git a/public/stylesheets/forum.css b/public/stylesheets/forum.css index 4f6a7f0598..ed575c12d4 100644 --- a/public/stylesheets/forum.css +++ b/public/stylesheets/forum.css @@ -6,7 +6,8 @@ a.forum_feed_link { float: right; } -.metas .delete { +.metas .delete, +.metas .ipban { margin-left: 1em; } .forum_topics_list .delete { diff --git a/todo b/todo index f8c360e5c6..4d1cba123e 100644 --- a/todo +++ b/todo @@ -32,11 +32,8 @@ finish games per day chart untranslated = https://github.com/ornicar/lila/issues/4 game stats timeline issues http://en.lichess.org/forum/lichess-feedback/move-times propagate IP ban to the user -forum post IP ban admin ip search interface analyse: show main line for every move http://en.lichess.org/forum/lichess-feedback/about-the-analysis-feature#5 -show victory in analysis page + other suggestions http://en.lichess.org/forum/lichess-feedback/a-couple-of-feature-requests#2 -only one stockfish instance for moves and analysis. Use a todo with two queues. next deploy -----------