diff --git a/app/config/Environments.scala b/app/config/Environments.scala new file mode 100644 index 00000000..9af43dfb --- /dev/null +++ b/app/config/Environments.scala @@ -0,0 +1,65 @@ +/* + * 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 config + +import com.google.inject.{Inject, Singleton} +import models.application.{Development, EnvironmentName, PreProduction, Production, Test} +import play.api.Logging +import play.api.libs.json.{Format, Json} + +case class EnvironmentConfig(environmentName: EnvironmentName, on: Boolean) + +object EnvironmentConfig { + + implicit val formatEnvironmentConfig: Format[EnvironmentConfig] = Json.format[EnvironmentConfig] + +} + +case class EnvironmentsConfig( + production: EnvironmentConfig, + preProduction: EnvironmentConfig, + test: EnvironmentConfig, + development: EnvironmentConfig +) + +object EnvironmentsConfig { + + implicit val formatEnvironmentsConfig: Format[EnvironmentsConfig] = Json.format[EnvironmentsConfig] + +} + +trait Environments { + + def config: EnvironmentsConfig + +} + +@Singleton +class EnvironmentsImpl @Inject() extends Environments with Logging { + + private val environmentsConfig = EnvironmentsConfig( + production = EnvironmentConfig(Production, on = true), + preProduction = EnvironmentConfig(PreProduction, on = true), + test = EnvironmentConfig(Test, on = true), + development = EnvironmentConfig(Development, on = true) + ) + + logger.info(s"Environment configuration${System.lineSeparator()}${Json.prettyPrint(Json.toJson(config))}") + + override def config: EnvironmentsConfig = environmentsConfig + +} diff --git a/app/config/Module.scala b/app/config/Module.scala index ce88c807..1c4458f0 100644 --- a/app/config/Module.scala +++ b/app/config/Module.scala @@ -47,7 +47,8 @@ class Module extends play.api.inject.Module { bindz[Domains].to(classOf[DomainsImpl]).eagerly(), bindz[Hods].to(classOf[HodsImpl]).eagerly(), bindz[Platforms].to(classOf[PlatformsImpl]).eagerly(), - bindz[EmailDomains].to(classOf[EmailDomainsImpl]).eagerly() + bindz[EmailDomains].to(classOf[EmailDomainsImpl]).eagerly(), + bindz[Environments].to(classOf[EnvironmentsImpl]).eagerly() ) val authTokenInitialiserBindings: Seq[Binding[?]] = if (configuration.get[Boolean]("create-internal-auth-token-on-start")) { diff --git a/app/controllers/application/AddCredentialController.scala b/app/controllers/application/AddCredentialController.scala index c9102940..3b2bce7d 100644 --- a/app/controllers/application/AddCredentialController.scala +++ b/app/controllers/application/AddCredentialController.scala @@ -19,7 +19,7 @@ package controllers.application import controllers.actions._ import controllers.helpers.ErrorResultBuilder import forms.AddCredentialChecklistFormProvider -import models.application.{Application, Credential, Primary, Secondary} +import models.application.{Application, Credential, Production, Test} import models.exception.ApplicationCredentialLimitException import models.user.UserModel import play.api.i18n.{I18nSupport, Messages, MessagesApi} @@ -59,7 +59,7 @@ class AddCredentialController @Inject()( formWithErrors => Future.successful(BadRequest(view(formWithErrors, applicationId))), _ => - apiHubService.addCredential(request.application.id, Primary) flatMap { + apiHubService.addCredential(request.application.id, Production) flatMap { case Right(Some(credential)) => fetchApiNames(request.application).map( apiNames => @@ -74,7 +74,7 @@ class AddCredentialController @Inject()( def addDevelopmentCredential(applicationId: String): Action[AnyContent] = (identify andThen applicationAuth(applicationId)).async { implicit request => - apiHubService.addCredential(request.application.id, Secondary) flatMap { + apiHubService.addCredential(request.application.id, Test) flatMap { case Right(Some(_)) => Future.successful(SeeOther(controllers.application.routes.EnvironmentAndCredentialsController.onPageLoad(request.application.id).url)) case Right(None) => applicationNotFound(request.application) diff --git a/app/controllers/application/EnvironmentAndCredentialsController.scala b/app/controllers/application/EnvironmentAndCredentialsController.scala index 1ef37f66..46442895 100644 --- a/app/controllers/application/EnvironmentAndCredentialsController.scala +++ b/app/controllers/application/EnvironmentAndCredentialsController.scala @@ -17,14 +17,14 @@ package controllers.application import com.google.inject.Inject -import config.FrontendAppConfig +import config.{Environments, FrontendAppConfig} import controllers.actions.{ApplicationAuthActionProvider, IdentifierAction} import controllers.helpers.ErrorResultBuilder -import models.application.{EnvironmentName, Primary, Secondary} +import models.application.{EnvironmentName, Production, Test} import models.exception.ApplicationCredentialLimitException import models.user.Permissions import play.api.i18n.{I18nSupport, Messages} -import play.api.mvc._ +import play.api.mvc.* import services.ApiHubService import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController import views.html.application.EnvironmentAndCredentialsView @@ -38,12 +38,13 @@ class EnvironmentAndCredentialsController @Inject()( view: EnvironmentAndCredentialsView, apiHubService: ApiHubService, errorResultBuilder: ErrorResultBuilder, - config: FrontendAppConfig + config: FrontendAppConfig, + environments: Environments )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { def onPageLoad(id: String): Action[AnyContent] = (identify andThen applicationAuth(id, enrich = true)) { implicit request => - Ok(view(request.application, request.identifierRequest.user, config.helpDocsPath)) + Ok(view(request.application, request.identifierRequest.user, config.helpDocsPath, environments)) } def deletePrimaryCredential(id: String, clientId: String): Action[AnyContent] = (identify andThen applicationAuth(id, enrich = true)).async { @@ -51,7 +52,7 @@ class EnvironmentAndCredentialsController @Inject()( request.identifierRequest.user.permissions match { case Permissions(_, true, _) | Permissions(_, _, true) => val url = s"${controllers.application.routes.EnvironmentAndCredentialsController.onPageLoad(id).url}#hip-production" - deleteCredential(id, clientId, Primary, url) + deleteCredential(id, clientId, Production, url) case _ => Future.successful(Redirect(controllers.routes.UnauthorisedController.onPageLoad)) } @@ -60,7 +61,7 @@ class EnvironmentAndCredentialsController @Inject()( def deleteSecondaryCredential(id: String, clientId: String): Action[AnyContent] = (identify andThen applicationAuth(id, enrich = true)).async { implicit request => val url = s"${controllers.application.routes.EnvironmentAndCredentialsController.onPageLoad(id).url}#hip-development" - deleteCredential(id, clientId, Secondary, url) + deleteCredential(id, clientId, Test, url) } private def deleteCredential(id: String, clientId: String, environmentName: EnvironmentName, url: String)(implicit request: Request[?]) = { diff --git a/app/controllers/helpers/ApplicationApiBuilder.scala b/app/controllers/helpers/ApplicationApiBuilder.scala index 36098ad2..c4f66ac9 100644 --- a/app/controllers/helpers/ApplicationApiBuilder.scala +++ b/app/controllers/helpers/ApplicationApiBuilder.scala @@ -54,8 +54,8 @@ class ApplicationApiBuilder @Inject()( endpointMethod.summary, endpointMethod.description, endpointMethod.scopes, - ApplicationEndpointAccess(application, hasPendingAccessRequest(apiDetail.id, pendingAccessRequests), endpointMethod, Primary), - ApplicationEndpointAccess(application, hasPendingAccessRequest(apiDetail.id, pendingAccessRequests), endpointMethod, Secondary) + ApplicationEndpointAccess(application, hasPendingAccessRequest(apiDetail.id, pendingAccessRequests), endpointMethod, Production), + ApplicationEndpointAccess(application, hasPendingAccessRequest(apiDetail.id, pendingAccessRequests), endpointMethod, Test) ) ) } diff --git a/app/models/application/Application.scala b/app/models/application/Application.scala index 298e35a7..2128996a 100644 --- a/app/models/application/Application.scala +++ b/app/models/application/Application.scala @@ -33,7 +33,13 @@ case class Application ( issues: Seq[String] = Seq.empty, deleted: Option[Deleted] = None, teamName: Option[String] = None, -) +) { + + def newEnvironments: Map[EnvironmentName, Environment] = { + environments.toNewEnvironments + } + +} object Application { diff --git a/app/models/application/ApplicationLenses.scala b/app/models/application/ApplicationLenses.scala index bf7a6f76..f0fe01fa 100644 --- a/app/models/application/ApplicationLenses.scala +++ b/app/models/application/ApplicationLenses.scala @@ -21,11 +21,18 @@ import models.user.UserModel object ApplicationLenses { - val applicationEnvironments: Lens[Application, Environments] = - Lens[Application, Environments]( - get = _.environments, - set = (application, environments) => application.copy(environments = environments) + // TODO: replace these hacks with something better + private def setProductionEnvironment(application: Application, environment: Environment): Application = { + application.copy( + environments = application.environments.copy(primary = environment) ) + } + + private def setTestEnvironment(application: Application, environment: Environment): Application = { + application.copy( + environments = application.environments.copy(secondary = environment) + ) + } val environmentScopes: Lens[Environment, Seq[Scope]] = Lens[Environment, Seq[Scope]]( @@ -39,35 +46,29 @@ object ApplicationLenses { set = (environment, credentials) => environment.copy(credentials = credentials) ) - val environmentPrimary: Lens[Environments, Environment] = - Lens[Environments, Environment]( - get = _.primary, - set = (environments, primary) => environments.copy(primary = primary) + val applicationProduction: Lens[Application, Environment] = + Lens[Application, Environment]( + get = _.newEnvironments.getOrElse(Production, Environment()), + set = (application, environment) => setProductionEnvironment(application, environment) ) - val applicationPrimary: Lens[Application, Environment] = - Lens.compose(applicationEnvironments, environmentPrimary) - - val applicationPrimaryScopes: Lens[Application, Seq[Scope]] = - Lens.compose(applicationPrimary, environmentScopes) + val applicationProductionScopes: Lens[Application, Seq[Scope]] = + Lens.compose(applicationProduction, environmentScopes) - val applicationPrimaryCredentials: Lens[Application, Seq[Credential]] = - Lens.compose(applicationPrimary, environmentCredentials) + val applicationProductionCredentials: Lens[Application, Seq[Credential]] = + Lens.compose(applicationProduction, environmentCredentials) - val environmentSecondary: Lens[Environments, Environment] = - Lens[Environments, Environment]( - get = _.secondary, - set = (environments, secondary) => environments.copy(secondary = secondary) + val applicationTest: Lens[Application, Environment] = + Lens[Application, Environment]( + get = _.newEnvironments.getOrElse(Test, Environment()), + set = (application, environment) => setTestEnvironment(application, environment) ) - val applicationSecondary: Lens[Application, Environment] = - Lens.compose(applicationEnvironments, environmentSecondary) - - val applicationSecondaryScopes: Lens[Application, Seq[Scope]] = - Lens.compose(applicationSecondary, environmentScopes) + val applicationTestScopes: Lens[Application, Seq[Scope]] = + Lens.compose(applicationTest, environmentScopes) - val applicationSecondaryCredentials: Lens[Application, Seq[Credential]] = - Lens.compose(applicationSecondary, environmentCredentials) + val applicationTestCredentials: Lens[Application, Seq[Credential]] = + Lens.compose(applicationTest, environmentCredentials) val applicationTeamMembers: Lens[Application, Seq[TeamMember]] = Lens[Application, Seq[TeamMember]]( @@ -89,68 +90,69 @@ object ApplicationLenses { implicit class ApplicationLensOps(application: Application) { - def getPrimaryScopes: Seq[Scope] = - applicationPrimaryScopes.get(application) + def getProductionScopes: Seq[Scope] = + applicationProductionScopes.get(application) - def setPrimaryScopes(scopes: Seq[Scope]): Application = - applicationPrimaryScopes.set(application, scopes) + def setProductionScopes(scopes: Seq[Scope]): Application = + applicationProductionScopes.set(application, scopes) - def addPrimaryScope(scope: Scope): Application = - applicationPrimaryScopes.set( + def addProductionScope(scope: Scope): Application = + applicationProductionScopes.set( application, - applicationPrimaryScopes.get(application) :+ scope + applicationProductionScopes.get(application) :+ scope ) - def getPrimaryMasterCredential: Option[Credential] = - applicationPrimaryCredentials.get(application) + def getProductionMasterCredential: Option[Credential] = + applicationProductionCredentials.get(application) .sortWith((a, b) => a.created.isAfter(b.created)) .headOption - def getPrimaryCredentials: Seq[Credential] = - applicationPrimaryCredentials.get(application) + def getProductionCredentials: Seq[Credential] = + applicationProductionCredentials.get(application) - def setPrimaryCredentials(credentials: Seq[Credential]): Application = - applicationPrimaryCredentials.set(application, credentials) + def setProductionCredentials(credentials: Seq[Credential]): Application = + applicationProductionCredentials.set(application, credentials) - def addPrimaryCredential(credential: Credential): Application = - applicationPrimaryCredentials.set( + def addProductionCredential(credential: Credential): Application = + applicationProductionCredentials.set( application, - applicationPrimaryCredentials.get(application) :+ credential + applicationProductionCredentials.get(application) :+ credential ) - def getSecondaryScopes: Seq[Scope] = - applicationSecondaryScopes.get(application) + def getTestScopes: Seq[Scope] = + applicationTestScopes.get(application) - def setSecondaryScopes(scopes: Seq[Scope]): Application = - applicationSecondaryScopes.set(application, scopes) + def setTestScopes(scopes: Seq[Scope]): Application = + applicationTestScopes.set(application, scopes) - def addSecondaryScope(scope: Scope): Application = - applicationSecondaryScopes.set( + def addTestScope(scope: Scope): Application = + applicationTestScopes.set( application, - applicationSecondaryScopes.get(application) :+ scope + applicationTestScopes.get(application) :+ scope ) - def getSecondaryMasterCredential: Option[Credential] = - applicationSecondaryCredentials.get(application) + def getTestMasterCredential: Option[Credential] = + applicationTestCredentials.get(application) .sortWith((a, b) => a.created.isAfter(b.created)) .headOption - def getSecondaryCredentials: Seq[Credential] = - applicationSecondaryCredentials.get(application) + def getTestCredentials: Seq[Credential] = + applicationTestCredentials.get(application) - def setSecondaryCredentials(credentials: Seq[Credential]): Application = - applicationSecondaryCredentials.set(application, credentials) + def setTestCredentials(credentials: Seq[Credential]): Application = + applicationTestCredentials.set(application, credentials) - def addSecondaryCredential(credential: Credential): Application = - applicationSecondaryCredentials.set( + def addTestCredential(credential: Credential): Application = + applicationTestCredentials.set( application, - applicationSecondaryCredentials.get(application) :+ credential + applicationTestCredentials.get(application) :+ credential ) def getCredentialsFor(environmentName: EnvironmentName): Seq[Credential] = { environmentName match { - case Primary => application.getPrimaryCredentials - case Secondary => application.getSecondaryCredentials + case Production => application.getProductionCredentials + case Test => application.getTestCredentials + case _ => throw new IllegalArgumentException(s"Unsupported environment: $environmentName") // TODO } } @@ -209,7 +211,7 @@ object ApplicationLenses { def getRequiredScopeNames: Set[String] = { application - .getSecondaryScopes + .getTestScopes .map(_.name) .toSet } diff --git a/app/models/application/EnvironmentName.scala b/app/models/application/EnvironmentName.scala index f33ee726..1bfe5a91 100644 --- a/app/models/application/EnvironmentName.scala +++ b/app/models/application/EnvironmentName.scala @@ -20,12 +20,14 @@ import models.{Enumerable, WithName} sealed trait EnvironmentName -case object Primary extends WithName("primary") with EnvironmentName -case object Secondary extends WithName("secondary") with EnvironmentName +case object Production extends WithName("primary") with EnvironmentName +case object PreProduction extends WithName("pre-production") with EnvironmentName +case object Test extends WithName("secondary") with EnvironmentName +case object Development extends WithName("development") with EnvironmentName object EnvironmentName extends Enumerable.Implicits { - val values: Seq[EnvironmentName] = Seq(Primary, Secondary) + val values: Seq[EnvironmentName] = Seq(Production, PreProduction, Test, Development) implicit val enumerable: Enumerable[EnvironmentName] = Enumerable(values.map(value => value.toString -> value)*) diff --git a/app/models/application/Environments.scala b/app/models/application/Environments.scala index 7fb2d559..7895daf7 100644 --- a/app/models/application/Environments.scala +++ b/app/models/application/Environments.scala @@ -21,7 +21,13 @@ import play.api.libs.json.{Format, Json} case class Environments( primary:Environment, secondary:Environment -) +) { + + def toNewEnvironments: Map[EnvironmentName, Environment] = { + Map(Production -> primary, Test -> secondary) + } + +} object Environments { diff --git a/app/services/CurlCommandService.scala b/app/services/CurlCommandService.scala index 4830fc0d..bdfb2ddf 100644 --- a/app/services/CurlCommandService.scala +++ b/app/services/CurlCommandService.scala @@ -54,7 +54,7 @@ class CurlCommandService extends Logging { private def getCommonHeaders(application: Application): Map[String,String] = { val maybeAuthHeader = for { - credential <- application.getSecondaryMasterCredential + credential <- application.getTestMasterCredential secret <- credential.clientSecret credentials = s"${credential.clientId}:$secret" encodedCredentials = getEncoder.encodeToString(credentials.getBytes) diff --git a/app/viewmodels/application/ApplicationApi.scala b/app/viewmodels/application/ApplicationApi.scala index 2f396622..d3c69ada 100644 --- a/app/viewmodels/application/ApplicationApi.scala +++ b/app/viewmodels/application/ApplicationApi.scala @@ -18,7 +18,7 @@ package viewmodels.application import models.{Enumerable, WithName} import models.api.{ApiDetail, EndpointMethod} -import models.application.{Api, Application, EnvironmentName, Primary, Secondary, SelectedEndpoint} +import models.application.{Api, Application, EnvironmentName, Production, Test, SelectedEndpoint} import models.application.ApplicationLenses.ApplicationLensOps import play.api.libs.json.{Format, Json, Writes} @@ -52,8 +52,9 @@ object ApplicationEndpointAccess extends Enumerable.Implicits{ ): ApplicationEndpointAccess = { val scopes = environmentName match { - case Primary => application.getPrimaryScopes - case Secondary => application.getSecondaryScopes + case Production => application.getProductionScopes + case Test => application.getTestScopes + case _ => throw new IllegalArgumentException(s"Unsupported environment: $environmentName") // TODO } if (endpointMethod.scopes.toSet.subsetOf(scopes.map(_.name).toSet)) { diff --git a/app/views/admin/ManageApplicationsView.scala.html b/app/views/admin/ManageApplicationsView.scala.html index 151c126f..c50bef09 100644 --- a/app/views/admin/ManageApplicationsView.scala.html +++ b/app/views/admin/ManageApplicationsView.scala.html @@ -21,6 +21,7 @@ @import views.html.components.SearchBox @import views.html.components.IconsLink @import models.application.Application +@import models.application.ApplicationLenses._ @import views.html.helper.CSPNonce @import views.html.components.Paginator @@ -41,7 +42,7 @@ } @clientIds(application: Application) = @{ - Seq(application.environments.primary, application.environments.secondary).flatMap(_.credentials.map(_.clientId)).mkString(",") + Seq(application.getProductionCredentials, application.getTestCredentials).flatten.map(_.clientId).mkString(",") } @layout( diff --git a/app/views/application/EnvironmentAndCredentialsView.scala.html b/app/views/application/EnvironmentAndCredentialsView.scala.html index 8f3cc40f..f86d3524 100644 --- a/app/views/application/EnvironmentAndCredentialsView.scala.html +++ b/app/views/application/EnvironmentAndCredentialsView.scala.html @@ -14,6 +14,7 @@ * limitations under the License. *@ +@import config.Environments @import models.application._ @import models.application.ApplicationLenses._ @import models.user.UserModel @@ -29,7 +30,7 @@ iconsLink: IconsLink ) -@(application: Application, user: UserModel, apiHubGuideUrl: String)(implicit request: Request[?], messages: Messages) +@(application: Application, user: UserModel, apiHubGuideUrl: String, environments: Environments)(implicit request: Request[?], messages: Messages) @sortCredentials(credentials: Seq[Credential]) = @{ credentials.sortWith((a, b) => a.created.isBefore(b.created)) @@ -38,14 +39,14 @@ @canDeleteCredentials(environmentName: EnvironmentName) = @{ application.getCredentialsFor(environmentName).size > 1 && (user.permissions.canSupport || - (application.hasTeamMember(user) && (environmentName == Secondary || user.permissions.isPrivileged))) + (application.hasTeamMember(user) && (environmentName == Test || user.permissions.isPrivileged))) } @rolesGuidanceLink() = { @messages("environmentAndCredentials.notPrivileged.additional3") } -@developmentTab() = { +@testTab() = {
@messages("environmentAndCredentials.clientId"): @@ -90,7 +91,7 @@
@messages("environmentAndCredentials.noCredentials")
} else { - @for(credential <- application.getPrimaryCredentials) { + @for(credential <- application.getProductionCredentials) {@messages("environmentAndCredentials.clientId"): @@ -164,7 +165,7 @@