From 5ed15e3371fcc3cf5a217035d8de72d3a04a5d88 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Sun, 26 May 2024 19:18:51 +0200 Subject: [PATCH] [WIP] - added authentification for a user --- .../faraphel/tasks_valider/MainActivity.kt | 22 +- .../connectivity/task/TaskServer.kt | 46 ++-- .../task/api/TaskSessionManagerApi.kt | 230 ++++++++++++------ .../connectivity/task/session/TaskSession.kt | 6 +- .../task/session/TaskSessionManager.kt | 100 +++++--- .../database/api/TaskDatabaseApi.kt | 18 +- .../tasks_valider/database/dao/ClassDao.kt | 2 +- .../tasks_valider/database/dao/PersonDao.kt | 2 +- .../database/dao/RelationClassPersonDao.kt | 2 +- .../tasks_valider/database/dao/SessionDao.kt | 2 +- .../tasks_valider/database/dao/SubjectDao.kt | 2 +- .../tasks_valider/database/dao/TaskDao.kt | 2 +- .../database/dao/ValidationDao.kt | 2 +- .../database/entities/PersonEntity.kt | 49 +++- .../ui/screen/authentification/client.kt | 40 +++ .../ui/screen/authentification/server.kt | 48 ++++ .../internet/{screen.kt => selection.kt} | 2 +- .../communication/internet/server/screen.kt | 64 +++-- .../communication/{screen.kt => selection.kt} | 6 +- .../communication/wifiP2p/server/screen.kt | 18 +- 20 files changed, 465 insertions(+), 198 deletions(-) create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt create mode 100644 app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt rename app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/{screen.kt => selection.kt} (95%) rename app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/{screen.kt => selection.kt} (94%) diff --git a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt index 69e4231..9d897df 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/MainActivity.kt @@ -6,14 +6,9 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.RequiresApi -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import com.faraphel.tasks_valider.connectivity.bwf.BwfManager import com.faraphel.tasks_valider.database.TaskDatabase -import com.faraphel.tasks_valider.ui.screen.scan.qr.ScanBarcodeScreen -import com.journeyapps.barcodescanner.BarcodeResult +import com.faraphel.tasks_valider.ui.screen.communication.CommunicationModeSelectionScreen class MainActivity : ComponentActivity() { @@ -28,21 +23,8 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) this.setContent { - @Composable - fun example() { - val barcode = remember { mutableStateOf(null) } - if (barcode.value == null) ScanBarcodeScreen(this, barcode) - else Text("code: ${barcode.value!!.text}") - } - - example() + CommunicationModeSelectionScreen(this) } - - /* - this.setContent { - CommunicationScreen(this) - } - */ } @SuppressLint("UnspecifiedRegisterReceiverFlag") 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 ba2151a..2eb7b6b 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,10 @@ package com.faraphel.tasks_valider.connectivity.task import com.faraphel.tasks_valider.connectivity.task.api.TaskSessionManagerApi -import com.faraphel.tasks_valider.connectivity.task.session.TaskRole -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.faraphel.tasks_valider.database.api.TaskDatabaseApi +import com.faraphel.tasks_valider.database.entities.PersonEntity import fi.iki.elonen.NanoHTTPD @@ -16,30 +15,33 @@ import fi.iki.elonen.NanoHTTPD */ class TaskServer( private val port: Int, - private val database: TaskDatabase + private val database: TaskDatabase, + adminPersonEntity: PersonEntity, ) : NanoHTTPD(port) { - companion object { - private val TASK_SESSION_ADMIN = TaskSession( ///< the admin default session - role = TaskRole.ADMIN - ) - } - - private val sessionManager = TaskSessionManager() ///< the session manager - private val adminSessionId = this.sessionManager.newSessionData(TASK_SESSION_ADMIN) ///< default admin session id - - private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager) ///< the api of the session manager + private val sessionManager = TaskSessionManager(adminPersonEntity) ///< the session manager private val databaseApi = TaskDatabaseApi(this.database) ///< the api of the database + private val sessionManagerApi = TaskSessionManagerApi(this.sessionManager, this.database) ///< the api of the session manager + + /** + * Get the admin person entity + * @return the admin person entity + */ + fun getAdminPersonEntity(): PersonEntity { + return this.sessionManager.getAdminPersonEntity() + } /** * Return a new client that can be used by the admin + * @return the client */ - fun getClientAdmin(): TaskClient { + fun getAdminClient(): TaskClient { // create the session cookie for the admin val cookieSession = okhttp3.Cookie.Builder() .domain("localhost") - .name("sessionId") - .value(adminSessionId) - .build() + .name("sessionToken") + .value(this.sessionManager.adminSessionToken) + .build() + // create a new client return TaskClient( "localhost", @@ -54,7 +56,8 @@ class TaskServer( */ override fun serve(httpSession: IHTTPSession): Response { // get the session data of the client - val taskSession = this.sessionManager.getOrCreateSessionData(httpSession) + val taskSessionData = this.sessionManager.getSessionData(httpSession) + val taskSession = taskSessionData?.second // parse the url val uri: String = httpSession.uri.trim('/') @@ -69,11 +72,11 @@ class TaskServer( ) // get the response from the correct part of the application - val response = when (requestType) { + return when (requestType) { // session requests "sessions" -> this.sessionManagerApi.handleRequest(taskSession, httpSession, path) // entities requests - "entities" -> return this.databaseApi.handleRequest(taskSession, httpSession, path) + "entities" -> this.databaseApi.handleRequest(taskSession, httpSession, path) // invalid requests else -> newFixedLengthResponse( @@ -82,9 +85,6 @@ class TaskServer( "Unknown request type" ) } - - // wrap additional information in the response - return this.sessionManager.responseSetSessionData(response, httpSession.cookies) } /** diff --git a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt index 305e3f3..5f861b5 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/connectivity/task/api/TaskSessionManagerApi.kt @@ -1,9 +1,9 @@ package com.faraphel.tasks_valider.connectivity.task.api 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.connectivity.task.session.TaskSessionManager +import com.faraphel.tasks_valider.database.TaskDatabase import com.google.gson.Gson import com.google.gson.reflect.TypeToken import fi.iki.elonen.NanoHTTPD @@ -12,7 +12,10 @@ import fi.iki.elonen.NanoHTTPD /** * the HTTP API for the session manager */ -class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) { +class TaskSessionManagerApi( + private val sessionManager: TaskSessionManager, + private val database: TaskDatabase +) { private val jsonParser = Gson() ///< the json parser /** @@ -22,38 +25,41 @@ class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) { * @param path the path of the request */ fun handleRequest( - taskSession: TaskSession, + taskSession: TaskSession?, httpSession: NanoHTTPD.IHTTPSession, path: MutableList, ): NanoHTTPD.Response { - // get the target session id - val targetSessionId = path.removeFirstOrNull() + val action = path.removeFirstOrNull() - return if (targetSessionId == null) { - // no specific session targeted - this.handleRequestGeneric(taskSession, httpSession) - } else { - // a specific session is targeted - this.handleRequestSpecific(taskSession, httpSession, targetSessionId) + return when (action) { + "self" -> this.handleRequestSelf(taskSession, httpSession, path) + "all" -> this.handleRequestAll(taskSession, httpSession, path) + else -> + NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Unknown action" + ) } } /** - * Handle a request with no specific session targeted + * Handle an HTTP Api request about the user own session */ - private fun handleRequestGeneric( - taskSession: TaskSession, + fun handleRequestSelf( + taskSession: TaskSession?, httpSession: NanoHTTPD.IHTTPSession, + path: MutableList, ): NanoHTTPD.Response { when (httpSession.method) { - // get all the session data + // get the session data NanoHTTPD.Method.GET -> { - // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.READ)) + // check if the session is valid + if (taskSession == null) return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.FORBIDDEN, + NanoHTTPD.Response.Status.UNAUTHORIZED, "text/plain", - "Forbidden" + "No session" ) // return the session data @@ -63,68 +69,74 @@ class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) { jsonParser.toJson(taskSession) ) } - // other action are limited - else -> { - return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED, - "text/plain", - "Unknown method" - ) - } - } - } - private fun handleRequestSpecific( - taskSession: TaskSession, - httpSession: NanoHTTPD.IHTTPSession, - targetSessionId: String, - ): NanoHTTPD.Response { - when (httpSession.method) { - // change a specific client session data + // connect the user to the session NanoHTTPD.Method.POST -> { - // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.ADMIN)) + // get the user identifiers + val identifiers: Map = jsonParser.fromJson( + httpSession.inputStream.bufferedReader().readText(), + object : TypeToken>() {}.type + ) + + // check for the id + if (!identifiers.contains("id")) return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.FORBIDDEN, + NanoHTTPD.Response.Status.BAD_REQUEST, "text/plain", - "You are not allowed to update a session" + "Missing id" ) - // parse the content of the request - val targetSession = jsonParser.fromJson( - httpSession.inputStream.bufferedReader().readText(), - TaskSession::class.java - ) + // get the id of the user (if invalid, return an error) + val personId: Long = identifiers["id"]!!.toLongOrNull() + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Invalid id" + ) - // update the session - this.sessionManager.setSessionData(targetSessionId, targetSession) + // check for the password + if (!identifiers.contains("password")) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Missing password" + ) - // success message - return NanoHTTPD.newFixedLengthResponse( + // check if the identifiers are correct + val person = this.database.personDao().getById(personId) + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "No person with this id" + ) + + // check if the password is correct + if (!person.checkPassword(identifiers["password"]!!)) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.UNAUTHORIZED, + "text/plain", + "Invalid password" + ) + + // create a new session for the userJHH + val (sessionToken, session) = this.sessionManager.newSessionData(person) + + // create the response + val response = NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.OK, "text/plain", "Session updated" ) - } - // delete the session - NanoHTTPD.Method.DELETE -> { - // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.ADMIN)) - return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.FORBIDDEN, - "text/plain", - "You are not allowed to delete a session" - ) - // delete the target session - this.sessionManager.deleteSessionData(targetSessionId) - - // success message - return NanoHTTPD.newFixedLengthResponse( - NanoHTTPD.Response.Status.OK, - "text/plain", - "Session deleted" + // set the session token in the cookies + this.sessionManager.responseSetSessionData( + response, + httpSession.cookies, + sessionToken ) + + // return the response + return response } // ignore other methods else -> { @@ -136,4 +148,86 @@ class TaskSessionManagerApi(private val sessionManager: TaskSessionManager) { } } } -} \ No newline at end of file + + /** + * Handle an HTTP Api request about all the sessions + */ + private fun handleRequestAll( + taskSession: TaskSession?, + httpSession: NanoHTTPD.IHTTPSession, + path: MutableList, + ): NanoHTTPD.Response { + // check if the session is valid + if (taskSession == null) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.UNAUTHORIZED, + "text/plain", + "No session" + ) + + // check the permission of the session + if (taskSession.person.role.permissions.contains(TaskPermission.ADMIN)) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.FORBIDDEN, + "text/plain", + "You are not allowed to update a session" + ) + + when (httpSession.method) { + // change a specific client session data + NanoHTTPD.Method.POST -> { + // parse the content of the request + val targetSession = jsonParser.fromJson( + httpSession.inputStream.bufferedReader().readText(), + TaskSession::class.java + ) + + val targetSessionToken = path.removeFirstOrNull() + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Missing session token" + ) + + // update the session + this.sessionManager.updateSessionData(targetSessionToken, targetSession) + + // success message + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.OK, + "text/plain", + "Session updated" + ) + } + + // delete the session + NanoHTTPD.Method.DELETE -> { + val targetSessionToken = path.removeFirstOrNull() + ?: return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "text/plain", + "Missing session token" + ) + + // delete the target session + this.sessionManager.deleteSessionData(targetSessionToken) + + // success message + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.OK, + "text/plain", + "Session deleted" + ) + } + + // ignore other methods + else -> { + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED, + "text/plain", + "Invalid method" + ) + } + } + } +} 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 84c4c37..99998cb 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 @@ -1,13 +1,13 @@ package com.faraphel.tasks_valider.connectivity.task.session +import com.faraphel.tasks_valider.database.entities.PersonEntity import kotlinx.serialization.Serializable /** * store the data of a session in the task system - * @param role the role accorded to the session */ @Serializable data class TaskSession( - var role: TaskRole = TaskRole.STUDENT -) + val person: PersonEntity, +) \ No newline at end of file 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 index e0552c6..772bb6d 100644 --- 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 @@ -1,7 +1,6 @@ package com.faraphel.tasks_valider.connectivity.task.session -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken +import com.faraphel.tasks_valider.database.entities.PersonEntity import fi.iki.elonen.NanoHTTPD import java.util.* @@ -9,21 +8,37 @@ import java.util.* /** * The manager for the session system */ -class TaskSessionManager { +class TaskSessionManager(///< the admin person entity + private val adminPersonEntity: PersonEntity +) { private val sessions = mutableMapOf() ///< sessions specific data + private val adminPersonData = this.newSessionData(this.adminPersonEntity) ///< the session of the admin + val adminSessionToken = this.adminPersonData.first ///< the session token of the admin + val adminSession = this.adminPersonData.second ///< the session of the admin + + /** + * Get the admin person entity + * @return the admin person entity + */ + fun getAdminPersonEntity(): PersonEntity { + return this.adminPersonEntity + } + /** * Create a new session - * @param session the data for the session (optional) - * @param sessionId the session id to use (optional) - * @return a new session identifier + * @param person the person for the session (optional) + * @return a new session identifier and the corresponding session */ - fun newSessionData( - session: TaskSession = TaskSession(), - sessionId: String = UUID.randomUUID().toString() - ): String { - this.sessions[sessionId] = session - return sessionId + fun newSessionData(person: PersonEntity): Pair { + // create a new session token that is a secret random string + val sessionToken: String = UUID.randomUUID().toString() + // create a new session + val session = TaskSession(person) + // store the session + this.sessions[sessionToken] = session + // return the session data + return Pair(sessionToken, session) } /** @@ -31,54 +46,60 @@ class TaskSessionManager { * @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) + fun getSessionData(httpSession: NanoHTTPD.IHTTPSession): Pair? { + // get the session token from the cookies + val sessionToken = httpSession.cookies.read("sessionToken") ?: return null + // get the session data + val sessionData = this.getSessionData(sessionToken) + // return the session data return sessionData } /** * Get data from a session identifier - * @param sessionId the identifier of the session + * @param sessionToken the identifier of the session * @return the session data */ - fun getSessionData(sessionId: String): TaskSession? { - return this.sessions[sessionId] + fun getSessionData(sessionToken: String): Pair? { + // get the session data + val session = this.sessions[sessionToken] + ?: return null + // return the session data + return Pair(sessionToken, session) } /** - * Set the data of a session - * @param sessionId the identifier of the session - * @param session the session data + * Update the session data + * @param sessionToken the identifier of the session + * @param session the new session data */ - fun setSessionData(sessionId: String, session: TaskSession) { - this.sessions[sessionId] = session + fun updateSessionData(sessionToken: String, session: TaskSession) { + // update the session + this.sessions[sessionToken] = session } /** * Delete a session - * @param sessionId the identifier of the session + * @param sessionToken the identifier of the session */ - fun deleteSessionData(sessionId: String): TaskSession? { - return this.sessions.remove(sessionId) + fun deleteSessionData(sessionToken: String): Pair? { + // remove the session + val session = this.sessions.remove(sessionToken) ?: + return null + // return the session data + return Pair(sessionToken, session) } /** * Get data from a http session. If it does not exist, create it. * @param httpSession the HTTP session */ - fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession { + fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession, person: PersonEntity): Pair { // 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 + val sessionData = this.getSessionData(httpSession) + ?: this.newSessionData(person) + // return the session data + return sessionData } /** @@ -88,10 +109,11 @@ class TaskSessionManager { */ fun responseSetSessionData( response: NanoHTTPD.Response, - cookies: NanoHTTPD.CookieHandler + cookies: NanoHTTPD.CookieHandler, + sessionToken: String ): NanoHTTPD.Response { // update the cookie of the user - cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData())) + cookies.set(NanoHTTPD.Cookie("sessionToken", sessionToken)) // load them in the response cookies.unloadQueue(response) diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt index 2303a34..84ca7c0 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/api/TaskDatabaseApi.kt @@ -27,10 +27,18 @@ class TaskDatabaseApi(private val database: TaskDatabase) { * @param path the path of the request */ fun handleRequest( - taskSession: TaskSession, + taskSession: TaskSession?, httpSession: NanoHTTPD.IHTTPSession, path: MutableList ): NanoHTTPD.Response { + // check if the user is authenticated + if (taskSession == null) + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.UNAUTHORIZED, + "text/plain", + "Unauthorized" + ) + // get the entity name val entityName = path.removeFirstOrNull() ?: return NanoHTTPD.newFixedLengthResponse( @@ -53,7 +61,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) { // TODO(Faraphel): should only be allowed to read data concerning the current class session NanoHTTPD.Method.HEAD -> { // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.READ)) + if (taskSession.person.role.permissions.contains(TaskPermission.READ)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -65,7 +73,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) { // get the data from the database NanoHTTPD.Method.GET -> { // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.READ)) + if (taskSession.person.role.permissions.contains(TaskPermission.READ)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -77,7 +85,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) { // insert the data into the database NanoHTTPD.Method.POST -> { // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.WRITE)) + if (taskSession.person.role.permissions.contains(TaskPermission.WRITE)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", @@ -89,7 +97,7 @@ class TaskDatabaseApi(private val database: TaskDatabase) { // delete the data from the database NanoHTTPD.Method.DELETE -> { // check the permission of the session - if (taskSession.role.permissions.contains(TaskPermission.WRITE)) + if (taskSession.person.role.permissions.contains(TaskPermission.WRITE)) return NanoHTTPD.newFixedLengthResponse( NanoHTTPD.Response.Status.FORBIDDEN, "text/plain", diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt index baef405..879e624 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ClassDao.kt @@ -17,7 +17,7 @@ interface ClassDao : BaseDao { * Get the object from its identifier */ @Query("SELECT * FROM ${ClassEntity.TABLE_NAME} WHERE id = :id") - fun getById(id: Long): ClassEntity + fun getById(id: Long): ClassEntity? /** * Get all the sessions this class attended diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt index 601a2f6..10ef3e9 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/PersonDao.kt @@ -14,7 +14,7 @@ interface PersonDao : BaseDao { * Get the object from its identifier */ @Query("SELECT * FROM ${PersonEntity.TABLE_NAME} WHERE id = :id") - fun getById(id: Long): PersonEntity + fun getById(id: Long): PersonEntity? /** * Allow to get all the classes the person is attending as a student diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt index b0296df..30bea9b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/RelationClassPersonDao.kt @@ -19,5 +19,5 @@ interface RelationClassPersonDao : BaseDao { "class_id = :classId AND " + "student_id = :studentId" ) - fun getById(classId: Long, studentId: Long): RelationClassPersonEntity + fun getById(classId: Long, studentId: Long): RelationClassPersonEntity? } diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt index 8054915..e6c9c2a 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SessionDao.kt @@ -14,5 +14,5 @@ interface SessionDao : BaseDao { * Get the object from its identifier */ @Query("SELECT * FROM ${SessionEntity.TABLE_NAME} WHERE id = :id") - fun getById(id: Long): SessionEntity + fun getById(id: Long): SessionEntity? } diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt index 1b0616f..eba5345 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/SubjectDao.kt @@ -15,7 +15,7 @@ interface SubjectDao : BaseDao { * Get the object from its identifier */ @Query("SELECT * FROM ${SubjectEntity.TABLE_NAME} WHERE id = :id") - fun getById(id: Long): SubjectEntity + fun getById(id: Long): SubjectEntity? /** * Get all the tasks available in a subject diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt index 0053cd0..ff8e4e1 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/TaskDao.kt @@ -15,7 +15,7 @@ interface TaskDao : BaseDao { * Get the object from its identifier */ @Query("SELECT * FROM ${TaskEntity.TABLE_NAME} WHERE id = :id") - fun getById(id: Long): TaskEntity + fun getById(id: Long): TaskEntity? /** * Get all the validations have been approved for this tasks diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt index 544da65..3d32dab 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/dao/ValidationDao.kt @@ -20,5 +20,5 @@ interface ValidationDao : BaseDao { "student_id = :studentId and " + "task_id = :taskId" ) - fun getById(teacherId: Long, studentId: Long, taskId: Long): ValidationEntity + fun getById(teacherId: Long, studentId: Long, taskId: Long): ValidationEntity? } diff --git a/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt index 48e696f..0e62606 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/database/entities/PersonEntity.kt @@ -1,26 +1,61 @@ package com.faraphel.tasks_valider.database.entities -import androidx.compose.ui.text.capitalize import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey +import com.faraphel.tasks_valider.connectivity.task.session.TaskRole import com.faraphel.tasks_valider.database.entities.base.BaseEntity +import kotlinx.serialization.Serializable +import java.security.MessageDigest import java.util.* +@Serializable @Entity(tableName = PersonEntity.TABLE_NAME) data class PersonEntity ( @ColumnInfo("id") @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo("first_name") val firstName: String, @ColumnInfo("last_name") val lastName: String, - @ColumnInfo("card_id") val cardId: UUID, - @ColumnInfo("password_hash") val passwordHash: String + @ColumnInfo("card_id") val cardId: String? = null, + @ColumnInfo("password_hash") val passwordHash: String? = null, + @ColumnInfo("role") val role: TaskRole = TaskRole.STUDENT, ) : BaseEntity() { + companion object { + const val TABLE_NAME = "persons" + + /** + * Hash a password + */ + fun hashPassword(password: String): String { + val digester = MessageDigest.getInstance("SHA-256") + val hash = digester.digest(password.toByteArray()) + return hash.joinToString("") { byte -> "%02x".format(byte) } + } + } + + /** + * Constructor with string password + */ + constructor( + firstName: String, + lastName: String, + cardId: String? = null, + password: String? = null, + role: TaskRole, + ) : this( + firstName = firstName, + lastName = lastName, + cardId = cardId, + passwordHash = password?.let { hashPassword(password) }, + role = role, + ) + /** * Return the full name of the person */ - fun fullName(): String = "${this.firstName.capitalize()} ${this.lastName.uppercase()}" + fun fullName(): String = "${this.firstName.capitalize(Locale.ROOT)} ${this.lastName.uppercase()}" - companion object { - const val TABLE_NAME = "persons" - } + /** + * Check if the password is correct + */ + fun checkPassword(password: String): Boolean = this.passwordHash == hashPassword(password) } diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt new file mode 100644 index 0000000..1c83eb0 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/client.kt @@ -0,0 +1,40 @@ +package com.faraphel.tasks_valider.ui.screen.authentification + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.text.input.PasswordVisualTransformation + + +/** + * Authentification screen where the client can give his information + */ +@Composable +fun AuthentificationClientScreen() { + val cardId = remember { mutableStateOf("") } + val password = remember { mutableStateOf("") } + + Column { + // card identifier + TextField( + value = cardId.value, + onValueChange = { text -> cardId.value = text }, + ) + + // password + TextField( + value = password.value, + visualTransformation = PasswordVisualTransformation(), + onValueChange = { text -> password.value = text }, + ) + + // button + Button(onClick = { /* do something */ }) { + Text("Submit") + } + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt new file mode 100644 index 0000000..0ac5504 --- /dev/null +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/authentification/server.kt @@ -0,0 +1,48 @@ +package com.faraphel.tasks_valider.ui.screen.authentification + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.faraphel.tasks_valider.connectivity.task.session.TaskRole +import com.faraphel.tasks_valider.database.entities.PersonEntity + + +/** + * Authentification screen where the host can give his information + */ +@Composable +fun AuthentificationServerScreen(personEntity: MutableState) { + val firstName = remember { mutableStateOf("") } + val lastName = remember { mutableStateOf("") } + + Column { + // first name + TextField( + value = firstName.value, + onValueChange = { text -> firstName.value = text }, + ) + + // last name + TextField( + value = lastName.value, + onValueChange = { text -> lastName.value = text }, + ) + + // confirm button + Button(onClick = { + // create the person entity with the given information + personEntity.value = PersonEntity( + firstName = firstName.value, + lastName = lastName.value, + role = TaskRole.ADMIN + ) + }) { + Text("Submit") + } + } +} diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/selection.kt similarity index 95% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/selection.kt index 335c2f7..262be98 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/screen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/selection.kt @@ -14,7 +14,7 @@ import com.faraphel.tasks_valider.ui.screen.communication.internet.server.Commun @Composable -fun CommunicationInternetScreen(activity: Activity) { +fun CommunicationInternetSelectScreen(activity: Activity) { val controller = rememberNavController() NavHost(navController = controller, startDestination = "mode") { diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt index cdf8f13..e4b888d 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/internet/server/screen.kt @@ -19,24 +19,47 @@ import androidx.room.Room import com.faraphel.tasks_valider.connectivity.task.TaskClient import com.faraphel.tasks_valider.connectivity.task.TaskServer import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.entities.PersonEntity +import com.faraphel.tasks_valider.ui.screen.authentification.AuthentificationServerScreen import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen +/** + * Screen for the host to configure the server + */ @Composable fun CommunicationInternetServerScreen(activity: Activity) { + val adminPersonEntity = remember { mutableStateOf(null) } + + // if the admin person is not created, prompt the user for the admin information + if (adminPersonEntity.value == null) { + return AuthentificationServerScreen(adminPersonEntity) + } + val client = remember { mutableStateOf(null) } - // if the server is not created, prompt the user for the server configuration - if (client.value == null) CommunicationInternetServerContent(activity, client) - // else, go to the base tasks screen - else TaskSessionScreen(activity, client.value!!) + // if the client to the server is not created, prompt the user for the server configuration + if (client.value == null) { + return CommunicationInternetServerContent( + activity, + adminPersonEntity.value!!, + client + ) + } + + // otherwise, go to the base tasks screen + return TaskSessionScreen(activity, client.value!!) } @Composable -fun CommunicationInternetServerContent(activity: Activity, client: MutableState) { +fun CommunicationInternetServerContent( + activity: Activity, + adminPersonEntity: PersonEntity, + client: MutableState +) { val expandedStudentList = remember { mutableStateOf(false) } val serverPort = remember { mutableIntStateOf(DEFAULT_SERVER_PORT) } @@ -73,24 +96,27 @@ fun CommunicationInternetServerContent(activity: Activity, client: MutableState< ) Button(onClick = { - // Reset the database | TODO(Faraphel): only for testing purpose - activity.deleteDatabase("local") - - // Create the database - val database = Room.databaseBuilder( - activity, - TaskDatabase::class.java, - "local" - ).build() - - // Create the server - Log.i("room-server", "creating the server") Thread { // a thread is used for networking - val server = TaskServer(serverPort.intValue, database) + // Reset the database | TODO(Faraphel): only for testing purpose + activity.deleteDatabase("local") + + // Create the database + val database = Room.databaseBuilder( + activity, + TaskDatabase::class.java, + "local" + ).build() + + // Insert the admin in the database + database.personDao().insert(adminPersonEntity) + + // Create the server + Log.i("room-server", "creating the server") + val server = TaskServer(serverPort.intValue, database, adminPersonEntity) server.start() // Get the client from the server - client.value = server.getClientAdmin() + client.value = server.getAdminClient() }.start() }) { Text("Create") diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt similarity index 94% rename from app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt rename to app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt index bb260ce..fac1c7b 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/screen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/selection.kt @@ -13,7 +13,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.faraphel.tasks_valider.connectivity.bwf.BwfManager -import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetScreen +import com.faraphel.tasks_valider.ui.screen.communication.internet.CommunicationInternetSelectScreen import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationWifiP2pScreen @@ -24,7 +24,7 @@ import com.faraphel.tasks_valider.ui.screen.communication.wifiP2p.CommunicationW * @param activity: The activity that hosts the communication screen. */ @Composable -fun CommunicationScreen(activity: Activity) { +fun CommunicationModeSelectionScreen(activity: Activity) { val controller = rememberNavController() NavHost( @@ -35,7 +35,7 @@ fun CommunicationScreen(activity: Activity) { CommunicationSelectContent(controller, activity) } composable("internet") { - CommunicationInternetScreen(activity) + CommunicationInternetSelectScreen(activity) } composable("wifi-p2p") { val bwfManager = BwfManager.fromActivity(activity) diff --git a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt index f608bc0..3a64b92 100644 --- a/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt +++ b/app/src/main/java/com/faraphel/tasks_valider/ui/screen/communication/wifiP2p/server/screen.kt @@ -20,6 +20,7 @@ import com.faraphel.tasks_valider.connectivity.bwf.BwfManager import com.faraphel.tasks_valider.connectivity.task.TaskClient import com.faraphel.tasks_valider.connectivity.task.TaskServer import com.faraphel.tasks_valider.database.TaskDatabase +import com.faraphel.tasks_valider.database.entities.PersonEntity import com.faraphel.tasks_valider.ui.screen.communication.DEFAULT_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.communication.RANGE_SERVER_PORT import com.faraphel.tasks_valider.ui.screen.task.TaskSessionScreen @@ -90,14 +91,25 @@ fun CommunicationWifiP2pServerContent( "local" ).build() + // Create the admin + // TODO: the admin should be created from the card reader + val adminPersonEntity = PersonEntity( + "admin", + "admin", + "123456789", + "admin", + ) + + // Insert the admin in the database + database.personDao().insert(adminPersonEntity) + bwfManager.recreateGroup { // Create the server - Log.i("room-server", "creating the server") - val server = TaskServer(serverPort.intValue, database) + val server = TaskServer(serverPort.intValue, database, adminPersonEntity) server.start() // Get the client from the server - client.value = server.getClientAdmin() + client.value = server.getAdminClient() } }) { Text("Create")