diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/AsyncAuth.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/AsyncAuth.scala index 87f08c6..e8c0087 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/AsyncAuth.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/AsyncAuth.scala @@ -6,7 +6,7 @@ import play.api.mvc._ import scala.concurrent.{ ExecutionContext, Future } trait AsyncAuth { - self: AuthConfig with Controller => + self: AuthConfig with AbstractController => def authorized(authority: Authority)(implicit request: RequestHeader, context: ExecutionContext): Future[Either[Result, (User, ResultUpdater)]] = { restoreUser collect { @@ -38,11 +38,7 @@ trait AsyncAuth { } private[auth] def extractToken(request: RequestHeader): Option[AuthenticityToken] = { - if (environment.mode == Mode.Test) { - request.headers.get("PLAY2_AUTH_TEST_TOKEN") orElse tokenAccessor.extract(request) - } else { - tokenAccessor.extract(request) - } + tokenAccessor.extract(request) } } diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthActionBuilders.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthActionBuilders.scala index 9718604..896968c 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthActionBuilders.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthActionBuilders.scala @@ -3,7 +3,7 @@ package jp.t2v.lab.play2.auth import play.api.mvc._ import scala.concurrent.Future -trait AuthActionBuilders extends AsyncAuth { self: AuthConfig with Controller => +trait AuthActionBuilders extends AsyncAuth { self: AuthConfig with AbstractController => final case class GenericOptionalAuthRequest[+A, R[+_] <: Request[_]](user: Option[User], underlying: R[A]) extends WrappedRequest[A](underlying.asInstanceOf[Request[A]]) final case class GenericAuthRequest[+A, R[+_] <: Request[_]](user: User, underlying: R[A]) extends WrappedRequest[A](underlying.asInstanceOf[Request[A]]) @@ -11,12 +11,14 @@ trait AuthActionBuilders extends AsyncAuth { self: AuthConfig with Controller => final case class GenericOptionalAuthFunction[R[+_] <: Request[_]]() extends ActionFunction[R, ({type L[+A] = GenericOptionalAuthRequest[A, R]})#L] { def invokeBlock[A](request: R[A], block: GenericOptionalAuthRequest[A, R] => Future[Result]) = { implicit val ctx = executionContext - restoreUser(request, executionContext) recover { + restoreUser(request, ctx) recover { case _ => None -> identity[Result] _ } flatMap { case (user, cookieUpdater) => block(GenericOptionalAuthRequest[A, R](user, request)).map(cookieUpdater) } } + + override protected def executionContext = self.controllerComponents.executionContext } final case class GenericAuthenticationRefiner[R[+_] <: Request[_]]() extends ActionRefiner[({type L[+A] = GenericOptionalAuthRequest[A, R]})#L, ({type L[+A] = GenericAuthRequest[A, R]})#L] { @@ -28,6 +30,8 @@ trait AuthActionBuilders extends AsyncAuth { self: AuthConfig with Controller => authenticationFailed(request).map(Left.apply[Result, GenericAuthRequest[A, R]]) } } + + override protected def executionContext = self.controllerComponents.executionContext } final case class GenericAuthorizationFilter[R[+_] <: Request[_]](authority: Authority) extends ActionFilter[({type L[+B] = GenericAuthRequest[B, R]})#L] { @@ -39,17 +43,19 @@ trait AuthActionBuilders extends AsyncAuth { self: AuthConfig with Controller => case _ => authorizationFailed(request, request.user, Some(authority)).map(Some.apply) } } + + override protected def executionContext = self.controllerComponents.executionContext } - final def composeOptionalAuthAction[R[+_] <: Request[_]](builder: ActionBuilder[R]): ActionBuilder[({type L[+A] = GenericOptionalAuthRequest[A, R]})#L] = { + final def composeOptionalAuthAction[R[+_] <: Request[_], B](builder: ActionBuilder[R, B]): ActionBuilder[({type L[+A] = GenericOptionalAuthRequest[A, R]})#L, B] = { builder.andThen[({type L[A] = GenericOptionalAuthRequest[A, R]})#L](GenericOptionalAuthFunction[R]()) } - final def composeAuthenticationAction[R[+_] <: Request[_]](builder: ActionBuilder[R]): ActionBuilder[({type L[+A] = GenericAuthRequest[A, R]})#L] = { - composeOptionalAuthAction[R](builder).andThen[({type L[+A] = GenericAuthRequest[A, R]})#L](GenericAuthenticationRefiner[R]()) + final def composeAuthenticationAction[R[+_] <: Request[_], B](builder: ActionBuilder[R, B]): ActionBuilder[({type L[+A] = GenericAuthRequest[A, R]})#L, B] = { + composeOptionalAuthAction(builder).andThen[({type L[+A] = GenericAuthRequest[A, R]})#L](GenericAuthenticationRefiner[R]()) } - final def composeAuthorizationAction[R[+_] <: Request[_]](builder: ActionBuilder[R])(authority: Authority): ActionBuilder[({type L[+A] = GenericAuthRequest[A, R]})#L] = { + final def composeAuthorizationAction[R[+_] <: Request[_], B](builder: ActionBuilder[R, B])(authority: Authority): ActionBuilder[({type L[+A] = GenericAuthRequest[A, R]})#L, B] = { composeAuthenticationAction(builder).andThen[({type L[+A] = GenericAuthRequest[A, R]})#L](GenericAuthorizationFilter[R](authority)) } @@ -59,8 +65,8 @@ trait AuthActionBuilders extends AsyncAuth { self: AuthConfig with Controller => final val AuthenticationRefiner: ActionRefiner[OptionalAuthRequest, AuthRequest] = GenericAuthenticationRefiner[Request]() final def AuthorizationFilter(authority: Authority): ActionFilter[AuthRequest] = GenericAuthorizationFilter[Request](authority) - final val OptionalAuthAction: ActionBuilder[OptionalAuthRequest] = composeOptionalAuthAction(Action) - final val AuthenticationAction: ActionBuilder[AuthRequest] = composeAuthenticationAction(Action) - final def AuthorizationAction(authority: Authority): ActionBuilder[AuthRequest] = composeAuthorizationAction(Action)(authority) + final val OptionalAuthAction: ActionBuilder[OptionalAuthRequest, AnyContent] = composeOptionalAuthAction(Action) + final val AuthenticationAction: ActionBuilder[AuthRequest, AnyContent] = composeAuthenticationAction(Action) + final def AuthorizationAction(authority: Authority): ActionBuilder[AuthRequest, AnyContent] = composeAuthorizationAction(Action)(authority) } diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthConfig.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthConfig.scala index 0ef7fa7..3b204ce 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthConfig.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthConfig.scala @@ -15,51 +15,18 @@ trait AuthConfig { type Authority - val environment: Environment - - val idContainer: AsyncIdContainer[Id] - - implicit def idTag: ClassTag[Id] - def sessionTimeoutInSeconds: Int def resolveUser(id: Id)(implicit context: ExecutionContext): Future[Option[User]] - def loginSucceeded(request: RequestHeader)(implicit context: ExecutionContext): Future[Result] - - def logoutSucceeded(request: RequestHeader)(implicit context: ExecutionContext): Future[Result] - def authenticationFailed(request: RequestHeader)(implicit context: ExecutionContext): Future[Result] def authorizationFailed(request: RequestHeader, user: User, authority: Option[Authority])(implicit context: ExecutionContext): Future[Result] def authorize(user: User, authority: Authority)(implicit context: ExecutionContext): Future[Boolean] - @deprecated("it will be deleted since 0.14.x. use CookieTokenAccessor constructor", since = "0.13.1") - final lazy val cookieName: String = throw new AssertionError("use tokenAccessor setting instead.") - - @deprecated("it will be deleted since 0.14.0. use CookieTokenAccessor constructor", since = "0.13.1") - final lazy val cookieSecureOption: Boolean = throw new AssertionError("use tokenAccessor setting instead.") - - @deprecated("it will be deleted since 0.14.0. use CookieTokenAccessor constructor", since = "0.13.1") - final lazy val cookieHttpOnlyOption: Boolean = throw new AssertionError("use tokenAccessor setting instead.") - - @deprecated("it will be deleted since 0.14.0. use CookieTokenAccessor constructor", since = "0.13.1") - final lazy val cookieDomainOption: Option[String] = throw new AssertionError("use tokenAccessor setting instead.") - - @deprecated("it will be deleted since 0.14.0. use CookieTokenAccessor constructor", since = "0.13.1") - final lazy val cookiePathOption: String = throw new AssertionError("use tokenAccessor setting instead.") - - @deprecated("it will be deleted since 0.14.0. use CookieTokenAccessor constructor", since = "0.13.1") - final lazy val isTransientCookie: Boolean = throw new AssertionError("use tokenAccessor setting instead.") + def idContainer: AsyncIdContainer[Id] - lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor( - cookieName = "PLAY2AUTH_SESS_ID", - cookieSecureOption = environment.mode == Mode.Prod, - cookieHttpOnlyOption = true, - cookieDomainOption = None, - cookiePathOption = "/", - cookieMaxAge = Some(sessionTimeoutInSeconds) - ) + def tokenAccessor: TokenAccessor } diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthCookieSigner.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthCookieSigner.scala new file mode 100644 index 0000000..72c43e6 --- /dev/null +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthCookieSigner.scala @@ -0,0 +1,19 @@ +package jp.t2v.lab.play2.auth + +import play.api.{Application, Play} +import play.api.libs.crypto.CookieSigner + +/** + * Code from play.api.libs.Crypto. Which is private object now. + */ +object AuthCookieSigner { + + private val cookieSignerCache: (Application) => CookieSigner = Application.instanceCache[CookieSigner] + + def cookieSigner: CookieSigner = { + Play.maybeApplication.fold { + sys.error("The global cookie signer instance requires a running application!") + }(cookieSignerCache) + } + +} diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthElement.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthElement.scala index 5362a60..ea36653 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/AuthElement.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/AuthElement.scala @@ -1,11 +1,13 @@ package jp.t2v.lab.play2.auth -import play.api.mvc.{Result, Controller} -import jp.t2v.lab.play2.stackc.{RequestWithAttributes, RequestAttributeKey, StackableController} +import play.api.mvc.{AbstractController, Result} +import jp.t2v.lab.play2.stackc.{RequestAttributeKey, RequestWithAttributes, StackableController} + import scala.concurrent.Future +import play.api.mvc.BaseController trait AuthElement extends StackableController with AsyncAuth { - self: Controller with AuthConfig => + self: AbstractController with AuthConfig => private[auth] case object AuthKey extends RequestAttributeKey[User] case object AuthorityKey extends RequestAttributeKey[Authority] @@ -33,7 +35,7 @@ trait AuthElement extends StackableController with AsyncAuth { } trait OptionalAuthElement extends StackableController with AsyncAuth { - self: Controller with AuthConfig => + self: AbstractController with AuthConfig => private[auth] case object AuthKey extends RequestAttributeKey[User] @@ -49,7 +51,7 @@ trait OptionalAuthElement extends StackableController with AsyncAuth { } trait AuthenticationElement extends StackableController with AsyncAuth { - self: Controller with AuthConfig => + self: AbstractController with AuthConfig => private[auth] case object AuthKey extends RequestAttributeKey[User] diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/CacheIdContainer.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/CacheIdContainer.scala index 1549be0..acbed68 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/CacheIdContainer.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/CacheIdContainer.scala @@ -1,6 +1,6 @@ package jp.t2v.lab.play2.auth -import play.api.cache.{ Cache, CacheApi } +import play.api.cache.AsyncCacheApi import play.api.Play._ import scala.annotation.tailrec @@ -10,13 +10,18 @@ import java.util.concurrent.TimeUnit import scala.concurrent.duration.Duration import scala.reflect.ClassTag +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext +import scala.concurrent.Await -class CacheIdContainer[Id: ClassTag] (cacheApi: CacheApi) extends IdContainer[Id] { +class CacheIdContainer[Id: ClassTag] (cacheApi: AsyncCacheApi)(implicit val ec: ExecutionContext) extends IdContainer[Id] { private[auth] val tokenSuffix = ":token" private[auth] val userIdSuffix = ":userId" private[auth] val random = new Random(new SecureRandom()) + private def intToDuration(seconds: Int): Duration = if (seconds == 0) Duration.Inf else seconds.seconds + def startNewSession(userId: Id, timeoutInSeconds: Int): AuthenticityToken = { removeByUserId(userId) val token = generate @@ -32,7 +37,7 @@ class CacheIdContainer[Id: ClassTag] (cacheApi: CacheApi) extends IdContainer[Id } private[auth] def removeByUserId(userId: Id) { - cacheApi.get[String](userId.toString + userIdSuffix) foreach unsetToken + cacheApi.get[String](userId.toString + userIdSuffix).foreach(x=>x.foreach(unsetToken))(ec) unsetUserId(userId) } @@ -48,7 +53,10 @@ class CacheIdContainer[Id: ClassTag] (cacheApi: CacheApi) extends IdContainer[Id cacheApi.remove(userId.toString + userIdSuffix) } - def get(token: AuthenticityToken) = cacheApi.get(token + tokenSuffix).map(_.asInstanceOf[Id]) + def get(token: AuthenticityToken) = { + val f = cacheApi.get(token + tokenSuffix).map(_.map(_.asInstanceOf[Id])) + Await.result(f, Duration.Inf) + } private[auth] def store(token: AuthenticityToken, userId: Id, timeoutInSeconds: Int) { def intToDuration(seconds: Int): Duration = if (seconds == 0) Duration.Inf else Duration(seconds, TimeUnit.SECONDS) diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/CookieTokenAccessor.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/CookieTokenAccessor.scala index e27f83d..3f1a463 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/CookieTokenAccessor.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/CookieTokenAccessor.scala @@ -21,6 +21,13 @@ class CookieTokenAccessor( } def delete(result: Result)(implicit request: RequestHeader): Result = { - result.discardingCookies(DiscardingCookie(cookieName)) + + // before... + //result.discardingCookies(DiscardingCookie(cookieName)) + + // Since the discardedMaxAge of the cookie is "-1.day.toSecond.toInt" and it gets + // caught in the check that it does not receive the minus of Max-age of HTMLUNIT, + // operation avoidance. In the latest version of Playframework, DiscardedMaxAge is corrected to 0 ing + result.withCookies(DiscardingCookie(cookieName).toCookie.copy(maxAge = Some(0))) } } diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/LoginLogout.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/LoginLogout.scala index 036c3b6..136b131 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/LoginLogout.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/LoginLogout.scala @@ -6,31 +6,24 @@ import play.api.libs.Crypto import scala.concurrent.{Future, ExecutionContext} trait Login { - self: Controller with AuthConfig => + self: AbstractController with AuthConfig => - def gotoLoginSucceeded(userId: Id)(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = { - gotoLoginSucceeded(userId, loginSucceeded(request)) + def markLoggedIn(userId: Id)(implicit request: RequestHeader, ctx: ExecutionContext): Result => Future[Result] = { result => + idContainer.startNewSession(userId, sessionTimeoutInSeconds).map(token => tokenAccessor.put(token)(result)) } - def gotoLoginSucceeded(userId: Id, result: => Future[Result])(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = for { - token <- idContainer.startNewSession(userId, sessionTimeoutInSeconds) - r <- result - } yield tokenAccessor.put(token)(r) } trait Logout { - self: Controller with AuthConfig => + self: AbstractController with AuthConfig => - def gotoLogoutSucceeded(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = { - gotoLogoutSucceeded(logoutSucceeded(request)) - } - - def gotoLogoutSucceeded(result: => Future[Result])(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = { + def markLoggedOut()(implicit request: RequestHeader, ctx: ExecutionContext): Result => Future[Result] = { result => tokenAccessor.extract(request) foreach idContainer.remove - result.map(tokenAccessor.delete) + Future.successful(tokenAccessor.delete(result)) } + } trait LoginLogout extends Login with Logout { - self: Controller with AuthConfig => + self: AbstractController with AuthConfig => } \ No newline at end of file diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/TokenAccessor.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/TokenAccessor.scala index 3fb965d..fb46b63 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/TokenAccessor.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/TokenAccessor.scala @@ -13,10 +13,10 @@ trait TokenAccessor { protected def verifyHmac(token: SignedToken): Option[AuthenticityToken] = { val (hmac, value) = token.splitAt(40) - if (safeEquals(Crypto.sign(value), hmac)) Some(value) else None + if (safeEquals(AuthCookieSigner.cookieSigner.sign(value), hmac)) Some(value) else None } - protected def sign(token: AuthenticityToken): SignedToken = Crypto.sign(token) + token + protected def sign(token: AuthenticityToken): SignedToken = AuthCookieSigner.cookieSigner.sign(token) + token // Do not change this unless you understand the security issues behind timing attacks. // This method intentionally runs in constant time if the two strings have the same length. diff --git a/module/src/main/scala/jp/t2v/lab/play2/auth/package.scala b/module/src/main/scala/jp/t2v/lab/play2/auth/package.scala index 22439a6..a92a0bc 100644 --- a/module/src/main/scala/jp/t2v/lab/play2/auth/package.scala +++ b/module/src/main/scala/jp/t2v/lab/play2/auth/package.scala @@ -9,7 +9,4 @@ package object auth { type ResultUpdater = Result => Result - @deprecated("renamed to TransparentIdContainer", since = "0.13.1") - type CookieIdContainer[Id] = TransparentIdContainer[Id] - } diff --git a/project/Build.scala b/project/Build.scala index 30109d6..602aa3e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1,5 +1,7 @@ import sbt._ import Keys._ +import play.routes.compiler.InjectedRoutesGenerator +import play.sbt.routes.RoutesKeys.routesGenerator import play.twirl.sbt.Import.TwirlKeys object ApplicationBuild extends Build { @@ -9,9 +11,9 @@ object ApplicationBuild extends Build { val playVersion = play.core.PlayVersion.current lazy val baseSettings = Seq( - version := "0.14.2", - scalaVersion := "2.11.8", - crossScalaVersions := Seq("2.10.5", "2.11.8"), + version := "0.16.0-SNAPSHOT", + scalaVersion := "2.11.11", + crossScalaVersions := Seq("2.11.11", "2.12.6"), organization := "jp.t2v", resolvers ++= Resolver.typesafeRepo("releases") :: @@ -58,7 +60,7 @@ object ApplicationBuild extends Build { baseSettings, libraryDependencies += "com.typesafe.play" %% "play" % playVersion % "provided", libraryDependencies += "com.typesafe.play" %% "play-cache" % playVersion % "provided", - libraryDependencies += "jp.t2v" %% "stackable-controller" % "0.5.1", + libraryDependencies += "jp.t2v" %% "stackable-controller" % "0.7.0-SNAPSHOT", name := appName, publishMavenStyle := appPublishMavenStyle, publishArtifact in Test := appPublishArtifactInTest, @@ -84,18 +86,20 @@ object ApplicationBuild extends Build { .settings( baseSettings, resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases", + libraryDependencies += play.sbt.Play.autoImport.guice, libraryDependencies += play.sbt.Play.autoImport.cache, libraryDependencies += play.sbt.Play.autoImport.specs2 % Test, libraryDependencies += play.sbt.Play.autoImport.jdbc, + libraryDependencies += "com.h2database" % "h2" % "1.4.193", libraryDependencies += "org.mindrot" % "jbcrypt" % "0.3m", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc" % "2.2.7", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-config" % "2.2.7", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-syntax-support-macro" % "2.2.7", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-test" % "2.2.7" % "test", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-play-initializer" % "2.4.0", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-play-dbapi-adapter" % "2.4.0", - libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-play-fixture" % "2.4.0", - libraryDependencies += "org.flywaydb" %% "flyway-play" % "2.0.1", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc" % "3.2.0", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-config" % "3.2.0", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-syntax-support-macro" % "3.2.0", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-test" % "3.2.0" % "test", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-play-initializer" % "2.6.0-scalikejdbc-3.2", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-play-dbapi-adapter" % "2.6.0-scalikejdbc-3.2", + libraryDependencies += "org.scalikejdbc" %% "scalikejdbc-play-fixture" % "2.6.0-scalikejdbc-3.2", + libraryDependencies += "org.flywaydb" %% "flyway-play" % "5.0.0", TwirlKeys.templateImports in Compile ++= Seq( "jp.t2v.lab.play2.auth.sample._", "play.api.data.Form", @@ -108,7 +112,8 @@ object ApplicationBuild extends Build { publishArtifact := false, packagedArtifacts := Map.empty, publishTo <<=(version)(appPublishTo), - pomExtra := appPomExtra + pomExtra := appPomExtra, + routesGenerator := InjectedRoutesGenerator ) .dependsOn(core, test % "test") @@ -119,6 +124,7 @@ object ApplicationBuild extends Build { libraryDependencies += "com.typesafe.play" %% "play" % playVersion % "provided", libraryDependencies += "com.typesafe.play" %% "play-ws" % playVersion % "provided", libraryDependencies += "com.typesafe.play" %% "play-cache" % playVersion % "provided", + libraryDependencies += "com.typesafe.play" %% "play-ahc-ws-standalone" % "1.1.9", publishMavenStyle := appPublishMavenStyle, publishArtifact in Test := appPublishArtifactInTest, pomIncludeRepository := appPomIncludeRepository, @@ -136,14 +142,14 @@ object ApplicationBuild extends Build { libraryDependencies ++= Seq( "com.typesafe.play" %% "play-ws" % playVersion, "com.typesafe.play" %% "play-cache" % playVersion, - "org.flywaydb" %% "flyway-play" % "2.0.1", - "org.scalikejdbc" %% "scalikejdbc" % "2.2.7", - "org.scalikejdbc" %% "scalikejdbc-config" % "2.2.7", - "org.scalikejdbc" %% "scalikejdbc-syntax-support-macro" % "2.2.7", - "org.scalikejdbc" %% "scalikejdbc-test" % "2.2.7" % "test", - "org.scalikejdbc" %% "scalikejdbc-play-initializer" % "2.4.0", - "org.scalikejdbc" %% "scalikejdbc-play-dbapi-adapter" % "2.4.0", - "org.scalikejdbc" %% "scalikejdbc-play-fixture" % "2.4.0" + "org.flywaydb" %% "flyway-play" % "5.0.0", + "org.scalikejdbc" %% "scalikejdbc" % "3.2.0", + "org.scalikejdbc" %% "scalikejdbc-config" % "3.2.0", + "org.scalikejdbc" %% "scalikejdbc-syntax-support-macro" % "3.2.0", + "org.scalikejdbc" %% "scalikejdbc-test" % "3.2.0" % "test", + "org.scalikejdbc" %% "scalikejdbc-play-initializer" % "2.6.0-scalikejdbc-3.2", + "org.scalikejdbc" %% "scalikejdbc-play-dbapi-adapter" % "2.6.0-scalikejdbc-3.2", + "org.scalikejdbc" %% "scalikejdbc-play-fixture" % "2.6.0-scalikejdbc-3.2" ), publish := { }, publishArtifact := false, diff --git a/project/build.properties b/project/build.properties index 43b8278..64317fd 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt index 5516b3b..54e97b8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,7 +5,7 @@ resolvers ++= Seq( ) // Use the Play sbt plugin for Play projects -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.6") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.0") scalacOptions ++= Seq("-deprecation", "-unchecked", "-language:_") diff --git a/sample/app/Global.scala b/sample/app/Global.scala index 342425b..fc9662a 100644 --- a/sample/app/Global.scala +++ b/sample/app/Global.scala @@ -1,20 +1,18 @@ -import play.api._ +import play.api.inject.ApplicationLifecycle import jp.t2v.lab.play2.auth.sample._ import jp.t2v.lab.play2.auth.sample.Role._ import scalikejdbc._ +import javax.inject._ -object Global extends GlobalSettings { - - override def onStart(app: Application) { - if (Account.findAll.isEmpty) { - Seq( - Account(1, "alice@example.com", "secret", "Alice", Administrator), - Account(2, "bob@example.com", "secret", "Bob", NormalUser), - Account(3, "chris@example.com", "secret", "Chris", NormalUser) - ) foreach Account.create - } +@Singleton +class GlobalInitializer @Inject() (appLifecycle: ApplicationLifecycle) { + if (Account.findAll.isEmpty) { + Seq( + Account(1, "alice@example.com", "secret", "Alice", Administrator), + Account(2, "bob@example.com", "secret", "Bob", NormalUser), + Account(3, "chris@example.com", "secret", "Chris", NormalUser) + ) foreach Account.create } - } diff --git a/sample/app/Module.scala b/sample/app/Module.scala new file mode 100644 index 0000000..72a03f9 --- /dev/null +++ b/sample/app/Module.scala @@ -0,0 +1,19 @@ +import com.google.inject.AbstractModule + +/** + * This class is a Guice module that tells Guice how to bind several + * different types. This Guice module is created when the Play + * application starts. + * + * Play will automatically use any class called `Module` that is in + * the root package. You can create modules in other locations by + * adding `play.modules.enabled` settings to the `application.conf` + * configuration file. + */ +class Module extends AbstractModule { + + override def configure() = { + bind(classOf[GlobalInitializer]).asEagerSingleton() + } + +} diff --git a/sample/app/controllers/BaseAuthConfig.scala b/sample/app/controllers/BaseAuthConfig.scala index e69b078..8f07912 100644 --- a/sample/app/controllers/BaseAuthConfig.scala +++ b/sample/app/controllers/BaseAuthConfig.scala @@ -13,7 +13,6 @@ import scala.collection.concurrent.TrieMap import scala.util.Random import java.security.SecureRandom import scala.annotation.tailrec -import play.api.cache.Cache trait BaseAuthConfig extends AuthConfig { diff --git a/sample/app/controllers/basic/Messages.scala b/sample/app/controllers/basic/Messages.scala index 4e81ff1..faf255d 100644 --- a/sample/app/controllers/basic/Messages.scala +++ b/sample/app/controllers/basic/Messages.scala @@ -9,8 +9,12 @@ import views.html import jp.t2v.lab.play2.auth.sample.Role._ import play.api.Environment import play.twirl.api.Html +import play.api.mvc.BaseController +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController +import javax.inject.Inject -class Messages @Inject() (val environment: Environment) extends Controller with AuthElement with AuthConfigImpl { +class Messages @Inject() (components: ControllerComponents) extends AbstractController(components) with AuthElement with AuthConfigImpl { def main = StackAction(AuthorityKey -> NormalUser) { implicit request => val title = "message main" @@ -34,4 +38,4 @@ class Messages @Inject() (val environment: Environment) extends Controller with protected implicit def template(implicit user: User): String => Html => Html = html.basic.fullTemplate(user) -} \ No newline at end of file +} diff --git a/sample/app/controllers/builder/AuthConfigImpl.scala b/sample/app/controllers/builder/AuthConfigImpl.scala index 1d00176..243527f 100644 --- a/sample/app/controllers/builder/AuthConfigImpl.scala +++ b/sample/app/controllers/builder/AuthConfigImpl.scala @@ -5,6 +5,9 @@ import play.api.mvc.RequestHeader import play.api.mvc.Results._ import scala.concurrent.{Future, ExecutionContext} +import jp.t2v.lab.play2.auth.TokenAccessor +import jp.t2v.lab.play2.auth.CookieTokenAccessor +import play.api.Environment trait AuthConfigImpl extends BaseAuthConfig { @@ -14,4 +17,14 @@ trait AuthConfigImpl extends BaseAuthConfig { def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) + val environment: Environment + override lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor( + cookieName = "PLAY2AUTH_SESS_ID", + cookieSecureOption = environment.mode == play.api.Mode.Prod, + cookieHttpOnlyOption = true, + cookieDomainOption = None, + cookiePathOption = "/", + cookieMaxAge = None + ) + } \ No newline at end of file diff --git a/sample/app/controllers/builder/Messages.scala b/sample/app/controllers/builder/Messages.scala index f4000e7..1dd9c8d 100644 --- a/sample/app/controllers/builder/Messages.scala +++ b/sample/app/controllers/builder/Messages.scala @@ -12,9 +12,13 @@ import scalikejdbc.{ DB, DBSession } import views.html import scala.concurrent.Future +import javax.inject.Inject +import play.api.Environment +import scala.concurrent.ExecutionContext class TransactionalRequest[+A](val dbSession: DBSession, request: Request[A]) extends WrappedRequest[A](request) -object TransactionalAction extends ActionBuilder[TransactionalRequest] { + +class TransactionalAction @Inject() (val parser: BodyParsers.Default, val executionContext: ExecutionContext) extends ActionBuilder[TransactionalRequest, AnyContent] { override def invokeBlock[A](request: Request[A], block: (TransactionalRequest[A]) => Future[Result]): Future[Result] = { import scalikejdbc.TxBoundary.Future._ implicit val ctx = executionContext @@ -24,20 +28,20 @@ object TransactionalAction extends ActionBuilder[TransactionalRequest] { } } -class Messages @Inject() (val environment: Environment) extends Controller with AuthActionBuilders with AuthConfigImpl { +class Messages @Inject() (components: ControllerComponents, val environment: Environment, parser: BodyParsers.Default, transactionalAction: TransactionalAction) extends AbstractController(components) with AuthActionBuilders with AuthConfigImpl { type AuthTxRequest[+A] = GenericAuthRequest[A, TransactionalRequest] - final def AuthorizationTxAction(authority: Authority): ActionBuilder[AuthTxRequest] = composeAuthorizationAction(TransactionalAction)(authority) + final def AuthorizationTxAction(authority: Authority): ActionBuilder[AuthTxRequest, AnyContent] = composeAuthorizationAction(transactionalAction)(authority) class PjaxAuthRequest[+A](val template: String => Html => Html, val authRequest: AuthTxRequest[A]) extends WrappedRequest[A](authRequest) - object PjaxRefiner extends ActionTransformer[AuthTxRequest, PjaxAuthRequest] { + class PjaxRefiner(val executionContext: ExecutionContext) extends ActionTransformer[AuthTxRequest, PjaxAuthRequest] { override protected def transform[A](request: AuthTxRequest[A]): Future[PjaxAuthRequest[A]] = { val template: String => Html => Html = if (request.headers.keys("X-Pjax")) html.pjaxTemplate.apply else html.builder.fullTemplate.apply(request.user) Future.successful(new PjaxAuthRequest(template, request)) } } - def MyAction(authority: Authority): ActionBuilder[PjaxAuthRequest] = AuthorizationTxAction(authority) andThen PjaxRefiner + def MyAction(authority: Authority): ActionBuilder[PjaxAuthRequest, AnyContent] = AuthorizationTxAction(authority) andThen (new PjaxRefiner(components.executionContext)) def main = MyAction(NormalUser) { implicit request => val title = "message main" diff --git a/sample/app/controllers/builder/Sessions.scala b/sample/app/controllers/builder/Sessions.scala index 8719bba..1b253b7 100644 --- a/sample/app/controllers/builder/Sessions.scala +++ b/sample/app/controllers/builder/Sessions.scala @@ -12,8 +12,12 @@ import views.html import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits.defaultContext +import play.api.mvc.AbstractController +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.Environment -class Sessions @Inject() (val environment: Environment) extends Controller with LoginLogout with AuthConfigImpl { +class Sessions @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with LoginLogout with AuthConfigImpl { val loginForm = Form { mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) @@ -25,7 +29,8 @@ class Sessions @Inject() (val environment: Environment) extends Controller with } def logout = Action.async { implicit request => - gotoLogoutSucceeded.map(_.flashing( + val x = markLoggedOut() + x(Redirect(routes.Sessions.login).flashing( "success" -> "You've been logged out" )) } @@ -33,7 +38,7 @@ class Sessions @Inject() (val environment: Environment) extends Controller with def authenticate = Action.async { implicit request => loginForm.bindFromRequest.fold( formWithErrors => Future.successful(BadRequest(html.builder.login(formWithErrors))), - user => gotoLoginSucceeded(user.get.id) + user => {val x = markLoggedIn(user.get.id); x(Redirect(routes.Messages.main))} ) } diff --git a/sample/app/controllers/csrf/AuthConfigImpl.scala b/sample/app/controllers/csrf/AuthConfigImpl.scala index 1192abe..3ba3259 100644 --- a/sample/app/controllers/csrf/AuthConfigImpl.scala +++ b/sample/app/controllers/csrf/AuthConfigImpl.scala @@ -5,6 +5,9 @@ import play.api.mvc.RequestHeader import play.api.mvc.Results._ import scala.concurrent.{Future, ExecutionContext} +import jp.t2v.lab.play2.auth.TokenAccessor +import jp.t2v.lab.play2.auth.CookieTokenAccessor +import play.api.Environment trait AuthConfigImpl extends BaseAuthConfig { @@ -12,4 +15,15 @@ trait AuthConfigImpl extends BaseAuthConfig { def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) + val environment: Environment + override lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor( + cookieName = "PLAY2AUTH_SESS_ID", + cookieSecureOption = environment.mode == play.api.Mode.Prod, + cookieHttpOnlyOption = true, + cookieDomainOption = None, + cookiePathOption = "/", + cookieMaxAge = None + ) + + } diff --git a/sample/app/controllers/csrf/PreventingCsrfSample.scala b/sample/app/controllers/csrf/PreventingCsrfSample.scala index 71e7d5a..36cc8e9 100644 --- a/sample/app/controllers/csrf/PreventingCsrfSample.scala +++ b/sample/app/controllers/csrf/PreventingCsrfSample.scala @@ -8,9 +8,14 @@ import jp.t2v.lab.play2.auth.sample.Role._ import play.api.Environment import play.api.data.Form import play.api.data.Forms._ -import play.api.mvc.Controller +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController +import controllers.stack.TokenValidateElement +import javax.inject.Inject +import play.api.Environment +import play.Logger -class PreventingCsrfSample @Inject() (val environment: Environment) extends Controller with TokenValidateElement with AuthElement with AuthConfigImpl { +class PreventingCsrfSample @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with TokenValidateElement with AuthElement with AuthConfigImpl { def formWithToken = StackAction(AuthorityKey -> NormalUser, IgnoreTokenValidation -> true) { implicit req => Ok(views.html.csrf.formWithToken()) diff --git a/sample/app/controllers/csrf/Sessions.scala b/sample/app/controllers/csrf/Sessions.scala index c9894c7..135cd9e 100644 --- a/sample/app/controllers/csrf/Sessions.scala +++ b/sample/app/controllers/csrf/Sessions.scala @@ -12,8 +12,12 @@ import views.html import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits.defaultContext +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController +import play.api.Environment -class Sessions @Inject() (val environment: Environment) extends Controller with LoginLogout with AuthConfigImpl { +class Sessions @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with LoginLogout with AuthConfigImpl { val loginForm = Form { mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) @@ -25,7 +29,8 @@ class Sessions @Inject() (val environment: Environment) extends Controller with } def logout = Action.async { implicit request => - gotoLogoutSucceeded.map(_.flashing( + val x = markLoggedOut() + x(Redirect(routes.Sessions.login).flashing( "success" -> "You've been logged out" )) } @@ -33,7 +38,7 @@ class Sessions @Inject() (val environment: Environment) extends Controller with def authenticate = Action.async { implicit request => loginForm.bindFromRequest.fold( formWithErrors => Future.successful(BadRequest(html.csrf.login(formWithErrors))), - user => gotoLoginSucceeded(user.get.id) + user => {val x = markLoggedIn(user.get.id); x(Redirect(routes.PreventingCsrfSample.formWithToken))} ) } diff --git a/sample/app/controllers/ephemeral/AuthConfigImpl.scala b/sample/app/controllers/ephemeral/AuthConfigImpl.scala index 4ad4c86..437c2c6 100644 --- a/sample/app/controllers/ephemeral/AuthConfigImpl.scala +++ b/sample/app/controllers/ephemeral/AuthConfigImpl.scala @@ -6,6 +6,7 @@ import play.api.mvc.RequestHeader import play.api.mvc.Results._ import scala.concurrent.{Future, ExecutionContext} +import play.api.Environment trait AuthConfigImpl extends BaseAuthConfig { @@ -15,9 +16,10 @@ trait AuthConfigImpl extends BaseAuthConfig { def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) + val environment: Environment override lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor( cookieName = "PLAY2AUTH_SESS_ID", - cookieSecureOption = play.api.Play.isProd(play.api.Play.current), + cookieSecureOption = environment.mode == play.api.Mode.Prod, cookieHttpOnlyOption = true, cookieDomainOption = None, cookiePathOption = "/", diff --git a/sample/app/controllers/ephemeral/Messages.scala b/sample/app/controllers/ephemeral/Messages.scala index 1383a11..6218001 100644 --- a/sample/app/controllers/ephemeral/Messages.scala +++ b/sample/app/controllers/ephemeral/Messages.scala @@ -4,12 +4,14 @@ import javax.inject.Inject import controllers.stack.Pjax import jp.t2v.lab.play2.auth.AuthElement -import play.api.mvc.Controller import views.html import jp.t2v.lab.play2.auth.sample.Role._ +import javax.inject.Inject +import play.api.mvc.ControllerComponents import play.api.Environment +import play.api.mvc.AbstractController -class Messages @Inject() (val environment: Environment) extends Controller with Pjax with AuthElement with AuthConfigImpl { +class Messages @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with Pjax with AuthElement with AuthConfigImpl { def main = StackAction(AuthorityKey -> NormalUser) { implicit request => val title = "message main" diff --git a/sample/app/controllers/ephemeral/Sessions.scala b/sample/app/controllers/ephemeral/Sessions.scala index 0936008..2b0dd5e 100644 --- a/sample/app/controllers/ephemeral/Sessions.scala +++ b/sample/app/controllers/ephemeral/Sessions.scala @@ -12,8 +12,12 @@ import views.html import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits.defaultContext +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.Environment +import play.api.mvc.AbstractController -class Sessions @Inject() (val environment: Environment) extends Controller with LoginLogout with AuthConfigImpl { +class Sessions @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with LoginLogout with AuthConfigImpl { val loginForm = Form { mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) @@ -25,7 +29,8 @@ class Sessions @Inject() (val environment: Environment) extends Controller with } def logout = Action.async { implicit request => - gotoLogoutSucceeded.map(_.flashing( + val x = markLoggedOut() + x(Redirect(routes.Sessions.login).flashing( "success" -> "You've been logged out" )) } @@ -33,7 +38,7 @@ class Sessions @Inject() (val environment: Environment) extends Controller with def authenticate = Action.async { implicit request => loginForm.bindFromRequest.fold( formWithErrors => Future.successful(BadRequest(html.ephemeral.login(formWithErrors))), - user => gotoLoginSucceeded(user.get.id) + user => {val x = markLoggedIn(user.get.id); x(Redirect(routes.Messages.main))} ) } diff --git a/sample/app/controllers/rememberme/Messages.scala b/sample/app/controllers/rememberme/Messages.scala index 6b6dd17..c199e27 100644 --- a/sample/app/controllers/rememberme/Messages.scala +++ b/sample/app/controllers/rememberme/Messages.scala @@ -4,12 +4,13 @@ import javax.inject.Inject import controllers.stack.Pjax import jp.t2v.lab.play2.auth.AuthElement -import play.api.mvc.Controller import views.html import jp.t2v.lab.play2.auth.sample.Role._ -import play.api.Environment +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController -class Messages @Inject() (val environment: Environment) extends Controller with Pjax with AuthElement with AuthConfigImpl { +class Messages @Inject() (components: ControllerComponents) extends AbstractController(components) with Pjax with AuthElement with AuthConfigImpl { def main = StackAction(AuthorityKey -> NormalUser) { implicit request => val title = "message main" diff --git a/sample/app/controllers/rememberme/Sessions.scala b/sample/app/controllers/rememberme/Sessions.scala index bcfdc37..d913556 100644 --- a/sample/app/controllers/rememberme/Sessions.scala +++ b/sample/app/controllers/rememberme/Sessions.scala @@ -12,8 +12,11 @@ import views.html import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits.defaultContext +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController -class Sessions @Inject() (val environment: Environment) extends Controller with LoginLogout with AuthConfigImpl { +class Sessions @Inject() (components: ControllerComponents) extends AbstractController(components) with LoginLogout with AuthConfigImpl { val loginForm = Form { mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) @@ -28,7 +31,8 @@ class Sessions @Inject() (val environment: Environment) extends Controller with } def logout = Action.async { implicit request => - gotoLogoutSucceeded.map(_.flashing( + val x = markLoggedOut() + x(Redirect(routes.Sessions.login).flashing( "success" -> "You've been logged out" )) } @@ -38,8 +42,9 @@ class Sessions @Inject() (val environment: Environment) extends Controller with loginForm.bindFromRequest.fold( formWithErrors => Future.successful(BadRequest(html.rememberme.login(formWithErrors, rememberme))), { user => - val req = request.copy(tags = request.tags + ("rememberme" -> rememberme.get.toString)) - gotoLoginSucceeded(user.get.id)(req, defaultContext).map(_.withSession("rememberme" -> rememberme.get.toString)) + val req = request.withTag("rememberme", rememberme.get.toString) + val x = markLoggedIn(user.get.id)(req, defaultContext) + x(Redirect(routes.Messages.main).withSession("rememberme" -> rememberme.get.toString)) } ) } diff --git a/sample/app/controllers/stack/Pjax.scala b/sample/app/controllers/stack/Pjax.scala index e5ecef5..97a8253 100644 --- a/sample/app/controllers/stack/Pjax.scala +++ b/sample/app/controllers/stack/Pjax.scala @@ -8,9 +8,10 @@ import play.twirl.api.Html import views.html import scala.concurrent.Future +import play.api.mvc.AbstractController trait Pjax extends StackableController with AuthElement { - self: Controller with BaseAuthConfig => + self: AbstractController with BaseAuthConfig => type Template = String => Html => Html diff --git a/sample/app/controllers/stack/TokenValidateElement.scala b/sample/app/controllers/stack/TokenValidateElement.scala index 9758dd3..8e89ea8 100644 --- a/sample/app/controllers/stack/TokenValidateElement.scala +++ b/sample/app/controllers/stack/TokenValidateElement.scala @@ -7,9 +7,10 @@ import play.api.data._ import play.api.data.Forms._ import scala.util.Random import java.security.SecureRandom +import play.api.mvc.AbstractController trait TokenValidateElement extends StackableController { - self: Controller => + self: AbstractController => private val PreventingCsrfTokenSessionKey = "preventingCsrfToken" diff --git a/sample/app/controllers/standard/AuthConfigImpl.scala b/sample/app/controllers/standard/AuthConfigImpl.scala index 24fa5e2..817ce58 100644 --- a/sample/app/controllers/standard/AuthConfigImpl.scala +++ b/sample/app/controllers/standard/AuthConfigImpl.scala @@ -4,6 +4,10 @@ import controllers.BaseAuthConfig import play.api.mvc.RequestHeader import play.api.mvc.Results._ +import jp.t2v.lab.play2.auth.TokenAccessor +import jp.t2v.lab.play2.auth.CookieTokenAccessor +import play.api.Environment + import scala.concurrent.{Future, ExecutionContext} trait AuthConfigImpl extends BaseAuthConfig { @@ -14,4 +18,13 @@ trait AuthConfigImpl extends BaseAuthConfig { def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) + val environment: Environment + override lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor( + cookieName = "PLAY2AUTH_SESS_ID", + cookieSecureOption = environment.mode == play.api.Mode.Prod, + cookieHttpOnlyOption = true, + cookieDomainOption = None, + cookiePathOption = "/", + cookieMaxAge = Some(60*60*24*7) + ) } \ No newline at end of file diff --git a/sample/app/controllers/standard/Messages.scala b/sample/app/controllers/standard/Messages.scala index 23a5f9c..ec9c3db 100644 --- a/sample/app/controllers/standard/Messages.scala +++ b/sample/app/controllers/standard/Messages.scala @@ -4,13 +4,15 @@ import javax.inject.Inject import controllers.stack.Pjax import jp.t2v.lab.play2.auth.AuthElement -import play.api.mvc.Controller import views.html import jp.t2v.lab.play2.auth.sample.Role._ import play.api.Environment +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController -class Messages @Inject() (val environment: Environment) extends Controller with Pjax with AuthElement with AuthConfigImpl { - +class Messages @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with Pjax with AuthElement with AuthConfigImpl { + def main = StackAction(AuthorityKey -> NormalUser) { implicit request => val title = "message main" Ok(html.message.main(title)) diff --git a/sample/app/controllers/standard/Sessions.scala b/sample/app/controllers/standard/Sessions.scala index b2ad2c6..47ce55e 100644 --- a/sample/app/controllers/standard/Sessions.scala +++ b/sample/app/controllers/standard/Sessions.scala @@ -7,13 +7,17 @@ import jp.t2v.lab.play2.auth.sample.Account import play.api.Environment import play.api.data.Form import play.api.data.Forms._ -import play.api.mvc.{ Action, Controller } +import play.api.mvc.Action import views.html import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits.defaultContext +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController +import play.api.Environment -class Sessions @Inject() (val environment: Environment) extends Controller with LoginLogout with AuthConfigImpl { +class Sessions @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with LoginLogout with AuthConfigImpl { val loginForm = Form { mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) @@ -25,7 +29,8 @@ class Sessions @Inject() (val environment: Environment) extends Controller with } def logout = Action.async { implicit request => - gotoLogoutSucceeded.map(_.flashing( + val x = markLoggedOut() + x(Redirect(routes.Sessions.login).flashing( "success" -> "You've been logged out" ).removingFromSession("rememberme")) } @@ -33,7 +38,7 @@ class Sessions @Inject() (val environment: Environment) extends Controller with def authenticate = Action.async { implicit request => loginForm.bindFromRequest.fold( formWithErrors => Future.successful(BadRequest(html.standard.login(formWithErrors))), - user => gotoLoginSucceeded(user.get.id) + user => {val x = markLoggedIn(user.get.id); x(Redirect(routes.Messages.main))} ) } diff --git a/sample/app/controllers/stateless/AuthConfigImpl.scala b/sample/app/controllers/stateless/AuthConfigImpl.scala index dd18cd2..06fdc96 100644 --- a/sample/app/controllers/stateless/AuthConfigImpl.scala +++ b/sample/app/controllers/stateless/AuthConfigImpl.scala @@ -5,7 +5,11 @@ import play.api.mvc.RequestHeader import play.api.mvc.Results._ import scala.concurrent.{Future, ExecutionContext} -import jp.t2v.lab.play2.auth.{CookieIdContainer, AsyncIdContainer} +import jp.t2v.lab.play2.auth.{TransparentIdContainer, AsyncIdContainer} + +import jp.t2v.lab.play2.auth.TokenAccessor +import jp.t2v.lab.play2.auth.CookieTokenAccessor +import play.api.Environment trait AuthConfigImpl extends BaseAuthConfig { @@ -15,6 +19,15 @@ trait AuthConfigImpl extends BaseAuthConfig { def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) - override lazy val idContainer = AsyncIdContainer(new CookieIdContainer[Id]) - + override lazy val idContainer = AsyncIdContainer(new TransparentIdContainer[Id]) + + val environment: Environment + override lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor( + cookieName = "PLAY2AUTH_SESS_ID", + cookieSecureOption = environment.mode == play.api.Mode.Prod, + cookieHttpOnlyOption = true, + cookieDomainOption = None, + cookiePathOption = "/", + cookieMaxAge = None + ) } \ No newline at end of file diff --git a/sample/app/controllers/stateless/Messages.scala b/sample/app/controllers/stateless/Messages.scala index 911eddd..41606c5 100644 --- a/sample/app/controllers/stateless/Messages.scala +++ b/sample/app/controllers/stateless/Messages.scala @@ -4,12 +4,14 @@ import javax.inject.Inject import controllers.stack.Pjax import jp.t2v.lab.play2.auth.AuthElement -import play.api.mvc.Controller import views.html import jp.t2v.lab.play2.auth.sample.Role._ +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController import play.api.Environment -class Messages @Inject() (val environment: Environment) extends Controller with Pjax with AuthElement with AuthConfigImpl { +class Messages @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with Pjax with AuthElement with AuthConfigImpl { def main = StackAction(AuthorityKey -> NormalUser) { implicit request => val title = "message main" @@ -33,4 +35,4 @@ class Messages @Inject() (val environment: Environment) extends Controller with protected val fullTemplate: User => Template = html.stateless.fullTemplate.apply -} \ No newline at end of file +} diff --git a/sample/app/controllers/stateless/Sessions.scala b/sample/app/controllers/stateless/Sessions.scala index d710b2a..2d6800e 100644 --- a/sample/app/controllers/stateless/Sessions.scala +++ b/sample/app/controllers/stateless/Sessions.scala @@ -7,13 +7,17 @@ import jp.t2v.lab.play2.auth.sample.Account import play.api.Environment import play.api.data.Form import play.api.data.Forms._ -import play.api.mvc.{ Action, Controller } +import play.api.mvc.Action import views.html import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits.defaultContext +import javax.inject.Inject +import play.api.mvc.ControllerComponents +import play.api.mvc.AbstractController +import play.api.Environment -class Sessions @Inject() (val environment: Environment) extends Controller with LoginLogout with AuthConfigImpl { +class Sessions @Inject() (components: ControllerComponents, val environment: Environment) extends AbstractController(components) with LoginLogout with AuthConfigImpl { val loginForm = Form { mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) @@ -25,7 +29,8 @@ class Sessions @Inject() (val environment: Environment) extends Controller with } def logout = Action.async { implicit request => - gotoLogoutSucceeded.map(_.flashing( + val x = markLoggedOut() + x(Redirect(routes.Sessions.login).flashing( "success" -> "You've been logged out" )) } @@ -33,7 +38,7 @@ class Sessions @Inject() (val environment: Environment) extends Controller with def authenticate = Action.async { implicit request => loginForm.bindFromRequest.fold( formWithErrors => Future.successful(BadRequest(html.stateless.login(formWithErrors))), - user => gotoLoginSucceeded(user.get.id) + user => {val x = markLoggedIn(user.get.id); x(Redirect(routes.Messages.main))} ) } diff --git a/sample/conf/application.conf b/sample/conf/application.conf index 20009c6..6baf364 100644 --- a/sample/conf/application.conf +++ b/sample/conf/application.conf @@ -1,6 +1,8 @@ # This is the main configuration file for the application. # ~~~~~ +play.filters.disabled+=play.filters.csrf.CSRFFilter + # Secret key # ~~~~~ # The secret key is used to secure cryptographics functions. diff --git a/sample/test/ApplicationSpec.scala b/sample/test/ApplicationSpec.scala index e1bff3c..bee8fc0 100644 --- a/sample/test/ApplicationSpec.scala +++ b/sample/test/ApplicationSpec.scala @@ -6,20 +6,30 @@ import play.api.test.Helpers._ import controllers.standard.{ AuthConfigImpl, Messages } import jp.t2v.lab.play2.auth.test.Helpers._ import java.io.File +import play.api.inject.guice.GuiceApplicationBuilder +import play.api.Mode -import play.api.Environment +class ApplicationSpec extends Specification with play.api.test.StubControllerComponentsFactory { -class ApplicationSpec extends Specification { + lazy val fakeApp = new GuiceApplicationBuilder() + .configure(Helpers.inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1"))) + .in(Mode.Test) + .build() object config extends AuthConfigImpl { - override val environment: Environment = Environment.simple() + val environment = fakeApp.environment } + def withApp = new play.api.test.WithApplication(fakeApp) { + val con = new Messages(stubControllerComponents(), fakeApp.environment) + val res = con.list(FakeRequest().withLoggedIn(config)(1)) + contentType(res) must beSome("text/html") + + + } + "Messages" should { - "return list when user is authorized" in new WithApplication(FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { - val res = new Messages(Environment.simple()).list(FakeRequest().withLoggedIn(config)(1)) - contentType(res) must beSome("text/html") - } + "return list when user is authorized" in withApp } } diff --git a/sample/test/IntegrationSpec.scala b/sample/test/IntegrationSpec.scala index c39a416..bcfa3b7 100644 --- a/sample/test/IntegrationSpec.scala +++ b/sample/test/IntegrationSpec.scala @@ -5,24 +5,32 @@ import org.specs2.mutable._ import play.api.test._ import play.api.test.Helpers._ import java.io.File +import play.api.inject.guice.GuiceApplicationBuilder +import play.api.Mode +import play.Logger class IntegrationSpec extends Specification { + def fakeApp = new GuiceApplicationBuilder() + .configure(Helpers.inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1"))) + .in(Mode.Test) + .build() + "Standard Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login failed browser.goTo(baseURL) - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secretxxx") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secretxxx") browser.$("#loginbutton").click() browser.pageSource must contain("Invalid email or password") // login succeded - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain ("Sign in") @@ -38,14 +46,14 @@ class IntegrationSpec extends Specification { } - "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login succeded browser.goTo(baseURL) - browser.$("#email").text("bob@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("bob@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -55,8 +63,8 @@ class IntegrationSpec extends Specification { browser.pageSource must contain("no permission") browser.goTo(s"${baseURL}/standard/logout") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.goTo(s"${baseURL}/standard/messages/write") @@ -68,19 +76,19 @@ class IntegrationSpec extends Specification { "Builder Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login failed browser.goTo(s"${baseURL}/builder/") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secretxxx") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secretxxx") browser.$("#loginbutton").click() browser.pageSource must contain("Invalid email or password") // login succeded - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -95,14 +103,14 @@ class IntegrationSpec extends Specification { } - "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login succeded browser.goTo(s"${baseURL}/builder/") - browser.$("#email").text("bob@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("bob@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -112,8 +120,8 @@ class IntegrationSpec extends Specification { browser.pageSource must contain("no permission") browser.goTo(s"${baseURL}/builder/logout") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.goTo(s"${baseURL}/builder/messages/write") @@ -125,20 +133,20 @@ class IntegrationSpec extends Specification { "CSRF Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login browser.goTo(s"${baseURL}/csrf/") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") browser.pageSource must contain("logout") - + // submit with token form - browser.$("#message").text("testmessage") + browser.$("#message").fill().`with`("testmessage") browser.$("#submitbutton").click() browser.pageSource must contain("testmessage") @@ -146,7 +154,7 @@ class IntegrationSpec extends Specification { browser.goTo(s"$baseURL/csrf/without_token") browser.pageSource must not contain("Sign in") browser.pageSource must contain("logout") - browser.$("#message").text("testmessage") + browser.$("#message").fill().`with`("testmessage") browser.$("#submitbutton").click() browser.pageSource must not contain("testmessage") @@ -156,19 +164,19 @@ class IntegrationSpec extends Specification { "Ephemeral Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login failed browser.goTo(s"${baseURL}/ephemeral/") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secretxxx") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secretxxx") browser.$("#loginbutton").click() browser.pageSource must contain("Invalid email or password") // login succeded - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -184,14 +192,14 @@ class IntegrationSpec extends Specification { } - "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login succeded browser.goTo(s"${baseURL}/ephemeral/") - browser.$("#email").text("bob@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("bob@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -202,8 +210,8 @@ class IntegrationSpec extends Specification { browser.pageSource must contain("no permission") browser.goTo(s"${baseURL}/ephemeral/logout") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.goTo(s"${baseURL}/ephemeral/messages/write") @@ -215,19 +223,19 @@ class IntegrationSpec extends Specification { "Stateless Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login failed browser.goTo(s"$baseURL/stateless/") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secretxxx") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secretxxx") browser.$("#loginbutton").click() browser.pageSource must contain("Invalid email or password") // login succeded - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain ("Sign in") @@ -239,17 +247,16 @@ class IntegrationSpec extends Specification { browser.goTo(s"$baseURL/stateless/messages/write") browser.pageSource must contain("Sign in") - } - "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login succeded browser.goTo(s"$baseURL/stateless/") - browser.$("#email").text("bob@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("bob@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -259,8 +266,8 @@ class IntegrationSpec extends Specification { browser.pageSource must contain("no permission") browser.goTo(s"${baseURL}/stateless/logout") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.goTo(s"${baseURL}/stateless/messages/write") @@ -272,12 +279,12 @@ class IntegrationSpec extends Specification { "HTTP Basic Auth Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login failed browser.goTo(s"$baseURL/basic/") - browser.url must equalTo("/basic/messages/main") + browser.url must equalTo("basic/messages/main") } @@ -285,19 +292,19 @@ class IntegrationSpec extends Specification { "Remember Me Sample" should { - "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "work from within a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login failed browser.goTo(s"$baseURL/rememberme/") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secretxxx") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secretxxx") browser.$("#loginbutton").click() browser.pageSource must contain("Invalid email or password") // login succeded - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain ("Sign in") @@ -312,8 +319,8 @@ class IntegrationSpec extends Specification { browser.pageSource must contain("Sign in") // login succeded - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#rememberme").click() browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) @@ -325,14 +332,14 @@ class IntegrationSpec extends Specification { } - "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = FakeApplication(additionalConfiguration = inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1")))) { + "authorize" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = fakeApp) { val baseURL = s"http://localhost:${port}" // login succeded browser.goTo(s"$baseURL/rememberme/") - browser.$("#email").text("bob@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("bob@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.pageSource must not contain("Sign in") @@ -342,8 +349,8 @@ class IntegrationSpec extends Specification { browser.pageSource must contain("no permission") browser.goTo(s"${baseURL}/rememberme/logout") - browser.$("#email").text("alice@example.com") - browser.$("#password").text("secret") + browser.$("#email").fill().`with`("alice@example.com") + browser.$("#password").fill().`with`("secret") browser.$("#loginbutton").click() browser.$("dl.error").size must equalTo(0) browser.goTo(s"${baseURL}/standard/messages/write") diff --git a/social-sample/app/controllers/Application.scala b/social-sample/app/controllers/Application.scala index 4074987..d757352 100644 --- a/social-sample/app/controllers/Application.scala +++ b/social-sample/app/controllers/Application.scala @@ -16,9 +16,10 @@ import jp.t2v.lab.play2.auth.social.providers.facebook.{ FacebookController, Fac import jp.t2v.lab.play2.auth.social.providers.github.{ GitHubController, GitHubProviderUserSupport } import jp.t2v.lab.play2.auth.social.providers.slack.SlackController import play.api.Environment -import play.api.cache.CacheApi +import play.api.cache.AsyncCacheApi +import play.api.libs.ws.WSClient -class Application @Inject() (val environment: Environment, val cacheApi: CacheApi) extends Controller with OptionalAuthElement with AuthConfigImpl with Logout { +class Application @Inject() (components: ControllerComponents, val environment: Environment, val cacheApi: AsyncCacheApi) extends AbstractController(components) with OptionalAuthElement with AuthConfigImpl with Logout { def index = StackAction { implicit request => DB.readOnly { implicit session => @@ -32,7 +33,7 @@ class Application @Inject() (val environment: Environment, val cacheApi: CacheAp } def logout = Action.async { implicit request => - gotoLogoutSucceeded + loginSucceeded(request) } } @@ -44,10 +45,12 @@ trait AuthConfigImpl extends AuthConfig { val idTag: ClassTag[Id] = classTag[Id] val sessionTimeoutInSeconds: Int = 3600 - val cacheApi: CacheApi + val cacheApi: AsyncCacheApi val idContainer: AsyncIdContainer[Id] = AsyncIdContainer(new CacheIdContainer[Id](cacheApi)) - + + override lazy val tokenAccessor: TokenAccessor = new CookieTokenAccessor() + def resolveUser(id: Id)(implicit ctx: ExecutionContext): Future[Option[User]] = Future.successful(DB.readOnly { implicit session => User.find(id) @@ -71,7 +74,8 @@ trait AuthConfigImpl extends AuthConfig { } -class FacebookAuthController @Inject() (val environment: Environment, val cacheApi: CacheApi) extends FacebookController +class FacebookAuthController @Inject() (components: ControllerComponents, val ws: WSClient, val environment: Environment, val cacheApi: AsyncCacheApi) extends AbstractController(components) + with FacebookController with AuthConfigImpl with FacebookProviderUserSupport { @@ -91,9 +95,9 @@ class FacebookAuthController @Inject() (val environment: Environment, val cacheA case None => val id = User.create(providerUser.name, providerUser.coverUrl).id FacebookUser.save(id, providerUser) - gotoLoginSucceeded(id) + loginSucceeded(request).flatMap(markLoggedIn(id)) case Some(fu) => - gotoLoginSucceeded(fu.userId) + loginSucceeded(request).flatMap(markLoggedIn(fu.userId)) } } } @@ -101,7 +105,8 @@ class FacebookAuthController @Inject() (val environment: Environment, val cacheA } -class GitHubAuthController @Inject() (val environment: Environment, val cacheApi: CacheApi) extends GitHubController +class GitHubAuthController @Inject() (components: ControllerComponents, val ws: WSClient, val environment: Environment, val cacheApi: AsyncCacheApi) extends AbstractController(components) + with GitHubController with AuthConfigImpl with GitHubProviderUserSupport { @@ -121,9 +126,9 @@ class GitHubAuthController @Inject() (val environment: Environment, val cacheApi case None => val id = User.create(providerUser.login, providerUser.avatarUrl).id GitHubUser.save(id, providerUser) - gotoLoginSucceeded(id) + loginSucceeded(request).flatMap(markLoggedIn(id)) case Some(gh) => - gotoLoginSucceeded(gh.userId) + loginSucceeded(request).flatMap(markLoggedIn(gh.userId)) } } } @@ -131,7 +136,8 @@ class GitHubAuthController @Inject() (val environment: Environment, val cacheApi } -class TwitterAuthController @Inject() (val environment: Environment, val cacheApi: CacheApi) extends TwitterController +class TwitterAuthController @Inject() (components: ControllerComponents, val ws: WSClient, val environment: Environment, val cacheApi: AsyncCacheApi) extends AbstractController(components) + with TwitterController with AuthConfigImpl with TwitterProviderUserSupport { @@ -151,9 +157,10 @@ class TwitterAuthController @Inject() (val environment: Environment, val cacheAp case None => val id = User.create(providerUser.screenName, providerUser.profileImageUrl).id TwitterUser.save(id, providerUser) - gotoLoginSucceeded(id) + //gotoLoginSucceeded(id) + loginSucceeded(request).flatMap(markLoggedIn(id)) case Some(tu) => - gotoLoginSucceeded(tu.userId) + loginSucceeded(request).flatMap(markLoggedIn(tu.userId)) } } } @@ -161,7 +168,8 @@ class TwitterAuthController @Inject() (val environment: Environment, val cacheAp } -class SlackAuthController @Inject() (val environment: Environment, val cacheApi: CacheApi) extends SlackController +class SlackAuthController @Inject() (components: ControllerComponents, val ws: WSClient, val environment: Environment, val cacheApi: AsyncCacheApi) extends AbstractController(components) + with SlackController with AuthConfigImpl { override def onOAuthLinkSucceeded(accessToken: AccessToken, consumerUser: User)(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = { diff --git a/social-sample/conf/routes b/social-sample/conf/routes index 424cf07..8f14bf4 100644 --- a/social-sample/conf/routes +++ b/social-sample/conf/routes @@ -14,4 +14,5 @@ GET /link/facebook @controllers.FacebookAuthController.link(s GET /authorize/facebook @controllers.FacebookAuthController.authorize GET /link/slack @controllers.SlackAuthController.link(scope: String) -GET /authorize/slack @controllers.SlackAuthController.authorize \ No newline at end of file +GET /authorize/slack @controllers.SlackAuthController.authorize + diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth10aController.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth10aController.scala index 217cf85..2ef58dd 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth10aController.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth10aController.scala @@ -6,11 +6,14 @@ import play.api.data.Form import play.api.data.Forms._ import play.api.libs.oauth._ import play.api.mvc._ +import jp.t2v.lab.play2.stackc.StackableController + import scala.concurrent.Future +import jp.t2v.lab.play2.auth.LoginLogout -trait OAuth10aController extends Controller with OAuthController { - self: OptionalAuthElement with AuthConfig => +trait OAuth10aController extends AbstractController with OAuthController { + self: OptionalAuthElement with AuthConfig with LoginLogout => protected val authenticator: OAuth10aAuthenticator @@ -19,7 +22,7 @@ trait OAuth10aController extends Controller with OAuthController { def login = AsyncStack(ExecutionContextKey -> OAuthExecutionContext) { implicit request => implicit val ec = StackActionExecutionContext loggedIn match { - case Some(_) => loginSucceeded(request) + case Some(u) => loginSucceeded(request) case None => authenticator.oauth.retrieveRequestToken(authenticator.callbackURL) match { case Right(token) => Future.successful( diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth2Controller.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth2Controller.scala index ae67cf6..444daa2 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth2Controller.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuth2Controller.scala @@ -10,13 +10,15 @@ import play.api.mvc._ import scala.concurrent.Future import scala.util.control.NonFatal +import jp.t2v.lab.play2.auth.LoginLogout +import scala.concurrent.ExecutionContext -trait OAuth2Controller extends Controller with OAuthController { self: OptionalAuthElement with AuthConfig => +trait OAuth2Controller extends AbstractController with OAuthController with LoginLogout { self: OptionalAuthElement with AuthConfig => protected val authenticator: OAuth2Authenticator protected val OAuth2StateKey = "play.auth.social.oauth2.state" - + // TODO scope is optional in some services // TODO some services have more optional parameter def login(scope: String) = AsyncStack(ExecutionContextKey -> OAuthExecutionContext) { implicit request => diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuthController.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuthController.scala index a972e93..b894bc0 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuthController.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/core/OAuthController.scala @@ -4,11 +4,14 @@ import jp.t2v.lab.play2.auth.{ AuthConfig, OptionalAuthElement } import play.api.mvc.{ Result, RequestHeader } import scala.concurrent.{ ExecutionContext, Future } +import jp.t2v.lab.play2.auth.LoginLogout -trait OAuthController { self: OptionalAuthElement with AuthConfig => +trait OAuthController { self: OptionalAuthElement with AuthConfig with LoginLogout => protected val authenticator: OAuthAuthenticator + protected def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext): Future[Result] + type AccessToken = authenticator.AccessToken def onOAuthLoginSucceeded(token: AccessToken)(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookAuthenticator.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookAuthenticator.scala index bccc829..b55fd92 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookAuthenticator.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookAuthenticator.scala @@ -6,13 +6,16 @@ import jp.t2v.lab.play2.auth.social.core.{ AccessTokenRetrievalFailedException, import play.api.Logger import play.api.Play.current import play.api.http.{ HeaderNames, MimeTypes } -import play.api.libs.ws.{ WS, WSResponse } +import play.api.libs.ws.WSResponse import play.api.mvc.Results import scala.concurrent.{ ExecutionContext, Future } import scala.util.control.NonFatal +import javax.inject.Inject +import play.api.libs.ws.WSClient +import play.api.libs.ws.EmptyBody -class FacebookAuthenticator extends OAuth2Authenticator { +class FacebookAuthenticator(ws: WSClient) extends OAuth2Authenticator { type AccessToken = String @@ -29,14 +32,14 @@ class FacebookAuthenticator extends OAuth2Authenticator { lazy val callbackUrl = current.configuration.getString("facebook.callbackURL").getOrElse(sys.error("facebook.callbackURL is missing")) def retrieveAccessToken(code: String)(implicit ctx: ExecutionContext): Future[AccessToken] = { - WS.url(accessTokenUrl) - .withQueryString( + ws.url(accessTokenUrl) + .withQueryStringParameters( "client_id" -> clientId, "client_secret" -> clientSecret, "redirect_uri" -> callbackUrl, "code" -> code) - .withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON) - .post(Results.EmptyContent()) + .withHttpHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON) + .post(EmptyBody) .map { response => Logger(getClass).debug("Retrieving access token from provider API: " + response.body) parseAccessTokenResponse(response) diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookController.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookController.scala index 2be18c9..bdad8d5 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookController.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookController.scala @@ -2,12 +2,15 @@ package jp.t2v.lab.play2.auth.social.providers.facebook import jp.t2v.lab.play2.auth.social.core.OAuth2Controller import jp.t2v.lab.play2.auth.{ AuthConfig, Login, OptionalAuthElement } +import play.api.libs.ws.WSClient trait FacebookController extends OAuth2Controller with AuthConfig with OptionalAuthElement with Login { + + val ws: WSClient - val authenticator = new FacebookAuthenticator + val authenticator = new FacebookAuthenticator(ws) } diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookProviderUserSupport.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookProviderUserSupport.scala index a1e87bf..0f9e118 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookProviderUserSupport.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/facebook/FacebookProviderUserSupport.scala @@ -2,7 +2,7 @@ package jp.t2v.lab.play2.auth.social.providers.facebook import jp.t2v.lab.play2.auth.social.core.OAuthProviderUserSupport import play.api.Logger -import play.api.libs.ws.{ WS, WSResponse } +import play.api.libs.ws.WSResponse import play.api.Play.current import scala.concurrent.{ ExecutionContext, Future } @@ -24,8 +24,8 @@ trait FacebookProviderUserSupport extends OAuthProviderUserSupport { def retrieveProviderUser(accessToken: AccessToken)(implicit ctx: ExecutionContext): Future[ProviderUser] = { for { - response <- WS.url("https://graph.facebook.com/me") - .withQueryString("access_token" -> accessToken, "fields" -> "name,first_name,last_name,picture.type(large),email") + response <- ws.url("https://graph.facebook.com/me") + .withQueryStringParameters("access_token" -> accessToken, "fields" -> "name,first_name,last_name,picture.type(large),email") .get() } yield { Logger(getClass).debug("Retrieving user info from provider API: " + response.body) diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubAuthenticator.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubAuthenticator.scala index 368fabd..89be4d3 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubAuthenticator.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubAuthenticator.scala @@ -6,13 +6,15 @@ import jp.t2v.lab.play2.auth.social.core.{ AccessTokenRetrievalFailedException, import play.api.Logger import play.api.Play.current import play.api.http.{ HeaderNames, MimeTypes } -import play.api.libs.ws.{ WS, WSResponse } +import play.api.libs.ws.WSResponse import play.api.mvc.Results import scala.concurrent.{ ExecutionContext, Future } import scala.util.control.NonFatal +import play.api.libs.ws.WSClient +import play.api.libs.ws.EmptyBody -class GitHubAuthenticator extends OAuth2Authenticator { +class GitHubAuthenticator(ws: WSClient) extends OAuth2Authenticator { type AccessToken = String @@ -29,13 +31,13 @@ class GitHubAuthenticator extends OAuth2Authenticator { lazy val callbackUrl = current.configuration.getString("github.callbackURL").getOrElse(sys.error("github.callbackURL is missing")) def retrieveAccessToken(code: String)(implicit ctx: ExecutionContext): Future[AccessToken] = { - WS.url(accessTokenUrl) - .withQueryString( + ws.url(accessTokenUrl) + .withQueryStringParameters( "client_id" -> clientId, "client_secret" -> clientSecret, "code" -> code) - .withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON) - .post(Results.EmptyContent()) + .withHttpHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON) + .post(EmptyBody) .map { response => Logger(getClass).debug("Retrieving access token from provider API: " + response.body) parseAccessTokenResponse(response) diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubController.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubController.scala index 4cc9409..e2cf834 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubController.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubController.scala @@ -2,12 +2,15 @@ package jp.t2v.lab.play2.auth.social.providers.github import jp.t2v.lab.play2.auth.social.core.OAuth2Controller import jp.t2v.lab.play2.auth.{ AuthConfig, Login, OptionalAuthElement } +import play.api.libs.ws.WSClient trait GitHubController extends OAuth2Controller with AuthConfig with OptionalAuthElement with Login { + + val ws: WSClient - val authenticator = new GitHubAuthenticator + val authenticator = new GitHubAuthenticator(ws) } \ No newline at end of file diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubProviderUserSupport.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubProviderUserSupport.scala index 957d8b2..3de2f48 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubProviderUserSupport.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/github/GitHubProviderUserSupport.scala @@ -2,7 +2,7 @@ package jp.t2v.lab.play2.auth.social.providers.github import jp.t2v.lab.play2.auth.social.core.OAuthProviderUserSupport import play.api.Play.current -import play.api.libs.ws.{ WS, WSResponse } +import play.api.libs.ws.WSResponse import scala.concurrent.{ ExecutionContext, Future } @@ -23,7 +23,7 @@ trait GitHubProviderUserSupport extends OAuthProviderUserSupport { def retrieveProviderUser(accessToken: String)(implicit ctx: ExecutionContext): Future[ProviderUser] = { for { - response <- WS.url("https://api.github.com/user").withHeaders("Authorization" -> s"token ${accessToken}").get() + response <- ws.url("https://api.github.com/user").withHttpHeaders("Authorization" -> s"token ${accessToken}").get() } yield { readProviderUser(accessToken, response) } diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackAuthenticator.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackAuthenticator.scala index 578320b..8592ae3 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackAuthenticator.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackAuthenticator.scala @@ -5,14 +5,16 @@ import java.net.URLEncoder import jp.t2v.lab.play2.auth.social.core.{ AccessTokenRetrievalFailedException, OAuth2Authenticator } import play.api.Logger import play.api.http.{ HeaderNames, MimeTypes } -import play.api.libs.ws.{ WS, WSResponse } +import play.api.libs.ws.WSResponse import play.api.Play.current import play.api.mvc.Results import scala.concurrent.{ ExecutionContext, Future } import scala.util.control.NonFatal +import play.api.libs.ws.WSClient +import play.api.libs.ws.EmptyBody -class SlackAuthenticator extends OAuth2Authenticator { +class SlackAuthenticator(ws: WSClient) extends OAuth2Authenticator { type AccessToken = String @@ -37,14 +39,14 @@ class SlackAuthenticator extends OAuth2Authenticator { } override def retrieveAccessToken(code: String)(implicit ctx: ExecutionContext): Future[AccessToken] = { - WS.url(accessTokenUrl) - .withQueryString( + ws.url(accessTokenUrl) + .withQueryStringParameters( "client_id" -> clientId, "client_secret" -> clientSecret, "redirect_uri" -> callbackUrl, "code" -> code) - .withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON) - .post(Results.EmptyContent()) + .withHttpHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON) + .post(EmptyBody) .map { response => Logger(getClass).debug("Retrieving access token from provider API: " + response.body) parseAccessTokenResponse(response) diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackController.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackController.scala index 71ce585..63d53e3 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackController.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/slack/SlackController.scala @@ -2,12 +2,15 @@ package jp.t2v.lab.play2.auth.social.providers.slack import jp.t2v.lab.play2.auth.social.core.OAuth2Controller import jp.t2v.lab.play2.auth.{ AuthConfig, Login, OptionalAuthElement } +import play.api.libs.ws.WSClient trait SlackController extends OAuth2Controller with AuthConfig with OptionalAuthElement with Login { - val authenticator = new SlackAuthenticator + val ws: WSClient + + val authenticator = new SlackAuthenticator(ws) } \ No newline at end of file diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterAuthenticator.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterAuthenticator.scala index 2b65a71..9a908cf 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterAuthenticator.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterAuthenticator.scala @@ -6,7 +6,7 @@ import play.api.libs.oauth.ConsumerKey class TwitterAuthenticator extends OAuth10aAuthenticator { - type AccessToken = TwitterOAuth10aAccessToken + type AccessToken = String//TwitterOAuth10aAccessToken val providerName: String = "twitter" diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterController.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterController.scala index dcd3730..99166d6 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterController.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterController.scala @@ -3,19 +3,21 @@ package jp.t2v.lab.play2.auth.social.providers.twitter import jp.t2v.lab.play2.auth.social.core.OAuth10aController import jp.t2v.lab.play2.auth.{ AuthConfig, Login, OptionalAuthElement } import play.api.libs.oauth.RequestToken +import jp.t2v.lab.play2.auth.LoginLogout trait TwitterController extends OAuth10aController with AuthConfig with OptionalAuthElement - with Login { + with LoginLogout { val authenticator = new TwitterAuthenticator - def requestTokenToAccessToken(requestToken: RequestToken): AccessToken = { - TwitterOAuth10aAccessToken( - requestToken.token, - requestToken.secret - ) + override def requestTokenToAccessToken(requestToken: RequestToken): AccessToken = { + requestToken.token ++ "|-sep-|" ++ requestToken.secret +// TwitterOAuth10aAccessToken( +// requestToken.token, +// requestToken.secret +// ) } } diff --git a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterProviderUserSupport.scala b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterProviderUserSupport.scala index b934d27..fdb0b3f 100644 --- a/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterProviderUserSupport.scala +++ b/social/src/main/scala/jp/t2v/lab/play2/auth/social/providers/twitter/TwitterProviderUserSupport.scala @@ -4,32 +4,44 @@ import jp.t2v.lab.play2.auth.social.core.OAuthProviderUserSupport import play.api.Logger import play.api.Play.current import play.api.libs.oauth.{ OAuthCalculator, RequestToken } -import play.api.libs.ws.{ WS, WSResponse } +import play.api.libs.ws.WSResponse import scala.concurrent.{ ExecutionContext, Future } +import play.api.libs.ws.WSClient trait TwitterProviderUserSupport extends OAuthProviderUserSupport { self: TwitterController => + + val ws: WSClient type ProviderUser = TwitterUser + + private def splitToken(accessToken: AccessToken):(String, String) = { + accessToken.split("|-sep-|").toList match { + case a::b::Nil => (a,b) + case _ => throw new IllegalArgumentException + } + } private def readProviderUser(accessToken: AccessToken, response: WSResponse): ProviderUser = { val j = response.json + val (token, secret) = splitToken(accessToken) TwitterUser( (j \ "id").as[Long], (j \ "screen_name").as[String], (j \ "name").as[String], (j \ "description").as[String], (j \ "profile_image_url").as[String], - accessToken.token, - accessToken.secret + token, + secret ) } def retrieveProviderUser(accessToken: AccessToken)(implicit ctx: ExecutionContext): Future[ProviderUser] = { + val (token, secret) = splitToken(accessToken) for { - response <- WS.url("https://api.twitter.com/1.1/account/verify_credentials.json") - .sign(OAuthCalculator(authenticator.consumerKey, RequestToken(accessToken.token, accessToken.secret))).get() + response <- ws.url("https://api.twitter.com/1.1/account/verify_credentials.json") + .sign(OAuthCalculator(authenticator.consumerKey, RequestToken(token, secret))).get() } yield { Logger(getClass).debug("Retrieving user info from Twitter API: " + response.body) readProviderUser(accessToken, response) diff --git a/test/src/main/scala/jp/t2v/lab/play2/auth/test/Helpers.scala b/test/src/main/scala/jp/t2v/lab/play2/auth/test/Helpers.scala index ef9a642..05b04e9 100644 --- a/test/src/main/scala/jp/t2v/lab/play2/auth/test/Helpers.scala +++ b/test/src/main/scala/jp/t2v/lab/play2/auth/test/Helpers.scala @@ -7,16 +7,23 @@ import play.api.libs.Crypto import scala.concurrent.Await import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global +import jp.t2v.lab.play2.auth.CookieTokenAccessor +import jp.t2v.lab.play2.auth.AuthCookieSigner trait Helpers { implicit class AuthFakeRequest[A](fakeRequest: FakeRequest[A]) { def withLoggedIn(implicit config: AuthConfig): config.Id => FakeRequest[A] = { id => + def sign(s: String) = AuthCookieSigner.cookieSigner.sign(s) + s val token = Await.result(config.idContainer.startNewSession(id, config.sessionTimeoutInSeconds)(fakeRequest, global), 10.seconds) - fakeRequest.withHeaders("PLAY2_AUTH_TEST_TOKEN" -> token) + val c = Cookie("PLAY2AUTH_SESS_ID", sign(token)) + fakeRequest.withCookies(c) + //fakeRequest.withHeaders("PLAY2_AUTH_TEST_TOKEN" -> token) } + + } }