1

I'm using Play! 2.4 with Deadbolt2 for authorization. However, since I introduced the authorization rules, I'm unable to write successful tests for my controllers. As an example:

class VisitController @Inject() (authorization: DeadboltActions) extends Controller {
  def fetchDailyVisits(date: Date) = authorization.Restrict(List(Array(ADMIN_ROLE), Array(MANAGER_ROLE))) {
    Action.async {
      visitService.findDailyVisits(date).map(result =>
        Ok(Json.toJson(result))
      )
    }
  }
}

I'm using specs2 in the tests. My test looks like this atm:

class VisitControllerSpec extends PlaySpecification with Mockito with ScalaFutures {
  val deadboltActions = mock[DeadboltActions]
"VisitControllerSpec#fetchDailyVisits" should {

    val testDate = Date.from(LocalDate.of(2016, 2, 25)
      .atStartOfDay(ZoneId.systemDefault()).toInstant)

    "Return Status Ok with returned list" in {

      val expected = List(completeVisitWithId, anotherCompleteVisitWithId)
      visitService.findDailyVisits(testDate) returns Future { expected }

      val request = FakeRequest(GET, "/visits?date=2016-02-25")

      val result = new VisitController(deadboltActions)
        .fetchDailyVisits(testDate)(request)

      result.futureValue.header.status must beEqualTo(OK)
      contentAsJson(result) must_== Json.toJson(expected)
    }
  }
}

How do I mock deadboltActions in a way I can specify the user will be allowed access?

Is there another way? Maybe by providing a different DeadboltHandler? It seems kind of obvious this would be the way to go, I just don't seem to be able to figure it out and there aren't a lot of Deadbolt2 examples out there (at least for scala).

Or, being more extreme, any other authorization framework out there that works well with scala play and allows to handle security as a cross-cutting concern without poluting the controllers? Deadbolt2 is too limited for this reason, but I honestly can't find a better authorization framework (unless I write my own).

redwulf
  • 1,317
  • 3
  • 13
  • 35

2 Answers2

0

There are a number of different ways you can do this.

If your DeadboltHandler has a DAO injected for accessing the subject, you can override the binding of the DAO to provide one containing test subjects.

abstract class AbstractControllerSpec extends PlaySpecification {
  sequential
  isolated

  def testApp: Application =  new  GuiceApplicationBuilder().in(Mode.Test).bindings(bind[SubjectDao].to[TestSubjectDao]).build()
}

See the test app for an example of using this approach.

Alternatively, you can extend your DeadboltHandler implementation to override getSubject and provide a test subject from here. The binding is handled in the same way as above.

Finally, you can keep all your code as-is and populate the test database with subjects; the requests you send will be shaped by your authentication requirements (headers, something in a cookie, etc).

For unit testing, something similar applies. Given a SubjectDao that has some hard-coded subjects for test purposes, you can use WithApplication and an injector look-up to get what you need.

class TestSubjectDao extends SubjectDao {

  val subjects: Map[String, Subject] = Map("greet" -> new SecuritySubject("greet",
                                                                           List(SecurityRole("foo"),
                                                                                 SecurityRole("bar")),
                                                                           List(SecurityPermission("killer.undead.zombie"))),
                                            "lotte" -> new SecuritySubject("lotte",
                                                                            List(SecurityRole("hurdy")),
                                                                            List(SecurityPermission("killer.undead.vampire"))),
                                            "steve" -> new SecuritySubject("steve",
                                                                            List(SecurityRole("bar")),
                                                                            List(SecurityPermission("curator.museum.insects"))),
                                            "mani" -> new SecuritySubject("mani",
                                                                           List(SecurityRole("bar"),
                                                                                 SecurityRole("hurdy")),
                                                                           List(SecurityPermission("zombie.movie.enthusiast"))),
                                            "trippel" -> new SecuritySubject("trippel",
                                                                           List(SecurityRole("foo"),
                                                                                 SecurityRole("hurdy")),
                                                                           List[SecurityPermission]()))

  override def user(userName: String): Option[Subject] = subjects.get(userName)
}

With a controller that looks something like this:

class Subject @Inject()(deadbolt: DeadboltActions) extends Controller {

  def subjectMustBePresent = deadbolt.SubjectPresent()() { authRequest =>
    Future {
      Ok("Content accessible")
    }
  }
}

We can then unit test it like this:

import be.objectify.deadbolt.scala.DeadboltActions
import be.objectify.deadbolt.scala.test.controllers.composed.Subject
import be.objectify.deadbolt.scala.test.dao.{SubjectDao, TestSubjectDao}
import play.api.Mode
import play.api.inject._
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.mvc.{Result, Results}
import play.api.test.{FakeRequest, PlaySpecification, WithApplication}

