From 6b4b420a225d42f6e8704bddfd1328bf9e694508 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 10 May 2024 09:44:49 +0200 Subject: [PATCH] [WIP] added role system and a basic API for the session management --- .../connectivity/task/TaskServer.kt | 94 ++--------- .../task/session/TaskPermission.kt | 11 +- .../connectivity/task/session/TaskRole.kt | 31 ++++ .../connectivity/task/session/TaskSession.kt | 4 +- .../task/session/TaskSessionManager.kt | 147 ++++++++++++++++++ .../tasks_valider/database/TaskDatabase.kt | 15 +- 6 files changed, 204 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.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 f731c92..b92fba3 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,11 +1,8 @@ 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.connectivity.task.session.TaskSessionManager import com.faraphel.tasks_valider.database.TaskDatabase -import com.google.gson.Gson import fi.iki.elonen.NanoHTTPD -import java.util.* /** @@ -17,78 +14,11 @@ class TaskServer( private val port: Int, private val database: TaskDatabase ) : NanoHTTPD(port) { - - // Session - - // 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 + private val sessionManager = TaskSessionManager() ///< the session manager override fun serve(httpSession: IHTTPSession): Response { // get the session data of the client - val taskSession = this.getOrCreateSessionData(httpSession) + val taskSession = this.sessionManager.getOrCreateSessionData(httpSession) // get the method used val method: Method = httpSession.method @@ -108,9 +38,8 @@ class TaskServer( // get the response from the correct part of the application val response = when (requestType) { // session requests - "sessions" -> { - TODO("Faraphel: handle admin changing permission") - } + "sessions" -> + this.sessionManager.handleApiRequest(taskSession, httpSession, method) // entities requests "entities" -> { @@ -121,26 +50,25 @@ class TaskServer( "Invalid entity name" ) - this.database.handleApiRequest(taskSession, httpSession, method, entityName) + // TODO(Faraphel): the uri arguments should be handled in the handleApiRequest + return this.database.handleApiRequest(taskSession, httpSession, method, entityName) } + // invalid requests - else -> { + else -> newFixedLengthResponse( NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", "Invalid request type" ) - } } // wrap additional information in the response - return this.responseSetSessionData(response, httpSession.cookies) + return this.sessionManager.responseSetSessionData(response, httpSession.cookies) } /** * Start the server with the default configuration */ - override fun start() { - super.start(SOCKET_READ_TIMEOUT, false) - } + override fun start() = super.start(SOCKET_READ_TIMEOUT, false) } 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 index 897591e..74d1809 100644 --- 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 @@ -1,12 +1,7 @@ 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, + READ, + WRITE, + ADMIN } diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt new file mode 100644 index 0000000..cb519be --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskRole.kt @@ -0,0 +1,31 @@ +package com.faraphel.tasks_valider.connectivity.task.session + + +/** + * A role system that can be used for in the task system + */ +enum class TaskRole(val value: String) { + NONE("none") { + override var permissions: List = listOf() + }, + STUDENT("student") { + override var permissions = listOf( + TaskPermission.READ + ) + }, + TEACHER("teacher") { + override var permissions: List = listOf( + TaskPermission.READ, + TaskPermission.WRITE + ) + }, + ADMIN("admin") { + override var permissions: List = listOf( + TaskPermission.READ, + TaskPermission.WRITE, + TaskPermission.ADMIN + ) + }; + + abstract var permissions: List +} 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 index 5b99518..8956377 100644 --- 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 @@ -3,8 +3,8 @@ 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 + * @param role the role accorded to the session */ data class TaskSession( - var permission: TaskPermission = TaskPermission.STUDENT + var role: TaskRole = TaskRole.STUDENT ) diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt new file mode 100644 index 0000000..87a7a5d --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/session/TaskSessionManager.kt @@ -0,0 +1,147 @@ +package com.faraphel.tasks_valider.connectivity.task.session + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import fi.iki.elonen.NanoHTTPD +import java.util.* + + +/** + * The manager for the session system + */ +class TaskSessionManager { + private val jsonParser = Gson() ///< the json parser + private val jsonTypeToken = object : TypeToken>(){} ///< the json type + private val sessions = mutableMapOf() ///< sessions specific data + + /** + * Create a new session + * @param session the data for the session (optional) + * @param sessionId the session id to use (optional) + * @return a new session identifiant + */ + fun newSessionData( + session: TaskSession = TaskSession(), + sessionId: String = UUID.randomUUID().toString() + ): String { + this.sessions[sessionId] = session + return sessionId + } + + /** + * Get data from a http session + * @param httpSession the HTTP session + * @return the session data + */ + fun getSessionData(httpSession: NanoHTTPD.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 + */ + 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 + */ + fun getOrCreateSessionData(httpSession: NanoHTTPD.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 + */ + fun responseSetSessionData( + response: NanoHTTPD.Response, + cookies: NanoHTTPD.CookieHandler + ): NanoHTTPD.Response { + cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData())) + cookies.unloadQueue(response) + return response + } + + /** + * Handle a HTTP Api request + * @param taskSession the data of the client session + * @param httpSession the data of the http session + * @param method the method used + */ + fun handleApiRequest( + taskSession: TaskSession, + httpSession: NanoHTTPD.IHTTPSession, + method: NanoHTTPD.Method + ): NanoHTTPD.Response { + when (method) { + // get a client session data + NanoHTTPD.Method.GET -> { + TODO("Faraphel: implement get") + } + // change a client session data + NanoHTTPD.Method.POST -> { + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.ADMIN)) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.FORBIDDEN, + "text/plain", + "Forbidden" + ) + + // parse the content of the request + val data: Map = jsonParser.fromJson( + httpSession.inputStream.bufferedReader().readText(), + jsonTypeToken.type + ) + + // update the role + data["role"]?.let { role -> + try { + taskSession.role = TaskRole.valueOf(role) + } catch (exception: IllegalArgumentException) { + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Invalid role" + ) + } + // NOTE(Faraphel): does a modification on the object require updating the array ? + } + + // success message + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.OK, + "text/plain", + "Session updated" + ) + } + + // other action are limited + else -> { + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED, + "text/plain", + "Method not allowed" + ) + } + } + } +} \ No newline at end of file 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 e8d8515..17bd5bb 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 @@ -4,6 +4,7 @@ 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.TaskRole 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 @@ -83,10 +84,11 @@ abstract class TaskDatabase : RoomDatabase() { ) // dispatch the request to the correct entity API - return when (method) { + when (method) { // check if the data is in the database NanoHTTPD.Method.HEAD -> { - if (taskSession.permission < TaskPermission.STUDENT) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.READ)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -97,7 +99,8 @@ abstract class TaskDatabase : RoomDatabase() { } // get the data from the database NanoHTTPD.Method.GET -> { - if (taskSession.permission < TaskPermission.STUDENT) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.READ)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -108,7 +111,8 @@ abstract class TaskDatabase : RoomDatabase() { } // insert the data into the database NanoHTTPD.Method.POST -> { - if (taskSession.permission < TaskPermission.TEACHER) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.WRITE)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -119,7 +123,8 @@ abstract class TaskDatabase : RoomDatabase() { } // delete the data from the database NanoHTTPD.Method.DELETE -> { - if (taskSession.permission < TaskPermission.TEACHER) + // check the permission of the session + if (taskSession.role.permissions.contains(TaskPermission.WRITE)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain",