Skip to content

Commit

Permalink
APB-8939 - populate existing main agent (#309)
Browse files Browse the repository at this point in the history
* APB-8939 - populate existing main agent
  • Loading branch information
nubz authored Dec 18, 2024
1 parent 0cd3b5e commit 2874cca
Show file tree
Hide file tree
Showing 17 changed files with 638 additions and 91 deletions.
28 changes: 23 additions & 5 deletions app/uk/gov/hmrc/agentclientrelationships/auth/AuthActions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package uk.gov.hmrc.agentclientrelationships.auth
import play.api.Logging
import play.api.mvc._
import uk.gov.hmrc.agentclientrelationships.controllers.ErrorResults._
import uk.gov.hmrc.agentclientrelationships.model._
import uk.gov.hmrc.agentclientrelationships.model.{EnrolmentKey => LocalEnrolmentKey, _}
import uk.gov.hmrc.agentmtdidentifiers.model.{Enrolment => _, _}
import uk.gov.hmrc.auth.core.AffinityGroup.{Individual, Organisation}
import uk.gov.hmrc.auth.core.AuthProvider.{GovernmentGateway, PrivilegedApplication}
Expand Down Expand Up @@ -139,21 +139,39 @@ trait AuthActions extends AuthorisedFunctions with Logging {
}
}

// BTA & PTA Call
// Authorisation request response is a special case where we need to check for multiple services
def withAuthorisedClientForServiceKeys[A, T](serviceKeys: Seq[String])(
body: Seq[LocalEnrolmentKey] => Future[Result]
)(implicit ec: ExecutionContext, hc: HeaderCarrier): Future[Result] =
authorised(AuthProviders(GovernmentGateway) and (Individual or Organisation))
.retrieve(allEnrolments) { enrolments =>
val requiredEnrolments = for {
serviceKey <- serviceKeys
enrolment <- enrolments.getEnrolment(serviceKey)
} yield (
LocalEnrolmentKey(serviceKey, enrolment.identifiers.map(id => Identifier(id.key, id.value)))
)

requiredEnrolments match {
case s if s.isEmpty => Future.successful(NoPermissionToPerformOperation)
case _ => body(requiredEnrolments)
}
}

def withAuthorisedAsClient[A, T](
body: Map[Service, TaxIdentifier] => Future[Result]
)(implicit ec: ExecutionContext, hc: HeaderCarrier): Future[Result] =
authorised(AuthProviders(GovernmentGateway) and (Individual or Organisation))
.retrieve(allEnrolments) { enrolments =>
val identifiers: Map[Service, TaxIdentifier] = (for {
val identifiers = for {
supportedService <- supportedServices
enrolment <- enrolments.getEnrolment(supportedService.enrolmentKey)
clientId <- enrolment.identifiers.headOption
} yield (supportedService, supportedService.supportedClientIdType.createUnderlying(clientId.value))).toMap
} yield (supportedService, supportedService.supportedClientIdType.createUnderlying(clientId.value))

identifiers match {
case s if s.isEmpty => Future.successful(NoPermissionToPerformOperation)
case _ => body(identifiers)
case _ => body(identifiers.toMap)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package uk.gov.hmrc.agentclientrelationships.connectors
import play.api.http.Status.{CREATED, NOT_FOUND, OK}
import play.api.libs.json.{Json, Reads}
import uk.gov.hmrc.agentclientrelationships.config.AppConfig
import uk.gov.hmrc.agentclientrelationships.model.{ActiveRelationship, InactiveRelationship}
import uk.gov.hmrc.agentclientrelationships.model.{ActiveRelationship, InactiveRelationship, IrvRelationship}
import uk.gov.hmrc.agentclientrelationships.util.HttpAPIMonitor
import uk.gov.hmrc.agentmtdidentifiers.model.Arn
import uk.gov.hmrc.http.HttpReads.Implicits._
Expand Down Expand Up @@ -120,4 +120,24 @@ class AgentFiRelationshipConnector @Inject() (appConfig: AppConfig, http: HttpCl
}
}
}

