-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Siyavash Habashi
committed
Nov 4, 2016
1 parent
c63191e
commit 4564c75
Showing
6 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/target | ||
|
||
/project/target | ||
/project/project |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
name := "basicAuthenticator" | ||
|
||
organization := "net.habashi" | ||
|
||
version := "1.0.0" | ||
|
||
scalaVersion := "2.11.7" | ||
|
||
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" | ||
|
||
libraryDependencies ++= Seq( | ||
"com.typesafe.play" %% "play" % "2.5.6", | ||
"org.scalatestplus.play" %% "scalatestplus-play" % "1.5.0" % "test" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version=0.13.8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
logLevel := Level.Warn |
61 changes: 61 additions & 0 deletions
61
src/main/scala/net/habashi/BasicAuthenticationFilter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package net.habashi | ||
|
||
import akka.stream.Materializer | ||
import play.api.mvc.{Filter, RequestHeader, Result, Results} | ||
import sun.misc.BASE64Decoder | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
/** | ||
* Enables BasicAuthentication implemented as Filter for applications built with Play!. | ||
* | ||
* @param username to validate | ||
* @param password to validate | ||
* @param mat implicit materializer | ||
* @param ec used executionContext | ||
*/ | ||
class BasicAuthenticationFilter(val username: String, val password: String) | ||
(implicit val mat: Materializer, ec: ExecutionContext) extends Filter { | ||
|
||
private object Constants { | ||
lazy val AuthorizationHeaderName = "authorization" | ||
lazy val BasicAuthenticationIdentifier = "Basic " | ||
} | ||
|
||
private lazy val unauthorizedResult = Future.successful { | ||
Results.Unauthorized.withHeaders(("WWW-Authenticate", """Basic realm="BasicAuthentication"""")) | ||
} | ||
|
||
override def apply(nextFilter: (RequestHeader) => Future[Result])(requestHeader: RequestHeader): Future[Result] = { | ||
requestHeader.headers.get(Constants.AuthorizationHeaderName) match { | ||
case Some(authorizationBody) => | ||
if (authorizationBody.startsWith(Constants.BasicAuthenticationIdentifier)) { | ||
val basicAuthenticationBody = authorizationBody.replace(Constants.BasicAuthenticationIdentifier, "") | ||
val decodedPayload = decodeBase64(basicAuthenticationBody) | ||
|
||
if (validateUsernamePassword(decodedPayload)) { | ||
nextFilter(requestHeader) | ||
} else { | ||
unauthorizedResult | ||
} | ||
} else { | ||
unauthorizedResult | ||
} | ||
case None => unauthorizedResult | ||
} | ||
} | ||
|
||
private def decodeBase64(string: String): String = { | ||
val decodedByteArray = new BASE64Decoder().decodeBuffer(string) | ||
new String(decodedByteArray, "UTF-8") | ||
} | ||
|
||
private def validateUsernamePassword(payload: String): Boolean = { | ||
val usernamePassword = payload.split(":") | ||
|
||
usernamePassword.length == 2 && | ||
usernamePassword(0) == username && | ||
usernamePassword(1) == password | ||
} | ||
|
||
} |
100 changes: 100 additions & 0 deletions
100
src/test/scala/net/habashi/BasicAuthenticationFilterTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package net.habashi | ||
|
||
import akka.stream.Materializer | ||
import org.scalatest.concurrent.ScalaFutures | ||
import org.scalatest.{Matchers, Outcome, fixture} | ||
import org.scalatestplus.play.OneAppPerSuite | ||
import play.api.mvc.{RequestHeader, Result, Results} | ||
import play.api.test.FakeRequest | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
class BasicAuthenticationFilterTest extends fixture.FlatSpec with OneAppPerSuite with Matchers with ScalaFutures { | ||
|
||
implicit lazy val im: ExecutionContext = scala.concurrent.ExecutionContext.global | ||
|
||
implicit lazy val materializer: Materializer = app.materializer | ||
|
||
private val authenticatedResult = Results.Ok | ||
|
||
private val unauthenticatedResult: Result = Results.Unauthorized.withHeaders(("WWW-Authenticate", """Basic realm="BasicAuthentication"""")) | ||
|
||
private val nextFilter = (requestHeader: RequestHeader) => Future.successful(authenticatedResult) | ||
|
||
case class FixtureParam(basicAuthenticationFilter: BasicAuthenticationFilter) | ||
|
||
override protected def withFixture(test: OneArgTest): Outcome = { | ||
val username: String = "Clark Kent" | ||
val password: String = "Pikachu" | ||
|
||
val basicAuthenticationFilter = new BasicAuthenticationFilter(username, password) | ||
val fixtureParam = FixtureParam(basicAuthenticationFilter) | ||
|
||
super.withFixture(test.toNoArgTest(fixtureParam)) | ||
} | ||
|
||
"A request without header" must "result in 401 Unauthorized" in { | ||
fixParam => { | ||
// given | ||
val requestHeader = FakeRequest() | ||
|
||
// when | ||
val result = fixParam.basicAuthenticationFilter.apply(nextFilter)(requestHeader) | ||
|
||
// then | ||
ScalaFutures.whenReady(result) { | ||
r => | ||
r shouldBe unauthenticatedResult | ||
} | ||
} | ||
} | ||
|
||
"A request without an Authorization-Header starting with the Basic-Identifier" must "result in 401 Unauthorized" in { | ||
fixParam => { | ||
// given | ||
val requestHeader = FakeRequest().withHeaders(("authorization", "Some Clark Kent:Pikachu")) | ||
|
||
// when | ||
val result = fixParam.basicAuthenticationFilter.apply(nextFilter)(requestHeader) | ||
|
||
// then | ||
ScalaFutures.whenReady(result) { | ||
r => | ||
r shouldBe unauthenticatedResult | ||
} | ||
} | ||
} | ||
|
||
"A request not authenticated with a Base64 decoded string" must "result in 401 Unauthorized" in { | ||
fixParam => { | ||
// given | ||
val requestHeader = FakeRequest().withHeaders(("authorization", "Basic Clark Kent:Pikachu")) | ||
|
||
// when | ||
val result = fixParam.basicAuthenticationFilter.apply(nextFilter)(requestHeader) | ||
|
||
// then | ||
ScalaFutures.whenReady(result) { | ||
r => | ||
r shouldBe unauthenticatedResult | ||
} | ||
} | ||
} | ||
|
||
"A well authenticated request" must "proceed with the nextFilters" in { | ||
fixParam => { | ||
// given | ||
val requestHeader = FakeRequest().withHeaders(("authorization", "Basic Q2xhcmsgS2VudDpQaWthY2h1")) | ||
|
||
// when | ||
val result = fixParam.basicAuthenticationFilter.apply(nextFilter)(requestHeader) | ||
|
||
// then | ||
ScalaFutures.whenReady(result) { | ||
r => | ||
r shouldBe authenticatedResult | ||
} | ||
} | ||
} | ||
|
||
} |