OAuth2Service.scala 6.2 KB

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