| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- package com.weEat.services
- import play.api.Configuration
- import codes.reactive.scalatime._
- import com.github.t3hnar.bcrypt._
- import com.weEat.models.{User,Authorization}
- import java.time.Instant
- import javax.inject.{Inject,Singleton}
- import scala.concurrent.{ExecutionContext,Future}
- import scala.util.Success
- import scalaoauth2.provider._
- import org.mongodb.scala.model.Filters._
- import org.mongodb.scala.bson.DefaultBsonTransformers
- import org.mongodb.scala.bson.conversions.Bson
- import scala.language.implicitConversions
- @Singleton
- class OAuth2Service @Inject()(db: MongoDBService, config: Configuration)
- extends AuthorizationHandler[User]
- with ProtectedResourceHandler[Authorization]
- with DefaultBsonTransformers {
- implicit val ec: ExecutionContext = ExecutionContext.global
- import db.withCollection
- override def createAccessToken(auth: AuthInfo[User]) = {
- val newAuth = Authorization(auth.user)
- withCollection(Authorization) { (auths) =>
- auths.insertOne(newAuth).head().map((_) => newAuth.toToken())
- }.flatten
- }
- val devMode = config.getOptional[Boolean](s"oauth.dev").getOrElse(false)
- override def validateClient(
- cred: Option[ClientCredential],
- requ: AuthorizationRequest
- ): Future[Boolean] = {
- cred.fold[Future[Boolean]](Future.failed(new UnauthorizedClient("username required"))) { (cc) =>
- requ.grantType match {
- case OAuthGrantType.PASSWORD =>
- cc.clientSecret.fold[Future[Boolean]](Future.failed(new UnauthorizedClient("password required"))) { (sec) =>
- validateUsernamePassword(cc.clientId, sec).map(_.isDefined)
- }
- case OAuthGrantType.REFRESH_TOKEN => validateUsernameRefresh(
- cc.clientId,
- Authorization.decodeToken(RefreshTokenRequest(requ).refreshToken)
- )
- case OAuthGrantType.AUTHORIZATION_CODE => Future.failed(new UnsupportedGrantType())
- case OAuthGrantType.CLIENT_CREDENTIALS => Future.failed(new UnsupportedGrantType())
- case OAuthGrantType.IMPLICIT => Future.failed(new UnsupportedGrantType())
- }
- }
- }
- def validateUsernamePassword(
- username: String,
- pass: String
- ): Future[Option[User]] = withCollection(User) { (users) =>
- users.find(withDevModeCond(equal("email", username)))
- .first()
- .toFutureOption()
- // TODO: Replace bcrypt with something that doesn't use String objects.
- .filter {
- case Some(user) => pass.isBcryptedSafeBounded(user.password) == Success(true)
- case None => false
- }
-
- }.flatten
- private def withDevModeCond(bson: Bson) =
- if (devMode) bson else and(bson, not(equal("devOnly", true)))
- private def validateUsernameRefresh(username: String, refresh: Array[Byte]) =
- withCollection(Authorization) { (auths) =>
- auths.find(and(
- equal("email", username),
- equal("refreshToken", refresh)
- )).first().toFutureOption().map { _.nonEmpty }
- }.flatten
- override def findUser(
- cred: Option[ClientCredential],
- requ: AuthorizationRequest
- ): Future[Option[User]] = withCollection(User) { (users) =>
- cred.fold[Future[Option[User]]](Future.failed(new UnsupportedGrantType("client_id required"))) { (cc) =>
- users.find(withDevModeCond(equal("email", cc.clientId))).first().toFutureOption()
- }
- }.flatten
-
- /* 2020-07-25: Never re-issue the same authorization token. Always generate a
- * new one.
- */
- override def getStoredAccessToken(auth: AuthInfo[User]) = {
- Future.successful(None)
- }
- override def findAccessToken(token: String): Future[Option[AccessToken]] =
- withCollection(Authorization) { (auths) =>
- auths.find(
- freshAccessToken(Authorization.decodeToken(token))
- ).first().toFutureOption()
- .map(_.map(_.toToken()))
- }.flatten
- override def findAuthInfoByAccessToken(token: AccessToken) =
- withCollection(Authorization) { (auths) =>
- auths.find(freshAccessToken(Authorization.decodeToken(token.token)))
- .first()
- .toFutureOption()
- .map(_.map(auth => AuthInfo(auth, Some(auth.email), None, None)))
- }.flatten
- private def freshAccessToken(token: Array[Byte]) = and(
- gt("created", Instant.now() - Authorization.accessFreshTime),
- equal("accessToken", token)
- )
- implicit def optFut2FutOpt[A](
- x: Option[Future[A]]
- )(implicit ec: ExecutionContext): Future[Option[A]] = x.fold[Future[Option[A]]](Future.successful(None)) {
- _.map(Some(_))
- }
- override def findAuthInfoByRefreshToken(token: String) =
- withCollection(Authorization) { (auths) =>
- auths.find(freshRefreshToken(Authorization.decodeToken(token)))
- .first()
- .toFutureOption()
- }.flatten
- .map(authOpt => withCollection(User) { (users) =>
- optFut2FutOpt(authOpt.map({ (auth) => users.find(
- withDevModeCond(equal("_id", auth.userId))
- ).first()
- .toFuture()
- .map({ (user) => AuthInfo(user, Some(user.email), None, None)})
- }))
- }.flatten).flatten
-
- private def freshRefreshToken(token: Array[Byte]) = and(
- gt("created", Instant.now() - Authorization.refreshFreshTime),
- equal("refreshToken", token)
- )
- override def refreshAccessToken(
- auth: AuthInfo[User],
- refreshToken: String
- ): Future[AccessToken] = {
- val newAuth = Authorization(auth.user)
- withCollection(Authorization) { (auths) =>
- auths.replaceOne(
- and(
- equal("userId", auth.user._id),
- equal("refreshToken", Authorization.decodeToken(refreshToken)),
- gt("created", Instant.now() - Authorization.refreshFreshTime),
- ),
- newAuth
- ).head().map( (_) => newAuth.toToken())
- }.flatten
- }
- def revokeAccessToken(
- auth: AuthInfo[Authorization],
- user: String,
- refresh: String
- ): Future[Unit] =
- if (user != auth.user.email)
- Future.failed(
- new InvalidClient("Invalid client or client is not authorized")
- )
- else withCollection(Authorization) { (auths) =>
- auths.deleteOne(and(
- equal("userId", auth.user.userId),
- equal("refreshToken", Authorization.decodeToken(refresh))
- )).head().map { (_) => {} }
- }.flatten
- override def deleteAuthCode(token: String): Future[Unit] =
- Future.failed(new UnsupportedGrantType("Code grant authorizations are not supported."))
- override def findAuthInfoByCode(
- code: String
- ): Future[Option[AuthInfo[User]]] =
- Future.failed(new UnsupportedGrantType("Code grant authorizations are not supported."))
- }
|