Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/play2.6 alternative #191

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions module/src/main/scala/jp/t2v/lab/play2/auth/AsyncAuth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ 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]])

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] {
Expand All @@ -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] {
Expand All @@ -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))
}

Expand All @@ -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)

}
37 changes: 2 additions & 35 deletions module/src/main/scala/jp/t2v/lab/play2/auth/AuthConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
19 changes: 19 additions & 0 deletions module/src/main/scala/jp/t2v/lab/play2/auth/AuthCookieSigner.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package jp.t2v.lab.play2.auth
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[質問] あまり分かっていないですが、play2-authにAuthCookieSignerはある(これはそれのコピーっぽい)のに、ここで同じものを定義しているのは何か理由があるのでしょうか。

https://github.com/qtamaki/play2-auth/blob/2b4eac240ab073792e967aa6990039452d023579/module/src/main/scala/jp/t2v/lab/play2/auth/AuthCookieSigner.scala

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Play2authをPlay2.6対応するために作ったforkなので、Play2Authと同じものだと思います。


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)
}

}
12 changes: 7 additions & 5 deletions module/src/main/scala/jp/t2v/lab/play2/auth/AuthElement.scala
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -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]

Expand All @@ -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]

Expand Down
16 changes: 12 additions & 4 deletions module/src/main/scala/jp/t2v/lab/play2/auth/CacheIdContainer.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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)
}

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
}
23 changes: 8 additions & 15 deletions module/src/main/scala/jp/t2v/lab/play2/auth/LoginLogout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions module/src/main/scala/jp/t2v/lab/play2/auth/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,4 @@ package object auth {

type ResultUpdater = Result => Result

@deprecated("renamed to TransparentIdContainer", since = "0.13.1")
type CookieIdContainer[Id] = TransparentIdContainer[Id]

}
Loading