|
Play Framework/Scala example source code file (CSRFCommonSpecs.scala)
The CSRFCommonSpecs.scala Play Framework example source code
/*
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
package play.filters.csrf
import org.specs2.mutable.Specification
import play.api.libs.ws._
import scala.concurrent.Future
import play.api.mvc.{Handler, Session}
import play.api.libs.Crypto
import play.api.test.{FakeApplication, TestServer, PlaySpecification}
import play.api.http.{ContentTypes, ContentTypeOf, Writeable}
import org.specs2.matcher.MatchResult
/**
* Specs for functionality that each CSRF filter/action shares in common
*/
trait CSRFCommonSpecs extends Specification with PlaySpecification {
import CSRFConf._
// This extracts the tests out into different configurations
def sharedTests(csrfCheckRequest: CsrfTester, csrfAddToken: CsrfTester, generate: => String,
addToken: (WSRequestHolder, String) => WSRequestHolder,
getToken: WSResponse => Option[String], compareTokens: (String, String) => MatchResult[Any],
errorStatusCode: Int) = {
// accept/reject tokens
"accept requests with token in query string" in {
lazy val token = generate
csrfCheckRequest(req => addToken(req.withQueryString(TokenName -> token), token)
.post(Map("foo" -> "bar"))
)(_.status must_== OK)
}
"accept requests with token in form body" in {
lazy val token = generate
csrfCheckRequest(req => addToken(req, token)
.post(Map("foo" -> "bar", TokenName -> token))
)(_.status must_== OK)
}
/* TODO: write multipart/form-data Writable
"accept requests with a session token and token in multipart body" in {
lazy val token = generate
makeRequest(_.withSession(TokenName -> token)
.post(Map("foo" -> "bar", TokenName -> token))
).status must_== OK
}
*/
"accept requests with token in header" in {
lazy val token = generate
csrfCheckRequest(req => addToken(req, token)
.withHeaders(HeaderName -> token)
.post(Map("foo" -> "bar"))
)(_.status must_== OK)
}
"accept requests with nocheck header" in {
csrfCheckRequest(_.withHeaders(HeaderName -> HeaderNoCheck)
.post(Map("foo" -> "bar"))
)(_.status must_== OK)
}
"accept requests with ajax header" in {
csrfCheckRequest(_.withHeaders("X-Requested-With" -> "a spoon")
.post(Map("foo" -> "bar"))
)(_.status must_== OK)
}
"reject requests with different token in body" in {
csrfCheckRequest(req => addToken(req, generate)
.post(Map("foo" -> "bar", TokenName -> generate))
)(_.status must_== errorStatusCode)
}
"reject requests with token in session but none elsewhere" in {
csrfCheckRequest(req => addToken(req, generate)
.post(Map("foo" -> "bar"))
)(_.status must_== errorStatusCode)
}
"reject requests with token in body but not in session" in {
csrfCheckRequest(
_.post(Map("foo" -> "bar", TokenName -> generate))
)(_.status must_== errorStatusCode)
}
// add to response
"add a token if none is found" in {
csrfAddToken(_.get()) { response =>
val token = response.body
token must not be empty
val rspToken = getToken(response)
rspToken must beSome.like {
case s => compareTokens(token, s)
}
}
}
"not set the token if already set" in {
lazy val token = generate
Thread.sleep(2)
csrfAddToken(req => addToken(req, token).get()) { response =>
getToken(response) must beNone
compareTokens(token, response.body)
// Ensure that nothing was updated
response.cookies must beEmpty
}
}
}
"a CSRF filter" should {
"work with signed session tokens" in {
def csrfCheckRequest = buildCsrfCheckRequest(false)
def csrfAddToken = buildCsrfAddToken()
def generate = Crypto.generateSignedToken
def addToken(req: WSRequestHolder, token: String) = req.withSession(TokenName -> token)
def getToken(response: WSResponse) = {
val session = response.cookies.find(_.name.exists(_ == Session.COOKIE_NAME)).flatMap(_.value).map(Session.decode)
session.flatMap(_.get(TokenName))
}
def compareTokens(a: String, b: String) = Crypto.compareSignedTokens(a, b) must beTrue
sharedTests(csrfCheckRequest, csrfAddToken, generate, addToken, getToken, compareTokens, FORBIDDEN)
"reject requests with unsigned token in body" in {
csrfCheckRequest(req => addToken(req, generate)
.post(Map("foo" -> "bar", TokenName -> "foo"))
)(_.status must_== FORBIDDEN)
}
"reject requests with unsigned token in session" in {
csrfCheckRequest(req => addToken(req, "foo")
.post(Map("foo" -> "bar", TokenName -> generate))
)(_.status must_== FORBIDDEN)
}
"return a different token on each request" in {
lazy val token = generate
Thread.sleep(2)
csrfAddToken(req => addToken(req, token).get()) { response =>
// it shouldn't be equal, to protect against BREACH vulnerability
response.body must_!= token
Crypto.compareSignedTokens(token, response.body) must beTrue
}
}
}
"work with unsigned session tokens" in {
def csrfCheckRequest = buildCsrfCheckRequest(false, "csrf.sign.tokens" -> "false")
def csrfAddToken = buildCsrfAddToken("csrf.sign.tokens" -> "false")
def generate = Crypto.generateToken
def addToken(req: WSRequestHolder, token: String) = req.withSession(TokenName -> token)
def getToken(response: WSResponse) = {
val session = response.cookies.find(_.name.exists(_ == Session.COOKIE_NAME)).flatMap(_.value).map(Session.decode)
session.flatMap(_.get(TokenName))
}
def compareTokens(a: String, b: String) = a must_== b
sharedTests(csrfCheckRequest, csrfAddToken, generate, addToken, getToken, compareTokens, FORBIDDEN)
}
"work with signed cookie tokens" in {
def csrfCheckRequest = buildCsrfCheckRequest(false, "csrf.cookie.name" -> "csrf")
def csrfAddToken = buildCsrfAddToken("csrf.cookie.name" -> "csrf")
def generate = Crypto.generateSignedToken
def addToken(req: WSRequestHolder, token: String) = req.withCookies("csrf" -> token)
def getToken(response: WSResponse) = response.cookies.find(_.name.exists(_ == "csrf")).flatMap(_.value)
def compareTokens(a: String, b: String) = Crypto.compareSignedTokens(a, b) must beTrue
sharedTests(csrfCheckRequest, csrfAddToken, generate, addToken, getToken, compareTokens, FORBIDDEN)
}
"work with unsigned cookie tokens" in {
def csrfCheckRequest = buildCsrfCheckRequest(false, "csrf.cookie.name" -> "csrf", "csrf.sign.tokens" -> "false")
def csrfAddToken = buildCsrfAddToken("csrf.cookie.name" -> "csrf", "csrf.sign.tokens" -> "false")
def generate = Crypto.generateToken
def addToken(req: WSRequestHolder, token: String) = req.withCookies("csrf" -> token)
def getToken(response: WSResponse) = response.cookies.find(_.name.exists(_ == "csrf")).flatMap(_.value)
def compareTokens(a: String, b: String) = a must_== b
sharedTests(csrfCheckRequest, csrfAddToken, generate, addToken, getToken, compareTokens, FORBIDDEN)
}
"work with secure cookie tokens" in {
def csrfCheckRequest = buildCsrfCheckRequest(false, "csrf.cookie.name" -> "csrf", "csrf.cookie.secure" -> "true")
def csrfAddToken = buildCsrfAddToken("csrf.cookie.name" -> "csrf", "csrf.cookie.secure" -> "true")
def generate = Crypto.generateSignedToken
def addToken(req: WSRequestHolder, token: String) = req.withCookies("csrf" -> token)
def getToken(response: WSResponse) = {
response.cookies.find(_.name.exists(_ == "csrf")).flatMap { cookie =>
cookie.secure must beTrue
cookie.value
}
}
def compareTokens(a: String, b: String) = Crypto.compareSignedTokens(a, b) must beTrue
sharedTests(csrfCheckRequest, csrfAddToken, generate, addToken, getToken, compareTokens, FORBIDDEN)
}
"work with checking failed result" in {
def csrfCheckRequest = buildCsrfCheckRequest(true, "csrf.cookie.name" -> "csrf")
def csrfAddToken = buildCsrfAddToken("csrf.cookie.name" -> "csrf")
def generate = Crypto.generateSignedToken
def addToken(req: WSRequestHolder, token: String) = req.withCookies("csrf" -> token)
def getToken(response: WSResponse) = response.cookies.find(_.name.exists(_ == "csrf")).flatMap(_.value)
def compareTokens(a: String, b: String) = Crypto.compareSignedTokens(a, b) must beTrue
sharedTests(csrfCheckRequest, csrfAddToken, generate, addToken, getToken, compareTokens, UNAUTHORIZED)
}
}
trait CsrfTester {
def apply[T](makeRequest: WSRequestHolder => Future[WSResponse])(handleResponse: WSResponse => T): T
}
/**
* Set up a request that will go through the CSRF action. The action must return 200 OK if successful.
*/
def buildCsrfCheckRequest(sendUnauthorizedResult: Boolean, configuration: (String, String)*): CsrfTester
/**
* Make a request that will have a token generated and added to the request and response if not present. The request
* must return the generated token in the body, accessed as if a template had accessed it.
*/
def buildCsrfAddToken(configuration: (String, String)*): CsrfTester
implicit class EnrichedRequestHolder(request: WSRequestHolder) {
def withSession(session: (String, String)*): WSRequestHolder = {
withCookies(Session.COOKIE_NAME -> Session.encode(session.toMap))
}
def withCookies(cookies: (String, String)*): WSRequestHolder = {
request.withHeaders(COOKIE -> cookies.map(c => c._1 + "=" + c._2).mkString(", "))
}
}
implicit def simpleFormWriteable: Writeable[Map[String, String]] = Writeable.writeableOf_urlEncodedForm.map[Map[String, String]](_.mapValues(v => Seq(v)))
implicit def simpleFormContentType: ContentTypeOf[Map[String, String]] = ContentTypeOf[Map[String, String]](Some(ContentTypes.FORM))
def withServer[T](config: Seq[(String, String)])(router: PartialFunction[(String, String), Handler])(block: => T) = running(TestServer(testServerPort, FakeApplication(
additionalConfiguration = Map(config:_*) ++ Map("application.secret" -> "foobar"),
withRoutes = router
)))(block)
}
Other Play Framework source code examplesHere is a short list of links related to this Play Framework CSRFCommonSpecs.scala source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.