From 0e52dfa357785ff13bd76828778fa21422e023ff Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sun, 11 Mar 2018 11:25:36 -0500 Subject: [PATCH] implement IpTrust --- app/controllers/Auth.scala | 8 +++---- modules/security/src/main/Env.scala | 4 +++- .../security/src/main/GarbageCollector.scala | 16 ++++---------- modules/security/src/main/IpIntel.scala | 19 ++++++++++------ modules/security/src/main/IpTrust.scala | 22 +++++++++++++++++++ 5 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 modules/security/src/main/IpTrust.scala diff --git a/app/controllers/Auth.scala b/app/controllers/Auth.scala index 64a19a3b63..f7485f9d95 100644 --- a/app/controllers/Auth.scala +++ b/app/controllers/Auth.scala @@ -135,19 +135,19 @@ object Auth extends LilaController { case object Nope extends MustConfirmEmail(false) case object YesBecausePrint extends MustConfirmEmail(true) - case object YesBecauseIp extends MustConfirmEmail(true) - case object YesBecauseProxy extends MustConfirmEmail(true) + case object YesBecauseIpExists extends MustConfirmEmail(true) + case object YesBecauseIpSusp extends MustConfirmEmail(true) case object YesBecauseMobile extends MustConfirmEmail(true) case object YesBecauseUA extends MustConfirmEmail(true) def apply(print: Option[FingerPrint])(implicit ctx: Context): Fu[MustConfirmEmail] = { val ip = HTTPRequest lastRemoteAddress ctx.req api.recentByIpExists(ip) flatMap { ipExists => - if (ipExists) fuccess(YesBecauseIp) + if (ipExists) fuccess(YesBecauseIpExists) else print.??(api.recentByPrintExists) flatMap { printExists => if (printExists) fuccess(YesBecausePrint) else if (HTTPRequest weirdUA ctx.req) fuccess(YesBecauseUA) - else Env.security.ipIntel(ip).map(80 <).map { _.fold(YesBecauseProxy, Nope) } + else Env.security.ipTrust.isSuspicious(ip).map { _.fold(YesBecauseIpSusp, Nope) } } } } diff --git a/modules/security/src/main/Env.scala b/modules/security/src/main/Env.scala index 4120b08b7c..b5e3a0f73d 100644 --- a/modules/security/src/main/Env.scala +++ b/modules/security/src/main/Env.scala @@ -98,7 +98,7 @@ final class Env( lazy val garbageCollector = new GarbageCollector( userSpyApi, - ipIntel, + ipTrust, slack, ugcArmedSetting.get, system @@ -167,6 +167,8 @@ final class Env( scheduler.once(30 seconds)(tor.refresh(_ => funit)) scheduler.effect(TorRefreshDelay, "Refresh Tor exit nodes")(tor.refresh(firewall.unblockIps)) + lazy val ipTrust = new IpTrust(ipIntel, geoIP, tor, firewall) + lazy val api = new SecurityApi(storeColl, firewall, geoIP, authenticator, emailAddressValidator, tryOAuthServer) lazy val csrfRequestHandler = new CSRFRequestHandler(NetDomain) diff --git a/modules/security/src/main/GarbageCollector.scala b/modules/security/src/main/GarbageCollector.scala index ecb3a79a73..0cd1a34bae 100644 --- a/modules/security/src/main/GarbageCollector.scala +++ b/modules/security/src/main/GarbageCollector.scala @@ -9,14 +9,13 @@ import lila.user.{ User, UserRepo } // codename UGC final class GarbageCollector( userSpy: UserSpyApi, - ipIntel: IpIntel, + ipTrust: IpTrust, slack: lila.slack.SlackApi, isArmed: () => Boolean, system: akka.actor.ActorSystem ) { - /* User just signed up and doesn't have security data yet, - * so wait a bit */ + // User just signed up and doesn't have security data yet, so wait a bit def delay(user: User, ip: IpAddress, email: EmailAddress): Unit = if (!recentlyChecked.get(user.id) && user.createdAt.isAfter(DateTime.now minusDays 3)) { recentlyChecked put user.id @@ -25,12 +24,12 @@ final class GarbageCollector( } } - private val recentlyChecked = new lila.memo.ExpireSetMemo(ttl = 5 minutes) + private val recentlyChecked = new lila.memo.ExpireSetMemo(ttl = 3 seconds) private def apply(user: User, ip: IpAddress, email: EmailAddress): Funit = userSpy(user) flatMap { spy => badOtherAccounts(spy.otherUsers.map(_.user)) ?? { others => - lila.common.Future.exists(spy.ips)(isBadIp).map { + lila.common.Future.exists(spy.ips)(ipTrust.isSuspicious).map { _ ?? { val ipBan = spy.usersSharingIp.forall { u => isBadAccount(u) || !u.seenAt.exists(DateTime.now.minusMonths(2).isBefore) @@ -49,13 +48,6 @@ final class GarbageCollector( (others.size > 1 && others.forall(isBadAccount) && others.headOption.exists(_.disabled)) option others } - private def isBadIp(ip: UserSpy.IPData): Fu[Boolean] = if (ip.blocked || - ip.location == Location.unknown || - ip.location == Location.tor || - ip.location.shortCountry == "Iran" // some undetected proxies - ) fuccess(true) - else ipIntel(ip.ip).map { 75 < _ } - private def isBadAccount(user: User) = user.troll || user.engine private def collect(user: User, email: EmailAddress, others: List[User], ipBan: Boolean): Funit = { diff --git a/modules/security/src/main/IpIntel.scala b/modules/security/src/main/IpIntel.scala index f10c46675d..e9f5b173f0 100644 --- a/modules/security/src/main/IpIntel.scala +++ b/modules/security/src/main/IpIntel.scala @@ -15,15 +15,9 @@ final class IpIntel(asyncCache: lila.memo.AsyncCache.Builder, lichessEmail: Stri } def failable(ip: IpAddress): Fu[Int] = - if (blackList.exists(ip.value.startsWith)) fuccess(90) + if (IpIntel isBlacklisted ip) fuccess(90) else cache get ip - // Proxies ipintel doesn't detect - private val blackList = List( - "5.121.", - "5.122." - ) - private val cache = asyncCache.multi[IpAddress, Int]( name = "ipIntel", f = ip => { @@ -44,3 +38,14 @@ final class IpIntel(asyncCache: lila.memo.AsyncCache.Builder, lichessEmail: Stri expireAfter = _.ExpireAfterAccess(3 days) ) } + +object IpIntel { + + // Proxies ipintel doesn't detect + private val blackList = List( + "5.121.", + "5.122." + ) + + def isBlacklisted(ip: IpAddress): Boolean = blackList.exists(ip.value.startsWith) +} diff --git a/modules/security/src/main/IpTrust.scala b/modules/security/src/main/IpTrust.scala new file mode 100644 index 0000000000..77fb4acf2c --- /dev/null +++ b/modules/security/src/main/IpTrust.scala @@ -0,0 +1,22 @@ +package lila.security + +import lila.common.IpAddress + +case class IpTrust(intelApi: IpIntel, geoApi: GeoIP, torApi: Tor, firewallApi: Firewall) { + + def isSuspicious(ip: IpAddress): Fu[Boolean] = + if (IpIntel isBlacklisted ip) yep + else if (firewallApi blocksIp ip) yep + else if (torApi isExitNode ip) yep + else { + val location = geoApi orUnknown ip + if (location == Location.unknown || location == Location.tor) yep + else if (location.shortCountry == "Iran") yep // some undetected proxies + else if (location.shortCountry == "United Arab Emirates") yep // some undetected proxies + else intelApi(ip).map { 75 < _ } + } + + def isSuspicious(ipData: UserSpy.IPData): Fu[Boolean] = isSuspicious(ipData.ip) + + private val yep = fuccess(true) +}