Merge pull request #775 from KPrasch/baby-carrots

Unicode Policy Labels - Conventional Character Control API Keys
pull/778/head
Tux 2019-02-16 22:41:54 +00:00 committed by GitHub
commit 4404c7a3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 179 additions and 101 deletions

View File

@ -14,6 +14,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import datetime
import json
import random
from base64 import b64encode, b64decode
@ -45,6 +46,8 @@ from bytestring_splitter import BytestringKwargifier, BytestringSplittingError
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
from constant_sorrow import constants, constant_or_bytes
from constant_sorrow.constants import INCLUDED_IN_BYTESTRING, PUBLIC_ONLY
import nucypher
from nucypher.blockchain.eth.actors import PolicyAuthor, Miner
from nucypher.blockchain.eth.agents import MinerAgent
from nucypher.characters.base import Character, Learner
@ -217,10 +220,11 @@ class Alice(Character, PolicyAuthor):
Character control endpoint for creating a policy and making
arrangements with Ursulas.
This is an unfinished API endpoint. You are probably looking for grant.
TODO: This is an unfinished API endpoint. You are probably looking for grant.
TODO: Needs input cleansing and validation
TODO: Provide more informative errors
"""
# TODO: Needs input cleansing and validation
# TODO: Provide more informative errors
try:
request_data = json.loads(request.data)
@ -238,28 +242,27 @@ class Alice(Character, PolicyAuthor):
new_policy = drone_alice.create_policy(bob, label, m, n,
federated=federated_only)
# TODO: Serialize the policy
return Response('Policy created!', status=200)
@alice_control.route('/derive_policy_pubkey', methods=['POST'])
def derive_policy_pubkey():
response_data = {'result': {'label': new_policy.label.decode(),
'policy_encrypting_key': new_policy.public_key.to_bytes().hex()},
'version': str(nucypher.__version__)}
return Response(json.dumps(response_data), status=200)
@alice_control.route('/derive_policy_pubkey/<label>', methods=['POST'])
def derive_policy_pubkey(label):
"""
Character control endpoint for deriving a policy pubkey given
a label.
"""
try:
request_data = json.loads(request.data)
label = b64decode(request_data['label'])
except (KeyError, JSONDecodeError) as e:
return Response(str(e), status=400)
policy_pubkey = drone_alice.get_policy_pubkey_from_label(label)
label_bytes = label.encode()
policy_pubkey = drone_alice.get_policy_pubkey_from_label(label_bytes)
response_data = {
'result': {
'policy_encrypting_pubkey': bytes(policy_pubkey).hex(),
}
'policy_encrypting_key': bytes(policy_pubkey).hex(),
},
'version': str(nucypher.__version__)
}
return Response(json.dumps(response_data), status=200)
@ -276,17 +279,21 @@ class Alice(Character, PolicyAuthor):
bob_pubkey_enc = bytes.fromhex(request_data['bob_encrypting_key'])
bob_pubkey_sig = bytes.fromhex(request_data['bob_signing_key'])
label = b64decode(request_data['label'])
label = request_data['label'].encode()
# TODO: Do we change this to something like "threshold"
m, n = request_data['m'], request_data['n']
expiration_time = maya.MayaDT.from_iso8601(
request_data['expiration_time'])
federated_only = True # const for now
expiration = request_data.get("expiration_time")
if expiration:
expiration_time = maya.MayaDT.from_iso8601(
request_data['expiration_time'])
else:
expiration_time = (maya.now() + datetime.timedelta(days=3))
bob = Bob.from_public_keys({DecryptingPower: bob_pubkey_enc,
SigningPower: bob_pubkey_sig},
federated_only=True)
federated_only=True) # TODO: Const for now
except (KeyError, JSONDecodeError) as e:
print(e) # TODO: Make this a genuine log. Just for demos for now.
return Response(str(e), status=400)
new_policy = drone_alice.grant(bob, label, m=m, n=n,
@ -295,10 +302,11 @@ class Alice(Character, PolicyAuthor):
response_data = {
'result': {
'treasure_map': b64encode(bytes(new_policy.treasure_map)).decode(),
'policy_encrypting_pubkey': bytes(new_policy.public_key).hex(),
'alice_signing_pubkey': bytes(new_policy.alice.stamp).hex(),
'label': b64encode(new_policy.label).decode(),
}
'policy_encrypting_key': bytes(new_policy.public_key).hex(),
'alice_signing_key': bytes(new_policy.alice.stamp).hex(),
'label': new_policy.label.decode(),
},
'version': str(nucypher.__version__)
}
return Response(json.dumps(response_data), status=200)
@ -552,8 +560,8 @@ class Bob(Character):
try:
request_data = json.loads(request.data)
label = b64decode(request_data['label'])
alice_pubkey_sig = bytes.fromhex(request_data['alice_signing_pubkey'])
label = request_data['label'].encode()
alice_pubkey_sig = bytes.fromhex(request_data['alice_signing_key'])
except (KeyError, JSONDecodeError) as e:
return Response(e, status=400)
@ -569,21 +577,21 @@ class Bob(Character):
"""
try:
request_data = json.loads(request.data)
label = b64decode(request_data['label'])
policy_pubkey_enc = bytes.fromhex(request_data['policy_encrypting_pubkey'])
alice_pubkey_sig = bytes.fromhex(request_data['alice_signing_pubkey'])
message_kit = b64decode(request_data['message_kit'])
label = request_data['label'].encode()
policy_pubkey_enc = bytes.fromhex(request_data['policy_encrypting_key'])
alice_pubkey_sig = bytes.fromhex(request_data['alice_signing_key'])
message_kit = b64decode(request_data['message_kit'].encode())
except (KeyError, JSONDecodeError) as e:
return Response(e, status=400)
policy_encrypting_key = UmbralPublicKey.from_bytes(policy_pubkey_enc)
alice_pubkey_sig = UmbralPublicKey.from_bytes(alice_pubkey_sig)
message_kit = UmbralMessageKit.from_bytes(message_kit)
message_kit = UmbralMessageKit.from_bytes(message_kit) # TODO: May raise UnknownOpenSSLError and InvalidTag.
data_source = Enrico.from_public_keys({SigningPower: message_kit.sender_pubkey_sig},
policy_encrypting_key=policy_encrypting_key,
label=label)
drone_bob.join_policy(label=label, alice_pubkey_sig=alice_pubkey_sig)
plaintexts = drone_bob.retrieve(message_kit=message_kit,
data_source=data_source,
@ -594,7 +602,8 @@ class Bob(Character):
response_data = {
'result': {
'plaintext': plaintexts,
}
},
'version': str(nucypher.__version__)
}
return Response(json.dumps(response_data), status=200)
@ -1132,18 +1141,19 @@ class Enrico(Character):
"""
try:
request_data = json.loads(request.data)
message = b64decode(request_data['message'])
message = request_data['message']
except (KeyError, JSONDecodeError) as e:
return Response(str(e), status=400)
message_kit, signature = drone_enrico.encrypt_message(message)
# Encrypt
message_kit, signature = drone_enrico.encrypt_message(bytes(message, encoding='utf-8'))
response_data = {
'result': {
'message_kit': b64encode(message_kit.to_bytes()).decode(),
'message_kit': b64encode(message_kit.to_bytes()).decode(), # FIXME
'signature': b64encode(bytes(signature)).decode(),
}
},
'version': str(nucypher.__version__)
}
return Response(json.dumps(response_data), status=200)

View File

@ -177,7 +177,7 @@ def alice(click_config,
alice_control = ALICE.make_wsgi_app()
click.secho("Starting Alice Character Control...")
click.secho(f"Alice Signing Key {bytes(ALICE.stamp).hex()}", fg="green", bold=True)
click.secho(f"Alice Verifying Key {bytes(ALICE.stamp).hex()}", fg="green", bold=True)
# Run
if dry_run:

View File

@ -170,7 +170,7 @@ def bob(click_config,
bob_control = BOB.make_wsgi_app()
click.secho("Starting Bob Character Control...")
click.secho(f"Bob Signing Key {bytes(BOB.stamp).hex()}", fg="green", bold=True)
click.secho(f"Bob Verifying Key {bytes(BOB.stamp).hex()}", fg="green", bold=True)
click.secho(f"Bob Encrypting Key {bytes(BOB.public_keys(DecryptingPower)).hex()}", fg="blue", bold=True)
# Run

View File

@ -36,7 +36,7 @@ def enrico(action, policy_encrypting_key, dry_run, http_port):
enrico_control = ENRICO.make_wsgi_app()
click.secho("Starting Enrico Character Control...")
click.secho(f"Enrico Signing Key {bytes(ENRICO.stamp).hex()}", fg="green", bold=True)
click.secho(f"Enrico Verifying Key {bytes(ENRICO.stamp).hex()}", fg="green", bold=True)
# Run
if dry_run:

View File

@ -15,9 +15,6 @@ from nucypher.network.middleware import RestMiddleware
from nucypher.network.nodes import FleetStateTracker
from nucypher.utilities.logging import SimpleObserver
globalLogPublisher.addObserver(SimpleObserver())
MOE_BANNER = r"""
_______
| | |.-----..-----.

