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._
|
2021-06-15 01:15:10 -06:00
|
|
|
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
|
2021-06-22 10:43:41 -06:00
|
|
|
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),
|
2021-06-15 01:15:10 -06:00
|
|
|
responseType = get("response_type", req),
|
|
|
|
redirectUri = get("redirect_uri", req),
|
|
|
|
state = get("state", req),
|
|
|
|
codeChallengeMethod = get("code_challenge_method", req),
|
2021-06-28 03:57:44 -06:00
|
|
|
codeChallenge = get("code_challenge", req),
|
2021-06-15 01:15:10 -06:00
|
|
|
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 {
|
2021-06-16 01:11:42 -06:00
|
|
|
case Validated.Valid(authorized) =>
|
|
|
|
env.oAuth.authorizationApi.create(authorized) map { code =>
|
2021-06-28 11:21:52 -06:00
|
|
|
SeeOther(authorized.redirectUrl(code))
|
2021-06-16 01:11:42 -06:00
|
|
|
}
|
2021-06-28 11:21:52 -06:00
|
|
|
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(
|
2021-06-16 07:45:49 -06:00
|
|
|
"grant_type" -> optional(text),
|
|
|
|
"code" -> optional(text),
|
|
|
|
"code_verifier" -> optional(text),
|
2021-07-03 04:12:58 -06:00
|
|
|
"client_id" -> optional(text),
|
2021-06-16 07:45:49 -06:00
|
|
|
"redirect_uri" -> optional(text),
|
2021-07-03 04:12:58 -06:00
|
|
|
"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
|
|
|
}
|
2021-06-16 07:45:49 -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
|
|
|
|
2021-07-03 04:12:58 -06:00
|
|
|
def legacyTokenApply =
|
|
|
|
Action.async(parse.form(accessTokenRequestForm)) { implicit req =>
|
2021-07-05 03:29:22 -06:00
|
|
|
req.body.prepareLegacy(AccessTokenRequest.BasicAuth from req) match {
|
2021-07-03 04:12:58 -06:00
|
|
|
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,
|
2021-07-03 04:12:58 -06:00
|
|
|
"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
|
|
|
|
2021-06-22 10:43:41 -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-22 10:43:41 -06:00
|
|
|
}
|
|
|
|
|
2021-06-19 15:49:14 -06:00
|
|
|
private val revokeClientForm = Form(single("origin" -> text))
|
|
|
|
|
|
|
|
def revokeClient =
|
|
|
|
AuthBody { implicit ctx => me =>
|
2021-06-22 10:43:41 -06:00
|
|
|
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,
|
2021-07-03 09:03:59 -06:00
|
|
|
origin => env.oAuth.tokenApi.revokeByClientOrigin(origin, me) inject NoContent
|
2021-06-19 15:49:14 -06:00
|
|
|
)
|
|
|
|
}
|
2021-07-13 12:35:02 -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
|
|
|
}
|