def findRelationshipForClient(clientId: String)(implicit hc: HeaderCarrier): Future[Option[IrvRelationship]] =
monitor(s"ConsumedAPI-AgentFiRelationship-GET") {
http
.get(
url"${appConfig.agentFiRelationshipBaseUrl}/agent-fi-relationship/relationships/service/PERSONAL-INCOME-RECORD/clientId/$clientId"
)
.execute[HttpResponse]
.map { response =>
response.status match {
case OK => response.json.as[List[IrvRelationship]].headOption
case NOT_FOUND => None
case status =>
throw UpstreamErrorResponse(
s"Unexpected status $status received from AFI get active relationship for client",
status
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import play.api.libs.json.Json
import play.api.mvc.{Action, AnyContent, ControllerComponents}
import uk.gov.hmrc.agentclientrelationships.auth.AuthActions
import uk.gov.hmrc.agentclientrelationships.config.AppConfig
import uk.gov.hmrc.agentclientrelationships.model.EnrolmentKey
import uk.gov.hmrc.agentclientrelationships.model.invitationLink.InvitationLinkFailureResponse._
import uk.gov.hmrc.agentclientrelationships.model.invitationLink.{ValidateInvitationRequest, ValidateInvitationResponse}
import uk.gov.hmrc.agentclientrelationships.repository.InvitationsRepository
import uk.gov.hmrc.agentclientrelationships.services.InvitationLinkService
import uk.gov.hmrc.agentclientrelationships.services.{CheckRelationshipsService, InvitationLinkService, InvitationService}
import uk.gov.hmrc.agentmtdidentifiers.model.Service
import uk.gov.hmrc.agentmtdidentifiers.model.Service.{HMRCMTDIT, HMRCMTDITSUPP}
import uk.gov.hmrc.auth.core.AuthConnector
Expand All @@ -36,7 +36,8 @@ import scala.concurrent.{ExecutionContext, Future}
@Singleton
class InvitationLinkController @Inject() (
agentReferenceService: InvitationLinkService,
invitationsRepository: InvitationsRepository,
invitationService: InvitationService,
checkRelationshipsService: CheckRelationshipsService,
val authConnector: AuthConnector,
val appConfig: AppConfig,
cc: ControllerComponents
Expand Down Expand Up @@ -72,34 +73,49 @@ class InvitationLinkController @Inject() (
// TODO: this is a duplicate of what's used in the ClientDetailsController - we really want centralised config
private val multiAgentServices: Map[String, String] = Map(HMRCMTDIT -> HMRCMTDITSUPP)

private def servicesToSearchInvitationsFor(enrolments: Seq[EnrolmentKey], serviceKeys: Seq[String]): Set[String] = {
val suppServices =
serviceKeys.filter(multiAgentServices.contains).map(service => multiAgentServices(service))
(enrolments.map(_.service) ++ suppServices).map {
case "HMRC-NI" | "HMRC-PT" if serviceKeys.contains("HMRC-MTD-IT") => "HMRC-MTD-IT"
case "HMRC-NI" | "HMRC-PT" => "PERSONAL-INCOME-RECORD"
case serviceKey => serviceKey
}.toSet
}

def validateInvitationForClient: Action[ValidateInvitationRequest] =
Action.async(parse.json[ValidateInvitationRequest]) { implicit request =>
withAuthorisedAsClient { enrolments =>
val targetServices = request.body.serviceKeys
val targetEnrolments = enrolments.view.filterKeys(key => targetServices.contains(key.enrolmentKey)).toMap
withAuthorisedClientForServiceKeys(request.body.serviceKeys) { enrolments =>
agentReferenceService.validateInvitationRequest(request.body.uid).flatMap {
case Right(validateLinkModel) =>
val mainServices = targetEnrolments.keys.map(_.id).toSeq
val suppServices =
mainServices.filter(multiAgentServices.contains).map(service => multiAgentServices(service))
val servicesToSearch = mainServices ++ suppServices
val clientIds = targetEnrolments.values.map(_.value).toSeq
invitationsRepository.findAllForAgent(validateLinkModel.arn.value, servicesToSearch, clientIds).map {
case Seq(invitation) =>
val response = ValidateInvitationResponse(
invitation.invitationId,
invitation.service,
validateLinkModel.name,
invitation.status,
invitation.lastUpdated
)
Ok(Json.toJson(response))
case _ =>
Logger(getClass).warn(
s"Invitation was not found for UID: ${request.body.uid}, service keys: ${request.body.serviceKeys}"
)
NotFound
}
case Right(validateLinkResponse) =>
val servicesToSearch = servicesToSearchInvitationsFor(enrolments, request.body.serviceKeys)
val clientIdsToSearch = enrolments.map(e => e.oneTaxIdentifier()).map(_.value)
invitationService
.findAllForAgent(validateLinkResponse.arn.value, servicesToSearch, clientIdsToSearch)
.flatMap {
case Seq(invitation) =>
for {
existingRelationship <-
checkRelationshipsService
.findCurrentMainAgent(invitation, enrolments.find(_.service == invitation.service))
} yield Ok(
Json.toJson(
ValidateInvitationResponse(
invitation.invitationId,
invitation.service,
validateLinkResponse.name,
invitation.status,
invitation.lastUpdated,
existingMainAgent = existingRelationship
)
)
)
case _ =>
Logger(getClass).warn(
s"Invitation was not found for UID: ${request.body.uid}, service keys: ${request.body.serviceKeys}"
)
Future.successful(NotFound)
}
case Left(AgentSuspended) =>
Logger(getClass).warn(s"Agent is suspended for UID: ${request.body.uid}")
Future(Forbidden)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.agentclientrelationships.model

import play.api.libs.json.{Json, OFormat}
import uk.gov.hmrc.agentmtdidentifiers.model.Arn

case class IrvRelationship(arn: Arn)

object IrvRelationship {
implicit val format: OFormat[IrvRelationship] = Json.format[IrvRelationship]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.agentclientrelationships.model.invitationLink

import play.api.libs.json._

case class ExistingMainAgent(
agencyName: String,
sameAgent: Boolean
)

object ExistingMainAgent {
implicit val format: Format[ExistingMainAgent] = Json.format[ExistingMainAgent]
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ case class ValidateInvitationResponse(
serviceKey: String,
agentName: String,
status: InvitationStatus,
lastModifiedDate: Instant
lastModifiedDate: Instant,
existingMainAgent: Option[ExistingMainAgent]
)

object ValidateInvitationResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ class PartialAuthRepository @Inject() (mongoComponent: MongoComponent)(implicit
)
.headOption()

/* this will only find partially authorised ITSA main agents for a given nino string */
def findMainAgent(nino: String): Future[Option[PartialAuthRelationship]] =
collection
.find(
and(
equal("service", HMRCMTDIT),
equal("nino", nino)
)
)
.headOption()

def deletePartialAuth(serviceId: String, nino: Nino, arn: Arn): Future[Boolean] =
collection
.deleteOne(
Expand Down
Loading

0 comments on commit 2874cca

Please sign in to comment.