lila/app/controllers/Mod.scala

441 lines
16 KiB
Scala
Raw Normal View History

package controllers
2019-12-08 10:35:26 -07:00
import com.github.ghik.silencer.silent
2019-12-13 07:30:20 -07:00
import lila.api.{ BodyContext, Context }
import lila.app._
2017-09-04 11:41:58 -06:00
import lila.chat.Chat
2019-12-13 07:30:20 -07:00
import lila.common.{ EmailAddress, HTTPRequest, IpAddress }
2019-07-22 04:48:52 -06:00
import lila.mod.UserSearch
2019-12-04 18:47:46 -07:00
import lila.report.{ Suspect, Mod => AsMod }
2019-12-13 07:30:20 -07:00
import lila.security.{ FingerHash, Permission }
2019-12-04 16:39:16 -07:00
import lila.user.{ User => UserModel, Title }
import ornicar.scalalib.Zero
2014-02-26 17:18:09 -07:00
import views._
import play.api.data._
import play.api.data.Forms._
import play.api.mvc._
2019-12-04 18:47:46 -07:00
final class Mod(
env: Env,
2019-12-05 14:51:18 -07:00
reportC: => Report,
userC: => User
2019-12-04 18:47:46 -07:00
) extends LilaController(env) {
2019-12-13 07:30:20 -07:00
private def modApi = env.mod.api
2019-12-04 16:39:16 -07:00
private def modLogApi = env.mod.logApi
private def assessApi = env.mod.assessApi
2019-12-13 07:30:20 -07:00
def engine(username: String, v: Boolean) =
OAuthModBody(_.MarkEngine) { me =>
withSuspect(username) { sus =>
for {
inquiry <- env.report.api.inquiries ofModId me.id
_ <- modApi.setEngine(AsMod(me), sus, v)
} yield (inquiry, sus).some
}
}(ctx =>
me => {
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
}
)
2013-05-10 03:56:34 -06:00
2016-10-17 04:01:00 -06:00
def publicChat = Secure(_.ChatTimeout) { implicit ctx => _ =>
2019-12-04 16:39:16 -07:00
env.mod.publicChat.all map {
2016-10-17 04:01:00 -06:00
case (tournamentsAndChats, simulsAndChats) =>
Ok(html.mod.publicChat(tournamentsAndChats, simulsAndChats))
}
}
2019-12-13 07:30:20 -07:00
def booster(username: String, v: Boolean) =
OAuthModBody(_.MarkBooster) { me =>
withSuspect(username) { prev =>
for {
inquiry <- env.report.api.inquiries ofModId me.id
suspect <- modApi.setBooster(AsMod(me), prev, v)
} yield (inquiry, suspect).some
}
}(ctx =>
me => {
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
}
)
2016-10-17 04:01:00 -06:00
2019-12-13 07:30:20 -07:00
def troll(username: String, v: Boolean) =
OAuthModBody(_.Shadowban) { me =>
withSuspect(username) { prev =>
for {
2019-12-04 16:39:16 -07:00
inquiry <- env.report.api.inquiries ofModId me.id
2019-12-13 07:30:20 -07:00
suspect <- modApi.setTroll(AsMod(me), prev, v)
} yield (inquiry, suspect).some
2017-03-30 10:21:52 -06:00
}
2019-12-13 07:30:20 -07:00
}(ctx =>
me => {
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
}
)
2017-03-30 10:21:52 -06:00
2019-12-13 07:30:20 -07:00
def warn(username: String, subject: String) =
OAuthModBody(_.ModMessage) { me =>
lila.message.ModPreset.bySubject(subject) ?? { preset =>
withSuspect(username) { prev =>
for {
inquiry <- env.report.api.inquiries ofModId me.id
suspect <- modApi.setTroll(AsMod(me), prev, prev.user.troll)
thread <- env.message.api.sendPreset(me, suspect.user, preset)
_ <- env.mod.logApi.modMessage(thread.creatorId, thread.invitedId, thread.name)
} yield (inquiry, suspect).some
}
}
}(ctx =>
me => {
case (inquiry, suspect) => reportC.onInquiryClose(inquiry, me, suspect.some)(ctx)
}
)
2013-05-10 03:56:34 -06:00
2019-12-13 07:30:20 -07:00
def ipBan(username: String, v: Boolean) =
OAuthMod(_.IpBan) { _ => me =>
withSuspect(username) { sus =>
modApi.setBan(AsMod(me), sus, v) map some
}
}(actionResult(username))
def deletePmsAndChats(username: String) =
OAuthMod(_.Shadowban) { _ => _ =>
withSuspect(username) { sus =>
env.mod.publicChat.delete(sus) >>
env.message.api.deleteThreadsBy(sus.user) map some
}
}(actionResult(username))
2018-05-06 04:03:28 -06:00
def disableTwoFactor(username: String) = Secure(_.DisableTwoFactor) { implicit ctx => me =>
2019-12-08 11:12:00 -07:00
modApi.disableTwoFactor(me.id, username) >> userC.modZoneOrRedirect(username)
2018-05-06 04:03:28 -06:00
}
2019-12-13 07:30:20 -07:00
def closeAccount(username: String) =
OAuthMod(_.CloseAccount) { _ => me =>
env.user.repo named username flatMap {
_ ?? { user =>
modLogApi.closeAccount(me.id, user.id) >>
env.closeAccount(user.id, self = false) map some
}
}
2019-12-13 07:30:20 -07:00
}(actionResult(username))
2019-12-13 07:30:20 -07:00
def reopenAccount(username: String) =
OAuthMod(_.ReopenAccount) { _ => me =>
modApi.reopenAccount(me.id, username) map some
}(actionResult(username))
2014-02-01 06:13:22 -07:00
2019-12-13 07:30:20 -07:00
def reportban(username: String, v: Boolean) =
OAuthMod(_.ReportBan) { _ => me =>
withSuspect(username) { sus =>
modApi.setReportban(AsMod(me), sus, v) map some
}
}(actionResult(username))
2019-12-13 07:30:20 -07:00
def rankban(username: String, v: Boolean) =
OAuthMod(_.RemoveRanking) { _ => me =>
withSuspect(username) { sus =>
modApi.setRankban(AsMod(me), sus, v) map some
}
}(actionResult(username))
2017-08-03 04:43:29 -06:00
def impersonate(username: String) = Auth { implicit ctx => me =>
2019-12-04 16:39:16 -07:00
if (username == "-" && env.mod.impersonate.isImpersonated(me)) fuccess {
env.mod.impersonate.stop(me)
2017-08-03 04:43:29 -06:00
Redirect(routes.User.show(me.username))
2019-12-13 07:30:20 -07:00
} else if (isGranted(_.Impersonate)) OptionFuRedirect(env.user.repo named username) { user =>
2019-12-04 16:39:16 -07:00
env.mod.impersonate.start(me, user)
2017-08-03 04:43:29 -06:00
fuccess(routes.User.show(user.username))
2019-12-13 07:30:20 -07:00
} else notFound
2017-08-03 04:43:29 -06:00
}
2016-10-17 04:01:00 -06:00
def setTitle(username: String) = SecureBody(_.SetTitle) { implicit ctx => me =>
implicit def req = ctx.body
lila.user.DataForm.title.bindFromRequest.fold(
2019-12-08 10:35:26 -07:00
_ => fuccess(redirect(username, mod = true)),
2019-12-13 07:30:20 -07:00
title =>
modApi.setTitle(me.id, username, title map Title.apply) >>
env.security.automaticEmail.onTitleSet(username) >>-
env.user.lightUserApi.invalidate(UserModel normalize username) inject
redirect(username, mod = false)
2016-10-17 04:01:00 -06:00
)
2013-09-11 04:38:16 -06:00
}
2016-10-17 04:01:00 -06:00
def setEmail(username: String) = SecureBody(_.SetEmail) { implicit ctx => me =>
implicit def req = ctx.body
2019-12-04 18:47:46 -07:00
OptionFuResult(env.user.repo named username) { user =>
2019-12-13 07:30:20 -07:00
env.security.forms
.modEmail(user)
.bindFromRequest
.fold(
err => BadRequest(err.toString).fuccess,
rawEmail => {
val email = env.security.emailAddressValidator
.validate(EmailAddress(rawEmail)) err s"Invalid email ${rawEmail}"
modApi.setEmail(me.id, user.id, email.acceptable) inject redirect(user.username, mod = true)
}
)
2016-10-17 04:01:00 -06:00
}
2014-02-26 17:18:09 -07:00
}
2019-12-13 07:30:20 -07:00
def notifySlack(username: String) =
OAuthMod(_.ModNote) { _ => me =>
withSuspect(username) { sus =>
env.slack.api.userMod(user = sus.user, mod = me) map some
}
}(actionResult(username))
2015-08-12 05:17:16 -06:00
2019-12-08 10:35:26 -07:00
def log = Secure(_.ModLog) { implicit ctx => _ =>
2016-10-19 08:55:32 -06:00
modLogApi.recent map { html.mod.log(_) }
2013-05-10 03:56:34 -06:00
}
2014-01-16 01:46:01 -07:00
2019-12-13 07:30:20 -07:00
private def communications(username: String, priv: Boolean) =
Secure { perms =>
if (priv) perms.ViewPrivateComms else perms.Shadowban
} { implicit ctx => me =>
OptionFuOk(env.user.repo named username) { user =>
2019-12-20 09:50:00 -07:00
env.game.gameRepo
.recentPovsByUserFromSecondary(user, 80)
.mon(_.mod.comm.segment("recentPovs"))
.flatMap { povs =>
2019-12-13 07:30:20 -07:00
priv.?? {
2019-12-20 09:50:00 -07:00
env.chat.api.playerChat
.optionsByOrderedIds(povs.map(_.gameId).map(Chat.Id.apply))
.mon(_.mod.comm.segment("playerChats"))
2019-12-13 07:30:20 -07:00
} zip
2019-12-20 09:50:00 -07:00
priv.?? {
env.message.repo
.visibleOrDeletedByUser(user.id, 60)
.map {
_ filter (_ hasPostsWrittenBy user.id) take 30
}
.mon(_.mod.comm.segment("pms"))
} zip
(env.shutup.api getPublicLines user.id)
.mon(_.mod.comm.segment("publicChats")) zip
(env.security userSpy user)
.mon(_.mod.comm.segment("userSpy")) zip
env.user.noteApi
.forMod(user.id)
.mon(_.mod.comm.segment("notes")) zip
env.mod.logApi
.userHistory(user.id)
.mon(_.mod.comm.segment("history")) zip
env.report.api.inquiries
.ofModId(me.id)
.mon(_.mod.comm.segment("inquiries")) flatMap {
case chats ~ threads ~ publicLines ~ spy ~ notes ~ history ~ inquiry =>
lila.security.UserSpy
.withMeSortedWithEmails(env.user.repo, user, spy.otherUsers)
.mon(_.mod.comm.segment("otherUsers"))
.map { othersWithEmail =>
if (priv && !inquiry.??(_.isRecentCommOf(Suspect(user))))
env.slack.api.commlog(mod = me, user = user, inquiry.map(_.oldestAtom.by.value))
val povWithChats = (povs zip chats) collect {
case (p, Some(c)) if c.nonEmpty => p -> c
} take 15
val filteredNotes = notes.filter(_.from != "irwin")
html.mod.communication(
user,
povWithChats,
threads,
publicLines,
spy,
othersWithEmail,
filteredNotes,
history,
priv
)
}
}
}
}
2016-10-17 04:01:00 -06:00
}
2019-12-13 07:30:20 -07:00
def communicationPublic(username: String) = communications(username, false)
2017-09-04 11:41:58 -06:00
def communicationPrivate(username: String) = communications(username, true)
2019-12-08 10:35:26 -07:00
def ipIntel(ip: String) = Secure(_.IpBan) { _ => _ =>
2019-12-04 16:39:16 -07:00
env.security.ipIntel.failable(IpAddress(ip)).map { Ok(_) }.recover {
2016-10-17 04:01:00 -06:00
case e: Exception => InternalServerError(e.getMessage)
}
}
2017-05-02 08:40:37 -06:00
protected[controllers] def redirect(username: String, mod: Boolean = true) =
2019-08-02 03:33:37 -06:00
Redirect(userUrl(username, mod))
protected[controllers] def userUrl(username: String, mod: Boolean = true) =
s"${routes.User.show(username).url}${mod ?? "?mod"}"
2016-10-19 08:55:32 -06:00
def refreshUserAssess(username: String) = Secure(_.MarkEngine) { implicit ctx => me =>
2019-12-04 18:47:46 -07:00
OptionFuResult(env.user.repo named username) { user =>
2018-08-22 08:55:07 -06:00
assessApi.refreshAssessByUsername(username) >>
2019-12-04 16:39:16 -07:00
env.irwin.api.requests.fromMod(Suspect(user), AsMod(me)) >>
2019-12-04 18:47:46 -07:00
userC.renderModZoneActions(username)
2018-08-22 08:55:07 -06:00
}
}
2016-01-10 21:09:37 -07:00
2017-05-10 17:07:04 -06:00
def spontaneousInquiry(username: String) = Secure(_.SeeReport) { implicit ctx => me =>
2019-12-04 18:47:46 -07:00
OptionFuResult(env.user.repo named username) { user =>
2019-12-04 16:39:16 -07:00
env.report.api.inquiries.spontaneous(AsMod(me), Suspect(user)) inject redirect(user.username, true)
2017-05-10 17:07:04 -06:00
}
}
2019-12-08 10:35:26 -07:00
def gamify = Secure(_.SeeReport) { implicit ctx => _ =>
2019-12-04 16:39:16 -07:00
env.mod.gamify.leaderboards zip
env.mod.gamify.history(orCompute = true) map {
2019-12-13 07:30:20 -07:00
case (leaderboards, history) => Ok(html.mod.gamify.index(leaderboards, history))
}
2016-01-10 22:18:26 -07:00
}
2019-12-08 10:35:26 -07:00
def gamifyPeriod(periodStr: String) = Secure(_.SeeReport) { implicit ctx => _ =>
2016-10-17 04:01:00 -06:00
lila.mod.Gamify.Period(periodStr).fold(notFound) { period =>
2019-12-04 16:39:16 -07:00
env.mod.gamify.leaderboards map { leaderboards =>
2016-10-17 04:01:00 -06:00
Ok(html.mod.gamify.period(leaderboards, period))
2016-01-10 21:09:37 -07:00
}
2016-10-17 04:01:00 -06:00
}
2016-01-10 21:09:37 -07:00
}
2019-12-08 10:35:26 -07:00
def search = SecureBody(_.UserSearch) { implicit ctx => _ =>
implicit def req = ctx.body
2019-12-13 07:30:20 -07:00
val f = UserSearch.form
f.bindFromRequest.fold(
err => BadRequest(html.mod.search(err, Nil)).fuccess,
2019-12-04 16:39:16 -07:00
query => env.mod.search(query) map { html.mod.search(f.fill(query), _) }
)
}
2016-06-10 18:13:57 -06:00
2019-03-20 21:21:47 -06:00
protected[controllers] def searchTerm(q: String)(implicit ctx: Context) = {
val query = UserSearch exact q
2019-12-13 07:30:20 -07:00
env.mod.search(query) map { users =>
Ok(html.mod.search(UserSearch.form fill query, users))
}
2019-03-20 21:21:47 -06:00
}
2019-02-12 23:58:56 -07:00
2019-12-08 10:35:26 -07:00
def print(fh: String) = SecureBody(_.PrintBan) { implicit ctx => _ =>
2019-08-09 03:23:31 -06:00
val hash = FingerHash(fh)
for {
2019-12-13 07:30:20 -07:00
uids <- env.security.api recentUserIdsByFingerHash hash
users <- env.user.repo usersFromSecondary uids.reverse
2019-12-04 18:47:46 -07:00
withEmails <- env.user.repo withEmailsU users
2019-12-13 07:30:20 -07:00
uas <- env.security.api.printUas(hash)
2019-12-04 16:39:16 -07:00
} yield Ok(html.mod.search.print(hash, withEmails, uas, env.security.printBan blocks hash))
2019-08-09 03:23:31 -06:00
}
2019-12-08 10:35:26 -07:00
def printBan(v: Boolean, fh: String) = Secure(_.PrintBan) { _ => _ =>
2019-12-04 16:39:16 -07:00
env.security.printBan.toggle(FingerHash(fh), v) inject
2019-08-09 03:23:31 -06:00
Redirect(routes.Mod.print(fh))
}
2019-12-08 10:35:26 -07:00
def chatUser(username: String) = Secure(_.ChatTimeout) { implicit ctx => _ =>
2019-12-04 18:47:46 -07:00
implicit val lightUser = env.user.lightUserSync
2016-10-17 04:01:00 -06:00
JsonOptionOk {
2019-12-04 16:39:16 -07:00
env.chat.api.userChat userModInfo username map2 lila.chat.JsonView.userModInfo
2016-10-17 04:01:00 -06:00
}
2016-06-10 18:13:57 -06:00
}
2016-06-20 10:44:30 -06:00
2019-12-08 10:35:26 -07:00
def permissions(username: String) = Secure(_.ChangePermission) { implicit ctx => _ =>
2019-12-04 18:47:46 -07:00
OptionOk(env.user.repo named username) { user =>
2017-01-14 08:42:26 -07:00
html.mod.permissions(user)
2016-10-17 04:01:00 -06:00
}
2016-06-20 10:44:30 -06:00
}
2017-01-14 08:42:26 -07:00
def savePermissions(username: String) = SecureBody(_.ChangePermission) { implicit ctx => me =>
2016-10-17 04:01:00 -06:00
implicit def req = ctx.body
2017-01-14 08:58:24 -07:00
import lila.security.Permission
2019-12-04 18:47:46 -07:00
OptionFuResult(env.user.repo named username) { user =>
2019-12-13 07:30:20 -07:00
Form(
single(
"permissions" -> list(text.verifying { str =>
Permission.allButSuperAdmin.exists(_.name == str)
})
)
).bindFromRequest.fold(
2019-12-08 10:35:26 -07:00
_ => BadRequest(html.mod.permissions(user)).fuccess,
2016-10-17 04:01:00 -06:00
permissions =>
modApi.setPermissions(AsMod(me), user.username, Permission(permissions)) >> {
2017-12-27 09:46:50 -07:00
(Permission(permissions) diff Permission(user.roles) contains Permission.Coach) ??
2019-12-04 16:39:16 -07:00
env.security.automaticEmail.onBecomeCoach(user)
2018-04-11 17:34:14 -06:00
} >> {
2019-12-04 16:39:16 -07:00
Permission(permissions).exists(_ is Permission.SeeReport) ?? env.plan.api.setLifetime(user)
} inject redirect(user.username, true)
2016-10-17 04:01:00 -06:00
)
}
2016-06-20 10:44:30 -06:00
}
def emailConfirm = SecureBody(_.SetEmail) { implicit ctx => me =>
get("q") match {
case None => Ok(html.mod.emailConfirm("", none, none)).fuccess
case Some(rawQuery) =>
val query = rawQuery.trim.split(' ').toList
2019-12-13 07:30:20 -07:00
val email = query.headOption
.map(EmailAddress.apply) flatMap env.security.emailAddressValidator.validate
val username = query lift 1
def tryWith(setEmail: EmailAddress, q: String): Fu[Option[Result]] =
2019-12-04 16:39:16 -07:00
env.mod.search(UserSearch.exact(q)) flatMap {
2019-12-13 07:30:20 -07:00
case List(UserModel.WithEmails(user, _)) =>
(!user.everLoggedIn).?? {
lila.mon.user.register.modConfirmEmail.increment()
modApi.setEmail(me.id, user.id, setEmail)
} >>
env.user.repo.email(user.id) map { email =>
Ok(html.mod.emailConfirm("", user.some, email)).some
}
case _ => fuccess(none)
}
email.?? { em =>
2019-02-19 04:09:44 -07:00
tryWith(em.acceptable, em.acceptable.value) orElse {
username ?? { tryWith(em.acceptable, _) }
}
} getOrElse BadRequest(html.mod.emailConfirm(rawQuery, none, none)).fuccess
}
}
2019-12-08 10:35:26 -07:00
def chatPanic = Secure(_.Shadowban) { implicit ctx => _ =>
2019-12-04 16:39:16 -07:00
Ok(html.mod.chatPanic(env.chat.panic.get)).fuccess
2017-10-28 15:40:52 -06:00
}
2019-12-13 07:30:20 -07:00
def chatPanicPost =
OAuthMod(_.Shadowban) { req => me =>
val v = getBool("v", req)
env.chat.panic.set(v)
env.slack.api.chatPanic(me, v)
fuccess(().some)
}(_ => _ => _ => Redirect(routes.Mod.chatPanic).fuccess)
2017-10-28 15:40:52 -06:00
2019-12-08 10:35:26 -07:00
def eventStream = OAuthSecure(_.Admin) { _ => _ =>
2019-12-04 18:47:46 -07:00
noProxyBuffer(Ok.chunked(env.mod.stream())).fuccess
2018-07-29 23:01:25 -06:00
}
private def withSuspect[A](username: String)(f: Suspect => Fu[A])(implicit zero: Zero[A]): Fu[A] =
2019-12-04 16:39:16 -07:00
env.report.api getSuspect username flatMap {
_ ?? f
}
private def OAuthMod[A](perm: Permission.Selector)(f: RequestHeader => UserModel => Fu[Option[A]])(
2019-12-13 07:30:20 -07:00
secure: Context => UserModel => A => Fu[Result]
): Action[Unit] = SecureOrScoped(perm)(
secure = ctx => me => f(ctx.req)(me) flatMap { _ ?? secure(ctx)(me) },
2019-12-13 07:30:20 -07:00
scoped = req =>
me =>
f(req)(me) flatMap { res =>
res.isDefined ?? fuccess(jsonOkResult)
}
)
private def OAuthModBody[A](perm: Permission.Selector)(f: UserModel => Fu[Option[A]])(
2019-12-13 07:30:20 -07:00
secure: BodyContext[_] => UserModel => A => Fu[Result]
): Action[AnyContent] = SecureOrScopedBody(perm)(
secure = ctx => me => f(me) flatMap { _ ?? secure(ctx)(me) },
2019-12-13 07:30:20 -07:00
scoped = _ =>
me =>
f(me) flatMap { res =>
res.isDefined ?? fuccess(jsonOkResult)
}
)
2019-12-08 10:35:26 -07:00
private def actionResult(username: String)(ctx: Context)(@silent user: UserModel)(@silent res: Any) =
2019-12-04 18:47:46 -07:00
if (HTTPRequest isSynchronousHttp ctx.req) fuccess(redirect(username))
else userC.renderModZoneActions(username)(ctx)
}