Skip to content

Commit

Permalink
APIS-6276: added command handler and code for ChangeSandboxApplicatio… (
Browse files Browse the repository at this point in the history
#483)

* APIS-6276: added command handler and code for ChangeSandboxApplicationName command

* APIS-6276: WIP

* APIS-6276: WIP

* APIS-6276 - WIP

* APIS-6276 - WIP

* APIS-6276 - WIP

* APIS-6276: added types to commands to group them better

* APIS-6276: added more auditing to cover new events

* APIS-6276: bump events library version

---------

Co-authored-by: Andy Spaven <[email protected]>
  • Loading branch information
mattclark-zerogravit and AndySpaven authored Mar 21, 2024
1 parent 6544307 commit 8ccb0b7
Show file tree
Hide file tree
Showing 97 changed files with 2,397 additions and 217 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,13 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri
)
)

def updateDescription(applicationId: ApplicationId, description: Option[String]) =
updateApplication(applicationId, Updates.set("description", Codecs.toBson(description.filterNot(_.isBlank()))))

def updateLegacyPrivacyPolicyUrl(applicationId: ApplicationId, privacyPolicyUrl: Option[String]) = {
updateApplication(applicationId, Updates.set("access.privacyPolicyUrl", Codecs.toBson(privacyPolicyUrl.filterNot(_.isBlank()))))
}

def updateRedirectUris(applicationId: ApplicationId, redirectUris: List[RedirectUri]) =
updateApplication(applicationId, Updates.set("access.redirectUris", Codecs.toBson(redirectUris)))

