implement IP ban

This commit is contained in:
Thibault Duplessis 2012-06-15 12:13:10 +02:00
parent 87dcd0d97c
commit 62dc62fbb6
11 changed files with 81 additions and 13 deletions

View file

@ -4,8 +4,10 @@ import lila._
import views._
import play.api.mvc._
import play.api.mvc.Results._
import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Akka
object Main extends LilaController {
@ -15,4 +17,14 @@ object Main extends LilaController {
uidOption = get("uid"),
username = ctx.me map (_.username))
}
val blocked = Action { implicit req
Async {
Akka.future {
println("BLOCK %s %s".format(req.remoteAddress, req))
Thread sleep 10 * 1000
BadRequest(html.base.blocked())
}
}
}
}

View file

@ -21,6 +21,7 @@ object User extends LilaController {
def lobbyMessenger = env.lobby.messenger
def bookmarkApi = env.bookmark.api
def securityStore = env.security.store
def firewall = env.security.firewall
def show(username: String) = showFilter(username, "all", 1)
@ -105,6 +106,17 @@ object User extends LilaController {
}
}
def ban(username: String) = Secure(Permission.IpBan) { implicit ctx
_
IOptionIORedirect(userRepo byId username) { user
for {
spy securityStore userSpy username
_ io(spy.ips foreach firewall.block)
_ user.isChatBan.fold(io(), lobbyMessenger mute username)
} yield routes.User show username
}
}
val stats = todo
def export(username: String) = Open { implicit ctx

View file

@ -13,9 +13,10 @@ final class MonoMemo[A](ttl: Int, f: IO[A]) {
private var value: A = _
private var ts: Double = 0
private def expired = nowMillis > ts + ttl
private def refresh {
def refresh {
value = f.unsafePerformIO
ts = nowMillis
}
private def expired = nowMillis > ts + ttl
}

View file

@ -11,6 +11,7 @@ import com.mongodb.casbah.commons.conversions.scala.RegisterJodaTimeConversionHe
package object lila
extends OrnicarValidation
with OrnicarCommon
with OrnicarRegex
with scalaz.Identitys
with scalaz.NonEmptyLists
with scalaz.Strings

View file

@ -8,29 +8,41 @@ import play.api.mvc.Results.BadRequest
import com.mongodb.casbah.MongoCollection
import com.mongodb.casbah.Imports._
import scalaz.effects._
import java.util.Date
final class Firewall(
collection: MongoCollection,
cacheTtl: Int,
enabled: Boolean) {
def requestHandler(req: RequestHeader): Option[Handler] =
(enabled && blocks(req.remoteAddress)) option {
Action {
println("IP block " + req.headers.get("X-Real-IP"))
BadRequest("Your IP has been blocked due to abuse.")
}
}
def requestHandler(req: RequestHeader): Option[Handler] =
(enabled && blocks(req.remoteAddress)) option controllers.Main.blocked
def blocks(ip: String) = ips.apply contains ip
def accepts(ip: String) = !blocks(ip)
def block(ip: String) {
if (validIp(ip)) {
if (accepts(ip)) {
collection += DBObject("_id" -> ip, "date" -> new Date)
ips.refresh
}
}
else println("Invalid IP block: " + ip)
}
// http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
private val ipRegex = """^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$""".r
private def validIp(ip: String) =
(ipRegex matches ip) && ip != "127.0.0.1" && ip != "0.0.0.0"
private val ips = new MonoMemo(cacheTtl, fetch)
private def fetch: IO[Set[String]] = io {
collection.find() map { obj
obj.getAs[String]("_id")
}
}
} map (_.flatten.toSet)
}

View file

@ -17,6 +17,7 @@ object Permission {
case object StaffForum extends Permission("ROLE_STAFF_FORUM")
case object ModerateForum extends Permission("ROLE_MODERATE_FORUM")
case object UserSpy extends Permission("ROLE_USER_SPY")
case object IpBan extends Permission("ROLE_IP_BAN")
case object Admin extends Permission("ROLE_ADMIN") {
override val children = List(
@ -25,7 +26,8 @@ object Permission {
MarkEngine,
StaffForum,
ModerateForum,
UserSpy)
UserSpy,
IpBan)
}
case object SuperAdmin extends Permission("ROLE_SUPER_ADMIN") {
override val children = List(Admin)

View file

@ -0,0 +1,22 @@
@()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>lichess - maintenance</title>
<style type="text/css">
html, body {
font:16px monospace;
background: #000;
color: #ebd488;
text-align: center;
}
</style>
</head>
<body>
<h1>lichess.org is temporarily unreachable</h2>
<br />
<br />
<img src="/assets/images/maintenance.jpg" alt="Lichess maintenance" />
</body>
</html>

View file

@ -45,6 +45,11 @@
<input class="confirm" type="submit" value="@u.isChatBan.fold("Unmute", "Mute")" />
</form>
}
@if(isGranted(Permission.IpBan)) {
<form method="post" action="@routes.User.ban(u.username)">
<input class="confirm" type="submit" value="IP ban" />
</form>
}
}
}

View file

@ -95,7 +95,7 @@ session {
}
firewall {
cache_ttl=5 seconds
enabled=false
enabled=true
}
# trust proxy X-Forwarded-For header

View file

@ -70,6 +70,7 @@ GET /@/:username/:filterName controllers.User.showFilter(username: Str
GET /@/:username controllers.User.show(username: String)
POST /@/:username/engine controllers.User.engine(username: String)
POST /@/:username/mute controllers.User.mute(username: String)
POST /@/:username/ban controllers.User.ban(username: String)
GET /people controllers.User.list(page: Int ?= 1)
GET /people/stats controllers.User.stats
GET /people/autocomplete controllers.User.autocomplete

View file

@ -17,7 +17,7 @@ trait Dependencies {
val specs2 = "org.specs2" %% "specs2" % "1.9.2"
val casbah = "com.mongodb.casbah" %% "casbah" % "2.1.5-1"
val salat = "com.novus" %% "salat-core" % "0.0.8-SNAPSHOT"
val scalalib = "com.github.ornicar" %% "scalalib" % "1.30"
val scalalib = "com.github.ornicar" %% "scalalib" % "1.33"
val config = "com.typesafe" % "config" % "0.4.0"
val json = "com.codahale" %% "jerkson" % "0.5.0"
val guava = "com.google.guava" % "guava" % "11.0.2"