mirror of https://github.com/nucypher/nucypher.git
commit
af249f91b4
69
cli/main.py
69
cli/main.py
|
@ -92,7 +92,8 @@ class NucypherClickConfig:
|
|||
"""Initialize contract agency and set them on config"""
|
||||
|
||||
if simulation is True:
|
||||
self.blockchain.interface._registry._swap_registry(filepath=self.sim_registry_filepath) # TODO: Public API for mirroring existing registry
|
||||
# TODO: Public API for mirroring existing registry
|
||||
self.blockchain.interface._registry._swap_registry(filepath=self.sim_registry_filepath)
|
||||
|
||||
self.token_agent = NucypherTokenAgent(blockchain=self.blockchain)
|
||||
self.miner_agent = MinerAgent(token_agent=self.token_agent)
|
||||
|
@ -133,15 +134,15 @@ def cli(config, verbose, version, config_file):
|
|||
def configure(config, action, config_file, config_root, temp):
|
||||
"""Manage the nucypher .ini configuration file"""
|
||||
|
||||
if temp:
|
||||
node_configuration = NodeConfiguration(temp=temp, auto_initialize=True)
|
||||
elif config_root:
|
||||
node_configuration = NodeConfiguration(config_root=config_root)
|
||||
else:
|
||||
if config_root:
|
||||
node_configuration = NodeConfiguration(config_root=config_root, auto_initialize=False)
|
||||
elif temp:
|
||||
node_configuration = NodeConfiguration(temp=temp, auto_initialize=False)
|
||||
elif config_file:
|
||||
click.echo("Using configuration file at: {}".format(config_file))
|
||||
node_configuration = NodeConfiguration.from_configuration_file(filepath=config_file)
|
||||
|
||||
click.echo("Using configuration directory: {}".format(node_configuration.config_root))
|
||||
else:
|
||||
node_configuration = NodeConfiguration() # Fully Default
|
||||
|
||||
def __destroy():
|
||||
click.confirm("Permanently destroy all nucypher configurations, known nodes, certificates and keys?", abort=True)
|
||||
|
@ -149,25 +150,24 @@ def configure(config, action, config_file, config_root, temp):
|
|||
click.echo("Deleted configuration files at {}".format(node_configuration.config_root))
|
||||
|
||||
def __initialize():
|
||||
# TODO: temp config message?
|
||||
click.confirm("Initialize new nucypher configuration?", abort=True)
|
||||
node_configuration.initialize_configuration()
|
||||
click.echo("Created configuration files at {}".format(node_configuration.config_root))
|
||||
|
||||
if action == "validate":
|
||||
is_valid = validate_configuration_file(config_file)
|
||||
result = 'Valid' if is_valid else 'Invalid'
|
||||
click.echo('{} is {}'.format(config_file, result))
|
||||
|
||||
elif action == "init":
|
||||
if action == "init":
|
||||
__initialize()
|
||||
|
||||
elif action == "destroy":
|
||||
__destroy()
|
||||
|
||||
elif action == "reset":
|
||||
__destroy()
|
||||
__initialize()
|
||||
|
||||
elif action == "validate":
|
||||
is_valid = validate_configuration_file(config_file)
|
||||
result = 'Valid' if is_valid else 'Invalid'
|
||||
click.echo('{} is {}'.format(config_file, result))
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument('action', default='list', required=False)
|
||||
|
@ -665,15 +665,15 @@ def status(config, provider, contracts, network):
|
|||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--config-file', type=click.Path())
|
||||
@click.option('--federated-only', is_flag=True, default=False)
|
||||
@click.option('--dev', is_flag=True, default=False)
|
||||
@click.option('--rest-host', type=str, default='localhost')
|
||||
@click.option('--rest-port', type=int, default=UrsulaConfiguration.DEFAULT_REST_PORT)
|
||||
@click.option('--dev', is_flag=True, default=True)
|
||||
@click.option('--federated-only', is_flag=True)
|
||||
@click.option('--rest-host', type=str)
|
||||
@click.option('--rest-port', type=int)
|
||||
@click.option('--db-name', type=str)
|
||||
@click.option('--checksum-address', type=str)
|
||||
@click.option('--teacher-uri', type=str)
|
||||
@click.option('--metadata-dir', type=click.Path())
|
||||
@click.option('--config-file', type=click.Path())
|
||||
def run_ursula(rest_port,
|
||||
rest_host,
|
||||
db_name,
|
||||
|
@ -697,13 +697,16 @@ def run_ursula(rest_port,
|
|||
but can be overridden (mostly for testing purposes) with inline cli options.
|
||||
|
||||
"""
|
||||
if not dev:
|
||||
click.echo("WARNING: Development mode is disabled")
|
||||
|
||||
temp = True if dev else False
|
||||
|
||||
if config_file:
|
||||
ursula_config = UrsulaConfiguration.from_configuration_file(filepath=config_file)
|
||||
else:
|
||||
ursula_config = UrsulaConfiguration(temp=temp,
|
||||
auto_initialize=temp,
|
||||
auto_initialize=dev,
|
||||
rest_host=rest_host,
|
||||
rest_port=rest_port,
|
||||
db_name=db_name,
|
||||
|
@ -719,27 +722,33 @@ def run_ursula(rest_port,
|
|||
ursula = ursula_config.produce()
|
||||
if teacher_uri:
|
||||
|
||||
# TODO: Validate and handle teacher URI paring here
|
||||
if 'http' not in teacher_uri:
|
||||
teacher_uri = 'https://'+teacher_uri
|
||||
url = urlparse(url=teacher_uri)
|
||||
host, port = url.hostname, url.port
|
||||
|
||||
teacher_config = UrsulaConfiguration(temp=True,
|
||||
auto_initialize=True,
|
||||
rest_host=host,
|
||||
rest_port=port,
|
||||
is_me=False,
|
||||
federated_only=federated_only,
|
||||
known_nodes=(ursula, ))
|
||||
teacher = teacher_config.produce()
|
||||
|
||||
# Know each other
|
||||
# ursula.remember_node(teacher)
|
||||
teacher_ursula = teacher_config.produce()
|
||||
|
||||
ursula.start_learning_loop() # Enter learning loop
|
||||
ursula.get_deployer().run() # Run TLS Deployer
|
||||
|
||||
if not federated_only:
|
||||
ursula.stake() # Start staking daemon
|
||||
try:
|
||||
ursula.start_learning_loop() # Enter learning loop
|
||||
ursula.get_deployer().run() # Run TLS Deployer (Reactor)
|
||||
if not federated_only: # TODO: Resume / Init
|
||||
ursula.stake() # Start staking daemon
|
||||
finally:
|
||||
# Cleanup
|
||||
if ursula_config.temp:
|
||||
click.echo("Cleaning up temporary runtime files and directories")
|
||||
ursula_config.cleanup()
|
||||
click.echo("Exited gracefully") # TODO: Integrate with other graceful shutdown functionality
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -394,9 +394,9 @@ class Ursula(Character, VerifiableNode, Miner):
|
|||
# Ursula
|
||||
rest_host,
|
||||
rest_port,
|
||||
certificate=None,
|
||||
certificate=None, # TODO: from_certificate classmethod instead, use only filepath..?
|
||||
certificate_filepath: str = None,
|
||||
db_name=None,
|
||||
db_name=None, # TODO: deprecate db_name, use only filepath.?
|
||||
db_filepath: str = None,
|
||||
is_me=True,
|
||||
interface_signature=None,
|
||||
|
@ -422,7 +422,8 @@ class Ursula(Character, VerifiableNode, Miner):
|
|||
|
||||
self._work_orders = []
|
||||
|
||||
Character.__init__(self, is_me=is_me,
|
||||
Character.__init__(self,
|
||||
is_me=is_me,
|
||||
checksum_address=checksum_address,
|
||||
always_be_learning=always_be_learning,
|
||||
federated_only=federated_only,
|
||||
|
@ -492,7 +493,7 @@ class Ursula(Character, VerifiableNode, Miner):
|
|||
# Unless the caller passed a crypto power, we'll make our own TLSHostingPower for this stranger.
|
||||
rest_server = ProxyRESTServer(
|
||||
rest_host=rest_host,
|
||||
rest_port=rest_port,
|
||||
rest_port=rest_port
|
||||
)
|
||||
if certificate or certificate_filepath:
|
||||
tls_hosting_power = TLSHostingPower(rest_server=rest_server,
|
||||
|
|
|
@ -5,10 +5,12 @@ from os.path import abspath
|
|||
from constant_sorrow import constants
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
|
||||
from cryptography.x509 import Certificate
|
||||
|
||||
from nucypher.blockchain.eth.agents import EthereumContractAgent
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_FILE_LOCATION
|
||||
from nucypher.config.node import NodeConfiguration
|
||||
from nucypher.crypto.powers import CryptoPower
|
||||
|
||||
|
||||
class UrsulaConfiguration(NodeConfiguration):
|
||||
|
@ -16,22 +18,24 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
DEFAULT_TLS_CURVE = ec.SECP384R1
|
||||
DEFAULT_REST_HOST = 'localhost'
|
||||
DEFAULT_REST_PORT = 9151
|
||||
DEFAULT_DB_TEMPLATE = "ursula.{port}.db"
|
||||
__REGISTRY_NAME = 'contract_registry.json'
|
||||
|
||||
def __init__(self,
|
||||
rest_host: str = DEFAULT_REST_HOST,
|
||||
rest_port: int = DEFAULT_REST_PORT,
|
||||
rest_host: str = None,
|
||||
rest_port: int = None,
|
||||
|
||||
# TLS
|
||||
tls_curve: EllipticCurve = DEFAULT_TLS_CURVE,
|
||||
tls_private_key=None,
|
||||
certificate: bytes = None,
|
||||
tls_curve: EllipticCurve = None,
|
||||
tls_private_key: bytes = None,
|
||||
certificate: Certificate = None,
|
||||
certificate_filepath: str = None,
|
||||
|
||||
# Ursula
|
||||
db_name: str = None,
|
||||
db_filepath: str = None,
|
||||
interface_signature=None,
|
||||
crypto_power=None,
|
||||
crypto_power: CryptoPower = None,
|
||||
|
||||
# Blockchain
|
||||
miner_agent: EthereumContractAgent = None,
|
||||
|
@ -42,20 +46,17 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
) -> None:
|
||||
|
||||
# REST
|
||||
self.rest_host = rest_host
|
||||
self.rest_port = rest_port
|
||||
self.db_name = db_name or "ursula.{port}.db".format(port=self.rest_port)
|
||||
self.rest_host = rest_host or self.DEFAULT_REST_HOST
|
||||
self.rest_port = rest_port or self. DEFAULT_REST_PORT
|
||||
self.db_name = db_name or self.DEFAULT_DB_TEMPLATE.format(port=self.rest_port)
|
||||
self.db_filepath = db_filepath or constants.UNINITIALIZED_CONFIGURATION
|
||||
|
||||
#
|
||||
# TLS
|
||||
#
|
||||
self.tls_curve = tls_curve
|
||||
self.tls_curve = tls_curve or self.DEFAULT_TLS_CURVE
|
||||
self.tls_private_key = tls_private_key
|
||||
self.certificate: bytes = certificate
|
||||
|
||||
# if certificate_filepath is None:
|
||||
# certificate_filepath = certificate_filepath or os.path.join(self.known_certificates_dir, 'ursula.pem')
|
||||
self.certificate = certificate
|
||||
self.certificate_filepath = certificate_filepath
|
||||
|
||||
# Ursula
|
||||
|
@ -79,10 +80,18 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
instance = cls(**{**payload, **overrides})
|
||||
return instance
|
||||
|
||||
def generate_runtime_filepaths(self):
|
||||
super().generate_runtime_filepaths()
|
||||
self.db_filepath = os.path.join(self.config_root, self.db_name)
|
||||
self.registry_filepath = os.path.join(self.config_root, 'contract_registry.json')
|
||||
def _generate_runtime_filepaths(self, commit=True) -> dict:
|
||||
base_filepaths = super()._generate_runtime_filepaths(commit=commit)
|
||||
# TODO: Handle pre-existing certificates, injecting the path
|
||||
# if not self.certificate_filepath:
|
||||
# certificate_filepath = certificate_filepath or os.path.join(self.known_certificates_dir, 'ursula.pem')
|
||||
filepaths = dict(db_filepath=os.path.join(self.config_root, self.db_name),
|
||||
registry_filepath=os.path.join(self.config_root, self.__REGISTRY_NAME))
|
||||
if commit:
|
||||
for field, filepath in filepaths.items():
|
||||
setattr(self, field, filepath)
|
||||
base_filepaths.update(filepaths)
|
||||
return filepaths
|
||||
|
||||
@property
|
||||
def payload(self) -> dict:
|
||||
|
@ -99,7 +108,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
tls_curve=self.tls_curve,
|
||||
tls_private_key=self.tls_private_key,
|
||||
certificate=self.certificate,
|
||||
# certificate_filepath=self.certificate_filepath, # TODO
|
||||
# certificate_filepath=self.certificate_filepath, # TODO: Handle existing certificates, injecting the path
|
||||
|
||||
# Ursula
|
||||
interface_signature=self.interface_signature,
|
||||
|
@ -121,7 +130,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
ursula = Ursula(**merged_parameters)
|
||||
|
||||
if self.temp:
|
||||
class MockDatastoreThreadPool(object):
|
||||
class MockDatastoreThreadPool(object): # TODO: Does this belong here..?
|
||||
def callInThread(self, f, *args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
ursula.datastore_threadpool = MockDatastoreThreadPool()
|
||||
|
@ -144,7 +153,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
class AliceConfiguration(NodeConfiguration):
|
||||
|
||||
def __init__(self,
|
||||
policy_agent=None,
|
||||
policy_agent: EthereumContractAgent = None,
|
||||
*args, **kwargs
|
||||
) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
@ -8,11 +8,14 @@ from constant_sorrow import constants
|
|||
from itertools import islice
|
||||
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, DEFAULT_CONFIG_FILE_LOCATION, TEMPLATE_CONFIG_FILE_LOCATION
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
|
||||
|
||||
class NodeConfiguration:
|
||||
|
||||
DEFAULT_OPERATING_MODE = 'decentralized'
|
||||
TEMP_CONFIGURATION_DIR_PREFIX = "nucypher-tmp-cli-"
|
||||
DEFAULT_NETWORK_MIDDLEWARE_CLASS = RestMiddleware
|
||||
|
||||
class ConfigurationError(RuntimeError):
|
||||
pass
|
||||
|
@ -28,12 +31,12 @@ class NodeConfiguration:
|
|||
|
||||
checksum_address: str = None,
|
||||
is_me: bool = True,
|
||||
federated_only: bool = False,
|
||||
network_middleware=None,
|
||||
federated_only: bool = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
|
||||
# Informant
|
||||
known_metadata_dir: str = None,
|
||||
start_learning_on_same_thread: bool = True,
|
||||
start_learning_on_same_thread: bool = False,
|
||||
abort_on_learning_error: bool = False,
|
||||
always_be_learning: bool = True,
|
||||
known_nodes: Iterable = None,
|
||||
|
@ -41,30 +44,50 @@ class NodeConfiguration:
|
|||
|
||||
) -> None:
|
||||
|
||||
self.__temp_dir = constants.NO_TEMPORARY_CONFIGURATION
|
||||
#
|
||||
# Configuration root
|
||||
#
|
||||
self.temp = temp
|
||||
self.__temp_dir = constants.LIVE_CONFIGURATION
|
||||
if temp and not config_root:
|
||||
self.__temp_dir = constants.UNINITIALIZED_TEMPORARY_CONFIGURATION
|
||||
config_root = constants.UNINITIALIZED_TEMPORARY_CONFIGURATION
|
||||
# Create a temp dir and set it as the config root if no config root was specified
|
||||
self.__temp_dir = constants.UNINITIALIZED_CONFIGURATION
|
||||
config_root = constants.UNINITIALIZED_CONFIGURATION
|
||||
elif not config_root:
|
||||
config_root = DEFAULT_CONFIG_ROOT
|
||||
|
||||
self.temp = temp
|
||||
self.config_root = config_root
|
||||
self.config_file_location = config_file_location
|
||||
|
||||
#
|
||||
# Node Filepaths (Configuration root files and subdirectories)
|
||||
#
|
||||
self.config_file_location = config_file_location
|
||||
self.keyring_dir = keyring_dir or constants.UNINITIALIZED_CONFIGURATION
|
||||
self.known_nodes_dir = constants.UNINITIALIZED_CONFIGURATION
|
||||
self.known_certificates_dir = known_metadata_dir or constants.UNINITIALIZED_CONFIGURATION
|
||||
self.known_metadata_dir = known_metadata_dir or constants.UNINITIALIZED_CONFIGURATION
|
||||
|
||||
if auto_initialize:
|
||||
self.initialize_configuration()
|
||||
self.initialize_configuration() # <<< Write runtime files and dirs
|
||||
|
||||
self.checksum_address = checksum_address
|
||||
self.is_me = is_me
|
||||
#
|
||||
# Node
|
||||
#
|
||||
if not federated_only: # TODO: get_config function?
|
||||
federated_only = True if self.DEFAULT_OPERATING_MODE is 'federated' else False
|
||||
self.federated_only = federated_only
|
||||
|
||||
if is_me:
|
||||
network_middleware = network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE_CLASS()
|
||||
self.network_middleware = network_middleware
|
||||
|
||||
#
|
||||
# Identity
|
||||
#
|
||||
self.is_me = is_me
|
||||
self.checksum_address = checksum_address
|
||||
|
||||
# Learning
|
||||
self.known_nodes = known_nodes
|
||||
self.network_middleare = network_middleware
|
||||
self.start_learning_on_same_thread = start_learning_on_same_thread
|
||||
self.abort_on_learning_error = abort_on_learning_error
|
||||
self.always_be_learning = always_be_learning
|
||||
|
@ -75,23 +98,21 @@ class NodeConfiguration:
|
|||
template_file = stack.enter_context(open(TEMPLATE_CONFIG_FILE_LOCATION, 'r'))
|
||||
new_file = stack.enter_context(open(filepath, 'w+'))
|
||||
if new_file.read() != '':
|
||||
raise self.ConfigurationError("{} is not a blank file. Do you have an existing configuration?")
|
||||
for line in islice(template_file, 12, None):
|
||||
raise self.ConfigurationError("{} is not a blank file. Do you have an existing configuration file?")
|
||||
|
||||
for line in islice(template_file, 12, None): # chop the warning header
|
||||
new_file.writelines(line.lstrip(';')) # TODO Copy Default Sections, Perhaps interactively
|
||||
|
||||
def check_config_tree(self, configuration_dir: str = None) -> bool:
|
||||
def check_config_tree(self, configuration_dir: str = None) -> bool: # TODO: more filesystem validation
|
||||
path = configuration_dir if configuration_dir else self.config_root
|
||||
if not os.path.exists(path):
|
||||
raise self.ConfigurationError(
|
||||
'No Nucypher configuration directory found at {}.'.format(configuration_dir))
|
||||
'No configuration directory found at {}.'.format(configuration_dir))
|
||||
return True
|
||||
|
||||
def generate_runtime_filepaths(self) -> None:
|
||||
"""Dynamically generate paths based on configuration root directory"""
|
||||
self.keyring_dir = os.path.join(self.config_root, 'keyring')
|
||||
self.known_nodes_dir = os.path.join(self.config_root, 'known_nodes')
|
||||
self.known_certificates_dir = os.path.join(self.config_root, 'certificates')
|
||||
self.known_metadata_dir = os.path.join(self.config_root, 'metadata')
|
||||
@property
|
||||
def runtime_filepaths(self):
|
||||
return self._generate_runtime_filepaths(commit=False)
|
||||
|
||||
@property
|
||||
def payload(self):
|
||||
|
@ -107,7 +128,7 @@ class NodeConfiguration:
|
|||
start_learning_on_same_thread=self.start_learning_on_same_thread,
|
||||
abort_on_learning_error=self.abort_on_learning_error,
|
||||
always_be_learning=self.always_be_learning,
|
||||
network_middleware=self.network_middleare,
|
||||
network_middleware=self.network_middleware,
|
||||
|
||||
# Knowledge
|
||||
known_nodes=self.known_nodes,
|
||||
|
@ -116,6 +137,24 @@ class NodeConfiguration:
|
|||
)
|
||||
return base_payload
|
||||
|
||||
def _generate_runtime_filepaths(self, commit=True) -> dict:
|
||||
"""Dynamically generate paths based on configuration root directory"""
|
||||
if self.temp and commit and self.config_root is constants.UNINITIALIZED_CONFIGURATION:
|
||||
raise self.ConfigurationError("Cannot pre-generate filepaths for temporary node configurations.")
|
||||
filepaths = dict(config_root=self.config_root,
|
||||
keyring_dir=os.path.join(self.config_root, 'keyring'),
|
||||
known_nodes_dir=os.path.join(self.config_root, 'known_nodes'),
|
||||
known_certificates_dir=os.path.join(self.config_root, 'certificates'),
|
||||
known_metadata_dir=os.path.join(self.config_root, 'metadata'))
|
||||
if commit:
|
||||
for field, filepath in filepaths.items():
|
||||
setattr(self, field, filepath)
|
||||
return filepaths
|
||||
|
||||
def cleanup(self):
|
||||
if self.temp:
|
||||
self.__temp_dir.cleanup()
|
||||
|
||||
def initialize_configuration(self) -> str:
|
||||
"""Create the configuration and runtime directory tree starting with thr config root directory."""
|
||||
|
||||
|
@ -123,11 +162,11 @@ class NodeConfiguration:
|
|||
# Create Config Root
|
||||
#
|
||||
|
||||
if self.temp:
|
||||
if self.temp and self.config_root is constants.UNINITIALIZED_CONFIGURATION:
|
||||
self.__temp_dir = TemporaryDirectory(prefix=self.TEMP_CONFIGURATION_DIR_PREFIX)
|
||||
self.config_root = self.__temp_dir.name
|
||||
|
||||
else:
|
||||
if not self.temp and self.config_root is not constants.UNINITIALIZED_CONFIGURATION:
|
||||
try:
|
||||
os.mkdir(self.config_root, mode=0o755)
|
||||
except FileExistsError:
|
||||
|
@ -141,14 +180,18 @@ class NodeConfiguration:
|
|||
# Create Config Subdirectories
|
||||
#
|
||||
|
||||
self.generate_runtime_filepaths()
|
||||
self._generate_runtime_filepaths(commit=True)
|
||||
try:
|
||||
os.mkdir(self.keyring_dir, mode=0o700) # keyring
|
||||
os.mkdir(self.known_nodes_dir, mode=0o755) # known_nodes
|
||||
os.mkdir(self.known_certificates_dir, mode=0o755) # known_certs
|
||||
os.mkdir(self.known_metadata_dir, mode=0o755) # known_metadata
|
||||
except FileExistsError:
|
||||
raise # TODO
|
||||
# TODO: beef up the error message
|
||||
# existing_paths = [os.path.join(self.config_root, f) for f in os.listdir(self.config_root)]
|
||||
# NodeConfiguration.ConfigurationError("There are existing files at {}".format())
|
||||
message = "There are pre-existing nucypher installation files at {}".format(self.config_root)
|
||||
raise NodeConfiguration.ConfigurationError(message)
|
||||
|
||||
self.check_config_tree(configuration_dir=self.config_root)
|
||||
return self.config_root
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import os
|
||||
|
||||
import datetime
|
||||
import os
|
||||
from random import SystemRandom
|
||||
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.x509 import Certificate
|
||||
from typing import Union
|
||||
from typing import Union, Tuple
|
||||
|
||||
import sha3
|
||||
from constant_sorrow import constants
|
||||
from cryptography import x509
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography.x509 import Certificate
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
from umbral import pre
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
|
||||
|
@ -72,7 +71,9 @@ def keccak_digest(*messages: bytes) -> bytes:
|
|||
return hash.digest()
|
||||
|
||||
|
||||
def ecdsa_sign(message: bytes, privkey: UmbralPrivateKey) -> bytes:
|
||||
def ecdsa_sign(message: bytes,
|
||||
privkey: UmbralPrivateKey
|
||||
) -> bytes:
|
||||
"""
|
||||
Accepts a hashed message and signs it with the private key given.
|
||||
|
||||
|
@ -86,11 +87,10 @@ def ecdsa_sign(message: bytes, privkey: UmbralPrivateKey) -> bytes:
|
|||
return signature_der_bytes
|
||||
|
||||
|
||||
def ecdsa_verify(
|
||||
message: bytes,
|
||||
signature: bytes,
|
||||
pubkey: UmbralPublicKey
|
||||
) -> bool:
|
||||
def ecdsa_verify(message: bytes,
|
||||
signature: bytes,
|
||||
pubkey: UmbralPublicKey
|
||||
) -> bool:
|
||||
"""
|
||||
Accepts a message and signature and verifies it with the
|
||||
provided public key.
|
||||
|
@ -132,19 +132,24 @@ def _save_tls_certificate(certificate: Certificate,
|
|||
return certificate_filepath
|
||||
|
||||
|
||||
def load_tls_certificate(filepath):
|
||||
with open(filepath, 'r') as certificate_file:
|
||||
cert = x509.load_pem_x509_certificate(certificate_file.read(),
|
||||
backend=default_backend())
|
||||
return cert
|
||||
def load_tls_certificate(filepath: str) -> Certificate:
|
||||
"""Deserialize an X509 certificate from a filepath"""
|
||||
try:
|
||||
with open(filepath, 'r') as certificate_file:
|
||||
cert = x509.load_pem_x509_certificate(certificate_file.read(),
|
||||
backend=default_backend())
|
||||
return cert
|
||||
except FileNotFoundError:
|
||||
raise # TODO: Better error message here
|
||||
|
||||
|
||||
def generate_self_signed_certificate(common_name,
|
||||
curve,
|
||||
host,
|
||||
certificate_dir,
|
||||
private_key=None,
|
||||
days_valid=365):
|
||||
def generate_self_signed_certificate(common_name: str,
|
||||
curve: EllipticCurve,
|
||||
host: str,
|
||||
certificate_dir: str,
|
||||
private_key: _EllipticCurvePrivateKey = None,
|
||||
days_valid: int = 365
|
||||
) -> Tuple[Certificate, _EllipticCurvePrivateKey, str]:
|
||||
|
||||
if not private_key:
|
||||
private_key = ec.generate_private_key(curve, default_backend())
|
||||
|
@ -165,19 +170,19 @@ def generate_self_signed_certificate(common_name,
|
|||
cert = cert.add_extension(x509.SubjectAlternativeName([x509.DNSName(host)]), critical=False)
|
||||
cert = cert.sign(private_key, hashes.SHA512(), default_backend())
|
||||
|
||||
# if certificate_dir:
|
||||
tls_certificate_filepath = _save_tls_certificate(cert, directory=certificate_dir, common_name=common_name)
|
||||
# else:
|
||||
# tls_certificate_filepath = constants.CERTIFICATE_NOT_SAVED
|
||||
if certificate_dir: # TODO: Make this more configurable?
|
||||
tls_certificate_filepath = _save_tls_certificate(cert, directory=certificate_dir, common_name=common_name)
|
||||
else:
|
||||
tls_certificate_filepath = constants.CERTIFICATE_NOT_SAVED
|
||||
|
||||
return cert, private_key, tls_certificate_filepath
|
||||
|
||||
|
||||
def encrypt_and_sign(recipient_pubkey_enc: UmbralPublicKey,
|
||||
plaintext: bytes,
|
||||
signer: Union["SignatureStamp", "_Constant"],
|
||||
sign_plaintext=True,
|
||||
) -> tuple:
|
||||
plaintext: bytes,
|
||||
signer: 'SignatureStamp',
|
||||
sign_plaintext: bool = True
|
||||
) -> Tuple[UmbralMessageKit, 'SignatureStamp']:
|
||||
|
||||
if signer is not constants.DO_NOT_SIGN:
|
||||
# The caller didn't expressly tell us not to sign; we'll sign.
|
||||
|
@ -197,7 +202,7 @@ def encrypt_and_sign(recipient_pubkey_enc: UmbralPublicKey,
|
|||
else:
|
||||
# Don't sign.
|
||||
signature = sig_header = constants.NOT_SIGNED
|
||||
alice_pubkey = None
|
||||
alice_pubkey = None # TODO: ..eh?
|
||||
ciphertext, capsule = pre.encrypt(recipient_pubkey_enc, sig_header + plaintext)
|
||||
message_kit = UmbralMessageKit(ciphertext=ciphertext, capsule=capsule)
|
||||
|
||||
|
|
|
@ -21,19 +21,16 @@ class ProxyRESTServer:
|
|||
log = getLogger("characters")
|
||||
|
||||
def __init__(self,
|
||||
rest_host,
|
||||
rest_port,
|
||||
routes=None,
|
||||
rest_host: str,
|
||||
rest_port: int,
|
||||
routes: 'ProxyRESTRoutes' = None,
|
||||
) -> None:
|
||||
self.rest_interface = InterfaceInfo(host=rest_host, port=rest_port)
|
||||
if routes:
|
||||
if routes: # if is me
|
||||
self.rest_app = routes.rest_app
|
||||
db_filepath = routes.db_filepath
|
||||
self.db_filepath = routes.db_filepath
|
||||
else:
|
||||
self.rest_app = constants.PUBLIC_ONLY
|
||||
db_filepath = constants.NO_DATABASE_CONNECTION
|
||||
|
||||
self.db_filepath = db_filepath
|
||||
|
||||
def rest_url(self):
|
||||
return "{}:{}".format(self.rest_interface.host, self.rest_interface.port)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import contextlib
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
|
@ -9,28 +11,50 @@ import pytest
|
|||
from nucypher.config.node import NodeConfiguration
|
||||
|
||||
|
||||
def test_initialize_temp_configuration_directory(temp_config_root):
|
||||
def test_initialize_configuration_directory():
|
||||
runner = CliRunner()
|
||||
|
||||
# Use the system temporary storage area
|
||||
result = runner.invoke(cli, ['configure', 'init', '--temp'], input='Y', catch_exceptions=False)
|
||||
assert '/tmp' in result.output, "Configuration not in system temporary directory"
|
||||
assert NodeConfiguration.TEMP_CONFIGURATION_DIR_PREFIX in result.output
|
||||
assert result.exit_code == 0
|
||||
|
||||
result = runner.invoke(cli, ['configure', 'destroy', '--temp'], input='Y', catch_exceptions=False)
|
||||
assert NodeConfiguration.TEMP_CONFIGURATION_DIR_PREFIX in result.output
|
||||
assert result.exit_code == 0
|
||||
# Use an custom configuration directory
|
||||
custom_filepath = '/tmp/nucypher-tmp-test-custom'
|
||||
|
||||
args = ['configure', 'init', '--config-root', temp_config_root]
|
||||
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
|
||||
assert '/tmp' in result.output, "Configuration not in system temporary directory"
|
||||
assert temp_config_root in result.output
|
||||
assert result.exit_code == 0
|
||||
try:
|
||||
os.mkdir(custom_filepath) # TODO: Create a top-level installation directory, instead of writing only subdirs
|
||||
|
||||
args = ['configure', 'destroy', '--config-root', temp_config_root]
|
||||
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
|
||||
assert temp_config_root in result.output
|
||||
assert result.exit_code == 0
|
||||
args = ['configure', 'init', '--config-root', custom_filepath]
|
||||
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
|
||||
assert '[y/N]' in result.output
|
||||
assert '/tmp' in result.output, "Configuration not in system temporary directory"
|
||||
assert 'Created' in result.output
|
||||
assert custom_filepath in result.output
|
||||
assert result.exit_code == 0
|
||||
assert os.path.isdir(custom_filepath)
|
||||
|
||||
assert True
|
||||
|
||||
# Ensure that there are not pre-existing configuration files at config_root
|
||||
with pytest.raises(NodeConfiguration.ConfigurationError):
|
||||
_result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
|
||||
|
||||
args = ['configure', 'destroy', '--config-root', custom_filepath]
|
||||
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
|
||||
assert '[y/N]' in result.output
|
||||
assert '/tmp' in result.output, "Configuration not in system temporary directory"
|
||||
assert 'Deleted' in result.output
|
||||
assert custom_filepath in result.output
|
||||
assert result.exit_code == 0
|
||||
assert not os.path.isdir(custom_filepath)
|
||||
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
shutil.rmtree(custom_filepath)
|
||||
|
||||
# # TODO: Integrate with run ursula
|
||||
|
||||
|
||||
@pytest.mark.skip("To be implemented")
|
||||
|
|
|
@ -1,13 +1,48 @@
|
|||
import pytest_twisted
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from twisted.internet import threads
|
||||
from twisted.internet.error import CannotListenError
|
||||
|
||||
from cli.main import cli
|
||||
from nucypher.characters.base import Learner
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
|
||||
|
||||
def test_run_lone_federated_ursula():
|
||||
runner = CliRunner()
|
||||
@pytest.mark.skip(reason="Handle second call to reactor.run, or multiproc")
|
||||
@pytest_twisted.inlineCallbacks
|
||||
def test_run_lone_federated_default_ursula():
|
||||
|
||||
args = ['run_ursula',
|
||||
'--temp',
|
||||
'--dev',
|
||||
'--federated-only',
|
||||
'--teacher-uri', 'localhost:5556']
|
||||
'--rest-port', '9999', # TODO: use different port to avoid premature ConnectionError with many test runs?
|
||||
]
|
||||
|
||||
runner = CliRunner()
|
||||
result = yield threads.deferToThread(runner.invoke(cli, args, catch_exceptions=False))
|
||||
# result = runner.invoke(cli, args, catch_exceptions=False) # TODO: Handle second call to reactor.run
|
||||
alone = "WARNING - Can't learn right now: Need some nodes to start learning from."
|
||||
time.sleep(Learner._SHORT_LEARNING_DELAY)
|
||||
assert alone in result.output
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Cannot start another Ursula on the same REST port
|
||||
with pytest.raises(CannotListenError):
|
||||
_result = runner.invoke(cli, args, catch_exceptions=False)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Handle second call to reactor.run, or multiproc")
|
||||
def test_federated_ursula_with_manual_teacher_uri():
|
||||
args = ['run_ursula',
|
||||
'--dev',
|
||||
'--federated-only',
|
||||
'--rest-port', '9091', # TODO: Test Constant?
|
||||
'--teacher-uri', 'localhost:{}'.format(UrsulaConfiguration.DEFAULT_REST_PORT)]
|
||||
|
||||
# TODO: Handle second call to reactor.run
|
||||
runner = CliRunner()
|
||||
result_with_teacher = runner.invoke(cli, args, catch_exceptions=False)
|
||||
pass
|
||||
assert result_with_teacher.exit_code == 0
|
||||
|
|
|
@ -12,7 +12,7 @@ def pytest_addoption(parser):
|
|||
parser.addoption("--runslow",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="run test even if they are marked as slow")
|
||||
help="run tests even if they are marked as slow")
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
|
|
|
@ -39,22 +39,23 @@ def tempfile_path():
|
|||
os.remove(path)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture(scope="module")
|
||||
def temp_dir_path():
|
||||
temp_dir = tempfile.TemporaryDirectory(prefix='nucypher-test-')
|
||||
yield temp_dir.name
|
||||
temp_dir.cleanup()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.fixture(scope="module")
|
||||
def temp_config_root(temp_dir_path):
|
||||
"""
|
||||
User is responsible for closing the file given at the path.
|
||||
"""
|
||||
default_node_config = NodeConfiguration(temp=True,
|
||||
auto_initialize=True,
|
||||
auto_initialize=False,
|
||||
config_root=temp_dir_path)
|
||||
yield default_node_config.config_root
|
||||
default_node_config.cleanup()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
@ -70,40 +71,36 @@ def test_keystore():
|
|||
#
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ursula_federated_test_config(temp_config_root):
|
||||
def ursula_federated_test_config():
|
||||
|
||||
ursula_config = UrsulaConfiguration(temp=True,
|
||||
auto_initialize=True,
|
||||
is_me=True,
|
||||
config_root=temp_config_root,
|
||||
rest_host="localhost",
|
||||
always_be_learning=False,
|
||||
abort_on_learning_error=True,
|
||||
federated_only=True)
|
||||
yield ursula_config
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ursula_decentralized_test_config(temp_config_root, three_agents):
|
||||
def ursula_decentralized_test_config(three_agents):
|
||||
token_agent, miner_agent, policy_agent = three_agents
|
||||
|
||||
ursula_config = UrsulaConfiguration(temp=True,
|
||||
auto_initialize=True,
|
||||
config_root=temp_config_root,
|
||||
is_me=True,
|
||||
rest_host="localhost",
|
||||
always_be_learning=False,
|
||||
abort_on_learning_error=True,
|
||||
miner_agent=miner_agent,
|
||||
federated_only=False)
|
||||
|
||||
yield ursula_config
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def alice_federated_test_config(federated_ursulas, temp_config_root):
|
||||
def alice_federated_test_config(federated_ursulas):
|
||||
config = AliceConfiguration(temp=True,
|
||||
is_me=True,
|
||||
auto_initialize=True,
|
||||
config_root=temp_config_root,
|
||||
is_me=True,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
known_nodes=federated_ursulas,
|
||||
federated_only=True,
|
||||
|
@ -112,20 +109,18 @@ def alice_federated_test_config(federated_ursulas, temp_config_root):
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def alice_blockchain_test_config(blockchain_ursulas, three_agents, temp_config_root):
|
||||
def alice_blockchain_test_config(blockchain_ursulas, three_agents):
|
||||
token_agent, miner_agent, policy_agent = three_agents
|
||||
etherbase, alice_address, bob_address, *everyone_else = token_agent.blockchain.interface.w3.eth.accounts
|
||||
|
||||
config = AliceConfiguration(temp=True,
|
||||
is_me=True,
|
||||
auto_initialize=True,
|
||||
config_root=temp_config_root,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
policy_agent=policy_agent,
|
||||
known_nodes=blockchain_ursulas,
|
||||
abort_on_learning_error=True,
|
||||
checksum_address=alice_address)
|
||||
|
||||
yield config
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
import pytest_twisted
|
||||
import requests
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
@ -45,5 +45,8 @@ def test_federated_nodes_connect_via_tls_and_verify(ursula_federated_test_config
|
|||
yield threads.deferToThread(check_node_with_cert, node, "test-cert")
|
||||
finally:
|
||||
os.remove("test-cert")
|
||||
#
|
||||
# def test_node_metadata_contains_proper_cert():
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="To be implemented")
|
||||
def test_node_metadata_contains_proper_cert():
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue