From 2b2a831a64016a71bfa2f25e362786a0058f973f Mon Sep 17 00:00:00 2001 From: "@diksha-diksha" <176032121+diksha-diksha@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:49:13 +0100 Subject: [PATCH] Audit for Authorisation failure --- app/v1/controllers/AuthorisedController.scala | 35 +++++++++++++++---- .../controllers/CustomerInfoController.scala | 25 ++++++++++--- .../controllers/SubmitReturnController.scala | 2 +- app/v1/services/AuditService.scala | 2 +- .../AuthorisedControllerSpec.scala | 6 ++++ 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/app/v1/controllers/AuthorisedController.scala b/app/v1/controllers/AuthorisedController.scala index 52459fbd..2312795d 100644 --- a/app/v1/controllers/AuthorisedController.scala +++ b/app/v1/controllers/AuthorisedController.scala @@ -17,25 +17,29 @@ package v1.controllers import play.api.libs.json.Json +import play.api.libs.typedmap.TypedKey import play.api.mvc._ import uk.gov.hmrc.auth.core.Enrolment import uk.gov.hmrc.auth.core.authorise.Predicate import uk.gov.hmrc.http.HeaderCarrier import uk.gov.hmrc.play.bootstrap.backend.controller.BackendController +import utils.IdGenerator import v1.controllers.requestParsers.validators.validations.VrnValidation import v1.models.auth.UserDetails import v1.models.errors.{DownstreamError, ForbiddenDownstreamError, LegacyUnauthorisedError, VrnFormatError} -import v1.services.EnrolmentsAuthService +import v1.services.{AuditService, EnrolmentsAuthService} import scala.concurrent.{ExecutionContext, Future} case class UserRequest[A](userDetails: UserDetails, request: Request[A]) extends WrappedRequest[A](request) abstract class AuthorisedController(cc: ControllerComponents)(implicit ec: ExecutionContext) extends BackendController(cc) { +object AuditFailureHandler { val AttrKey: TypedKey[(String, RequestHeader, Result) => Unit] = TypedKey("auditFailureHandler")} val authService: EnrolmentsAuthService + val idGenerator: IdGenerator - def authorisedAction(vrn: String, nrsRequired: Boolean = false): ActionBuilder[UserRequest, AnyContent] = new ActionBuilder[UserRequest, AnyContent] { + def authorisedAction(vrn: String, nrsRequired: Boolean = false, auditOnFailure: Boolean = false): ActionBuilder[UserRequest, AnyContent] = new ActionBuilder[UserRequest, AnyContent] { override def parser: BodyParser[AnyContent] = cc.parsers.defaultBodyParser @@ -51,16 +55,35 @@ abstract class AuthorisedController(cc: ControllerComponents)(implicit ec: Execu implicit val headerCarrier: HeaderCarrier = hc(request) val clientId = request.headers.get("X-Client-Id").getOrElse("N/A") + val correlationId: String = idGenerator.getUid + + def auditFailure(result: Result): Unit = + if (auditOnFailure) { + println("checking for audit failure") + request.attrs + .get(AuditFailureHandler.AttrKey) + .foreach{handler => + println(s"checking for handler failure") + handler(correlationId, request, result)} + } if (VrnValidation.validate(vrn) == Nil) { authService.authorised(predicate(vrn), nrsRequired)(headerCarrier,executionContext, request).flatMap[Result] { case Right(userDetails) => block(UserRequest(userDetails.copy(clientId = clientId), request)) - case Left(LegacyUnauthorisedError) => Future.successful(Forbidden(Json.toJson(LegacyUnauthorisedError))) - case Left(ForbiddenDownstreamError) => Future.successful(Forbidden(Json.toJson(DownstreamError))) - case Left(_) => Future.successful(InternalServerError(Json.toJson(DownstreamError))) + case Left(LegacyUnauthorisedError) => val result = Forbidden(Json.toJson(LegacyUnauthorisedError)) + auditFailure(result) + Future.successful(result) + case Left(ForbiddenDownstreamError) => val result = Forbidden(Json.toJson(DownstreamError)) + auditFailure(result) + Future.successful(result) + case Left(_) => val result = InternalServerError(Json.toJson(DownstreamError)) + auditFailure(result) + Future.successful(result) } } else { - Future.successful(BadRequest(Json.toJson(VrnFormatError))) + val result = BadRequest(Json.toJson(VrnFormatError)) + auditFailure(result) + Future.successful(result) } } } diff --git a/app/v1/controllers/CustomerInfoController.scala b/app/v1/controllers/CustomerInfoController.scala index ff980283..c62623eb 100644 --- a/app/v1/controllers/CustomerInfoController.scala +++ b/app/v1/controllers/CustomerInfoController.scala @@ -19,11 +19,14 @@ package v1.controllers import cats.data.EitherT import cats.implicits._ import play.api.libs.json.Json -import play.api.mvc.{Action, AnyContent, ControllerComponents, Result} +import play.api.mvc.{Action, AnyContent, ControllerComponents, Request, RequestHeader, Result} +import uk.gov.hmrc.http.HeaderCarrier +import uk.gov.hmrc.play.http.HeaderCarrierConverter import utils.{EndpointLogContext, IdGenerator, Logging} import v1.audit.AuditEvents import v1.controllers.requestParsers.CustomerInfoRequestParser -import v1.models.audit.AuditResponse +import v1.models.audit.{AuditError, AuditResponse} +import v1.models.auth.UserDetails import v1.models.errors._ import v1.models.request.information.CustomerRawData import v1.models.response.information.{CustomerDetails, CustomerInfoResponse, FlatRateScheme} @@ -48,9 +51,22 @@ extends AuthorisedController(cc) with BaseController with Logging { endpointName = "retrieveCustomerInfo" ) - def retrieveCustomerInfo(vrn: String): Action[AnyContent] = - authorisedAction(vrn).async { implicit request => + def retrieveCustomerInfo(vrn: String): Action[AnyContent] = { + val handler: (String, RequestHeader, Result) => Unit = {(correlationId, request, result) => + implicit val hc: HeaderCarrier = HeaderCarrierConverter.fromRequestAndSession(request, request.session) + println(s"[Audit DEBUG] inside custom audit = $correlationId, status=$result.header.status}") + auditService.auditEvent(AuditEvents.auditCustomerInfo( + correlationId, + UserDetails("unknown", None, "unknown", None), // or better if accessible + AuditResponse(result.header.status, Left(Seq(AuditError("UNAUTHORISED_OR_INVALID")))) + )) + } + authorisedAction(vrn, + auditOnFailure = true + ).async { implicit request => + val updatedRequest = request.addAttr(AuditFailureHandler.AttrKey, handler) + implicit val reqWithAttr: Request[AnyContent] = updatedRequest implicit val correlationId: String = idGenerator.getUid infoLog(s"[${endpointLogContext.controllerName}][${endpointLogContext.endpointName}] " + s"Retrieve Customer Info for VRN : $vrn with correlationId : $correlationId") @@ -91,6 +107,7 @@ extends AuthorisedController(cc) with BaseController with Logging { leftResult }.merge } + } private def errorResult(errorWrapper: ErrorWrapper): Result = { (errorWrapper.error: @unchecked) match { diff --git a/app/v1/controllers/SubmitReturnController.scala b/app/v1/controllers/SubmitReturnController.scala index 1b9e5d67..939df440 100644 --- a/app/v1/controllers/SubmitReturnController.scala +++ b/app/v1/controllers/SubmitReturnController.scala @@ -43,7 +43,7 @@ class SubmitReturnController @Inject()(val authService: EnrolmentsAuthService, auditService: AuditService, cc: ControllerComponents, dateTime: CurrentDateTime, - idGenerator: IdGenerator, + val idGenerator: IdGenerator, override val metrics: MetricRegistry) (implicit ec: ExecutionContext) extends AuthorisedController(cc) with BaseController with Timer with Logging { diff --git a/app/v1/services/AuditService.scala b/app/v1/services/AuditService.scala index 19747272..73bc5594 100644 --- a/app/v1/services/AuditService.scala +++ b/app/v1/services/AuditService.scala @@ -45,7 +45,7 @@ class AuditService @Inject()(auditConnector: AuditConnector, detail = Json.toJson(event.detail), tags = eventTags ) - + logger.info(s"[Audit DEBUG] Sending event: type = ${event.auditType}, detail = ${Json.prettyPrint(Json.toJson(event.detail))}") auditConnector.sendExtendedEvent(dataEvent) } } \ No newline at end of file diff --git a/test/v1/controllers/AuthorisedControllerSpec.scala b/test/v1/controllers/AuthorisedControllerSpec.scala index 8ad24c08..3cb05c03 100644 --- a/test/v1/controllers/AuthorisedControllerSpec.scala +++ b/test/v1/controllers/AuthorisedControllerSpec.scala @@ -20,6 +20,7 @@ import play.api.libs.json.{JsValue, Json} import play.api.mvc._ import uk.gov.hmrc.auth.core.Enrolment import uk.gov.hmrc.http.HeaderCarrier +import utils.IdGenerator import v1.mocks.services.MockEnrolmentsAuthService import v1.models.errors.{DownstreamError, ForbiddenDownstreamError, LegacyUnauthorisedError, MtdError} import v1.services.EnrolmentsAuthService @@ -34,8 +35,13 @@ class AuthorisedControllerSpec extends ControllerBaseSpec { val hc: HeaderCarrier = HeaderCarrier() val authorisedController: TestController = new TestController() + val mockIdGenerator: IdGenerator = new IdGenerator { + override def getUid: String = "test-correlation-id" + } + class TestController extends AuthorisedController(cc) { override val authService: EnrolmentsAuthService = mockEnrolmentsAuthService + override val idGenerator: IdGenerator = mockIdGenerator def action(vrn: String): Action[AnyContent] = authorisedAction(vrn).async { Future.successful(Ok(Json.obj()))