lila/app/controllers/OAuth.scala

156 lines
5.3 KiB
Scala
Raw Normal View History

2021-06-14 16:05:27 -06:00
package controllers
2021-06-20 00:05:36 -06:00
import cats.data.Validated
2021-06-16 03:02:03 -06:00
import play.api.data.Form
import play.api.data.Forms._
import play.api.libs.json.Json
2021-06-20 00:05:36 -06:00
import play.api.mvc._
2021-06-15 04:04:18 -06:00
import scalatags.Text.all.stringFrag
2021-06-20 00:05:36 -06:00
import views._
2021-06-15 04:04:18 -06:00
import lila.api.Context
import lila.common.HTTPRequest
2021-06-20 00:05:36 -06:00
import lila.app._
2021-07-01 03:13:57 -06:00
import lila.oauth.{ AccessToken, AccessTokenRequest, AuthorizationRequest }
2021-06-14 16:05:27 -06:00
final class OAuth(env: Env) extends LilaController(env) {
2021-06-15 05:54:55 -06:00
private def reqToAuthorizationRequest(req: RequestHeader) =
AuthorizationRequest.Raw(
2021-06-16 00:05:27 -06:00
clientId = get("client_id", req),
responseType = get("response_type", req),
redirectUri = get("redirect_uri", req),
state = get("state", req),
codeChallengeMethod = get("code_challenge_method", req),
codeChallenge = get("code_challenge", req),
scope = get("scope", req)
)
2021-06-15 05:54:55 -06:00
private def withPrompt(f: AuthorizationRequest.Prompt => Fu[Result])(implicit ctx: Context) =
reqToAuthorizationRequest(ctx.req).prompt match {
case Validated.Valid(prompt) => f(prompt)
case Validated.Invalid(error) =>
BadRequest(html.site.message("Bad authorization request")(stringFrag(error.description))).fuccess
}
2021-06-15 04:04:18 -06:00
2021-06-14 16:05:27 -06:00
def authorize =
Open { implicit ctx =>
2021-06-15 05:54:55 -06:00
withPrompt { prompt =>
fuccess(ctx.me.fold(Redirect(routes.Auth.login.url, Map("referrer" -> List(ctx.req.uri)))) { me =>
2021-06-28 02:36:57 -06:00
Ok(
2021-07-01 03:13:57 -06:00
html.oAuth.authorize(prompt, me, s"${routes.OAuth.authorizeApply}?${ctx.req.rawQueryString}")
2021-06-28 02:36:57 -06:00
)
2021-06-15 05:54:55 -06:00
})
}
}
2021-06-28 02:36:57 -06:00
def legacyAuthorize =
Action { req =>
MovedPermanently(s"${routes.OAuth.authorize}?${req.rawQueryString}")
}
2021-06-15 05:54:55 -06:00
def authorizeApply =
Auth { implicit ctx => me =>
withPrompt { prompt =>
2021-06-28 01:38:07 -06:00
prompt.authorize(me, env.oAuth.legacyClientApi.apply) flatMap {
case Validated.Valid(authorized) =>
env.oAuth.authorizationApi.create(authorized) map { code =>
SeeOther(authorized.redirectUrl(code))
}
case Validated.Invalid(error) => SeeOther(prompt.redirectUri.error(error, prompt.state)).fuccess
2021-06-15 05:54:55 -06:00
}
}
2021-06-14 16:05:27 -06:00
}
2021-06-16 02:11:35 -06:00
2021-06-18 01:25:51 -06:00
private val accessTokenRequestForm = Form(
2021-06-16 03:02:03 -06:00
mapping(
"grant_type" -> optional(text),
"code" -> optional(text),
"code_verifier" -> optional(text),
"client_id" -> optional(text),
"redirect_uri" -> optional(text),
"client_secret" -> optional(text)
2021-06-16 03:02:03 -06:00
)(AccessTokenRequest.Raw.apply)(AccessTokenRequest.Raw.unapply)
)
def tokenApply =
2021-06-16 03:41:59 -06:00
Action.async(parse.form(accessTokenRequestForm)) { implicit req =>
req.body.prepare match {
2021-06-16 07:55:42 -06:00
case Validated.Valid(prepared) =>
2021-06-16 08:27:51 -06:00
env.oAuth.authorizationApi.consume(prepared) flatMap {
case Validated.Valid(granted) =>
2021-06-20 00:05:36 -06:00
env.oAuth.tokenApi.create(granted) map { token =>
Ok(
Json
.obj(
2021-06-23 08:55:10 -06:00
"token_type" -> "Bearer",
2021-07-07 04:48:08 -06:00
"access_token" -> token.plain.secret
2021-06-20 00:05:36 -06:00
)
.add("expires_in" -> token.expires.map(_.getSeconds - nowSeconds))
2021-06-16 13:07:52 -06:00
)
2021-06-20 00:05:36 -06:00
}
2021-06-16 08:27:51 -06:00
case Validated.Invalid(err) => BadRequest(err.toJson).fuccess
2021-06-16 07:55:42 -06:00
}
case Validated.Invalid(err) => BadRequest(err.toJson).fuccess
2021-06-16 03:41:59 -06:00
}
2021-06-16 02:11:35 -06:00
}
2021-06-19 15:49:14 -06:00
def legacyTokenApply =
Action.async(parse.form(accessTokenRequestForm)) { implicit req =>
req.body.prepareLegacy(AccessTokenRequest.BasicAuth from req) match {
case Validated.Valid(prepared) =>
env.oAuth.authorizationApi.consume(prepared) flatMap {
case Validated.Valid(granted) =>
env.oAuth.tokenApi.create(granted) map { token =>
Ok(
Json
.obj(
"token_type" -> "Bearer",
2021-07-07 04:48:08 -06:00
"access_token" -> token.plain.secret,
"refresh_token" -> s"invalid_for_bc_${lila.common.ThreadLocalRandom.nextString(17)}"
)
.add("expires_in" -> token.expires.map(_.getSeconds - nowSeconds))
)
}
case Validated.Invalid(err) => BadRequest(err.toJson).fuccess
}
case Validated.Invalid(err) => BadRequest(err.toJson).fuccess
}
}
2021-06-28 02:36:57 -06:00
def tokenRevoke =
2021-06-22 11:49:18 -06:00
Scoped() { implicit req => _ =>
HTTPRequest.bearer(req) ?? { token =>
2021-07-12 05:40:10 -06:00
env.oAuth.tokenApi.revoke(token) inject NoContent
2021-06-22 11:49:18 -06:00
}
}
2021-06-19 15:49:14 -06:00
private val revokeClientForm = Form(single("origin" -> text))
def revokeClient =
AuthBody { implicit ctx => me =>
implicit def body = ctx.body
2021-06-19 15:49:14 -06:00
revokeClientForm
.bindFromRequest()
.fold(
2021-06-19 16:16:41 -06:00
_ => BadRequest.fuccess,
origin => env.oAuth.tokenApi.revokeByClientOrigin(origin, me) inject NoContent
2021-06-19 15:49:14 -06:00
)
}
def challengeTokens =
ScopedBody(_.Web.Mod) { implicit req => me =>
if (isGranted(_.ApiChallengeAdmin, me))
lila.oauth.OAuthTokenForm.adminChallengeTokens
.bindFromRequest()
.fold(
err => BadRequest(apiFormError(err)).fuccess,
data =>
env.oAuth.tokenApi.adminChallengeTokens(data, me).map { tokens =>
JsonOk(tokens.view.mapValues(t => t.plain.secret).toMap)
}
)
else Unauthorized(jsonError("Missing permission")).fuccess
}
2021-06-14 16:05:27 -06:00
}