diff --git a/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/AccountService.java b/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/AccountService.java index 68a5a0ff..5a7c7ce3 100644 --- a/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/AccountService.java +++ b/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/AccountService.java @@ -111,7 +111,7 @@ public class AccountService { * Renvoie la liste des comptes utilisateurs contenus dans la base de données sous forme d'une liste d'objets * {@link Account}. Seuls les noms d'utilisateurs ainsi que les niveaux de permissions sont récupérés, les * autres champs étant volontairement initialisés avec une valeur {@code null}. - * @return Une liste d'objet {@link Account} représsentant les différents comptes utilisateurs existant dans la base + * @return Une liste d'objet {@link Account} représentant les différents comptes utilisateurs existant dans la base * de données. */ public List getAccountList(){ diff --git a/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/FileAccountDao.java b/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/FileAccountDao.java index f0c96a68..3695b015 100644 --- a/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/FileAccountDao.java +++ b/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/FileAccountDao.java @@ -10,9 +10,19 @@ import com.pqt.server.tools.security.MD5HashTool; import java.util.*; import java.util.stream.Collectors; -//TODO écrire Javadoc //TODO ajouter logs -public class FileAccountDao implements IAccountDao { + +/** + * Implémentation de l'interface {@link IAccountDao} utilisant un fichier contenant des objets sérialisés comme + * source de persistance de données. + *

+ * Cette classe n'est pas faite pour gérer les accès concurentiels au fichier assurant la persistance, et n'est donc pas + * thread-safe. Elle est conçue pour que tous les accès soient effectués depuis un même thread et depuis un unique objet. + *

+ * Cette classe manipule les mot de passe sous forme chiffrée via un système de hash (md5) + salt, et ne fait pas + * persister les mots de passes non-chiffrées. Les noms d'utilisateurs sont stockés sans chiffrage. + */ +class FileAccountDao implements IAccountDao { private static final String ACCOUNT_FILE_NAME = "acc.pqt"; @@ -29,76 +39,107 @@ public class FileAccountDao implements IAccountDao { loadFromFile(); } + /** + * Recherche une correspondance entre un objet {@link Account} et les objets {@link AccountEntry} contenu dans + * la collection {@code entries}. La correspondance se base sur la valeur renvoyée par {@link Account#getUsername()} + * et sur {@link AccountEntry#getUsername()}. + * @param account données à utiliser pour rechercher la correspondance. + * @param entries collection de données à utiliser pour rechercher la correspondance + * @return La première correspondance trouvée, ou {@code null} si aucune correspondance n'a pu être faite. + */ private AccountEntry lookupMatchingEntry(Account account, Collection entries){ return entries.stream().filter(accountEntry -> accountEntry.getUsername().equals(account.getUsername())).findFirst().orElse(null); } @Override - public boolean isAccountConnected(Account account) { + public synchronized boolean isAccountConnected(Account account) { return lookupMatchingEntry(account, connectedAccount)!=null; } @Override - public boolean submitAccountCredentials(Account acc, boolean desiredState) { - if(isAccountRegistered(acc)){ - if(desiredState!=isAccountConnected(acc)){ + public synchronized boolean submitAccountCredentials(Account account, boolean desiredState) { + if(isAccountRegistered(account)){ + if(desiredState!=isAccountConnected(account)){ if(desiredState) - return connect(acc); + return connect(account); else - return disconnect(acc); + return disconnect(account); } } return false; } + /** + * Passe un compte déconnecté dans l'état connecté. N'effecctue le changement que si un compte déconnecté correspond + * aux données fournies et que le mot de passe fournit par {@code account.getPassword()} corresspond à celui du + * compte correspondant. + * + * @param account données à utiliser pour effectuer la correspondance et l'identification + * @return {@code true} si le changement d'état a eu lieu, {@code false sinon} + */ private boolean connect(Account account){ - Optional entry = accountEntries.stream().filter(accountEntry -> accountEntry.getUsername().equals(account.getUsername())).findFirst(); - if(!entry.isPresent()) + AccountEntry entry = lookupMatchingEntry(account, accountEntries); + if(entry==null) return false; else{ - String expectedUsername = entry.get().getUsername(); - String expectedPasswordHash = entry.get().getPasswordHash(); - String salt = entry.get().getSalt(); + String expectedUsername = entry.getUsername(); + String expectedPasswordHash = entry.getPasswordHash(); + String salt = entry.getSalt(); if(expectedUsername.equals(account.getUsername()) && hashTool.hashAndSalt(account.getPassword(), salt).equals(expectedPasswordHash)){ - connectedAccount.add(entry.get()); + connectedAccount.add(entry); return true; }else return false; } } + /** + * Passe un comtpe connecté dans l'état déconnecté. N'effectue le changement que si un compte connecté correspond + * aux données fournies. + * @param account données à utiliser pour efffectuer la correspondance avec un compte + * @return {@code true} si le changement d'état a eu lieu, {@code false sinon} + */ private boolean disconnect(Account account){ - Optional entry = accountEntries.stream().filter(accountEntry -> accountEntry.getUsername().equals(account.getUsername())).findFirst(); - if(entry.isPresent() && connectedAccount.contains(entry.get())){ - connectedAccount.remove(entry.get()); + AccountEntry entry = lookupMatchingEntry(account, accountEntries); + if(entry!=null && connectedAccount.contains(entry)){ + connectedAccount.remove(entry); return true; } return false; } @Override - public boolean isAccountRegistered(Account account) { + public synchronized boolean isAccountRegistered(Account account) { return lookupMatchingEntry(account, accountEntries)!=null; } @Override - public AccountLevel getAccountPermissionLevel(Account account) { + public synchronized AccountLevel getAccountPermissionLevel(Account account) { if(isAccountRegistered(account)) return lookupMatchingEntry(account, accountEntries).getLevel(); return null; } @Override - public List getAccountList() { + public synchronized List getAccountList() { return accountEntries.stream().map(accountEntry -> new Account(accountEntry.getUsername(), null, accountEntry.getLevel())).collect(Collectors.toList()); } + /** + * Sauvegarde les données des comptes dans le fichier de sauvegarde. + */ private void saveToFile(){ fileManager.saveSetToFile(accountEntries); } + /** + * Charge les données des comptes depuis le fichier de sauvegarde. + *

+ * Attention : pour des raisons de cohérence des données, tous les comptes connectés sont repassés dans l'état + * déconnectés une fois le chargement fait. + */ private void loadFromFile(){ this.accountEntries = new HashSet<>(fileManager.loadSetFromFile()); //TODO faire check des comptes au lieu de tout déconnecter? diff --git a/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/IAccountDao.java b/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/IAccountDao.java index 3c9302a7..04c70f81 100644 --- a/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/IAccountDao.java +++ b/Workspace/server/src/main/WEB-INF/classes/com/pqt/server/module/account/IAccountDao.java @@ -5,16 +5,58 @@ import com.pqt.core.entities.user_account.AccountLevel; import java.util.List; -//TODO écrire Javadoc -public interface IAccountDao { +/** + * Cette interface définit les méthodes de base que doivent avoir les classes de DAO du service {@link AccountService}. + *

+ * Les implémentations de cette interface sont censé assurer la persistance des donnéess de connexions des comptes + * utilisateurs et doit également garder une trace niveau runtime de l'état des comptes (état connecté ou état + * déconnecté). Enfin, les implémentations doivent pouvoir déterminer une correspondance entre un nom d'utilisateur et + * un compte, et doit pouvoir effectuer les changements d'état sur la base d'un mot de passe non-chiffré fournit grâce + * à une instance {@link Account} (voir {@link #submitAccountCredentials(Account, boolean)}). + * + * @author Guillaume "Cess" Prost + */ +interface IAccountDao { + /** + * @see AccountService#isAccountConnected(Account) + * + * @param account données à utiliser + * @return {@code true} si les données correspondent à un compte et que ce dernier est connecté, {@code false} + * sinon. + */ boolean isAccountConnected(Account account); - boolean submitAccountCredentials(Account acc, boolean desiredState); + /** + * @see AccountService#submitAccountCredentials(Account, boolean) + * + * @param account données à utiliser + * @param desiredState état désiré pour le compte + * @return {@code true} si les données correspondent à un compte et que le changement d'état a eu lieu, + * {@code false} sinon. + */ + boolean submitAccountCredentials(Account account, boolean desiredState); + /** + * @see AccountService#isAccountRegistered(Account) + * + * @param account données à utiliser + * @return {@code true} si les données correspondent à un compte, {@code false} sinon. + */ boolean isAccountRegistered(Account account); + /** + * @see AccountService#getAccountPermissionLevel(Account) + * @param account données à utiliser + * @return Le niveau d'accréditation du compte utilisateur correspondant aux données, ou {@code null} si aucun + * compte ne correspond. + */ AccountLevel getAccountPermissionLevel(Account account); + /** + * @see AccountService#getAccountList() + * @return Une liste d'objet {@link Account} représentant les différents comptes utilisateurs existant dans la base + * de données. + */ List getAccountList(); }