def test_jwt_encode(encryption_type): with get_test_volttron_home(messagebus='zmq') as vhome: if encryption_type == "private_key": algorithm = "HS256" encoded_key = get_random_key().encode("utf-8") else: with certs_profile_1(vhome) as certs: algorithm = "RS256" encoded_key = CertWrapper.get_private_key( certs.server_certs[0].key_file) claims = { "woot": ["bah"], "all I want": 3210, "do it next": { "foo": "billy" } } token = jwt.encode(claims, encoded_key, algorithm) if encryption_type == 'tls': decode_key = CertWrapper.get_cert_public_key( certs.server_certs[0].cert_file) new_claimes = jwt.decode(token, decode_key, algorithm) else: new_claimes = jwt.decode(token, encoded_key, algorithm) assert not DeepDiff(claims, new_claimes)
def test_authenticate_get_request_fails(): with get_test_volttron_home(messagebus='zmq'): authorize_ep, test_user = set_test_admin() env = get_test_web_env('/authenticate', method='GET') response = authorize_ep.handle_authenticate(env, test_user) assert ('Content-Type', 'text/plain') in response.headers.items() assert '405 Method Not Allowed' in response.status
def test_update_platform_config(): my_config = {"bind-web-address": "http://v2:8080", "vip-address": "tcp://127.0.0.1:22196"} with get_test_volttron_home(messagebus='zmq') as vhome: # Empty platform config currently (note this wouldn't be if the platform actually has started) config = get_platform_config() assert 0 == len(config) # Test the config is updated update_platform_config(my_config) # Tests that we get back the correct data. config = get_platform_config() for k, v in my_config.items(): assert v == config.get(k) assert 0 == len(set(my_config) - set(config)) # Make sure that it persisted in the correct file. from configparser import ConfigParser cfg = ConfigParser() cfg.read(get_config_path()) assert cfg.has_section("volttron") for k, v in my_config.items(): assert v == cfg.get("volttron", k)
def test_discovery(scheme): vhome = create_volttron_home() # creates a vhome level key store keystore = KeyStore() serverkey = decode_key(keystore.public) # Depending upon scheme we enable/disable password jwt and certificate based jwt. if scheme == 'https': with certs_profile_1('/'.join([vhome, 'certs'])) as certs: config_params = dict(web_ssl_key=certs.server_certs[0].key_file, web_ssl_cert=certs.server_certs[0].cert_file) else: config_params = dict(web_secret_key=get_random_key()) with get_test_volttron_home(messagebus='zmq', config_params=config_params): instance_name = "booballoon" host, port = get_hostname_and_random_port() # this is the vip address address = f"tcp://{host}:{port}" def _construct_query_mock(core): """ Internal function that creates a concrete response for the data. when query('instance-name').get() is called the passed instance name is returned """ nonlocal instance_name, address kv = {"instance-name": instance_name, "addresses": [address]} return MockQuery(**kv) with mock.patch('volttron.platform.vip.agent.subsystems.query.Query', _construct_query_mock): host, port = get_hostname_and_random_port() bind_web_address = f"{scheme}://{host}:{port}" serverkey = decode_key(keystore.public) mws = MasterWebService(serverkey=serverkey, identity=MASTER_WEB, address=address, bind_web_address=bind_web_address, **config_params) mws.startupagent(sender='testweb') env = get_test_web_env("/discovery/") mock_start_response = mock.Mock() # A closingiterator is returned from the response object so we use the next # on the returned response. Then we can do json responses. response = mws.app_routing(env, mock_start_response).__next__() # load json into a dict for testing responses. response = jsonapi.loads(response.decode('utf-8')) assert response.get('instance-name') is not None assert instance_name == response.get('instance-name') assert keystore.public == response.get('serverkey') assert address == response.get('vip-address')
def test_authenticate_must_use_post_request(): with get_test_volttron_home(messagebus='zmq'): env = get_test_web_env('/authenticate', method='GET') authorize_ep = AuthenticateEndpoints(web_secret_key=get_random_key()) response = authorize_ep.get_auth_token(env, {}) assert ('Content-Type', 'text/html') in response.headers.items() assert '401 Unauthorized' in response.status
def test_both_private_key_and_passphrase(): with pytest.raises( ValueError, match="Must use either ssl_private_key or web_secret_key not both!" ): with get_test_volttron_home(messagebus='zmq') as vhome: with certs_profile_1(vhome) as certs: authorizeep = AuthenticateEndpoints( web_secret_key=get_random_key(), tls_private_key=certs.server_certs[0].key)
def auth_file_platform_tuple(): with get_test_volttron_home('zmq') as vhome: auth_file = AuthFile(os.path.join(vhome, 'auth.json')) gevent.sleep(0.5) yield auth_file allow_entries = auth_file.read_allow_entries() auth_file.remove_by_indices(list(range(3, len(allow_entries)))) gevent.sleep(0.5)
def test_persistent_users(): with get_test_volttron_home(messagebus='zmq'): username_test = "mytest" username_test_passwd = "value-plus" adminep = AdminEndpoints() oid = id(adminep) adminep.add_user(username_test, username_test_passwd, ['admin']) another_ep = AdminEndpoints() assert oid != id(another_ep) assert len(another_ep._userdict) == 1 assert username_test == list(another_ep._userdict)[0]
def test_authenticate_post_request(): with get_test_volttron_home(messagebus='zmq'): authorize_ep, test_user = set_test_admin() env = get_test_web_env('/authenticate', method='POST') response = authorize_ep.handle_authenticate(env, test_user) assert ('Content-Type', 'application/json') in response.headers.items() assert '200 OK' in response.status response_token = json.loads(response.response[0].decode('utf-8')) refresh_token = response_token['refresh_token'] access_token = response_token["access_token"] assert 3 == len(refresh_token.split('.')) assert 3 == len(access_token.split("."))
def test_authenticate_endpoint(scheme): kwargs = {} # Note this is not a context wrapper, it just does the creation for us vhome = create_volttron_home() if scheme == 'https': with certs_profile_1(vhome) as certs: kwargs['web_ssl_key'] = certs.server_certs[0].key_file kwargs['web_ssl_cert'] = certs.server_certs[0].cert_file else: kwargs['web_secret_key'] = get_random_key() # We are specifying the volttron_home here so we don't create an additional one. with get_test_volttron_home(messagebus='zmq', config_params=kwargs, volttron_home=vhome): user = '******' passwd = 'cat' adminep = AdminEndpoints() adminep.add_user(user, passwd) env = get_test_web_env('/authenticate', method='POST') if scheme == 'http': authorizeep = AuthenticateEndpoints( web_secret_key=kwargs.get('web_secret_key')) else: authorizeep = AuthenticateEndpoints( tls_private_key=CertWrapper.load_key(kwargs.get( 'web_ssl_key'))) invalid_login_username_params = dict(username='******', password=passwd) response = authorizeep.get_auth_tokens(env, invalid_login_username_params) # assert '401 Unauthorized' in response.content assert '401 UNAUTHORIZED' == response.status invalid_login_password_params = dict(username=user, password='******') response = authorizeep.get_auth_tokens(env, invalid_login_password_params) assert '401 UNAUTHORIZED' == response.status valid_login_params = urlencode(dict(username=user, password=passwd)) response = authorizeep.get_auth_tokens(env, valid_login_params) assert '200 OK' == response.status assert "application/json" in response.content_type response_data = json.loads(response.data.decode('utf-8')) assert 3 == len(response_data["refresh_token"].split('.')) assert 3 == len(response_data["access_token"].split('.'))
def test_authenticate_delete_request(): with get_test_volttron_home(messagebus='zmq'): authorize_ep, test_user = set_test_admin() # Get tokens for test env = get_test_web_env('/authenticate', method='POST') response = authorize_ep.handle_authenticate(env, test_user) # Touch Delete endpoint env = get_test_web_env('/authenticate', method='DELETE') response = authorize_ep.handle_authenticate(env, test_user) assert ('Content-Type', 'text/plain') in response.headers.items() assert '501 Not Implemented' in response.status
def test_admin_unauthorized(): config_params = {"web-secret-key": get_random_key()} with get_test_volttron_home(messagebus='zmq', config_params=config_params): myuser = '******' mypass = '******' adminep = AdminEndpoints() adminep.add_user(myuser, mypass) # User hasn't logged in so this should be not authorized. env = get_test_web_env('/admin/api/boo') response = adminep.admin(env, {}) assert '401 Unauthorized' == response.status assert b'Unauthorized User' in response.response[0]
def test_add_user(): with get_test_volttron_home(messagebus='zmq') as vhome: webuserpath = os.path.join(vhome, ___WEB_USER_FILE_NAME__) assert not os.path.exists(webuserpath) username_test = "test" username_test_passwd = "passwd" adminep = AdminEndpoints() adminep.add_user(username_test, username_test_passwd, ['admin']) # since add_user is async with persistance we use sleep to allow the write # gevent.sleep(0.01) assert os.path.exists(webuserpath) with open(webuserpath) as fp: users = jsonapi.load(fp) assert len(users) == 1 assert users.get(username_test) is not None user = users.get(username_test) objid = id(user) assert ['admin'] == user['groups'] assert user['hashed_password'] is not None original_hashed_passwordd = user['hashed_password'] # raise ValueError if not overwrite == True with pytest.raises( ValueError, match= f"The user {username_test} is already present and overwrite not set to True" ): adminep.add_user(username_test, username_test_passwd, ['admin']) # make sure the overwrite works because we are changing the group adminep.add_user(username_test, username_test_passwd, ['read_only', 'jr-devs'], overwrite=True) assert os.path.exists(webuserpath) with open(webuserpath) as fp: users = jsonapi.load(fp) assert len(users) == 1 assert users.get(username_test) is not None user = users.get(username_test) assert objid != id(user) assert ['read_only', 'jr-devs'] == user['groups'] assert user['hashed_password'] is not None assert original_hashed_passwordd != user['hashed_password']
def test_admin_login_page(): with get_test_volttron_home(messagebus='zmq'): username_test = "mytest" username_test_passwd = "value-plus" adminep = AdminEndpoints() adminep.add_user(username_test, username_test_passwd, ['admin']) myenv = get_test_web_env(path='login.html') response = adminep.admin(myenv, {}) jinja_mock = myenv['JINJA2_TEMPLATE_ENV'] assert 1 == jinja_mock.get_template.call_count assert ('login.html', ) == jinja_mock.get_template.call_args[0] assert 1 == jinja_mock.get_template.return_value.render.call_count assert 'text/html' == response.headers.get('Content-Type') # assert ('Content-Type', 'text/html') in response.headers assert '200 OK' == response.status
def test_authenticate_put_request(): with get_test_volttron_home(messagebus='zmq'): authorize_ep, test_user = set_test_admin() # Get tokens for test env = get_test_web_env('/authenticate', method='POST') response = authorize_ep.handle_authenticate(env, test_user) response_token = json.loads(response.response[0].decode('utf-8')) refresh_token = response_token['refresh_token'] access_token = response_token["access_token"] # Test PUT Request env = get_test_web_env('/authenticate', method='PUT') env["HTTP_AUTHORIZATION"] = "BEARER " + refresh_token response = authorize_ep.handle_authenticate(env, data={}) assert ('Content-Type', 'application/json') in response.headers.items() assert '200 OK' in response.status
def test_get_random_key(): with get_test_volttron_home(messagebus='zmq'): key = get_random_key() # According to docs the default length is 65 # # Note 2x default is what we should get due to hex encoding. assert 130 == len(key) with pytest.raises(ValueError) as err: key = get_random_key(0) with pytest.raises(ValueError) as err: key = get_random_key(-1) key = get_random_key(20) # note 2x the passed random key assert 40 == len(key)
def test_authenticate_put_request_refresh_expires(): with get_test_volttron_home(messagebus='zmq'): authorize_ep, test_user = set_test_admin() # Get tokens for test env = get_test_web_env('/authenticate', method='POST') response = authorize_ep.handle_authenticate(env, test_user) response_token = json.loads(response.response[0].decode('utf-8')) refresh_token = response_token['refresh_token'] access_token = response_token["access_token"] # Wait for refresh token to expire gevent.sleep(20) env = get_test_web_env('/authenticate', method='PUT') env["HTTP_AUTHORIZATION"] = "BEARER " + refresh_token response = authorize_ep.handle_authenticate(env, data={}) assert ('Content-Type', 'text/html') in response.headers.items() assert "401 Unauthorized" in response.status
def test_authenticate_endpoint(scheme): kwargs = {} # Note this is not a context wrapper, it just does the creation for us vhome = create_volttron_home() if scheme == 'https': with certs_profile_1(vhome) as certs: kwargs['web_ssl_key'] = certs.server_certs[0].key_file kwargs['web_ssl_cert'] = certs.server_certs[0].cert_file else: kwargs['web_secret_key'] = binascii.hexlify( os.urandom(65)).decode('utf-8') host, port = get_hostname_and_random_port() kwargs['bind_web_address'] = f"{scheme}://{host}:{port}" # We are specifying the volttron_home here so we don't create an additional one. with get_test_volttron_home(messagebus='zmq', config_params=kwargs, volttron_home=vhome): # add a user so that we can actually log in. user = '******' passwd = 'cat' adminep = AdminEndpoints() adminep.add_user(user, passwd, groups=['foo', 'read-only']) expected_claims = dict(groups=['foo', 'read-only']) with get_master_web(**kwargs) as mw: data = urlencode(dict(username=user, password=passwd)).encode('utf-8') assert len(data) > 0 # test not sending any parameters. env = get_test_web_env("/authenticate", input_data=data, method='POST') mocked_start_response, response = get_server_response(env, mw) assert 3 == len(response.split(".")) claims = mw.get_user_claims(response) assert claims assert not DeepDiff(expected_claims, claims)
def test_set_platform_password_setup(): with get_test_volttron_home(messagebus='zmq') as vhome: # Note these passwords are not right so we expect to be redirected back to the # first.html params = urlencode( dict(username='******', password1='goodwin', password2='wowsa')) env = get_test_web_env("/admin/setpassword", method='POST') # , input_data=input) jinja_mock = env['JINJA2_TEMPLATE_ENV'] adminep = AdminEndpoints() response = adminep.admin(env, params) assert 'Location' not in response.headers assert 200 == response.status_code assert 'text/html' == response.headers.get('Content-Type') assert 1 == jinja_mock.get_template.call_count assert ('first.html', ) == jinja_mock.get_template.call_args[0] assert 1 == jinja_mock.get_template.return_value.render.call_count jinja_mock.reset_mock() # Now we have the correct password1 and password2 set we expect to redirected to # /admin/login.html params = urlencode( dict(username='******', password1='wowsa', password2='wowsa')) env = get_test_web_env("/admin/setpassword", method='POST') # , input_data=input) # expect Location and Content-Type headers to be set response = adminep.admin(env, params) assert 3 == len(response.headers) assert 'Location' in response.headers assert '/admin/login.html' == response.headers.get('Location') assert 302 == response.status_code webuserpath = os.path.join(vhome, 'web-users.json') with open(webuserpath) as wup: users = jsonapi.load(wup) assert users.get('bart') is not None user = users.get('bart') assert user['hashed_password'] is not None assert argon2.verify("wowsa", user['hashed_password'])