138 lines
4.5 KiB
Scala
138 lines
4.5 KiB
Scala
package lila.security
|
|
|
|
import org.joda.time.DateTime
|
|
import play.api.mvc.RequestHeader
|
|
import scala.concurrent.duration._
|
|
|
|
import lila.common.{ Bus, EmailAddress, HTTPRequest, IpAddress, ThreadLocalRandom }
|
|
import lila.user.User
|
|
|
|
// codename UGC
|
|
final class GarbageCollector(
|
|
userLogins: UserLoginsApi,
|
|
ipTrust: IpTrust,
|
|
irc: lila.irc.IrcApi,
|
|
noteApi: lila.user.NoteApi,
|
|
isArmed: () => Boolean
|
|
)(implicit
|
|
ec: scala.concurrent.ExecutionContext,
|
|
system: akka.actor.ActorSystem
|
|
) {
|
|
|
|
private val logger = lila.security.logger.branch("GarbageCollector")
|
|
|
|
private val justOnce = lila.memo.OnceEvery(10 minutes)
|
|
|
|
private case class ApplyData(user: User, ip: IpAddress, email: EmailAddress, req: RequestHeader) {
|
|
override def toString = s"${user.username} $ip ${email.value} $req"
|
|
}
|
|
|
|
// User just signed up and doesn't have security data yet, so wait a bit
|
|
def delay(user: User, email: EmailAddress, req: RequestHeader): Unit =
|
|
if (user.createdAt.isAfter(DateTime.now minusDays 3)) {
|
|
val ip = HTTPRequest ipAddress req
|
|
system.scheduler
|
|
.scheduleOnce(6 seconds) {
|
|
val applyData = ApplyData(user, ip, email, req)
|
|
logger.debug(s"delay $applyData")
|
|
lila.common.Future
|
|
.retry(
|
|
() => ensurePrintAvailable(applyData),
|
|
delay = 10 seconds,
|
|
retries = 5,
|
|
logger = none
|
|
)
|
|
.recoverDefault >> apply(applyData)
|
|
()
|
|
}
|
|
.unit
|
|
}
|
|
|
|
private def ensurePrintAvailable(data: ApplyData): Funit =
|
|
userLogins userHasPrint data.user flatMap {
|
|
case false => fufail("No print available yet")
|
|
case _ => funit
|
|
}
|
|
|
|
private def apply(data: ApplyData): Funit =
|
|
data match {
|
|
case ApplyData(user, ip, email, req) =>
|
|
for {
|
|
spy <- userLogins(user, 300)
|
|
ipSusp <- ipTrust.isSuspicious(ip)
|
|
_ <- {
|
|
val printOpt = spy.prints.headOption
|
|
logger.debug(s"apply ${data.user.username} print=$printOpt")
|
|
Bus.publish(
|
|
lila.security.UserSignup(user, email, req, printOpt.map(_.fp.value), ipSusp),
|
|
"userSignup"
|
|
)
|
|
printOpt.filter(_.banned).map(_.fp.value) match {
|
|
case Some(print) => collect(user, email, msg = s"Print ban: `${print.value}`")
|
|
case _ =>
|
|
badOtherAccounts(spy.otherUsers.map(_.user)) ?? { others =>
|
|
logger.debug(s"other ${data.user.username} others=${others.map(_.username)}")
|
|
lila.common.Future
|
|
.exists(spy.ips)(ipTrust.isSuspicious)
|
|
.map {
|
|
_ ?? collect(
|
|
user,
|
|
email,
|
|
msg = s"Prev users: ${others.map(o => "@" + o.username).mkString(", ")}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} yield ()
|
|
}
|
|
|
|
private def badOtherAccounts(accounts: List[User]): Option[List[User]] = {
|
|
val others = accounts
|
|
.sortBy(-_.createdAt.getSeconds)
|
|
.takeWhile(_.createdAt.isAfter(DateTime.now minusDays 10))
|
|
.take(4)
|
|
(others.sizeIs > 1 && others.forall(isBadAccount) && others.headOption.exists(_.disabled)) option others
|
|
}
|
|
|
|
private def isBadAccount(user: User) = user.lameOrTrollOrAlt
|
|
|
|
private def collect(user: User, email: EmailAddress, msg: => String): Funit = justOnce(user.id) ?? {
|
|
hasBeenCollectedBefore(user) flatMap {
|
|
case true => funit
|
|
case _ =>
|
|
val armed = isArmed()
|
|
val wait = (30 + ThreadLocalRandom.nextInt(300)).seconds
|
|
val message =
|
|
s"Will dispose of @${user.username} in $wait. Email: ${email.value}. $msg${!armed ?? " [SIMULATION]"}"
|
|
logger.info(message)
|
|
noteApi.lichessWrite(user, s"Garbage collected because of $msg")
|
|
irc.garbageCollector(message) >>- {
|
|
if (armed) {
|
|
doInitialSb(user)
|
|
system.scheduler
|
|
.scheduleOnce(wait) {
|
|
doCollect(user)
|
|
}
|
|
.unit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private def hasBeenCollectedBefore(user: User): Fu[Boolean] =
|
|
noteApi.byUserForMod(user.id).map(_.exists(_.text startsWith "Garbage collected"))
|
|
|
|
private def doInitialSb(user: User): Unit =
|
|
Bus.publish(
|
|
lila.hub.actorApi.security.GCImmediateSb(user.id),
|
|
"garbageCollect"
|
|
)
|
|
|
|
private def doCollect(user: User): Unit =
|
|
Bus.publish(
|
|
lila.hub.actorApi.security.GarbageCollect(user.id),
|
|
"garbageCollect"
|
|
)
|
|
}
|