我使用Play!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。我的测试看起来像这个自动取款机:
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太有限了,但老实说,我找不到一个更好的授权框架(除非我自己编写)。
发布于 2016-04-20 13:48:50
它没有完全回答我最初的问题,这个问题主要与Deadbolt2有关,但我一直对必须在控制器中指定授权规则感到沮丧,这并不是真正的横切。
史蒂夫·查洛纳提供的答案是有帮助的,但仍然迫使我经历了一些困难。
输入潘诺普特。该授权框架基于过滤器而不是操作链,因此它允许在中央位置和控制器外部轻松地指定授权规则。
在Panoptes中设置您的安全规则有点类似于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存储库中的自述文件包含更多细节和代码示例。
它还可以定制到允许创建您自己的AuthorizationRules的程度。在我的项目中,我需要检查发出呼叫的移动设备是否已在系统中注册。我可以为每个路径与我的模式匹配的请求编写一个AuthorizationRule来处理这个问题。
单元测试控制器非常简单,因为任何对安全层的模拟都可以避免。它们可以像其他任何类一样被测试。
如果您有类似的问题,或者也认为授权规则不属于控制器,那么可以使用潘诺普特,它可能适合您的需要。希望这能帮到别人。
发布于 2016-04-14 09:09:41
有许多不同的方法可以做到这一点。
如果您的DeadboltHandler中注入了用于访问主题的DAO,则可以重写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
}
}
} https://stackoverflow.com/questions/36603480
复制相似问题