diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnector.scala b/app/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnector.scala index eaec6a6..211d30a 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnector.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnector.scala @@ -16,9 +16,10 @@ package uk.gov.hmrc.openidconnect.userinfo.connectors +import play.api.Logger import uk.gov.hmrc.domain.Nino import uk.gov.hmrc.openidconnect.userinfo.config.WSHttp -import uk.gov.hmrc.openidconnect.userinfo.domain.NinoNotFoundException +import uk.gov.hmrc.openidconnect.userinfo.domain.Enrolment import uk.gov.hmrc.play.auth.microservice.connectors.ConfidenceLevel._ import uk.gov.hmrc.play.config.ServicesConfig import uk.gov.hmrc.play.http.{HeaderCarrier, HttpGet} @@ -31,18 +32,53 @@ trait AuthConnector extends uk.gov.hmrc.play.auth.microservice.connectors.AuthCo val http: HttpGet def confidenceLevel()(implicit hc: HeaderCarrier): Future[Option[Int]] = { - http.GET(s"$authBaseUrl/auth/authority") map { resp => - (resp.json \ "confidenceLevel").asOpt[Int] - } recover { - case e: Throwable => None + http.GET(s"$authBaseUrl/auth/authority") map { resp => + (resp.json \ "confidenceLevel").asOpt[Int] + } recover { + case e: Throwable => { + Logger.error(e.getMessage, e) + None } + } } - def fetchNino()(implicit hc:HeaderCarrier): Future[Nino] = { + def fetchNino()(implicit hc:HeaderCarrier): Future[Option[Nino]] = { http.GET(s"$authBaseUrl/auth/authority") map { - resp => (resp.json \ "nino").asOpt[Nino] match { - case Some(n) => n - case None => throw NinoNotFoundException() + resp => (resp.json \ "nino").asOpt[Nino] + } recover { + case e: Throwable => { + Logger.error("Nino not present.", e) + None + } + } + } + + def fetchEnrolments()(implicit headerCarrier: HeaderCarrier): Future[Option[Seq[Enrolment]]] = { + getEnrolmentsUri flatMap { + case Some(enrolmentsUri) => { + http.GET(s"$authBaseUrl$enrolmentsUri") map { response => + response.json.asOpt[Seq[Enrolment]] + } recover { + case e: Throwable => { + Logger.error(e.getMessage, e) + None + } + } + } + case None => { + Logger.debug("No enrolment uri.") + Future.successful(None) + } + } + } + + private def getEnrolmentsUri()(implicit hc: HeaderCarrier): Future[Option[String]] = { + http.GET(s"$authBaseUrl/auth/authority").map { response => + (response.json \ "enrolments").asOpt[String] + } recover { + case e: Throwable => { + Logger.error(e.getMessage, e) + None } } } diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnector.scala b/app/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnector.scala index 75abe14..211cae4 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnector.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnector.scala @@ -17,14 +17,14 @@ package uk.gov.hmrc.openidconnect.userinfo.connectors import play.api.Logger -import uk.gov.hmrc.openidconnect.userinfo.config.{WSHttp, AppContext} +import uk.gov.hmrc.domain.Nino +import uk.gov.hmrc.openidconnect.userinfo.config.{AppContext, WSHttp} import uk.gov.hmrc.openidconnect.userinfo.domain.DesUserInfo import uk.gov.hmrc.play.config.ServicesConfig import uk.gov.hmrc.play.http._ import uk.gov.hmrc.play.http.logging.Authorization import scala.concurrent.Future - import scala.concurrent.ExecutionContext.Implicits.global trait DesConnector { @@ -36,16 +36,20 @@ trait DesConnector { val desEnvironment: String val desBearerToken: String - def fetchUserInfo(nino: String)(implicit hc: HeaderCarrier): Future[Option[DesUserInfo]] = { + def fetchUserInfo(nino: Option[Nino])(implicit hc: HeaderCarrier): Future[Option[DesUserInfo]] = { val newHc = hc.copy(authorization = Some(Authorization(s"Bearer $desBearerToken"))).withExtraHeaders("Environment" -> desEnvironment) - val url = s"$serviceUrl/pay-as-you-earn/individuals/${withoutSuffix(nino)}" - Logger.debug(s"GET $url with environment=$desEnvironment") + nino map { ninoValue => + val url = s"$serviceUrl/pay-as-you-earn/individuals/${withoutSuffix(ninoValue.nino)}" + Logger.debug(s"GET $url with environment=$desEnvironment") - http.GET[DesUserInfo](url)(implicitly[HttpReads[DesUserInfo]], newHc) map (Some(_)) recover { - case _: NotFoundException | _: BadRequestException => - Logger.debug(s"User information for nino $nino is not available in DES") - None + http.GET[DesUserInfo](url)(implicitly[HttpReads[DesUserInfo]], newHc) map (Some(_)) recover { + case _: NotFoundException | _: BadRequestException => + Logger.debug(s"User information for nino $nino is not available in DES") + None + } + } getOrElse { + Future.successful(None) } } diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/data/UserInfoGenerator.scala b/app/uk/gov/hmrc/openidconnect/userinfo/data/UserInfoGenerator.scala index 96cb063..52b0c9f 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/data/UserInfoGenerator.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/data/UserInfoGenerator.scala @@ -16,7 +16,7 @@ package uk.gov.hmrc.openidconnect.userinfo.data -import uk.gov.hmrc.openidconnect.userinfo.domain.{Address, UserInfo} +import uk.gov.hmrc.openidconnect.userinfo.domain.{Address, Enrolment, EnrolmentIdentifier, UserInfo} import org.joda.time._ import org.scalacheck.Gen @@ -29,6 +29,8 @@ trait UserInfoGenerator { |London |NW1 9NT |Great Britain""".stripMargin, Some("NW1 9NT"), Some("Great Britain"))) + val enrolments = Seq(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121")))) + private lazy val ninoPrefixes = "ABCEGHJKLMNPRSTWXYZ" private lazy val ninoSuffixes = "ABCD" @@ -65,7 +67,7 @@ trait UserInfoGenerator { middleName <- middleNameGen dob <- dateOfBirth nino <- formattedNino - } yield UserInfo(name, lastName, middleName, address, Some(dob), Some(nino)) + } yield UserInfo(name, lastName, middleName, address, Some(dob), Some(nino), Some(enrolments)) } object UserInfoGenerator extends UserInfoGenerator diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/domain/Enrolment.scala b/app/uk/gov/hmrc/openidconnect/userinfo/domain/Enrolment.scala new file mode 100644 index 0000000..b781e92 --- /dev/null +++ b/app/uk/gov/hmrc/openidconnect/userinfo/domain/Enrolment.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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.openidconnect.userinfo.domain + +case class EnrolmentIdentifier(key: String, value: String) + +case class Enrolment(key: String, + identifiers: Seq[EnrolmentIdentifier] = Seq(), + state: String = "Activated") { + + def getIdentifier(key: String): Option[EnrolmentIdentifier] = identifiers.find { ei => + ei.key.equalsIgnoreCase(key) + } + + def isActivated = state.toLowerCase == "activated" + +} + +case object ActiveEnrolment { + def unapply(enrolment: Enrolment): Option[String] = + if (enrolment.isActivated) Some(enrolment.key) + else None +} diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/domain/UserInfo.scala b/app/uk/gov/hmrc/openidconnect/userinfo/domain/UserInfo.scala index 81724d6..881f117 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/domain/UserInfo.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/domain/UserInfo.scala @@ -27,7 +27,8 @@ case class UserInfo(given_name: Option[String], middle_name: Option[String], address: Option[Address], birthdate: Option[LocalDate], - uk_gov_nino: Option[String]) + uk_gov_nino: Option[String], + hmrc_enrolments: Option[Seq[Enrolment]]) case class UserInformation(profile: Option[UserProfile], address: Option[Address], diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/domain/package.scala b/app/uk/gov/hmrc/openidconnect/userinfo/domain/package.scala index 25b16bd..ea9675d 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/domain/package.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/domain/package.scala @@ -21,31 +21,48 @@ import play.api.libs.json._ import play.api.libs.functional.syntax._ package object domain { - implicit val dateReads = Reads.jodaDateReads("yyyy-MM-dd") - implicit val dateWrites = Writes.jodaDateWrites("yyyy-MM-dd") - implicit val addressFmt = Json.format[Address] - implicit val userInfoFmt = Json.format[UserInfo] - implicit val apiAccessFmt = Json.format[APIAccess] implicit val desUserName : Reads[DesUserName] = ( - (JsPath \ "firstForenameOrInitial").readNullable[String] and - (JsPath \ "secondForenameOrInitial").readNullable[String] and - (JsPath \ "surname").readNullable[String] - )(DesUserName.apply _) + (JsPath \ "firstForenameOrInitial").readNullable[String] and + (JsPath \ "secondForenameOrInitial").readNullable[String] and + (JsPath \ "surname").readNullable[String] + )(DesUserName.apply _) implicit val desAddress : Reads[DesAddress] = ( - (JsPath \ "line1").readNullable[String] and - (JsPath \ "line2").readNullable[String] and - (JsPath \ "line3").readNullable[String] and - (JsPath \ "line4").readNullable[String] and - (JsPath \ "postcode").readNullable[String] and - (JsPath \ "countryCode").readNullable[Int] - )(DesAddress.apply _) + (JsPath \ "line1").readNullable[String] and + (JsPath \ "line2").readNullable[String] and + (JsPath \ "line3").readNullable[String] and + (JsPath \ "line4").readNullable[String] and + (JsPath \ "postcode").readNullable[String] and + (JsPath \ "countryCode").readNullable[Int] + )(DesAddress.apply _) implicit val desUserInfo : Reads[DesUserInfo] = ( - (JsPath \ "names" \ "1").read[DesUserName] and - (JsPath \ "dateOfBirth").readNullable[LocalDate] and - (JsPath \ "addresses" \ "1").read[DesAddress] - )(DesUserInfo.apply _) + (JsPath \ "names" \ "1").read[DesUserName] and + (JsPath \ "dateOfBirth").readNullable[LocalDate] and + (JsPath \ "addresses" \ "1").read[DesAddress] + )(DesUserInfo.apply _) + + implicit val idformat = Json.format[EnrolmentIdentifier] + implicit val format = Format( + ((__ \ "key").read[String] and + (__ \ "identifiers").read[Seq[EnrolmentIdentifier]] and + (__ \ "state").readNullable[String]) { + (key, ids, optState) => + Enrolment( + key, + ids, + optState.getOrElse("Activate") + ) + }, + Writes[Enrolment] { enrolment => + Json.writes[Enrolment].writes(enrolment).as[JsObject] + } + ) + implicit val dateReads = Reads.jodaDateReads("yyyy-MM-dd") + implicit val dateWrites = Writes.jodaDateWrites("yyyy-MM-dd") + implicit val addressFmt = Json.format[Address] + implicit val userInfoFmt = Json.format[UserInfo] + implicit val apiAccessFmt = Json.format[APIAccess] } diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoService.scala b/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoService.scala index e1d6386..f296a32 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoService.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoService.scala @@ -17,13 +17,16 @@ package uk.gov.hmrc.openidconnect.userinfo.services import play.api.Logger -import uk.gov.hmrc.openidconnect.userinfo.connectors.{AuthConnector, DesConnector} +import uk.gov.hmrc.domain.Nino +import uk.gov.hmrc.openidconnect.userinfo.connectors.{AuthConnector, DesConnector, ThirdPartyDelegatedAuthorityConnector} import uk.gov.hmrc.openidconnect.userinfo.data.UserInfoGenerator import uk.gov.hmrc.openidconnect.userinfo.domain._ -import uk.gov.hmrc.play.http.HeaderCarrier -import scala.concurrent.ExecutionContext.Implicits.global +import uk.gov.hmrc.play.http.logging.Authorization +import uk.gov.hmrc.play.http.{HeaderCarrier, UnauthorizedException} -import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Future, Promise} +import scala.util.{Failure, Success} trait UserInfoService { def fetchUserInfo()(implicit hc: HeaderCarrier): Future[Option[UserInfo]] @@ -33,19 +36,57 @@ trait LiveUserInfoService extends UserInfoService { val authConnector: AuthConnector val desConnector: DesConnector val userInfoTransformer: UserInfoTransformer + val thirdPartyDelegatedAuthorityConnector: ThirdPartyDelegatedAuthorityConnector + override def fetchUserInfo()(implicit hc: HeaderCarrier): Future[Option[UserInfo]] = { + def bearerToken(authorization: Authorization) = augmentString(authorization.value).stripPrefix("Bearer ") + + def scopes = hc.authorization match { + case Some(authorization) => thirdPartyDelegatedAuthorityConnector.fetchScopes(bearerToken(authorization)) + case None => Future.failed(new UnauthorizedException("Bearer token is required")) + } + + val promiseNino = Promise[Option[Nino]]() + val maybeNino = promiseNino.future + + scopes flatMap { scopes => + val scopesForNino = Set("profile", "address", "openid:gov-uk-identifiers") + if ((scopesForNino -- scopes).size != scopesForNino.size) { + authConnector.fetchNino() onComplete { + case Success(nino) => promiseNino success nino + case Failure(exception) => throw exception + } + } else promiseNino success None + + val promiseDesUserInfo = Promise[Option[DesUserInfo]] + val maybeDesUserInfo = promiseDesUserInfo.future + + val scopesDes = Set("profile", "address") + if ((scopesDes -- scopes).size != scopesDes.size) { + maybeNino onComplete { + case Success(hopefulyNino) => desConnector.fetchUserInfo(hopefulyNino) onComplete { + case Success(desUserInfo) => promiseDesUserInfo success desUserInfo + case Failure(exception) => throw exception + } + case Failure(exception) => throw exception + } + } else promiseDesUserInfo success None + + def maybeEnrolments = if (scopes.contains("openid:hmrc_enrolments")) authConnector.fetchEnrolments() else Future.successful(None) - val future: Future[UserInfo] = for { - nino <- authConnector.fetchNino() - desUserInfo <- desConnector.fetchUserInfo(nino.nino) - userInfo <- userInfoTransformer.transform(desUserInfo, nino.nino) - } yield userInfo + val future: Future[UserInfo] = for { + nino <- maybeNino + enrolments <- maybeEnrolments + desUserInfo <- maybeDesUserInfo + } yield userInfoTransformer.transform(scopes, desUserInfo, nino, enrolments) - future map (Some(_)) recover { - case NinoNotFoundException() => - Logger.debug("Nino not present in Bearer Token") - None + future map (Some((_: UserInfo))) recover { + case e: Throwable => { + Logger.debug(e.getMessage, e) + None + } + } } } } @@ -62,6 +103,7 @@ object LiveUserInfoService extends LiveUserInfoService { override val desConnector = DesConnector override val authConnector = AuthConnector override val userInfoTransformer = UserInfoTransformer + override val thirdPartyDelegatedAuthorityConnector = ThirdPartyDelegatedAuthorityConnector } object SandboxUserInfoService extends SandboxUserInfoService { diff --git a/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformer.scala b/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformer.scala index 0fdbb06..7cba43e 100644 --- a/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformer.scala +++ b/app/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformer.scala @@ -17,54 +17,41 @@ package uk.gov.hmrc.openidconnect.userinfo.services import org.joda.time.LocalDate -import uk.gov.hmrc.openidconnect.userinfo.connectors.ThirdPartyDelegatedAuthorityConnector -import uk.gov.hmrc.openidconnect.userinfo.domain.{DesAddress, Address, UserInfo, DesUserInfo} -import uk.gov.hmrc.play.http.logging.Authorization -import uk.gov.hmrc.play.http.{UnauthorizedException, HeaderCarrier} - -import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global +import uk.gov.hmrc.domain.Nino +import uk.gov.hmrc.openidconnect.userinfo.domain.{Address, DesAddress, DesUserInfo, Enrolment, UserInfo} trait UserInfoTransformer { val countryService: CountryService - val thirdPartyDelegatedAuthorityConnector: ThirdPartyDelegatedAuthorityConnector - - def transform(desUserInfo: Option[DesUserInfo], nino: String)(implicit hc:HeaderCarrier): Future[UserInfo] = { - def bearerToken(authorization: Authorization) = authorization.value.stripPrefix("Bearer ") - hc.authorization match { - case Some(authorization) => thirdPartyDelegatedAuthorityConnector.fetchScopes(bearerToken(authorization)) map { scopes => - constructUserInfo(desUserInfo, nino, scopes) - } - case None => Future.failed(new UnauthorizedException("Bearer token is required")) - } - } + def transform(scopes: Set[String], desUserInfo: Option[DesUserInfo], nino: Option[Nino], enrolments: Option[Seq[Enrolment]]): UserInfo = { + def profile = if (scopes.contains("profile")) desUserInfo map (u => UserProfile(u.name.firstForenameOrInitial, u.name.surname, u.name.secondForenameOrInitial, u.dateOfBirth)) else None - private def constructUserInfo(desUserInfo: Option[DesUserInfo], nino: String, scopes: Set[String]): UserInfo = { - val userProfile = desUserInfo map (u => UserProfile(u.name.firstForenameOrInitial, u.name.surname, u.name.secondForenameOrInitial, u.dateOfBirth)) - val country = desUserInfo flatMap (u => u.address.countryCode flatMap countryService.getCountry) + def address = if (scopes.contains("address")) { + val country = desUserInfo flatMap (u => u.address.countryCode flatMap countryService.getCountry) + desUserInfo map (u => Address(formattedAddress(u.address, country), u.address.postcode, country)) + } else None - val profile = if (scopes.contains("profile")) userProfile else None - val identifier = if (scopes.contains("openid:gov-uk-identifiers")) Some(nino) else None - val address = if (scopes.contains("address")) desUserInfo map (u => Address(formattedAddress(u.address, country), u.address.postcode, country)) else None + val identifier = if (scopes.contains("openid:gov-uk-identifiers")) nino.map(_.nino) else None + val userEnrolments = if (scopes.contains("openid:hmrc_enrolments")) enrolments else None UserInfo(profile.flatMap(_.firstName), profile.flatMap(_.familyName), profile.flatMap(_.middleName), address, profile.flatMap(_.birthDate), - identifier) + identifier, + userEnrolments) } private def formattedAddress(desAddress: DesAddress, country: Option[String]) = { - Seq(desAddress.line1,desAddress.line2, desAddress.line3, desAddress.line4, desAddress.postcode, country).flatten.mkString("\n") + Seq(desAddress.line1, desAddress.line2, desAddress.line3, desAddress.line4, desAddress.postcode, country).flatten.mkString("\n") } private case class UserProfile(firstName: Option[String], familyName: Option[String], middleName: Option[String], birthDate: Option[LocalDate]) + } object UserInfoTransformer extends UserInfoTransformer { override val countryService = CountryService - override val thirdPartyDelegatedAuthorityConnector = ThirdPartyDelegatedAuthorityConnector } diff --git a/public/api/conf/1.0/schemas/userinfo.json b/public/api/conf/1.0/schemas/userinfo.json index 6496971..ecc77bf 100644 --- a/public/api/conf/1.0/schemas/userinfo.json +++ b/public/api/conf/1.0/schemas/userinfo.json @@ -47,6 +47,47 @@ "example": "Great Britain" } } + }, + "hmrc_enrolment": { + "type": "object", + "description": "End-user's HMRC enrolment.", + "properties": { + "service": { + "type": "string", + "description": "HMRC service name.", + "example": "IR-SA" + }, + "identifiers": { + "type": "object", + "description": "End-user's identifiers associated to this HMRC service.", + "example": "UTR : 123456789013" + }, + "state": { + "type": "string", + "description": "End-user's HMRC enrolment status.", + "enum": [ + "awaitingActivation", + "activated", + "pending", + "givenToAgent" + ] + }, + "confidenceLevel": { + "type": "integer", + "required": false, + "description": "End-user's confidence level to this HMRC service.", + "example": 200 + } + } + }, + "hmrc_enrolments": { + "type": "array", + "description": "End-user's HMRC enrolments.", + "items": [ + { + "type": "hmrc_enrolment" + } + ] } } } diff --git a/test/it/UserInfoServiceSpec.scala b/test/it/UserInfoServiceSpec.scala index ac7a1cb..7117622 100644 --- a/test/it/UserInfoServiceSpec.scala +++ b/test/it/UserInfoServiceSpec.scala @@ -31,13 +31,16 @@ class UserInfoServiceSpec extends BaseFeatureSpec { val ukCountryCode = 1 val desUserInfo = DesUserInfo(DesUserName(Some("John"), Some("A"), Some("Smith")), Some(LocalDate.parse("1980-01-01")), DesAddress(Some("1 Station Road"), Some("Town Centre"), Some("London"), Some("England"), Some("NW1 6XE"), Some(ukCountryCode))) + val enrolments = Seq(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121")))) + val userInfo = UserInfo( Some("John"), Some("Smith"), Some("A"), Some(Address("1 Station Road\nTown Centre\nLondon\nEngland\nNW1 6XE\nGREAT BRITAIN", Some("NW1 6XE"), Some("GREAT BRITAIN"))), Some(LocalDate.parse("1980-01-01")), - Some("AB123456A")) + Some("AB123456A"), + Some(enrolments)) val desUserInfoWithoutFirstName = DesUserInfo(DesUserName(None, Some("A"), Some("Smith")), Some(LocalDate.parse("1980-01-01")), DesAddress(Some("1 Station Road"), Some("Town Centre"), Some("London"), Some("England"), Some("NW1 6XE"), Some(ukCountryCode))) val userInfoWithoutFirstName = UserInfo( @@ -46,7 +49,9 @@ class UserInfoServiceSpec extends BaseFeatureSpec { Some("A"), Some(Address("1 Station Road\nTown Centre\nLondon\nEngland\nNW1 6XE\nGREAT BRITAIN", Some("NW1 6XE"), Some("GREAT BRITAIN"))), Some(LocalDate.parse("1980-01-01")), - Some("AB123456A")) + Some("AB123456A"), + Some(enrolments) + ) val desUserInfoWithoutFamilyName = DesUserInfo(DesUserName(Some("John"), Some("A"), None), Some(LocalDate.parse("1980-01-01")), DesAddress(Some("1 Station Road"), Some("Town Centre"), Some("London"), Some("England"), Some("NW1 6XE"), Some(ukCountryCode))) val userInfoWithoutFamilyName = UserInfo( @@ -55,7 +60,8 @@ class UserInfoServiceSpec extends BaseFeatureSpec { Some("A"), Some(Address("1 Station Road\nTown Centre\nLondon\nEngland\nNW1 6XE\nGREAT BRITAIN", Some("NW1 6XE"), Some("GREAT BRITAIN"))), Some(LocalDate.parse("1980-01-01")), - Some("AB123456A")) + Some("AB123456A"), + Some(enrolments)) val desUserInfoWithPartialAddress = DesUserInfo(DesUserName(Some("John"), Some("A"), Some("Smith")), Some(LocalDate.parse("1980-01-01")), DesAddress(Some("1 Station Road"), None, Some("Lancaster"), Some("England"), Some("NW1 6XE"), Some(ukCountryCode))) val userInfoWithPartialAddress = UserInfo( @@ -64,19 +70,23 @@ class UserInfoServiceSpec extends BaseFeatureSpec { Some("A"), Some(Address("1 Station Road\nLancaster\nEngland\nNW1 6XE\nGREAT BRITAIN", Some("NW1 6XE"), Some("GREAT BRITAIN"))), Some(LocalDate.parse("1980-01-01")), - Some("AB123456A")) + Some("AB123456A"), + None) feature("fetch user information") { scenario("fetch user profile") { - Given("A Auth token with 'openid', 'profile', 'address' and 'openid:gov-uk-identifiers' scopes") + Given("A Auth token with 'openid', 'profile', 'address', 'openid:gov-uk-identifiers' and 'openid:hmrc_enrolments' scopes") thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, - Set("openid", "profile", "address", "openid:gov-uk-identifiers")) + Set("openid", "profile", "address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments")) And("The Auth token has a confidence level above 200 and a NINO") authStub.willReturnAuthorityWith(ConfidenceLevel.L200, Nino(nino)) + And("The authority has enrolments") + authStub.willReturnEnrolmentsWith() + And("DES contains user information for the NINO") desStub.willReturnUserInformation(desUserInfo, nino) @@ -92,13 +102,16 @@ class UserInfoServiceSpec extends BaseFeatureSpec { scenario("fetch user profile without first name") { - Given("A Auth token with 'openid', 'profile', 'address' and 'openid:gov-uk-identifiers' scopes") + Given("A Auth token with 'openid', 'profile', 'address', 'openid:gov-uk-identifiers' and 'openid:hmrc_enrolments' scopes") thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, - Set("openid", "profile", "address", "openid:gov-uk-identifiers")) + Set("openid", "profile", "address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments")) And("The Auth token has a confidence level above 200 and a NINO") authStub.willReturnAuthorityWith(ConfidenceLevel.L200, Nino(nino)) + And("The authority has enrolments") + authStub.willReturnEnrolmentsWith() + And("DES contains user information for the NINO") desStub.willReturnUserInformation(desUserInfoWithoutFirstName, nino) @@ -114,13 +127,16 @@ class UserInfoServiceSpec extends BaseFeatureSpec { scenario("fetch user profile without family name") { - Given("A Auth token with 'openid', 'profile', 'address' and 'openid:gov-uk-identifiers' scopes") + Given("A Auth token with 'openid', 'profile', 'address', 'openid:gov-uk-identifiers' and 'openid:hmrc_enrolments' scopes") thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, - Set("openid", "profile", "address", "openid:gov-uk-identifiers")) + Set("openid", "profile", "address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments")) And("The Auth token has a confidence level above 200 and a NINO") authStub.willReturnAuthorityWith(ConfidenceLevel.L200, Nino(nino)) + And("The authority has enrolments") + authStub.willReturnEnrolmentsWith() + And("DES contains user information for the NINO") desStub.willReturnUserInformation(desUserInfoWithoutFamilyName, nino) @@ -155,5 +171,93 @@ class UserInfoServiceSpec extends BaseFeatureSpec { result.code shouldBe 200 Json.parse(result.body) shouldBe Json.toJson(userInfoWithPartialAddress) } + + scenario("fetch user data without enrolments when there are no enrolments") { + + Given("A Auth token with 'openid', 'profile', 'address', 'openid:gov-uk-identifiers' and 'openid:hmrc_enrolments' scopes") + thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, + Set("openid", "profile", "address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments")) + + And("The Auth token has a confidence level above 200 and a NINO") + authStub.willReturnAuthorityWith(ConfidenceLevel.L200, Nino(nino)) + + And("DES contains user information for the NINO") + desStub.willReturnUserInformation(desUserInfo, nino) + + When("We request the user information") + val result = Http(s"$serviceUrl") + .headers(Seq("Authorization" -> s"Bearer $authBearerToken", "Accept" -> "application/vnd.hmrc.1.0+json")) + .asString + + Then("The user information is returned") + result.code shouldBe 200 + Json.parse(result.body) shouldBe Json.toJson(userInfo.copy(hmrc_enrolments = None)) + } + + scenario("fetch user data without address and user details when there are no address and user details") { + + Given("A Auth token with 'openid', 'profile', 'address', 'openid:gov-uk-identifiers' and 'openid:hmrc_enrolments' scopes") + thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, + Set("openid", "profile", "address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments")) + + And("The Auth token has a confidence level above 200 and a NINO") + authStub.willReturnAuthorityWith(ConfidenceLevel.L200, Nino(nino)) + + And("The authority has enrolments") + authStub.willReturnEnrolmentsWith() + + When("We request the user information") + val result = Http(s"$serviceUrl") + .headers(Seq("Authorization" -> s"Bearer $authBearerToken", "Accept" -> "application/vnd.hmrc.1.0+json")) + .asString + + Then("The user information is returned") + result.code shouldBe 200 + val userWithNinoAndEnrolmentsOnly = userInfo.copy(given_name = None, family_name = None, middle_name = None, address = None, birthdate = None) + Json.parse(result.body) shouldBe Json.toJson(userWithNinoAndEnrolmentsOnly) + } + + scenario("fetch enrolments only when scope contains 'openid:hmrc_enrolments'") { + + Given("A Auth token with 'openid:hmrc_enrolments' scopes") + thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, + Set("openid:hmrc_enrolments")) + + And("The Auth token has a confidence level above 200 and a NINO") + authStub.willReturnAuthorityWith(ConfidenceLevel.L200, Nino(nino)) + + And("The authority has enrolments") + authStub.willReturnEnrolmentsWith() + + When("We request the user information") + val result = Http(s"$serviceUrl") + .headers(Seq("Authorization" -> s"Bearer $authBearerToken", "Accept" -> "application/vnd.hmrc.1.0+json")) + .asString + + Then("The user information is returned") + result.code shouldBe 200 + val userWithEnrolmentsOnly = userInfo.copy(given_name = None, family_name = None, middle_name = None, address = None, birthdate = None, uk_gov_nino = None) + Json.parse(result.body) shouldBe Json.toJson(userWithEnrolmentsOnly) + } + + scenario("return 401 - unauthorized when confidence level is less than 200") { + + Given("A Auth token with 'openid', 'profile', 'address', 'openid:gov-uk-identifiers' and 'openid:hmrc_enrolments' scopes") + thirdPartyDelegatedAuthorityStub.willReturnScopesForAuthBearerToken(authBearerToken, + Set("openid", "profile", "address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments")) + + And("The Auth token has a confidence level above 200 and a NINO") + authStub.willReturnAuthorityWith(ConfidenceLevel.L100, Nino(nino)) + + When("We request the user information") + val result = Http(s"$serviceUrl") + .headers(Seq("Authorization" -> s"Bearer $authBearerToken", "Accept" -> "application/vnd.hmrc.1.0+json")) + .asString + + Then("The user information is returned") + result.code shouldBe 401 + + Json.parse(result.body) shouldBe Json.parse(s"""{"code":"UNAUTHORIZED","message":"Bearer token is missing or not authorized"}""".stripMargin) + } } } diff --git a/test/it/stubs/AuthStub.scala b/test/it/stubs/AuthStub.scala index 28bc3ff..97d47b1 100644 --- a/test/it/stubs/AuthStub.scala +++ b/test/it/stubs/AuthStub.scala @@ -30,9 +30,30 @@ object AuthStub extends Stub { s""" |{ | "confidenceLevel": ${confidenceLevel.level}, - | "nino": "${nino.nino}" + | "nino": "${nino.nino}", + | "enrolments": "/auth/oid/2/enrolments" |} - """.stripMargin))) + """.stripMargin + ))) } + def willReturnEnrolmentsWith() = { + stub.mock.register(get(urlPathEqualTo(s"/auth/oid/2/enrolments")) + .willReturn(aResponse().withBody( + s""" + |[ + | { + | "key": "IR-SA", + | "identifiers": [ + | { + | "key": "UTR", + | "value": "174371121" + | } + | ], + | "state": "Activated" + | } + |] + """.stripMargin + ))) + } } diff --git a/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnectorSpec.scala b/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnectorSpec.scala index 881e61c..696c84e 100644 --- a/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnectorSpec.scala +++ b/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/AuthConnectorSpec.scala @@ -21,7 +21,7 @@ import org.scalatest.mock.MockitoSugar import uk.gov.hmrc.domain.Nino import uk.gov.hmrc.openidconnect.userinfo.config.WSHttp import uk.gov.hmrc.openidconnect.userinfo.connectors.AuthConnector -import uk.gov.hmrc.openidconnect.userinfo.domain.NinoNotFoundException +import uk.gov.hmrc.openidconnect.userinfo.domain.{Enrolment, EnrolmentIdentifier, NinoNotFoundException} import uk.gov.hmrc.play.auth.microservice.connectors.ConfidenceLevel import uk.gov.hmrc.play.auth.microservice.connectors.ConfidenceLevel._ import uk.gov.hmrc.play.http.{HeaderCarrier, HttpGet, Upstream5xxResponse} @@ -49,17 +49,36 @@ class AuthConnectorSpec extends WireMockSugar { "fetchNino" should { "return the authority's nino" in new TestAuthConnector(wiremockBaseUrl) { given().get(urlPathEqualTo("/auth/authority")).returns("""{"nino":"NB966669A"}""") - fetchNino().futureValue shouldBe Nino("NB966669A") + fetchNino().futureValue shouldBe Some(Nino("NB966669A")) } - "fail with NinoNotFoundException when authority's NINO is not in the response" in new TestAuthConnector(wiremockBaseUrl) { + "return None when authority's NINO is not in the response" in new TestAuthConnector(wiremockBaseUrl) { given().get(urlPathEqualTo("/auth/authority")).returns("""{"credentialStrength":"weak"}""") - intercept[NinoNotFoundException]{await(fetchNino())} + fetchNino().futureValue shouldBe None } - "fail when auth request fails" in new TestAuthConnector(wiremockBaseUrl) { + "return None when auth request fails" in new TestAuthConnector(wiremockBaseUrl) { given().get(urlPathEqualTo("/auth/authority")).returns(500) - intercept[Upstream5xxResponse]{await(fetchNino())} + fetchNino().futureValue shouldBe None + } + } + + "fetchEnrloments" should { + "return the authority enrloments" in new TestAuthConnector(wiremockBaseUrl) { + given().get(urlPathEqualTo("/auth/authority")).returns(authorityJson(L200)) + given().get(urlPathEqualTo("/uri/to/enrolments")).returns(enrolmentsJson()) + fetchEnrolments().futureValue shouldBe Some(Seq(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121"))))) + } + + "return None when there is no URI for enrolments" in new TestAuthConnector(wiremockBaseUrl) { + given().get(urlPathEqualTo("/auth/authority")).returns("""{"credentialStrength":"weak"}""") + fetchEnrolments().futureValue shouldBe None + } + + "return None when there are no enrolments at all" in new TestAuthConnector(wiremockBaseUrl) { + given().get(urlPathEqualTo("/auth/authority")).returns(authorityJson(L200)) + given().get(urlPathEqualTo("/uri/to/enrolments")).returns("{}") + fetchEnrolments().futureValue shouldBe None } } @@ -75,8 +94,26 @@ class TestAuthConnector(wiremockBaseUrl: String) extends AuthConnector with Mock s""" |{ | "credentialStrength":"weak", - | "confidenceLevel": ${confidenceLevel.level} + | "confidenceLevel": ${confidenceLevel.level}, + | "enrolments": "/uri/to/enrolments" |} """.stripMargin } + + def enrolmentsJson() = { + s""" + |[ + | { + | "key": "IR-SA", + | "identifiers": [ + | { + | "key": "UTR", + | "value": "174371121" + | } + | ], + | "state": "Activated" + | } + |] + """.stripMargin + } } diff --git a/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnectorSpec.scala b/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnectorSpec.scala index f3bd970..ed82ce5 100644 --- a/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnectorSpec.scala +++ b/test/unit/uk/gov/hmrc/openidconnect/userinfo/connectors/DesConnectorSpec.scala @@ -22,6 +22,7 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.github.tomakehurst.wiremock.core.WireMockConfiguration._ import org.joda.time.LocalDate import org.scalatest.BeforeAndAfterEach +import uk.gov.hmrc.domain.Nino import uk.gov.hmrc.openidconnect.userinfo.config.WSHttp import uk.gov.hmrc.openidconnect.userinfo.connectors.DesConnector import uk.gov.hmrc.openidconnect.userinfo.domain._ @@ -62,7 +63,7 @@ class DesConnectorSpec extends UnitSpec with BeforeAndAfterEach with WithFakeApp } "fetch user info" should { - val nino = "AA111111A" + val nino = Some(Nino("AA111111A")) val ninoWithoutSuffix = "AA111111" "return the user info" in new Setup { diff --git a/test/unit/uk/gov/hmrc/openidconnect/userinfo/controllers/UserInfoControllerSpec.scala b/test/unit/uk/gov/hmrc/openidconnect/userinfo/controllers/UserInfoControllerSpec.scala index 134a326..1daa5c4 100644 --- a/test/unit/uk/gov/hmrc/openidconnect/userinfo/controllers/UserInfoControllerSpec.scala +++ b/test/unit/uk/gov/hmrc/openidconnect/userinfo/controllers/UserInfoControllerSpec.scala @@ -25,7 +25,7 @@ import org.scalatest.mock.MockitoSugar import play.api.libs.json.Json import play.api.test.FakeRequest import uk.gov.hmrc.openidconnect.userinfo.controllers.{LiveUserInfoController, SandboxUserInfoController} -import uk.gov.hmrc.openidconnect.userinfo.domain.{Address, UserInfo} +import uk.gov.hmrc.openidconnect.userinfo.domain.{Address, Enrolment, EnrolmentIdentifier, UserInfo} import uk.gov.hmrc.openidconnect.userinfo.services.{LiveUserInfoService, SandboxUserInfoService} import uk.gov.hmrc.play.filters.MicroserviceFilterSupport import uk.gov.hmrc.play.http.HeaderCarrier @@ -39,7 +39,9 @@ class UserInfoControllerSpec extends UnitSpec with MockitoSugar with ScalaFuture Some("Hannibal"), Some(Address("221B\\BAKER STREET\\nLONDON\\NW1 9NT\\nUnited Kingdom", Some("NW1 9NT"), Some("United Kingdom"))), Some(LocalDate.parse("1982-11-15")), - Some("AR778351B")) + Some("AR778351B"), + Some(Seq(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121"))))) + ) trait Setup extends MicroserviceFilterSupport { val mockLiveUserInfoService = mock[LiveUserInfoService] diff --git a/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoServiceSpec.scala b/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoServiceSpec.scala index f4154de..082dd7d 100644 --- a/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoServiceSpec.scala +++ b/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoServiceSpec.scala @@ -17,26 +17,29 @@ package unit.uk.gov.hmrc.openidconnect.userinfo.services import org.mockito.BDDMockito.given +import org.mockito.Matchers.any +import org.mockito.Mockito.{never, verify} +import org.scalatest.concurrent.ScalaFutures +import org.scalatest.mock.MockitoSugar import uk.gov.hmrc.domain.Nino -import uk.gov.hmrc.openidconnect.userinfo.connectors.{AuthConnector, DesConnector} +import uk.gov.hmrc.openidconnect.userinfo.connectors.{AuthConnector, DesConnector, ThirdPartyDelegatedAuthorityConnector} import uk.gov.hmrc.openidconnect.userinfo.data.UserInfoGenerator import uk.gov.hmrc.openidconnect.userinfo.domain._ -import org.scalatest.concurrent.ScalaFutures -import org.scalatest.mock.MockitoSugar -import uk.gov.hmrc.openidconnect.userinfo.services.{UserInfoTransformer, SandboxUserInfoService, LiveUserInfoService} +import uk.gov.hmrc.openidconnect.userinfo.services.{LiveUserInfoService, SandboxUserInfoService, UserInfoTransformer} import uk.gov.hmrc.play.http.HeaderCarrier +import uk.gov.hmrc.play.http.logging.Authorization import uk.gov.hmrc.play.test.UnitSpec -import scala.concurrent.Future.failed - class UserInfoServiceSpec extends UnitSpec with MockitoSugar with ScalaFutures { - val nino = "AB123456A" + val nino = Nino("AB123456A") + val authBearerToken = "AUTH_BEARER_TOKEN" val desUserInfo = DesUserInfo(DesUserName(Some("John"), None, Some("Smith")), None, DesAddress(Some("1 Station Road"), Some("Town Centre"), None, None, None, None)) - val userInfo = UserInfo(Some("John"), Some("Smith"), None, Some(Address("1 Station Road\nTown Centre", None, None)),None, Some(nino)) + val enrolments = Seq(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121")))) + val userInfo = UserInfo(Some("John"), Some("Smith"), None, Some(Address("1 Station Road\nTown Centre", None, None)),None, Some(nino).map(_.nino), Some(enrolments)) trait Setup { - implicit val headers = HeaderCarrier() + implicit val headers = HeaderCarrier().copy(authorization = Some(Authorization(s"Bearer $authBearerToken"))) val sandboxInfoService = new SandboxUserInfoService { override val userInfoGenerator = mock[UserInfoGenerator] @@ -46,41 +49,83 @@ class UserInfoServiceSpec extends UnitSpec with MockitoSugar with ScalaFutures { override val authConnector: AuthConnector = mock[AuthConnector] override val desConnector: DesConnector = mock[DesConnector] override val userInfoTransformer = mock[UserInfoTransformer] + override val thirdPartyDelegatedAuthorityConnector = mock[ThirdPartyDelegatedAuthorityConnector] } } "LiveUserInfoService" should { - "return the userInfo" in new Setup { + "requests all available data" in new Setup { - given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(Nino(nino)) - given(liveInfoService.desConnector.fetchUserInfo(nino)(headers)).willReturn(Some(desUserInfo)) - given(liveInfoService.userInfoTransformer.transform(Some(desUserInfo), nino)(headers)).willReturn(userInfo) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") + given(liveInfoService.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(headers)).willReturn(scopes) + given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(Some(nino)) + given(liveInfoService.authConnector.fetchEnrolments()(headers)).willReturn(Some(enrolments)) + given(liveInfoService.desConnector.fetchUserInfo(Some(nino))(headers)).willReturn(Some(desUserInfo)) + given(liveInfoService.userInfoTransformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))).willReturn(any[UserInfo], any[UserInfo]) - val result = await(liveInfoService.fetchUserInfo()) + await(liveInfoService.fetchUserInfo()) - result shouldBe Some(userInfo) + verify(liveInfoService.desConnector).fetchUserInfo(Some(nino)) + verify(liveInfoService.authConnector).fetchNino() + verify(liveInfoService.authConnector).fetchEnrolments() } "return None when the NINO is not in the authority" in new Setup { - given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(failed(new NinoNotFoundException)) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") + given(liveInfoService.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(headers)).willReturn(scopes) + given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(None) val result = await(liveInfoService.fetchUserInfo()) result shouldBe None } - "return only the nino when there is no user information for this NINO" in new Setup { + "does not request DES::fetchUserInfo when the scopes does not contain 'address' nor 'profile'" in new Setup { - val userInfoWithNinoOnly = UserInfo(None, None, None, None, None, Some(nino)) + val scopes = Set("openid:gov-uk-identifiers", "openid:hmrc_enrolments") + given(liveInfoService.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(headers)).willReturn(scopes) - given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(Nino(nino)) - given(liveInfoService.desConnector.fetchUserInfo(nino)(headers)).willReturn(None) - given(liveInfoService.userInfoTransformer.transform(None, nino)(headers)).willReturn(userInfoWithNinoOnly) + given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(Some(nino)) + given(liveInfoService.authConnector.fetchEnrolments()(headers)).willReturn(Some(enrolments)) + given(liveInfoService.userInfoTransformer.transform(scopes, None, Some(nino), Some(enrolments))).willReturn(any[UserInfo], any[UserInfo]) - val result = await(liveInfoService.fetchUserInfo()) + await(liveInfoService.fetchUserInfo()) + + verify(liveInfoService.desConnector, never).fetchUserInfo(any[Option[Nino]])(any[HeaderCarrier]) + verify(liveInfoService.authConnector).fetchNino() + verify(liveInfoService.authConnector).fetchEnrolments() + } + + "does not request AUTH::fetchNino nor DES::fetchUserInfo when the scopes does not contain 'address' nor 'profile' nor 'openid:gov-uk-identifiers'" in new Setup { + + val scopes = Set("openid:hmrc_enrolments") + given(liveInfoService.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(headers)).willReturn(scopes) + + given(liveInfoService.authConnector.fetchEnrolments()(headers)).willReturn(Some(enrolments)) + given(liveInfoService.userInfoTransformer.transform(scopes, None, None, Some(enrolments))).willReturn(any[UserInfo], any[UserInfo]) + + await(liveInfoService.fetchUserInfo()) + + verify(liveInfoService.desConnector, never).fetchUserInfo(any[Option[Nino]])(any[HeaderCarrier]) + verify(liveInfoService.authConnector, never).fetchNino() + verify(liveInfoService.authConnector).fetchEnrolments() + } + + "does not request AUTH::fetchEnrloments when the scopes does not contain 'openid:hmrc_enrolments'" in new Setup { + + val scopes = Set("address", "profile", "openid:gov-uk-identifiers") + given(liveInfoService.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(headers)).willReturn(scopes) + + given(liveInfoService.authConnector.fetchNino()(headers)).willReturn(Some(nino)) + given(liveInfoService.desConnector.fetchUserInfo(Some(nino))(headers)).willReturn(None) + given(liveInfoService.userInfoTransformer.transform(scopes, None, None, Some(enrolments))).willReturn(any[UserInfo], any[UserInfo]) + + await(liveInfoService.fetchUserInfo()) - result shouldBe Some(userInfoWithNinoOnly) + verify(liveInfoService.authConnector, never).fetchEnrolments() + verify(liveInfoService.authConnector).fetchNino() + verify(liveInfoService.desConnector).fetchUserInfo(any[Option[Nino]])(any[HeaderCarrier]) } } diff --git a/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformerSpec.scala b/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformerSpec.scala index feccd8d..44c880a 100644 --- a/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformerSpec.scala +++ b/test/unit/uk/gov/hmrc/openidconnect/userinfo/services/UserInfoTransformerSpec.scala @@ -16,23 +16,23 @@ package unit.uk.gov.hmrc.openidconnect.userinfo.services +import scala.concurrent.ExecutionContext.Implicits.global import org.joda.time.LocalDate import org.mockito.BDDMockito.given import org.scalatest.mock.MockitoSugar -import uk.gov.hmrc.openidconnect.userinfo.connectors.ThirdPartyDelegatedAuthorityConnector +import uk.gov.hmrc.domain.Nino import uk.gov.hmrc.openidconnect.userinfo.domain._ import uk.gov.hmrc.openidconnect.userinfo.services.{CountryService, UserInfoTransformer} import uk.gov.hmrc.play.http.HeaderCarrier -import uk.gov.hmrc.play.http.logging.Authorization import uk.gov.hmrc.play.test.UnitSpec class UserInfoTransformerSpec extends UnitSpec with MockitoSugar { val ukCountryCode = 10 - val nino = "AB123456A" - val authBearerToken = "AUTH_BEARER_TOKEN" + val nino = Nino("AB123456A") val desAddress: DesAddress = DesAddress(Some("1 Station Road"), Some("Town Centre"), Some("London"), Some("England"), Some("NW1 6XE"), Some(ukCountryCode)) val desUserInfo = DesUserInfo(DesUserName(Some("John"), Some("A"), Some("Smith")), Some(LocalDate.parse("1980-01-01")), desAddress) + val enrolments = Seq(Enrolment("IR-SA", List(EnrolmentIdentifier("UTR", "174371121")))) val userAddress: Address = Address("1 Station Road\nTown Centre\nLondon\nEngland\nNW1 6XE\nUnited Kingdom", Some("NW1 6XE"), Some("United Kingdom")) val userInfo = UserInfo( @@ -41,122 +41,132 @@ class UserInfoTransformerSpec extends UnitSpec with MockitoSugar { Some("A"), Some(userAddress), Some(LocalDate.parse("1980-01-01")), - Some("AB123456A")) + Some("AB123456A"), + Some(enrolments) + ) trait Setup { - implicit val hc: HeaderCarrier = HeaderCarrier().copy(authorization = Some(Authorization(s"Bearer $authBearerToken"))) + implicit val hc: HeaderCarrier = HeaderCarrier() val transformer = new UserInfoTransformer { override val countryService = mock[CountryService] - override val thirdPartyDelegatedAuthorityConnector = mock[ThirdPartyDelegatedAuthorityConnector] } given(transformer.countryService.getCountry(ukCountryCode)).willReturn(Some("United Kingdom")) } "transform" should { - "return the full object when the delegated authority has scope 'address', 'profile' and 'openid:gov-uk-identifiers'" in new Setup { + "return the full object when the delegated authority has scope 'address', 'profile', 'openid:gov-uk-identifiers' and 'openid:hrmc_enrolments'" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") - val result = await(transformer.transform(Some(desUserInfo), nino)) + val result = await(transformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))) result shouldBe userInfo } "return only the nino when des user info could not be retrieved" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers") - val result = await(transformer.transform(None, nino)) + val result = await(transformer.transform(scopes, None, Some(nino), None)) - result shouldBe UserInfo(None, None, None, None, None, Some(nino)) + result shouldBe UserInfo(None, None, None, None, None, Some(nino.map(_.nino)), None) } "does not return the address when the delegated authority does not have the scope 'address'" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("profile", "openid:gov-uk-identifiers")) + val scopes = Set("profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") - val result = await(transformer.transform(Some(desUserInfo), nino)) + val result = await(transformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))) result shouldBe userInfo.copy(address = None) } + + "does not return the enrolments when the delegated authority does not have the scope 'openid:hmrc_enrolments'" in new Setup { + + val scopes = Set("address", "profile", "openid:gov-uk-identifiers") + + val result = await(transformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))) + + result shouldBe userInfo.copy(hmrc_enrolments = None) + } + "does not return the user profile when the delegated authority does not have the scope 'profile'" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "openid:gov-uk-identifiers")) + val scopes = Set("address", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") - val result = await(transformer.transform(Some(desUserInfo), nino)) + val result = await(transformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))) result shouldBe userInfo.copy(given_name = None, family_name = None, middle_name = None, birthdate = None) } - "does not return the nino when the delegated authority does not have the scope 'openid:gov-uk-identifiers'" in new Setup { + "does not return the nino when the delegated authority does not have the scope 'openid:gov-uk-identifiers', 'openid:hmrc_enrolments'" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile")) + val scopes = Set("address", "profile", "openid:hmrc_enrolments") - val result = await(transformer.transform(Some(desUserInfo), nino)) + val result = await(transformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))) result shouldBe userInfo.copy(uk_gov_nino = None) } "return an empty object when the delegated authority does have only 'openid' scope" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("openid")) + val scopes = Set("openid") - val result = await(transformer.transform(Some(desUserInfo), nino)) + val result = await(transformer.transform(scopes, Some(desUserInfo), Some(nino), Some(enrolments))) - result shouldBe UserInfo(None, None, None, None, None, None) + result shouldBe UserInfo(None, None, None, None, None, None, None) } "handle missing first line of address" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") val desUserMissingline1 = desUserInfo.copy(address = desAddress.copy(line1=None)) - val result = await(transformer.transform(Some(desUserMissingline1), nino)) + val result = await(transformer.transform(scopes, Some(desUserMissingline1), Some(nino), Some(enrolments))) val userInfoMissingLine1 = userInfo.copy(address = Some(userAddress.copy(formatted = "Town Centre\nLondon\nEngland\nNW1 6XE\nUnited Kingdom"))) result shouldBe userInfoMissingLine1 } "handle missing second line of address" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") val desUserMissingLine2 = desUserInfo.copy(address = desAddress.copy(line2=None)) - val result = await(transformer.transform(Some(desUserMissingLine2), nino)) + val result = await(transformer.transform(scopes, Some(desUserMissingLine2), Some(nino),Some(enrolments))) val userInfoMissingLine2 = userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nLondon\nEngland\nNW1 6XE\nUnited Kingdom"))) result shouldBe userInfoMissingLine2 } "handle missing third line of address" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers", "openid:hmrc_enrolments") val desUserMissingLine3 = desUserInfo.copy(address = desAddress.copy(line3=None)) - val result = await(transformer.transform(Some(desUserMissingLine3), nino)) + val result = await(transformer.transform(scopes, Some(desUserMissingLine3), Some(nino),Some(enrolments))) val userInfoMissingLine3 = userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nEngland\nNW1 6XE\nUnited Kingdom"))) result shouldBe userInfoMissingLine3 } "handle missing fourth line of address" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers") val desUserMissingLine4 = desUserInfo.copy(address = desAddress.copy(line4=None)) - val result = await(transformer.transform(Some(desUserMissingLine4), nino)) - val userInfoMissingLine4 = userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nNW1 6XE\nUnited Kingdom"))) + val result = await(transformer.transform(scopes, Some(desUserMissingLine4), Some(nino),None)) + val userInfoMissingLine4 = userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nNW1 6XE\nUnited Kingdom")),hmrc_enrolments = None) result shouldBe userInfoMissingLine4 } "handle missing post code in address" in new Setup { - given(transformer.thirdPartyDelegatedAuthorityConnector.fetchScopes(authBearerToken)(hc)).willReturn(Set("address", "profile", "openid:gov-uk-identifiers")) + val scopes = Set("address", "profile", "openid:gov-uk-identifiers") val desUserMissingPostCode = desUserInfo.copy(address = desAddress.copy(postcode = None)) - val result = await(transformer.transform(Some(desUserMissingPostCode), nino)) - val userInfoMissingPostCode = userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nEngland\nUnited Kingdom", - postal_code = None))) + val result = await(transformer.transform(scopes, Some(desUserMissingPostCode), Some(nino),None)) + val userInfoMissingPostCode = userInfo.copy(address = Some(userAddress.copy(formatted = "1 Station Road\nTown Centre\nLondon\nEngland\nUnited Kingdom",postal_code = None)), hmrc_enrolments = None) result shouldBe userInfoMissingPostCode } }