OAuth2Service.scala 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package com.weEat.services
  2. import play.api.Configuration
  3. import codes.reactive.scalatime._
  4. import com.github.t3hnar.bcrypt._
  5. import com.weEat.models.{User,Authorization}
  6. import java.time.Instant
  7. import javax.inject.{Inject,Singleton}
  8. import scala.concurrent.{ExecutionContext,Future}
  9. import scala.util.Success
  10. import scalaoauth2.provider._
  11. import org.mongodb.scala.model.Filters._
  12. import org.mongodb.scala.bson.DefaultBsonTransformers
  13. import org.mongodb.scala.bson.conversions.Bson
  14. import scala.language.implicitConversions
  15. @Singleton
  16. class OAuth2Service @Inject()(db: MongoDBService, config: Configuration)
  17. extends AuthorizationHandler[User]
  18. with ProtectedResourceHandler[Authorization]
  19. with DefaultBsonTransformers {
  20. implicit val ec: ExecutionContext = ExecutionContext.global
  21. import db.withCollection
  22. override def createAccessToken(auth: AuthInfo[User]) = {
  23. val newAuth = Authorization(auth.user)
  24. withCollection(Authorization) { (auths) =>
  25. auths.insertOne(newAuth).head().map((_) => newAuth.toToken())
  26. }.flatten
  27. }
  28. val devMode = config.getOptional[Boolean](s"oauth.dev").getOrElse(false)
  29. override def validateClient(
  30. cred: Option[ClientCredential],
  31. requ: AuthorizationRequest
  32. ): Future[Boolean] = {
  33. cred.fold[Future[Boolean]](Future.failed(new UnauthorizedClient("username required"))) { (cc) =>
  34. requ.grantType match {
  35. case OAuthGrantType.PASSWORD =>
  36. cc.clientSecret.fold[Future[Boolean]](Future.failed(new UnauthorizedClient("password required"))) { (sec) =>
  37. validateUsernamePassword(cc.clientId, sec).map(_.isDefined)
  38. }
  39. case OAuthGrantType.REFRESH_TOKEN => validateUsernameRefresh(
  40. cc.clientId,
  41. Authorization.decodeToken(RefreshTokenRequest(requ).refreshToken)
  42. )
  43. case OAuthGrantType.AUTHORIZATION_CODE => Future.failed(new UnsupportedGrantType())
  44. case OAuthGrantType.CLIENT_CREDENTIALS => Future.failed(new UnsupportedGrantType())
  45. case OAuthGrantType.IMPLICIT => Future.failed(new UnsupportedGrantType())
  46. }
  47. }
  48. }
  49. def validateUsernamePassword(
  50. username: String,
  51. pass: String
  52. ): Future[Option[User]] = withCollection(User) { (users) =>
  53. users.find(withDevModeCond(equal("email", username)))
  54. .first()
  55. .toFutureOption()
  56. // TODO: Replace bcrypt with something that doesn't use String objects.
  57. .filter {
  58. case Some(user) => pass.isBcryptedSafeBounded(user.password) == Success(true)
  59. case None => false
  60. }
  61. }.flatten
  62. private def withDevModeCond(bson: Bson) =
  63. if (devMode) bson else and(bson, not(equal("devOnly", true)))
  64. private def validateUsernameRefresh(username: String, refresh: Array[Byte]) =
  65. withCollection(Authorization) { (auths) =>
  66. auths.find(and(
  67. equal("email", username),
  68. equal("refreshToken", refresh)
  69. )).first().toFutureOption().map { _.nonEmpty }
  70. }.flatten
  71. override def findUser(
  72. cred: Option[ClientCredential],
  73. requ: AuthorizationRequest
  74. ): Future[Option[User]] = withCollection(User) { (users) =>
  75. cred.fold[Future[Option[User]]](Future.failed(new UnsupportedGrantType("client_id required"))) { (cc) =>
  76. users.find(withDevModeCond(equal("email", cc.clientId))).first().toFutureOption()
  77. }
  78. }.flatten
  79. /* 2020-07-25: Never re-issue the same authorization token. Always generate a
  80. * new one.
  81. */
  82. override def getStoredAccessToken(auth: AuthInfo[User]) = {
  83. Future.successful(None)
  84. }
  85. override def findAccessToken(token: String): Future[Option[AccessToken]] =
  86. withCollection(Authorization) { (auths) =>
  87. auths.find(
  88. freshAccessToken(Authorization.decodeToken(token))
  89. ).first().toFutureOption()
  90. .map(_.map(_.toToken()))
  91. }.flatten
  92. override def findAuthInfoByAccessToken(token: AccessToken) =
  93. withCollection(Authorization) { (auths) =>
  94. auths.find(freshAccessToken(Authorization.decodeToken(token.token)))
  95. .first()
  96. .toFutureOption()
  97. .map(_.map(auth => AuthInfo(auth, Some(auth.email), None, None)))
  98. }.flatten
  99. private def freshAccessToken(token: Array[Byte]) = and(
  100. gt("created", Instant.now() - Authorization.accessFreshTime),
  101. equal("accessToken", token)
  102. )
  103. implicit def optFut2FutOpt[A](
  104. x: Option[Future[A]]
  105. )(implicit ec: ExecutionContext): Future[Option[A]] = x.fold[Future[Option[A]]](Future.successful(None)) {
  106. _.map(Some(_))
  107. }
  108. override def findAuthInfoByRefreshToken(token: String) =
  109. withCollection(Authorization) { (auths) =>
  110. auths.find(freshRefreshToken(Authorization.decodeToken(token)))
  111. .first()
  112. .toFutureOption()
  113. }.flatten
  114. .map(authOpt => withCollection(User) { (users) =>
  115. optFut2FutOpt(authOpt.map({ (auth) => users.find(
  116. withDevModeCond(equal("_id", auth.userId))
  117. ).first()
  118. .toFuture()
  119. .map({ (user) => AuthInfo(user, Some(user.email), None, None)})
  120. }))
  121. }.flatten).flatten
  122. private def freshRefreshToken(token: Array[Byte]) = and(
  123. gt("created", Instant.now() - Authorization.refreshFreshTime),
  124. equal("refreshToken", token)
  125. )
  126. override def refreshAccessToken(
  127. auth: AuthInfo[User],
  128. refreshToken: String
  129. ): Future[AccessToken] = {
  130. val newAuth = Authorization(auth.user)
  131. withCollection(Authorization) { (auths) =>
  132. auths.replaceOne(
  133. and(
  134. equal("userId", auth.user._id),
  135. equal("refreshToken", Authorization.decodeToken(refreshToken)),
  136. gt("created", Instant.now() - Authorization.refreshFreshTime),
  137. ),
  138. newAuth
  139. ).head().map( (_) => newAuth.toToken())
  140. }.flatten
  141. }
  142. def revokeAccessToken(
  143. auth: AuthInfo[Authorization],
  144. user: String,
  145. refresh: String
  146. ): Future[Unit] =
  147. if (user != auth.user.email)
  148. Future.failed(
  149. new InvalidClient("Invalid client or client is not authorized")
  150. )
  151. else withCollection(Authorization) { (auths) =>
  152. auths.deleteOne(and(
  153. equal("userId", auth.user.userId),
  154. equal("refreshToken", Authorization.decodeToken(refresh))
  155. )).head().map { (_) => {} }
  156. }.flatten
  157. override def deleteAuthCode(token: String): Future[Unit] =
  158. Future.failed(new UnsupportedGrantType("Code grant authorizations are not supported."))
  159. override def findAuthInfoByCode(
  160. code: String
  161. ): Future[Option[AuthInfo[User]]] =
  162. Future.failed(new UnsupportedGrantType("Code grant authorizations are not supported."))
  163. }