git
This commit is contained in:
commit
739d10f15f
@ -1,163 +1,276 @@
|
|||||||
import json
|
import json
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
import appdirs
|
||||||
from encryption_handler import EncryptionHandler
|
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:
|
class DataHandler:
|
||||||
def __get_file_data(self) -> dict[str, dict[str, str]]:
|
"""
|
||||||
with open(file_path, "r", encoding="utf8") as file:
|
WARNING:-
|
||||||
return json.load(file)
|
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:
|
A class that handles the stored data.
|
||||||
with open(file_path, "w", encoding="utf8") as file:
|
This class does NOT handle encryption directly. Look at ./encryption_handler.py
|
||||||
json.dump(data, file)
|
When a instance is created, the instance should be used for one user ONLY.
|
||||||
|
|
||||||
def set_password(self, password: str) -> None:
|
In this class "data" is used. THe following is the format of data:-
|
||||||
with open(password_check_path, "wb") as password_check_file:
|
data:- {
|
||||||
password_check_file.write(
|
entry_name: {
|
||||||
EncryptionHandler.encrypt("Correct".encode(), password.encode())
|
field_name: field_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Example for "data":
|
||||||
|
{
|
||||||
|
"Amazon": {
|
||||||
|
"Username": "Kosh",
|
||||||
|
"Password": "Pass1234"
|
||||||
|
},
|
||||||
|
"Matrix": {
|
||||||
|
"Username": "Kosh",
|
||||||
|
"Email": "kosh@fake.com",
|
||||||
|
"id": "@kosh:matrix.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes:-
|
||||||
|
__file_path (private):- The path to the file where the users encrypted data is stored.
|
||||||
|
__password (private):- The users password.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__file_path: str
|
||||||
|
__user_name: str
|
||||||
|
__password: str
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if not path.exists(self.__file_path):
|
||||||
|
raise ValueError(f"User {user_name} does not exist.")
|
||||||
|
|
||||||
|
# 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 verify_password(self, password: str) -> None:
|
def get_data(self) -> dict[str, dict[str, str]]:
|
||||||
with open(password_check_path, "rb") as password_check_file:
|
"""
|
||||||
EncryptionHandler.decrypt(password_check_file.read(), password.encode())
|
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 change_password(self, old_password: str, new_password: str) -> None:
|
def write_data(self, data: dict[str, dict[str, str]]) -> None:
|
||||||
self.verify_password(old_password)
|
"""
|
||||||
data: dict[str, dict[str, str]] = {}
|
Write the data of the user.
|
||||||
|
Parameters:-
|
||||||
for entry_name in self.get_entry_names():
|
data:- Read the class documentation for more information
|
||||||
data[entry_name] = {}
|
"""
|
||||||
for field_name in self.get_field_names(entry_name):
|
json_data: str = json.dumps(data)
|
||||||
data[entry_name][field_name] = \
|
encrypted_data: bytes = EncryptionHandler.encrypt(
|
||||||
self.get_field_data(entry_name, field_name, old_password)
|
json_data.encode(),
|
||||||
|
self.__password.encode()
|
||||||
self.set_password(new_password)
|
|
||||||
self.__write_file_data({})
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
with open(self.__file_path, "wb") as file:
|
||||||
|
file.write(encrypted_data)
|
||||||
|
|
||||||
def add_entry(self, entry_name: str) -> None:
|
def change_password(self, password: str) -> None:
|
||||||
data = self.__get_file_data()
|
"""
|
||||||
|
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:
|
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] = {}
|
data[entry_name] = fields
|
||||||
self.__write_file_data(data)
|
self.write_data(data)
|
||||||
|
|
||||||
def delete_entry(self, entry_name: str) -> None:
|
def delete_entry(self, entry_name: str) -> None:
|
||||||
data = self.__get_file_data()
|
"""
|
||||||
try:
|
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)
|
data.pop(entry_name)
|
||||||
except KeyError:
|
self.write_data(data)
|
||||||
raise ValueError(f"No entry with name \"{entry_name}\"") from KeyError
|
|
||||||
self.__write_file_data(data)
|
|
||||||
|
|
||||||
def edit_entry_name(self, entry_name: str, new_entry_name: str) -> None:
|
def edit_entry_name(self, old_entry_name: str, new_entry_name: str) -> None:
|
||||||
if new_entry_name in self.get_entry_names():
|
"""
|
||||||
|
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")
|
raise ValueError(f"\"{new_entry_name}\" already exists")
|
||||||
if entry_name not in self.get_entry_names():
|
if old_entry_name not in data:
|
||||||
raise ValueError(f"No entry with name \"{entry_name}\"")
|
raise ValueError(f"No entry with name \"{old_entry_name}\"")
|
||||||
|
|
||||||
fields: dict[str, str] = self.__get_file_data()[entry_name]
|
self.delete_entry(old_entry_name)
|
||||||
self.delete_entry(entry_name)
|
self.add_entry(new_entry_name, data[old_entry_name])
|
||||||
data = self.__get_file_data()
|
|
||||||
data[new_entry_name] = fields
|
|
||||||
self.__write_file_data(data)
|
|
||||||
|
|
||||||
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,
|
entry_name: str,
|
||||||
field_name: str,
|
field_name: str,
|
||||||
field_value: str,
|
field_value: str,
|
||||||
password: str
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.verify_password(password)
|
"""
|
||||||
if entry_name not in self.get_entry_names():
|
Adds a new field to a given entry.
|
||||||
raise ValueError(f"No entry with name {entry_name}")
|
Parameters:-
|
||||||
if field_name in self.get_field_names(entry_name):
|
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}")
|
raise ValueError(f"Field {field_name} already exists under entry {entry_name}")
|
||||||
|
data[entry_name][field_name] = field_value
|
||||||
data: dict[str, dict[str, str]] = self.__get_file_data()
|
self.write_data(data)
|
||||||
data[entry_name][field_name] = EncryptionHandler.encrypt(
|
|
||||||
field_value.encode(),
|
|
||||||
password.encode()
|
|
||||||
).decode("unicode-escape")
|
|
||||||
self.__write_file_data(data)
|
|
||||||
|
|
||||||
def delete_field(self, entry_name: str, field_name: str) -> None:
|
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}\"")
|
raise ValueError(f"No entry with name \"{entry_name}\"")
|
||||||
if field_name not in self.get_field_names(entry_name):
|
if field_name not in data[entry_name]:
|
||||||
raise ValueError(f"{entry_name} does not have field {field_name}")
|
raise ValueError(f"No field {field_name} under entry {entry_name}")
|
||||||
data = self.__get_file_data()
|
|
||||||
data[entry_name].pop(field_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:
|
def edit_field_name(
|
||||||
if entry_name not in self.get_entry_names():
|
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}\"")
|
raise ValueError(f"No entry with name \"{entry_name}\"")
|
||||||
if field_name not in self.get_field_names(entry_name):
|
if old_field_name not in data[entry_name]:
|
||||||
raise ValueError(f"{entry_name} does not have field {field_name}")
|
raise ValueError(f"No field {old_field_name} under entry {entry_name}")
|
||||||
if new_field_name in self.get_field_names(entry_name):
|
if new_field_name in data[entry_name]:
|
||||||
raise ValueError(f"{entry_name} does has a field {new_field_name}")
|
raise ValueError(f"Field {new_field_name} already exists under {entry_name}")
|
||||||
field_data = self.__get_file_data()[entry_name][field_name]
|
data[entry_name][new_field_name] = data[entry_name][old_field_name]
|
||||||
self.delete_field(entry_name, field_name)
|
data[entry_name].pop(old_field_name)
|
||||||
data = self.__get_file_data()
|
self.write_data(data)
|
||||||
data[entry_name][new_field_name] = field_data
|
|
||||||
self.__write_file_data(data)
|
|
||||||
|
|
||||||
def edit_field_value(self,
|
def edit_field_value(self,
|
||||||
entry_name: str,
|
entry_name: str,
|
||||||
field_name: str,
|
field_name: str,
|
||||||
new_value: str,
|
new_value: str,
|
||||||
password: str
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.verify_password(password)
|
"""
|
||||||
if entry_name not in self.get_entry_names():
|
Change the value of the given field under the given entry.
|
||||||
raise ValueError(f"No entry called {entry_name}")
|
Parameters:-
|
||||||
if field_name not in self.get_field_names(entry_name):
|
entry_name
|
||||||
raise ValueError(f"No field called {field_name} under {entry_name}")
|
field_name
|
||||||
self.delete_field(entry_name, field_name)
|
new_value
|
||||||
self.add_field(entry_name, field_name, new_value, password)
|
Raises:-
|
||||||
|
If entry_name does not exist, a ValueError will be raised.
|
||||||
def get_field_data(self, entry_name: str, field_name: str, password: str) -> str:
|
If field_name does not exists, a ValueError will be raised.
|
||||||
self.verify_password(password)
|
"""
|
||||||
if field_name not in self.get_field_names(entry_name):
|
data: dict[str, dict[str, str]] = self.get_data()
|
||||||
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()
|
|
||||||
if entry_name not in data:
|
if entry_name not in data:
|
||||||
raise ValueError(f"No entry with name {entry_name}.")
|
raise ValueError(f"No entry with name \"{entry_name}\"")
|
||||||
return list(data[entry_name].keys())
|
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,
|
def test():
|
||||||
entry_name: str,
|
#DataHandler.create_user("Kosh", "p")
|
||||||
fields: dict[str, str],
|
d = DataHandler("Kosh", "p")
|
||||||
password: str
|
d.edit_field_value("Not Amazon", "p", "v")
|
||||||
) -> None:
|
|
||||||
self.verify_password(password)
|
|
||||||
if entry_name in self.get_entry_names():
|
if __name__ == "__main__":
|
||||||
raise ValueError(f"Entry {entry_name} already exists.")
|
test()
|
||||||
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)
|
|
||||||
|
2
backend/requirements.txt
Normal file
2
backend/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
appdirs==1.4.4
|
||||||
|
pycryptodome==3.18.0
|
Loading…
x
Reference in New Issue
Block a user