From a9a02bcde7815f902f19f2ee47ac76fba8a70eea Mon Sep 17 00:00:00 2001 From: Faraphel Date: Thu, 9 May 2024 21:45:10 +0200 Subject: [PATCH] [WIP] implemented very basic session, permission and authentication system --- .../connectivity/task/TaskServer.kt | 135 ++++++++++++++---- .../task/session/TaskPermission.kt | 12 ++ .../connectivity/task/session/TaskSession.kt | 10 ++ .../tasks_valider/database/TaskDatabase.kt | 76 +++++++++- 4 files changed, 205 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt index 0337103..f731c92 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/TaskServer.kt @@ -1,9 +1,11 @@ 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.api.base.BaseApi import com.google.gson.Gson import fi.iki.elonen.NanoHTTPD +import java.util.* /** @@ -15,39 +17,124 @@ class TaskServer( private val port: Int, private val database: TaskDatabase ) : NanoHTTPD(port) { - private val jsonParser = Gson() - override fun serve(session: IHTTPSession): Response { - val method: Method = session.method - val uri: String = session.uri + // Session - // remove the first slash - val entityName: String = uri.substring(1) - // get the matching entity API in the database - val entityApi = database.getApiFromName(entityName) + // TODO(Faraphel): theses functions should be moved somewhere else for specific session management + + private val sessions = mutableMapOf() ///< sessions specific data + + /** + * 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( - Response.Status.NOT_FOUND, + NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "Invalid entity name" + "Invalid request type" ) - // handle the request - return when (method) { - // check if the data is in the database - Method.HEAD -> entityApi.head(session) - // get the data from the dao - Method.GET -> entityApi.get(session) - // insert the data into the database - Method.POST -> entityApi.post(session) - // other methods are not allowed - else -> + // get the response from the correct part of the application + val response = when (requestType) { + // session requests + "sessions" -> { + TODO("Faraphel: handle admin changing permission") + } + + // entities requests + "entities" -> { + 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( - Response.Status.METHOD_NOT_ALLOWED, + NanoHTTPD.Response.Status.BAD_REQUEST, "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) } /** diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt new file mode 100644 index 0000000..897591e --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskPermission.kt @@ -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, +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt new file mode 100644 index 0000000..5b99518 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSession.kt @@ -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 +) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt index 9b84639..e8d8515 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/TaskDatabase.kt @@ -3,6 +3,8 @@ package com.faraphel.tasks_valider.database import androidx.room.Database import androidx.room.RoomDatabase 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.base.BaseApi 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.TaskGroupEntity 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. - * @param name the name of the entity + * dispatch an API request */ - fun getApiFromName(name: String): BaseApi? { - return this.api[name] + fun handleApiRequest( + 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" + ) + } } }