View File

@ -378,7 +378,7 @@ class NodeConfiguration(ABC):
storage_type = storage_payload[NodeStorage._TYPE_LABEL]
storage_class = node_storage_subclasses[storage_type]
node_storage = storage_class.from_payload(payload=storage_payload,
character_class=cls._CHARACTER_CLASS,
# character_class=cls._CHARACTER_CLASS, # TODO: Do not pass this here - Always Use Ursula
federated_only=payload['federated_only'],
serializer=cls.NODE_SERIALIZER,
deserializer=cls.NODE_DESERIALIZER)

View File

@ -39,6 +39,7 @@ class NotFound(UnexpectedResponse):
class NucypherMiddlewareClient:
library = requests
timeout = 1.2
@staticmethod
def response_cleaner(response):
@ -60,6 +61,9 @@ class NucypherMiddlewareClient:
def invoke_method(self, method, url, *args, **kwargs):
self.clean_params(kwargs)
if not kwargs.get("timeout"):
if self.timeout:
kwargs["timeout"] = self.timeout
response = method(url, *args, **kwargs)
return response

View File

@ -99,7 +99,7 @@ class GlobalConsoleLogger:
cls.start()
def logToSentry(cls, event):
def logToSentry(event):
# Handle Logs...
if not event.get('isError') or 'failure' not in event:

