Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Siyavash Habashi committed Nov 4, 2016
1 parent c63191e commit 4564c75
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target

/project/target
/project/project
14 changes: 14 additions & 0 deletions build.sbt
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"
)
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=0.13.8
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logLevel := Level.Warn
61 changes: 61 additions & 0 deletions src/main/scala/net/habashi/BasicAuthenticationFilter.scala
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 src/test/scala/net/habashi/BasicAuthenticationFilterTest.scala
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
}
}
}

}

0 comments on commit 4564c75

Please sign in to comment.