Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report users with >= 80 playbans over multiple accounts #5245

Merged
merged 3 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ lazy val bookmark = module("bookmark", Seq(common, memo, db, hub, user, game)).s
)
)

lazy val report = module("report", Seq(common, db, user, game, security)).settings(
lazy val report = module("report", Seq(common, db, user, game, security, playban)).settings(
libraryDependencies ++= provided(play.api, reactivemongo.driver, reactivemongo.iteratees)
)

Expand Down
10 changes: 10 additions & 0 deletions modules/report/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ final class Env(
isOnline: lila.user.User.ID => Boolean,
noteApi: lila.user.NoteApi,
securityApi: lila.security.SecurityApi,
userSpyApi: lila.security.UserSpyApi,
playbanApi: lila.playban.PlaybanApi,
system: ActorSystem,
hub: lila.hub.Env,
settingStore: lila.memo.SettingStore.Builder,
Expand Down Expand Up @@ -39,6 +41,8 @@ final class Env(
autoAnalysis,
noteApi,
securityApi,
userSpyApi,
playbanApi,
isOnline,
asyncCache,
scoreThreshold = scoreThresholdSetting.get
Expand All @@ -58,6 +62,10 @@ final class Env(
}
}), name = ActorName)

system.lilaBus.subscribeFun('playban) {
case lila.hub.actorApi.playban.Playban(userId, _) => api.maybeAutoPlaybanReport(userId)
}

system.scheduler.schedule(1 minute, 1 minute) { api.inquiries.expire }

