app/conf/Config.scala (116 lines of code) (raw):
package conf
import com.google.auth.oauth2.ServiceAccountCredentials
import com.gu.googleauth.{
AntiForgeryChecker,
GoogleAuthConfig,
GoogleGroupChecker
}
import com.gu.janus.JanusConfig
import com.gu.janus.model._
import com.gu.play.secretrotation.SnapshotProvider
import models._
import play.api.Configuration
import java.io.{File, FileInputStream}
import scala.util.{Failure, Success, Try}
object Config {
def roleArn(awsAccountAuthConfigKey: String, config: Configuration): String =
requiredString(config, s"federation.$awsAccountAuthConfigKey.aws.roleArn")
// extract aws account ID from Role ARN
private val AwsAccountId = """arn:aws:iam::(\d+):role/.+""".r
def accountNumber(
awsAccountAuthConfigKey: String,
config: Configuration
): Try[String] = {
for {
role <- Try(roleArn(awsAccountAuthConfigKey, config))
accountNumber <- role match {
case AwsAccountId(accountId) => Success(accountId)
case _ =>
Failure(
new JanusConfigurationException(
s"Could not extract account number from role ARN $role",
s"federation.$awsAccountAuthConfigKey.aws.roleArn"
)
)
}
} yield accountNumber
}
def validateAccountConfig(
janusData: JanusData,
config: Configuration
): AccountConfigStatus = {
val janusAccountKeys = janusData.accounts.map(_.authConfigKey)
Try(config.get[Configuration]("federation")).fold(
{ err =>
if (janusAccountKeys.isEmpty) ConfigSuccess
else FederationConfigError(err)
},
{ federationConfig =>
val configAccountKeys = federationConfig.subKeys
if (configAccountKeys == janusAccountKeys) {
ConfigSuccess
} else if (configAccountKeys.subsetOf(janusAccountKeys)) {
// config is missing for one or more janusData accounts
ConfigError(janusAccountKeys -- configAccountKeys)
} else {
// config contains unnecessary entries for accounts that are not in the janusData
ConfigWarn(configAccountKeys -- janusAccountKeys)
}
}
)
}
def host(config: Configuration): String = {
requiredString(config, "host")
}
def janusData(config: Configuration): JanusData = {
config
.getOptional[String]("data.fileLocation")
.map(filePath => JanusConfig.load(new File(filePath)))
.getOrElse(JanusConfig.load("janusData.conf"))
}
def googleSettings(
config: Configuration,
secretStateSupplier: SnapshotProvider
): GoogleAuthConfig = {
val clientId = requiredString(config, "auth.google.clientId")
val clientSecret = requiredString(config, "auth.google.clientSecret")
val domain = requiredString(config, "auth.domain")
val redirectUrl = s"${requiredString(config, "host")}/oauthCallback"
GoogleAuthConfig(
clientId = clientId,
clientSecret = clientSecret,
redirectUrl = redirectUrl,
domains = List(domain),
antiForgeryChecker = AntiForgeryChecker(secretStateSupplier)
)
}
def googleGroupChecker(config: Configuration): GoogleGroupChecker = {
val twoFAUser = requiredString(config, "auth.google.2faUser")
val serviceAccountCertPath =
requiredString(config, "auth.google.serviceAccountCertPath")
val credentials: ServiceAccountCredentials = {
val jsonCertStream =
Try(new FileInputStream(serviceAccountCertPath))
.getOrElse(
throw new JanusConfigurationException(
s"Could not load service account JSON",
serviceAccountCertPath
)
)
ServiceAccountCredentials.fromStream(jsonCertStream)
}
new GoogleGroupChecker(twoFAUser, credentials)
}
def twoFAGroup(config: Configuration): String = {
requiredString(config, "auth.google.2faGroupId")
}
private def requiredString(config: Configuration, key: String): String = {
config.getOptional[String](key).getOrElse {
throw new JanusConfigurationException(
s"Missing required config property",
key
)
}
}
class JanusConfigurationException(message: String, location: String)
extends Throwable(
s"$message at $location"
)
}