1

我正在使用播放!2.4 使用 Deadbolt2 进行授权。但是,由于我引入了授权规则,我无法为我的控制器编写成功的测试。举个例子:

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

我在测试中使用 specs2。我的测试看起来像这样的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)
    }
  }
}

如何以可以指定允许用户访问的方式模拟 deadboltActions?

还有其他方法吗?也许通过提供不同的 DeadboltHandler?似乎很明显这将是要走的路,我只是似乎无法弄清楚,而且那里没有很多 Deadbolt2 示例(至少对于 scala 而言)。

或者,更极端的是,任何其他授权框架都可以很好地与 scala play 配合使用,并且允许将安全性作为一个横切关注点来处理,而不会污染控制器?由于这个原因,Deadbolt2 太有限了,但老实说我找不到更好的授权框架(除非我自己写)。

4

2 回答 2

0

它并没有完全回答我最初的问题,这主要与 Deadbolt2 相关,但我一直对我必须在控制器中指定我的授权规则这一事实感到沮丧,这并不是真正的交叉。

Steve Chaloner提供的答案有所帮助,但仍然迫使我经历了一些困难。

输入Panoptes。此授权框架基于过滤器而不是操作链,因此它允许在中央位置和控制器外部轻松指定授权规则。

设置你的安全规则Panoptes有点类似于 Spring Security,它看起来像这样:

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

除此之外,您需要几行来声明过滤器并插入您的 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])
  }
}

git 存储库中的 README 文件包含更多详细信息和代码示例。

它还可以自定义到允许创建自己的 AuthorizationRules 的程度。在我的项目中,我需要检查拨打电话的移动设备是否已在系统中注册。对于路径与我的模式匹配的每个请求,我都可以编写一个 AuthorizationRule 来为我处理这个问题。

单元测试控制器更加简单,因为可以省略对安全层的任何模拟。它们可以像任何其他类一样进行测试。

如果您遇到类似问题或认为授权规则不属于控制器,请访问Panoptes,它可能适合您的需求。希望这对其他人有帮助。

于 2016-04-20T13:48:50.683 回答
0

有许多不同的方法可以做到这一点。

如果您DeadboltHandler为访问主题注入了 DAO,则可以覆盖 DAO 的绑定以提供一个包含测试主题的对象。

abstract class AbstractControllerSpec extends PlaySpecification {
  sequential
  isolated

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

有关使用此方法的示例,请参阅测试应用程序。

或者,您可以扩展您的DeadboltHandler实现以覆盖getSubject并从此处提供测试主题。绑定的处理方式与上述相同。

最后,您可以保持所有代码原样并使用主题填充测试数据库;您发送的请求将取决于您的身份验证要求(标头、cookie 中的内容等)。

对于单元测试,类似的情况也适用。给定一个SubjectDao具有一些用于测试目的的硬编码主题,您可以使用WithApplication一个注入器查找来获得您需要的内容。

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

使用看起来像这样的控制器:

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

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

然后我们可以像这样对它进行单元测试:

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
    }
  }
}  
于 2016-04-14T09:09:41.337 回答