[WIP] implemented very basic session, permission and authentication system

This commit is contained in:
Faraphel 2024-05-09 21:45:10 +02:00
parent b97fb71f8f
commit a9a02bcde7
4 changed files with 205 additions and 28 deletions

View file

@ -1,9 +1,11 @@
package com.faraphel.tasks_valider.connectivity.task package com.faraphel.tasks_valider.connectivity.task
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
import com.faraphel.tasks_valider.database.TaskDatabase import com.faraphel.tasks_valider.database.TaskDatabase
import com.faraphel.tasks_valider.database.api.base.BaseApi
import com.google.gson.Gson import com.google.gson.Gson
import fi.iki.elonen.NanoHTTPD import fi.iki.elonen.NanoHTTPD
import java.util.*
/** /**
@ -15,39 +17,124 @@ class TaskServer(
private val port: Int, private val port: Int,
private val database: TaskDatabase private val database: TaskDatabase
) : NanoHTTPD(port) { ) : NanoHTTPD(port) {
private val jsonParser = Gson()
override fun serve(session: IHTTPSession): Response { // Session
val method: Method = session.method
val uri: String = session.uri
// remove the first slash // TODO(Faraphel): theses functions should be moved somewhere else for specific session management
val entityName: String = uri.substring(1)
// get the matching entity API in the database private val sessions = mutableMapOf<String, TaskSession>() ///< sessions specific data
val entityApi = database.getApiFromName(entityName)
/**
* Create a new session
* @param session the data for the session (optional)
* @return a new session identifiant
*/
private fun newSessionData(session: TaskSession = TaskSession()): String {
val sessionId = UUID.randomUUID().toString()
this.sessions[sessionId] = session
return sessionId
}
/**
* Get data from a http session
* @param httpSession the HTTP session
* @return the session data
*/
private fun getSessionData(httpSession: IHTTPSession): TaskSession? {
val sessionId = httpSession.cookies.read("sessionId") ?: return null
val sessionData = this.getSessionData(sessionId)
return sessionData
}
/**
* Get data from a session identifiant
* @param sessionId the identifiant of the session
* @return the session data
*/
private fun getSessionData(sessionId: String): TaskSession? {
return this.sessions[sessionId]
}
/**
* Get data from a http session. If it does not exist, create it.
* @param httpSession the HTTP session
*/
private fun getOrCreateSessionData(httpSession: IHTTPSession): TaskSession {
// try to get the session directly
var session = this.getSessionData(httpSession)
// if the session does not exist, create it
if (session == null) {
val sessionId = this.newSessionData()
session = this.getSessionData(sessionId)!!
}
// return the session
return session
}
/**
* Insert new cookies for a session in a response
* @param response the response to inject cookies into
* @param cookies the cookie handler
*/
private fun responseSetSessionData(response: Response, cookies: CookieHandler): Response {
cookies.set(Cookie("sessionId", this.newSessionData()))
cookies.unloadQueue(response)
return response
}
// HTTP
override fun serve(httpSession: IHTTPSession): Response {
// get the session data of the client
val taskSession = this.getOrCreateSessionData(httpSession)
// get the method used
val method: Method = httpSession.method
// parse the url
val uri: String = httpSession.uri.substring(1) // remove the first slash
val uriComponents = uri.split("/")
// get the type of the request from the uri
val requestType = uriComponents.getOrNull(0)
?: return newFixedLengthResponse( ?: return newFixedLengthResponse(
Response.Status.NOT_FOUND, NanoHTTPD.Response.Status.BAD_REQUEST,
"text/plain", "text/plain",
"Invalid entity name" "Invalid request type"
) )
// handle the request // get the response from the correct part of the application
return when (method) { val response = when (requestType) {
// check if the data is in the database // session requests
Method.HEAD -> entityApi.head(session) "sessions" -> {
// get the data from the dao TODO("Faraphel: handle admin changing permission")
Method.GET -> entityApi.get(session) }
// insert the data into the database
Method.POST -> entityApi.post(session) // entities requests
// other methods are not allowed "entities" -> {
else -> val entityName = uriComponents.getOrNull(1)
?: return newFixedLengthResponse(
NanoHTTPD.Response.Status.BAD_REQUEST,
"text/plain",
"Invalid entity name"
)
this.database.handleApiRequest(taskSession, httpSession, method, entityName)
}
// invalid requests
else -> {
newFixedLengthResponse( newFixedLengthResponse(
Response.Status.METHOD_NOT_ALLOWED, NanoHTTPD.Response.Status.BAD_REQUEST,
"text/plain", "text/plain",
"Method not allowed" "Invalid request type"
) )
// TODO(Faraphel): implement a permission system }
} }
// wrap additional information in the response
return this.responseSetSessionData(response, httpSession.cookies)
} }
/** /**

View file

@ -0,0 +1,12 @@
package com.faraphel.tasks_valider.connectivity.task.session
/**
* A permission level that can be used for in the task system
*/
enum class TaskPermission {
NONE,
STUDENT,
TEACHER,
ADMIN,
}