import scala.concurrent.Future

object SubjectPresentUnitSpec extends PlaySpecification with Results {
  "Subject present " should {
    "should result in a 401 when no subject is present" in new WithApplication(new GuiceApplicationBuilder().in(Mode.Test).bindings(bind[SubjectDao].to[TestSubjectDao]).build()) {
      val deadbolt: DeadboltActions = implicitApp.injector.instanceOf[DeadboltActions]
      val controller = new Subject(deadbolt)
      val result: Future[Result] = call(controller.subjectMustBePresent(), FakeRequest())
      val statusCode: Int = status(result)
      statusCode must be equalTo 401
    }

    "should result in a 200 when a subject is present" in new WithApplication(new GuiceApplicationBuilder().in(Mode.Test).bindings(bind[SubjectDao].to[TestSubjectDao]).build()) {
      val deadbolt: DeadboltActions = implicitApp.injector.instanceOf[DeadboltActions]
      val controller = new Subject(deadbolt)
      val result: Future[Result] = call(controller.subjectMustBePresent(), FakeRequest().withHeaders(("x-deadbolt-test-user", "greet")))
      val statusCode: Int = status(result)
      statusCode must be equalTo 200
    }
  }
}  
Steve Chaloner
  • 8,162
  • 1
  • 22
  • 38
  • Just had a look at the examples, but I'm not trying to test the entire stack, just the controller, its inputs and expected outputs and for that, I need to mock the services my controller uses, as shown in my example. That means DeadboltActions needs to be mocked as well or, alternatively, I need to create the entire context somehow and inject a valid full instance of DeadboltActions. My problem is figuring out a way to do that, so any help there would be great. Or am I missing something? – redwulf Apr 14 '16 at 10:47
  • @redwulf I've edited to add a full example for unit testing. – Steve Chaloner Apr 14 '16 at 11:39
  • WithApplication constructor takes no arguments as of Play 2.5.11. – Vikas Tikoo Feb 06 '17 at 18:35
0

It doesn't answer exactly to my original question, which was mostly related with Deadbolt2, but I kept getting frustrated with the fact I had to specify my authorization rules in my controllers, which is not truly cross cutting.

The answer provided by Steve Chaloner helps, but still forced me to go through a few hoops.

Enter Panoptes. This authorization framework is based on Filters instead of Action chaining, so it allows to easily specify authorization rules in a central location and outside of the controllers.

Setting your security rules in Panoptes is somewhat similar to Spring Security and it looks like this:

class BasicAuthHandler extends AuthorizationHandler {

  override def config: Set[(Pattern, _ <: AuthorizationRule)] = {
    Set(
      Pattern(Some(POST), "/products") -> atLeastOne(withRole("Admin"), withRole("Manager"))
      Pattern(Some(GET), "/cart[/A-Za-z0-9]*") -> withRole("Admin"),
      Pattern(None, "/orders[/A-Za-z0-9]*") -> withRole("Admin")
    )
  }
}

Other than that, you need a couple of lines to declare the filter and plug in your AuthorizationHandler.

class Filters @Inject()(securityFilter: SecurityFilter) extends HttpFilters {
  override def filters = Seq(securityFilter)
}

class ControllerProviderModule extends AbstractModule {
  override def configure(): Unit = {   bind(classOf[AuthorizationHandler]).to(classOf[MyAuthorizationHandler])
  }
}

The README file in the git repository has more details and code samples.

It's also customizable to the point it allows to create your own AuthorizationRules. In my project I have a requirement where I need to check the mobile device that makes the call is registered in the system. I can write an AuthorizationRule to handle this for me for every request whose path matches my pattern.

Unit testing controllers is extra simple, because any mocking of the security layer can be ommited. They can be tested like any other class.

If you're having similar issues or also believe authorization rules don't belong in the controllers, have a go at Panoptes, it might suit your needs. Hope this helps someone else.

Community
  • 1
  • 1
redwulf
  • 1,317
  • 3
  • 13
  • 35
  • 1
    This approach is now available in Deadbolt as of 2.5.1-SNAPSHOT, inspired by your answer and looking at Panoptes. https://deadbolt-scala.readme.io/v2.5.1/docs/authorized-routes – Steve Chaloner Apr 29 '16 at 07:19
  • Hey looks great. Since deadlines at my client are tight and I already did the work of creating Panoptes, I'll stick with it for now, but it's great to see Deadbolt has these features available too. By the way, the docs don't mention it, but it's also possible to write custom authorization rules by implementing a trait, right? Cheers – redwulf Apr 29 '16 at 08:37
  • Absolutely - you can just implement `FilterFunction`. I didn't realise you wrote Panoptes - nice work! – Steve Chaloner Apr 29 '16 at 08:40