PQT_Gestionnaire_vent_stock/Workspace/server/src/main/java/com/pqt/server/module/account/FileAccountDao.java

149 lines
6.0 KiB
Java

package com.pqt.server.module.account;
import com.pqt.core.entities.user_account.Account;
import com.pqt.core.entities.user_account.AccountLevel;
import com.pqt.server.tools.io.ISerialFileManager;
import com.pqt.server.tools.io.SimpleSerialFileManagerFactory;
import com.pqt.server.tools.security.IHashTool;
import com.pqt.server.tools.security.SHA256HashTool;
import java.util.*;
import java.util.stream.Collectors;
//TODO ajouter logs
/**
* Implémentation de l'interface {@link IAccountDao} utilisant un fichier contenant des objets sérialisés comme
* source de persistance de données.
* <p/>
* 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.
* <p/>
* Cette classe manipule les mot de passe sous forme chiffrée via un système de hash (SHA-256) + 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";
private Set<AccountEntry> accountEntries;
private Set<AccountEntry> connectedAccount;
private IHashTool hashTool;
private ISerialFileManager<AccountEntry> fileManager;
FileAccountDao() {
accountEntries = new HashSet<>();
connectedAccount = new HashSet<>();
hashTool = new SHA256HashTool();
fileManager = SimpleSerialFileManagerFactory.getFileManager(AccountEntry.class, ACCOUNT_FILE_NAME);
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<AccountEntry> entries){
return entries.stream().filter(accountEntry -> accountEntry.getUsername().equals(account.getUsername())).findFirst().orElse(null);
}
@Override
public synchronized boolean isAccountConnected(Account account) {
return lookupMatchingEntry(account, connectedAccount)!=null;
}
@Override
public synchronized boolean submitAccountCredentials(Account account, boolean desiredState) {
if(isAccountRegistered(account)){
if(desiredState!=isAccountConnected(account)){
if(desiredState)
return connect(account);
else
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){
AccountEntry entry = lookupMatchingEntry(account, accountEntries);
if(entry==null)
return false;
else{
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);
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){
AccountEntry entry = lookupMatchingEntry(account, accountEntries);
if(entry!=null && connectedAccount.contains(entry)){
connectedAccount.remove(entry);
return true;
}
return false;
}
@Override
public synchronized boolean isAccountRegistered(Account account) {
return lookupMatchingEntry(account, accountEntries)!=null;
}
@Override
public synchronized AccountLevel getAccountPermissionLevel(Account account) {
if(isAccountRegistered(account))
return lookupMatchingEntry(account, accountEntries).getLevel();
return null;
}
@Override
public synchronized List<Account> 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.
* <p/>
* <b>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.</b>
*/
private void loadFromFile(){
this.accountEntries = new HashSet<>(fileManager.loadSetFromFile());
//TODO faire check des comptes au lieu de tout déconnecter?
this.connectedAccount.clear();
}
}