View file

@ -0,0 +1,10 @@
package com.faraphel.tasks_valider.connectivity.task.session
/**
* store the data of a session in the task system
* @param permission the permission accorded to the session
*/
data class TaskSession(
var permission: TaskPermission = TaskPermission.STUDENT
)

View file

@ -3,6 +3,8 @@ package com.faraphel.tasks_valider.database
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
import com.faraphel.tasks_valider.database.api.* import com.faraphel.tasks_valider.database.api.*
import com.faraphel.tasks_valider.database.api.base.BaseApi import com.faraphel.tasks_valider.database.api.base.BaseApi
import com.faraphel.tasks_valider.database.converters.InstantConverter import com.faraphel.tasks_valider.database.converters.InstantConverter
@ -18,6 +20,7 @@ import com.faraphel.tasks_valider.database.entities.StudentEntity
import com.faraphel.tasks_valider.database.entities.TaskEntity import com.faraphel.tasks_valider.database.entities.TaskEntity
import com.faraphel.tasks_valider.database.entities.TaskGroupEntity import com.faraphel.tasks_valider.database.entities.TaskGroupEntity
import com.faraphel.tasks_valider.database.entities.TeacherEntity import com.faraphel.tasks_valider.database.entities.TeacherEntity
import fi.iki.elonen.NanoHTTPD
/** /**
@ -63,10 +66,75 @@ abstract class TaskDatabase : RoomDatabase() {
) )
/** /**
* get the api for an entity from its name. * dispatch an API request
* @param name the name of the entity
*/ */
fun getApiFromName(name: String): BaseApi? { fun handleApiRequest(
return this.api[name] taskSession: TaskSession,
httpSession: NanoHTTPD.IHTTPSession,
method: NanoHTTPD.Method,
entityName: String
): NanoHTTPD.Response {
// get the correspond Api object for this entity
val entityApi = this.api[entityName]
?: return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.NOT_FOUND,
"text/plain",
"Invalid entity name"
)
// dispatch the request to the correct entity API
return when (method) {
// check if the data is in the database
NanoHTTPD.Method.HEAD -> {
if (taskSession.permission < TaskPermission.STUDENT)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.FORBIDDEN,
"text/plain",
"Forbidden"
)
return entityApi.head(httpSession)
}
// get the data from the database
NanoHTTPD.Method.GET -> {
if (taskSession.permission < TaskPermission.STUDENT)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.FORBIDDEN,
"text/plain",
"Forbidden"
)
return entityApi.get(httpSession)
}
// insert the data into the database
NanoHTTPD.Method.POST -> {
if (taskSession.permission < TaskPermission.TEACHER)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.FORBIDDEN,
"text/plain",
"Forbidden"
)
return entityApi.post(httpSession)
}
// delete the data from the database
NanoHTTPD.Method.DELETE -> {
if (taskSession.permission < TaskPermission.TEACHER)
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.FORBIDDEN,
"text/plain",
"Forbidden"
)
return entityApi.delete(httpSession)
}
// other methods are not allowed
else ->
return NanoHTTPD.newFixedLengthResponse(
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
"text/plain",
"Method not allowed"
)
}
} }
} }