Skip to content

Commit

Permalink
APIS-4747 - Add access requirements functionality (#72)
Browse files Browse the repository at this point in the history
* APIS-4747 - Add Access Levels concept to model

* APIS-4747 - Reduce size of potential json for now

* APIS-4747 - Partial work

* APIS-4747 - Fixed formatter

* APIS-4747 - Remove commented code

* APIS-4747 - Rename fieldsDefinition to apiFieldDefintions throughout

* APIS-4747 - Renaming classes

* APIS-4747 - Rename access level requirement entities to remove the word level

* APIS-4747 - Remove unused value

* APIS-4747 - Ensure invalid requirement combinations are prevented

* APIS-4747 - Much improved data model
  • Loading branch information
AndySpaven authored Apr 24, 2020
1 parent 249c30c commit 9b42296
Show file tree
Hide file tree
Showing 31 changed files with 672 additions and 363 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import cats.data.NonEmptyList
import uk.gov.hmrc.apisubscriptionfields.FieldsDefinitionTestData
import uk.gov.hmrc.apisubscriptionfields.FieldDefinitionTestData

trait AcceptanceTestSpec extends FeatureSpec
with GivenWhenThen
with BeforeAndAfterAll
with Matchers
with GuiceOneServerPerSuite
with FieldsDefinitionTestData {
with FieldDefinitionTestData {

protected val ValidRequest = FakeRequest()
.withHeaders(RequestHeaders.ACCEPT_HMRC_JSON_HEADER)
Expand Down Expand Up @@ -74,9 +74,9 @@ trait AcceptanceTestSpec extends FeatureSpec
fakeRequestWithHeaders.withMethod(PUT).withJsonBody(Json.toJson(contents))

protected def validDefinitionPutRequest(fieldDefinitions: NonEmptyList[FieldDefinition]): FakeRequest[AnyContentAsJson] =
validDefinitionPutRequest(FieldsDefinitionRequest(fieldDefinitions))
validDefinitionPutRequest(FieldDefinitionsRequest(fieldDefinitions))

protected def validDefinitionPutRequest(contents: FieldsDefinitionRequest): FakeRequest[AnyContentAsJson] =
protected def validDefinitionPutRequest(contents: FieldDefinitionsRequest): FakeRequest[AnyContentAsJson] =
fakeRequestWithHeaders.withMethod(PUT).withJsonBody(Json.toJson(contents))

protected def fakeRequestWithHeaders: FakeRequest[AnyContentAsEmpty.type] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ import play.api.mvc.request.RequestTarget
import play.api.test.FakeRequest
import play.api.test.Helpers._
import uk.gov.hmrc.apisubscriptionfields.model._
import uk.gov.hmrc.apisubscriptionfields.{FieldsDefinitionTestData, SubscriptionFieldsTestData}
import uk.gov.hmrc.apisubscriptionfields.{FieldDefinitionTestData, SubscriptionFieldsTestData}
import scala.concurrent.Future
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.concurrent.duration._

class ApiSubscriptionFieldsHappySpec extends AcceptanceTestSpec
with OptionValues
with JsonFormatters
with SubscriptionFieldsTestData
with FieldsDefinitionTestData
with FieldDefinitionTestData
with BeforeAndAfterAll {

override def beforeAll() {
val putRequest = validDefinitionPutRequest(FieldsDefinitionRequest(FakeFieldsDefinitions))
val putRequest = validDefinitionPutRequest(FieldDefinitionsRequest(NelOfFieldDefinitions))
.withTarget( RequestTarget(uriString="", path=definitionEndpoint(fakeRawContext, fakeRawVersion), queryString = Map.empty))

Await.result(route(app, putRequest).get, 10.seconds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ import play.api.mvc._
import play.api.mvc.request.RequestTarget
import play.api.test.Helpers._
import uk.gov.hmrc.apisubscriptionfields.model._
import uk.gov.hmrc.apisubscriptionfields.{FieldsDefinitionTestData, SubscriptionFieldsTestData}
import uk.gov.hmrc.apisubscriptionfields.{FieldDefinitionTestData, SubscriptionFieldsTestData}

import scala.concurrent.Future

class SubscriptionFieldDefinitionsHappySpec extends AcceptanceTestSpec
with OptionValues
with SubscriptionFieldsTestData
with FieldsDefinitionTestData
with FieldDefinitionTestData
with JsonFormatters {


feature("Fields-Definition") {


scenario("the API is called to store some new fields definitions") {
Given("Definitiions are created ")
val putRequest = validDefinitionPutRequest(FieldsDefinitionRequest(FakeFieldsDefinitions))
val putRequest = validDefinitionPutRequest(FieldDefinitionsRequest(NelOfFieldDefinitions))
.withTarget( RequestTarget(uriString="", path=definitionEndpoint(fakeRawContext, fakeRawVersion), queryString = Map.empty))

When("a PUT request with data is sent to the API")
Expand All @@ -50,10 +50,10 @@ class SubscriptionFieldDefinitionsHappySpec extends AcceptanceTestSpec
status(putResultFuture) shouldBe CREATED

And("the response body should be a valid response")
val sfr = contentAsJson(putResultFuture).validate[FieldsDefinitionResponse]
val sfr = contentAsJson(putResultFuture).validate[ApiFieldDefinitionsResponse]

sfr.isSuccess shouldBe true
sfr.get shouldBe FieldsDefinitionResponse(fakeRawContext, fakeRawVersion, FakeFieldsDefinitions)
sfr.get shouldBe ApiFieldDefinitionsResponse(fakeRawContext, fakeRawVersion, NelOfFieldDefinitions)
}


Expand All @@ -76,10 +76,10 @@ class SubscriptionFieldDefinitionsHappySpec extends AcceptanceTestSpec
status(resultFuture) shouldBe OK

And("the response body should be a valid response")
val fdr = contentAsJson(resultFuture).validate[FieldsDefinitionResponse]
val fdr = contentAsJson(resultFuture).validate[ApiFieldDefinitionsResponse]

fdr.isSuccess shouldBe true
fdr.get shouldBe FakeFieldsDefinitionResponse
fdr.get shouldBe FakeApiFieldDefinitionsResponse
}

scenario("the API is called to GET all fields definitions") {
Expand All @@ -99,16 +99,16 @@ class SubscriptionFieldDefinitionsHappySpec extends AcceptanceTestSpec
status(resultFuture) shouldBe OK

And("the response body should be a valid response")
val allFdr = contentAsJson(resultFuture).validate[BulkFieldsDefinitionsResponse]
val allFdr = contentAsJson(resultFuture).validate[BulkApiFieldDefinitionsResponse]

allFdr.isSuccess shouldBe true
allFdr.get shouldBe BulkFieldsDefinitionsResponse(List(FakeFieldsDefinitionResponse))
allFdr.get shouldBe BulkApiFieldDefinitionsResponse(List(FakeApiFieldDefinitionsResponse))
}

scenario("the API is called to update some existing fields definitions") {

Given("a request with valid payload")
val request = validDefinitionPutRequest(FieldsDefinitionRequest(FakeFieldsDefinitions))
val request = validDefinitionPutRequest(FieldDefinitionsRequest(NelOfFieldDefinitions))
.withTarget( RequestTarget(uriString="", path=definitionEndpoint(fakeRawContext, fakeRawVersion), queryString = Map.empty))

When("a PUT request with data is sent to the API")
Expand All @@ -117,14 +117,13 @@ class SubscriptionFieldDefinitionsHappySpec extends AcceptanceTestSpec
Then(s"a response with a 200 status is received")
result shouldBe 'defined
val resultFuture = result.value

status(resultFuture) shouldBe OK

And("the response body should be a valid response")
val sfr = contentAsJson(resultFuture).validate[FieldsDefinitionResponse]
val sfr = contentAsJson(resultFuture).validate[ApiFieldDefinitionsResponse]

sfr.isSuccess shouldBe true
sfr.get shouldBe FieldsDefinitionResponse(fakeRawContext, fakeRawVersion, FakeFieldsDefinitions)
sfr.get shouldBe ApiFieldDefinitionsResponse(fakeRawContext, fakeRawVersion, NelOfFieldDefinitions)
}

scenario("the API is called to delete some existing fields definitions") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ import javax.inject.{Inject, Singleton}
import play.api.libs.json.{JsValue, Json, JsSuccess, JsError}
import play.api.mvc._
import uk.gov.hmrc.apisubscriptionfields.model._
import uk.gov.hmrc.apisubscriptionfields.service.FieldsDefinitionService

import scala.concurrent.ExecutionContext.Implicits.global
import uk.gov.hmrc.apisubscriptionfields.service.ApiFieldDefinitionsService
import scala.concurrent.Future
import scala.util.{Try,Success,Failure}
import play.api.Logger
import java.util.UUID
import scala.concurrent.ExecutionContext

@Singleton
class FieldsDefinitionController @Inject() (cc: ControllerComponents, service: FieldsDefinitionService) extends CommonController {
class ApiFieldDefinitionsController @Inject() (cc: ControllerComponents, service: ApiFieldDefinitionsService)(implicit ec: ExecutionContext) extends CommonController {

import JsonFormatters._

Expand All @@ -44,7 +44,7 @@ class FieldsDefinitionController @Inject() (cc: ControllerComponents, service: F
NotFound(JsErrorResponse(ErrorCode.NOT_FOUND_CODE, s"Fields definition not found for (${apiContext.value}, ${apiVersion.value})"))

def validateFieldsDefinition(): Action[JsValue] = Action(parse.json) { request =>
Try(request.body.validate[FieldsDefinitionRequest]) match {
Try(request.body.validate[FieldDefinitionsRequest]) match {
case Success(JsSuccess(payload, _)) => Ok("")
case Success(JsError(errs)) => {
badRequestWithTag( (tag:UUID) => s"A JSON error occurred: [${tag.toString}] ${Json.prettyPrint(JsError.toJson(errs))}")
Expand All @@ -56,7 +56,7 @@ class FieldsDefinitionController @Inject() (cc: ControllerComponents, service: F
}

def upsertFieldsDefinition(apiContext: ApiContext, apiVersion: ApiVersion): Action[JsValue] = Action.async(parse.json) { implicit request =>
withJsonBody[FieldsDefinitionRequest] { payload =>
withJsonBody[FieldDefinitionsRequest] { payload =>
service.upsert(apiContext, apiVersion, payload.fieldDefinitions) map {
case (response, true) => Created(Json.toJson(response))
case (response, false) => Ok(Json.toJson(response))
Expand All @@ -80,7 +80,7 @@ class FieldsDefinitionController @Inject() (cc: ControllerComponents, service: F
} recover recovery
}

private def asActionResult(eventualMaybeResponse: Future[Option[FieldsDefinitionResponse]], apiContext: ApiContext, apiVersion: ApiVersion) = {
private def asActionResult(eventualMaybeResponse: Future[Option[ApiFieldDefinitionsResponse]], apiContext: ApiContext, apiVersion: ApiVersion) = {
eventualMaybeResponse map {
case Some(subscriptionFields) => Ok(Json.toJson(subscriptionFields))
case None => notFoundResponse(apiContext, apiVersion)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2020 HM Revenue & Customs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package uk.gov.hmrc.apisubscriptionfields.model

sealed trait DevhubAccessRequirement
object DevhubAccessRequirement {
final val Default: DevhubAccessRequirement = Anyone

case object NoOne extends DevhubAccessRequirement
case object AdminOnly extends DevhubAccessRequirement
case object Anyone extends DevhubAccessRequirement
}

case class DevhubAccessRequirements private (
val read: DevhubAccessRequirement,
val write: DevhubAccessRequirement) {
def satisfiesRead(dal: DevhubAccessLevel): Boolean = dal.satisfiesRequirement(read) // ReadWrite will be at least as strict.
def satisfiesWrite(dal: DevhubAccessLevel): Boolean = dal.satisfiesRequirement(write)
}


object DevhubAccessRequirements {
import DevhubAccessRequirement._

final val Default = new DevhubAccessRequirements(DevhubAccessRequirement.Default, DevhubAccessRequirement.Default)

// Do not allow greater restrictions on read than on write
// - it would make no sense to allow NoOne read but everyone write or developer write and admin read
//
def apply(read: DevhubAccessRequirement, write: DevhubAccessRequirement = DevhubAccessRequirement.Default): DevhubAccessRequirements = (read,write) match {
case (NoOne, _) => new DevhubAccessRequirements(NoOne, NoOne)
case (AdminOnly, Anyone) => new DevhubAccessRequirements(AdminOnly,AdminOnly)
case _ => new DevhubAccessRequirements(read,write)
}
}

case class AccessRequirements(devhub: DevhubAccessRequirements)
object AccessRequirements {
final val Default = AccessRequirements(devhub = DevhubAccessRequirements.Default)
}


sealed trait DevhubAccessLevel {
def satisfiesRequirement(requirement: DevhubAccessRequirement): Boolean = DevhubAccessLevel.satisfies(requirement)(this)
}
object DevhubAccessLevel {
case object Developer extends DevhubAccessLevel
case object Admininstator extends DevhubAccessLevel

import DevhubAccessRequirement._
def satisfies(requirement: DevhubAccessRequirement)(actual: DevhubAccessLevel): Boolean = (requirement, actual) match {
case (NoOne, _) => false
case (AdminOnly, Developer) => false
case _ => true
}
}
Loading

0 comments on commit 9b42296

Please sign in to comment.