190 lines
6.9 KiB
Kotlin
190 lines
6.9 KiB
Kotlin
package com.restapi.domain
|
|
|
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
import com.fasterxml.jackson.databind.SerializationFeature
|
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
import com.restapi.config.AppConfig.Companion.appConfig
|
|
import com.restapi.config.AuthUser
|
|
import io.ebean.Database
|
|
import io.ebean.DatabaseFactory
|
|
import io.ebean.config.CurrentTenantProvider
|
|
import io.ebean.config.CurrentUserProvider
|
|
import io.ebean.config.DatabaseConfig
|
|
import io.ebean.config.TenantMode
|
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
|
import org.bouncycastle.util.io.pem.PemReader
|
|
import org.jose4j.jwk.JsonWebKey
|
|
import org.jose4j.jwk.PublicJsonWebKey
|
|
import org.jose4j.jwk.RsaJsonWebKey
|
|
import org.jose4j.jwk.RsaJwkGenerator
|
|
import org.jose4j.jws.AlgorithmIdentifiers
|
|
import org.jose4j.jws.JsonWebSignature
|
|
import org.slf4j.LoggerFactory
|
|
import redis.clients.jedis.JedisPooled
|
|
import java.io.StringReader
|
|
import java.io.StringWriter
|
|
import java.security.KeyFactory
|
|
import java.security.interfaces.RSAPrivateKey
|
|
import java.security.interfaces.RSAPublicKey
|
|
import java.security.spec.PKCS8EncodedKeySpec
|
|
import java.security.spec.X509EncodedKeySpec
|
|
import java.util.*
|
|
import kotlin.jvm.optionals.getOrDefault
|
|
|
|
|
|
object Session {
|
|
private val KEY_ID = "${appConfig.appName()}-KEY"
|
|
private val logger = LoggerFactory.getLogger("session")
|
|
private val currentUser = object : ThreadLocal<AuthUser>() {
|
|
override fun initialValue(): AuthUser {
|
|
return AuthUser("", "", emptyList<String>())
|
|
}
|
|
}
|
|
|
|
fun setAuthorizedUser(a: AuthUser) = currentUser.set(a)
|
|
|
|
//if not passed in ENV, then we shall generate and print
|
|
private fun makeRsaJsonWebKey(publicKey: String, privateKey: String): RsaJsonWebKey {
|
|
logger.warn("making KeyPair from Config \n$publicKey\n\n$privateKey")
|
|
val newPublicKey = readPublicKey(publicKey)
|
|
val newPrivateKey = readPrivateKey(privateKey)
|
|
val rsa = PublicJsonWebKey.Factory.newPublicJwk(newPublicKey) as RsaJsonWebKey
|
|
rsa.privateKey = newPrivateKey
|
|
rsa.keyId = KEY_ID
|
|
return rsa
|
|
}
|
|
|
|
private val keyFactory = KeyFactory.getInstance("RSA")
|
|
|
|
private fun readPublicKey(file: String): RSAPublicKey {
|
|
StringReader(file).use { keyReader ->
|
|
PemReader(keyReader).use { pemReader ->
|
|
val pemObject = pemReader.readPemObject()
|
|
val content = pemObject.content
|
|
val pubKeySpec = X509EncodedKeySpec(content)
|
|
return keyFactory.generatePublic(pubKeySpec) as RSAPublicKey
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun readPrivateKey(file: String): RSAPrivateKey {
|
|
StringReader(file).use { keyReader ->
|
|
PemReader(keyReader).use { pemReader ->
|
|
val pemObject = pemReader.readPemObject()
|
|
val content = pemObject.content
|
|
val privKeySpec = PKCS8EncodedKeySpec(content)
|
|
return keyFactory.generatePrivate(privKeySpec) as RSAPrivateKey
|
|
}
|
|
}
|
|
}
|
|
|
|
private val keypair: RsaJsonWebKey by lazy {
|
|
if (appConfig.privateKey().isPresent && appConfig.publicKey().isPresent) {
|
|
makeRsaJsonWebKey(
|
|
appConfig.publicKey().get(),
|
|
appConfig.privateKey().get()
|
|
)
|
|
|
|
} else {
|
|
RsaJwkGenerator.generateJwk(2048).apply {
|
|
keyId = KEY_ID
|
|
logger.warn("Save this PrivateKey/PublicKey and pass it in the Config File from next time")
|
|
StringWriter().use { s ->
|
|
JcaPEMWriter(s).use { p ->
|
|
p.writeObject(privateKey)
|
|
}
|
|
logger.warn("Private Key\n${s.toString()}")
|
|
|
|
}
|
|
|
|
StringWriter().use { s ->
|
|
JcaPEMWriter(s).use { p ->
|
|
p.writeObject(publicKey)
|
|
}
|
|
|
|
logger.warn("Public Key\n${s.toString()}")
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fun signPayload(payload: String): String {
|
|
|
|
// Create a new JsonWebSignature
|
|
val jws = JsonWebSignature()
|
|
|
|
|
|
// Set the payload, or signed content, on the JWS object
|
|
jws.setPayload(payload)
|
|
|
|
|
|
// Set the signature algorithm on the JWS that will integrity protect the payload
|
|
jws.algorithmHeaderValue = AlgorithmIdentifiers.RSA_PSS_USING_SHA512
|
|
|
|
|
|
// Set the signing key on the JWS
|
|
// Note that your application will need to determine where/how to get the key
|
|
// and here we just use an example from the JWS spec
|
|
jws.setKey(keypair.privateKey)
|
|
|
|
|
|
// Sign the JWS and produce the compact serialization or complete JWS representation, which
|
|
// is a string consisting of three dot ('.') separated base64url-encoded
|
|
// parts in the form Header.Payload.Signature
|
|
return jws.getCompactSerialization()
|
|
|
|
|
|
}
|
|
|
|
private val sc = DatabaseConfig().apply {
|
|
loadFromProperties(Properties().apply {
|
|
setProperty("datasource.db.username", appConfig.dbUser())
|
|
setProperty("datasource.db.password", appConfig.dbPass())
|
|
setProperty("datasource.db.url", appConfig.dbUrl())
|
|
setProperty("ebean.migration.run", appConfig.dbRunMigration().toString())
|
|
})
|
|
tenantMode = TenantMode.PARTITION
|
|
currentTenantProvider = CurrentTenantProvider { currentUser.get().tenant }
|
|
currentUserProvider = CurrentUserProvider { currentUser.get().userName }
|
|
}
|
|
|
|
val database: Database = DatabaseFactory.create(sc)
|
|
val objectMapper = jacksonObjectMapper().apply {
|
|
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
|
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
|
|
findAndRegisterModules()
|
|
}
|
|
|
|
fun currentUser() = currentUser.get().userName
|
|
fun currentTenant() = currentUser.get().tenant
|
|
fun currentRoles() = currentUser.get().roles
|
|
fun jwk() = keypair.toParams(JsonWebKey.OutputControlLevel.PUBLIC_ONLY)
|
|
|
|
fun Database.findByEntityAndId(entity: String, id: String): DataModel {
|
|
return find(DataModel::class.java)
|
|
.where()
|
|
.eq("uniqueIdentifier", id)
|
|
.eq("entityName", entity)
|
|
.findOne() ?: throw DataNotFoundException
|
|
}
|
|
|
|
private fun seqName(entity: String) = "sequence_$entity"
|
|
fun creatSeq(entity: String): Int {
|
|
return database.sqlUpdate("CREATE SEQUENCE IF NOT EXISTS ${seqName(entity)} START 1;").execute()
|
|
}
|
|
|
|
fun nextUniqId(entity: String): String {
|
|
val s = database
|
|
.sqlQuery("SELECT nextval('${seqName(entity)}');")
|
|
.findOne()?.getLong("nextval") ?: throw DataNotFoundException
|
|
return String.format("%s-%s", entity, "$s".padStart(10, '0'))
|
|
}
|
|
|
|
val redis = JedisPooled(appConfig.redisUri().getOrDefault("redis://localhost:6739/0"))
|
|
|
|
}
|
|
|
|
object DataNotFoundException : Exception() {
|
|
private fun readResolve(): Any = DataNotFoundException
|
|
} |