[WIP] - added authentification for a user

This commit is contained in:
Faraphel 2024-05-26 19:18:51 +02:00
parent 56330ff7c7
commit 5ed15e3371
20 changed files with 465 additions and 198 deletions

View file

@ -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<BarcodeResult?>(null) }
if (barcode.value == null) ScanBarcodeScreen(this, barcode)
else Text("code: ${barcode.value!!.text}")
}
example()
CommunicationModeSelectionScreen(this)
}
/*
this.setContent {
CommunicationScreen(this)
}
*/
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")

View file

@ -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)
}
/**

View file

@ -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<String>,
): 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<String>,
): 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<String, String> = jsonParser.fromJson(
httpSession.inputStream.bufferedReader().readText(),
object : TypeToken<Map<String, String>>() {}.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) {
}
}
}
}
/**
* Handle an HTTP Api request about all the sessions
*/
private fun handleRequestAll(
taskSession: TaskSession?,
httpSession: NanoHTTPD.IHTTPSession,
path: MutableList<String>,
): 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"
)
}
}
}
}

View file

@ -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,
)

View file

@ -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<String, TaskSession>() ///< 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<String, TaskSession> {
// 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<String, TaskSession>? {
// 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<String, TaskSession>? {
// 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<String, TaskSession>? {
// 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<String, 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
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)

View file

@ -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<String>
): 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",

View file

@ -17,7 +17,7 @@ interface ClassDao : BaseDao<ClassEntity> {
* 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

View file

@ -14,7 +14,7 @@ interface PersonDao : BaseDao<PersonEntity> {
* 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

View file

@ -19,5 +19,5 @@ interface RelationClassPersonDao : BaseDao<RelationClassPersonEntity> {
"class_id = :classId AND " +
"student_id = :studentId"
)
fun getById(classId: Long, studentId: Long): RelationClassPersonEntity
fun getById(classId: Long, studentId: Long): RelationClassPersonEntity?
}

View file

@ -14,5 +14,5 @@ interface SessionDao : BaseDao<SessionEntity> {
* 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?
}

View file

@ -15,7 +15,7 @@ interface SubjectDao : BaseDao<SubjectEntity> {
* 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

View file

@ -15,7 +15,7 @@ interface TaskDao : BaseDao<TaskEntity> {
* 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

View file

@ -20,5 +20,5 @@ interface ValidationDao : BaseDao<ValidationEntity> {
"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?
}

View file

@ -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)
}

View file

@ -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")
}
}
}

View file

@ -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<PersonEntity?>) {
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")
}
}
}

View file

@ -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") {

View file

@ -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<PersonEntity?>(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<TaskClient?>(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<TaskClient?>) {
fun CommunicationInternetServerContent(
activity: Activity,
adminPersonEntity: PersonEntity,
client: MutableState<TaskClient?>
) {
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")

View file

@ -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)

View file

@ -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")