KTOR: How I can find Location Info before call processing? - ktor

I made a custom feature that should check the user's permissions to use the request. Can I monitor request LocationInfo?
Can this looks like that?
if (!User.accessTo.contains(CALL_LOCATION_INFO)){
call.respond(HttpStatusCode.BadRequest) }
That`s my feature code:
data class UserRights(
val haveFullAccess:Boolean,
val accessTo:List<String>,
val canUpdate:Boolean,
val canDelete:Boolean,
val canBan:Boolean,
val canMute:Boolean)
var User = UserRights(false, listOf(""),false,false,false,false)
class RightsChecker(configuration: Configuration) {
val prop = configuration.prop // get snapshot of config into immutable property
class Configuration {
var prop = "value"
}
companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, RightsChecker> {
override val key = AttributeKey<RightsChecker>("RightsChecker")
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): RightsChecker {
val configuration = RightsChecker.Configuration().apply(configure)
val feature = RightsChecker(configuration)
val FilterPhase = PipelinePhase("CallFilter")
pipeline.insertPhaseAfter(ApplicationCallPipeline.Infrastructure, FilterPhase)
pipeline.intercept(FilterPhase) {
val session = call.sessions.get<SessionData>() ?: SessionData(0, "Guest")
when (session.role) {
"Guest" -> User = UserRights(
haveFullAccess = false,
accessTo = listOf(""),
canUpdate = false,
canDelete = false,
canBan = false,
canMute = false)
"User" -> User = UserRights(
haveFullAccess = false,
accessTo = listOf("lUsers"),
canUpdate = false,
canDelete = false,
canBan = false,
canMute = false)
"Admin" -> User = UserRights(
haveFullAccess = true,
accessTo = listOf("lUsers"),
canUpdate = true,
canDelete = true,
canBan = true,
canMute = true)
}
if (!User.accessTo.contains(CALL_LOCATION_INFO)){
call.respond(HttpStatusCode.BadRequest)
}
}
return feature
}
}
}
How you can see, I`m using UserRights data class with rights in it. "accesTo" - is list of location names (format can be changed) what user can use. Feature must just check location name contain in "accesTo" list before request processing.
Thank you for help!
UPD: Locations code:
#Location("/login") data class lLoginData(val email:String, val password: String)
#Location("/users") data class lGetUsers(val page:Int, val limit:Int)
#Location("/users/user") data class lUser(val email: String)
#Location("/users") data class lUpdateData(val userID: Int, val datatype:String, val newData:String)
#Location("/users") data class lRegData(val email: String, val username:String, val userpass:String)

If I understand you correctly, then you simply want to know what route / uri was called.
Here is a small server that answers with the route called.
private val locationKey = AttributeKey("location")
val module = fun Application.() {
install(Routing) {
intercept(ApplicationCallPipeline.Call) {
val location = call.request.local.uri
call.attributes.put(locationKey, location)
}
get("{...}") {
val location = call.attributes[locationKey]
call.respond(location)
}
}
}
As can be seen, I am using call.request.local.uri to get the uri of the call.
When I navigate to http://localhost:5001/hello/route, the server answers with /hello/route.
Does this answer your question?

Related

Encoding a password with argon2 and save on PostgreSQL

I'm trying to encox user password to save in postgresql database. Using kotlin and argon2. I did the functions and in the tests, it works perfectly. But when I save it in the database, and I try to compare the passwords, it always gives an error. Could anyone help?
I created the following functions:
private val argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id, 32, 64)
fun String.encode(): String = argon2.hash(3, 64 * 1024, 1, this.toCharArray())
fun String.checkEncoding(hash: String): Boolean = argon2.verify(hash, this.toCharArray())
and in tests, everything works perfectly
class HashingTest : ShouldSpec({
context("Encoding a string") {
should("encode correctly") {
val dataOne = "S#!sc_%kah"
val encondeOne = dataOne.encode()
val dataTwo = "S#!sc_%kah"
dataTwo.checkEncoding(encondeOne) shouldBe true // works fine!
}
}
})
When I save to the database, and try to compare, I always get an error
//ENTITY
data class User(
override val id: UUID,
val username: String,
val password: String,
) : IEntity
//INSERT SCRITP -> EXPOSED FRAMEWORK
private fun newUser(schema: String, entity: User) {
val table = toUserTable(schema)
table
.insert {
it[id] = entity.id
it[username] = entity.username
it[password] = entity.password.encode()
}
}
//FIND USER
fun findUser(schema: String, model: SignInModel): User? {
val table = toUserTable(schema)
val user = table
.select { table.username eq model.username }
.firstNotNullOfOrNull { toUser(schema, it) }
val verifying = model.password.checkEncoding(user!!.password) // ERROR HERE, ALWAYS RETURNS FALSE
return when (verifying) {
true -> user
false -> null
}
}
I don't know if you used your findUser() and newUser() in a transaction but queries must be called in a transaction like below:
private fun newUser(schema: String, entity: User) {
val table = toUserTable(schema)
transaction {
table
.insert {
it[id] = entity.id
it[username] = entity.username
it[password] = entity.password.encode()
}
}
}
and:
fun findUser(schema: String, model: SignInModel): User? {
val table = toUserTable(schema)
val user = transaction {
table
.select { table.username eq model.username }
.firstNotNullOfOrNull { toUser(schema, it) }
}
val verifying = model.password.checkEncoding(user!!.password)
return when (verifying) {
true -> user
false -> null
}
}

