Add encrypted file cache of passwords
This commit is contained in:
parent
a5c08ab235
commit
e7d7a03e18
4 changed files with 118 additions and 16 deletions
|
@ -21,6 +21,8 @@ import binascii
|
||||||
import click
|
import click
|
||||||
import click_config_file
|
import click_config_file
|
||||||
import passpy
|
import passpy
|
||||||
|
import secretstorage
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
try:
|
try:
|
||||||
import requests
|
import requests
|
||||||
HAS_REQUESTS_LIB = True
|
HAS_REQUESTS_LIB = True
|
||||||
|
@ -101,8 +103,15 @@ class NextcloudHandler:
|
||||||
self.http = 'https'
|
self.http = 'https'
|
||||||
self.timeout = params.get('timeout', 3)
|
self.timeout = params.get('timeout', 3)
|
||||||
self.ssl = True
|
self.ssl = True
|
||||||
self.cached_passwords = []
|
self.encryption_pass = None
|
||||||
self.last_cache = -1
|
self._get_encryption_pass_from_keyring()
|
||||||
|
self.cache = {}
|
||||||
|
self.cache_filename = os.path.join(
|
||||||
|
os.environ['HOME'],
|
||||||
|
'.cache',
|
||||||
|
'nc_password_client.cache'
|
||||||
|
)
|
||||||
|
self._read_cache()
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
proxies = {
|
proxies = {
|
||||||
'http': params.get('https_proxy', ''),
|
'http': params.get('https_proxy', ''),
|
||||||
|
@ -190,6 +199,90 @@ class NextcloudHandler:
|
||||||
)
|
)
|
||||||
self.keychain = json.loads(decrypt(text, key))
|
self.keychain = json.loads(decrypt(text, key))
|
||||||
|
|
||||||
|
def _get_encryption_pass_from_keyring(self):
|
||||||
|
connection = secretstorage.dbus_init()
|
||||||
|
collection = secretstorage.get_default_collection(connection)
|
||||||
|
if collection.is_locked:
|
||||||
|
collection.unlock()
|
||||||
|
for item in collection.get_all_items():
|
||||||
|
if item.get_label() == 'nc_password_client':
|
||||||
|
self.encryption_pass = item.get_secret()
|
||||||
|
if self.encryption_pass is None:
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_get_encryption_pass_from_keyring",
|
||||||
|
"message": "There wasn't an encryption password in the keyring, will generate one"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.encryption_pass = Fernet.generate_key()
|
||||||
|
attributes = {
|
||||||
|
'application': 'nc_password_client',
|
||||||
|
}
|
||||||
|
item = collection.create_item('nc_password_client', attributes, self.encryption_pass)
|
||||||
|
else:
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_get_encryption_pass_from_keyring",
|
||||||
|
"message": "Encryption password obtained from keyring"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
collection.lock()
|
||||||
|
|
||||||
|
def _read_cache(self):
|
||||||
|
if os.path.exists(self.cache_filename):
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_read_cache",
|
||||||
|
"message": "Reading cached passwords"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
with open(self.cache_filename, 'r', encoding='utf-8') as cache_file:
|
||||||
|
content = cache_file.read()
|
||||||
|
if len(content) != 0:
|
||||||
|
cipher_suite = Fernet(self.encryption_pass)
|
||||||
|
self.cache = json.loads(cipher_suite.decrypt(content))
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_read_cache",
|
||||||
|
"last_update": self.cache['last_update'],
|
||||||
|
"total_cached_password": len(self.cache['cached_passwords'])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_read_cache",
|
||||||
|
"message": "The cache file was empty, so initializing cache"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.cache = {
|
||||||
|
"last_update": -1,
|
||||||
|
"cached_passwords": []
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_read_cache",
|
||||||
|
"message": "There wasn't a cache file, so initializing cache"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.cache = {
|
||||||
|
"last_update": -1,
|
||||||
|
"cached_passwords": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def _write_cache(self):
|
||||||
|
self.debug(
|
||||||
|
{
|
||||||
|
"action": "_write_cache",
|
||||||
|
"message": "Writing cached passwords to file"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
with open(self.cache_filename, 'bw') as cache_file:
|
||||||
|
cipher_suite = Fernet(self.encryption_pass)
|
||||||
|
encrypted_cache = cipher_suite.encrypt(bytes(json.dumps(self.cache), 'utf-8'))
|
||||||
|
cache_file.write(encrypted_cache)
|
||||||
|
|
||||||
def _safer_obj(self, obj, fields=None):
|
def _safer_obj(self, obj, fields=None):
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = ['password', 'token', 'secret', 'pass', 'passwd', 'hash']
|
fields = ['password', 'token', 'secret', 'pass', 'passwd', 'hash']
|
||||||
|
@ -523,18 +616,26 @@ class NextcloudHandler:
|
||||||
|
|
||||||
def list_passwords(self):
|
def list_passwords(self):
|
||||||
'''List all passwords'''
|
'''List all passwords'''
|
||||||
if self.last_cache < (time.time() - self.cache_duration):
|
if self.cache['last_update'] < (time.time() - self.cache_duration):
|
||||||
self.debug(
|
self.debug(
|
||||||
{ "action": "list_passwords", "message": "Updating cached list of password" }
|
{
|
||||||
|
"action": "list_passwords",
|
||||||
|
"message": "Updating cached list of password",
|
||||||
|
"last_update": self.cache['last_update'],
|
||||||
|
"cache_duration": self.cache_duration
|
||||||
|
}
|
||||||
)
|
)
|
||||||
self.cached_passwords = self.get("index.php/apps/passwords/api/1.0/password/list")
|
self.cache['cached_passwords'] = self.get(
|
||||||
if self.cached_passwords:
|
"index.php/apps/passwords/api/1.0/password/list"
|
||||||
self.last_cache = time.time()
|
)
|
||||||
|
if self.cache['cached_passwords']:
|
||||||
|
self.cache['last_update'] = time.time()
|
||||||
|
self._write_cache()
|
||||||
else:
|
else:
|
||||||
self.debug(
|
self.debug(
|
||||||
{ "action": "list_passwords", "message": "Reusing cached list of password" }
|
{ "action": "list_passwords", "message": "Reusing cached list of password" }
|
||||||
)
|
)
|
||||||
return self.cached_passwords
|
return self.cache['cached_passwords']
|
||||||
|
|
||||||
def list_passwords_folders(self):
|
def list_passwords_folders(self):
|
||||||
'''List passwords folders'''
|
'''List passwords folders'''
|
||||||
|
@ -674,7 +775,7 @@ class NextcloudHandler:
|
||||||
for password in all_passwords:
|
for password in all_passwords:
|
||||||
if self.is_same_password(obj, password):
|
if self.is_same_password(obj, password):
|
||||||
safer_password = dict(password, **{ 'password': '***' })
|
safer_password = dict(password, **{ 'password': '***' })
|
||||||
self._log.debug(
|
self.debug(
|
||||||
{
|
{
|
||||||
"action": "test_exists_password",
|
"action": "test_exists_password",
|
||||||
"message": "Same password already exists",
|
"message": "Same password already exists",
|
||||||
|
@ -683,7 +784,7 @@ class NextcloudHandler:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
self._log.debug(
|
self.debug(
|
||||||
{
|
{
|
||||||
"action": "test_exists_password",
|
"action": "test_exists_password",
|
||||||
"message": "No password match found",
|
"message": "No password match found",
|
||||||
|
@ -1050,7 +1151,7 @@ class NcPasswordClient:
|
||||||
{ "action": "created_password", "object": new_password }
|
{ "action": "created_password", "object": new_password }
|
||||||
)
|
)
|
||||||
count += 1
|
count += 1
|
||||||
self._log.debug(
|
self.debug(
|
||||||
{
|
{
|
||||||
"action": "migrate_pass",
|
"action": "migrate_pass",
|
||||||
"message": "Migrate Pass summary",
|
"message": "Migrate Pass summary",
|
||||||
|
@ -1098,7 +1199,7 @@ class NcPasswordClient:
|
||||||
for item in passwords:
|
for item in passwords:
|
||||||
for checked in checked_passwords:
|
for checked in checked_passwords:
|
||||||
if self.nc.is_same_password(checked, item):
|
if self.nc.is_same_password(checked, item):
|
||||||
self._log.debug(
|
self.debug(
|
||||||
{
|
{
|
||||||
"action": "remove_duplicate",
|
"action": "remove_duplicate",
|
||||||
"object": self.nc.delete_password(item)
|
"object": self.nc.delete_password(item)
|
||||||
|
@ -1109,7 +1210,7 @@ class NcPasswordClient:
|
||||||
checked_passwords.append(item)
|
checked_passwords.append(item)
|
||||||
if limit != -1 and count >= limit:
|
if limit != -1 and count >= limit:
|
||||||
break
|
break
|
||||||
self._log.debug(
|
self.debug(
|
||||||
{
|
{
|
||||||
"action": "remove_duplicates",
|
"action": "remove_duplicates",
|
||||||
"message": "Remove duplicates summary",
|
"message": "Remove duplicates summary",
|
||||||
|
|
|
@ -7,7 +7,7 @@ Homepage = "https://repos.susurrando.com/adelgado/nc_password_client"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "nc_password_client"
|
name = "nc_password_client"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
description = "Nextcloud Password client"
|
description = "Nextcloud Password client"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "Antonio J. Delgado", email = "ad@susurrando.com" }]
|
authors = [{ name = "Antonio J. Delgado", email = "ad@susurrando.com" }]
|
||||||
|
|
|
@ -3,3 +3,4 @@ click_config_file
|
||||||
requests
|
requests
|
||||||
pysodium
|
pysodium
|
||||||
passpy
|
passpy
|
||||||
|
secretstorage
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = nc_password_client
|
name = nc_password_client
|
||||||
version = 0.0.1
|
version = 0.0.2
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
packages = nc_password_client
|
packages = nc_password_client
|
||||||
|
|
Loading…
Reference in a new issue