View File

@ -26,6 +26,8 @@ from constant_sorrow.constants import CERTIFICATE_NOT_SAVED
class _TestMiddlewareClient(NucypherMiddlewareClient):
timeout = None
@staticmethod
def response_cleaner(response):
response.content = response.data

View File

@ -14,10 +14,14 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import random
from collections import OrderedDict
from typing import Set
import maya
from typing import Set
from nucypher.characters.lawful import Ursula
from nucypher.network.middleware import RestMiddleware
@ -76,3 +80,15 @@ class MockPolicyCreation:
assert tx_hash == cls.tx_hash
cls.waited_for_receipt = True
def generate_random_label() -> bytes:
"""
Generates a random bytestring for use as a test label.
:return: bytes
"""
adjs = ('my', 'sesame-street', 'black', 'cute')
nouns = ('lizard', 'super-secret', 'data', 'coffee')
combinations = list('-'.join((a, n)) for a in adjs for n in nouns)
selection = random.choice(combinations)
random_label = f'label://{selection}-{os.urandom(4).hex()}'
return bytes(random_label, encoding='utf-8')

View File

@ -3,10 +3,14 @@ import json
from base64 import b64encode, b64decode
import maya
import pytest
import nucypher
from nucypher.characters.lawful import Enrico
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import DecryptingPower
from nucypher.policy.models import TreasureMap
from nucypher.utilities.sandbox.policy import generate_random_label
def test_alice_character_control_create_policy(alice_control_test_client, federated_bob):
@ -22,33 +26,28 @@ def test_alice_character_control_create_policy(alice_control_test_client, federa
}
response = alice_control_test_client.put('/create_policy', data=json.dumps(request_data))
assert response.status_code == 200
assert response.data == b'Policy created!'
create_policy_response = json.loads(response.data)
assert 'version' in create_policy_response
assert 'label' in create_policy_response['result']
try:
bytes.fromhex(create_policy_response['result']['policy_encrypting_key'])
except (KeyError, ValueError):
pytest.fail("Invalid Policy Encrypting Key")
# Send bad data to assert error returns
response = alice_control_test_client.put('/create_policy', data='bad')
assert response.status_code == 400
del(request_data['bob_encrypting_key'])
response = alice_control_test_client.put('/create_policy', data=json.dumps(request_data))
def test_alice_character_control_derive_policy_pubkey(alice_control_test_client):
request_data = {
'label': b64encode(b'test').decode(),
}
response = alice_control_test_client.post('/derive_policy_pubkey', data=json.dumps(request_data))
label = 'test'
response = alice_control_test_client.post(f'/derive_policy_pubkey/{label}')
assert response.status_code == 200
response_data = json.loads(response.data)
assert 'policy_encrypting_pubkey' in response_data['result']
# Test bad data returns an error
response = alice_control_test_client.post('/derive_policy_pubkey', data='bad')
assert response.status_code == 400
del(request_data['label'])
response = alice_control_test_client.post('/derive_policy_pubkey', data=request_data)
assert response.status_code == 400
assert 'policy_encrypting_key' in response_data['result']
def test_alice_character_control_grant(alice_control_test_client, federated_bob):
@ -57,7 +56,7 @@ def test_alice_character_control_grant(alice_control_test_client, federated_bob)
request_data = {
'bob_encrypting_key': bytes(bob_pubkey_enc).hex(),
'bob_signing_key': bytes(federated_bob.stamp).hex(),
'label': b64encode(bytes(b'test')).decode(),
'label': 'test',
'm': 2,
'n': 3,
'expiration_time': (maya.now() + datetime.timedelta(days=3)).iso8601(),
@ -67,8 +66,8 @@ def test_alice_character_control_grant(alice_control_test_client, federated_bob)
response_data = json.loads(response.data)
assert 'treasure_map' in response_data['result']
assert 'policy_encrypting_pubkey' in response_data['result']
assert 'alice_signing_pubkey' in response_data['result']
assert 'policy_encrypting_key' in response_data['result']
assert 'alice_signing_key' in response_data['result']
assert 'label' in response_data['result']
map_bytes = b64decode(response_data['result']['treasure_map'])
@ -85,8 +84,8 @@ def test_alice_character_control_grant(alice_control_test_client, federated_bob)
def test_bob_character_control_join_policy(bob_control_test_client, enacted_federated_policy):
request_data = {
'label': b64encode(enacted_federated_policy.label).decode(),
'alice_signing_pubkey': bytes(enacted_federated_policy.alice.stamp).hex(),
'label': enacted_federated_policy.label.decode(),
'alice_signing_key': bytes(enacted_federated_policy.alice.stamp).hex(),
}
# Simulate passing in a teacher-uri
@ -100,19 +99,21 @@ def test_bob_character_control_join_policy(bob_control_test_client, enacted_fede
response = bob_control_test_client.post('/join_policy', data='bad')
assert response.status_code == 400
del(request_data['alice_signing_pubkey'])
response = bob_control_test_client.put('/join_policy', data=json.dumps(request_data))
# Missing Key results in bad request
del(request_data['alice_signing_key'])
response = bob_control_test_client.post('/join_policy', data=json.dumps(request_data))
assert response.status_code == 400
def test_bob_character_control_retrieve(bob_control_test_client, enacted_federated_policy, capsule_side_channel):
message_kit, data_source = capsule_side_channel
request_data = {
'label': b64encode(enacted_federated_policy.label).decode(),
'policy_encrypting_pubkey': bytes(enacted_federated_policy.public_key).hex(),
'alice_signing_pubkey': bytes(enacted_federated_policy.alice.stamp).hex(),
'label': enacted_federated_policy.label.decode(),
'policy_encrypting_key': bytes(enacted_federated_policy.public_key).hex(),
'alice_signing_key': bytes(enacted_federated_policy.alice.stamp).hex(),
'message_kit': b64encode(message_kit.to_bytes()).decode(),
'datasource_signing_pubkey': bytes(data_source.stamp).hex(),
'datasource_signing_key': bytes(data_source.stamp).hex(),
}
response = bob_control_test_client.post('/retrieve', data=json.dumps(request_data))
@ -128,7 +129,7 @@ def test_bob_character_control_retrieve(bob_control_test_client, enacted_federat
response = bob_control_test_client.post('/retrieve', data='bad')
assert response.status_code == 400
del(request_data['alice_signing_pubkey'])
del(request_data['alice_signing_key'])
response = bob_control_test_client.put('/retrieve', data=json.dumps(request_data))
@ -145,8 +146,7 @@ def test_enrico_character_control_encrypt_message(enrico_control_test_client):
assert 'signature' in response_data['result']
# Check that it serializes correctly.
message_kit = UmbralMessageKit.from_bytes(
b64decode(response_data['result']['message_kit']))
message_kit = UmbralMessageKit.from_bytes(b64decode(response_data['result']['message_kit']))
# Send bad data to assert error return
response = enrico_control_test_client.post('/encrypt_message', data='bad')
@ -157,39 +157,50 @@ def test_enrico_character_control_encrypt_message(enrico_control_test_client):
assert response.status_code == 400
def test_character_control_lifecycle(alice_control_test_client, bob_control_test_client,
def test_character_control_lifecycle(alice_control_test_client,
bob_control_test_client,
enrico_control_from_alice,
federated_alice, federated_bob):
federated_alice,
federated_bob,
federated_ursulas,
random_policy_label):
random_label = random_policy_label.decode() # Unicode string
# Create a policy via Alice control
alice_request_data = {
'bob_encrypting_key': bytes(federated_bob.public_keys(DecryptingPower)).hex(),
'label': b64encode(b'test').decode(),
'bob_signing_key': bytes(federated_bob.stamp).hex(),
'm': 2, 'n': 3,
'expiration_time': (maya.now() + datetime.timedelta(days=3)).iso8601(),
'bob_encrypting_key': bytes(federated_bob.public_keys(DecryptingPower)).hex(),
'm': 1, 'n': 1,
'label': random_label,
# 'expiration_time': (maya.now() + datetime.timedelta(days=3)).iso8601(), # TODO
}
response = alice_control_test_client.put('/grant', data=json.dumps(alice_request_data))
assert response.status_code == 200
# Check Response Keys
alice_response_data = json.loads(response.data)
assert 'treasure_map' in alice_response_data['result']
assert 'policy_encrypting_pubkey' in alice_response_data['result']
assert 'alice_signing_pubkey' in alice_response_data['result']
assert 'policy_encrypting_key' in alice_response_data['result']
assert 'alice_signing_key' in alice_response_data['result']
assert 'label' in alice_response_data['result']
assert 'version' in alice_response_data
assert str(nucypher.__version__) == alice_response_data['version']
# This is sidechannel policy metadata. It should be given to Bob by the
# application developer at some point.
policy_pubkey_enc_hex = alice_response_data['result']['policy_encrypting_pubkey']
alice_pubkey_sig_hex = alice_response_data['result']['alice_signing_pubkey']
label = b64decode(alice_response_data['result']['label'])
policy_pubkey_enc_hex = alice_response_data['result']['policy_encrypting_key']
alice_pubkey_sig_hex = alice_response_data['result']['alice_signing_key']
label = alice_response_data['result']['label']
# Encrypt some data via Enrico control
# Alice will also be Enrico via Enrico.from_alice
# (see enrico_control_from_alice fixture)
enrico_encoded_message = "I'm bereaved, not a sap!" # type: str
enrico_request_data = {
'message': b64encode(b"I'm bereaved, not a sap!").decode(),
'message': enrico_encoded_message,
}
response = enrico_control_from_alice.post('/encrypt_message', data=json.dumps(enrico_request_data))
@ -199,17 +210,23 @@ def test_character_control_lifecycle(alice_control_test_client, bob_control_test
assert 'message_kit' in enrico_response_data['result']
assert 'signature' in enrico_response_data['result']
kit_bytes = b64decode(enrico_response_data['result']['message_kit'])
kit_bytes = b64decode(enrico_response_data['result']['message_kit'].encode())
bob_message_kit = UmbralMessageKit.from_bytes(kit_bytes)
# Retrieve data via Bob control
encoded_message_kit = b64encode(bob_message_kit.to_bytes()).decode()
bob_request_data = {
'label': b64encode(label).decode(),
'policy_encrypting_pubkey': policy_pubkey_enc_hex,
'alice_signing_pubkey': alice_pubkey_sig_hex,
'message_kit': b64encode(bob_message_kit.to_bytes()).decode(),
'label': label,
'policy_encrypting_key': policy_pubkey_enc_hex,
'alice_signing_key': alice_pubkey_sig_hex,
'message_kit': encoded_message_kit,
}
# Give bob a node to remember
teacher = list(federated_ursulas)[1]
federated_bob.remember_node(teacher)
response = bob_control_test_client.post('/retrieve', data=json.dumps(bob_request_data))
assert response.status_code == 200

View File

@ -17,7 +17,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import pytest
from bytestring_splitter import BytestringSplitter, BytestringSplittingError
from nucypher.characters.lawful import Enrico
from nucypher.crypto.api import secure_random
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.signing import Signature
@ -51,3 +53,26 @@ def test_trying_to_extract_too_many_bytes_raises_typeerror():
with pytest.raises(BytestringSplittingError):
rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes, return_remainder=True)
def test_message_kit_serialization_via_enrico(enacted_federated_policy, federated_alice):
# Enrico
enrico = Enrico.from_alice(federated_alice, label=enacted_federated_policy.label)
# Plaintext
message = 'this is a message'
plaintext_bytes = bytes(message, encoding='utf-8')
# Create
message_kit, signature = enrico.encrypt_message(message=plaintext_bytes)
# Serialize
message_kit_bytes = message_kit.to_bytes()
# Deserialize
the_same_message_kit = UmbralMessageKit.from_bytes(message_kit_bytes)
# Confirm
assert message_kit_bytes == the_same_message_kit.to_bytes()

View File

@ -17,6 +17,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import datetime
import os
import random
import tempfile
import maya
@ -40,6 +41,7 @@ from nucypher.utilities.sandbox.constants import (NUMBER_OF_URSULAS_IN_DEVELOPME
DEVELOPMENT_TOKEN_AIRDROP_AMOUNT, MOCK_URSULA_STARTING_PORT,
MOCK_POLICY_DEFAULT_M)
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
from nucypher.utilities.sandbox.policy import generate_random_label
from nucypher.utilities.sandbox.ursula import make_federated_ursulas, make_decentralized_ursulas
TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts')
@ -207,7 +209,7 @@ def idle_federated_policy(federated_alice, federated_bob):
"""
m = MOCK_POLICY_DEFAULT_M
n = NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
random_label = b'label://' + os.urandom(32)
random_label = generate_random_label()
policy = federated_alice.create_policy(federated_bob, label=random_label, m=m, n=n, federated=True)
return policy
@ -235,7 +237,7 @@ def idle_blockchain_policy(blockchain_alice, blockchain_bob):
"""
Creates a Policy, in a manner typical of how Alice might do it, with a unique label
"""
random_label = b'label://' + os.urandom(32)
random_label = generate_random_label()
policy = blockchain_alice.create_policy(blockchain_bob, label=random_label, m=2, n=3)
return policy
@ -263,6 +265,11 @@ def capsule_side_channel(enacted_federated_policy):
return message_kit, enrico
@pytest.fixture(scope="module")
def random_policy_label():
yield generate_random_label()
#
# Alice, Bob, and Ursula
#
@ -347,8 +354,8 @@ def enrico_control_test_client(capsule_side_channel):
@pytest.fixture(scope='module')
def enrico_control_from_alice(federated_alice):
enrico = Enrico.from_alice(federated_alice, b'test')
def enrico_control_from_alice(federated_alice, random_policy_label):
enrico = Enrico.from_alice(federated_alice, random_policy_label)
enrico_control = enrico.make_wsgi_app()
enrico_control.config['DEBUG'] = True