2016-04-13 6 views
1

Ich benutze Play! 2.4 mit Deadbolt2 zur Autorisierung. Seit ich die Autorisierungsregeln eingeführt habe, kann ich keine erfolgreichen Tests für meine Controller schreiben. Als Beispiel:Wie testen Controller mit Deadbolt2 DeadboltActions oder gibt es ein anderes Framework, das dies leicht erlaubt?

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)) 
    ) 
    } 
    } 
} 

Ich verwende specs2 in den Tests. Mein Test sieht wie folgt aus 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) 
    } 
    } 
} 

Wie verspotten ich deadboltActions in einer Weise, die ich den Benutzer angeben kann, wird der Zugriff erlaubt?

Gibt es einen anderen Weg? Vielleicht mit einem anderen DeadboltHandler? Es scheint offensichtlich zu sein, dass dies der richtige Weg wäre. Ich bin einfach nicht in der Lage, es herauszufinden und es gibt nicht viele Deadbolt2-Beispiele (zumindest für Scala).

Oder, als extremer, alle anderen Autorisierungsrahmen da draußen, die gut mit Scala spielen und ermöglicht, Sicherheit als Querschnittsaufgabe zu behandeln, ohne die Controller zu stören? Deadbolt2 ist aus diesem Grund zu begrenzt, aber ehrlich gesagt kann ich keinen besseren Berechtigungsrahmen finden (außer ich schreibe meinen eigenen).

Antwort

0

Es antwortet nicht genau auf meine ursprüngliche Frage, was hauptsächlich mit Deadbolt2 zusammenhing, aber ich war immer frustriert darüber, dass ich meine Autorisierungsregeln in meinen Controllern angeben musste, was nicht wirklich übergreifend ist.

Die Antwort von Steve Chaloner hilft, aber zwang mich immer noch, ein paar Reifen zu durchlaufen.

Geben Sie Panoptes ein.Dieses Autorisierungsframework basiert auf Filtern anstelle von Aktionsketten, so dass Autorisierungsregeln an einem zentralen Ort und außerhalb der Controller leicht festgelegt werden können.

in Panoptes Ihre Sicherheitsregeln Einstellung ist etwas ähnlich wie Spring Security und es sieht wie folgt aus:

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, müssen Sie ein paar Zeilen um den Filter zu erklären und in Ihrem AuthorizationHandler stecken.

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]) 
    } 
} 

Die README-Datei im Git-Repository enthält weitere Details und Codebeispiele.

Es ist auch anpassbar an den Punkt erlaubt es, Ihre eigenen AuthorizationRules zu erstellen. In meinem Projekt habe ich eine Anforderung, wo ich das mobile Gerät überprüfen muss, dass der Anruf im System registriert ist. Ich kann eine AuthorizationRule schreiben, um das für mich für jede Anfrage zu behandeln, deren Pfad meinem Muster entspricht.

Unit-Test-Controller ist besonders einfach, weil jede Verspottung der Sicherheitsschicht weggelassen werden kann. Sie können wie jede andere Klasse getestet werden.

Wenn Sie ähnliche Probleme haben oder glauben, dass Autorisierungsregeln nicht zu den Controllern gehören, versuchen Sie es unter Panoptes, damit es Ihren Anforderungen entspricht. Hoffe das hilft jemand anderem.

+1

Dieser Ansatz ist jetzt in Deadbolt ab 2.5.1-SNAPSHOT verfügbar, inspiriert von Ihrer Antwort und mit Blick auf Panoptes. https://deadbolt-scala.readme.io/v2.5.1/docs/authorized-routes –

+0

Hey sieht gut aus. Da die Deadlines bei meinem Kunden eng sind und ich schon die Arbeit an der Erstellung von Panoptes gemacht habe, bleibe ich für den Moment dran, aber es ist toll zu sehen, dass Deadbolt diese Funktionen ebenfalls zur Verfügung hat. Übrigens erwähnen die Docs es nicht, aber es ist auch möglich, benutzerdefinierte Autorisierungsregeln zu schreiben, indem man ein Merkmal implementiert, nicht wahr? Prost – redwulf

+0

Absolut - Sie können nur 'FilterFunction' implementieren. Ich wusste nicht, dass du Panoptes geschrieben hast - nette Arbeit! –

0

Es gibt verschiedene Möglichkeiten, dies zu tun.

Wenn Ihre DeadboltHandler ein DAO für den Zugriff auf das Thema injiziert hat, können Sie die Bindung des DAO außer Kraft setzen, um eines bereitzustellen, das Testobjekte enthält.

abstract class AbstractControllerSpec extends PlaySpecification { 
    sequential 
    isolated 

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

Siehe the test app für ein Beispiel, diesen Ansatz zu verwenden.

Alternativ können Sie Ihre DeadboltHandler Implementierung auf getSubject überschreiben und eine Testperson von hier bereitstellen. Die Bindung wird auf die gleiche Weise wie oben behandelt.

Schließlich können Sie Ihren gesamten Code unverändert lassen und die Testdatenbank mit Themen füllen; Die von Ihnen gesendeten Anfragen werden von Ihren Authentifizierungsanforderungen (Header, etwas in einem Cookie usw.) geprägt.

Für Unit-Tests gilt etwas Ähnliches. Mit einem , der einige hartcodierte Themen für Testzwecke hat, können Sie WithApplication und ein Injektor-Lookup verwenden, um zu bekommen, was Sie brauchen.

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) 
} 

Mit einem Controller, der etwa wie folgt aussieht:

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

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

Wir können dann Unit-Test es wie folgt aus:

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 
    } 
    } 
} 
+0

Ich habe mir nur die Beispiele angeschaut, aber ich versuche nicht, den gesamten Stack zu testen, nur den Controller, seine Eingaben und erwarteten Ausgaben und dafür muss ich die Dienste meines Controllers verspotten, wie in meinem Beispiel. Das bedeutet, dass DeadboltActions ebenfalls gespottet werden muss, oder alternativ muss ich den gesamten Kontext irgendwie erstellen und eine gültige vollständige Instanz von DeadboltActions einbringen. Mein Problem ist, einen Weg zu finden, dies zu tun, also wäre jede Hilfe dort großartig. Oder fehlt mir etwas? – redwulf

+0

@redwulf Ich habe bearbeitet, um ein vollständiges Beispiel für Komponententests hinzuzufügen. –

+0

WithApplication-Konstruktor übernimmt ab Play 2.5.11 keine Argumente. –

Verwandte Themen