Returning one of different object types from single function in kotlin

I have the following structure at present:
#Entity
#Table(name = "table_app_settings")
data class AppSetting(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "app_setting_id")
val id: Long? = null,
#Column(name = "app_setting_name")
val name: String = "",
#Column(name = "app_setting_value")
var value: String = "",
#Column(name = "app_setting_type")
val type: AppSettingType,
)
enum class AppSettingType {
CHAR,
STRING,
BYTE,
SHORT,
INT,
LONG,
DOUBLE,
FLOAT,
BOOLEAN,
}
This is then saved to the database with the following:
override fun saveAppSetting(setting: AppSetting): DatabaseResult<AppSetting> {
log.info("Saving App Setting ${setting.name} to database.")
return try {
// Attempt to save the entity to the database. If we do not throw an exception, return success.
val savedSetting = appSettingsRepository.save(setting)
DatabaseResult(
code = ResultCode.CREATION_SUCCESS,
entity = savedSetting
)
} catch(exception: DataAccessException) {
log.error("Unable to save App Setting ${setting.name} to database. Reason: ${exception.message}")
DatabaseResult(
code = ResultCode.CREATION_FAILURE
)
}
}
Now, let's say that I wish to save a Char type to database, I figure I would use the following:
override fun saveAppSetting(name: String, value: Char): DatabaseResult<Char> {
val appSettingResult = saveAppSetting(AppSetting(
name = name,
value = value.toString(),
type = AppSettingType.CHAR,
))
return if(appSettingResult.code != ResultCode.CREATION_FAILURE) {
val entity = getAppSetting<Char>(appSettingResult.entity?.name!!).entity.toString().first()
DatabaseResult(
code = appSettingResult.code,
entity = entity
)
} else {
DatabaseResult(
code = ResultCode.CREATION_FAILURE,
)
}
}
I also figured that I would need to do the following in order to retrieve the correct object type:
override fun getAppSetting(name: String): DatabaseResult<Any?> {
log.info("Getting App Setting $name from database.")
val appSetting = appSettingsRepository.findAppSettingByName(name)
return if(appSetting != null) {
log.info("App Setting $name has ID of ${appSetting.id} within the database")
when(appSetting.type) {
AppSettingType.CHAR -> {
DatabaseResult<Char>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.first(),
)
}
AppSettingType.STRING -> {
DatabaseResult<String>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value,
)
}
AppSettingType.BYTE -> {
DatabaseResult<Byte>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toByte(),
)
}
AppSettingType.SHORT -> {
DatabaseResult<Short>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toShort(),
)
}
AppSettingType.INT -> {
DatabaseResult<Int>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toInt(),
)
}
AppSettingType.LONG -> {
DatabaseResult<Long>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toLong(),
)
}
AppSettingType.DOUBLE -> {
DatabaseResult<Double>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toDouble(),
)
}
AppSettingType.FLOAT -> {
DatabaseResult<Float>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toFloat()
)
}
AppSettingType.BOOLEAN -> {
DatabaseResult<Boolean>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toBoolean()
)
}
}
} else {
log.error("App Setting $name does not seem to exist within the database.")
DatabaseResult(
code = ResultCode.FETCH_FAILURE
)
}
However, when I then wish to use said object, I still have to write something like the following:
val newBarcode = getAppSetting("barcode_value").entity.toString().toInt()
Assuming I've "initialised" barcode_value with a value of 177 (for example).
How can I get the function to return what I need without having to do .toString.to...()?
Yes this all possible, here is a simplified demo, firstly
import kotlin.reflect.KClass
data class AppSetting(
val id: Long? = null,
val name: String = "",
var value: String = "",
val type: AppSettingType,
)
enum class AppSettingType(val clazz: KClass<out Any>) {
CHAR(Char::class),
STRING(String::class),
INT(Int::class),
}
So I added a clazz so from the enum we know the Kotlin type
and now a function to simulate your repository fetch
fun findAppSettingByName(name: String): AppSetting? {
return when(name) {
"Char thing" -> AppSetting(value= "C", type = AppSettingType.CHAR)
"String thing" -> AppSetting(value= "Str", type = AppSettingType.STRING)
"Int thing" -> AppSetting(value= "42", type = AppSettingType.INT)
else -> throw IllegalArgumentException()
}
}
Next in the function declaration I have made it generic with T and for the purposes of the demo removed the DatabaseResult container. Then I added a clazz parameter which is the typical Java way of carrying the required class information into the function:
fun <T : Any> getAppSetting(name: String, clazz: KClass<T>): T? {
val appSetting: AppSetting? = findAppSettingByName(name)
return appSetting?.let {
require(clazz == appSetting.type.clazz) {
"appSetting.type=${appSetting.type.clazz} mismatched with requested class=${clazz}"
}
when (appSetting.type) {
AppSettingType.CHAR -> appSetting.value.first()
AppSettingType.STRING -> appSetting.value
AppSettingType.INT -> appSetting.value.toInt()
} as T
}
}
the as T is important to cast the values into the required return type - this is unchecked but the when() clause should be creating the correct types.
Now let's test it:
val c1: Char? = getAppSetting("Char thing", Char::class)
val s1: String? = getAppSetting("String thing", String::class)
val i1: Int? = getAppSetting("Int thing", Int::class)
println("c1=$c1 s1=$s1 i1=$i1")
val c2: Char? = getAppSetting("Char thing")
val s2: String? = getAppSetting("String thing")
val i2: Int? = getAppSetting("Int thing")
println("c2=$c2 s2=$s2 i2=$i2")
}
The output is
c1=C s1=Str i1=42
c2=C s2=Str i2=42
But how do c2/s2/i2 work, the final part is this function
inline fun <reified T : Any> getAppSetting(name: String) = getAppSetting(name, T::class)
This is reified generic parameters... there is no need to pass the clazz because this can be found from the data type of the receiving variable.
There are many articles about this advanced topic, e.g.
https://typealias.com/guides/getting-real-with-reified-type-parameters/
https://medium.com/kotlin-thursdays/introduction-to-kotlin-generics-reified-generic-parameters-7643f53ba513
Now, I didn't completely answer what you wanted because you wanted to receive a DatabaseResult<T> wrapper. What might be possible, is to have a function that returns DatabaseResult<T> and you can obtain the T from it as the "clazz" parameter, but I'll leave that for someone else to improve on :-) but I think that gets you pretty close.

How to inject scopeId into Koin to get the dependency?

In https://github.com/InsertKoinIO/koin/blob/master/koin-projects/docs/reference/koin-android/scope.md#sharing-instances-between-components-with-scopes it is shown the below example
module {
// Shared user session data
scope(named("session")) {
scoped { UserSession() }
}
// Inject UserSession instance from "session" Scope
factory { (scopeId : ScopeID) -> Presenter(getScope(scopeId).get())}
}
But I don't even know how to get presenter?
I try
val nameScope = getKoin().createScope("SomeName", named("session"))
val presenter = get<Presenter>(nameScope.id)
but it's not the correct. How to get my presenter?
After tracing the code, the way to do it is to use parameter to pass over the scopeId
For the above example, it will be
val nameScope = getKoin().createScope("SomeName", named("session"))
val presenter = get<Presenter>(parameters = { parametersOf(nameScope.id) )
If there's qualifier, we just need to send through them as well
One Example as below where we need a parameter of the lambda to send through scopeId and name of the qualifier. (the argument is self definable through the parameters of any type).
module {
scope(named("AScopeName")) {
scoped(qualifier = named("scopedName")) { Dependency() }
factory(qualifier = named("factoryName")) { Dependency() }
}
factory { (scopeId: ScopeID, name: String) ->
Environment(getScope(scopeId).get(qualifier = named(name)))
}
}
Then the calling is as simple as below
val nameScope = getKoin().createScope("SomeName", named("AScopeName"))
val environment = get<Environment>(parameters = { parametersOf(nameScope.id, "scopedName") })
Or we could also
val nameScope = getKoin().createScope("SomeName", named("AScopeName"))
val environment = get<Environment>(parameters = { parametersOf("SomeName", "scopedName") })

Kotlin data class create dynamically json of its fields using GSON

I have a data class like this:
data class TestModel(
val id: Int,
val description: String,
val picture: String)
If I create JSON from this data class using GSON and it generates a result like this
{"id":1,"description":"Test", "picture": "picturePath"}
What to do if I need the following JSON from my data class:
{"id":1, "description":"Test"}
And other times:
`{"id":1, "picture": "picturePath"}
`
Thanks in advance!
You can solve this problem with writing custom adapter and with optional types:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
data class TestModel(
val id: Int,
val description: String? = "",
val picture: String? = "")
class TesModelTypeAdapter : TypeAdapter<TestModel>() {
override fun read(reader: JsonReader?): TestModel {
var id: Int? = null
var picture: String? = null
var description: String? = null
reader?.beginObject()
while (reader?.hasNext() == true) {
val name = reader.nextName()
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
continue
}
when (name) {
"id" -> id = reader.nextInt()
"picture" -> picture = reader.nextString()
"description" -> description = reader.nextString()
}
}
reader?.endObject()
return when {
!picture.isNullOrBlank() && description.isNullOrBlank() -> TestModel(id = id ?: 0, picture = picture)
!description.isNullOrBlank() && picture.isNullOrBlank() -> TestModel(id = id ?: 0, description = description)
else -> TestModel(id ?: 0, picture, description)
}
}
override fun write(out: JsonWriter?, value: TestModel?) {
out?.apply {
beginObject()
value?.let {
when {
!it.picture.isNullOrBlank() && it.description.isNullOrBlank() -> {
name("id").value(it.id)
name("picture").value(it.picture)
}
!it.description.isNullOrBlank() && it.picture.isNullOrBlank() -> {
name("id").value(it.id)
name("description").value(it.description)
}
else -> {
name("id").value(it.id)
name("picture").value(it.picture)
name("description").value(it.description)
}
}
}
endObject()
}
}
}
class App {
companion object {
#JvmStatic fun main(args: Array<String>) {
val tm = TestModel(12, description = "Hello desc")
val tm2 = TestModel(23, picture = "https://www.pexels.com/photo/daylight-forest-glossy-lake-443446/")
val tm3 = TestModel(12, "Hello desc", "https://www.pexels.com/photo/daylight-forest-glossy-lake-443446/")
val gson = GsonBuilder().registerTypeAdapter(TestModel::class.java, TesModelTypeAdapter()).create()
System.out.println(gson.toJson(tm))
System.out.println(gson.toJson(tm2))
System.out.println(gson.toJson(tm3))
}
}
}
Here is actually a way to ignore fields, that are not marked via #Exposed annotation. In order for this to work, special configuration should be used when instantiating Gson. Here is how you can to this.
Easy way is to mark the field as #Transient. Then it would not be either serialized and deserialized.
I want to give you alternative ways without manually serialization/deserialization.
data class TestModel(
val id: Int,
val description: String? = null,
val picture: String? = null)
When you create json from data class
val params = TestModel(id = 1, description = "custom text")
or
val params = TestModel(id = 1, picture = "picture path")
If one of them field is null of data class GSON skips that field
automatically.

