Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HIPP-1577: Investigate configurable environments #394

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions app/config/Environments.scala
Original file line number Diff line number Diff line change
@@ -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

}
3 changes: 2 additions & 1 deletion app/config/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/application/AddCredentialController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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 =>
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,20 +38,21 @@ 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 {
implicit request =>
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))
}
Expand All @@ -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[?]) = {
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/helpers/ApplicationApiBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
)
}
Expand Down
8 changes: 7 additions & 1 deletion app/models/application/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
124 changes: 63 additions & 61 deletions app/models/application/ApplicationLenses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]](
Expand All @@ -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]](
Expand All @@ -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
}
}

Expand Down Expand Up @@ -209,7 +211,7 @@ object ApplicationLenses {

def getRequiredScopeNames: Set[String] = {
application
.getSecondaryScopes
.getTestScopes
.map(_.name)
.toSet
}
Expand Down
8 changes: 5 additions & 3 deletions app/models/application/EnvironmentName.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)*)
Expand Down
Loading