Rewrote data handler... Users added.

This commit is contained in:
Kosh 2023-10-09 23:11:00 +05:30
parent 7d3d1806eb
commit a666972c08

View File

@ -1,163 +1,276 @@
import json
from os import path
import appdirs
from encryption_handler import EncryptionHandler
file_path: str = "/home/kosh/.local/share/kosh_pass/data.json"
password_check_path: str = "/home/kosh/.local/share/kosh_pass/password_check"
class DataHandler:
def __get_file_data(self) -> dict[str, dict[str, str]]:
with open(file_path, "r", encoding="utf8") as file:
return json.load(file)
"""
WARNING:-
THere is no fail-safe for if the app is opened for the first time.
def __write_file_data(self, data: dict[str, dict[str, str]]) -> None:
with open(file_path, "w", encoding="utf8") as file:
json.dump(data, file)
A class that handles the stored data.
This class does NOT handle encryption directly. Look at ./encryption_handler.py
When a instance is created, the instance should be used for one user ONLY.
def set_password(self, password: str) -> None:
with open(password_check_path, "wb") as password_check_file:
password_check_file.write(
EncryptionHandler.encrypt("Correct".encode(), password.encode())
)
In this class "data" is used. THe following is the format of data:-
data:- {
entry_name: {
field_name: field_value
}
}
Example for "data":
{
"Amazon": {
"Username": "Kosh",
"Password": "Pass1234"
},
"Matrix": {
"Username": "Kosh",
"Email": "kosh@fake.com",
"id": "@kosh:matrix.com"
}
}
def verify_password(self, password: str) -> None:
with open(password_check_path, "rb") as password_check_file:
EncryptionHandler.decrypt(password_check_file.read(), password.encode())
def change_password(self, old_password: str, new_password: str) -> None:
self.verify_password(old_password)
data: dict[str, dict[str, str]] = {}
Attributes:-
__file_path (private):- The path to the file where the users encrypted data is stored.
__password (private):- The users password.
"""
for entry_name in self.get_entry_names():
data[entry_name] = {}
for field_name in self.get_field_names(entry_name):
data[entry_name][field_name] = \
self.get_field_data(entry_name, field_name, old_password)
__file_path: str
__user_name: str
__password: str
self.set_password(new_password)
self.__write_file_data({})
def __init__(self, user_name: str, password: str) -> None:
"""
Logs in the user
Parameters:-
user_name
password
Raises:-
If user_name does not exist, ValueError will be thrown
If password is wrong, ValueError will be thrown
WARNING:-
If this is the first time the app is opened, the user_name will be shown as wrong
"""
self.__file_path = appdirs.user_data_dir(appname="Unnamed_Password_Manager")
self.__user_name = user_name
self.__file_path = path.join(self.__file_path, self.__user_name)
self.__password = password
for entry_name in data:
self.add_entry(entry_name)
for field_name in data[entry_name]:
self.add_field(
entry_name, field_name, data[entry_name][field_name], new_password
)
if not path.exists(self.__file_path):
raise ValueError(f"User {user_name} does not exist.")
def add_entry(self, entry_name: str) -> None:
data = self.__get_file_data()
# This will call encryption handler,
# which will throw an error if the password is wrong
self.get_data()
@staticmethod
def create_user(user_name: str, password: str) -> None:
"""
Creates a new user.
Parameters:-
user_name
password
Raises:-
If user_name exists, ValueError is thrown
"""
file_path = path.join(
appdirs.user_data_dir(appname="Unnamed_Password_Manager"),
user_name
)
if path.exists(file_path):
raise ValueError(f"User {user_name} already exists")
with open(file_path, "wb") as file:
file.write(EncryptionHandler.encrypt(b"{}", password.encode()))
def get_data(self) -> dict[str, dict[str, str]]:
"""
Get the data of the user.
Return:-
data:- Read the class documentation for more information.
"""
with open(self.__file_path, "rb") as file:
data: str = \
EncryptionHandler.decrypt(file.read(), self.__password.encode())\
.decode()
return json.loads(data)
def write_data(self, data: dict[str, dict[str, str]]) -> None:
"""
Write the data of the user.
Parameters:-
data:- Read the class documentation for more information
"""
json_data: str = json.dumps(data)
encrypted_data: bytes = EncryptionHandler.encrypt(
json_data.encode(),
self.__password.encode()
)
with open(self.__file_path, "wb") as file:
file.write(encrypted_data)
def change_password(self, password: str) -> None:
"""
Changes the password of the user.
Parameters:-
password
"""
data: dict[str, dict[str, str]] = self.get_data()
self.__password = password
self.write_data(data)
def add_entry(self, entry_name: str, fields: dict[str, str]) -> None:
"""
Add a new entry:-
Parameters:-
entry_name
fields:- {
field_name_1: field_value_1,
field_name_2: field_value_2
}
Raises:-
A ValueError is raised if entry_name already exists.
"""
data: dict[str, dict[str, str]] = self.get_data()
if entry_name in data:
raise ValueError(f"An entry with the name {entry_name} already exists.")
raise ValueError(f"Entry {entry_name} already exists.")
data[entry_name] = {}
self.__write_file_data(data)
data[entry_name] = fields
self.write_data(data)
def delete_entry(self, entry_name: str) -> None:
data = self.__get_file_data()
try:
data.pop(entry_name)
except KeyError:
raise ValueError(f"No entry with name \"{entry_name}\"") from KeyError
self.__write_file_data(data)
"""
Deletes the given entry.
Parameters:-
entry_name
Errors:-
If the requested entry does not exist, an error will be thrown
"""
data: dict[str, dict[str, str]] = self.get_data()
if entry_name not in data:
raise ValueError(f"{entry_name} does not exist.")
data.pop(entry_name)
self.write_data(data)
def edit_entry_name(self, entry_name: str, new_entry_name: str) -> None:
if new_entry_name in self.get_entry_names():
def edit_entry_name(self, old_entry_name: str, new_entry_name: str) -> None:
"""
Changes the name of the entry.
Parameters:-
old_entry_name
new_entry_name
Errors:-
If the old_entry_name does not exist, a ValueError will be thrown.
If new_entry_name already exists, a ValueError will be raised.
"""
data: dict[str, dict[str, str]] = self.get_data()
if new_entry_name in data:
raise ValueError(f"\"{new_entry_name}\" already exists")
if entry_name not in self.get_entry_names():
raise ValueError(f"No entry with name \"{entry_name}\"")
if old_entry_name not in data:
raise ValueError(f"No entry with name \"{old_entry_name}\"")
fields: dict[str, str] = self.__get_file_data()[entry_name]
self.delete_entry(entry_name)
data = self.__get_file_data()
data[new_entry_name] = fields
self.__write_file_data(data)
self.delete_entry(old_entry_name)
self.add_entry(new_entry_name, data[old_entry_name])
def get_entry_names(self, ) -> list[str]:
return list(self.__get_file_data().keys())
def add_field(self,
def add_field(self,
entry_name: str,
field_name: str,
field_value: str,
password: str
) -> None:
self.verify_password(password)
if entry_name not in self.get_entry_names():
raise ValueError(f"No entry with name {entry_name}")
if field_name in self.get_field_names(entry_name):
"""
Adds a new field to a given entry.
Parameters:-
entry_name
field_name
field_value
Raises:-
If entry_name does not exist, a ValueError will be raised.
If field_name already exists, a ValueError will be raised.
"""
data: dict[str, dict[str, str]] = self.get_data()
if entry_name not in data:
raise ValueError(f"No entry with name \"{entry_name}\"")
if field_name in data[entry_name]:
raise ValueError(f"Field {field_name} already exists under entry {entry_name}")
data: dict[str, dict[str, str]] = self.__get_file_data()
data[entry_name][field_name] = EncryptionHandler.encrypt(
field_value.encode(),
password.encode()
).decode("unicode-escape")
self.__write_file_data(data)
data[entry_name][field_name] = field_value
self.write_data(data)
def delete_field(self, entry_name: str, field_name: str) -> None:
if entry_name not in self.get_entry_names():
"""
Adds a new field to a given entry.
Parameters:-
entry_name
field_name
Raises:-
If entry_name does not exist, a ValueError will be raised.
If field_name does not exists, a ValueError will be raised.
"""
data: dict[str, dict[str, str]] = self.get_data()
if entry_name not in data:
raise ValueError(f"No entry with name \"{entry_name}\"")
if field_name not in self.get_field_names(entry_name):
raise ValueError(f"{entry_name} does not have field {field_name}")
data = self.__get_file_data()
if field_name not in data[entry_name]:
raise ValueError(f"No field {field_name} under entry {entry_name}")
data[entry_name].pop(field_name)
self.__write_file_data(data)
self.write_data(data)
def edit_field_name(self, entry_name: str, field_name: str, new_field_name: str) -> None:
if entry_name not in self.get_entry_names():
def edit_field_name(
self,
entry_name: str,
old_field_name: str,
new_field_name: str
) -> None:
"""
Change the name of a field under the given entry.
Parameters:-
entry_name
old_field_name
new_field_name
Raises:-
If entry_name does not exist, a ValueError will be raised.
If old_field_name does not exists, a ValueError will be raised.
If field_name already exists, a ValueError will be raised.
"""
data: dict[str, dict[str, str]] = self.get_data()
if entry_name not in data:
raise ValueError(f"No entry with name \"{entry_name}\"")
if field_name not in self.get_field_names(entry_name):
raise ValueError(f"{entry_name} does not have field {field_name}")
if new_field_name in self.get_field_names(entry_name):
raise ValueError(f"{entry_name} does has a field {new_field_name}")
field_data = self.__get_file_data()[entry_name][field_name]
self.delete_field(entry_name, field_name)
data = self.__get_file_data()
data[entry_name][new_field_name] = field_data
self.__write_file_data(data)
if old_field_name not in data[entry_name]:
raise ValueError(f"No field {old_field_name} under entry {entry_name}")
if new_field_name in data[entry_name]:
raise ValueError(f"Field {new_field_name} already exists under {entry_name}")
data[entry_name][new_field_name] = data[entry_name][old_field_name]
data[entry_name].pop(old_field_name)
self.write_data(data)
def edit_field_value(self,
def edit_field_value(self,
entry_name: str,
field_name: str,
new_value: str,
password: str
) -> None:
self.verify_password(password)
if entry_name not in self.get_entry_names():
raise ValueError(f"No entry called {entry_name}")
if field_name not in self.get_field_names(entry_name):
raise ValueError(f"No field called {field_name} under {entry_name}")
self.delete_field(entry_name, field_name)
self.add_field(entry_name, field_name, new_value, password)
def get_field_data(self, entry_name: str, field_name: str, password: str) -> str:
self.verify_password(password)
if field_name not in self.get_field_names(entry_name):
raise ValueError(f"No field with name {field_name} under {entry_name}.")
encrypted_data = self.__get_file_data()[entry_name][field_name].\
encode("ISO-8859-1")
plain_data = EncryptionHandler.decrypt(
encrypted_data,
password.encode("ISO-8859-1")
).decode("utf-8")
return plain_data
def get_field_names(self, entry_name: str) -> list[str]:
data = self.__get_file_data()
"""
Change the value of the given field under the given entry.
Parameters:-
entry_name
field_name
new_value
Raises:-
If entry_name does not exist, a ValueError will be raised.
If field_name does not exists, a ValueError will be raised.
"""
data: dict[str, dict[str, str]] = self.get_data()
if entry_name not in data:
raise ValueError(f"No entry with name {entry_name}.")
return list(data[entry_name].keys())
raise ValueError(f"No entry with name \"{entry_name}\"")
if field_name not in data[entry_name]:
raise ValueError(f"No field {field_name} under entry {entry_name}")
data[entry_name][field_name] = new_value
self.write_data(data)
def add_entry_and_fields(
self,
entry_name: str,
fields: dict[str, str],
password: str
) -> None:
self.verify_password(password)
if entry_name in self.get_entry_names():
raise ValueError(f"Entry {entry_name} already exists.")
self.add_entry(entry_name)
for field_name, field_value in fields.items():
print(field_name, field_value)
self.add_field(entry_name, field_name, field_value, password)
def test():
#DataHandler.create_user("Kosh", "p")
d = DataHandler("Kosh", "p")
d.edit_field_value("Not Amazon", "p", "v")
if __name__ == "__main__":
test()