Skip to content

Commit 6a9572c

Browse files
authored
Merge pull request #11 from FragmentScreen/fix/keyring-alt
Keyring Alternative
2 parents c2e0fd5 + 3057ffb commit 6a9572c

File tree

4 files changed

+119
-11
lines changed

4 files changed

+119
-11
lines changed

fGOaria/classes/oauth.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .client_oauth import ClientOauth
33
from ..utils.imports_config import *
44
from .token import Token
5+
from ..utils.encryption import TokenEncryption
56

67

78
class OAuth :
@@ -16,6 +17,7 @@ def __init__(self) -> None:
1617
self.client_secret = os.getenv('ARIA_CLIENT_SECRET')
1718
self.token_str_key = os.getenv('ARIA_KEYRING')
1819
self.refresh_grant = os.getenv('ARIA_CONNECTION_REFRESH_GRANT')
20+
self.token_encryption = TokenEncryption()
1921

2022

2123
# LOGIN
@@ -79,19 +81,34 @@ def refresh_token(self, token : Token) -> Union[Token, None] :
7981
# KEYRING STORAGE
8082

8183
def get_keyring_token_data(self) -> Union[dict, None] :
82-
token_data_str = keyring.get_password(self.token_str_key, '')
83-
if not token_data_str :
84-
raise Exception(' Either the password entered is incorrect, or no access token is stored.')
85-
return json.loads(token_data_str)
84+
try:
85+
token_data_str = keyring.get_password(self.token_str_key, '')
86+
if not token_data_str :
87+
token_data = self.token_encryption.decrypt_token(self.password)
88+
if token_data:
89+
return token_data
90+
raise Exception(' Either the password entered is incorrect, or no access token is stored.')
91+
return json.loads(token_data_str)
92+
except Exception as e:
93+
token_data = self.token_encryption.decrypt_token(self.password)
94+
if token_data:
95+
return token_data
96+
raise Exception('Failed to retrieve token from both keyring and fallback storage.')
8697

8798
def set_token_keyring_data(self, token : Token) -> None :
8899
try :
89100
click.echo('Attempting to store Token...')
90101
token_json = json.dumps(token.to_dict())
91-
keyring.set_password(self.token_str_key, '', token_json)
92-
print_with_spaces('Token data successfully stored in keyring.')
93-
except key_err.PasswordSetError as e :
94-
logging.error(f"Error setting keyring: {e}")
102+
try:
103+
keyring.set_password(self.token_str_key, '', token_json)
104+
print_with_spaces('Token data successfully stored in keyring.')
105+
except key_err.PasswordSetError as e :
106+
logging.warning(f"Keyring storage failed, using fallback encryption: {e}")
107+
self.token_encryption.encrypt_token(token.to_dict(), self.password)
108+
print_with_spaces('Token data successfully stored using fallback encryption.')
109+
except Exception as e:
110+
logging.error(f"Error storing token: {e}")
111+
raise Exception("Failed to store token in both keyring and fallback storage.")
95112

96113

97114
# GET DATA OBJECTS

fGOaria/utils/encryption.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from cryptography.fernet import Fernet
2+
from cryptography.hazmat.primitives import hashes
3+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
4+
import base64
5+
import os
6+
import json
7+
import logging
8+
from pathlib import Path
9+
import platform
10+
11+
class TokenEncryption:
12+
def __init__(self):
13+
self.token_file = Path.home() / '.aria_token'
14+
self.salt_file = Path.home() / '.aria_salt'
15+
self._ensure_salt_exists()
16+
logging.info(f"TokenEncryption initialized on {platform.system()}")
17+
18+
def _ensure_salt_exists(self):
19+
"""Ensure the salt file exists and contains a valid salt."""
20+
try:
21+
if not self.salt_file.exists():
22+
salt = os.urandom(16)
23+
self.salt_file.write_bytes(salt)
24+
logging.info(f"Created new salt file at {self.salt_file}")
25+
else:
26+
salt = self.salt_file.read_bytes()
27+
if len(salt) != 16:
28+
salt = os.urandom(16)
29+
self.salt_file.write_bytes(salt)
30+
logging.info("Regenerated invalid salt file")
31+
except Exception as e:
32+
logging.error(f"Error managing salt file: {str(e)}")
33+
raise Exception(f"Failed to manage salt file: {str(e)}")
34+
35+
def _get_key(self, password: str) -> bytes:
36+
"""Derive an encryption key from the password using PBKDF2."""
37+
try:
38+
salt = self.salt_file.read_bytes()
39+
kdf = PBKDF2HMAC(
40+
algorithm=hashes.SHA256(),
41+
length=32,
42+
salt=salt,
43+
iterations=100000,
44+
)
45+
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
46+
return key
47+
except Exception as e:
48+
logging.error(f"Error deriving encryption key: {str(e)}")
49+
raise Exception(f"Failed to derive encryption key: {str(e)}")
50+
51+
def encrypt_token(self, token_data: dict, password: str) -> None:
52+
"""Encrypt and store token data."""
53+
try:
54+
key = self._get_key(password)
55+
f = Fernet(key)
56+
encrypted_data = f.encrypt(json.dumps(token_data).encode())
57+
self.token_file.write_bytes(encrypted_data)
58+
logging.info(f"Successfully encrypted and stored token at {self.token_file}")
59+
except Exception as e:
60+
logging.error(f"Error encrypting token: {str(e)}")
61+
raise Exception(f"Failed to encrypt token: {str(e)}")
62+
63+
def decrypt_token(self, password: str) -> dict:
64+
"""Decrypt and retrieve token data."""
65+
if not self.token_file.exists():
66+
logging.info("No token file found")
67+
return None
68+
69+
try:
70+
key = self._get_key(password)
71+
f = Fernet(key)
72+
encrypted_data = self.token_file.read_bytes()
73+
decrypted_data = f.decrypt(encrypted_data)
74+
logging.info("Successfully decrypted token")
75+
return json.loads(decrypted_data)
76+
except Exception as e:
77+
logging.error(f"Error decrypting token: {str(e)}")
78+
return None
79+
80+
def clear_token(self) -> None:
81+
"""Remove stored token data."""
82+
try:
83+
if self.token_file.exists():
84+
self.token_file.unlink()
85+
logging.info("Successfully cleared token file")
86+
except Exception as e:
87+
logging.error(f"Error clearing token file: {str(e)}")
88+
raise Exception(f"Failed to clear token file: {str(e)}")

requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
click==8.1.7
2+
requests==2.31.0
23
keyring==24.3.0
3-
PyYAML==6.0.1
4+
python-dotenv==1.0.0
45
questionary==2.0.1
5-
Requests==2.31.0
6+
PyYAML==6.0.1
7+
cryptography==42.0.2
68
setuptools==68.2.2

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
setup(
88
name='fandanGO-aria',
9-
version='2.2.0',
9+
version='2.2.1',
1010
description="ARIA connection for managing access and metadata deposition, primarily through fandanGO",
1111
long_description=long_description,
1212
long_description_content_type='text/markdown',
@@ -22,6 +22,7 @@
2222
'python-dotenv',
2323
'questionary',
2424
'PyYAML',
25+
'cryptography',
2526
],
2627
entry_points={
2728
'console_scripts': [

0 commit comments

Comments
 (0)