add more stuff

This commit is contained in:
gowthaman.b 2024-01-05 12:08:27 +05:30
parent 82fb57bd85
commit d506078804
7 changed files with 306 additions and 22 deletions

9
.idea/misc.xml generated
View File

@ -4,10 +4,13 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="NimToolchainService"> <component name="MavenProjectsManager">
<option name="rootPaths"> <option name="originalFiles">
<list /> <list>
<option value="$PROJECT_DIR$/../txn_workflow/pom.xml" />
</list>
</option> </option>
<option name="workspaceImportForciblyTurnedOn" value="true" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />

1
.idea/vcs.xml generated
View File

@ -3,5 +3,6 @@
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../rmc-modules-app" vcs="Git" /> <mapping directory="$PROJECT_DIR$/../rmc-modules-app" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../txn_workflow" vcs="Git" />
</component> </component>
</project> </project>

View File

@ -5,12 +5,15 @@ import com.restapi.config.*
import com.restapi.config.AppConfig.Companion.appConfig import com.restapi.config.AppConfig.Companion.appConfig
import com.restapi.config.Auth.validateAuthToken import com.restapi.config.Auth.validateAuthToken
import com.restapi.controllers.Entities import com.restapi.controllers.Entities
import com.restapi.domain.AnonSession
import com.restapi.domain.DataNotFoundException import com.restapi.domain.DataNotFoundException
import com.restapi.domain.Session
import com.restapi.domain.Session.currentTenant import com.restapi.domain.Session.currentTenant
import com.restapi.domain.Session.currentUser import com.restapi.domain.Session.currentUser
import com.restapi.domain.Session.objectMapper import com.restapi.domain.Session.objectMapper
import com.restapi.domain.Session.setAuthorizedUser import com.restapi.domain.Session.setAuthorizedUser
import com.restapi.domain.Session.signPayload import com.restapi.domain.Session.signPayload
import com.restapi.domain.TenantModel
import io.ebean.DataIntegrityException import io.ebean.DataIntegrityException
import io.ebean.DuplicateKeyException import io.ebean.DuplicateKeyException
import io.javalin.Javalin import io.javalin.Javalin
@ -60,6 +63,43 @@ fun main(args: Array<String>) {
} }
.routes { .routes {
path("/auth") { path("/auth") {
get("/session") {
//a simple session to keep track of anon users
val at = it.getAuthHeader()
val tenant = Session.database.find(TenantModel::class.java)
.where()
.eq("domain",it.host())
.findOne() ?: throw UnauthorizedResponse()
if(at == null){
//new session
val s = AnonSession().apply {
sessionId = UUID.randomUUID().toString()
firstSeenAt = LocalDateTime.now()
lastSeenAt = LocalDateTime.now()
tenantId = tenant.name
headerMap = it.headerMap()
}
Session.database.save(s)
it.json(s)
} else {
val s = Session.database.find(AnonSession::class.java)
.where()
.eq("sessionId", at)
.findOne() ?: throw UnauthorizedResponse()
Session.database.save(
s.apply {
lastSeenAt = LocalDateTime.now()
headerMap = it.headerMap()
}
)
it.json(s)
}
}
get("/endpoint", Auth::endPoint) get("/endpoint", Auth::endPoint)
get("/init", Auth::init) get("/init", Auth::init)
get("/code", Auth::code) get("/code", Auth::code)
@ -74,10 +114,10 @@ fun main(args: Array<String>) {
TimeUnit.MINUTES TimeUnit.MINUTES
) )
val authToken = ctx.header("Authorization") val authToken = ctx.getAuthHeader() ?: throw UnauthorizedResponse()
?.replace("Bearer ", "")
?.replace("Bearer: ", "")
?.trim() ?: throw UnauthorizedResponse() //there are 2 scenarios, 1) auth user for admin 2) non user for flow, we need to handle both
setAuthorizedUser(validateAuthToken(authToken = authToken)) setAuthorizedUser(validateAuthToken(authToken = authToken))
@ -96,11 +136,12 @@ fun main(args: Array<String>) {
it.header("X-Signature", signPayload(outEncoded)) it.header("X-Signature", signPayload(outEncoded))
if (appConfig.enforcePayloadEncryption()) { if (appConfig.enforcePayloadEncryption()) {
//todo:, encrypt and set the response back to user //todo: encrypt and send the response back to user
} }
} }
path("/api") { path("/api") {
post("/audit/{action}") { post("/audit/{action}") {
logger.warn("User ${currentUser()} of tenant ${currentTenant()} has performed ${it.pathParam("action")} @ ${LocalDateTime.now()}") logger.warn("User ${currentUser()} of tenant ${currentTenant()} has performed ${it.pathParam("action")} @ ${LocalDateTime.now()}")
it.json(mapOf("status" to true)) it.json(mapOf("status" to true))
@ -132,3 +173,8 @@ fun main(args: Array<String>) {
.start(appConfig.portNumber()) .start(appConfig.portNumber())
} }
private fun Context.getAuthHeader() = header("Authorization")
?.replace("Bearer ", "")
?.replace("Bearer: ", "")
?.trim()

View File

@ -81,6 +81,21 @@ interface AppConfig {
@Key("app.security.public_key") @Key("app.security.public_key")
fun publicKey(): Optional<String> fun publicKey(): Optional<String>
@Key("app.locate.api_key")
fun locateApiKey(): Optional<String>
@Key("app.s3.url")
fun s3Url(): Optional<String>
@Key("app.s3.bucket")
fun s3Bucket(): Optional<String>
@Key("app.s3.access_key")
fun s3AccessKey(): Optional<String>
@Key("app.s3.secret_key")
fun s3SecretKey(): Optional<String>
companion object { companion object {
val appConfig: AppConfig = ConfigFactory.builder().build().create(AppConfig::class.java) val appConfig: AppConfig = ConfigFactory.builder().build().create(AppConfig::class.java)
} }

View File

@ -2,6 +2,7 @@ package com.restapi.config
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import com.restapi.config.AppConfig.Companion.appConfig import com.restapi.config.AppConfig.Companion.appConfig
import com.restapi.domain.AnonSession
import com.restapi.domain.Session import com.restapi.domain.Session
import com.restapi.domain.Session.objectMapper import com.restapi.domain.Session.objectMapper
import io.javalin.http.BadRequestResponse import io.javalin.http.BadRequestResponse
@ -74,6 +75,23 @@ object Auth {
fun validateAuthToken(authToken: String, skipValidate: Boolean = false): AuthUser { fun validateAuthToken(authToken: String, skipValidate: Boolean = false): AuthUser {
//check if this is anon session
val anonSession = Session.database.find(AnonSession::class.java)
.where()
.eq("sessionId", authToken)
.findOne()
if (anonSession != null) {
return AuthUser(
userName = authToken,
tenant = anonSession.tenantId,
roles = emptyList(),
token = authToken,
expiry = LocalDateTime.now().plusDays(1)
)
}
// Validate the JWT and process it to the Claims // Validate the JWT and process it to the Claims
val jwtClaims = if (skipValidate) jwtConsumerSkipValidate.process(authToken) else jwtConsumer.process(authToken) val jwtClaims = if (skipValidate) jwtConsumerSkipValidate.process(authToken) else jwtConsumer.process(authToken)
val userId = jwtClaims.jwtClaims.claimsMap["preferred_username"] as String val userId = jwtClaims.jwtClaims.claimsMap["preferred_username"] as String

View File

@ -5,17 +5,8 @@ import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import io.ebean.Model import io.ebean.Model
import io.ebean.annotation.DbArray import io.ebean.annotation.*
import io.ebean.annotation.DbDefault
import io.ebean.annotation.DbJsonB
import io.ebean.annotation.Index import io.ebean.annotation.Index
import io.ebean.annotation.Platform
import io.ebean.annotation.SoftDelete
import io.ebean.annotation.TenantId
import io.ebean.annotation.WhenCreated
import io.ebean.annotation.WhenModified
import io.ebean.annotation.WhoCreated
import io.ebean.annotation.WhoModified
import java.time.LocalDateTime import java.time.LocalDateTime
import javax.persistence.* import javax.persistence.*
@ -48,7 +39,6 @@ abstract class BaseModel : Model() {
var modifiedAt: LocalDateTime? = null var modifiedAt: LocalDateTime? = null
@WhoCreated @WhoCreated
var createdBy: String = "" var createdBy: String = ""
@ -61,6 +51,7 @@ abstract class BaseModel : Model() {
@DbDefault("0") @DbDefault("0")
var currentApprovalLevel: Int = 0 var currentApprovalLevel: Int = 0
@DbDefault("0") @DbDefault("0")
var requiredApprovalLevels: Int = 0 var requiredApprovalLevels: Int = 0
@ -110,11 +101,17 @@ open class AuditLog : BaseTenantModel() {
var uniqueIdentifier: String = "" var uniqueIdentifier: String = ""
@DbJsonB @DbJsonB
@Index(definition = "create index audit_log_values_idx on audit_log using GIN (data)", platforms = [Platform.POSTGRES]) @Index(
definition = "create index audit_log_values_idx on audit_log using GIN (data)",
platforms = [Platform.POSTGRES]
)
var data: Map<String, Any> = hashMapOf() var data: Map<String, Any> = hashMapOf()
@DbJsonB @DbJsonB
@Index(definition = "create index audit_log_changes_idx on audit_log using GIN (changes)", platforms = [Platform.POSTGRES]) @Index(
definition = "create index audit_log_changes_idx on audit_log using GIN (changes)",
platforms = [Platform.POSTGRES]
)
var changes: Map<String, Any> = hashMapOf() var changes: Map<String, Any> = hashMapOf()
} }
@ -171,7 +168,7 @@ enum class JobType {
@Entity @Entity
@Index(unique = true, name = "sql_unique_id", columnNames = ["entity_name", "sql_id", "tenant_id"]) @Index(unique = true, name = "sql_unique_id", columnNames = ["entity_name", "sql_id", "tenant_id"])
open class SqlModel : BaseTenantModel(){ open class SqlModel : BaseTenantModel() {
@JsonDeserialize(using = SafeStringDeserializer::class) @JsonDeserialize(using = SafeStringDeserializer::class)
var sqlId: String = "" var sqlId: String = ""
@ -181,6 +178,7 @@ open class SqlModel : BaseTenantModel(){
@Column(columnDefinition = "text") @Column(columnDefinition = "text")
var sql: String = "" var sql: String = ""
} }
@Entity @Entity
open class JobModel : BaseTenantModel() { open class JobModel : BaseTenantModel() {
@Index(unique = true) @Index(unique = true)
@ -216,6 +214,19 @@ open class DataModel : BaseTenantModel() {
} }
@Entity
@Index(unique = true, name = "unique_session_id", columnNames = ["session_id"])
open class AnonSession : BaseTenantModel() {
var sessionId: String? = null
var ip: String? = null
var firstSeenAt: LocalDateTime? = null
var lastSeenAt: LocalDateTime? = null
@DbJsonB
var headerMap: Map<String, String> = hashMapOf()
}
class SafeStringDeserializer : JsonDeserializer<String>() { class SafeStringDeserializer : JsonDeserializer<String>() {
private val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$") private val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$")

View File

@ -0,0 +1,190 @@
package com.restapi.integ
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.restapi.config.AppConfig
import io.minio.MinioClient
import io.minio.UploadObjectArgs
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.URIBuilder
import org.apache.http.impl.client.HttpClients
import org.apache.http.message.BasicNameValuePair
import org.apache.http.util.EntityUtils
import org.slf4j.LoggerFactory
import java.io.File
val logger = LoggerFactory.getLogger("ExternalAPI")
object S3 {
private val s3Url = AppConfig.appConfig.s3Url().orElse("s3.amazonaws.com")
private val minioClient: MinioClient by lazy {
MinioClient.builder()
.endpoint("https://$s3Url")
.credentials(
AppConfig.appConfig.s3AccessKey().orElseThrow(),
AppConfig.appConfig.s3SecretKey().orElseThrow()
).build()
}
fun uploadFilesToS3(f: File, name: String): Result<String> {
val objStore: String = AppConfig.appConfig.s3Bucket().orElseThrow()
logger.warn("Trying to upload Object ${f.absolutePath} -> $objStore")
try {
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(objStore)
.`object`(name)
.filename(f.absolutePath)
.build()
)
return Result.success("https://${objStore}.${s3Url}/$name")
} catch (e: Exception) {
logger.warn("Error in file Upload ${f.absolutePath} ==> ${e.message} => Objstore[${objStore}]")
return Result.failure(e)
}
}
}
data class LoqateFullAddrResponse(
@JsonProperty("Items")
val items: List<Item>
) {
data class Item(
@JsonProperty("AdminAreaCode")
val adminAreaCode: String,
@JsonProperty("AdminAreaName")
val adminAreaName: String,
@JsonProperty("Barcode")
val barcode: String,
@JsonProperty("Block")
val block: String,
@JsonProperty("BuildingName")
val buildingName: String,
@JsonProperty("BuildingNumber")
val buildingNumber: String,
@JsonProperty("City")
val city: String,
@JsonProperty("Company")
val company: String,
@JsonProperty("CountryIso2")
val countryIso2: String,
@JsonProperty("CountryIso3")
val countryIso3: String,
@JsonProperty("CountryIsoNumber")
val countryIsoNumber: String,
@JsonProperty("CountryName")
val countryName: String,
@JsonProperty("DataLevel")
val dataLevel: String,
@JsonProperty("Department")
val department: String,
@JsonProperty("District")
val district: String,
@JsonProperty("DomesticId")
val domesticId: String,
@JsonProperty("Field1")
val field1: String,
@JsonProperty("Field10")
val field10: String,
@JsonProperty("Field11")
val field11: String,
@JsonProperty("Field12")
val field12: String,
@JsonProperty("Field13")
val field13: String,
@JsonProperty("Field14")
val field14: String,
@JsonProperty("Field15")
val field15: String,
@JsonProperty("Field16")
val field16: String,
@JsonProperty("Field17")
val field17: String,
@JsonProperty("Field18")
val field18: String,
@JsonProperty("Field19")
val field19: String,
@JsonProperty("Field2")
val field2: String,
@JsonProperty("Field20")
val field20: String,
@JsonProperty("Field3")
val field3: String,
@JsonProperty("Field4")
val field4: String,
@JsonProperty("Field5")
val field5: String,
@JsonProperty("Field6")
val field6: String,
@JsonProperty("Field7")
val field7: String,
@JsonProperty("Field8")
val field8: String,
@JsonProperty("Field9")
val field9: String,
@JsonProperty("Id")
val id: String,
@JsonProperty("Label")
val label: String,
@JsonProperty("Language")
val language: String,
@JsonProperty("LanguageAlternatives")
val languageAlternatives: String,
@JsonProperty("Line1")
val line1: String,
@JsonProperty("Line2")
val line2: String,
@JsonProperty("Line3")
val line3: String,
@JsonProperty("Line4")
val line4: String,
@JsonProperty("Line5")
val line5: String,
@JsonProperty("Neighbourhood")
val neighbourhood: String,
@JsonProperty("POBoxNumber")
val pOBoxNumber: String,
@JsonProperty("PostalCode")
val postalCode: String,
@JsonProperty("Province")
val province: String,
@JsonProperty("ProvinceCode")
val provinceCode: String,
@JsonProperty("ProvinceName")
val provinceName: String,
@JsonProperty("SecondaryStreet")
val secondaryStreet: String,
@JsonProperty("SortingNumber1")
val sortingNumber1: String,
@JsonProperty("SortingNumber2")
val sortingNumber2: String,
@JsonProperty("Street")
val street: String,
@JsonProperty("SubBuilding")
val subBuilding: String,
@JsonProperty("Type")
val type: String
)
}
object LoqateAPI {
//todo: add search?
fun findFullAddr(id: String): LoqateFullAddrResponse {
//https://api.addressy.com/Capture/Interactive/Retrieve/v1.2/
return HttpClients.createDefault().use { h ->
val key = AppConfig.appConfig.locateApiKey().orElseThrow()
val ub = URIBuilder("https://api.addressy.com/Capture/Interactive/Retrieve/v1.2/json3.ws")
.setParameters(
BasicNameValuePair("Key", key),
BasicNameValuePair("Id", id)
).build()
h.execute(HttpGet(ub)).use {
jacksonObjectMapper().readValue(EntityUtils.toString(it.entity))
}
}
}
}