Enable flood control in all websocket chat rooms

This commit is contained in:
Thibault Duplessis 2012-06-12 19:59:25 +02:00
parent 3530158d2e
commit e1abc9b7fb
10 changed files with 89 additions and 19 deletions

View file

@ -51,7 +51,8 @@ final class CoreEnv private (application: Application, val settings: Settings) {
userRepo = user.userRepo,
getGame = game.gameRepo.game,
roundSocket = round.socket,
roundMessenger = round.messenger)
roundMessenger = round.messenger,
flood = security.flood)
lazy val setup = new lila.setup.SetupEnv(
settings = settings,
@ -85,7 +86,8 @@ final class CoreEnv private (application: Application, val settings: Settings) {
userRepo = user.userRepo,
eloUpdater = user.eloUpdater,
i18nKeys = i18n.keys,
ai = ai.ai)
ai = ai.ai,
flood = security.flood)
lazy val analyse = new lila.analyse.AnalyseEnv(
settings = settings,

View file

@ -13,6 +13,7 @@ import timeline.Entry
import user.{ User, UserRepo }
import game.DbGame
import round.{ Socket RoundSocket, Messenger RoundMessenger }
import security.Flood
import core.Settings
final class LobbyEnv(
@ -22,7 +23,8 @@ final class LobbyEnv(
userRepo: UserRepo,
getGame: String => IO[Option[DbGame]],
roundSocket: RoundSocket,
roundMessenger: RoundMessenger) {
roundMessenger: RoundMessenger,
flood: Flood) {
implicit val ctx = app
import settings._
@ -39,7 +41,9 @@ final class LobbyEnv(
timeout = SiteUidTimeout
)), name = ActorLobbyHub)
lazy val socket = new Socket(hub = hub)
lazy val socket = new Socket(
hub = hub,
flood = flood)
lazy val fisherman = new Fisherman(
hookRepo = hookRepo,

View file

@ -14,8 +14,9 @@ import implicits.RichJs._
import socket.{ Util, PingVersion, Quit }
import timeline.Entry
import game.DbGame
import security.Flood
final class Socket(hub: ActorRef) {
final class Socket(hub: ActorRef, flood: Flood) {
implicit val timeout = Timeout(1 second)
@ -34,6 +35,7 @@ final class Socket(hub: ActorRef) {
case Some("talk") for {
data e obj "d"
txt data str "txt"
if flood.allowMessage(uid, txt)
uname username
} hub ! Talk(txt, uname)
case Some("p") e int "v" foreach { v

View file

@ -20,17 +20,14 @@ object Builder {
.removalListener(onRemove)
.build[K, V](f)
def expiry[K, V](ttl: Int): Cache[K, V] =
cacheBuilder[K, V](ttl).build[K, V]
private def cacheBuilder[K, V](ttl: Int): CacheBuilder[K, V] =
CacheBuilder.newBuilder()
.expireAfterWrite(ttl, TimeUnit.MILLISECONDS)
.asInstanceOf[CacheBuilder[K, V]]
def expiry[K, V](ttl: Int): Cache[K, V] =
CacheBuilder.newBuilder()
.expireAfterWrite(ttl, TimeUnit.MILLISECONDS)
.asInstanceOf[CacheBuilder[K, V]]
.build[K, V]
implicit def functionToRemovalListener[K, V](f: (K, V) Unit): RemovalListener[K, V] =
new RemovalListener[K, V] {
def onRemoval(notification: RemovalNotification[K, V]) =

View file

@ -65,7 +65,9 @@ final class Hub(
sender ! Connected(enumerator, member)
}
case Events(Nil)
case Events(events) notify(events)
case GameEvents(_, Nil)
case GameEvents(_, events) notify(events)
case Quit(uid) {
@ -86,7 +88,7 @@ final class Hub(
.filter(_.watcher)
.map(_.username)
.toList.partition(_.isDefined) match {
case (users, anons) users.flatten.distinct |> { userList =>
case (users, anons) users.flatten.distinct |> { userList
anons.size match {
case 0 userList
case 1 userList :+ "Anonymous"

View file

@ -13,6 +13,7 @@ import elo.EloUpdater
import ai.Ai
import core.Settings
import i18n.I18nKeys
import security.Flood
final class RoundEnv(
app: Application,
@ -22,7 +23,8 @@ final class RoundEnv(
userRepo: UserRepo,
eloUpdater: EloUpdater,
i18nKeys: I18nKeys,
ai: () Ai) {
ai: () Ai,
flood: Flood) {
implicit val ctx = app
import settings._
@ -41,7 +43,8 @@ final class RoundEnv(
getPlayerPov = gameRepo.pov,
hand = hand,
hubMaster = hubMaster,
messenger = messenger)
messenger = messenger,
flood = flood)
lazy val hand = new Hand(
gameRepo = gameRepo,

View file

@ -18,6 +18,7 @@ import game.{ Pov, PovRef }
import chess.Color
import socket.{ PingVersion, Quit }
import socket.Util.connectionFail
import security.Flood
import implicits.RichJs._
final class Socket(
@ -25,7 +26,8 @@ final class Socket(
getPlayerPov: String IO[Option[Pov]],
hand: Hand,
hubMaster: ActorRef,
messenger: Messenger) {
messenger: Messenger,
flood: Flood) {
private val timeoutDuration = 1 second
implicit private val timeout = Timeout(timeoutDuration)
@ -50,7 +52,10 @@ final class Socket(
case Some("p") e int "v" foreach { v
hub ! PingVersion(uid, v)
}
case Some("talk") e str "d" foreach { txt
case Some("talk") for {
txt e str "d"
if flood.allowMessage(uid, txt)
} {
val events = messenger.playerMessage(povRef, txt).unsafePerformIO
hub ! Events(events)
}
@ -75,9 +80,12 @@ final class Socket(
case Some("p") e int "v" foreach { v
hub ! PingVersion(uid, v)
}
case Some("talk") e str "d" foreach { txt
case Some("talk") for {
txt e str "d"
if flood.allowMessage(uid, txt)
} {
val events = messenger.watcherMessage(
povRef.gameId,
povRef.gameId,
member.username,
txt).unsafePerformIO
hub ! Events(events)

50
app/security/Flood.scala Normal file
View file

@ -0,0 +1,50 @@
package lila
package security
import memo.Builder
import org.joda.time.DateTime
import org.scala_tools.time.Imports._
final class Flood {
private val ttl = 60 * 1000
private val floodNumber = 4
private val floodDelay = 10 * 1000
case class Message(text: String, date: DateTime) {
def same(other: Message) = this.text == other.text
}
type Messages = List[Message]
// uid, [date, message]
private val messages = Builder.expiry[String, Messages](ttl)
def filterMessage[A](uid: String, text: String)(op: Unit) {
if (allowMessage(uid, text)) op
}
def allowMessage(uid: String, text: String): Boolean = {
val msg = Message(text, DateTime.now)
val msgs = ~Option(messages getIfPresent uid)
val allow = !duplicateMessage(msg, msgs) && !quickPost(msg, msgs)
allow ~ { a
if (a) messages.put(uid, msg :: msgs)
}
}
private def duplicateMessage(msg: Message, msgs: Messages): Boolean =
msgs.headOption.fold(
m (m same msg) || msgs.tail.headOption.fold(
_ same msg,
false
),
false)
private def quickPost(msg: Message, msgs: Messages): Boolean =
(msgs lift floodNumber).fold(
old old.date > (msg.date - floodDelay.millis),
false
)
}

View file

@ -21,6 +21,8 @@ final class SecurityEnv(
lazy val firewall = new security.Firewall(
collection = mongodb(MongoCollectionFirewall),
cacheTtl = FirewallCacheTtl)
lazy val flood = new security.Flood
lazy val forms = new DataForm(
userRepo = userRepo,

2
todo
View file

@ -21,7 +21,7 @@ guess friend list
autoclose top menus
tournaments http://www.chess.com/tournaments/help.html
fix game list translations
chats antiflood
temporary mod IP ban to help stopping cheaters
new translations:
-rematchOfferCanceled=Rematch offer canceled