예제 #1
0
def test_initialize_alice_with_custom_configuration_root(custom_filepath, click_runner, monkeypatch):
    monkeypatch.delenv(NUCYPHER_ENVVAR_KEYRING_PASSWORD, raising=False)

    # Use a custom local filepath for configuration
    init_args = ('alice', 'init',
                 '--network', TEMPORARY_DOMAIN,
                 '--federated-only',
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, init_args, input=FAKE_PASSWORD_CONFIRMED, catch_exceptions=False)
    assert result.exit_code == 0

    # CLI Output
    assert str(MOCK_CUSTOM_INSTALLATION_PATH) in result.output, "Configuration not in system temporary directory"
    assert "nucypher alice run" in result.output, 'Help message is missing suggested command'
    assert 'IPv4' not in result.output

    # Files and Directories
    assert os.path.isdir(custom_filepath), 'Configuration file does not exist'
    assert os.path.isdir(os.path.join(custom_filepath, 'keyring')), 'Keyring does not exist'
    assert os.path.isdir(os.path.join(custom_filepath, 'known_nodes')), 'known_nodes directory does not exist'

    custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename())
    assert os.path.isfile(custom_config_filepath), 'Configuration file does not exist'

    # Auth
    assert 'Enter NuCypher keyring password' in result.output, 'WARNING: User was not prompted for password'
    assert 'Repeat for confirmation:' in result.output, 'User was not prompted to confirm password'
예제 #2
0
def test_initialize_alice_with_custom_configuration_root(custom_filepath, click_runner):

    # Use a custom local filepath for configuration
    init_args = ('alice', 'init',
                 '--network', TEMPORARY_DOMAIN,
                 '--federated-only',
                 '--config-root', custom_filepath)

    user_input = '{password}\n{password}'.format(password=INSECURE_DEVELOPMENT_PASSWORD, ip=MOCK_IP_ADDRESS)
    result = click_runner.invoke(nucypher_cli, init_args, input=user_input, catch_exceptions=False)
    assert result.exit_code == 0

    # CLI Output
    assert MOCK_CUSTOM_INSTALLATION_PATH in result.output, "Configuration not in system temporary directory"
    assert "nucypher alice run" in result.output, 'Help message is missing suggested command'
    assert 'IPv4' not in result.output

    # Files and Directories
    assert os.path.isdir(custom_filepath), 'Configuration file does not exist'
    assert os.path.isdir(os.path.join(custom_filepath, 'keyring')), 'Keyring does not exist'
    assert os.path.isdir(os.path.join(custom_filepath, 'known_nodes')), 'known_nodes directory does not exist'

    custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename())
    assert os.path.isfile(custom_config_filepath), 'Configuration file does not exist'

    # Auth
    assert 'Enter NuCypher keyring password' in result.output, 'WARNING: User was not prompted for password'
    assert 'Repeat for confirmation:' in result.output, 'User was not prompted to confirm password'
예제 #3
0
def test_alice_destroy(click_runner, custom_filepath):
    """Should be the last test since it deletes the configuration file"""
    custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename()
    destroy_args = ('alice', 'destroy', '--config-file', custom_config_filepath, '--force')
    result = click_runner.invoke(nucypher_cli, destroy_args, catch_exceptions=False)
    assert result.exit_code == 0
    assert SUCCESSFUL_DESTRUCTION in result.output
    assert not custom_config_filepath.exists(), "Alice config file was deleted"
예제 #4
0
def test_alice_view_preexisting_configuration(click_runner, custom_filepath):
    custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename())
    view_args = ('alice', 'config', '--config-file', custom_config_filepath)
    result = click_runner.invoke(nucypher_cli, view_args, input=FAKE_PASSWORD_CONFIRMED)
    assert result.exit_code == 0
    assert "checksum_address" in result.output
    assert "domain" in result.output
    assert TEMPORARY_DOMAIN in result.output
    assert str(custom_filepath) in result.output
예제 #5
0
def test_alice_destroy(click_runner, custom_filepath):
    custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename())
    destroy_args = ('alice', 'destroy',
                    '--config-file', custom_config_filepath,
                    '--force')

    result = click_runner.invoke(nucypher_cli, destroy_args, catch_exceptions=False)
    assert result.exit_code == 0
    assert SUCCESSFUL_DESTRUCTION in result.output
    assert not os.path.exists(custom_config_filepath), "Alice config file was deleted"
예제 #6
0
def test_alice_control_starts_with_preexisting_configuration(
        click_runner, custom_filepath):
    custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename(
    )
    run_args = ('alice', 'run', '--dry-run', '--lonely', '--config-file',
                str(custom_config_filepath.absolute()))
    result = click_runner.invoke(nucypher_cli,
                                 run_args,
                                 input=FAKE_PASSWORD_CONFIRMED)
    assert result.exit_code == 0, result.exception
예제 #7
0
def test_alice_control_starts_with_preexisting_configuration(click_runner, custom_filepath):

    custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename())

    run_args = ('alice', 'run',
                '--dry-run',
                '--config-file', custom_config_filepath)

    user_input = '{password}\n{password}\n'.format(password=INSECURE_DEVELOPMENT_PASSWORD)
    result = click_runner.invoke(nucypher_cli, run_args, input=user_input)
    assert result.exit_code == 0
예제 #8
0
def test_alice_make_card(click_runner, custom_filepath):
    custom_config_filepath = os.path.join(
        custom_filepath, AliceConfiguration.generate_filename())
    command = ('alice', 'make-card', '--nickname', 'flora', '--config-file',
               custom_config_filepath)
    result = click_runner.invoke(nucypher_cli,
                                 command,
                                 input=FAKE_PASSWORD_CONFIRMED,
                                 catch_exceptions=False)
    assert result.exit_code == 0
    assert "Saved new character card " in result.output
