lila/app/controllers/LilaController.scala

688 lines
26 KiB
Scala

package controllers
import ornicar.scalalib.Zero
import play.api.data.Form
import play.api.data.FormBinding
import play.api.http._
import play.api.i18n.Lang
import play.api.libs.json.{ JsArray, JsObject, JsString, JsValue, Json, Writes }
import play.api.mvc._
import scala.annotation.nowarn
import scalatags.Text.Frag
import lila.api.{ BodyContext, Context, HeaderContext, PageData }
import lila.app._
import lila.common.{ ApiVersion, HTTPRequest, Nonce }
import lila.i18n.I18nLangPicker
import lila.notify.Notification.Notifies
import lila.oauth.{ OAuthScope, OAuthServer }
import lila.security.{ AppealUser, FingerPrintedUser, Granter, Permission }
import lila.user.{ UserContext, User => UserModel, Holder }
abstract private[controllers] class LilaController(val env: Env)
extends BaseController
with ContentTypes
with RequestGetter
with ResponseWriter {
def controllerComponents = env.controllerComponents
implicit def executionContext = env.executionContext
implicit def scheduler = env.scheduler
implicit protected val LilaResultZero = Zero.instance[Result](Results.NotFound)
implicit final protected class LilaPimpedResult(result: Result) {
def fuccess = scala.concurrent.Future successful result
def flashSuccess(msg: String): Result = result.flashing("success" -> msg)
def flashSuccess: Result = flashSuccess("")
def flashFailure(msg: String): Result = result.flashing("failure" -> msg)
def flashFailure: Result = flashFailure("")
}
implicit protected def LilaFragToResult(frag: Frag): Result = Ok(frag)
implicit protected def makeApiVersion(v: Int) = ApiVersion(v)
implicit protected lazy val formBinding: FormBinding = parse.formBinding(parse.DefaultMaxTextLength)
protected val keyPages = new KeyPages(env)
protected val renderNotFound = keyPages.notFound _
protected val rateLimitedMsg = "Too many requests. Try again later."
protected val rateLimited = Results.TooManyRequests(rateLimitedMsg)
protected val rateLimitedJson = Results.TooManyRequests(jsonError(rateLimitedMsg))
protected val rateLimitedFu = rateLimited.fuccess
implicit protected def LilaFunitToResult(
@nowarn("cat=unused") funit: Funit
)(implicit req: RequestHeader): Fu[Result] =
negotiate(
html = fuccess(Ok("ok")),
api = _ => fuccess(jsonOkResult)
)
implicit def ctxLang(implicit ctx: Context) = ctx.lang
implicit def ctxReq(implicit ctx: Context) = ctx.req
implicit def reqConfig(implicit req: RequestHeader) = ui.EmbedConfig(req)
def reqLang(implicit req: RequestHeader) = I18nLangPicker(req)
protected def EnableSharedArrayBuffer(res: Result)(implicit req: RequestHeader): Result =
res.withHeaders(
"Cross-Origin-Opener-Policy" -> "same-origin",
"Cross-Origin-Embedder-Policy" -> (if (HTTPRequest isChrome96OrMore req) "credentialless"
else "require-corp")
)
protected def NoCache(res: Result): Result =
res.withHeaders(
CACHE_CONTROL -> "no-cache, no-store, must-revalidate",
EXPIRES -> "0"
)
protected def Open(f: Context => Fu[Result]): Action[Unit] =
Open(parse.empty)(f)
protected def Open[A](parser: BodyParser[A])(f: Context => Fu[Result]): Action[A] =
Action.async(parser)(handleOpen(f, _))
protected def OpenBody(f: BodyContext[_] => Fu[Result]): Action[AnyContent] =
OpenBody(parse.anyContent)(f)
protected def OpenBody[A](parser: BodyParser[A])(f: BodyContext[A] => Fu[Result]): Action[A] =
Action.async(parser) { req =>
CSRF(req) {
reqToCtx(req) flatMap f
}
}
protected def OpenOrScoped(selectors: OAuthScope.Selector*)(
open: Context => Fu[Result],
scoped: RequestHeader => UserModel => Fu[Result]
): Action[Unit] =
Action.async(parse.empty) { req =>
if (HTTPRequest isOAuth req) handleScoped(selectors)(scoped)(req)
else handleOpen(open, req)
}
private def handleOpen(f: Context => Fu[Result], req: RequestHeader): Fu[Result] =
CSRF(req) {
reqToCtx(req) flatMap f
}
// protected def OpenOrScopedBody(selectors: OAuthScope.Selector*)(
// open: BodyContext[_] => Fu[Result],
// scoped: Request[_] => UserModel => Fu[Result]
// ): Action[AnyContent] = OpenOrScopedBody(parse.anyContent)(selectors)(auth, scoped)
protected def OpenOrScopedBody[A](parser: BodyParser[A])(selectors: Seq[OAuthScope.Selector])(
open: BodyContext[A] => Fu[Result],
scoped: Request[A] => UserModel => Fu[Result]
): Action[A] =
Action.async(parser) { req =>
if (HTTPRequest isOAuth req) ScopedBody(parser)(selectors)(scoped)(req)
else OpenBody(parser)(open)(req)
}
protected def AnonOrScoped(selectors: OAuthScope.Selector*)(
f: RequestHeader => Option[UserModel] => Fu[Result]
): Action[Unit] =
Action.async(parse.empty) { req =>
if (HTTPRequest isOAuth req) handleScoped(selectors)((req: RequestHeader) => me => f(req)(me.some))(req)
else f(req)(none)
}
protected def AnonOrScopedBody[A](parser: BodyParser[A])(selectors: OAuthScope.Selector*)(
f: Request[A] => Option[UserModel] => Fu[Result]
): Action[A] =
Action.async(parser) { req =>
if (HTTPRequest isOAuth req)
ScopedBody(parser)(selectors)((req: Request[A]) => me => f(req)(me.some))(req)
else f(req)(none)
}
protected def AuthOrScoped(selectors: OAuthScope.Selector*)(
auth: Context => UserModel => Fu[Result],
scoped: RequestHeader => UserModel => Fu[Result]
): Action[Unit] =
Action.async(parse.empty) { req =>
if (HTTPRequest isOAuth req) handleScoped(selectors)(scoped)(req)
else handleAuth(auth, req)
}
protected def AuthOrScopedBody(selectors: OAuthScope.Selector*)(
auth: BodyContext[_] => UserModel => Fu[Result],
scoped: Request[_] => UserModel => Fu[Result]
): Action[AnyContent] = AuthOrScopedBody(parse.anyContent)(selectors)(auth, scoped)
protected def AuthOrScopedBody[A](parser: BodyParser[A])(selectors: Seq[OAuthScope.Selector])(
auth: BodyContext[A] => UserModel => Fu[Result],
scoped: Request[A] => UserModel => Fu[Result]
): Action[A] =
Action.async(parser) { req =>
if (HTTPRequest isOAuth req) ScopedBody(parser)(selectors)(scoped)(req)
else AuthBody(parser)(auth)(req)
}
protected def Auth(f: Context => UserModel => Fu[Result]): Action[Unit] =
Auth(parse.empty)(f)
protected def Auth[A](parser: BodyParser[A])(f: Context => UserModel => Fu[Result]): Action[A] =
Action.async(parser) { handleAuth(f, _) }
private def handleAuth(f: Context => UserModel => Fu[Result], req: RequestHeader): Fu[Result] =
CSRF(req) {
reqToCtx(req) flatMap { ctx =>
ctx.me.fold(authenticationFailed(ctx))(f(ctx))
}
}
protected def AuthBody(f: BodyContext[_] => UserModel => Fu[Result]): Action[AnyContent] =
AuthBody(parse.anyContent)(f)
protected def AuthBody[A](parser: BodyParser[A])(f: BodyContext[A] => UserModel => Fu[Result]): Action[A] =
Action.async(parser) { req =>
CSRF(req) {
reqToCtx(req) flatMap { ctx =>
ctx.me.fold(authenticationFailed(ctx))(f(ctx))
}
}
}
protected def Secure(perm: Permission.Selector)(f: Context => Holder => Fu[Result]): Action[AnyContent] =
Secure(perm(Permission))(f)
protected def Secure(perm: Permission)(f: Context => Holder => Fu[Result]): Action[AnyContent] =
Secure(parse.anyContent)(perm)(f)
protected def Secure[A](
parser: BodyParser[A]
)(perm: Permission)(f: Context => Holder => Fu[Result]): Action[A] =
Auth(parser) { implicit ctx => me =>
if (isGranted(perm)) f(ctx)(Holder(me)) else authorizationFailed
}
protected def SecureF(s: UserModel => Boolean)(f: Context => UserModel => Fu[Result]): Action[AnyContent] =
Auth(parse.anyContent) { implicit ctx => me =>
if (s(me)) f(ctx)(me) else authorizationFailed
}
protected def SecureBody[A](
parser: BodyParser[A]
)(perm: Permission)(f: BodyContext[A] => Holder => Fu[Result]): Action[A] =
AuthBody(parser) { implicit ctx => me =>
if (isGranted(perm)) f(ctx)(Holder(me)) else authorizationFailed
}
protected def SecureBody(
perm: Permission.Selector
)(f: BodyContext[_] => Holder => Fu[Result]): Action[AnyContent] =
SecureBody(parse.anyContent)(perm(Permission))(f)
protected def Scoped[A](
parser: BodyParser[A]
)(selectors: Seq[OAuthScope.Selector])(f: RequestHeader => UserModel => Fu[Result]): Action[A] =
Action.async(parser)(handleScoped(selectors)(f))
protected def Scoped(
selectors: OAuthScope.Selector*
)(f: RequestHeader => UserModel => Fu[Result]): Action[Unit] =
Scoped(parse.empty)(selectors)(f)
protected def ScopedBody[A](
parser: BodyParser[A]
)(selectors: Seq[OAuthScope.Selector])(f: Request[A] => UserModel => Fu[Result]): Action[A] =
Action.async(parser)(handleScoped(selectors)(f))
protected def ScopedBody(
selectors: OAuthScope.Selector*
)(f: Request[_] => UserModel => Fu[Result]): Action[AnyContent] =
ScopedBody(parse.anyContent)(selectors)(f)
private def handleScoped[R <: RequestHeader](
selectors: Seq[OAuthScope.Selector]
)(f: R => UserModel => Fu[Result])(req: R): Fu[Result] = {
val scopes = OAuthScope select selectors
env.security.api.oauthScoped(req, scopes) flatMap {
case Left(e) => handleScopedFail(scopes, e)
case Right(scoped) =>
lila.mon.user.oauth.request(true).increment()
f(req)(scoped.user) map OAuthServer.responseHeaders(scopes, scoped.scopes)
}
}
protected def handleScopedFail(scopes: Seq[OAuthScope], e: OAuthServer.AuthError) = e match {
case e @ lila.oauth.OAuthServer.MissingScope(available) =>
lila.mon.user.oauth.request(false).increment()
OAuthServer
.responseHeaders(scopes, available) {
Unauthorized(jsonError(e.message))
}
.fuccess
case e =>
lila.mon.user.oauth.request(false).increment()
OAuthServer.responseHeaders(scopes, Nil) { Unauthorized(jsonError(e.message)) }.fuccess
}
protected def SecureOrScoped(perm: Permission.Selector)(
secure: Context => Holder => Fu[Result],
scoped: RequestHeader => Holder => Fu[Result]
): Action[Unit] =
Action.async(parse.empty) { req =>
if (HTTPRequest isOAuth req) securedScopedAction(perm, req)(scoped)
else Secure(parse.empty)(perm(Permission))(secure)(req)
}
protected def SecureOrScopedBody(perm: Permission.Selector)(
secure: BodyContext[_] => Holder => Fu[Result],
scoped: RequestHeader => Holder => Fu[Result]
): Action[AnyContent] =
Action.async(parse.anyContent) { req =>
if (HTTPRequest isOAuth req) securedScopedAction(perm, req.map(_ => ()))(scoped)
else SecureBody(parse.anyContent)(perm(Permission))(secure)(req)
}
private def securedScopedAction(perm: Permission.Selector, req: Request[Unit])(
f: RequestHeader => Holder => Fu[Result]
) =
Scoped() { req => me =>
IfGranted(perm, req, me)(f(req)(Holder(me)))
}(req)
def IfGranted(perm: Permission.Selector)(f: => Fu[Result])(implicit ctx: Context): Fu[Result] =
if (isGranted(perm)) f else authorizationFailed
def IfGranted(perm: Permission.Selector, req: RequestHeader, me: UserModel)(f: => Fu[Result]): Fu[Result] =
if (isGranted(perm, me)) f else authorizationFailed(req)
protected def Firewall[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
if (env.security.firewall accepts ctx.req) a
else keyPages.blacklisted.fuccess
protected def NoTor(res: => Fu[Result])(implicit ctx: Context) =
if (env.security.tor isExitNode ctx.ip)
Unauthorized(views.html.auth.bits.tor()).fuccess
else res
protected def NoEngine[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
if (ctx.me.exists(_.marks.engine)) Forbidden(views.html.site.message.noEngine).fuccess else a
protected def NoBooster[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
if (ctx.me.exists(_.marks.boost)) Forbidden(views.html.site.message.noBooster).fuccess else a
protected def NoLame[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
NoEngine(NoBooster(a))
protected def NoBot[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
if (ctx.me.exists(_.isBot)) Forbidden(views.html.site.message.noBot).fuccess else a
protected def NoLameOrBot[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
NoLame(NoBot(a))
protected def NoLameOrBot[A <: Result](me: UserModel)(a: => Fu[A]): Fu[Result] =
if (me.isBot) notForBotAccounts.fuccess
else if (me.lame) Results.Forbidden.fuccess
else a
protected def NoShadowban[A <: Result](a: => Fu[A])(implicit ctx: Context): Fu[Result] =
if (ctx.me.exists(_.marks.troll)) notFound else a
protected def NoPlayban(a: => Fu[Result])(implicit ctx: Context): Fu[Result] =
ctx.userId.??(env.playban.api.currentBan) flatMap {
_.fold(a) { ban =>
negotiate(
html = keyPages.home(Results.Forbidden),
api = _ =>
fuccess {
Forbidden(
jsonError(
s"Banned from playing for ${ban.remainingMinutes} minutes. Reason: Too many aborts, unplayed games, or rage quits."
)
) as JSON
}
)
}
}
protected def NoCurrentGame(a: => Fu[Result])(implicit ctx: Context): Fu[Result] =
ctx.me.??(env.preloader.currentGameMyTurn) flatMap {
_.fold(a) { current =>
negotiate(
html = keyPages.home(Results.Forbidden),
api = _ =>
fuccess {
Forbidden(
jsonError(
s"You are already playing ${current.opponent}"
)
) as JSON
}
)
}
}
protected def NoPlaybanOrCurrent(a: => Fu[Result])(implicit ctx: Context): Fu[Result] =
NoPlayban(NoCurrentGame(a))
protected def JsonOk(body: JsValue): Result = Ok(body) as JSON
protected def JsonOk[A: Writes](body: A): Result = Ok(Json toJson body) as JSON
protected def JsonOk[A: Writes](fua: Fu[A]): Fu[Result] = fua dmap { JsonOk(_) }
protected val jsonOkBody = Json.obj("ok" -> true)
protected val jsonOkResult = JsonOk(jsonOkBody)
protected def JsonOptionOk[A: Writes](fua: Fu[Option[A]]) =
fua flatMap {
_.fold(notFoundJson())(a => fuccess(JsonOk(a)))
}
protected def FormResult[A](form: Form[A])(op: A => Fu[Result])(implicit req: Request[_]): Fu[Result] =
form
.bindFromRequest()
.fold(
form => fuccess(BadRequest(form.errors mkString "\n")),
op
)
protected def FormFuResult[A, B: Writeable: ContentTypeOf](
form: Form[A]
)(err: Form[A] => Fu[B])(op: A => Fu[Result])(implicit req: Request[_]) =
form
.bindFromRequest()
.fold(
form => err(form) dmap { BadRequest(_) },
data => op(data)
)
protected def FuRedirect(fua: Fu[Call]) = fua map { Redirect(_) }
protected def OptionOk[A, B: Writeable: ContentTypeOf](
fua: Fu[Option[A]]
)(op: A => B)(implicit ctx: Context): Fu[Result] =
OptionFuOk(fua) { a =>
fuccess(op(a))
}
protected def OptionFuOk[A, B: Writeable: ContentTypeOf](
fua: Fu[Option[A]]
)(op: A => Fu[B])(implicit ctx: Context) =
fua flatMap { _.fold(notFound(ctx))(a => op(a) map { Ok(_) }) }
protected def OptionFuRedirect[A](fua: Fu[Option[A]])(op: A => Fu[Call])(implicit ctx: Context) =
fua flatMap {
_.fold(notFound(ctx))(a =>
op(a) map { b =>
Redirect(b)
}
)
}
protected def OptionFuRedirectUrl[A](fua: Fu[Option[A]])(op: A => Fu[String])(implicit ctx: Context) =
fua flatMap {
_.fold(notFound(ctx))(a =>
op(a) map { b =>
Redirect(b)
}
)
}
protected def OptionResult[A](fua: Fu[Option[A]])(op: A => Result)(implicit ctx: Context) =
OptionFuResult(fua) { a =>
fuccess(op(a))
}
protected def OptionFuResult[A](fua: Fu[Option[A]])(op: A => Fu[Result])(implicit ctx: Context) =
fua flatMap { _.fold(notFound(ctx))(a => op(a)) }
def notFound(implicit ctx: Context): Fu[Result] =
negotiate(
html =
if (HTTPRequest isSynchronousHttp ctx.req) fuccess(renderNotFound(ctx))
else fuccess(Results.NotFound("Resource not found")),
api = _ => notFoundJson("Resource not found")
)
def notFoundJson(msg: String = "Not found"): Fu[Result] =
fuccess {
NotFound(jsonError(msg))
}
def jsonError[A: Writes](err: A): JsObject = Json.obj("error" -> err)
def notForBotAccounts =
BadRequest(
jsonError("This API endpoint is not for Bot accounts.")
)
def ridiculousBackwardCompatibleJsonError(err: JsObject): JsObject =
err ++ Json.obj("error" -> err)
protected def notFoundReq(req: RequestHeader): Fu[Result] =
reqToCtx(req) flatMap (x => notFound(x))
protected def isGranted(permission: Permission.Selector, user: UserModel): Boolean =
Granter(permission(Permission))(user)
protected def isGranted(permission: Permission.Selector)(implicit ctx: Context): Boolean =
isGranted(permission(Permission))
protected def isGranted(permission: Permission)(implicit ctx: Context): Boolean =
ctx.me ?? Granter(permission)
protected def authenticationFailed(implicit ctx: Context): Fu[Result] =
negotiate(
html = fuccess {
Redirect(
if (HTTPRequest.isClosedLoginPath(ctx.req)) routes.Auth.login else routes.Auth.signup
) withCookies env.lilaCookie
.session(env.security.api.AccessUri, ctx.req.uri)
},
api = _ =>
env.lilaCookie
.ensure(ctx.req) {
Unauthorized(jsonError("Login required"))
}
.fuccess
)
private val forbiddenJsonResult = Forbidden(jsonError("Authorization failed"))
protected def authorizationFailed(implicit ctx: Context): Fu[Result] =
negotiate(
html = if (HTTPRequest isSynchronousHttp ctx.req) fuccess {
Forbidden(views.html.site.message.authFailed)
}
else fuccess(Results.Forbidden("Authorization failed")),
api = _ => fuccess(forbiddenJsonResult)
)
protected def authorizationFailed(req: RequestHeader): Fu[Result] =
negotiate(
html = fuccess(Results.Forbidden("Authorization failed")),
api = _ => fuccess(forbiddenJsonResult)
)(req)
protected def negotiate(html: => Fu[Result], api: ApiVersion => Fu[Result])(implicit
req: RequestHeader
): Fu[Result] =
lila.api.Mobile.Api
.requestVersion(req)
.fold(html) { v =>
api(v).dmap(_ as JSON)
}
.dmap(_.withHeaders("Vary" -> "Accept"))
protected def reqToCtx(req: RequestHeader): Fu[HeaderContext] =
restoreUser(req) flatMap { case (d, impersonatedBy) =>
val lang = getAndSaveLang(req, d.map(_.user))
val ctx = UserContext(req, d.map(_.user), impersonatedBy, lang)
pageDataBuilder(ctx, d.exists(_.hasFingerPrint)) dmap { Context(ctx, _) }
}
protected def reqToCtx[A](req: Request[A]): Fu[BodyContext[A]] =
restoreUser(req) flatMap { case (d, impersonatedBy) =>
val lang = getAndSaveLang(req, d.map(_.user))
val ctx = UserContext(req, d.map(_.user), impersonatedBy, lang)
pageDataBuilder(ctx, d.exists(_.hasFingerPrint)) dmap { Context(ctx, _) }
}
private def getAndSaveLang(req: RequestHeader, user: Option[UserModel]): Lang = {
val lang = I18nLangPicker(req, user.flatMap(_.lang))
user.filter(_.lang.fold(true)(_ != lang.code)) foreach { env.user.repo.setLang(_, lang) }
lang
}
private def pageDataBuilder(ctx: UserContext, hasFingerPrint: Boolean): Fu[PageData] = {
val isPage = HTTPRequest isSynchronousHttp ctx.req
val nonce = isPage option Nonce.random
ctx.me.fold(fuccess(PageData.anon(ctx.req, nonce, blindMode(ctx)))) { me =>
env.pref.api.getPref(me, ctx.req) zip {
if (isPage) {
env.user.lightUserApi preloadUser me
val enabledId = me.enabled option me.id
enabledId.??(env.team.api.nbRequests) zip
enabledId.??(env.challenge.api.countInFor.get) zip
enabledId.??(id => env.notifyM.api.unreadCount(Notifies(id)).dmap(_.value)) zip
env.mod.inquiryApi.forMod(me)
} else
fuccess {
(((0, 0), 0), none)
}
} map { case (pref, (((teamNbRequests, nbChallenges), nbNotifications), inquiry)) =>
PageData(
teamNbRequests,
nbChallenges,
nbNotifications,
pref,
blindMode = blindMode(ctx),
hasFingerprint = hasFingerPrint,
hasClas = isGranted(_.Teacher, me) || env.clas.studentCache.isStudent(me.id),
inquiry = inquiry,
nonce = nonce
)
}
}
}
private def blindMode(implicit ctx: UserContext) =
ctx.req.cookies.get(env.api.config.accessibility.blindCookieName) ?? { c =>
c.value.nonEmpty && c.value == env.api.config.accessibility.hash
}
// user, impersonatedBy
type RestoredUser = (Option[FingerPrintedUser], Option[UserModel])
private def restoreUser(req: RequestHeader): Fu[RestoredUser] =
env.security.api restoreUser req dmap {
case Some(Left(AppealUser(user))) if HTTPRequest.isClosedLoginPath(req) =>
FingerPrintedUser(user, true).some
case Some(Right(d)) if !env.net.isProd =>
d.copy(user =
d.user
.addRole(lila.security.Permission.Beta.dbKey)
.addRole(lila.security.Permission.Prismic.dbKey)
).some
case Some(Right(d)) => d.some
case _ => none
} flatMap {
case None => fuccess(None -> None)
case Some(d) =>
env.mod.impersonate.impersonating(d.user) map {
_.fold[RestoredUser](d.some -> None) { impersonated =>
FingerPrintedUser(impersonated, hasFingerPrint = true).some -> d.user.some
}
}
}
protected val csrfCheck = env.security.csrfRequestHandler.check _
protected val csrfForbiddenResult = Forbidden("Cross origin request forbidden").fuccess
private def CSRF(req: RequestHeader)(f: => Fu[Result]): Fu[Result] =
if (csrfCheck(req)) f else csrfForbiddenResult
protected def XhrOnly(res: => Fu[Result])(implicit ctx: Context) =
if (HTTPRequest isXhr ctx.req) res else notFound
protected def XhrOrRedirectHome(res: => Fu[Result])(implicit ctx: Context) =
if (HTTPRequest isXhr ctx.req) res
else Redirect(routes.Lobby.home).fuccess
protected def Reasonable(
page: Int,
max: Int = 40,
errorPage: => Fu[Result] = BadRequest("resource too old").fuccess
)(result: => Fu[Result]): Fu[Result] =
if (page < max && page > 0) result else errorPage
protected def NotForKids(f: => Fu[Result])(implicit ctx: Context) =
if (ctx.kid) notFound else f
protected def OnlyHumans(result: => Fu[Result])(implicit ctx: lila.api.Context) =
if (HTTPRequest isCrawler ctx.req) notFound else result
protected def OnlyHumansAndFacebookOrTwitter(result: => Fu[Result])(implicit ctx: lila.api.Context) =
if (HTTPRequest isFacebookOrTwitterBot ctx.req) result
else if (HTTPRequest isCrawler ctx.req) fuccess(NotFound)
else result
protected def NotManaged(result: => Fu[Result])(implicit ctx: Context) =
ctx.me.??(env.clas.api.student.isManaged) flatMap {
case true => notFound
case _ => result
}
private val jsonGlobalErrorRenamer = {
import play.api.libs.json._
__.json update (
(__ \ "global").json copyFrom (__ \ "").json.pick
) andThen (__ \ "").json.prune
}
protected def errorsAsJson(form: Form[_])(implicit lang: Lang): JsObject = {
val json = JsObject(
form.errors
.groupBy(_.key)
.view
.mapValues { errors =>
JsArray {
errors.map { e =>
JsString(lila.i18n.Translator.txt.literal(e.message, e.args, lang))
}
}
}
.toMap
)
json validate jsonGlobalErrorRenamer getOrElse json
}
protected def apiFormError(form: Form[_]): JsObject =
Json.obj("error" -> errorsAsJson(form)(lila.i18n.defaultLang))
protected def jsonFormError(err: Form[_])(implicit lang: Lang) =
fuccess(BadRequest(ridiculousBackwardCompatibleJsonError(errorsAsJson(err))))
protected def jsonFormErrorDefaultLang(err: Form[_]) =
jsonFormError(err)(lila.i18n.defaultLang)
protected def jsonFormErrorFor(err: Form[_], req: RequestHeader, user: Option[UserModel]) =
jsonFormError(err)(I18nLangPicker(req, user.flatMap(_.lang)))
protected def newJsonFormError(err: Form[_])(implicit lang: Lang) =
fuccess(BadRequest(errorsAsJson(err)))
protected def pageHit(req: RequestHeader): Unit =
if (HTTPRequest isHuman req) lila.mon.http.path(req.path).increment().unit
protected def makeCustomResult(status: Int, reasonPhrase: String) =
Result(
header = new ResponseHeader(status, reasonPhrase = reasonPhrase.some).pp,
body = play.api.http.HttpEntity.NoEntity
)
protected def pageHit(implicit ctx: lila.api.Context): Unit = pageHit(ctx.req)
protected val noProxyBufferHeader = "X-Accel-Buffering" -> "no"
protected val noProxyBuffer = (res: Result) => res.withHeaders(noProxyBufferHeader)
protected def asAttachment(name: String) = (res: Result) =>
res.withHeaders(CONTENT_DISPOSITION -> s"attachment; filename=$name")
protected def asAttachmentStream(name: String) = (res: Result) => noProxyBuffer(asAttachment(name)(res))
protected val pgnContentType = "application/x-chess-pgn"
protected val ndJsonContentType = "application/x-ndjson"
protected val csvContentType = "text/csv"
}