mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
* fix create_order issue * fix url and better error-handling * basic balances tab * activating services * Service management * Improve settings page and default to it if token unset * Fix history tab * Fix trade error handling * Add withdraw * calling specter-cloud for creating vaultoro orders * Improve trade screen and fixes * sidebar fix * black * refactor Service integration * refactor to have Service Classes like manifests * maturity * dynamic initialisation of service-classes and blueprints * fix sidebar_services * migrated templates and static into vaultoro folder * refactor config to manifest * some minor things * swan initial * rename and fix test * adding ServiceApiKeyStorageUserAware * fix * store the access token * directory indirection to shield templates from each others blueprint * first attempts with automatic withdrawals * proper tab highlighting * Update service_apikey_storage.py * Update oauth2_success.jinja * Awaiting refresh_token support * Service logo display on Addresses * Associate addr with a Service * address-data component reorg Separates the presentation html from the data as much as possible. * Services data/icon added to tx History * Now hitting the updated Swan endpoint to save deposit addrs * Simplified injecting Services data into JS * Reducing js calls back to server in tx-data; templatizing utxo in/outs * renaming "reserving" to "associating" an Address with a Service. * rename `manifest.py` files to `service.py`. * rename "api_data" to "service_data" to make the storage a bit more generalized. `ServiceApiKeyStorage` is now `ServiceEncryptedStorage` to match. * Beginning of factoring out Swan api to its own `api.py` file; need to rectify with `swan_client.py`. * Removed tx-table/row/data changes and address-table/row/data Kept only the bare minimum changes required to display the Services icon, plus optimizations. * deleted no longer used CustomElement * Adding services docs to mkdocs * Configuration for Services * more clever configuration * fix test * deleted swan_client * Changing the address abbreviation format to 7...7 and little big fix for not vertically aligned addresses in Firefox. * Better state management if Auth method changes * Cleanup, better user messaging; pulling Service methods out of controller and User * Redirect to services endpoint after setting up authentication. * PR cleanup, bug fixes, test suite updates * Fixed test case problem * First fix for delete API key button. * Service hooks; Option to fully remove Swan Integration; Logout clears plaintext_user_secret * Restoring bugfix from @moneymanolis * cleanup and black * Make service-decovery in AppImage work * import hashlib, maybe fix cypress * Update service_encrypted_storage.py * further bugfix on update * TODO: remove debugging in client.py before first release * fix controller-test * fix singleton testing issues Co-authored-by: Kim Neunert <k9ert@gmx.de> Co-authored-by: benk10 <ben.kaufman10@gmail.com> Co-authored-by: Kim Neunert <kneunert@gmail.com> Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
218 lines
8.3 KiB
Python
218 lines
8.3 KiB
Python
import json
|
|
import pytest
|
|
|
|
from flask_login import current_user
|
|
from flask_login.utils import login_user, logout_user
|
|
from mock import patch, Mock
|
|
from tempfile import TemporaryDirectory
|
|
from unittest.mock import MagicMock
|
|
from unittest import TestCase
|
|
|
|
from cryptoadvance.specter.managers.user_manager import UserManager
|
|
from cryptoadvance.specter.server import SpecterFlask
|
|
from cryptoadvance.specter.services.service import Service
|
|
from cryptoadvance.specter.services.service_encrypted_storage import (
|
|
ServiceEncryptedStorage,
|
|
ServiceEncryptedStorageError,
|
|
ServiceEncryptedStorageManager,
|
|
)
|
|
from cryptoadvance.specter.services.service_manager import ServiceManager
|
|
from cryptoadvance.specter.user import User, hash_password
|
|
|
|
|
|
class FakeService(Service):
|
|
# A dummy Service just used by the test suite
|
|
id = "test_service"
|
|
name = "Test Service"
|
|
has_blueprint = False
|
|
|
|
|
|
# @patch("cryptoadvance.specter.services.service_manager.app")
|
|
# def test_ServiceManager_loads_services(empty_data_folder, app):
|
|
# # app.config = MagicMock()
|
|
# # app.config.get.return_value = "prod"
|
|
# specter_mock = MagicMock()
|
|
# specter_mock.data_folder.return_value = empty_data_folder
|
|
|
|
# service_manager = ServiceManager(specter=specter_mock, devstatus_threshold="alpha")
|
|
# services = service_manager.services
|
|
# assert "swan" in services
|
|
|
|
|
|
def test_ServiceEncryptedStorage(empty_data_folder):
|
|
specter_mock = Mock()
|
|
specter_mock.config = {"uid": ""}
|
|
specter_mock.user_manager = Mock()
|
|
specter_mock.user_manager.users = [""]
|
|
|
|
user1 = User.from_json(
|
|
user_dict={
|
|
"id": "user1",
|
|
"username": "user1",
|
|
"password": hash_password("somepassword"),
|
|
"config": {},
|
|
"is_admin": False,
|
|
"services": None,
|
|
},
|
|
specter=specter_mock,
|
|
)
|
|
user2 = User.from_json(
|
|
user_dict={
|
|
"id": "user2",
|
|
"username": "user2",
|
|
"password": hash_password("somepassword"),
|
|
"config": {},
|
|
"is_admin": False,
|
|
"services": None,
|
|
},
|
|
specter=specter_mock,
|
|
)
|
|
user1._generate_user_secret("muh")
|
|
|
|
# Can set and get service storage fields
|
|
service_storage = ServiceEncryptedStorage(empty_data_folder, user1)
|
|
service_storage.set_service_data("a_service_id", {"somekey": "green"})
|
|
assert service_storage.get_service_data("a_service_id") == {"somekey": "green"}
|
|
assert service_storage.get_service_data("another_service_id") == {}
|
|
|
|
# We expect a call for a user that isn't logged in to fail
|
|
with pytest.raises(ServiceEncryptedStorageError) as execinfo:
|
|
ServiceEncryptedStorage(empty_data_folder, user2)
|
|
assert "must be authenticated with password" in str(execinfo.value)
|
|
|
|
|
|
def test_access_encrypted_storage_after_login(app_no_node: SpecterFlask):
|
|
"""ServiceEncryptedStorage should be accessible (decryptable) after user login"""
|
|
# Create test users; automatically generates their `user_secret` and kept decrypted
|
|
# in memory.
|
|
user_manager: UserManager = app_no_node.specter.user_manager
|
|
user_manager.create_user(
|
|
user_id="bob",
|
|
username="bob",
|
|
plaintext_password="plain_pass_bob",
|
|
config={},
|
|
)
|
|
user_manager.create_user(
|
|
user_id="alice",
|
|
username="alice",
|
|
plaintext_password="plain_pass_alice",
|
|
config={},
|
|
)
|
|
|
|
# The ServiceEncryptedStorageManager singleton creates a mess in the test suite. Have
|
|
# to override its (potentially) already-set values with references to this test's
|
|
# new instances and temp storage dir.
|
|
storage_manager = ServiceEncryptedStorageManager.get_instance()
|
|
storage_manager.data_folder = user_manager.data_folder
|
|
storage_manager.user_manager = user_manager
|
|
storage_manager.storage_by_user = {}
|
|
|
|
# Need a simulated request context to enable `current_user` lookup
|
|
with app_no_node.test_request_context():
|
|
login_user(user_manager.get_user("bob"))
|
|
|
|
# Test user's service_data should be decryptable but initially empty
|
|
service_data = storage_manager.get_current_user_service_data(
|
|
service_id=FakeService.id
|
|
)
|
|
assert service_data == {}
|
|
storage_manager.update_current_user_service_data(
|
|
service_id=FakeService.id, service_data={"somekey": "green"}
|
|
)
|
|
|
|
logout_user()
|
|
|
|
# Meanwhile Alice's account storage will still be blank...
|
|
login_user(user_manager.get_user("alice"))
|
|
service_data = storage_manager.get_current_user_service_data(
|
|
service_id=FakeService.id
|
|
)
|
|
assert (
|
|
storage_manager.get_current_user_service_data(service_id=FakeService.id)
|
|
== {}
|
|
)
|
|
|
|
logout_user()
|
|
|
|
# ...while Bob's can be retrieved when he's logged in.
|
|
login_user(user_manager.get_user("bob"))
|
|
service_data = storage_manager.get_current_user_service_data(
|
|
service_id=FakeService.id
|
|
)
|
|
assert storage_manager.get_current_user_service_data(
|
|
service_id=FakeService.id
|
|
) == {"somekey": "green"}
|
|
|
|
|
|
def test_remove_all_services_from_user(app_no_node: SpecterFlask, empty_data_folder):
|
|
"""ServiceEncryptedStorage should be accessible (decryptable) after user login"""
|
|
# Create test users; automatically generates their `user_secret` and kept decrypted
|
|
# in memory.
|
|
user_manager: UserManager = app_no_node.specter.user_manager
|
|
user_manager.create_user(
|
|
user_id="bob",
|
|
username="bob",
|
|
plaintext_password="plain_pass_bob",
|
|
config={},
|
|
)
|
|
|
|
# The ServiceEncryptedStorageManager singleton creates a mess in the test suite. Have
|
|
# to override its (potentially) already-set values with references to this test's
|
|
# new instances and temp storage dir.
|
|
storage_manager = ServiceEncryptedStorageManager.get_instance()
|
|
storage_manager.data_folder = user_manager.data_folder
|
|
storage_manager.user_manager = user_manager
|
|
storage_manager.storage_by_user = {}
|
|
|
|
# Need a simulated request context to enable `current_user` lookup
|
|
with app_no_node.test_request_context():
|
|
user = user_manager.get_user("bob")
|
|
login_user(user)
|
|
|
|
# Test user's service_data should be decryptable but initially empty
|
|
service_data = storage_manager.get_current_user_service_data(
|
|
service_id=FakeService.id
|
|
)
|
|
assert service_data == {}
|
|
storage_manager.update_current_user_service_data(
|
|
service_id=FakeService.id, service_data={"somekey": "green"}
|
|
)
|
|
|
|
# Check on disk. The <user>_services.json should now have our data.
|
|
service_encrypted_storage_file = (
|
|
storage_manager._get_current_user_service_storage().data_file
|
|
)
|
|
with open(service_encrypted_storage_file, "r") as storage_json_file:
|
|
data_on_disk = json.load(storage_json_file)
|
|
|
|
# Can't test the actual values because they're encrypted, but the Service.id key is plaintext
|
|
assert FakeService.id in data_on_disk
|
|
|
|
# Now remove all
|
|
app_no_node.specter.service_manager.remove_all_services_from_user(user)
|
|
|
|
# Verify data on disk; Bob's user should have his user_secret cleared.
|
|
users_file = app_no_node.specter.user_manager.users_file
|
|
with open(users_file, "r") as storage_json_file:
|
|
data_on_disk = json.load(storage_json_file)
|
|
found_bob = False
|
|
for user_entry in data_on_disk:
|
|
if user_entry["id"] == "bob":
|
|
found_bob = True
|
|
assert user_entry.get("encrypted_user_secret") is None
|
|
assert found_bob
|
|
|
|
# With no `user_secret` the decryption attempt must fail.
|
|
with pytest.raises(ServiceEncryptedStorageError) as execinfo:
|
|
storage_manager._get_current_user_service_storage()
|
|
assert "must be authenticated with password" in str(execinfo.value)
|
|
|
|
# Should now be cleared in memory (have to request raw data since we can't
|
|
# decrypt)...
|
|
service_encrypted_storage = storage_manager.get_raw_encrypted_data(user)
|
|
assert service_encrypted_storage == {}
|
|
|
|
# ...and on disk
|
|
with open(service_encrypted_storage_file, "r") as storage_json_file:
|
|
data_on_disk = json.load(storage_json_file)
|
|
assert data_on_disk == {}
|