예제 #9
0
def test_alice_make_card(click_runner, custom_filepath, mocker):
    mock_save_card = mocker.patch.object(Card, 'save')
    custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename(
    )
    command = ('alice', 'make-card', '--nickname', 'flora', '--config-file',
               str(custom_config_filepath.absolute()))
    result = click_runner.invoke(nucypher_cli,
                                 command,
                                 input=FAKE_PASSWORD_CONFIRMED,
                                 catch_exceptions=False)
    assert result.exit_code == 0
    mock_save_card.assert_called_once()
    assert "Saved new character card " in result.output
예제 #10
0
def test_alice_view_with_preexisting_configuration(click_runner, custom_filepath):
    custom_config_filepath = os.path.join(custom_filepath, AliceConfiguration.generate_filename())

    view_args = ('alice', 'view',
                 '--config-file', custom_config_filepath)

    user_input = '{password}\n{password}\n'.format(password=INSECURE_DEVELOPMENT_PASSWORD)
    result = click_runner.invoke(nucypher_cli, view_args, input=user_input)

    assert result.exit_code == 0
    assert "checksum_address" in result.output
    assert "domains" in result.output
    assert TEMPORARY_DOMAIN in result.output
    assert custom_filepath in result.output
예제 #11
0
def test_cli_lifecycle(click_runner, testerchain, random_policy_label,
                       federated_ursulas, blockchain_ursulas, custom_filepath,
                       custom_filepath_2, federated):
    """
    This is an end to end integration test that runs each cli call
    in it's own process using only CLI character control entry points,
    and a mock side channel that runs in the control process
    """

    # Boring Setup Stuff
    alice_config_root = custom_filepath
    bob_config_root = custom_filepath_2
    envvars = {'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD}

    # A side channel exists - Perhaps a dApp
    side_channel = MockSideChannel()

    shutil.rmtree(custom_filepath, ignore_errors=True)
    shutil.rmtree(custom_filepath_2, ignore_errors=True)
    """
    Scene 1: Alice Installs nucypher to a custom filepath and examines her configuration
    """

    # Alice performs an installation for the first time
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--config-root', alice_config_root)
    if federated:
        alice_init_args += ('--federated-only', )
    else:
        alice_init_args += ('--provider', TEST_PROVIDER_URI, '--no-registry',
                            '--pay-with', testerchain.alice_account)

    alice_init_response = click_runner.invoke(nucypher_cli,
                                              alice_init_args,
                                              catch_exceptions=False,
                                              env=envvars)
    assert alice_init_response.exit_code == 0

    # Alice uses her configuration file to run the character "view" command
    alice_configuration_file_location = os.path.join(
        alice_config_root, AliceConfiguration.generate_filename())
    alice_view_args = ('alice', 'public-keys', '--json-ipc', '--config-file',
                       alice_configuration_file_location)

    alice_view_result = click_runner.invoke(
        nucypher_cli,
        alice_view_args,
        input=INSECURE_DEVELOPMENT_PASSWORD,
        catch_exceptions=False,
        env=envvars)

    assert alice_view_result.exit_code == 0
    alice_view_response = json.loads(alice_view_result.output)

    # Alice expresses her desire to participate in data sharing with nucypher
    # by saving her public key somewhere Bob and Enrico can find it.
    side_channel.save_alice_pubkey(
        alice_view_response['result']['alice_verifying_key'])
    """
    Scene 2: Bob installs nucypher, examines his configuration and expresses his
    interest to participate in data retrieval by posting his public keys somewhere public (side-channel).
    """
    bob_init_args = ('bob', 'init', '--network', TEMPORARY_DOMAIN,
                     '--config-root', bob_config_root)
    if federated:
        bob_init_args += ('--federated-only', )
    else:
        bob_init_args += ('--provider', TEST_PROVIDER_URI, '--no-registry',
                          '--checksum-address', testerchain.bob_account)

    bob_init_response = click_runner.invoke(nucypher_cli,
                                            bob_init_args,
                                            catch_exceptions=False,
                                            env=envvars)
    assert bob_init_response.exit_code == 0

    # Alice uses her configuration file to run the character "view" command
    bob_configuration_file_location = os.path.join(
        bob_config_root, BobConfiguration.generate_filename())
    bob_view_args = ('bob', 'public-keys', '--json-ipc', '--config-file',
                     bob_configuration_file_location)

    bob_view_result = click_runner.invoke(nucypher_cli,
                                          bob_view_args,
                                          catch_exceptions=False,
                                          env=envvars)
    assert bob_view_result.exit_code == 0
    bob_view_response = json.loads(bob_view_result.output)

    # Bob interacts with the sidechannel
    bob_public_keys = MockSideChannel.BobPublicKeys(
        bob_view_response['result']['bob_encrypting_key'],
        bob_view_response['result']['bob_verifying_key'])

    side_channel.save_bob_public_keys(bob_public_keys)
    """
    Scene 3: Alice derives a policy keypair, and saves it's public key to a sidechannel.
    """

    random_label = random_policy_label.decode()  # Unicode string

    derive_args = ('alice', 'derive-policy-pubkey', '--mock-networking',
                   '--json-ipc', '--config-file',
                   alice_configuration_file_location, '--label', random_label)

    derive_response = click_runner.invoke(nucypher_cli,
                                          derive_args,
                                          catch_exceptions=False,
                                          env=envvars)
    assert derive_response.exit_code == 0

    derive_response = json.loads(derive_response.output)
    assert derive_response['result']['label'] == random_label

    # Alice and the sidechannel: at Tinagre
    policy = MockSideChannel.PolicyAndLabel(
        encrypting_key=derive_response['result']['policy_encrypting_key'],
        label=derive_response['result']['label'])
    side_channel.save_policy(policy=policy)
    """
    Scene 4: Enrico encrypts some data for some policy public key and saves it to a side channel.
    """
    def enrico_encrypts():

        # Fetch!
        policy = side_channel.fetch_policy()

        enrico_args = ('enrico', 'encrypt', '--json-ipc',
                       '--policy-encrypting-key', policy.encrypting_key,
                       '--message', PLAINTEXT)

        encrypt_result = click_runner.invoke(nucypher_cli,
                                             enrico_args,
                                             catch_exceptions=False,
                                             env=envvars)
        assert encrypt_result.exit_code == 0

        encrypt_result = json.loads(encrypt_result.output)
        encrypted_message = encrypt_result['result'][
            'message_kit']  # type: str

        side_channel.save_message_kit(message_kit=encrypted_message)
        return encrypt_result

    def _alice_decrypts(encrypt_result):
        """
        alice forgot what exactly she encrypted for bob.
        she decrypts it just to make sure.
        """
        policy = side_channel.fetch_policy()
        alice_signing_key = side_channel.fetch_alice_pubkey()
        message_kit = encrypt_result['result']['message_kit']

        decrypt_args = (
            'alice',
            'decrypt',
            '--mock-networking',
            '--json-ipc',
            '--config-file',
            alice_configuration_file_location,
            '--message-kit',
            message_kit,
            '--label',
            policy.label,
        )

        if federated:
            decrypt_args += ('--federated-only', )

        decrypt_response_fail = click_runner.invoke(nucypher_cli,
                                                    decrypt_args[0:7],
                                                    catch_exceptions=False,
                                                    env=envvars)
        assert decrypt_response_fail.exit_code == 2

        decrypt_response = click_runner.invoke(nucypher_cli,
                                               decrypt_args,
                                               catch_exceptions=False,
                                               env=envvars)
        decrypt_result = json.loads(decrypt_response.output)
        for cleartext in decrypt_result['result']['cleartexts']:
            assert b64decode(cleartext.encode()).decode() == PLAINTEXT

        # replenish the side channel
        side_channel.save_policy(policy=policy)
        side_channel.save_alice_pubkey(alice_signing_key)
        return encrypt_result

    """
    Scene 5: Alice grants access to Bob:
    We catch up with Alice later on, but before she has learned about existing Ursulas...
    """
    if federated:
        teacher = list(federated_ursulas)[0]
    else:
        teacher = list(blockchain_ursulas)[1]

    teacher_uri = teacher.seed_node_metadata(as_teacher_uri=True)

    # Some Ursula is running somewhere
    def _run_teacher(_encrypt_result):
        start_pytest_ursula_services(ursula=teacher)
        return teacher_uri

    def _grant(teacher_uri):

        # Alice fetched Bob's public keys from the side channel
        bob_keys = side_channel.fetch_bob_public_keys()
        bob_encrypting_key = bob_keys.bob_encrypting_key
        bob_verifying_key = bob_keys.bob_verifying_key

        grant_args = ('alice', 'grant', '--mock-networking', '--json-ipc',
                      '--network', TEMPORARY_DOMAIN, '--teacher', teacher_uri,
                      '--config-file', alice_configuration_file_location,
                      '--m', 2, '--n', 3, '--value', Web3.toWei(1, 'ether'),
                      '--expiration', (maya.now() +
                                       datetime.timedelta(days=3)).iso8601(),
                      '--label', random_label, '--bob-encrypting-key',
                      bob_encrypting_key, '--bob-verifying-key',
                      bob_verifying_key)

        if federated:
            grant_args += ('--federated-only', )
        else:
            grant_args += ('--provider', TEST_PROVIDER_URI)

        grant_result = click_runner.invoke(nucypher_cli,
                                           grant_args,
                                           catch_exceptions=False,
                                           env=envvars)
        assert grant_result.exit_code == 0

        grant_result = json.loads(grant_result.output)

        # TODO: Expand test to consider manual treasure map handing
        # # Alice puts the Treasure Map somewhere Bob can get it.
        # side_channel.save_treasure_map(treasure_map=grant_result['result']['treasure_map'])

        return grant_result

    def _bob_retrieves(_grant_result):
        """
        Scene 6: Bob retrieves encrypted data from the side channel and uses nucypher to re-encrypt it
        """

        # Bob interacts with a sidechannel
        ciphertext_message_kit = side_channel.fetch_message_kit()

        policy = side_channel.fetch_policy()
        policy_encrypting_key, label = policy

        alice_signing_key = side_channel.fetch_alice_pubkey()

        retrieve_args = ('bob', 'retrieve', '--mock-networking', '--json-ipc',
                         '--teacher', teacher_uri, '--config-file',
                         bob_configuration_file_location, '--message-kit',
                         ciphertext_message_kit, '--label', label,
                         '--policy-encrypting-key', policy_encrypting_key,
                         '--alice-verifying-key', alice_signing_key)

        if federated:
            retrieve_args += ('--federated-only', )

        retrieve_response = click_runner.invoke(nucypher_cli,
                                                retrieve_args,
                                                catch_exceptions=False,
                                                env=envvars)
        assert retrieve_response.exit_code == 0

        retrieve_response = json.loads(retrieve_response.output)
        for cleartext in retrieve_response['result']['cleartexts']:
            assert b64decode(cleartext.encode()).decode() == PLAINTEXT

        return

    # Run the Callbacks
    d = threads.deferToThread(enrico_encrypts)  # scene 4
    d.addCallback(_alice_decrypts)  # scene 5 (uncertainty)
    d.addCallback(_run_teacher)  # scene 6 (preamble)
    d.addCallback(_grant)  # scene 7
    d.addCallback(_bob_retrieves)  # scene 8

    yield d
예제 #12
0
def test_coexisting_configurations(click_runner, custom_filepath, testerchain,
                                   agency_local_registry, mocker):
    #
    # Setup
    #

    if custom_filepath.exists():
        shutil.rmtree(str(custom_filepath), ignore_errors=True)
    assert not custom_filepath.exists()

    # Parse node addresses
    # TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)?
    alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts

    envvars = {
        NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
        NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
        NUCYPHER_ENVVAR_BOB_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD
    }

    # Future configuration filepaths for assertions...
    public_keys_dir = custom_filepath / 'keystore' / 'public'
    known_nodes_dir = custom_filepath / 'known_nodes'

    # ... Ensure they do not exist to begin with.

    # No keys have been generated...
    assert not public_keys_dir.exists()

    # No known nodes exist...
    assert not known_nodes_dir.exists()

    # Not the configuration root...
    assert not custom_filepath.is_dir()

    # ... nothing
    None

    #
    # Create
    #

    # Expected config files
    alice_file_location = custom_filepath / AliceConfiguration.generate_filename(
    )
    ursula_file_location = custom_filepath / UrsulaConfiguration.generate_filename(
    )

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--payment-network', TEMPORARY_DOMAIN, '--eth-provider',
                       TEST_ETH_PROVIDER_URI, '--pay-with', alice,
                       '--registry-filepath',
                       str(agency_local_registry.filepath.absolute()),
                       '--config-root', str(custom_filepath.absolute()))

    result = click_runner.invoke(nucypher_cli,
                                 alice_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert alice_file_location.is_file()

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--payment-network', TEMPORARY_DOMAIN, '--eth-provider',
                 TEST_ETH_PROVIDER_URI, '--operator-address', ursula,
                 '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath',
                 str(agency_local_registry.filepath.absolute()),
                 '--config-root', str(custom_filepath.absolute()))

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0, result.output

    # All configuration files still exist.
    assert alice_file_location.is_file()
    assert ursula_file_location.is_file()

    key_spy = mocker.spy(Keystore, 'generate')

    # keystore signing key
    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--payment-network', TEMPORARY_DOMAIN, '--operator-address',
                 another_ursula, '--rest-host', MOCK_IP_ADDRESS_2,
                 '--registry-filepath',
                 str(agency_local_registry.filepath.absolute()),
                 '--eth-provider', TEST_ETH_PROVIDER_URI, '--config-root',
                 str(custom_filepath.absolute()))

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert alice_file_location.is_file()

    kid = key_spy.spy_return.id[:8]
    another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename(
        modifier=kid)
    assert another_ursula_configuration_file_location.is_file()

    assert ursula_file_location.is_file()

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run', '--dry-run', '--no-ip-checkup',
                '--config-file',
                str(another_ursula_configuration_file_location.absolute()))

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2

    Operator.READY_POLL_RATE = 1
    Operator.READY_TIMEOUT = 1
    with pytest.raises(Operator.ActorError):
        # Operator init success, but not bonded.
        result = click_runner.invoke(nucypher_cli,
                                     run_args,
                                     input=user_input,
                                     catch_exceptions=False)
    assert result.exit_code == 0
    Operator.READY_TIMEOUT = None

    # All configuration files still exist.
    assert alice_file_location.is_file()
    assert another_ursula_configuration_file_location.is_file()
    assert ursula_file_location.is_file()

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = (
        'ursula', 'destroy', '--force', '--config-file',
        str(another_ursula_configuration_file_location.absolute()))
    result = click_runner.invoke(nucypher_cli,
                                 another_ursula_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert not another_ursula_configuration_file_location.is_file()

    ursula_destruction_args = ('ursula', 'destroy', '--config-file',
                               str(ursula_file_location.absolute()))
    result = click_runner.invoke(nucypher_cli,
                                 ursula_destruction_args,
                                 input='Y',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert not ursula_file_location.is_file()

    alice_destruction_args = ('alice', 'destroy', '--force', '--config-file',
                              str(alice_file_location.absolute()))
    result = click_runner.invoke(nucypher_cli,
                                 alice_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert not alice_file_location.is_file()
def test_coexisting_configurations(click_runner,
                                   custom_filepath,
                                   testerchain,
                                   agency_local_registry):
    #
    # Setup
    #

    if custom_filepath.exists():
        shutil.rmtree(str(custom_filepath), ignore_errors=True)
    assert not custom_filepath.exists()

    # Parse node addresses
    # TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)?
    alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts

    envvars = {NUCYPHER_ENVVAR_KEYRING_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
               'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD}

    # Future configuration filepaths for assertions...
    public_keys_dir = custom_filepath / 'keyring' / 'public'
    known_nodes_dir = custom_filepath / 'known_nodes'

    # ... Ensure they do not exist to begin with.

    # No keys have been generated...
    assert not public_keys_dir.exists()

    # No known nodes exist...
    assert not known_nodes_dir.exists()

    # Not the configuration root...
    assert not os.path.isdir(custom_filepath)

    # ... nothing
    None

    #
    # Create
    #

    # Expected config files
    felix_file_location = custom_filepath / FelixConfiguration.generate_filename()
    alice_file_location = custom_filepath / AliceConfiguration.generate_filename()
    ursula_file_location = custom_filepath / UrsulaConfiguration.generate_filename()
    another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename(modifier=another_ursula)

    # Felix creates a system configuration
    felix_init_args = ('felix', 'init',
                       '--config-root', custom_filepath,
                       '--network', TEMPORARY_DOMAIN,
                       '--provider', TEST_PROVIDER_URI,
                       '--checksum-address', felix,
                       '--registry-filepath', agency_local_registry.filepath,
                       '--debug')

    result = click_runner.invoke(nucypher_cli, felix_init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isdir(custom_filepath)
    assert os.path.isfile(felix_file_location)
    assert os.path.isdir(public_keys_dir)
    assert len(os.listdir(public_keys_dir)) == 3

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init',
                       '--network', TEMPORARY_DOMAIN,
                       '--provider', TEST_PROVIDER_URI,
                       '--pay-with', alice,
                       '--registry-filepath', agency_local_registry.filepath,
                       '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert len(os.listdir(public_keys_dir)) == 5

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init',
                 '--network', TEMPORARY_DOMAIN,
                 '--provider', TEST_PROVIDER_URI,
                 '--worker-address', ursula,
                 '--rest-host', MOCK_IP_ADDRESS,
                 '--registry-filepath', agency_local_registry.filepath,
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert len(os.listdir(public_keys_dir)) == 8
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(ursula_file_location)

    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init',
                 '--network', TEMPORARY_DOMAIN,
                 '--worker-address', another_ursula,
                 '--rest-host', MOCK_IP_ADDRESS_2,
                 '--registry-filepath', agency_local_registry.filepath,
                 '--provider', TEST_PROVIDER_URI,
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run',
                '--dry-run',
                '--config-file', another_ursula_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2

    Worker.BONDING_POLL_RATE = 1
    Worker.BONDING_TIMEOUT = 1
    with pytest.raises(Teacher.UnbondedWorker):  # TODO: Why is this being checked here?
        # Worker init success, but not bonded.
        result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False)
    assert result.exit_code == 0
    Worker.BONDING_TIMEOUT = None

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = ('ursula', 'destroy',
                                       '--force',
                                       '--config-file', another_ursula_configuration_file_location)
    result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert not os.path.isfile(another_ursula_configuration_file_location)

    ursula_destruction_args = ('ursula', 'destroy', '--config-file', ursula_file_location)
    result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert len(os.listdir(public_keys_dir)) == 5
    assert not os.path.isfile(ursula_file_location)

    alice_destruction_args = ('alice', 'destroy', '--force', '--config-file', alice_file_location)
    result = click_runner.invoke(nucypher_cli, alice_destruction_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 3
    assert not os.path.isfile(alice_file_location)

    felix_destruction_args = ('felix', 'destroy', '--force', '--config-file', felix_file_location)
    result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 0
    assert not os.path.isfile(felix_file_location)
예제 #14
0
def run_entire_cli_lifecycle(click_runner,
                             random_policy_label,
                             ursulas,
                             custom_filepath,
                             custom_filepath_2,
                             registry_filepath=None,
                             testerchain=None):
    """
    This is an end to end integration test that runs each cli call
    in it's own process using only CLI character control entry points,
    and a mock side channel that runs in the control process
    """

    federated = list(ursulas)[0].federated_only

    # Boring Setup Stuff
    alice_config_root = custom_filepath
    bob_config_root = custom_filepath_2
    envvars = {
        NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
        NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
        NUCYPHER_ENVVAR_BOB_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD
    }

    # A side channel exists - Perhaps a dApp
    side_channel = MockSideChannel()

    shutil.rmtree(str(custom_filepath), ignore_errors=True)
    shutil.rmtree(str(custom_filepath_2), ignore_errors=True)
    """
    Scene 1: Alice Installs nucypher to a custom filepath and examines her configuration
    """

    # Alice performs an installation for the first time
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--config-root', str(alice_config_root.absolute()))
    if federated:
        alice_init_args += ('--federated-only', )
    else:
        alice_init_args += ('--eth-provider', TEST_ETH_PROVIDER_URI,
                            '--pay-with', testerchain.alice_account,
                            '--payment-network', TEMPORARY_DOMAIN,
                            '--registry-filepath',
                            str(registry_filepath.absolute()))

    alice_init_response = click_runner.invoke(nucypher_cli,
                                              alice_init_args,
                                              catch_exceptions=False,
                                              env=envvars)
    assert alice_init_response.exit_code == 0

    # Prevent previous global logger settings set by above command from writing non-IPC messages to stdout
    GlobalLoggerSettings.stop_console_logging()

    # Alice uses her configuration file to run the character "view" command
    alice_configuration_file_location = alice_config_root / AliceConfiguration.generate_filename(
    )
    alice_view_args = ('alice', 'public-keys', '--json-ipc', '--config-file',
                       str(alice_configuration_file_location.absolute()))

    alice_view_result = click_runner.invoke(
        nucypher_cli,
        alice_view_args,
        input=INSECURE_DEVELOPMENT_PASSWORD,
        catch_exceptions=False,
        env=envvars)

    assert alice_view_result.exit_code == 0

    try:
        alice_view_response = json.loads(alice_view_result.output)
    except JSONDecodeError:
        pytest.fail("Invalid JSON response from JSON-RPC Character process.")

    # Alice expresses her desire to participate in data sharing with nucypher
    # by saving her public key somewhere Bob and Enrico can find it.
    side_channel.save_alice_pubkey(
        alice_view_response['result']['alice_verifying_key'])
    """
    Scene 2: Bob installs nucypher, examines his configuration and expresses his
    interest to participate in data retrieval by posting his public keys somewhere public (side-channel).
    """
    bob_init_args = ('bob', 'init', '--network', TEMPORARY_DOMAIN,
                     '--config-root', str(bob_config_root.absolute()))
    if federated:
        bob_init_args += ('--federated-only', )
    else:
        bob_init_args += ('--eth-provider', TEST_ETH_PROVIDER_URI,
                          '--registry-filepath',
                          str(registry_filepath.absolute()),
                          '--checksum-address', testerchain.bob_account)

    bob_init_response = click_runner.invoke(nucypher_cli,
                                            bob_init_args,
                                            catch_exceptions=False,
                                            env=envvars)
    assert bob_init_response.exit_code == 0

    # Alice uses her configuration file to run the character "view" command
    bob_configuration_file_location = bob_config_root / BobConfiguration.generate_filename(
    )
    bob_view_args = (
        'bob',
        'public-keys',
        '--json-ipc',
        '--mock-networking',  # TODO: It's absurd for this public-keys command to connect at all.  1710
        '--lonely',  # TODO: This needs to be implied by `public-keys`.
        '--config-file',
        str(bob_configuration_file_location.absolute()))

    bob_view_result = click_runner.invoke(nucypher_cli,
                                          bob_view_args,
                                          catch_exceptions=False,
                                          env=envvars)
    assert bob_view_result.exit_code == 0
    bob_view_response = json.loads(bob_view_result.output)

    # Bob interacts with the sidechannel
    bob_public_keys = MockSideChannel.BobPublicKeys(
        bob_view_response['result']['bob_encrypting_key'],
        bob_view_response['result']['bob_verifying_key'])

    side_channel.save_bob_public_keys(bob_public_keys)
    """
    Scene 3: Alice derives a policy keypair, and saves its public key to a sidechannel.
    """

    random_label = random_policy_label.decode()  # Unicode string

    derive_args = ('alice', 'derive-policy-pubkey', '--mock-networking',
                   '--json-ipc', '--config-file',
                   str(alice_configuration_file_location.absolute()),
                   '--label', random_label)

    derive_response = click_runner.invoke(nucypher_cli,
                                          derive_args,
                                          catch_exceptions=False,
                                          env=envvars)
    assert derive_response.exit_code == 0

    derive_response = json.loads(derive_response.output)
    assert derive_response['result']['label'] == random_label

    # Alice and the sidechannel: at Tinagre
    policy = MockSideChannel.PolicyAndLabel(
        encrypting_key=derive_response['result']['policy_encrypting_key'],
        label=derive_response['result']['label'])
    side_channel.save_policy(policy=policy)
    """
    Scene 4: Enrico encrypts some data for some policy public key and saves it to a side channel.
    """
    def enrico_encrypts():

        # Fetch!
        policy = side_channel.fetch_policy()

        enrico_args = ('enrico', 'encrypt', '--json-ipc',
                       '--policy-encrypting-key', policy.encrypting_key,
                       '--message', PLAINTEXT)

        encrypt_result = click_runner.invoke(nucypher_cli,
                                             enrico_args,
                                             catch_exceptions=False,
                                             env=envvars)
        assert encrypt_result.exit_code == 0
        encrypt_result = json.loads(encrypt_result.output)
        encrypted_message = encrypt_result['result'][
            'message_kit']  # type: str

        side_channel.save_message_kit(message_kit=encrypted_message)
        return encrypt_result

    def _alice_decrypts(encrypt_result):
        """
        alice forgot what exactly she encrypted for bob.
        she decrypts it just to make sure.
        """
        policy = side_channel.fetch_policy()
        alice_signing_key = side_channel.fetch_alice_pubkey()
        message_kit = encrypt_result['result']['message_kit']

        decrypt_args = (
            'alice',
            'decrypt',
            '--mock-networking',
            '--json-ipc',
            '--config-file',
            str(alice_configuration_file_location.absolute()),
            '--message-kit',
            message_kit,
            '--label',
            policy.label,
        )

        if federated:
            decrypt_args += ('--federated-only', )

        decrypt_response_fail = click_runner.invoke(nucypher_cli,
                                                    decrypt_args[0:7],
                                                    catch_exceptions=False,
                                                    env=envvars)
        assert decrypt_response_fail.exit_code == 2

        decrypt_response = click_runner.invoke(nucypher_cli,
                                               decrypt_args,
                                               catch_exceptions=False,
                                               env=envvars)
        decrypt_result = json.loads(decrypt_response.output)
        for cleartext in decrypt_result['result']['cleartexts']:
            assert b64decode(cleartext.encode()).decode() == PLAINTEXT

        # replenish the side channel
        side_channel.save_policy(policy=policy)
        side_channel.save_alice_pubkey(alice_signing_key)
        return encrypt_result

    """
    Scene 5: Alice grants access to Bob:
    We catch up with Alice later on, but before she has learned about existing Ursulas...
    """
    if federated:
        teacher = list(ursulas)[0]
    else:
        teacher = list(ursulas)[1]

    teacher_uri = teacher.seed_node_metadata(as_teacher_uri=True)

    # Some Ursula is running somewhere
    def _run_teacher(_encrypt_result):
        # start_pytest_ursula_services(ursula=teacher)
        return teacher_uri

    def _grant(teacher_uri):

        # Alice fetched Bob's public keys from the side channel
        bob_keys = side_channel.fetch_bob_public_keys()
        bob_encrypting_key = bob_keys.bob_encrypting_key
        bob_verifying_key = bob_keys.bob_verifying_key
        if federated:
            current_time = maya.now()
        else:
            current_time = maya.MayaDT(
                epoch=testerchain.client.get_blocktime())

        expiration = (current_time + datetime.timedelta(days=2)
                      ).datetime().strftime("%Y-%m-%d %H:%M:%S")
        grant_args = ('alice', 'grant', '--mock-networking', '--json-ipc',
                      '--network', TEMPORARY_DOMAIN, '--teacher', teacher_uri,
                      '--config-file',
                      str(alice_configuration_file_location.absolute()), '-m',
                      2, '-n', 3, '--expiration', expiration, '--label',
                      random_label, '--bob-encrypting-key', bob_encrypting_key,
                      '--bob-verifying-key', bob_verifying_key)

        if federated:
            grant_args += ('--federated-only', )
        else:
            grant_args += ('--eth-provider', TEST_ETH_PROVIDER_URI, '--value',
                           Web3.toWei(9, 'gwei'))

        grant_result = click_runner.invoke(nucypher_cli,
                                           grant_args,
                                           catch_exceptions=False,
                                           env=envvars)
        assert grant_result.exit_code == 0, (grant_result.output,
                                             grant_result.exception)

        grant_result = json.loads(grant_result.output)

        # Alice puts the Treasure Map somewhere Bob can get it.
        side_channel.save_treasure_map(
            treasure_map=grant_result['result']['treasure_map'])

        return grant_result

    def _bob_retrieves(_grant_result):
        """
        Scene 6: Bob retrieves encrypted data from the side channel and uses nucypher to re-encrypt it
        """

        # Bob interacts with a sidechannel
        ciphertext_message_kit = side_channel.fetch_message_kit()

        policy = side_channel.fetch_policy()
        policy_encrypting_key, label = policy

        alice_signing_key = side_channel.fetch_alice_pubkey()

        retrieve_args = ('bob', 'retrieve-and-decrypt', '--mock-networking',
                         '--json-ipc', '--teacher', teacher_uri,
                         '--config-file',
                         str(bob_configuration_file_location.absolute()),
                         '--message-kit', ciphertext_message_kit,
                         '--treasure-map', side_channel.fetch_treasure_map(),
                         '--alice-verifying-key', alice_signing_key)

        retrieve_response = click_runner.invoke(nucypher_cli,
                                                retrieve_args,
                                                catch_exceptions=False,
                                                env=envvars)
        assert retrieve_response.exit_code == 0

        retrieve_response = json.loads(retrieve_response.output)
        for cleartext in retrieve_response['result']['cleartexts']:
            assert b64decode(cleartext.encode()).decode() == PLAINTEXT

        return

    # Run the Callbacks
    d = threads.deferToThread(enrico_encrypts)  # scene 4
    d.addCallback(_alice_decrypts)  # scene 5 (uncertainty)
    d.addCallback(_run_teacher)  # scene 6 (preamble)
    d.addCallback(_grant)  # scene 7
    d.addCallback(_bob_retrieves)  # scene 8

    return d
예제 #15
0
def test_coexisting_configurations(click_runner, custom_filepath,
                                   mock_primary_registry_filepath, testerchain,
                                   deploy_user_input):

    # Parse node addresses
    deployer, alice, ursula, another_ursula, *all_yall = testerchain.client.accounts

    envvars = {
        'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD,

        # Upgradeable Contracts
        'NUCYPHER_STAKING_ESCROW_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_POLICY_MANAGER_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_ADJUDICATOR_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_USER_ESCROW_PROXY_SECRET': INSECURE_DEVELOPMENT_PASSWORD,

        # Auxiliary
        'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD
    }

    # Future configuration filepaths for assertions...
    public_keys_dir = os.path.join(custom_filepath, 'keyring', 'public')
    known_nodes_dir = os.path.join(custom_filepath, 'known_nodes')

    # ... Ensure they do not exist to begin with.
    assert not os.path.isdir(public_keys_dir)
    assert not os.path.isfile(known_nodes_dir)

    # Deploy contracts
    deploy_args = ('contracts', '--registry-outfile',
                   mock_primary_registry_filepath, '--provider-uri',
                   TEST_PROVIDER_URI, '--config-root', custom_filepath,
                   '--poa')

    result = click_runner.invoke(deploy.deploy,
                                 deploy_args,
                                 input=f'0\nY\nDEPLOY',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # No keys have been generated...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(public_keys_dir)) == 0

    # No known nodes exist...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(known_nodes_dir)) == 0

    # Just the configuration root...
    assert os.path.isdir(custom_filepath)

    # and the fresh registry.
    assert os.path.isfile(mock_primary_registry_filepath)

    #
    # Create
    #

    # Expected config files
    felix_file_location = os.path.join(custom_filepath,
                                       FelixConfiguration.generate_filename())
    alice_file_location = os.path.join(custom_filepath,
                                       AliceConfiguration.generate_filename())
    ursula_file_location = os.path.join(
        custom_filepath, UrsulaConfiguration.generate_filename())
    another_ursula_configuration_file_location = os.path.join(
        custom_filepath,
        UrsulaConfiguration.generate_filename(modifier=another_ursula))

    # Felix creates a system configuration
    felix_init_args = ('felix', 'init', '--config-root', custom_filepath,
                       '--network', TEMPORARY_DOMAIN, '--provider-uri',
                       TEST_PROVIDER_URI, '--checksum-address', deployer,
                       '--registry-filepath', mock_primary_registry_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 felix_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert os.path.isfile(felix_file_location)
    assert len(os.listdir(public_keys_dir)) == 3

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--provider-uri', TEST_PROVIDER_URI, '--pay-with',
                       alice, '--registry-filepath',
                       mock_primary_registry_filepath, '--config-root',
                       custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 alice_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert os.path.isfile(alice_file_location)
    assert len(os.listdir(public_keys_dir)) == 5

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--provider-uri', TEST_PROVIDER_URI, '--checksum-address',
                 ursula, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath',
                 mock_primary_registry_filepath, '--config-root',
                 custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert os.path.isfile(ursula_file_location)

    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--checksum-address', another_ursula, '--rest-host',
                 MOCK_IP_ADDRESS_2, '--registry-filepath',
                 mock_primary_registry_filepath, '--provider-uri',
                 TEST_PROVIDER_URI, '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)

    assert len(os.listdir(public_keys_dir)) == 11

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run', '--dry-run', '--interactive', '--config-file',
                another_ursula_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
    result = click_runner.invoke(nucypher_cli,
                                 run_args,
                                 input=user_input,
                                 catch_exceptions=False)
    assert result.exit_code == 0

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = (
        'ursula', 'destroy', '--force', '--config-file',
        another_ursula_configuration_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 another_ursula_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert not os.path.isfile(another_ursula_configuration_file_location)

    ursula_destruction_args = ('ursula', 'destroy', '--config-file',
                               ursula_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 ursula_destruction_args,
                                 input='Y',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert len(os.listdir(public_keys_dir)) == 5
    assert not os.path.isfile(ursula_file_location)

    felix_destruction_args = ('alice', 'destroy', '--force', '--config-file',
                              alice_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 felix_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 3
    assert not os.path.isfile(alice_file_location)

    felix_destruction_args = ('felix', 'destroy', '--force', '--config-file',
                              felix_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 felix_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 0
    assert not os.path.isfile(felix_file_location)
def test_coexisting_configurations(click_runner, custom_filepath,
                                   mock_primary_registry_filepath, testerchain,
                                   test_registry, agency):
    #
    # Setup
    #

    # Parse node addresses
    alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts

    envvars = {
        'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD
    }

    # Future configuration filepaths for assertions...
    public_keys_dir = os.path.join(custom_filepath, 'keyring', 'public')
    known_nodes_dir = os.path.join(custom_filepath, 'known_nodes')

    # ... Ensure they do not exist to begin with.
    assert not os.path.isdir(public_keys_dir)
    assert not os.path.isfile(known_nodes_dir)

    # No keys have been generated...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(public_keys_dir)) == 0

    # No known nodes exist...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(known_nodes_dir)) == 0

    # Not the configuration root...
    assert not os.path.isdir(custom_filepath)

    #
    # Create
    #

    # Expected config files
    felix_file_location = os.path.join(custom_filepath,
                                       FelixConfiguration.generate_filename())
    alice_file_location = os.path.join(custom_filepath,
                                       AliceConfiguration.generate_filename())
    ursula_file_location = os.path.join(
        custom_filepath, UrsulaConfiguration.generate_filename())
    another_ursula_configuration_file_location = os.path.join(
        custom_filepath,
        UrsulaConfiguration.generate_filename(modifier=another_ursula))

    # Felix creates a system configuration
    felix_init_args = ('felix', 'init', '--config-root', custom_filepath,
                       '--network', TEMPORARY_DOMAIN, '--provider',
                       TEST_PROVIDER_URI, '--checksum-address', felix,
                       '--registry-filepath', mock_primary_registry_filepath,
                       '--debug')

    result = click_runner.invoke(nucypher_cli,
                                 felix_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isdir(custom_filepath)
    assert os.path.isfile(felix_file_location)
    assert os.path.isdir(public_keys_dir)
    assert len(os.listdir(public_keys_dir)) == 3

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--provider', TEST_PROVIDER_URI, '--pay-with', alice,
                       '--registry-filepath', mock_primary_registry_filepath,
                       '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 alice_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert len(os.listdir(public_keys_dir)) == 5

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--provider',
                 TEST_PROVIDER_URI, '--worker-address', ursula,
                 '--staker-address', staker, '--rest-host', MOCK_IP_ADDRESS,
                 '--registry-filepath', mock_primary_registry_filepath,
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert len(os.listdir(public_keys_dir)) == 8
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(ursula_file_location)

    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--worker-address', another_ursula, '--staker-address',
                 staker, '--rest-host', MOCK_IP_ADDRESS_2,
                 '--registry-filepath', mock_primary_registry_filepath,
                 '--provider', TEST_PROVIDER_URI, '--config-root',
                 custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run', '--dry-run', '--interactive', '--config-file',
                another_ursula_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
    with pytest.raises(Teacher.DetachedWorker):
        # Worker init success, but unassigned.
        result = click_runner.invoke(nucypher_cli,
                                     run_args,
                                     input=user_input,
                                     catch_exceptions=False)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = (
        'ursula', 'destroy', '--force', '--config-file',
        another_ursula_configuration_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 another_ursula_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert not os.path.isfile(another_ursula_configuration_file_location)

    ursula_destruction_args = ('ursula', 'destroy', '--config-file',
                               ursula_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 ursula_destruction_args,
                                 input='Y',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert len(os.listdir(public_keys_dir)) == 5
    assert not os.path.isfile(ursula_file_location)

    alice_destruction_args = ('alice', 'destroy', '--force', '--config-file',
                              alice_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 alice_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 3
    assert not os.path.isfile(alice_file_location)

    felix_destruction_args = ('felix', 'destroy', '--force', '--config-file',
                              felix_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 felix_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 0
    assert not os.path.isfile(felix_file_location)