149 lines
6.0 KiB
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();
|
|
}
|
|
}
|