lazy val reportColl = db(CollectionReport)
Expand All @@ -71,6 +79,8 @@ object Env {
isOnline = lila.user.Env.current.isOnline,
noteApi = lila.user.Env.current.noteApi,
securityApi = lila.security.Env.current.api,
userSpyApi = lila.security.Env.current.userSpyApi,
playbanApi = lila.playban.Env.current.api,
system = lila.common.PlayApp.system,
hub = lila.hub.Env.current,
settingStore = lila.memo.Env.current.settingStore,
Expand Down
2 changes: 2 additions & 0 deletions modules/report/src/main/Reason.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ object Reason {
case object Troll extends Reason
case object Boost extends Reason
case object Other extends Reason
case object Playbans extends Reason

val communication: Set[Reason] = Set(Insult, Troll, Other)

Expand All @@ -37,5 +38,6 @@ object Reason {
def isInsult = reason == Insult
def isPrint = reason == CheatPrint
def isTrollOrInsult = reason == Troll || reason == Insult
def isPlaybans = reason == Playbans
}
}
21 changes: 21 additions & 0 deletions modules/report/src/main/ReportApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ final class ReportApi(
autoAnalysis: AutoAnalysis,
noteApi: NoteApi,
securityApi: lila.security.SecurityApi,
userSpyApi: lila.security.UserSpyApi,
playbanApi: lila.playban.PlaybanApi,
isOnline: User.ID => Boolean,
asyncCache: lila.memo.AsyncCache.Builder,
scoreThreshold: () => Int
Expand Down Expand Up @@ -103,6 +105,25 @@ final class ReportApi(
case _ => funit
}

def maybeAutoPlaybanReport(userId: String): Funit =
userSpyApi.getUserIdsWithSameIpAndPrint(userId) map { ids =>
playbanApi.bans(ids.toList ::: List(userId)) map { bans =>
(bans.values.sum >= 80) ?? {
UserRepo.byId(userId) zip
getLichessReporter zip
findRecent(1, selectRecent(SuspectId(userId), Reason.Playbans)) flatMap {
case Some(abuser) ~ reporter ~ past if past.size < 1 => create(Report.Candidate(
reporter = reporter,
suspect = Suspect(abuser),
reason = Reason.Playbans,
text = s"${bans.values.sum} playbans over ${bans.keys.size} accounts with IP+Print match."
))
case _ => funit
}
}
}
}

def processAndGetBySuspect(suspect: Suspect): Fu[List[Report]] = for {
all <- recent(suspect, 10)
open = all.filter(_.open)
Expand Down
2 changes: 1 addition & 1 deletion modules/report/src/main/ReportScore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private final class ReportScore(
// https://github.com/ornicar/lila/issues/4587
def fixedAutoCommPrintScore(c: Report.Candidate)(score: Double): Double =
if (c.isAutoComm) baseScore
else if (c.isPrint || c.isCoachReview) baseScoreAboveThreshold
else if (c.isPrint || c.isCoachReview || c.isPlaybans) baseScoreAboveThreshold
else score
}

Expand Down
2 changes: 1 addition & 1 deletion modules/report/src/main/Room.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object Room {
case Reason.Cheat => Cheat
case Reason.CheatPrint => Print
case Reason.Troll | Reason.Insult => Coms
case Reason.Boost | Reason.Other => Other
case Reason.Boost | Reason.Playbans | Reason.Other => Other
}

def toReasons(room: Room): Set[Reason] = room match {
Expand Down
33 changes: 25 additions & 8 deletions modules/security/src/main/UserSpy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ case class UserSpy(
def otherUserIds = otherUsers.map(_.user.id)
}

private[security] final class UserSpyApi(firewall: Firewall, geoIP: GeoIP, coll: Coll) {
final class UserSpyApi(firewall: Firewall, geoIP: GeoIP, coll: Coll) {

import UserSpy._

def apply(user: User): Fu[UserSpy] = for {
infos ← Store.chronoInfoByUser(user.id)
ips = distinctRecent(infos.map(_.datedIp))
prints = distinctRecent(infos.flatMap(_.datedFp))
sharingIp ← exploreSimilar("ip")(user)(coll)
sharingFingerprint ← exploreSimilar("fp")(user)(coll)
sharingIp ← exploreSimilar("ip")(user)
sharingFingerprint ← exploreSimilar("fp")(user)
} yield UserSpy(
ips = ips map { ip =>
IPData(ip, firewall blocksIp ip.value, geoIP orUnknown ip.value)
Expand All @@ -62,20 +62,20 @@ private[security] final class UserSpyApi(firewall: Firewall, geoIP: GeoIP, coll:
readPreference = ReadPreference.secondaryPreferred
)

private def exploreSimilar(field: String)(user: User)(implicit coll: Coll): Fu[Set[User]] =
nextValues(field)(user) flatMap { nValues =>
private def exploreSimilar(field: String)(user: User): Fu[Set[User]] =
nextValues(field)(user.id) flatMap { nValues =>
nextUsers(field)(nValues, user)
}

private def nextValues(field: String)(user: User)(implicit coll: Coll): Fu[Set[Value]] =
private def nextValues(field: String)(userId: User.ID): Fu[Set[Value]] =
coll.find(
$doc("user" -> user.id),
$doc("user" -> userId),
$doc(field -> true)
).cursor[Bdoc]().gather[List]() map {
_.flatMap(_.getAs[Value](field))(breakOut)
}

private def nextUsers(field: String)(values: Set[Value], user: User)(implicit coll: Coll): Fu[Set[User]] =
private def nextUsers(field: String)(values: Set[Value], user: User): Fu[Set[User]] =
values.nonEmpty ?? {
coll.distinctWithReadPreference[String, Set](
"user",
Expand All @@ -88,6 +88,23 @@ private[security] final class UserSpyApi(firewall: Firewall, geoIP: GeoIP, coll:
userIds.nonEmpty ?? (UserRepo byIds userIds) map (_.toSet)
}
}

def getUserIdsWithSameIpAndPrint(userId: User.ID): Fu[Set[User.ID]] = {
for {
ips <- nextValues("ip")(userId)
fps <- nextValues("fp")(userId)
} yield (ips.nonEmpty && fps.nonEmpty) ?? {
coll.distinctWithReadPreference[String, Set](
"user",
$doc(
"ip" $in ips,
"fp" $in fps,
"user" $ne userId
).some,
ReadPreference.secondaryPreferred
)
}
} flatMap identity
}

object UserSpy {
Expand Down