encrypt IP addresses for non-admin moderators
parent
0290d578e2
commit
40265c3642
|
@ -197,6 +197,7 @@ final class EnvBoot(
|
|||
|
||||
lazy val mainDb: lila.db.Db = mongo.blockingDb("main", config.get[String]("mongodb.uri"))
|
||||
lazy val imageRepo = new lila.db.ImageRepo(mainDb(CollName("image")))
|
||||
lazy val symmetricCipher = new lila.common.SymmetricCipher(config.get[Secret]("api.symmetric_cipher.key"))
|
||||
|
||||
// wire all the lila modules
|
||||
lazy val memo: lila.memo.Env = wire[lila.memo.Env]
|
||||
|
|
|
@ -221,6 +221,7 @@ final class Mod(
|
|||
if (priv) perms.ViewPrivateComms else perms.Shadowban
|
||||
} { implicit ctx => me =>
|
||||
OptionFuOk(env.user.repo named username) { user =>
|
||||
implicit val renderIp = env.mod.ipRender(me)
|
||||
env.game.gameRepo
|
||||
.recentPovsByUserFromSecondary(user, 80)
|
||||
.mon(_.mod.comm.segment("recentPovs"))
|
||||
|
@ -346,7 +347,7 @@ final class Mod(
|
|||
}
|
||||
|
||||
def print(fh: String) =
|
||||
SecureBody(_.PrintBan) { implicit ctx => _ =>
|
||||
SecureBody(_.ViewPrintNoIP) { implicit ctx => _ =>
|
||||
val hash = FingerHash(fh)
|
||||
for {
|
||||
uids <- env.security.api recentUserIdsByFingerHash hash
|
||||
|
@ -363,8 +364,9 @@ final class Mod(
|
|||
}
|
||||
|
||||
def singleIp(ip: String) =
|
||||
SecureBody(_.IpBan) { implicit ctx => _ =>
|
||||
IpAddress.from(ip) ?? { address =>
|
||||
SecureBody(_.ViewPrintNoIP) { implicit ctx => me =>
|
||||
implicit val renderIp = env.mod.ipRender(me)
|
||||
env.mod.ipRender.decrypt(ip) ?? { address =>
|
||||
for {
|
||||
uids <- env.security.api recentUserIdsByIp address
|
||||
users <- env.user.repo usersFromSecondary uids.reverse
|
||||
|
|
|
@ -362,6 +362,7 @@ final class User(
|
|||
case UserModel.WithEmails(user, emails) =>
|
||||
import html.user.{ mod => view }
|
||||
import lila.app.ui.ScalatagsExtensions.LilaFragZero
|
||||
implicit val renderIp = env.mod.ipRender(holder)
|
||||
|
||||
val nbOthers = getInt("nbOthers") | 100
|
||||
|
||||
|
@ -394,9 +395,9 @@ final class User(
|
|||
html.user.mod.otherUsers(holder, user, _)
|
||||
}
|
||||
}
|
||||
val identification = userLoginsFu map { spy =>
|
||||
(Granter.canViewFp(holder, user) || Granter.canViewIp(holder, user)) ??
|
||||
html.user.mod.identification(spy)
|
||||
val identification = userLoginsFu map { logins =>
|
||||
Granter.is(_.ViewPrintNoIP)(holder) ??
|
||||
html.user.mod.identification(holder, logins)
|
||||
}
|
||||
val irwin = isGranted(_.MarkEngine) ?? env.irwin.api.reports.withPovs(user).map {
|
||||
_ ?? { reps =>
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package views.html.mod
|
||||
|
||||
import controllers.routes
|
||||
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.String.html.richText
|
||||
import lila.hub.actorApi.shutup.PublicSource
|
||||
import lila.mod.IpRender.RenderIp
|
||||
import lila.user.{ Holder, User }
|
||||
|
||||
import controllers.routes
|
||||
|
||||
object communication {
|
||||
|
||||
def apply(
|
||||
|
@ -21,7 +22,7 @@ object communication {
|
|||
history: List[lila.mod.Modlog],
|
||||
logins: lila.security.UserLogins.TableData,
|
||||
priv: Boolean
|
||||
)(implicit ctx: Context) =
|
||||
)(implicit ctx: Context, renderIp: RenderIp) =
|
||||
views.html.base.layout(
|
||||
title = u.username + " communications",
|
||||
moreCss = frag(
|
||||
|
@ -56,7 +57,7 @@ object communication {
|
|||
),
|
||||
isGranted(_.UserModView) option frag(
|
||||
div(cls := "mod-zone none"),
|
||||
views.html.user.mod.otherUsers(mod, u, logins)(ctx)(cls := "communication__logins")
|
||||
views.html.user.mod.otherUsers(mod, u, logins)(ctx, renderIp)(cls := "communication__logins")
|
||||
),
|
||||
history.nonEmpty option frag(
|
||||
h2("Moderation history"),
|
||||
|
|
|
@ -7,6 +7,7 @@ import lila.app.templating.Environment._
|
|||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.IpAddress
|
||||
import lila.security.FingerHash
|
||||
import lila.mod.IpRender.RenderIp
|
||||
|
||||
import controllers.routes
|
||||
|
||||
|
@ -80,7 +81,7 @@ object search {
|
|||
users: List[lila.user.User.WithEmails],
|
||||
uas: List[String],
|
||||
blocked: Boolean
|
||||
)(implicit ctx: Context) =
|
||||
)(implicit ctx: Context, renderIp: RenderIp) =
|
||||
views.html.base.layout(
|
||||
title = "IP address",
|
||||
moreCss = cssTag("mod.misc")
|
||||
|
@ -89,7 +90,7 @@ object search {
|
|||
views.html.mod.menu("search"),
|
||||
div(cls := "mod-search page-menu__content box")(
|
||||
div(cls := "box__top")(
|
||||
h1("IP address: ", address.value),
|
||||
h1("IP address: ", renderIp(address)),
|
||||
postForm(cls := "box__top__actions", action := routes.Mod.singleIpBan(!blocked, address.value))(
|
||||
submitButton(
|
||||
cls := List(
|
||||
|
@ -99,7 +100,7 @@ object search {
|
|||
)(if (blocked) "Banned" else "Ban this IP")
|
||||
)
|
||||
),
|
||||
div(cls := "box__pad")(
|
||||
isGranted(_.Admin) option div(cls := "box__pad")(
|
||||
h2("User agents"),
|
||||
ul(uas map { ua =>
|
||||
li(ua)
|
||||
|
|
|
@ -12,6 +12,7 @@ import lila.playban.RageSit
|
|||
import lila.security.Granter
|
||||
import lila.security.{ Permission, UserLogins }
|
||||
import lila.user.{ Holder, User }
|
||||
import lila.mod.IpRender.RenderIp
|
||||
|
||||
object mod {
|
||||
private def mzSection(key: String) = div(id := s"mz_$key", cls := "mz-section")
|
||||
|
@ -517,7 +518,10 @@ object mod {
|
|||
if (nb > 0) td(cls := "i", dataSort := nb)(content)
|
||||
else td
|
||||
|
||||
def otherUsers(mod: Holder, u: User, data: UserLogins.TableData)(implicit ctx: Context): Tag = {
|
||||
def otherUsers(mod: Holder, u: User, data: UserLogins.TableData)(implicit
|
||||
ctx: Context,
|
||||
renderIp: RenderIp
|
||||
): Tag = {
|
||||
import data._
|
||||
mzSection("others")(
|
||||
table(cls := "slist")(
|
||||
|
@ -551,7 +555,7 @@ object mod {
|
|||
val userNotes =
|
||||
notes.filter(n => n.to == o.id && (ctx.me.exists(n.isFrom) || isGranted(_.Admin)))
|
||||
tr(
|
||||
dataTags := s"${other.ips.mkString(" ")} ${other.fps.mkString(" ")}",
|
||||
dataTags := s"${other.ips.map(renderIp).mkString(" ")} ${other.fps.mkString(" ")}",
|
||||
cls := (o == u) option "same"
|
||||
)(
|
||||
if (o == u || Granter.canViewAltUsername(mod, o))
|
||||
|
@ -602,7 +606,10 @@ object mod {
|
|||
)
|
||||
}
|
||||
|
||||
def identification(spy: UserLogins)(implicit ctx: Context): Frag = {
|
||||
def identification(mod: Holder, logins: UserLogins)(implicit
|
||||
ctx: Context,
|
||||
renderIp: RenderIp
|
||||
): Frag = {
|
||||
val canIpBan = isGranted(_.IpBan)
|
||||
val canFpBan = isGranted(_.PrintBan)
|
||||
mzSection("identification")(
|
||||
|
@ -617,7 +624,7 @@ object mod {
|
|||
)
|
||||
),
|
||||
tbody(
|
||||
spy.distinctLocations.toList
|
||||
logins.distinctLocations.toList
|
||||
.sortBy(-_.seconds)
|
||||
.map { loc =>
|
||||
tr(
|
||||
|
@ -635,7 +642,7 @@ object mod {
|
|||
table(cls := "slist slist--sort")(
|
||||
thead(
|
||||
tr(
|
||||
th(pluralize("Device", spy.uas.size)),
|
||||
th(pluralize("Device", logins.uas.size)),
|
||||
th("OS"),
|
||||
th("Client"),
|
||||
sortNumberTh("Date"),
|
||||
|
@ -643,7 +650,7 @@ object mod {
|
|||
)
|
||||
),
|
||||
tbody(
|
||||
spy.uas
|
||||
logins.uas
|
||||
.sortBy(-_.seconds)
|
||||
.map { ua =>
|
||||
import ua.value.client._
|
||||
|
@ -666,7 +673,7 @@ object mod {
|
|||
table(cls := "slist spy_filter slist--sort")(
|
||||
thead(
|
||||
tr(
|
||||
th(pluralize("IP", spy.prints.size)),
|
||||
th(pluralize("IP", logins.prints.size)),
|
||||
sortNumberTh("Alts"),
|
||||
th,
|
||||
sortNumberTh("Date"),
|
||||
|
@ -674,9 +681,10 @@ object mod {
|
|||
)
|
||||
),
|
||||
tbody(
|
||||
spy.ips.sortBy(ip => (-ip.alts.score, -ip.ip.seconds)).map { ip =>
|
||||
logins.ips.sortBy(ip => (-ip.alts.score, -ip.ip.seconds)).map { ip =>
|
||||
val renderedIp = renderIp(ip.ip.value)
|
||||
tr(cls := ip.blocked option "blocked")(
|
||||
td(a(href := routes.Mod.singleIp(ip.ip.value.value))(ip.ip.value)),
|
||||
td(a(href := routes.Mod.singleIp(renderedIp))(renderedIp take 16)),
|
||||
td(dataSort := ip.alts.score)(altMarks(ip.alts)),
|
||||
td(ip.proxy option span(cls := "proxy")("PROXY")),
|
||||
td(dataSort := ip.ip.date.getMillis)(momentFromNowServer(ip.ip.date)),
|
||||
|
@ -698,14 +706,14 @@ object mod {
|
|||
table(cls := "slist spy_filter slist--sort")(
|
||||
thead(
|
||||
tr(
|
||||
th(pluralize("Print", spy.prints.size)),
|
||||
th(pluralize("Print", logins.prints.size)),
|
||||
sortNumberTh("Alts"),
|
||||
sortNumberTh("Date"),
|
||||
canFpBan option sortNumberTh
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
spy.prints.sortBy(fp => (-fp.alts.score, -fp.fp.seconds)).map { fp =>
|
||||
logins.prints.sortBy(fp => (-fp.alts.score, -fp.fp.seconds)).map { fp =>
|
||||
tr(cls := fp.banned option "blocked")(
|
||||
td(a(href := routes.Mod.print(fp.fp.value.value))(fp.fp.value)),
|
||||
td(dataSort := fp.alts.score)(altMarks(fp.alts)),
|
||||
|
|
|
@ -81,6 +81,7 @@ api {
|
|||
endpoint = "http://monitor.lichess.ovh:8086/write?db=events"
|
||||
env = "dev"
|
||||
}
|
||||
symmetric_cipher.key = "definitelynotthesameinprod"
|
||||
}
|
||||
accessibility {
|
||||
blind {
|
||||
|
|
|
@ -46,11 +46,16 @@ final class SymmetricCipher(secret: Secret) {
|
|||
def encrypt(text: String): Try[String] =
|
||||
bytes.encrypt(text.getBytes).map(Base64.getEncoder.encodeToString)
|
||||
|
||||
def decrypt(input: Array[Byte]): Try[Array[Byte]] =
|
||||
bytes.decrypt(input)
|
||||
|
||||
def decrypt(encryptedBase64String: String): Try[String] =
|
||||
decrypt(Base64.getDecoder.decode(encryptedBase64String)).map(_.map(_.toChar).mkString)
|
||||
bytes.decrypt(Base64.getDecoder.decode(encryptedBase64String)).map(_.map(_.toChar).mkString)
|
||||
}
|
||||
|
||||
object base64UrlFriendly {
|
||||
def encrypt(text: String): Try[String] =
|
||||
base64.encrypt(text).map(_.replace("/", "_"))
|
||||
|
||||
def decrypt(encrypted: String): Try[String] =
|
||||
base64.decrypt(encrypted.replace("_", "/"))
|
||||
}
|
||||
|
||||
object hex {
|
||||
|
|
|
@ -40,7 +40,8 @@ final class Env(
|
|||
noteApi: lila.user.NoteApi,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
slackApi: lila.irc.SlackApi,
|
||||
msgApi: lila.msg.MsgApi
|
||||
msgApi: lila.msg.MsgApi,
|
||||
symmetricCipher: lila.common.SymmetricCipher
|
||||
)(implicit
|
||||
ec: scala.concurrent.ExecutionContext,
|
||||
system: ActorSystem
|
||||
|
@ -78,6 +79,8 @@ final class Env(
|
|||
|
||||
lazy val presets = wire[ModPresetsApi]
|
||||
|
||||
lazy val ipRender = wire[IpRender]
|
||||
|
||||
private lazy val sandbagWatch = wire[SandbagWatch]
|
||||
|
||||
lila.common.Bus.subscribeFuns(
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package lila.mod
|
||||
|
||||
import com.github.blemale.scaffeine.Cache
|
||||
import com.github.blemale.scaffeine.LoadingCache
|
||||
|
||||
import lila.common.IpAddress
|
||||
import lila.common.SymmetricCipher
|
||||
import lila.memo.CacheApi
|
||||
import lila.security.Granter
|
||||
import lila.user.Holder
|
||||
|
||||
object IpRender {
|
||||
|
||||
type Raw = String
|
||||
type Rendered = String
|
||||
type RenderIp = IpAddress => Rendered
|
||||
}
|
||||
|
||||
final class IpRender(cipher: SymmetricCipher) {
|
||||
|
||||
import IpRender._
|
||||
|
||||
def apply(mod: Holder): RenderIp = if (Granter.is(_.Admin)(mod)) visible else encrypted
|
||||
|
||||
val visible = (ip: IpAddress) => ip.value
|
||||
|
||||
val encrypted = (ip: IpAddress) => cache.get(ip.value)
|
||||
|
||||
def decrypt(str: String) = IpAddress from {
|
||||
cipher.base64UrlFriendly.decrypt(str) getOrElse str
|
||||
}
|
||||
|
||||
private val cache: LoadingCache[Raw, Rendered] = CacheApi.scaffeineNoScheduler
|
||||
.maximumSize(4096)
|
||||
.build((raw: Raw) => cipher.base64UrlFriendly.encrypt(raw).get)
|
||||
}
|
|
@ -34,15 +34,6 @@ object Granter {
|
|||
}
|
||||
}
|
||||
|
||||
def canViewFp(mod: Holder, user: User): Boolean =
|
||||
is(_.PrintBan)(mod) || is(_.Hunter)(mod)
|
||||
|
||||
def canViewIp(mod: Holder, user: User): Boolean =
|
||||
is(_.IpBan)(mod)
|
||||
|
||||
def canViewEmail(mod: Holder, user: User): Boolean =
|
||||
is(_.Admin)(mod)
|
||||
|
||||
def canViewAltUsername(mod: Holder, user: User): Boolean =
|
||||
is(_.Admin)(mod) || {
|
||||
(is(_.Hunter)(mod) && user.marks.engine) ||
|
||||
|
|
|
@ -25,8 +25,9 @@ object Permission {
|
|||
case object SetKidMode extends Permission("SET_KID_MODE", List(UserModView), "Set Kid Mode")
|
||||
case object MarkEngine extends Permission("ADJUST_CHEATER", List(UserModView), "Mark as cheater")
|
||||
case object MarkBooster extends Permission("ADJUST_BOOSTER", List(UserModView), "Mark as booster")
|
||||
case object IpBan extends Permission("IP_BAN", List(UserModView), "IP ban")
|
||||
case object IpBan extends Permission("IP_BAN", List(UserModView, ViewPrintNoIP), "IP ban")
|
||||
case object PrintBan extends Permission("PRINT_BAN", List(UserModView), "Print ban")
|
||||
case object ViewPrintNoIP extends Permission("VIEW_PRINT_NOIP", "View Print & NoIP")
|
||||
case object DisableTwoFactor extends Permission("DISABLE_2FA", "Disable 2FA")
|
||||
case object CloseAccount extends Permission("CLOSE_ACCOUNT", List(UserModView), "Close/reopen account")
|
||||
case object SetTitle extends Permission("SET_TITLE", List(UserModView), "Set/unset title")
|
||||
|
@ -91,7 +92,8 @@ object Permission {
|
|||
UserSearch,
|
||||
RemoveRanking,
|
||||
ModMessage,
|
||||
ModNote
|
||||
ModNote,
|
||||
ViewPrintNoIP
|
||||
),
|
||||
"Hunter"
|
||||
)
|
||||
|
@ -109,7 +111,8 @@ object Permission {
|
|||
ModMessage,
|
||||
SeeReport,
|
||||
ModLog,
|
||||
ModNote
|
||||
ModNote,
|
||||
ViewPrintNoIP
|
||||
),
|
||||
"Shusher"
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue