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

MTDSA-25842 - copy shared code across #181

Merged
merged 2 commits into from
Sep 24, 2024
Merged
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
176 changes: 176 additions & 0 deletions app/shared/config/AppConfig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright 2023 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 shared.config

import cats.data.Validated
import cats.implicits.catsSyntaxValidatedId
import com.typesafe.config.Config
import play.api.{ConfigLoader, Configuration}
import shared.config.Deprecation.{Deprecated, NotDeprecated}
import shared.routing.Version
import uk.gov.hmrc.auth.core.ConfidenceLevel
import uk.gov.hmrc.play.bootstrap.config.ServicesConfig

import java.time.LocalDateTime
import java.time.format.{DateTimeFormatter, DateTimeFormatterBuilder}
import java.time.temporal.ChronoField
import javax.inject.{Inject, Singleton}

@Singleton
class AppConfig @Inject() (config: ServicesConfig, protected[config] val configuration: Configuration) {
// API name
def appName: String = config.getString("appName")

// MTD ID Lookup Config
def mtdIdBaseUrl: String = config.baseUrl("mtd-id-lookup")

private def serviceKeyFor(serviceName: String) = s"microservice.services.$serviceName"

protected def downstreamConfig(serviceName: String): DownstreamConfig = {
val baseUrl = config.baseUrl(serviceName)

val serviceKey = serviceKeyFor(serviceName)

val env = config.getString(s"$serviceKey.env")
val token = config.getString(s"$serviceKey.token")
val environmentHeaders = configuration.getOptional[Seq[String]](s"$serviceKey.environmentHeaders")

DownstreamConfig(baseUrl, env, token, environmentHeaders)
}

protected def basicAuthDownstreamConfig(serviceName: String): BasicAuthDownstreamConfig = {
val baseUrl = config.baseUrl(serviceName)

val serviceKey = serviceKeyFor(serviceName)

val env = config.getString(s"$serviceKey.env")
val clientId = config.getString(s"$serviceKey.clientId")
val clientSecret = config.getString(s"$serviceKey.clientSecret")
val environmentHeaders = configuration.getOptional[Seq[String]](s"$serviceKey.environmentHeaders")

BasicAuthDownstreamConfig(baseUrl, env, clientId, clientSecret, environmentHeaders)
}

def desDownstreamConfig: DownstreamConfig = downstreamConfig("des")
def ifsDownstreamConfig: DownstreamConfig = downstreamConfig("ifs")
def tysIfsDownstreamConfig: DownstreamConfig = downstreamConfig("tys-ifs")
def hipDownstreamConfig: BasicAuthDownstreamConfig = basicAuthDownstreamConfig("hip")

// API Config
def apiGatewayContext: String = config.getString("api.gateway.context")
def confidenceLevelConfig: ConfidenceLevelConfig = configuration.get[ConfidenceLevelConfig](s"api.confidence-level-check")

def apiStatus(version: Version): String = config.getString(s"api.$version.status")

def featureSwitchConfig: Configuration = configuration.getOptional[Configuration](s"feature-switch").getOrElse(Configuration.empty)

def endpointsEnabled(version: String): Boolean = config.getBoolean(s"api.$version.endpoints.enabled")

/** Like endpointsEnabled, but will return false if version doesn't exist.
*/
def safeEndpointsEnabled(version: String): Boolean =
configuration
.getOptional[Boolean](s"api.$version.endpoints.enabled")
.getOrElse(false)

def endpointsEnabled(version: Version): Boolean = config.getBoolean(s"api.$version.endpoints.enabled")

def apiVersionReleasedInProduction(version: String): Boolean = config.getBoolean(s"api.$version.endpoints.api-released-in-production")

def allowRequestCannotBeFulfilledHeader(version: Version): Boolean =
config.getBoolean(s"api.$version.endpoints.allow-request-cannot-be-fulfilled-header")

def endpointReleasedInProduction(version: String, name: String): Boolean = {
val versionReleasedInProd = apiVersionReleasedInProduction(version)
val path = s"api.$version.endpoints.released-in-production.$name"

val conf = configuration.underlying
if (versionReleasedInProd && conf.hasPath(path)) config.getBoolean(path) else versionReleasedInProd
}

def endpointAllowsSupportingAgents(endpointName: String): Boolean =
supportingAgentEndpoints.getOrElse(endpointName, false)

lazy private val supportingAgentEndpoints: Map[String, Boolean] =
configuration
.getOptional[Map[String, Boolean]]("api.supporting-agent-endpoints")
.getOrElse(Map.empty)

def apiDocumentationUrl: String =
configuration
.get[Option[String]]("api.documentation-url")
.getOrElse(s"https://developer.service.hmrc.gov.uk/api-documentation/docs/api/service/$appName")

private val DATE_FORMATTER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
.parseDefaulting(ChronoField.HOUR_OF_DAY, 23)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 59)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 59)
.toFormatter()

def deprecationFor(version: Version): Validated[String, Deprecation] = {
val isApiDeprecated: Boolean = apiStatus(version) == "DEPRECATED"

val deprecatedOn: Option[LocalDateTime] =
configuration
.getOptional[String](s"api.$version.deprecatedOn")
.map(value => LocalDateTime.parse(value, DATE_FORMATTER))

val sunsetDate: Option[LocalDateTime] =
configuration
.getOptional[String](s"api.$version.sunsetDate")
.map(value => LocalDateTime.parse(value, DATE_FORMATTER))

val isSunsetEnabled: Boolean =
configuration.getOptional[Boolean](s"api.$version.sunsetEnabled").getOrElse(true)

if (isApiDeprecated) {
(deprecatedOn, sunsetDate, isSunsetEnabled) match {
case (Some(dO), Some(sD), true) =>
if (sD.isAfter(dO))
Deprecated(dO, Some(sD)).valid
else
s"sunsetDate must be later than deprecatedOn date for a deprecated version $version".invalid
case (Some(dO), None, true) => Deprecated(dO, Some(dO.plusMonths(6).plusDays(1))).valid
case (Some(dO), _, false) => Deprecated(dO, None).valid
case _ => s"deprecatedOn date is required for a deprecated version $version".invalid
}

} else NotDeprecated.valid

}

}

case class ConfidenceLevelConfig(confidenceLevel: ConfidenceLevel, definitionEnabled: Boolean, authValidationEnabled: Boolean)

object ConfidenceLevelConfig {

implicit val configLoader: ConfigLoader[ConfidenceLevelConfig] = (rootConfig: Config, path: String) => {
val config = rootConfig.getConfig(path)
val confidenceLevelInt = config.getInt("confidence-level")

ConfidenceLevelConfig(
confidenceLevel = ConfidenceLevel
.fromInt(confidenceLevelInt)
.get, // let the Exception propagate if thrown by fromInt
definitionEnabled = config.getBoolean("definition.enabled"),
authValidationEnabled = config.getBoolean("auth-validation.enabled")
)
}

}
28 changes: 28 additions & 0 deletions app/shared/config/Deprecation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 HM Revenue & Customs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package shared.config

import java.time.LocalDateTime

sealed trait Deprecation

object Deprecation {
case object NotDeprecated extends Deprecation

case class Deprecated(deprecatedOn: LocalDateTime, sunsetDate: Option[LocalDateTime]) extends Deprecation

}
32 changes: 32 additions & 0 deletions app/shared/config/DownstreamConfig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023 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 shared.config

case class DownstreamConfig(
baseUrl: String,
env: String,
token: String,
environmentHeaders: Option[Seq[String]]
)

case class BasicAuthDownstreamConfig(
baseUrl: String,
env: String,
clientId: String,
clientSecret: String,
environmentHeaders: Option[Seq[String]]
)
42 changes: 42 additions & 0 deletions app/shared/config/FeatureSwitches.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2023 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 shared.config

import play.api.Configuration

trait FeatureSwitches {

protected val featureSwitchConfig: Configuration

def isEnabled(feature: String): Boolean = isConfigTrue(feature + ".enabled")

def isReleasedInProduction(feature: String): Boolean = isConfigTrue(feature + ".released-in-production")

private def isConfigTrue(key: String): Boolean = featureSwitchConfig.getOptional[Boolean](key).getOrElse(true)

def supportingAgentsAccessControlEnabled: Boolean = isEnabled("supporting-agents-access-control")

}

/** This is just here for non-typesafe usage such as Handlebars using OasFeatureRewriter. In most cases, should use the API-specific
* XyzFeatureSwitches class instead.
*/
case class ConfigFeatureSwitches private (protected val featureSwitchConfig: Configuration) extends FeatureSwitches

object ConfigFeatureSwitches {
def apply()(implicit appConfig: AppConfig): ConfigFeatureSwitches = ConfigFeatureSwitches(appConfig.featureSwitchConfig)
}
49 changes: 49 additions & 0 deletions app/shared/config/rewriters/ApiVersionTitleRewriter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2023 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 shared.config.rewriters

import shared.config.AppConfig
import shared.config.rewriters.DocumentationRewriters.CheckAndRewrite

import javax.inject.{Inject, Singleton}

@Singleton class ApiVersionTitleRewriter @Inject() (appConfig: AppConfig) {

private val rewriteTitleRegex = ".*(title: [\"]?)(.*)".r

val rewriteApiVersionTitle: CheckAndRewrite = CheckAndRewrite(
check = (version, filename) => {
filename == "application.yaml" &&
!appConfig.apiVersionReleasedInProduction(version)
},
rewrite = (_, _, yaml) => {
val maybeLine = rewriteTitleRegex.findFirstIn(yaml)
maybeLine
.collect {
case line if !line.toLowerCase.contains("[test only]") =>
val title = line
.split("title: ")(1)
.replace("\"", "")

val replacement = s""" title: "$title [test only]""""
rewriteTitleRegex.replaceFirstIn(yaml, replacement)
}
.getOrElse(yaml)
}
)

}
53 changes: 53 additions & 0 deletions app/shared/config/rewriters/DocumentationRewriters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2023 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 shared.config.rewriters

import controllers.Rewriter
import shared.config.rewriters.DocumentationRewriters.CheckAndRewrite

import javax.inject.{Inject, Singleton}

@Singleton class DocumentationRewriters @Inject() (apiVersionTitleRewriter: ApiVersionTitleRewriter,
endpointSummaryRewriter: EndpointSummaryRewriter,
endpointSummaryGroupRewriter: EndpointSummaryGroupRewriter,
oasFeatureRewriter: OasFeatureRewriter) {

lazy val rewriteables: Seq[CheckAndRewrite] =
List(
apiVersionTitleRewriter.rewriteApiVersionTitle,
endpointSummaryRewriter.rewriteEndpointSummary,
endpointSummaryGroupRewriter.rewriteGroupedEndpointSummaries,
oasFeatureRewriter.rewriteOasFeature
)

}

object DocumentationRewriters {

trait CheckRewrite {
def apply(version: String, filename: String): Boolean
}

case class CheckAndRewrite(check: CheckRewrite, rewrite: Rewriter) {

def maybeRewriter(version: String, filename: String): Option[Rewriter] =
if (check(version, filename)) Some(rewrite) else None

val asTuple: (CheckRewrite, Rewriter) = (check, rewrite)
}

}
Loading