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() { override fun initialValue(): AuthUser { return AuthUser("", "", emptyList()) } } 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 }