more msg security
parent
d31ea7d929
commit
45c7364b59
|
@ -16,7 +16,8 @@ final class Env(
|
|||
relationApi: lila.relation.RelationApi,
|
||||
prefApi: lila.pref.PrefApi,
|
||||
notifyApi: lila.notify.NotifyApi,
|
||||
cacheApi: lila.memo.CacheApi
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
spam: lila.security.Spam
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, scheduler: akka.actor.Scheduler) {
|
||||
|
||||
private val colls = wire[MsgColls]
|
||||
|
|
|
@ -54,13 +54,14 @@ final class MsgApi(
|
|||
val msg = Msg.make(text, orig)
|
||||
val threadId = MsgThread.id(orig, dest)
|
||||
!colls.thread.exists($id(threadId)) flatMap { isNew =>
|
||||
security.post(orig, dest, msg, isNew) flatMap {
|
||||
case MsgSecurity.Ok(mute) =>
|
||||
security.can.post(dest, msg, isNew) flatMap {
|
||||
case _: MsgSecurity.Reject => funit
|
||||
case send: MsgSecurity.Send =>
|
||||
val msgWrite = colls.msg.insert.one(writeMsg(msg, threadId))
|
||||
val threadWrite =
|
||||
if (isNew)
|
||||
colls.thread.insert.one {
|
||||
writeThread(MsgThread.make(orig, dest, msg), delBy = mute option dest)
|
||||
writeThread(MsgThread.make(orig, dest, msg), delBy = send.mute option dest)
|
||||
}.void
|
||||
else
|
||||
colls.thread.update
|
||||
|
@ -68,7 +69,7 @@ final class MsgApi(
|
|||
$id(threadId),
|
||||
$set("lastMsg" -> msg.asLast) ++ $pull(
|
||||
// unset deleted by receiver unless the message is muted
|
||||
"del" $in (orig :: (!mute).option(dest).toList)
|
||||
"del" $in (orig :: (!send.mute).option(dest).toList)
|
||||
)
|
||||
)
|
||||
.void
|
||||
|
|
|
@ -3,15 +3,19 @@ package lila.msg
|
|||
import org.joda.time.DateTime
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.Bus
|
||||
import lila.db.dsl._
|
||||
import lila.hub.actorApi.report.AutoFlag
|
||||
import lila.memo.RateLimit
|
||||
import lila.shutup.Analyser
|
||||
import lila.user.User
|
||||
|
||||
final private class MsgSecurity(
|
||||
colls: MsgColls,
|
||||
prefApi: lila.pref.PrefApi,
|
||||
userRepo: lila.user.UserRepo,
|
||||
relationApi: lila.relation.RelationApi
|
||||
relationApi: lila.relation.RelationApi,
|
||||
spam: lila.security.Spam
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
import BsonHandlers._
|
||||
|
@ -31,22 +35,46 @@ final private class MsgSecurity(
|
|||
key = "msg_reply.user"
|
||||
)
|
||||
|
||||
def post(orig: User.ID, dest: User.ID, msg: Msg, isNew: Boolean): Fu[Verdict] =
|
||||
may.post(orig, dest) flatMap {
|
||||
case false => fuccess(Block)
|
||||
case _ =>
|
||||
val limiter = if (isNew) CreateLimitPerUser else ReplyLimitPerUser
|
||||
if (!limiter(orig)(true)) fuccess(Limit)
|
||||
else
|
||||
muteTroll(orig, dest) map { troll =>
|
||||
Ok(mute = troll)
|
||||
object can {
|
||||
|
||||
def post(dest: User.ID, msg: Msg, isNew: Boolean): Fu[Verdict] =
|
||||
may.post(msg.user, dest) flatMap {
|
||||
case false => fuccess(Block)
|
||||
case _ =>
|
||||
isLimited(msg, isNew) orElse
|
||||
isSpam(msg) orElse
|
||||
isTroll(msg.user, dest) orElse
|
||||
isDirt(msg, isNew) getOrElse
|
||||
fuccess(Ok)
|
||||
} flatMap {
|
||||
case mute: Mute =>
|
||||
relationApi.fetchFollows(dest, msg.user) dmap { isFriend =>
|
||||
if (isFriend) Ok else mute
|
||||
}
|
||||
case verdict => fuccess(verdict)
|
||||
} addEffect {
|
||||
case Dirt =>
|
||||
Bus.publish(AutoFlag(msg.user, s"msg/${msg.user}/$dest", msg.text), "autoFlag")
|
||||
case Spam =>
|
||||
logger.warn(s"PM spam from ${msg.user}: ${msg.text}")
|
||||
case _ =>
|
||||
}
|
||||
|
||||
private def isLimited(msg: Msg, isNew: Boolean): Fu[Option[Verdict]] = {
|
||||
val limiter = if (isNew) CreateLimitPerUser else ReplyLimitPerUser
|
||||
!limiter(msg.user)(true) ?? fuccess(Limit.some)
|
||||
}
|
||||
|
||||
private def muteTroll(orig: User.ID, dest: User.ID): Fu[Boolean] =
|
||||
userRepo.isTroll(orig) >>&
|
||||
!userRepo.isTroll(dest) >>&
|
||||
!relationApi.fetchFollows(dest, orig)
|
||||
private def isSpam(msg: Msg): Fu[Option[Verdict]] =
|
||||
spam.detect(msg.text) ?? fuccess(Spam.some)
|
||||
|
||||
private def isTroll(orig: User.ID, dest: User.ID): Fu[Option[Verdict]] =
|
||||
userRepo.isTroll(orig) >>& !userRepo.isTroll(dest) dmap { _ option Troll }
|
||||
|
||||
private def isDirt(msg: Msg, isNew: Boolean): Fu[Option[Verdict]] =
|
||||
(isNew && Analyser(msg.text).dirty) ??
|
||||
!userRepo.isCreatedSince(msg.user, DateTime.now.minusDays(30)) dmap { _ option Dirt }
|
||||
}
|
||||
|
||||
object may {
|
||||
|
||||
|
@ -81,8 +109,14 @@ final private class MsgSecurity(
|
|||
private object MsgSecurity {
|
||||
|
||||
sealed trait Verdict
|
||||
sealed trait Reject extends Verdict
|
||||
sealed abstract class Send(val mute: Boolean) extends Verdict
|
||||
sealed abstract class Mute extends Send(true)
|
||||
|
||||
case class Ok(mute: Boolean) extends Verdict
|
||||
case object Block extends Verdict
|
||||
case object Limit extends Verdict
|
||||
case object Ok extends Send(false)
|
||||
case object Troll extends Mute
|
||||
case object Spam extends Mute
|
||||
case object Dirt extends Mute
|
||||
case object Block extends Reject
|
||||
case object Limit extends Reject
|
||||
}
|
||||
|
|
|
@ -341,6 +341,9 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
|
|||
|
||||
def isTroll(id: ID): Fu[Boolean] = coll.exists($id(id) ++ trollSelect(true))
|
||||
|
||||
def isCreatedSince(id: ID, since: DateTime): Fu[Boolean] =
|
||||
coll.exists($id(id) ++ $doc(F.createdAt $lt since))
|
||||
|
||||
def setRoles(id: ID, roles: List[String]) = coll.updateField($id(id), F.roles, roles)
|
||||
|
||||
def disableTwoFactor(id: ID) = coll.update.one($id(id), $unset(F.totpSecret))
|
||||
|
|
Loading…
Reference in New Issue