Skip to content

Commit

Permalink
Api-8024: added handling for UnsubscribeFromRetiredApi Command (#531)
Browse files Browse the repository at this point in the history
* API-8020 - Align events use of ApplicationName

* API-8024 - WIP

* API-8024: WIP

* API-8024: WIP2

* API-8024: fixed broken tests and bumped library

* API-8024: fixed broken tests and bumped library

* API-8024: added handling for UnsubscribeFromRetiredApi command

* API-8024: added logging

* API-8024: bump auto build plugin version

---------

Co-authored-by: Andy Spaven <[email protected]>
  • Loading branch information
mattclark-zerogravit and AndySpaven authored Dec 5, 2024
1 parent 6915e83 commit 6f6d2a7
Show file tree
Hide file tree
Showing 35 changed files with 578 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import uk.gov.hmrc.mongo.MongoComponent
import uk.gov.hmrc.mongo.play.json.{Codecs, PlayMongoRepository}

import uk.gov.hmrc.apiplatform.modules.common.domain.models.ApplicationId
import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.ApplicationName
import uk.gov.hmrc.apiplatform.modules.applications.submissions.domain.models.{ResponsibleIndividual, SubmissionId}
import uk.gov.hmrc.apiplatform.modules.approvals.domain.models.ResponsibleIndividualVerificationState.ResponsibleIndividualVerificationState
import uk.gov.hmrc.apiplatform.modules.approvals.domain.models.{
Expand Down Expand Up @@ -179,7 +178,7 @@ class ResponsibleIndividualVerificationRepository @Inject() (mongo: MongoCompone
evt.applicationId,
SubmissionId(evt.submissionId.value),
evt.submissionIndex,
ApplicationName(evt.applicationName),
evt.applicationName,
evt.eventDateTime,
ResponsibleIndividual.build(evt.responsibleIndividualName, evt.responsibleIndividualEmail.text),
evt.requestingAdminName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,21 @@ package uk.gov.hmrc.thirdpartyapplication.controllers

import javax.inject.{Inject, Singleton}
import scala.concurrent.ExecutionContext
import scala.concurrent.Future.successful

import play.api.libs.json.{Json, OFormat, Reads}
import play.api.libs.json.{Json, OFormat}
import play.api.mvc._

import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApplicationId, LaxEmailAddress}
import uk.gov.hmrc.apiplatform.modules.common.domain.models.ApplicationId
import uk.gov.hmrc.apiplatform.modules.common.services.{ApplicationLogger, EitherTHelper}
import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.ApplicationWithCollaborators
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.{ApplicationCommand, CommandFailures}
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.{ApplicationCommand, CommandFailures, DispatchRequest}
import uk.gov.hmrc.apiplatform.modules.events.applications.domain.models.ApplicationEvent
import uk.gov.hmrc.thirdpartyapplication.models.JsonFormatters._
import uk.gov.hmrc.thirdpartyapplication.models.db.StoredApplication
import uk.gov.hmrc.thirdpartyapplication.services._
import uk.gov.hmrc.thirdpartyapplication.services.commands.CommandHandler

object ApplicationCommandController {
case class DispatchRequest(command: ApplicationCommand, verifiedCollaboratorsToNotify: Set[LaxEmailAddress])

object DispatchRequest {

val readsDispatchRequest: Reads[DispatchRequest] = Json.reads[DispatchRequest]
val readsCommandAsDispatchRequest: Reads[DispatchRequest] = ApplicationCommand.formatter.map(cmd => DispatchRequest(cmd, Set.empty))

implicit val readsEitherAsDispatchRequest: Reads[DispatchRequest] = readsDispatchRequest orElse readsCommandAsDispatchRequest
}

case class DispatchResult(applicationResponse: ApplicationWithCollaborators, events: List[ApplicationEvent])

object DispatchResult {
Expand All @@ -55,8 +44,7 @@ object ApplicationCommandController {

@Singleton
class ApplicationCommandController @Inject() (
val applicationCommandDispatcher: ApplicationCommandDispatcher,
val applicationCommandAuthenticator: ApplicationCommandAuthenticator,
val applicationCommandService: ApplicationCommandService,
val applicationService: ApplicationService,
cc: ControllerComponents
)(implicit val ec: ExecutionContext
Expand All @@ -70,11 +58,21 @@ class ApplicationCommandController @Inject() (
val E = EitherTHelper.make[CommandHandler.Failures]

private def fails(applicationId: ApplicationId, cmd: ApplicationCommand)(e: CommandHandler.Failures) = {

val details = e.toList.map(CommandFailures.describe)

logger.warn(s"Command Process ${cmd.getClass.getSimpleName} failed for $applicationId because ${details.mkString("[", ",", "]")}")
BadRequest(Json.toJson(e.toList))

val hasAuthErrors = e.filter(failure =>
failure match {
case e: CommandFailures.InsufficientPrivileges => true
case _ => false
}
)

if (hasAuthErrors.isEmpty) {
BadRequest(Json.toJson(e.toList))
} else {
Unauthorized("Authentication needed for this command")
}
}

def update(applicationId: ApplicationId) = Action.async(parse.json) { implicit request =>
Expand All @@ -83,8 +81,10 @@ class ApplicationCommandController @Inject() (
}

withJsonBody[ApplicationCommand] { command =>
applicationCommandDispatcher.dispatch(applicationId, command, Set.empty) // Eventually we want to migrate everything to use the /dispatch endpoint with email list
.fold(fails(applicationId, command), passes(_))
applicationCommandService.authenticateAndDispatch(applicationId, command, Set.empty).fold(
fails(applicationId, command),
passes(_)
)
}
}

Expand All @@ -94,16 +94,10 @@ class ApplicationCommandController @Inject() (
Ok(Json.toJson(output))
}

lazy val unAuth = Unauthorized("Authentication needed for this command")

withJsonBody[DispatchRequest] { dispatchRequest =>
applicationCommandAuthenticator.authenticateCommand(dispatchRequest.command).flatMap(isAuthorised =>
if (isAuthorised) {
applicationCommandDispatcher.dispatch(applicationId, dispatchRequest.command, dispatchRequest.verifiedCollaboratorsToNotify).fold(
fails(applicationId, dispatchRequest.command),
passes(_)
)
} else successful(unAuth)
applicationCommandService.authenticateAndDispatch(applicationId, dispatchRequest.command, dispatchRequest.verifiedCollaboratorsToNotify).fold(
fails(applicationId, dispatchRequest.command),
passes(_)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2023 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.thirdpartyapplication.controllers

import java.time.{Clock, Instant}
import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}

import play.api.mvc._
import uk.gov.hmrc.http.HeaderCarrier
import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController

import uk.gov.hmrc.apiplatform.modules.common.domain.models._
import uk.gov.hmrc.apiplatform.modules.common.services.ClockNow
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.ApplicationCommands.UnsubscribeFromApi
import uk.gov.hmrc.thirdpartyapplication.repository.{ApplicationRepository, SubscriptionRepository}
import uk.gov.hmrc.thirdpartyapplication.services.ApplicationCommandDispatcher

@Singleton
class PublisherController @Inject() (
subscriptionRepository: SubscriptionRepository,
applicationRepository: ApplicationRepository,
applicationCommandDispatcher: ApplicationCommandDispatcher,
cc: ControllerComponents,
val clock: Clock
)(implicit val ec: ExecutionContext
) extends BackendController(cc) with JsonUtils with ClockNow {

// What do we want to do if unsubscribing goes wrong??

def deleteSubscribers(context: ApiContext, version: ApiVersionNbr): Action[AnyContent] = Action.async { implicit request =>
// can check user-agent header to check originator and check secret key, Unauth response if failed.
val ts: Instant = instant()
val apiIdentifier = ApiIdentifier(context, version)
val command = UnsubscribeFromApi(Actors.Process("Publisher"), apiIdentifier, ts)

def deleteSubscriptionFor(applicationId: ApplicationId)(implicit hc: HeaderCarrier): Future[Unit] = {
def logAndReturnUnit(msg: String) = {
logger.info(s"deleteSubscribers $msg for app $applicationId for Api context: $context Api Version: $version")
()
}

applicationCommandDispatcher.dispatch(applicationId, command, Set.empty)
.fold(_ => logAndReturnUnit("command failed"), _ => logAndReturnUnit("command succeeded"))
}

(for {
subscribers <- subscriptionRepository.getSubscribers(ApiIdentifier(context, version))
xs <- Future.sequence(subscribers.toList.map(id => deleteSubscriptionFor(id)))
} yield xs)
.map(_ => Ok)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ApplicationCommandAuthenticator @Inject() (
if (authControlConfig.enabled) {
cmd match {
case gkcmd: ApplicationCommand with GatekeeperMixin => isStrideAuthorised(gkcmd)
// TODO case something Process Mixin
case _ => successful(true)
}
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2024 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.thirdpartyapplication.services

import javax.inject.{Inject, Singleton}
import scala.concurrent.{ExecutionContext, Future}

import cats.data.{EitherT, NonEmptyList}

import uk.gov.hmrc.http.HeaderCarrier

import uk.gov.hmrc.apiplatform.modules.common.domain.models.{ApplicationId, LaxEmailAddress}
import uk.gov.hmrc.apiplatform.modules.common.services.{ApplicationLogger, EitherTHelper}
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.{ApplicationCommand, CommandFailures}
import uk.gov.hmrc.thirdpartyapplication.services.commands.CommandHandler

@Singleton
class ApplicationCommandService @Inject() (
val applicationCommandDispatcher: ApplicationCommandDispatcher,
val applicationCommandAuthenticator: ApplicationCommandAuthenticator
)(implicit ec: ExecutionContext
) extends ApplicationLogger {

import CommandHandler._

val E = EitherTHelper.make[Failures]

def authenticateAndDispatch(applicationId: ApplicationId, command: ApplicationCommand, collaboratorsToNotify: Set[LaxEmailAddress])(implicit hc: HeaderCarrier)
: EitherT[Future, Failures, Success] = {
(for {
isAuthorised <- E.liftF(applicationCommandAuthenticator.authenticateCommand(command))
_ <- E.cond(
isAuthorised,
(),
NonEmptyList.one(CommandFailures.InsufficientPrivileges("Not authenticated"))
)
dispatchResult <- applicationCommandDispatcher.dispatch(applicationId, command, collaboratorsToNotify)
} yield dispatchResult)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class AuditService @Inject() (val auditConnector: AuditConnector, val submission
E.liftF(
audit(
AppNameChanged,
Map("applicationId" -> app.id.value.toString, "oldApplicationName" -> evt.oldName, "newApplicationName" -> evt.newName)
Map("applicationId" -> app.id.value.toString, "oldApplicationName" -> evt.oldName.value, "newApplicationName" -> evt.newName.value)
)
)
.toOption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import cats.data._
import cats.implicits._

import uk.gov.hmrc.apiplatform.modules.common.domain.models.Actors
import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.ApplicationName
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.ApplicationCommands.ChangeProductionApplicationName
import uk.gov.hmrc.apiplatform.modules.events.applications.domain.models._
import uk.gov.hmrc.apiplatform.modules.uplift.services.UpliftNamingService
Expand Down Expand Up @@ -63,8 +64,8 @@ class ChangeProductionApplicationNameCommandHandler @Inject() (
applicationId = app.id,
eventDateTime = cmd.timestamp,
actor = Actors.GatekeeperUser(cmd.gatekeeperUser),
oldAppName = app.name.value,
newAppName = cmd.newName.value,
oldAppName = app.name,
newAppName = ApplicationName(cmd.newName.value),
requestingAdminEmail = getRequester(app, cmd.instigator)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import cats._
import cats.data._
import cats.implicits._

import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.ApplicationName
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.ApplicationCommands.ChangeSandboxApplicationName
import uk.gov.hmrc.apiplatform.modules.events.applications.domain.models._
import uk.gov.hmrc.apiplatform.modules.uplift.services.UpliftNamingService
Expand Down Expand Up @@ -63,8 +64,8 @@ class ChangeSandboxApplicationNameCommandHandler @Inject() (
applicationId = app.id,
eventDateTime = cmd.timestamp,
actor = cmd.actor,
oldName = app.name.value,
newName = cmd.newName.value
oldName = app.name,
newName = ApplicationName(cmd.newName.value)
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class SubmitApplicationApprovalRequestCommandHandler @Inject() (
applicationId = app.id,
eventDateTime = cmd.timestamp,
actor = cmd.actor,
applicationName = app.name.value,
applicationName = app.name,
requestingAdminName = cmd.requesterName,
requestingAdminEmail = cmd.requesterEmail,
responsibleIndividualName = importantSubmissionData.responsibleIndividual.fullName.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class SubmitTermsOfUseApprovalCommandHandler @Inject() (
applicationId = app.id,
eventDateTime = cmd.timestamp,
actor = cmd.actor,
applicationName = app.name.value,
applicationName = app.name,
requestingAdminName = cmd.requesterName,
requestingAdminEmail = cmd.requesterEmail,
responsibleIndividualName = importantSubmissionData.responsibleIndividual.fullName.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class VerifyResponsibleIndividualCommandHandler @Inject() (
applicationId = app.id,
eventDateTime = cmd.timestamp,
actor = Actors.AppCollaborator(requesterEmail),
app.name.value,
app.name,
cmd.requesterName,
requestingAdminEmail = getRequester(app, cmd.instigator),
responsibleIndividualName = cmd.riName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ import uk.gov.hmrc.thirdpartyapplication.services.commands.CommandHandler
@Singleton
class SubscriptionCommandsProcessor @Inject() (
subscribeToApiCommandHandler: SubscribeToApiCommandHandler,
unsubscribeFromApiCommandHandler: UnsubscribeFromApiCommandHandler
unsubscribeFromApiCommandHandler: UnsubscribeFromApiCommandHandler,
unsubscribeFromRetiredApiCommandHandler: UnsubscribeFromRetiredApiCommandHandler
) {
import CommandHandler._
import ApplicationCommands._

def process(app: StoredApplication, command: SubscriptionCommand)(implicit hc: HeaderCarrier): AppCmdResultT = command match {
case cmd: SubscribeToApi => subscribeToApiCommandHandler.process(app, cmd)
case cmd: UnsubscribeFromApi => unsubscribeFromApiCommandHandler.process(app, cmd)
case cmd: SubscribeToApi => subscribeToApiCommandHandler.process(app, cmd)
case cmd: UnsubscribeFromApi => unsubscribeFromApiCommandHandler.process(app, cmd)
case cmd: UnsubscribeFromRetiredApi => unsubscribeFromRetiredApiCommandHandler.process(app, cmd)
}
}
Loading

0 comments on commit 6f6d2a7

Please sign in to comment.