Expand All @@ -863,14 +870,11 @@ class ApplicationRepository @Inject() (mongo: MongoComponent, val metrics: Metri
def updateApplicationPrivacyPolicyLocation(applicationId: ApplicationId, location: PrivacyPolicyLocation): Future[StoredApplication] =
updateApplication(applicationId, Updates.set("access.importantSubmissionData.privacyPolicyLocation", Codecs.toBson(location)))

def updateLegacyApplicationPrivacyPolicyLocation(applicationId: ApplicationId, url: String): Future[StoredApplication] =
updateApplication(applicationId, Updates.set("access.privacyPolicyUrl", url))

def updateApplicationTermsAndConditionsLocation(applicationId: ApplicationId, location: TermsAndConditionsLocation): Future[StoredApplication] =
updateApplication(applicationId, Updates.set("access.importantSubmissionData.termsAndConditionsLocation", Codecs.toBson(location)))

def updateLegacyApplicationTermsAndConditionsLocation(applicationId: ApplicationId, url: String): Future[StoredApplication] =
updateApplication(applicationId, Updates.set("access.termsAndConditionsUrl", url))
def updateLegacyTermsAndConditionsUrl(applicationId: ApplicationId, termsAndConditionsUrl: Option[String]): Future[StoredApplication] =
updateApplication(applicationId, Updates.set("access.termsAndConditionsUrl", Codecs.toBson(termsAndConditionsUrl.filterNot(_.isBlank()))))

def updateApplicationChangeResponsibleIndividual(
applicationId: ApplicationId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,21 @@ 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, ApplicationCommands, CommandFailures}
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models._
import uk.gov.hmrc.thirdpartyapplication.models.db.StoredApplication
import uk.gov.hmrc.thirdpartyapplication.repository._
import uk.gov.hmrc.thirdpartyapplication.services.commands.{AddClientSecretCommandHandler, ChangeGrantLengthCommandHandler, CommandHandler, _}
import uk.gov.hmrc.thirdpartyapplication.services.commands._
import uk.gov.hmrc.thirdpartyapplication.services.commands.clientsecret.ClientSecretCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.collaborator.CollaboratorCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.delete.DeleteCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.grantlength.GrantLengthCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.ipallowlist.IpAllowListCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.namedescription.NameDescriptionCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.policy.PolicyCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.ratelimit.RateLimitCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.redirecturi.RedirectUriCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.submission.SubmissionCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.commands.subscription.SubscriptionCommandsProcessor
import uk.gov.hmrc.thirdpartyapplication.services.notifications.NotificationService

@Singleton
Expand All @@ -37,34 +48,17 @@ class ApplicationCommandDispatcher @Inject() (
notificationService: NotificationService,
apiPlatformEventService: ApiPlatformEventService,
auditService: AuditService,
addClientSecretCmdHdlr: AddClientSecretCommandHandler,
addCollaboratorCmdHdlr: AddCollaboratorCommandHandler,
addRedirectUriCommandHandle: AddRedirectUriCommandHandler,
removeClientSecretCmdHdlr: RemoveClientSecretCommandHandler,
changeGrantLengthCmdHdlr: ChangeGrantLengthCommandHandler,
changeRateLimitTierCmdHdlr: ChangeRateLimitTierCommandHandler,
changeProductionApplicationNameCmdHdlr: ChangeProductionApplicationNameCommandHandler,
removeCollaboratorCmdHdlr: RemoveCollaboratorCommandHandler,
changeProductionApplicationPrivacyPolicyLocationCmdHdlr: ChangeProductionApplicationPrivacyPolicyLocationCommandHandler,
changeProductionApplicationTermsAndConditionsLocationCmdHdlr: ChangeProductionApplicationTermsAndConditionsLocationCommandHandler,
changeResponsibleIndividualToSelfCmdHdlr: ChangeResponsibleIndividualToSelfCommandHandler,
changeResponsibleIndividualToOtherCmdHdlr: ChangeResponsibleIndividualToOtherCommandHandler,
changeRedirectUriCmdHdlr: ChangeRedirectUriCommandHandler,
verifyResponsibleIndividualCmdHdlr: VerifyResponsibleIndividualCommandHandler,
declineResponsibleIndividualCmdHdlr: DeclineResponsibleIndividualCommandHandler,
declineResponsibleIndividualDidNotVerifyCmdHdlr: DeclineResponsibleIndividualDidNotVerifyCommandHandler,
declineApplicationApprovalRequestCmdHdlr: DeclineApplicationApprovalRequestCommandHandler,
deleteApplicationByCollaboratorCmdHdlr: DeleteApplicationByCollaboratorCommandHandler,
deleteApplicationByGatekeeperCmdHdlr: DeleteApplicationByGatekeeperCommandHandler,
deleteUnusedApplicationCmdHdlr: DeleteUnusedApplicationCommandHandler,
deleteProductionCredentialsApplicationCmdHdlr: DeleteProductionCredentialsApplicationCommandHandler,
deleteRedirectUriCmdHdlr: DeleteRedirectUriCommandHandler,
subscribeToApiCmdHdlr: SubscribeToApiCommandHandler,
unsubscribeFromApiCmdHdlr: UnsubscribeFromApiCommandHandler,
updateRedirectUrisCmdHdlr: UpdateRedirectUrisCommandHandler,
allowApplicationAutoDeleteCmdHdlr: AllowApplicationAutoDeleteCommandHandler,
blockApplicationAutoDeleteCmdHdlr: BlockApplicationAutoDeleteCommandHandler,
changeIpAllowlistCommandHandler: ChangeIpAllowlistCommandHandler
clientSecretCommandsProcessor: ClientSecretCommandsProcessor,
collaboratorCommandsProcessor: CollaboratorCommandsProcessor,
deleteCommandsProcessor: DeleteCommandsProcessor,
grantLengthCommandsProcessor: GrantLengthCommandsProcessor,
ipAllowListCommandsProcessor: IpAllowListCommandsProcessor,
nameDescriptionCommandsProcessor: NameDescriptionCommandsProcessor,
policyCommandsProcessor: PolicyCommandsProcessor,
rateLimitCommandsProcessor: RateLimitCommandsProcessor,
redirectUriCommandsProcessor: RedirectUriCommandsProcessor,
submissionsCommandsProcessor: SubmissionCommandsProcessor,
subscriptionCommandsProcessor: SubscriptionCommandsProcessor
)(implicit val ec: ExecutionContext
) extends ApplicationLogger {

Expand All @@ -76,7 +70,7 @@ class ApplicationCommandDispatcher @Inject() (
def dispatch(applicationId: ApplicationId, command: ApplicationCommand, verifiedCollaborators: Set[LaxEmailAddress])(implicit hc: HeaderCarrier): AppCmdResultT = {
for {
app <- E.fromOptionF(applicationRepository.fetch(applicationId), NonEmptyList.one(CommandFailures.ApplicationNotFound))
updateResults <- processUpdate(app, command)
updateResults <- process(app, command)
(savedApp, events) = updateResults

_ <- E.liftF(apiPlatformEventService.applyEvents(events))
Expand All @@ -86,37 +80,20 @@ class ApplicationCommandDispatcher @Inject() (
}

// scalastyle:off cyclomatic.complexity
private def processUpdate(app: StoredApplication, command: ApplicationCommand)(implicit hc: HeaderCarrier): AppCmdResultT = {
import ApplicationCommands._
private def process(app: StoredApplication, command: ApplicationCommand)(implicit hc: HeaderCarrier): AppCmdResultT = {
command match {
case cmd: AddCollaborator => addCollaboratorCmdHdlr.process(app, cmd)
case cmd: RemoveCollaborator => removeCollaboratorCmdHdlr.process(app, cmd)
case cmd: AddClientSecret => addClientSecretCmdHdlr.process(app, cmd)
case cmd: AddRedirectUri => addRedirectUriCommandHandle.process(app, cmd)
case cmd: RemoveClientSecret => removeClientSecretCmdHdlr.process(app, cmd)
case cmd: ChangeGrantLength => changeGrantLengthCmdHdlr.process(app, cmd)
case cmd: ChangeRateLimitTier => changeRateLimitTierCmdHdlr.process(app, cmd)
case cmd: ChangeProductionApplicationName => changeProductionApplicationNameCmdHdlr.process(app, cmd)
case cmd: ChangeProductionApplicationPrivacyPolicyLocation => changeProductionApplicationPrivacyPolicyLocationCmdHdlr.process(app, cmd)
case cmd: ChangeProductionApplicationTermsAndConditionsLocation => changeProductionApplicationTermsAndConditionsLocationCmdHdlr.process(app, cmd)
case cmd: ChangeRedirectUri => changeRedirectUriCmdHdlr.process(app, cmd)
case cmd: ChangeResponsibleIndividualToSelf => changeResponsibleIndividualToSelfCmdHdlr.process(app, cmd)
case cmd: ChangeResponsibleIndividualToOther => changeResponsibleIndividualToOtherCmdHdlr.process(app, cmd)
case cmd: VerifyResponsibleIndividual => verifyResponsibleIndividualCmdHdlr.process(app, cmd)
case cmd: DeclineResponsibleIndividual => declineResponsibleIndividualCmdHdlr.process(app, cmd)
case cmd: DeclineResponsibleIndividualDidNotVerify => declineResponsibleIndividualDidNotVerifyCmdHdlr.process(app, cmd)
case cmd: DeclineApplicationApprovalRequest => declineApplicationApprovalRequestCmdHdlr.process(app, cmd)
case cmd: DeleteApplicationByCollaborator => deleteApplicationByCollaboratorCmdHdlr.process(app, cmd)
case cmd: DeleteApplicationByGatekeeper => deleteApplicationByGatekeeperCmdHdlr.process(app, cmd)
case cmd: DeleteUnusedApplication => deleteUnusedApplicationCmdHdlr.process(app, cmd)
case cmd: DeleteProductionCredentialsApplication => deleteProductionCredentialsApplicationCmdHdlr.process(app, cmd)
case cmd: DeleteRedirectUri => deleteRedirectUriCmdHdlr.process(app, cmd)
case cmd: SubscribeToApi => subscribeToApiCmdHdlr.process(app, cmd)
case cmd: UnsubscribeFromApi => unsubscribeFromApiCmdHdlr.process(app, cmd)
case cmd: UpdateRedirectUris => updateRedirectUrisCmdHdlr.process(app, cmd)
case cmd: AllowApplicationAutoDelete => allowApplicationAutoDeleteCmdHdlr.process(app, cmd)
case cmd: BlockApplicationAutoDelete => blockApplicationAutoDeleteCmdHdlr.process(app, cmd)
case cmd: ChangeIpAllowlist => changeIpAllowlistCommandHandler.process(app, cmd)
case cmd: ClientSecretCommand => clientSecretCommandsProcessor.process(app, cmd)
case cmd: CollaboratorCommand => collaboratorCommandsProcessor.process(app, cmd)
case cmd: DeleteCommand => deleteCommandsProcessor.process(app, cmd)
case cmd: GrantLengthCommand => grantLengthCommandsProcessor.process(app, cmd)
case cmd: IpAllowListCommand => ipAllowListCommandsProcessor.process(app, cmd)
case cmd: NameDescriptionCommand => nameDescriptionCommandsProcessor.process(app, cmd)
case cmd: PolicyCommand => policyCommandsProcessor.process(app, cmd)
case cmd: RateLimitCommand => rateLimitCommandsProcessor.process(app, cmd)
case cmd: RedirectCommand => redirectUriCommandsProcessor.process(app, cmd)
case cmd: SubmissionCommand => submissionsCommandsProcessor.process(app, cmd)
case cmd: SubscriptionCommand => subscriptionCommandsProcessor.process(app, cmd)

}
}
// scalastyle:on cyclomatic.complexity
Expand Down
84 changes: 74 additions & 10 deletions app/uk/gov/hmrc/thirdpartyapplication/services/AuditService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,84 @@ class AuditService @Inject() (val auditConnector: AuditConnector, val submission
// scalastyle:off cyclomatic.complexity
private def applyEvent(app: StoredApplication, event: ApplicationEvent)(implicit hc: HeaderCarrier): Future[Option[AuditResult]] = {
event match {
case evt: ApplicationApprovalRequestDeclined => auditApplicationApprovalRequestDeclined(app, evt)
case evt: ClientSecretAddedV2 => auditClientSecretAdded(app, evt)
case evt: ClientSecretRemovedV2 => auditClientSecretRemoved(app, evt)
case evt: CollaboratorAddedV2 => auditAddCollaborator(app, evt)
case evt: CollaboratorRemovedV2 => auditRemoveCollaborator(app, evt)
case evt: ApplicationDeletedByGatekeeper => auditApplicationDeletedByGatekeeper(app, evt)
case evt: ApiSubscribedV2 => auditApiSubscribed(app, evt)
case evt: ApiUnsubscribedV2 => auditApiUnsubscribed(app, evt)
case evt: RedirectUrisUpdatedV2 => auditRedirectUrisUpdated(app, evt)
case _ => Future.successful(None)
case evt: ApplicationApprovalRequestDeclined => auditApplicationApprovalRequestDeclined(app, evt)
case evt: ClientSecretAddedV2 => auditClientSecretAdded(app, evt)
case evt: ClientSecretRemovedV2 => auditClientSecretRemoved(app, evt)
case evt: CollaboratorAddedV2 => auditAddCollaborator(app, evt)
case evt: CollaboratorRemovedV2 => auditRemoveCollaborator(app, evt)
case evt: ApplicationDeletedByGatekeeper => auditApplicationDeletedByGatekeeper(app, evt)
case evt: ApiSubscribedV2 => auditApiSubscribed(app, evt)
case evt: ApiUnsubscribedV2 => auditApiUnsubscribed(app, evt)
case evt: RedirectUrisUpdatedV2 => auditRedirectUrisUpdated(app, evt)
case evt: SandboxApplicationNameChanged => auditSandboxApplicationNameChangeAction(app, evt)
case evt: SandboxApplicationPrivacyPolicyUrlChanged => auditSandboxApplicationPrivacyPolicyUrlChanged(app, evt)
case evt: SandboxApplicationPrivacyPolicyUrlRemoved => auditSandboxApplicationPrivacyPolicyUrlRemoved(app, evt)
case evt: SandboxApplicationTermsAndConditionsUrlChanged => auditSandboxApplicationTermsAndConditionsUrlChanged(app, evt)
case evt: SandboxApplicationTermsAndConditionsUrlRemoved => auditSandboxApplicationTermsAndConditionsUrlRemoved(app, evt)
case _ => Future.successful(None)
}
}
// scalastyle:on cyclomatic.complexity

private def auditSandboxApplicationNameChangeAction(app: StoredApplication, evt: SandboxApplicationNameChanged)(implicit hc: HeaderCarrier): Future[Option[AuditResult]] = {
E.liftF(
audit(
AppNameChanged,
Map("applicationId" -> app.id.value.toString, "oldApplicationName" -> evt.oldName, "newApplicationName" -> evt.newName)
)
)
.toOption
.value
}

private def auditSandboxApplicationTermsAndConditionsUrlChanged(app: StoredApplication, evt: SandboxApplicationTermsAndConditionsUrlChanged)(implicit hc: HeaderCarrier)
: Future[Option[AuditResult]] = {
E.liftF(
audit(
AppTermsAndConditionsUrlChanged,
Map("applicationId" -> app.id.value.toString, "newTermsAndConditionsUrl" -> evt.termsAndConditionsUrl)
)
)
.toOption
.value
}

private def auditSandboxApplicationTermsAndConditionsUrlRemoved(app: StoredApplication, evt: SandboxApplicationTermsAndConditionsUrlRemoved)(implicit hc: HeaderCarrier)
: Future[Option[AuditResult]] = {
E.liftF(
audit(
AppTermsAndConditionsUrlChanged,
Map("applicationId" -> app.id.value.toString, "newTermsAndConditionsUrl" -> "")
)
)
.toOption
.value
}

private def auditSandboxApplicationPrivacyPolicyUrlChanged(app: StoredApplication, evt: SandboxApplicationPrivacyPolicyUrlChanged)(implicit hc: HeaderCarrier)
: Future[Option[AuditResult]] = {
E.liftF(
audit(
AppPrivacyPolicyUrlChanged,
Map("applicationId" -> app.id.value.toString, "newPrivacyPolicyUrl" -> evt.privacyPolicyUrl)
)
)
.toOption
.value
}

private def auditSandboxApplicationPrivacyPolicyUrlRemoved(app: StoredApplication, evt: SandboxApplicationPrivacyPolicyUrlRemoved)(implicit hc: HeaderCarrier)
: Future[Option[AuditResult]] = {
E.liftF(
audit(
AppPrivacyPolicyUrlChanged,
Map("applicationId" -> app.id.value.toString, "newPrivacyPolicyUrl" -> "")
)
)
.toOption
.value
}

private def auditApplicationDeletedByGatekeeper(app: StoredApplication, evt: ApplicationDeletedByGatekeeper)(implicit hc: HeaderCarrier): Future[Option[AuditResult]] = {
E.liftF(auditGatekeeperAction(evt.actor.user, app, ApplicationDeleted, Map("requestedByEmailAddress" -> evt.requestingAdminEmail.text)))
.toOption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import cats.implicits._
import uk.gov.hmrc.apiplatform.modules.common.domain.models.Actors.GatekeeperUser
import uk.gov.hmrc.apiplatform.modules.common.domain.models.{Actor, Actors, Environment, LaxEmailAddress, UserId}
import uk.gov.hmrc.apiplatform.modules.common.services.EitherTHelper
import uk.gov.hmrc.apiplatform.modules.applications.access.domain.models.{Access, AccessType}
import uk.gov.hmrc.apiplatform.modules.applications.access.domain.models.Access
import uk.gov.hmrc.apiplatform.modules.applications.core.domain.models.{Collaborator, State, StateHistory}
import uk.gov.hmrc.apiplatform.modules.applications.submissions.domain.models.ImportantSubmissionData
import uk.gov.hmrc.apiplatform.modules.commands.applications.domain.models.{CommandFailure, CommandFailures}
Expand Down Expand Up @@ -139,6 +139,12 @@ object CommandHandler extends BaseCommandHandler[(StoredApplication, NonEmptyLis
GenericFailure("App is not in TESTING state")
)

def isInSandboxEnvironment(app: StoredApplication) =
cond(
app.environment == Environment.SANDBOX.toString(),
GenericFailure("App is not in Sandbox environment")
)

def isInProduction(app: StoredApplication) =
cond(
app.isInProduction,
Expand All @@ -151,8 +157,13 @@ object CommandHandler extends BaseCommandHandler[(StoredApplication, NonEmptyLis
GenericFailure("App is not in PENDING_RESPONSIBLE_INDIVIDUAL_VERIFICATION or PENDING_GATEKEEPER_APPROVAL state")
)

def isStandardAccess(app: StoredApplication) =
cond(app.access.accessType == AccessType.STANDARD, GenericFailure("App must have a STANDARD access type"))
def ensureStandardAccess(app: StoredApplication): Validated[Failures, Access.Standard] = {
val std = app.access match {
case std: Access.Standard => Some(std)
case _ => None
}
mustBeDefined(std, GenericFailure("App must have a STANDARD access type"))
}

def isStandardNewJourneyApp(app: StoredApplication) =
cond(
Expand Down
Loading

0 comments on commit 8ccb0b7

Please sign in to comment.