lila/modules/security/src/main/Firewall.scala

89 lines
2.9 KiB
Scala

package lila.security
import lila.common.PimpedJson._
import lila.common.LilaCookie
import lila.db.api._
import tube.firewallTube
import scala.concurrent.duration._
import play.api.mvc.{ RequestHeader, Handler, Action, Cookies }
import play.api.mvc.Results.Redirect
import play.api.libs.json._
import play.api.libs.concurrent.Execution.Implicits._
import play.modules.reactivemongo.Implicits._
import spray.caching.{ LruCache, Cache }
import org.joda.time.DateTime
import ornicar.scalalib.Random
final class Firewall(
cookieName: Option[String],
enabled: Boolean,
cachedIpsTtl: Duration) {
def requestHandler(req: RequestHeader): Fu[Option[Handler]] =
cookieName filter (_ enabled) zmap { cn
blocksIp(req.remoteAddress) map { bIp
val bCs = blocksCookies(req.cookies, cn)
if (bIp && !bCs) infectCookie(cn)(req).some
else if (bCs && !bIp) { blockIp(req.remoteAddress); None }
else None
}
}
def blocks(req: RequestHeader): Fu[Boolean] = if (enabled) {
cookieName.fold(blocksIp(req.remoteAddress)) { cn
blocksIp(req.remoteAddress) map (_ || blocksCookies(req.cookies, cn))
}
}
else fuccess(false)
def accepts(req: RequestHeader): Fu[Boolean] = blocks(req) map (!_)
def blockIp(ip: String): Funit = blocksIp(ip) flatMap { blocked
if (validIp(ip) && !blocked) {
log("Block IP: " + ip)
$insert(Json.obj("_id" -> ip, "date" -> DateTime.now)) >> cachedIps.clear
}
else fuccess(log("Invalid IP block: " + ip))
}
private def infectCookie(name: String)(implicit req: RequestHeader) = Action {
log("Infect cookie " + formatReq(req))
val cookie = LilaCookie.cookie(name, Random nextString 32)
Redirect("/") withCookies cookie
}
def logBlock(req: RequestHeader) {
log("Block " + formatReq(req))
}
private def log(msg: Any) {
println("[%s] %s".format("firewall", msg.toString))
}
private def formatReq(req: RequestHeader) =
"%s %s %s".format(req.remoteAddress, req.uri, req.headers.get("User-Agent") | "?")
private def blocksIp(ip: String): Fu[Boolean] = ips map (_ contains ip)
private def blocksCookies(cookies: Cookies, name: String) =
(cookies get name).isDefined
// 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 lazy val cachedIps = new {
private val cache: Cache[Set[String]] = LruCache(timeToLive = cachedIpsTtl)
def apply: Fu[Set[String]] = cache.fromFuture(true)(fetch)
def clear { cache.clear }
}
private def ips: Fu[Set[String]] = cachedIps.apply
private def fetch: Fu[Set[String]] =
$primitive($select.all, "id")(_.asOpt[String]) map (_.toSet)
}