specter-desktop/tests/test_services.py
kdmukai 7b27c79907
Feature: Service integration - Swan (#1517)
* 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>
2022-01-13 11:47:28 +01:00

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 == {}