├── .gitignore ├── README.md ├── action-cont-lib └── src │ └── main │ └── scala │ └── com │ └── github │ └── hexx │ └── play │ └── cont │ └── play2auth │ ├── AsyncAuthCont.scala │ └── AuthElementCont.scala ├── action-cont-simple └── src │ └── main │ └── scala │ └── com │ └── github │ └── hexx │ └── play │ └── cont │ └── simple │ ├── ActionCont.scala │ ├── Cont.scala │ ├── FlowCont.scala │ ├── LoginController.scala │ └── package.scala ├── action-cont └── src │ ├── main │ └── scala │ │ └── com │ │ └── github │ │ └── hexx │ │ └── play │ │ └── cont │ │ ├── ActionCont.scala │ │ ├── FlowCont.scala │ │ ├── FormCont.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── github │ └── hexx │ └── play │ └── cont │ └── FormContTest.scala ├── build.sbt ├── play2-auth-cont-sample ├── app │ ├── Global.scala │ ├── controllers │ │ ├── BaseAuthConfig.scala │ │ ├── basic │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── BasicAuthIdContainer.scala │ │ │ ├── BasicAuthTokenAccessor.scala │ │ │ └── Messages.scala │ │ ├── builder │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── Messages.scala │ │ │ └── Sessions.scala │ │ ├── cont │ │ │ ├── MessageCont.scala │ │ │ ├── PjaxCont.scala │ │ │ └── TokenValidateElementCont.scala │ │ ├── csrf │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── PreventingCsrfSample.scala │ │ │ └── Sessions.scala │ │ ├── ephemeral │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── Messages.scala │ │ │ └── Sessions.scala │ │ ├── rememberme │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── Messages.scala │ │ │ ├── RememberMeTokenAccessor.scala │ │ │ └── Sessions.scala │ │ ├── stack │ │ │ ├── Pjax.scala │ │ │ └── TokenValidateElement.scala │ │ ├── standard │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── Messages.scala │ │ │ └── Sessions.scala │ │ └── stateless │ │ │ ├── AuthConfigImpl.scala │ │ │ ├── Messages.scala │ │ │ └── Sessions.scala │ ├── jp │ │ └── t2v │ │ │ └── lab │ │ │ └── play2 │ │ │ └── auth │ │ │ └── sample │ │ │ ├── Account.scala │ │ │ └── Role.scala │ └── views │ │ ├── basic │ │ └── fullTemplate.scala.html │ │ ├── builder │ │ ├── fullTemplate.scala.html │ │ └── login.scala.html │ │ ├── csrf │ │ ├── formWithToken.scala.html │ │ ├── formWithoutToken.scala.html │ │ ├── fullTemplate.scala.html │ │ └── login.scala.html │ │ ├── ephemeral │ │ ├── fullTemplate.scala.html │ │ └── login.scala.html │ │ ├── message │ │ ├── detail.scala.html │ │ ├── list.scala.html │ │ ├── main.scala.html │ │ └── write.scala.html │ │ ├── pjaxTemplate.scala.html │ │ ├── rememberme │ │ ├── fullTemplate.scala.html │ │ └── login.scala.html │ │ ├── standard │ │ ├── fullTemplate.scala.html │ │ └── login.scala.html │ │ └── stateless │ │ ├── fullTemplate.scala.html │ │ └── login.scala.html ├── conf │ ├── application.conf │ ├── db │ │ └── migration │ │ │ └── default │ │ │ └── V1__create_tables.sql │ ├── play.plugins │ └── routes ├── public │ ├── images │ │ └── favicon.png │ ├── javascripts │ │ ├── jquery-1.7.1.min.js │ │ └── jquery.pjax.js │ └── stylesheets │ │ └── main.css └── test │ ├── ApplicationSpec.scala │ └── IntegrationSpec.scala └── project ├── build.properties └── plugins.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project 3 | target 4 | .idea 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActionCont 2 | 3 | 継続モナドを使ってPlay FrameworkのActionを組み立てるためのライブラリです。 4 | 5 | これは説明のために書かれたライブラリであり、今後メンテナンスしていく予定はありません。 6 | 7 | ## ライセンス 8 | 9 | public domain 10 | 11 | ## 概要 12 | 13 | 個人的な考えですが、継続モナドはWebアプリケーションのコントローラーを書くのに非常に適したものだと考えています。 14 | 15 | おおまかなイメージを話しますと、継続モナドはコールバック関数を受け取り、その前後に処理を挟むことができます。 16 | この動作をWebアプリケーションのコントローラーで考えてみますと、リクエストを受け取りレスポンスを返す関数を受け取り、その前後に処理を挟むことができるということになります。 17 | これはコントローラを構成する部品を作る上で便利な性質になります。 18 | たとえばJava EEのServlet Filterはまさにそういう動作をする仕組みです。 19 | それに加えて、継続モナドはモナドなのでScalaのfor構文を使い、自由に組み立てることができます。 20 | そう言われると、なんとなく継続モナドが便利な予感がしてきたでしょうか。 21 | 22 | 今回比較のために、がくぞ(@gakuzzzz)さんの [t2v/play2-auth](https://github.com/t2v/play2-auth) のサンプルのActionの合成部分を継続モナドを使って再実装させていただきました。 23 | play2-authのサンプルには、がくぞさん自身が作られた [t2v/stackable-controller](https://github.com/t2v/stackable-controller) を使ったActionの合成と、Play標準のActionBuilderを使ったActionの合成のサンプルが書かれています。 24 | 今回の継続モナドを使った手法と見比べていただければと思います。 25 | 26 | 以下、簡単にプロジェクトの説明をさせていただきます。 27 | また後日、詳しいブログ記事などを書く予定ですので、軽く雰囲気だけを掴んでください。 28 | 29 | ## action-cont 30 | 31 | 継続モナドの中心部分が入っているプロジェクトです。 32 | 今回 `ContT[Future, Result, A]` を `ActionCont[A]` と名付けました。 33 | 34 | ```scala 35 | type ActionCont[A] = ContT[Future, Result, A] 36 | ``` 37 | 38 | Scalazの `ContT` が使われていますが、今回のやり方ではもっと簡単な継続モナドでもかまいません。 39 | 40 | ```scala 41 | case class Cont[R, A](run: (A => R) => R) { 42 | def map[B](f: A => B): Cont[R, B] = Cont(k => run(a => k(f(a)))) 43 | def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = Cont(k => run(a => f(a).run(k))) 44 | } 45 | 46 | type ActionCont[A] = Cont[Future[Result], A] 47 | ``` 48 | 49 | Scalazではなく、以上のような簡単なコードでも同じように動作します。 50 | 51 | ## action-cont-lib 52 | 53 | 既存のライブラリを使って `ActionCont` の形の部品を作ったライブラリにする予定なのですが、今のところplay2-auth関連のものしかありません。 54 | 55 | - `AsyncAuthCont` はplay2-authの `AsyncAuth` に対応しています。 56 | - `AuthElementCont` はplay2-authの `AuthElement` に対応しています。 57 | 58 | どちらも後ろにContを付けただけです。 59 | 60 | ## play2-auth-cont-sample 61 | 62 | play2-authのsampleに対応しています。と言っても全部が再実装されているわけではなく、以下のものだけが継続モナドを使った実装になっています。 63 | 64 | - [play2-auth-cont-sample/app/controllers/cont/PjaxCont.scala](https://github.com/hexx/action-cont/blob/master/play2-auth-cont-sample/app/controllers/cont/PjaxCont.scala) 65 | - [play2-auth-cont-sample/app/controllers/cont/TokenValidateElementCont.scala](https://github.com/hexx/action-cont/blob/master/play2-auth-cont-sample/app/controllers/cont/TokenValidateElementCont.scala) 66 | - [play2-auth-cont-sample/app/controllers/cont/MessageCont.scala](https://github.com/hexx/action-cont/blob/master/play2-auth-cont-sample/app/controllers/cont/MessageCont.scala) 67 | - [play2-auth-cont-sample/app/controllers/standard/Messages.scala](https://github.com/hexx/action-cont/blob/master/play2-auth-cont-sample/app/controllers/standard/Messages.scala) 68 | - [play2-auth-cont-sample/app/controllers/csrf/PreventingCsrfSample.scala](https://github.com/hexx/action-cont/blob/master/play2-auth-cont-sample/app/controllers/csrf/PreventingCsrfSample.scala) 69 | 70 | この他の部分はStackable ControllerやActionBuilderで作られているので見比べてみてください。 71 | -------------------------------------------------------------------------------- /action-cont-lib/src/main/scala/com/github/hexx/play/cont/play2auth/AsyncAuthCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont.play2auth 2 | 3 | import com.github.hexx.play.cont.ActionCont 4 | import jp.t2v.lab.play2.auth.AuthConfig 5 | import play.api.mvc.RequestHeader 6 | 7 | import scala.concurrent.ExecutionContext 8 | 9 | trait AsyncAuthCont extends AuthConfig { 10 | def authorizedCont(authority: Authority)(implicit request: RequestHeader, ec: ExecutionContext): ActionCont[User] = 11 | ActionCont(f => 12 | restoreUserCont.run { 13 | case None => authenticationFailed(request) 14 | case Some(user) => 15 | authorize(user, authority).flatMap { 16 | case true => f(user) 17 | case _ => authorizationFailed(request, user, Some(authority)) 18 | } 19 | } 20 | ) 21 | 22 | private[play2auth] def restoreUserCont(implicit request: RequestHeader, ec: ExecutionContext): ActionCont[Option[User]] = 23 | ActionCont(f => 24 | (for { 25 | token <- tokenAccessor.extract(request) 26 | } yield for { 27 | Some(userId) <- idContainer.get(token) 28 | Some(user) <- resolveUser(userId) 29 | _ <- idContainer.prolongTimeout(token, sessionTimeoutInSeconds) 30 | result <- f(Option(user)) 31 | } yield tokenAccessor.put(token)(result) 32 | ) getOrElse f(Option.empty) 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /action-cont-lib/src/main/scala/com/github/hexx/play/cont/play2auth/AuthElementCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont.play2auth 2 | 3 | import com.github.hexx.play.cont.ActionCont 4 | import play.api.mvc.RequestHeader 5 | 6 | import scala.concurrent.ExecutionContext 7 | 8 | trait AuthElementCont extends AsyncAuthCont { 9 | def authElementCont(authority: Authority)(implicit request: RequestHeader, ec: ExecutionContext): ActionCont[User] = 10 | authorizedCont(authority) 11 | 12 | def authElementCont(implicit request: RequestHeader, ec: ExecutionContext): ActionCont[User] = 13 | ActionCont(_ => 14 | restoreUserCont.run { 15 | case Some(user) => authorizationFailed(request, user, None) 16 | case None => authenticationFailed(request) 17 | } 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /action-cont-simple/src/main/scala/com/github/hexx/play/cont/simple/ActionCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont.simple 2 | 3 | import play.api.mvc.Result 4 | import scala.concurrent.ExecutionContext 5 | import scala.concurrent.Future 6 | 7 | object ActionCont { 8 | def apply[A](f: (A => Future[Result]) => Future[Result]): ActionCont[A] = 9 | Cont(f) 10 | 11 | def fromFuture[A](future: => Future[A])(implicit ec: ExecutionContext): ActionCont[A] = 12 | Cont(future.flatMap) 13 | 14 | def successful[A](a: A)(implicit ec: ExecutionContext): ActionCont[A] = 15 | fromFuture(Future.successful(a)) 16 | 17 | def failed[A](throwable: Throwable)(implicit ec: ExecutionContext): ActionCont[A] = 18 | fromFuture(Future.failed(throwable)) 19 | 20 | def recover[A](actionCont: ActionCont[A])(pf: PartialFunction[Throwable, Future[Result]]) 21 | (implicit executor: ExecutionContext): ActionCont[A] = 22 | ActionCont(f => actionCont.run(f).recoverWith(pf)) 23 | } 24 | -------------------------------------------------------------------------------- /action-cont-simple/src/main/scala/com/github/hexx/play/cont/simple/Cont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont.simple 2 | 3 | case class Cont[R, A](run: (A => R) => R) { 4 | def map[B](f: A => B): Cont[R, B] = 5 | Cont(k => run(a => k(f(a)))) 6 | 7 | def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = 8 | Cont(k => run(a => f(a).run(k))) 9 | 10 | def withFilter(f: A => Boolean): Cont[R, A] = 11 | Cont(k => run(a => if (f(a)) k(a) else throw new NoSuchElementException("Cont must not fail to filter."))) 12 | } 13 | -------------------------------------------------------------------------------- /action-cont-simple/src/main/scala/com/github/hexx/play/cont/simple/FlowCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont.simple 2 | 3 | import play.api.mvc.AnyContent 4 | import play.api.mvc.Request 5 | import play.api.mvc.Result 6 | import scala.concurrent.ExecutionContext 7 | import scala.concurrent.Future 8 | 9 | object FlowCont { 10 | def apply[WholeRequestContext, NormalRequestContext]( 11 | request: Request[AnyContent], 12 | wholeCont: Request[AnyContent] => ActionCont[WholeRequestContext], 13 | normalCont: WholeRequestContext => ActionCont[NormalRequestContext], 14 | handlerCont: NormalRequestContext => ActionCont[Result], 15 | errorCont: WholeRequestContext => Throwable => ActionCont[Result]) 16 | (implicit executionContext: ExecutionContext): ActionCont[Result] = { 17 | 18 | for { 19 | // 正常系と異常系共通で適用される処理 20 | wholeRequestContext <- wholeCont(request) 21 | wholeResult <- ActionCont.recover( 22 | for { 23 | // 正常系だけで適用される処理 24 | normalRequestContext <- normalCont(wholeRequestContext) 25 | // コントローラーの処理本体 26 | result <- handlerCont(normalRequestContext) 27 | } yield result) { 28 | // 異常系の処理 29 | case e => errorCont(wholeRequestContext)(e).run(Future.successful) 30 | } 31 | } yield wholeResult 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /action-cont-simple/src/main/scala/com/github/hexx/play/cont/simple/LoginController.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont.simple 2 | 3 | import play.api.mvc.Action 4 | import play.api.mvc.AnyContent 5 | import play.api.mvc.Controller 6 | import play.api.mvc.Request 7 | import play.api.mvc.Result 8 | import play.api.data.Form 9 | import play.api.data.Forms._ 10 | import play.api.libs.json.Json 11 | import scala.concurrent.ExecutionContext 12 | import scala.concurrent.Future 13 | 14 | // 説明用のログイン処理 15 | // 説明用なので実装されていないところが多いです 16 | // より実践的な例は play2-auth-cont-sample を参考にしてください 17 | object LoginController extends Controller { 18 | case class AuthParam(name: String, password: String) 19 | 20 | val authParamForm = Form( 21 | mapping( 22 | "name" -> text, 23 | "password" -> text 24 | )(AuthParam.apply)(_ => None) 25 | ) 26 | 27 | case class User(id: Int, name: String) 28 | 29 | def authParamCont(request: Request[AnyContent]): ActionCont[AuthParam] = 30 | ActionCont((f: AuthParam => Future[Result]) => 31 | authParamForm.bindFromRequest()(request).fold( 32 | error => Future.successful(BadRequest), 33 | authParam => f(authParam) 34 | ) 35 | ) 36 | 37 | def corsCont: Request[AnyContent] => ActionCont[Unit] = ??? 38 | 39 | def loginCont: AuthParam => ActionCont[User] = ??? 40 | 41 | def combinedCont(request: Request[AnyContent]): ActionCont[User] = 42 | for { 43 | _ <- corsCont(request) 44 | authParam <- authParamCont(request) 45 | user <- loginCont(authParam) 46 | } yield user 47 | 48 | def login = Action.async { request => 49 | 50 | val cont: ActionCont[Result] = for { 51 | user <- combinedCont(request) 52 | } yield Ok(Json.obj("id" -> user.id)) 53 | 54 | cont.run(Future.successful) 55 | } 56 | 57 | class FormErrorException extends Throwable 58 | 59 | class UserNotFoundException extends Throwable 60 | 61 | def loginFlow = Action.async { request => 62 | implicit val ec: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext 63 | FlowCont( 64 | request = request, 65 | wholeCont = corsCont, 66 | normalCont = (_: Unit) => authParamCont(request), 67 | handlerCont = loginCont(_: AuthParam).map(user => Ok(Json.obj("id" -> user.id))), 68 | errorCont = (_: Unit) => ((_: Throwable) match { 69 | case e: FormErrorException => BadRequest 70 | case e: UserNotFoundException => NotFound 71 | case _ => InternalServerError 72 | }).andThen(ActionCont.successful) 73 | ).run(Future.successful) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /action-cont-simple/src/main/scala/com/github/hexx/play/cont/simple/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont 2 | 3 | import play.api.mvc.Result 4 | import scala.concurrent.Future 5 | 6 | package object simple { 7 | type ActionCont[A] = Cont[Future[Result], A] 8 | } 9 | -------------------------------------------------------------------------------- /action-cont/src/main/scala/com/github/hexx/play/cont/ActionCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont 2 | 3 | import play.api.mvc.Action 4 | import play.api.mvc.AnyContent 5 | import play.api.mvc.Request 6 | import play.api.mvc.Result 7 | import scala.concurrent.ExecutionContext 8 | import scala.concurrent.Future 9 | import scalaz._ 10 | import scalaz.contrib.std.scalaFuture._ 11 | 12 | object ActionCont extends IndexedContsTInstances with IndexedContsTFunctions { 13 | def apply[A](f: (A => Future[Result]) => Future[Result]): ActionCont[A] = 14 | ContT(f) 15 | 16 | def fromFuture[A](future: => Future[A])(implicit ec: ExecutionContext): ActionCont[A] = 17 | ActionCont(future.flatMap) 18 | 19 | def successful[A](a: A)(implicit ec: ExecutionContext): ActionCont[A] = 20 | fromFuture(Future.successful(a)) 21 | 22 | def failed[A](throwable: Throwable)(implicit ec: ExecutionContext): ActionCont[A] = 23 | fromFuture(Future.failed(throwable)) 24 | 25 | def run(f: Request[AnyContent] => ActionCont[Result])(implicit ec: ExecutionContext): Action[AnyContent] = 26 | Action.async(f(_).run_) 27 | 28 | def recover[A](actionCont: ActionCont[A])(pf: PartialFunction[Throwable, Future[Result]])(implicit executor: ExecutionContext): ActionCont[A] = 29 | ActionCont(f => actionCont.run(f).recoverWith(pf)) 30 | } 31 | -------------------------------------------------------------------------------- /action-cont/src/main/scala/com/github/hexx/play/cont/FlowCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont 2 | 3 | import play.api.mvc.AnyContent 4 | import play.api.mvc.Request 5 | import play.api.mvc.Result 6 | import scala.concurrent.ExecutionContext 7 | import scalaz.contrib.std.scalaFuture._ 8 | 9 | object FlowCont { 10 | def apply[WholeRequestContext, NormalRequestContext]( 11 | request: Request[AnyContent], 12 | wholeCont: Request[AnyContent] => ActionCont[WholeRequestContext], 13 | normalCont: WholeRequestContext => ActionCont[NormalRequestContext], 14 | handlerCont: NormalRequestContext => ActionCont[Result], 15 | errorCont: WholeRequestContext => Throwable => ActionCont[Result]) 16 | (implicit executionContext: ExecutionContext): ActionCont[Result] = { 17 | 18 | for { 19 | wholeRequestContext <- wholeCont(request) 20 | wholeResult <- ActionCont.recover( 21 | for { 22 | normalRequestContext <- normalCont(wholeRequestContext) 23 | result <- handlerCont(normalRequestContext) 24 | } yield result) { 25 | case e => errorCont(wholeRequestContext)(e).run_ 26 | } 27 | } yield wholeResult 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /action-cont/src/main/scala/com/github/hexx/play/cont/FormCont.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont 2 | 3 | import play.api.data.Form 4 | import play.api.mvc.Result 5 | import play.api.mvc.Request 6 | import scala.concurrent.Future 7 | 8 | case class FormErrorException[A]( 9 | message: String = null, 10 | cause: Throwable = null, 11 | form: Form[A] 12 | ) extends Exception(message, cause) 13 | 14 | object FormCont { 15 | def apply[A](form: Form[A], request: Request[_]): ActionCont[A] = 16 | ActionCont(form.bindFromRequest()(request).fold(form => Future.failed(FormErrorException(form = form)), _)) 17 | 18 | def hasErrors[A](form: Form[A], request: Request[_])(hasErrors: Form[A] => Future[Result]): ActionCont[A] = 19 | ActionCont(form.bindFromRequest()(request).fold(hasErrors, _)) 20 | } 21 | -------------------------------------------------------------------------------- /action-cont/src/main/scala/com/github/hexx/play/cont/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play 2 | 3 | import play.api.mvc.Result 4 | import scala.concurrent.Future 5 | import scalaz.ContT 6 | 7 | package object cont { 8 | type ActionCont[A] = ContT[Future, Result, A] 9 | 10 | implicit class ActionContWithFilter[A](val actionCont: ActionCont[A]) extends AnyVal { 11 | def withFilter(f: A => Boolean): ActionCont[A] = 12 | ActionCont(k => 13 | actionCont.run(a => 14 | if (f(a)) { 15 | k(a) 16 | } else { 17 | throw new NoSuchElementException("ActionCont must not fail to filter.") 18 | } 19 | ) 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /action-cont/src/test/scala/com/github/hexx/play/cont/FormContTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.hexx.play.cont 2 | 3 | import org.scalatest.FunSpec 4 | import play.api.data.Form 5 | import play.api.data.Forms._ 6 | import play.api.mvc.Action 7 | import play.api.mvc.AnyContent 8 | import play.api.mvc.Request 9 | import play.api.mvc.Result 10 | import play.api.mvc.Results._ 11 | import play.api.test.FakeRequest 12 | import play.api.test.Helpers._ 13 | import scala.concurrent.ExecutionContext.Implicits.global 14 | import scala.concurrent.Future 15 | 16 | import scala.concurrent.Await 17 | import scala.concurrent.duration._ 18 | 19 | class FormContSpec extends FunSpec { 20 | case class AuthenticationParameter( 21 | name: String, 22 | password: String 23 | ) 24 | 25 | val authenticationParameterForm: Form[AuthenticationParameter] = Form( 26 | mapping( 27 | "name" -> text, 28 | "password" -> text 29 | )(AuthenticationParameter.apply)(_ => None) 30 | ) 31 | 32 | def cont(request: Request[AnyContent]): ActionCont[Result] = 33 | for { 34 | a <- FormCont(authenticationParameterForm, request) 35 | } yield Ok(s"name: ${a.name}, password: ${a.password}") 36 | 37 | def contBadRequestIfError(request: Request[AnyContent]): ActionCont[Result] = 38 | for { 39 | a <- FormCont.hasErrors(authenticationParameterForm, request)(_ => Future.successful(BadRequest)) 40 | } yield Ok(s"name: ${a.name}, password: ${a.password}") 41 | 42 | describe("FormCont") { 43 | it("result in Ok") { 44 | val action = ActionCont.run(cont) 45 | val request = FakeRequest("POST", "/").withFormUrlEncodedBody("name" -> "hexx", "password" -> "hogeika") 46 | val result = call(action, request) 47 | 48 | assert(status(result) === OK) 49 | assert(contentAsString(result) === "name: hexx, password: hogeika") 50 | } 51 | 52 | it("produce FormErrorException by an insufficient request") { 53 | val action = ActionCont.run(cont) 54 | val request = FakeRequest("POST", "/").withFormUrlEncodedBody("name" -> "hexx") 55 | val result = call(action, request) 56 | 57 | intercept[FormErrorException[AuthenticationParameter]] { 58 | status(result) 59 | } 60 | } 61 | 62 | it("result in BadRequest by an insufficient request") { 63 | val action = ActionCont.run(contBadRequestIfError) 64 | val request = FakeRequest("POST", "/").withFormUrlEncodedBody("name" -> "hexx") 65 | val result = call(action, request) 66 | 67 | assert(status(result) === BAD_REQUEST) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val commonSettings = Seq( 2 | scalaVersion := "2.10.5", 3 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint", "-language:_"), 4 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 5 | ) 6 | 7 | lazy val root = (project in file(".")).aggregate( 8 | actionCont, 9 | actionContSimple, 10 | actionContLib, 11 | play2AuthContSample 12 | ) 13 | 14 | lazy val actionCont = (project in file("action-cont")).settings( 15 | commonSettings ++ Seq( 16 | name := "action-cont", 17 | organization := "com.github.hexx", 18 | libraryDependencies ++= Seq( 19 | "com.typesafe.play" %% "play" % play.core.PlayVersion.current % "provided", 20 | "org.scalaz" %% "scalaz-core" % "7.0.7", 21 | "org.typelevel" %% "scalaz-contrib-210" % "0.1.5", 22 | "com.typesafe.play" %% "play-test" % play.core.PlayVersion.current % "test", 23 | "org.scalatest" %% "scalatest" % "2.2.4" % "test" 24 | ) 25 | ):_* 26 | ) 27 | 28 | lazy val actionContSimple = (project in file("action-cont-simple")).settings( 29 | commonSettings ++ Seq( 30 | libraryDependencies ++= Seq( 31 | "com.typesafe.play" %% "play" % play.core.PlayVersion.current % "provided" 32 | ) 33 | ):_* 34 | ) 35 | 36 | lazy val actionContLib = (project in file("action-cont-lib")).settings( 37 | commonSettings ++ Seq( 38 | libraryDependencies ++= Seq( 39 | "jp.t2v" %% "play2-auth" % "0.13.2" 40 | ) 41 | ):_* 42 | ).dependsOn(actionCont) 43 | 44 | lazy val play2AuthContSample = (project in file("play2-auth-cont-sample")).settings( 45 | commonSettings ++ Seq( 46 | libraryDependencies ++= Seq( 47 | jdbc, 48 | "org.mindrot" % "jbcrypt" % "0.3m", 49 | "org.scalikejdbc" %% "scalikejdbc" % "2.2.6", 50 | "org.scalikejdbc" %% "scalikejdbc-config" % "2.2.6", 51 | "org.scalikejdbc" %% "scalikejdbc-syntax-support-macro" % "2.2.6", 52 | "org.scalikejdbc" %% "scalikejdbc-test" % "2.2.6" % "test", 53 | "org.scalikejdbc" %% "scalikejdbc-play-plugin" % "2.3.6", 54 | "com.github.tototoshi" %% "play-flyway" % "1.2.1", 55 | "jp.t2v" %% "play2-auth-test" % "0.13.2" % "test" 56 | ), 57 | TwirlKeys.templateImports += "jp.t2v.lab.play2.auth.sample._" 58 | ) 59 | ).enablePlugins(play.PlayScala).dependsOn(actionContLib) 60 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/Global.scala: -------------------------------------------------------------------------------- 1 | import play.api._ 2 | 3 | import jp.t2v.lab.play2.auth.sample._ 4 | import jp.t2v.lab.play2.auth.sample.Role._ 5 | import scalikejdbc._ 6 | 7 | object Global extends GlobalSettings { 8 | 9 | override def onStart(app: Application) { 10 | if (Account.findAll.isEmpty) { 11 | Seq( 12 | Account(1, "alice@example.com", "secret", "Alice", Administrator), 13 | Account(2, "bob@example.com", "secret", "Bob", NormalUser), 14 | Account(3, "chris@example.com", "secret", "Chris", NormalUser) 15 | ) foreach Account.create 16 | } 17 | 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/BaseAuthConfig.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import jp.t2v.lab.play2.auth.AuthConfig 4 | import jp.t2v.lab.play2.auth.sample.{Role, Account} 5 | import jp.t2v.lab.play2.auth.sample.Role._ 6 | import play.api.mvc.RequestHeader 7 | import play.api.mvc.Results._ 8 | 9 | import scala.concurrent.{Future, ExecutionContext} 10 | import scala.reflect._ 11 | import play.Logger 12 | 13 | trait BaseAuthConfig extends AuthConfig { 14 | 15 | type Id = Int 16 | type User = Account 17 | type Authority = Role 18 | 19 | val idTag: ClassTag[Id] = classTag[Id] 20 | val sessionTimeoutInSeconds = 3600 21 | 22 | def resolveUser(id: Id)(implicit ctx: ExecutionContext) = Future.successful(Account.findById(id)) 23 | def authorizationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = throw new AssertionError("don't use") 24 | override def authorizationFailed(request: RequestHeader, user: User, authority: Option[Authority])(implicit ctx: ExecutionContext) = { 25 | Logger.info(s"authorizationFailed. userId: ${user.id}, userName: ${user.name}, authority: $authority") 26 | Future.successful(Forbidden("no permission")) 27 | } 28 | def authorize(user: User, authority: Authority)(implicit ctx: ExecutionContext) = Future.successful((user.role, authority) match { 29 | case (Administrator, _) => true 30 | case (NormalUser, NormalUser) => true 31 | case _ => false 32 | }) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/basic/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.basic 2 | 3 | import play.api.mvc.RequestHeader 4 | import play.api.mvc.Results._ 5 | 6 | import scala.concurrent.{Future, ExecutionContext} 7 | import jp.t2v.lab.play2.auth.AuthConfig 8 | import jp.t2v.lab.play2.auth.sample.{Role, Account} 9 | import jp.t2v.lab.play2.auth.sample.Role._ 10 | import scala.reflect.{ClassTag, classTag} 11 | 12 | trait AuthConfigImpl extends AuthConfig { 13 | 14 | type Id = Account 15 | type User = Account 16 | type Authority = Role 17 | 18 | val idTag: ClassTag[Id] = classTag[Id] 19 | val sessionTimeoutInSeconds = 3600 20 | 21 | def resolveUser(id: Id)(implicit ctx: ExecutionContext) = Future.successful(Some(id)) 22 | def authorize(user: User, authority: Authority)(implicit ctx: ExecutionContext) = Future.successful((user.role, authority) match { 23 | case (Administrator, _) => true 24 | case (NormalUser, NormalUser) => true 25 | case _ => false 26 | }) 27 | 28 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = throw new AssertionError("don't use application Login") 29 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = throw new AssertionError("don't use application Logout") 30 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful { 31 | Unauthorized.withHeaders("WWW-Authenticate" -> """Basic realm="SECRET AREA"""") 32 | } 33 | def authorizationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Forbidden("no permission")) 34 | 35 | override lazy val idContainer = new BasicAuthIdContainer 36 | 37 | override lazy val tokenAccessor = new BasicAuthTokenAccessor 38 | 39 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/basic/BasicAuthIdContainer.scala: -------------------------------------------------------------------------------- 1 | package controllers.basic 2 | 3 | import jp.t2v.lab.play2.auth.{AuthenticityToken, AsyncIdContainer} 4 | import play.api.mvc.RequestHeader 5 | import scala.concurrent.{Future, ExecutionContext} 6 | import jp.t2v.lab.play2.auth.sample.Account 7 | 8 | class BasicAuthIdContainer extends AsyncIdContainer[Account] { 9 | override def prolongTimeout(token: AuthenticityToken, timeoutInSeconds: Int)(implicit request: RequestHeader, context: ExecutionContext): Future[Unit] = { 10 | Future.successful(()) 11 | } 12 | 13 | override def get(token: AuthenticityToken)(implicit context: ExecutionContext): Future[Option[Account]] = Future { 14 | val Pattern = "(.*?):(.*)".r 15 | PartialFunction.condOpt(token) { 16 | case Pattern(user, pass) => Account.authenticate(user, pass) 17 | }.flatten 18 | } 19 | 20 | override def remove(token: AuthenticityToken)(implicit context: ExecutionContext): Future[Unit] = { 21 | Future.successful(()) 22 | } 23 | 24 | override def startNewSession(userId: Account, timeoutInSeconds: Int)(implicit request: RequestHeader, context: ExecutionContext): Future[AuthenticityToken] = { 25 | throw new AssertionError("don't use") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/basic/BasicAuthTokenAccessor.scala: -------------------------------------------------------------------------------- 1 | package controllers.basic 2 | 3 | import jp.t2v.lab.play2.auth.{AuthenticityToken, TokenAccessor} 4 | import play.api.mvc.{Result, RequestHeader} 5 | import org.apache.commons.codec.binary.Base64 6 | import java.nio.charset.Charset 7 | 8 | class BasicAuthTokenAccessor extends TokenAccessor { 9 | 10 | override def delete(result: Result)(implicit request: RequestHeader): Result = result 11 | 12 | override def put(token: AuthenticityToken)(result: Result)(implicit request: RequestHeader): Result = result 13 | 14 | override def extract(request: RequestHeader): Option[AuthenticityToken] = { 15 | val encoded = for { 16 | h <- request.headers.get("Authorization") 17 | if h.startsWith("Basic ") 18 | } yield h.substring(6) 19 | encoded.map(s => new String(Base64.decodeBase64(s), Charset.forName("UTF-8"))) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/basic/Messages.scala: -------------------------------------------------------------------------------- 1 | package controllers.basic 2 | 3 | import controllers.stack.Pjax 4 | import jp.t2v.lab.play2.auth.AuthElement 5 | import play.api.mvc.Controller 6 | import views.html 7 | import jp.t2v.lab.play2.auth.sample.Role._ 8 | import play.twirl.api.Html 9 | 10 | trait Messages extends Controller with AuthElement with AuthConfigImpl { 11 | 12 | def main = StackAction(AuthorityKey -> NormalUser) { implicit request => 13 | val title = "message main" 14 | Ok(html.message.main(title)) 15 | } 16 | 17 | def list = StackAction(AuthorityKey -> NormalUser) { implicit request => 18 | val title = "all messages" 19 | Ok(html.message.list(title)) 20 | } 21 | 22 | def detail(id: Int) = StackAction(AuthorityKey -> NormalUser) { implicit request => 23 | val title = "messages detail " 24 | Ok(html.message.detail(title + id)) 25 | } 26 | 27 | def write = StackAction(AuthorityKey -> Administrator) { implicit request => 28 | val title = "write message" 29 | Ok(html.message.write(title)) 30 | } 31 | 32 | protected implicit def template(implicit user: User): String => Html => Html = html.basic.fullTemplate(user) 33 | 34 | } 35 | object Messages extends Messages 36 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/builder/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.builder 2 | 3 | import controllers.BaseAuthConfig 4 | import play.api.mvc.RequestHeader 5 | import play.api.mvc.Results._ 6 | 7 | import scala.concurrent.{Future, ExecutionContext} 8 | 9 | trait AuthConfigImpl extends BaseAuthConfig { 10 | 11 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Messages.main)) 12 | 13 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 14 | 15 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 16 | 17 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/builder/Messages.scala: -------------------------------------------------------------------------------- 1 | package controllers.builder 2 | 3 | import jp.t2v.lab.play2.auth.AuthActionBuilders 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import jp.t2v.lab.play2.auth.sample.Role._ 6 | import play.api.mvc._ 7 | import play.twirl.api.Html 8 | import scalikejdbc.{DB, DBSession} 9 | import views.html 10 | 11 | import scala.concurrent.Future 12 | 13 | class TransactionalRequest[A](val dbSession: DBSession, request: Request[A]) extends WrappedRequest[A](request) 14 | object TransactionalAction extends ActionBuilder[TransactionalRequest] { 15 | override def invokeBlock[A](request: Request[A], block: (TransactionalRequest[A]) => Future[Result]): Future[Result] = { 16 | import scalikejdbc.TxBoundary.Future._ 17 | implicit val ctx = executionContext 18 | DB.localTx { session => 19 | block(new TransactionalRequest(session, request)) 20 | } 21 | } 22 | } 23 | 24 | trait Messages extends Controller with AuthActionBuilders with AuthConfigImpl { 25 | 26 | type AuthTxRequest[A] = GenericAuthRequest[A, TransactionalRequest] 27 | final def AuthorizationTxAction(authority: Authority): ActionBuilder[AuthTxRequest] = composeAuthorizationAction(TransactionalAction)(authority) 28 | 29 | class PjaxAuthRequest[A](val template: String => Html => Html, val authRequest: AuthTxRequest[A]) extends WrappedRequest[A](authRequest) 30 | object PjaxRefiner extends ActionTransformer[AuthTxRequest, PjaxAuthRequest] { 31 | override protected def transform[A](request: AuthTxRequest[A]): Future[PjaxAuthRequest[A]] = { 32 | val template: String => Html => Html = if (request.headers.keys("X-Pjax")) html.pjaxTemplate.apply else html.builder.fullTemplate.apply(request.user) 33 | Future.successful(new PjaxAuthRequest(template, request)) 34 | } 35 | } 36 | 37 | def MyAction(authority: Authority): ActionBuilder[PjaxAuthRequest] = AuthorizationTxAction(authority) andThen PjaxRefiner 38 | 39 | def main = MyAction(NormalUser) { implicit request => 40 | val title = "message main" 41 | println(Account.findAll()(request.authRequest.underlying.dbSession)) 42 | Ok(html.message.main(title)(request.template)) 43 | } 44 | 45 | def list = MyAction(NormalUser) { implicit request => 46 | val title = "all messages" 47 | Ok(html.message.list(title)(request.template)) 48 | } 49 | 50 | def detail(id: Int) = MyAction(NormalUser) {implicit request => 51 | val title = "messages detail " 52 | Ok(html.message.detail(title + id)(request.template)) 53 | } 54 | 55 | def write = MyAction(Administrator) { implicit request => 56 | val title = "write message" 57 | Ok(html.message.write(title)(request.template)) 58 | } 59 | 60 | } 61 | object Messages extends Messages 62 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/builder/Sessions.scala: -------------------------------------------------------------------------------- 1 | package controllers.builder 2 | 3 | import jp.t2v.lab.play2.auth.LoginLogout 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import play.api.data.Form 6 | import play.api.data.Forms._ 7 | import play.api.mvc.{Action, Controller} 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 12 | 13 | object Sessions extends Controller with LoginLogout with AuthConfigImpl { 14 | 15 | val loginForm = Form { 16 | mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) 17 | .verifying("Invalid email or password", result => result.isDefined) 18 | } 19 | 20 | def login = Action { implicit request => 21 | Ok(html.builder.login(loginForm)) 22 | } 23 | 24 | def logout = Action.async { implicit request => 25 | gotoLogoutSucceeded.map(_.flashing( 26 | "success" -> "You've been logged out" 27 | )) 28 | } 29 | 30 | def authenticate = Action.async { implicit request => 31 | loginForm.bindFromRequest.fold( 32 | formWithErrors => Future.successful(BadRequest(html.builder.login(formWithErrors))), 33 | user => gotoLoginSucceeded(user.get.id) 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/cont/MessageCont.scala: -------------------------------------------------------------------------------- 1 | package controllers.cont 2 | 3 | import com.github.hexx.play.cont.ActionCont 4 | import com.github.hexx.play.cont.play2auth.AuthElementCont 5 | import jp.t2v.lab.play2.auth.AuthConfig 6 | import play.api.mvc.Request 7 | import play.api.mvc.Result 8 | import play.api.mvc.Results.Ok 9 | import play.twirl.api.Html 10 | import scala.concurrent.ExecutionContext 11 | 12 | trait MessageCont extends AuthElementCont with AuthConfig { 13 | type Template = controllers.cont.PjaxCont.Template 14 | 15 | def messageCont[A](authority: Authority, fullTemplate: User => Template, templateToHtml: Template => Html) 16 | (implicit request: Request[A], ec: ExecutionContext): ActionCont[Result] = 17 | for { 18 | user <- authElementCont(authority) 19 | template <- PjaxCont(fullTemplate(user)) 20 | } yield Ok(templateToHtml(template)) 21 | } 22 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/cont/PjaxCont.scala: -------------------------------------------------------------------------------- 1 | package controllers.cont 2 | 3 | import com.github.hexx.play.cont.ActionCont 4 | import play.api.mvc.RequestHeader 5 | import play.twirl.api.Html 6 | import views.html 7 | import scala.concurrent.ExecutionContext 8 | 9 | object PjaxCont { 10 | type Template = String => Html => Html 11 | 12 | def apply(fullTemplate: Template)(implicit request: RequestHeader, ec: ExecutionContext): ActionCont[Template] = 13 | ActionCont.successful(if (request.headers.keys("X-Pjax")) html.pjaxTemplate.apply else fullTemplate) 14 | } 15 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/cont/TokenValidateElementCont.scala: -------------------------------------------------------------------------------- 1 | package controllers.cont 2 | 3 | import com.github.hexx.play.cont.ActionCont 4 | import scala.concurrent.ExecutionContext 5 | import scala.concurrent.Future 6 | import play.api.mvc.Request 7 | import play.api.data._ 8 | import play.api.data.Forms._ 9 | import play.api.mvc.Results.BadRequest 10 | import scala.util.Random 11 | import java.security.SecureRandom 12 | import controllers.stack.PreventingCsrfToken 13 | 14 | trait TokenValidateElementCont { 15 | private[this] val PreventingCsrfTokenSessionKey = "preventingCsrfToken" 16 | 17 | private[this] val tokenForm = Form(PreventingCsrfToken.FormKey -> text) 18 | 19 | private[this] val random = new Random(new SecureRandom) 20 | 21 | private[this] val table = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') ++ "^`~:/?,.{[}}|+_()*^%$#@!" 22 | 23 | private[this] def generateToken: PreventingCsrfToken = PreventingCsrfToken { 24 | Iterator.continually(random.nextInt(table.size)).map(table).take(32).mkString 25 | } 26 | 27 | private[this] def validateToken(request: Request[_]): Boolean = (for { 28 | tokenInForm <- tokenForm.bindFromRequest()(request).value 29 | tokenInSession <- request.session.get(PreventingCsrfTokenSessionKey) 30 | } yield tokenInForm == tokenInSession) getOrElse false 31 | 32 | def tokenValidateElementCont[A](ignoreTokenValidation: Boolean)(implicit request: Request[A], ec: ExecutionContext): ActionCont[PreventingCsrfToken] = 33 | ActionCont(f => 34 | if (ignoreTokenValidation || validateToken(request)) { 35 | val newToken = generateToken 36 | f(newToken).map(_.withSession(PreventingCsrfTokenSessionKey -> newToken.value)) 37 | } else { 38 | Future.successful(BadRequest("Invalid preventing CSRF token")) 39 | } 40 | ) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/csrf/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.csrf 2 | 3 | import controllers.BaseAuthConfig 4 | import play.api.mvc.RequestHeader 5 | import play.api.mvc.Results._ 6 | 7 | import scala.concurrent.{Future, ExecutionContext} 8 | 9 | trait AuthConfigImpl extends BaseAuthConfig { 10 | 11 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.PreventingCsrfSample.formWithToken)) 12 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 13 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/csrf/PreventingCsrfSample.scala: -------------------------------------------------------------------------------- 1 | package controllers.csrf 2 | 3 | import com.github.hexx.play.cont._ 4 | import com.github.hexx.play.cont.play2auth.AuthElementCont 5 | import controllers.stack.PreventingCsrfToken 6 | import controllers.cont.TokenValidateElementCont 7 | import jp.t2v.lab.play2.auth.sample.Role._ 8 | import play.api.data.Form 9 | import play.api.data.Forms._ 10 | import play.api.mvc.Controller 11 | import play.api.mvc.AnyContent 12 | import play.api.mvc.Request 13 | import scala.concurrent.ExecutionContext 14 | 15 | trait PreventingCsrfSample extends Controller with AuthElementCont with TokenValidateElementCont with AuthConfigImpl { 16 | implicit val ec: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext 17 | 18 | def checkCont(authority: Authority, ignoreTokenValidation: Boolean) 19 | (implicit request: Request[AnyContent], ec: ExecutionContext): ActionCont[(User, PreventingCsrfToken)] = 20 | for { 21 | user <- authElementCont(NormalUser) 22 | token <- tokenValidateElementCont(ignoreTokenValidation) 23 | } yield (user, token) 24 | 25 | def formWithToken = ActionCont.run(implicit request => 26 | for ( 27 | (user, token) <- checkCont(NormalUser, ignoreTokenValidation = true) 28 | ) yield Ok(views.html.csrf.formWithToken()(user, token)) 29 | ) 30 | 31 | def formWithoutToken = ActionCont.run(implicit request => 32 | for ( 33 | (user, _) <- checkCont(NormalUser, ignoreTokenValidation = true) 34 | ) yield Ok(views.html.csrf.formWithoutToken()(user)) 35 | ) 36 | 37 | val form = Form { single("message" -> text) } 38 | 39 | def submitTarget = ActionCont.run(implicit request => 40 | for { 41 | _ <- checkCont(NormalUser, ignoreTokenValidation = false) 42 | message <- FormCont.hasErrors(form, request)(_ => throw new Exception) 43 | } yield Ok(message).as("text/plain") 44 | ) 45 | } 46 | 47 | object PreventingCsrfSample extends PreventingCsrfSample 48 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/csrf/Sessions.scala: -------------------------------------------------------------------------------- 1 | package controllers.csrf 2 | 3 | import jp.t2v.lab.play2.auth.LoginLogout 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import play.api.data.Form 6 | import play.api.data.Forms._ 7 | import play.api.mvc.{Action, Controller} 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 12 | 13 | object Sessions extends Controller with LoginLogout with AuthConfigImpl { 14 | 15 | val loginForm = Form { 16 | mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) 17 | .verifying("Invalid email or password", result => result.isDefined) 18 | } 19 | 20 | def login = Action { implicit request => 21 | Ok(html.csrf.login(loginForm)) 22 | } 23 | 24 | def logout = Action.async { implicit request => 25 | gotoLogoutSucceeded.map(_.flashing( 26 | "success" -> "You've been logged out" 27 | )) 28 | } 29 | 30 | def authenticate = Action.async { implicit request => 31 | loginForm.bindFromRequest.fold( 32 | formWithErrors => Future.successful(BadRequest(html.csrf.login(formWithErrors))), 33 | user => gotoLoginSucceeded(user.get.id) 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/ephemeral/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.ephemeral 2 | 3 | import controllers.BaseAuthConfig 4 | import play.api.mvc.RequestHeader 5 | import play.api.mvc.Results._ 6 | 7 | import scala.concurrent.{Future, ExecutionContext} 8 | 9 | trait AuthConfigImpl extends BaseAuthConfig { 10 | 11 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Messages.main)) 12 | 13 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 14 | 15 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 16 | 17 | override lazy val isTransientCookie = true 18 | 19 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/ephemeral/Messages.scala: -------------------------------------------------------------------------------- 1 | package controllers.ephemeral 2 | 3 | import controllers.stack.Pjax 4 | import jp.t2v.lab.play2.auth.AuthElement 5 | import play.api.mvc.Controller 6 | import views.html 7 | import jp.t2v.lab.play2.auth.sample.Role._ 8 | 9 | trait Messages extends Controller with Pjax with AuthElement with AuthConfigImpl { 10 | 11 | def main = StackAction(AuthorityKey -> NormalUser) { implicit request => 12 | val title = "message main" 13 | Ok(html.message.main(title)) 14 | } 15 | 16 | def list = StackAction(AuthorityKey -> NormalUser) { implicit request => 17 | val title = "all messages" 18 | Ok(html.message.list(title)) 19 | } 20 | 21 | def detail(id: Int) = StackAction(AuthorityKey -> NormalUser) { implicit request => 22 | val title = "messages detail " 23 | Ok(html.message.detail(title + id)) 24 | } 25 | 26 | def write = StackAction(AuthorityKey -> Administrator) { implicit request => 27 | val title = "write message" 28 | Ok(html.message.write(title)) 29 | } 30 | 31 | protected val fullTemplate: User => Template = html.ephemeral.fullTemplate.apply 32 | 33 | } 34 | object Messages extends Messages 35 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/ephemeral/Sessions.scala: -------------------------------------------------------------------------------- 1 | package controllers.ephemeral 2 | 3 | import jp.t2v.lab.play2.auth.LoginLogout 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import play.api.data.Form 6 | import play.api.data.Forms._ 7 | import play.api.mvc.{Action, Controller} 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 12 | 13 | object Sessions extends Controller with LoginLogout with AuthConfigImpl { 14 | 15 | val loginForm = Form { 16 | mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) 17 | .verifying("Invalid email or password", result => result.isDefined) 18 | } 19 | 20 | def login = Action { implicit request => 21 | Ok(html.ephemeral.login(loginForm)) 22 | } 23 | 24 | def logout = Action.async { implicit request => 25 | gotoLogoutSucceeded.map(_.flashing( 26 | "success" -> "You've been logged out" 27 | )) 28 | } 29 | 30 | def authenticate = Action.async { implicit request => 31 | loginForm.bindFromRequest.fold( 32 | formWithErrors => Future.successful(BadRequest(html.ephemeral.login(formWithErrors))), 33 | user => gotoLoginSucceeded(user.get.id) 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/rememberme/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.rememberme 2 | 3 | import controllers.BaseAuthConfig 4 | import play.api.mvc.RequestHeader 5 | import play.api.mvc.Results._ 6 | 7 | import scala.concurrent.{Future, ExecutionContext} 8 | 9 | trait AuthConfigImpl extends BaseAuthConfig { 10 | 11 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Messages.main)) 12 | 13 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 14 | 15 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 16 | 17 | override lazy val tokenAccessor = new RememberMeTokenAccessor(sessionTimeoutInSeconds) 18 | 19 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/rememberme/Messages.scala: -------------------------------------------------------------------------------- 1 | package controllers.rememberme 2 | 3 | import controllers.stack.Pjax 4 | import jp.t2v.lab.play2.auth.AuthElement 5 | import play.api.mvc.Controller 6 | import views.html 7 | import jp.t2v.lab.play2.auth.sample.Role._ 8 | 9 | trait Messages extends Controller with Pjax with AuthElement with AuthConfigImpl { 10 | 11 | def main = StackAction(AuthorityKey -> NormalUser) { implicit request => 12 | val title = "message main" 13 | Ok(html.message.main(title)) 14 | } 15 | 16 | def list = StackAction(AuthorityKey -> NormalUser) { implicit request => 17 | val title = "all messages" 18 | Ok(html.message.list(title)) 19 | } 20 | 21 | def detail(id: Int) = StackAction(AuthorityKey -> NormalUser) { implicit request => 22 | val title = "messages detail " 23 | Ok(html.message.detail(title + id)) 24 | } 25 | 26 | def write = StackAction(AuthorityKey -> Administrator) { implicit request => 27 | val title = "write message" 28 | Ok(html.message.write(title)) 29 | } 30 | 31 | protected val fullTemplate: User => Template = html.rememberme.fullTemplate.apply 32 | 33 | } 34 | object Messages extends Messages 35 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/rememberme/RememberMeTokenAccessor.scala: -------------------------------------------------------------------------------- 1 | package controllers.rememberme 2 | 3 | import jp.t2v.lab.play2.auth._ 4 | import play.api.mvc.{Cookie, RequestHeader, Result} 5 | 6 | class RememberMeTokenAccessor(maxAge: Int) extends CookieTokenAccessor() { 7 | 8 | override def put(token: AuthenticityToken)(result: Result)(implicit request: RequestHeader): Result = { 9 | val remember = request.tags.get("rememberme").exists("true" ==) || request.session.get("rememberme").exists("true" ==) 10 | val _maxAge = if (remember) Some(maxAge) else None 11 | val c = Cookie(cookieName, sign(token), _maxAge, cookiePathOption, cookieDomainOption, cookieSecureOption, cookieHttpOnlyOption) 12 | result.withCookies(c) 13 | } 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/rememberme/Sessions.scala: -------------------------------------------------------------------------------- 1 | package controllers.rememberme 2 | 3 | import jp.t2v.lab.play2.auth.LoginLogout 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import play.api.data.Form 6 | import play.api.data.Forms._ 7 | import play.api.mvc.{Action, Controller} 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 12 | 13 | object Sessions extends Controller with LoginLogout with AuthConfigImpl { 14 | 15 | val loginForm = Form { 16 | mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) 17 | .verifying("Invalid email or password", result => result.isDefined) 18 | } 19 | val remembermeForm = Form { 20 | "rememberme" -> boolean 21 | } 22 | 23 | def login = Action { implicit request => 24 | Ok(html.rememberme.login(loginForm, remembermeForm.fill(request.session.get("rememberme").exists("true" ==)))) 25 | } 26 | 27 | def logout = Action.async { implicit request => 28 | gotoLogoutSucceeded.map(_.flashing( 29 | "success" -> "You've been logged out" 30 | )) 31 | } 32 | 33 | def authenticate = Action.async { implicit request => 34 | val rememberme = remembermeForm.bindFromRequest() 35 | loginForm.bindFromRequest.fold( 36 | formWithErrors => Future.successful(BadRequest(html.rememberme.login(formWithErrors, rememberme))), 37 | { user => 38 | val req = request.copy(tags = request.tags + ("rememberme" -> rememberme.get.toString)) 39 | gotoLoginSucceeded(user.get.id)(req, defaultContext).map(_.withSession("rememberme" -> rememberme.get.toString)) 40 | } 41 | ) 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/stack/Pjax.scala: -------------------------------------------------------------------------------- 1 | package controllers.stack 2 | 3 | import controllers.BaseAuthConfig 4 | import jp.t2v.lab.play2.auth.AuthElement 5 | import jp.t2v.lab.play2.stackc.{RequestAttributeKey, RequestWithAttributes, StackableController} 6 | import play.api.mvc.{Controller, Result} 7 | import play.twirl.api.Html 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | 12 | trait Pjax extends StackableController with AuthElement { 13 | self: Controller with BaseAuthConfig => 14 | 15 | type Template = String => Html => Html 16 | 17 | case object TemplateKey extends RequestAttributeKey[Template] 18 | 19 | abstract override def proceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] => Future[Result]): Future[Result] = { 20 | super.proceed(req) { req => 21 | val template: Template = if (req.headers.keys("X-Pjax")) html.pjaxTemplate.apply else fullTemplate(loggedIn(req)) 22 | f(req.set(TemplateKey, template)) 23 | } 24 | } 25 | 26 | implicit def template(implicit req: RequestWithAttributes[_]): Template = req.get(TemplateKey).get 27 | 28 | protected val fullTemplate: User => Template 29 | 30 | } 31 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/stack/TokenValidateElement.scala: -------------------------------------------------------------------------------- 1 | package controllers.stack 2 | 3 | import jp.t2v.lab.play2.stackc.{RequestAttributeKey, RequestWithAttributes, StackableController} 4 | import scala.concurrent.Future 5 | import play.api.mvc.{Result, Request, Controller} 6 | import play.api.data._ 7 | import play.api.data.Forms._ 8 | import scala.util.Random 9 | import java.security.SecureRandom 10 | 11 | trait TokenValidateElement extends StackableController { 12 | self: Controller => 13 | 14 | private val PreventingCsrfTokenSessionKey = "preventingCsrfToken" 15 | 16 | private val tokenForm = Form(PreventingCsrfToken.FormKey -> text) 17 | 18 | private val random = new Random(new SecureRandom) 19 | private val table = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') ++ "^`~:/?,.{[}}|+_()*^%$#@!" 20 | 21 | private def generateToken: PreventingCsrfToken = PreventingCsrfToken { 22 | Iterator.continually(random.nextInt(table.size)).map(table).take(32).mkString 23 | } 24 | 25 | case object PreventingCsrfTokenKey extends RequestAttributeKey[PreventingCsrfToken] 26 | case object IgnoreTokenValidation extends RequestAttributeKey[Boolean] 27 | 28 | private def validateToken(request: Request[_]): Boolean = (for { 29 | tokenInForm <- tokenForm.bindFromRequest()(request).value 30 | tokenInSession <- request.session.get(PreventingCsrfTokenSessionKey) 31 | } yield tokenInForm == tokenInSession) getOrElse false 32 | 33 | override def proceed[A](request: RequestWithAttributes[A])(f: RequestWithAttributes[A] => Future[Result]): Future[Result] = { 34 | if (isIgnoreTokenValidation(request) || validateToken(request)) { 35 | implicit val ctx = StackActionExecutionContext(request) 36 | val newToken = generateToken 37 | super.proceed(request.set(PreventingCsrfTokenKey, newToken))(f) map { 38 | _.withSession(PreventingCsrfTokenSessionKey -> newToken.value) 39 | } 40 | } else { 41 | Future.successful(BadRequest("Invalid preventing CSRF token")) 42 | } 43 | } 44 | 45 | implicit def isIgnoreTokenValidation(implicit request: RequestWithAttributes[_]): Boolean = 46 | request.get(IgnoreTokenValidation).exists(identity) 47 | 48 | implicit def preventingCsrfToken(implicit request: RequestWithAttributes[_]): PreventingCsrfToken = 49 | request.get(PreventingCsrfTokenKey).get 50 | 51 | 52 | } 53 | 54 | case class PreventingCsrfToken(value: String) 55 | 56 | object PreventingCsrfToken { 57 | 58 | val FormKey = "preventingCsrfToken" 59 | 60 | } 61 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/standard/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.standard 2 | 3 | import controllers.BaseAuthConfig 4 | import play.api.mvc.RequestHeader 5 | import play.api.mvc.Results._ 6 | 7 | import scala.concurrent.{Future, ExecutionContext} 8 | 9 | trait AuthConfigImpl extends BaseAuthConfig { 10 | 11 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Messages.main)) 12 | 13 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 14 | 15 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 16 | 17 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/standard/Messages.scala: -------------------------------------------------------------------------------- 1 | package controllers.standard 2 | 3 | import com.github.hexx.play.cont.ActionCont 4 | import controllers.cont.MessageCont 5 | import jp.t2v.lab.play2.auth.sample.Role._ 6 | import play.api.mvc.Action 7 | import play.api.mvc.AnyContent 8 | import play.api.mvc.Controller 9 | import play.twirl.api.Html 10 | import scala.concurrent.ExecutionContext 11 | import views.html 12 | 13 | trait Messages extends Controller with MessageCont with AuthConfigImpl { 14 | implicit val ec: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext 15 | 16 | def action(authority: Authority, templateToHtml: Template => Html) 17 | (implicit ec: ExecutionContext): Action[AnyContent] = 18 | ActionCont.run(request => messageCont(authority, html.standard.fullTemplate.apply, templateToHtml)(request, ec)) 19 | 20 | def main = action(NormalUser, html.message.main("message main")(_)) 21 | 22 | def list = action(NormalUser, html.message.list("all messages")(_)) 23 | 24 | def detail(id: Int) = action(NormalUser, html.message.detail("messages detail " + id)(_)) 25 | 26 | def write = action(Administrator, html.message.write("write message")(_)) 27 | } 28 | 29 | object Messages extends Messages 30 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/standard/Sessions.scala: -------------------------------------------------------------------------------- 1 | package controllers.standard 2 | 3 | import jp.t2v.lab.play2.auth.LoginLogout 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import play.api.data.Form 6 | import play.api.data.Forms._ 7 | import play.api.mvc.{Action, Controller} 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 12 | 13 | object Sessions extends Controller with LoginLogout with AuthConfigImpl { 14 | 15 | val loginForm = Form { 16 | mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) 17 | .verifying("Invalid email or password", result => result.isDefined) 18 | } 19 | 20 | def login = Action { implicit request => 21 | Ok(html.standard.login(loginForm)) 22 | } 23 | 24 | def logout = Action.async { implicit request => 25 | gotoLogoutSucceeded.map(_.flashing( 26 | "success" -> "You've been logged out" 27 | ).removingFromSession("rememberme")) 28 | } 29 | 30 | def authenticate = Action.async { implicit request => 31 | loginForm.bindFromRequest.fold( 32 | formWithErrors => Future.successful(BadRequest(html.standard.login(formWithErrors))), 33 | user => gotoLoginSucceeded(user.get.id) 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/stateless/AuthConfigImpl.scala: -------------------------------------------------------------------------------- 1 | package controllers.stateless 2 | 3 | import controllers.BaseAuthConfig 4 | import play.api.mvc.RequestHeader 5 | import play.api.mvc.Results._ 6 | 7 | import scala.concurrent.{Future, ExecutionContext} 8 | import jp.t2v.lab.play2.auth.{CookieIdContainer, AsyncIdContainer} 9 | 10 | trait AuthConfigImpl extends BaseAuthConfig { 11 | 12 | def loginSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Messages.main)) 13 | 14 | def logoutSucceeded(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 15 | 16 | def authenticationFailed(request: RequestHeader)(implicit ctx: ExecutionContext) = Future.successful(Redirect(routes.Sessions.login)) 17 | 18 | override lazy val idContainer = AsyncIdContainer(new CookieIdContainer[Id]) 19 | 20 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/stateless/Messages.scala: -------------------------------------------------------------------------------- 1 | package controllers.stateless 2 | 3 | import controllers.stack.Pjax 4 | import jp.t2v.lab.play2.auth.AuthElement 5 | import play.api.mvc.Controller 6 | import views.html 7 | import jp.t2v.lab.play2.auth.sample.Role._ 8 | 9 | trait Messages extends Controller with Pjax with AuthElement with AuthConfigImpl { 10 | 11 | def main = StackAction(AuthorityKey -> NormalUser) { implicit request => 12 | val title = "message main" 13 | Ok(html.message.main(title)) 14 | } 15 | 16 | def list = StackAction(AuthorityKey -> NormalUser) { implicit request => 17 | val title = "all messages" 18 | Ok(html.message.list(title)) 19 | } 20 | 21 | def detail(id: Int) = StackAction(AuthorityKey -> NormalUser) { implicit request => 22 | val title = "messages detail " 23 | Ok(html.message.detail(title + id)) 24 | } 25 | 26 | def write = StackAction(AuthorityKey -> Administrator) { implicit request => 27 | val title = "write message" 28 | Ok(html.message.write(title)) 29 | } 30 | 31 | protected val fullTemplate: User => Template = html.stateless.fullTemplate.apply 32 | 33 | } 34 | object Messages extends Messages 35 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/controllers/stateless/Sessions.scala: -------------------------------------------------------------------------------- 1 | package controllers.stateless 2 | 3 | import jp.t2v.lab.play2.auth.LoginLogout 4 | import jp.t2v.lab.play2.auth.sample.Account 5 | import play.api.data.Form 6 | import play.api.data.Forms._ 7 | import play.api.mvc.{Action, Controller} 8 | import views.html 9 | 10 | import scala.concurrent.Future 11 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 12 | 13 | object Sessions extends Controller with LoginLogout with AuthConfigImpl { 14 | 15 | val loginForm = Form { 16 | mapping("email" -> email, "password" -> text)(Account.authenticate)(_.map(u => (u.email, ""))) 17 | .verifying("Invalid email or password", result => result.isDefined) 18 | } 19 | 20 | def login = Action { implicit request => 21 | Ok(html.stateless.login(loginForm)) 22 | } 23 | 24 | def logout = Action.async { implicit request => 25 | gotoLogoutSucceeded.map(_.flashing( 26 | "success" -> "You've been logged out" 27 | )) 28 | } 29 | 30 | def authenticate = Action.async { implicit request => 31 | loginForm.bindFromRequest.fold( 32 | formWithErrors => Future.successful(BadRequest(html.stateless.login(formWithErrors))), 33 | user => gotoLoginSucceeded(user.get.id) 34 | ) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/jp/t2v/lab/play2/auth/sample/Account.scala: -------------------------------------------------------------------------------- 1 | package jp.t2v.lab.play2.auth.sample 2 | 3 | import org.mindrot.jbcrypt.BCrypt 4 | import scalikejdbc._ 5 | 6 | case class Account(id: Int, email: String, password: String, name: String, role: Role) 7 | 8 | object Account extends SQLSyntaxSupport[Account] { 9 | 10 | private val a = syntax("a") 11 | 12 | def apply(a: SyntaxProvider[Account])(rs: WrappedResultSet): Account = autoConstruct(rs, a) 13 | 14 | private val auto = AutoSession 15 | 16 | def authenticate(email: String, password: String)(implicit s: DBSession = auto): Option[Account] = { 17 | findByEmail(email).filter { account => BCrypt.checkpw(password, account.password) } 18 | } 19 | 20 | def findByEmail(email: String)(implicit s: DBSession = auto): Option[Account] = withSQL { 21 | select.from(Account as a).where.eq(a.email, email) 22 | }.map(Account(a)).single.apply() 23 | 24 | def findById(id: Int)(implicit s: DBSession = auto): Option[Account] = withSQL { 25 | select.from(Account as a).where.eq(a.id, id) 26 | }.map(Account(a)).single.apply() 27 | 28 | def findAll()(implicit s: DBSession = auto): Seq[Account] = withSQL { 29 | select.from(Account as a) 30 | }.map(Account(a)).list.apply() 31 | 32 | def create(account: Account)(implicit s: DBSession = auto) { 33 | withSQL { 34 | import account._ 35 | val pass = BCrypt.hashpw(account.password, BCrypt.gensalt()) 36 | insert.into(Account).values(id, email, pass, name, role.toString) 37 | }.update.apply() 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/jp/t2v/lab/play2/auth/sample/Role.scala: -------------------------------------------------------------------------------- 1 | package jp.t2v.lab.play2.auth.sample 2 | 3 | import scalikejdbc.TypeBinder 4 | 5 | sealed trait Role 6 | 7 | object Role { 8 | 9 | case object Administrator extends Role 10 | case object NormalUser extends Role 11 | 12 | def valueOf(value: String): Role = value match { 13 | case "Administrator" => Administrator 14 | case "NormalUser" => NormalUser 15 | case _ => throw new IllegalArgumentException() 16 | } 17 | 18 | implicit val typeBinder: TypeBinder[Role] = TypeBinder.string.map(valueOf) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/basic/fullTemplate.scala.html: -------------------------------------------------------------------------------- 1 | @(account: Account)(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 |
7 |19 | @error.message 20 |
21 | } 22 | 23 | @flash.get("success").map { message => 24 |25 | @message 26 |
27 | } 28 | 29 |30 | 31 |
32 |33 | 34 |
35 |36 | 37 |
38 | 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/csrf/formWithToken.scala.html: -------------------------------------------------------------------------------- 1 | @()(implicit account: Account, token: stack.PreventingCsrfToken) 2 | 3 | @fullTemplate(account)("with Token") { 4 | 5 | @helper.form(action = controllers.csrf.routes.PreventingCsrfSample.submitTarget()) { 6 | 7 |19 | @error.message 20 |
21 | } 22 | 23 | @flash.get("success").map { message => 24 |25 | @message 26 |
27 | } 28 | 29 |30 | 31 |
32 |33 | 34 |
35 |36 | 37 |
38 | 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/ephemeral/fullTemplate.scala.html: -------------------------------------------------------------------------------- 1 | @(account: Account)(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 |19 | @error.message 20 |
21 | } 22 | 23 | @flash.get("success").map { message => 24 |25 | @message 26 |
27 | } 28 | 29 |30 | 31 |
32 |33 | 34 |
35 |36 | 37 |
38 | 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/message/detail.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(implicit template: String => Html => Html) 2 | 3 | @template(title) { 4 | 5 | detail 6 | 7 | } 8 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/message/list.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(implicit template: String => Html => Html) 2 | 3 | @template(title) { 4 | 5 | list 6 | 7 | } 8 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/message/main.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(implicit template: String => Html => Html) 2 | 3 | @template(title) { 4 | 5 | main 6 | 7 | } 8 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/message/write.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(implicit template: String => Html => Html) 2 | 3 | @template(title) { 4 | 5 | write 6 | 7 | } 8 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/pjaxTemplate.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String)(content: Html) 2 | 3 |19 | @error.message 20 |
21 | } 22 | 23 | @flash.get("success").map { message => 24 |25 | @message 26 |
27 | } 28 | 29 |30 | 31 |
32 |33 | 34 |
35 |36 | 37 |
38 |39 | 40 |
41 | 42 | } 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/standard/fullTemplate.scala.html: -------------------------------------------------------------------------------- 1 | @(account: Account)(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 |19 | @error.message 20 |
21 | } 22 | 23 | @flash.get("success").map { message => 24 |25 | @message 26 |
27 | } 28 | 29 |30 | 31 |
32 |33 | 34 |
35 |36 | 37 |
38 | 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/app/views/stateless/fullTemplate.scala.html: -------------------------------------------------------------------------------- 1 | @(account: Account)(title: String)(content: Html) 2 | 3 | 4 | 5 | 6 | 7 |19 | @error.message 20 |
21 | } 22 | 23 | @flash.get("success").map { message => 24 |25 | @message 26 |
27 | } 28 | 29 |30 | 31 |
32 |33 | 34 |
35 |36 | 37 |
38 | 39 | } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # If you deploy your application to several instances be sure to use the same key! 8 | application.secret="79V2@SMp]7wuJo0weB7b4PUIk41OmsFwivxL01S?JyEluwPvB]bs/GLR5_O0;sor" 9 | 10 | # The application languages 11 | # ~~~~~ 12 | application.langs="ja" 13 | 14 | # Global object class 15 | # ~~~~~ 16 | # Define the Global object class for this application. 17 | # Default to Global in the root package. 18 | # global=Global 19 | 20 | # Database configuration 21 | # ~~~~~ 22 | # You can declare as many datasources as you want. 23 | # By convention, the default datasource is named `default` 24 | # 25 | db.default.driver=org.h2.Driver 26 | db.default.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1" 27 | db.default.user=sa 28 | db.default.password="" 29 | 30 | # Connection Pool settings 31 | db.default.poolInitialSize=10 32 | db.default.poolMaxSize=20 33 | db.default.connectionTimeoutMillis=1000 34 | 35 | # Evolutions 36 | # ~~~~~ 37 | # You can disable evolutions if needed 38 | dbplugin=disabled 39 | evolutionplugin=disabled 40 | 41 | # Logger 42 | # ~~~~~ 43 | # You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory . 44 | 45 | # Root logger: 46 | logger.root=ERROR 47 | 48 | # Logger used by the framework: 49 | logger.play=INFO 50 | 51 | # Logger provided to your application: 52 | logger.application=DEBUG 53 | 54 | scalikejdbc.global.loggingSQLAndTime.enabled=true 55 | scalikejdbc.global.loggingSQLAndTime.logLevel=debug 56 | scalikejdbc.global.loggingSQLAndTime.warningEnabled=true 57 | scalikejdbc.global.loggingSQLAndTime.warningThresholdMillis=1000 58 | scalikejdbc.global.loggingSQLAndTime.warningLogLevel=warn 59 | 60 | #dbplugin=disabled 61 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/conf/db/migration/default/V1__create_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE account ( 2 | id integer NOT NULL PRIMARY KEY, 3 | email varchar NOT NULL UNIQUE, 4 | password varchar NOT NULL, 5 | name varchar NOT NULL, 6 | role varchar NOT NULL 7 | ); 8 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/conf/play.plugins: -------------------------------------------------------------------------------- 1 | 666:com.github.tototoshi.play2.flyway.Plugin 2 | 9999:scalikejdbc.PlayPlugin -------------------------------------------------------------------------------- /play2-auth-cont-sample/conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | # Standard 6 | GET / controllers.standard.Sessions.login 7 | POST /standard/login controllers.standard.Sessions.authenticate 8 | GET /standard/logout controllers.standard.Sessions.logout 9 | 10 | GET /standard/messages/main controllers.standard.Messages.main 11 | GET /standard/messages/list controllers.standard.Messages.list 12 | GET /standard/messages/detail/:id controllers.standard.Messages.detail(id: Int) 13 | GET /standard/messages/write controllers.standard.Messages.write 14 | 15 | # Builder 16 | GET /builder/ controllers.builder.Sessions.login 17 | POST /builder/login controllers.builder.Sessions.authenticate 18 | GET /builder/logout controllers.builder.Sessions.logout 19 | 20 | GET /builder/messages/main controllers.builder.Messages.main 21 | GET /builder/messages/list controllers.builder.Messages.list 22 | GET /builder/messages/detail/:id controllers.builder.Messages.detail(id: Int) 23 | GET /builder/messages/write controllers.builder.Messages.write 24 | 25 | # Csrf 26 | GET /csrf/ controllers.csrf.Sessions.login 27 | POST /csrf/login controllers.csrf.Sessions.authenticate 28 | GET /csrf/logout controllers.csrf.Sessions.logout 29 | 30 | GET /csrf/with_token controllers.csrf.PreventingCsrfSample.formWithToken 31 | GET /csrf/without_token controllers.csrf.PreventingCsrfSample.formWithoutToken 32 | POST /csrf/ controllers.csrf.PreventingCsrfSample.submitTarget 33 | 34 | 35 | # Ephemeral 36 | GET /ephemeral/ controllers.ephemeral.Sessions.login 37 | POST /ephemeral/login controllers.ephemeral.Sessions.authenticate 38 | GET /ephemeral/logout controllers.ephemeral.Sessions.logout 39 | 40 | GET /ephemeral/messages/main controllers.ephemeral.Messages.main 41 | GET /ephemeral/messages/list controllers.ephemeral.Messages.list 42 | GET /ephemeral/messages/detail/:id controllers.ephemeral.Messages.detail(id: Int) 43 | GET /ephemeral/messages/write controllers.ephemeral.Messages.write 44 | 45 | # Stateless 46 | GET /stateless/ controllers.stateless.Sessions.login 47 | POST /stateless/login controllers.stateless.Sessions.authenticate 48 | GET /stateless/logout controllers.stateless.Sessions.logout 49 | 50 | GET /stateless/messages/main controllers.stateless.Messages.main 51 | GET /stateless/messages/list controllers.stateless.Messages.list 52 | GET /stateless/messages/detail/:id controllers.stateless.Messages.detail(id: Int) 53 | GET /stateless/messages/write controllers.stateless.Messages.write 54 | 55 | 56 | # HTTP Basic Auth 57 | GET /basic/ controllers.Default.redirect(to = "/basic/messages/main") 58 | GET /basic/messages/main controllers.basic.Messages.main 59 | GET /basic/messages/list controllers.basic.Messages.list 60 | GET /basic/messages/detail/:id controllers.basic.Messages.detail(id: Int) 61 | GET /basic/messages/write controllers.basic.Messages.write 62 | 63 | 64 | # Remember Me 65 | GET /rememberme/ controllers.rememberme.Sessions.login 66 | POST /rememberme/login controllers.rememberme.Sessions.authenticate 67 | GET /rememberme/logout controllers.rememberme.Sessions.logout 68 | 69 | GET /rememberme/messages/main controllers.rememberme.Messages.main 70 | GET /rememberme/messages/list controllers.rememberme.Messages.list 71 | GET /rememberme/messages/detail/:id controllers.rememberme.Messages.detail(id: Int) 72 | GET /rememberme/messages/write controllers.rememberme.Messages.write 73 | 74 | 75 | # Map static resources from the /public folder to the /assets URL path 76 | GET /assets/*file controllers.Assets.at(path="/public", file) 77 | -------------------------------------------------------------------------------- /play2-auth-cont-sample/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexx/action-cont/a75d2fac64f5fbfc111a686a2fea5b8aab673905/play2-auth-cont-sample/public/images/favicon.png -------------------------------------------------------------------------------- /play2-auth-cont-sample/public/javascripts/jquery-1.7.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.7.1 jquery.com | jquery.org/license */ 2 | (function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;gt |