Ignore setter and set property directly

I have a class that writes a user to SharedPreferences every time it is set:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
init {
val userString = prefs.getString(USER, null)
if (userString != null) {
user = jsonAdapter.fromJson(userString)
}
}
}
Problem: If the user is set in the init block, it calls the setter and writes the user that we just got from the shared prefs... to the shared prefs.
Question 1: How can I directly set the property from the init block?
Question 2: Why do I have to initialize the User when I define a custom setter, but can omit the initialization when the default setter is used?
You need to directily initiliaze the property with the correct value. You can do this using the run function from the stdlib:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = run {
val userString = prefs.getString(USER, null)
if (userString != null) {
jsonAdapter.fromJson(userString)
} else {
User()
}
}
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
}
Shorter syntax proposed by Ilya Ryzhenkov on the Kotlin Slack:
var user: User = prefs.getString(USER, null)?.let { jsonAdapter.fromJson(it) } ?: User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
I believe the best solution is to use the 'backing property' concept described here: https://kotlinlang.org/docs/reference/properties.html#backing-properties
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // Type parameters are inferred
return _table ?: throw AssertionError("Set to null by another thread")
}
Then initialize the backing property in the constructor and do <backingproperty> = value instead of field = value as well as point the getter to the backing property.
Take a look at by map delegate, seems like this is the pattern you want:
class User(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
https://kotlinlang.org/docs/reference/delegated-properties.html#storing-properties-in-a-map