"""Tests for Auth0 handlers.""" import json from unittest.mock import patch from owsrequest import error_response from owsresponse import response import pytest from users import ( # noqa config, ) from users.connectors import redis from users.logic import auth0_client @pytest.mark.parametrize( ('user_id', 'formatted_user_id'), ( ('5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc'), ('auth0|5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc'), ('google-apps|someone@theorchard.com', 'google-apps|someone@theorchard.com'), ), ) def test_get_auth0_user_by_id(mocker, fixture_client, user_id, formatted_user_id): """Test /auth0/users/ handler.""" mocker.patch.object(auth0_client, 'read_user', return_value=response.Response()) fixture_client.get('/auth0/users/{}'.format(user_id)) auth0_client.read_user.assert_called_with(formatted_user_id) @pytest.mark.parametrize( ( 'user_id', 'formatted_user_id', 'redis_get_response', 'auth0_user_response', 'redis_set_called', 'user_picture', ), ( # cache hit success, unformatted Auth0 user id ( '5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc', 'http://picture.jpg'.encode('utf8'), None, False, 'http://picture.jpg', ), # cache hit success, prefixed Auth0 user id ( 'auth0|5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc', 'http://picture.jpg'.encode('utf8'), None, False, 'http://picture.jpg', ), # cache hit success, google-apps Auth0 user ( 'google-apps|someone@theorchard.com', 'google-apps|someone@theorchard.com', 'http://picture.jpg'.encode('utf8'), None, False, 'http://picture.jpg', ), # cache hit fail, call Auth0 ( 'auth0|5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc', None, response.Response(message={'picture': 'http://picture.jpg'}), True, 'http://picture.jpg', ), # cache hit fail, call Auth0, user picture is null ( 'auth0|5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc', None, response.Response(message={'picture': None}), True, '', ), # user not found ( 'auth0|5e4c2b5cf650080c9f572acc', 'auth0|5e4c2b5cf650080c9f572acc', None, response.create_not_found_response(), True, '', ), ), ) def test_read_user_picture_with_auth0_user_id( mocker, fixture_client, user_id, formatted_user_id, redis_get_response, auth0_user_response, redis_set_called, user_picture, ): """Test /auth0/users//picture handler.""" mocker.patch.object(redis.client, 'get', return_value=redis_get_response) mocker.patch.object(auth0_client, 'read_user', return_value=auth0_user_response) mocker.patch.object(redis.client, 'set') result = fixture_client.get(f'/auth0/users/{user_id}/picture') if redis_get_response: auth0_client.read_user.assert_not_called() else: auth0_client.read_user.assert_called_with(formatted_user_id) if redis_set_called: redis.client.set.assert_called_with( f'user_picture_{formatted_user_id}', '' if user_picture is None else user_picture, ex=config.REDIS_CACHE_TTL, ) else: redis.client.set.assert_not_called() assert result.data.decode('utf8') == user_picture def test_get_bulk_paginated_auth0_users(mocker, fixture_client): """Test /auth0/users/bulk handler.""" expected_response = {'user': 'user data'} mocker.patch.object( auth0_client, 'get_bulk_auth0_users_paginated', return_value=response.Response(expected_response), ) handler_response = fixture_client.get('/auth0/users/bulk?auth0_ids=123&auth0_ids=456') assert handler_response.status_code == 200 result = json.loads(handler_response.data.decode()) assert result == expected_response auth0_client.get_bulk_auth0_users_paginated.assert_called_with(['123', '456']) @pytest.mark.parametrize( ('email', 'email_to_logic', 'client_id'), [ ['test@theorchard.com', 'test@theorchard.com', None], ['Test@theorchard.com', 'test@theorchard.com', None], ['fooBar@theorchard.com', 'foobar@theorchard.com', None], ['FOO@theorchard.com', 'foo@theorchard.com', None], ['alice@ex.com', 'alice@ex.com', 'cid-123'], ], ) def test_create_user_password_reset( mocker, fixture_client, email, email_to_logic, client_id ): """Test create_user_password_reset handler.""" expected_response = {'user': 'details'} mocker.patch.object( auth0_client, 'send_password_reset', return_value=response.Response(expected_response) ) payload = {'email': email} if client_id is not None: payload['client_id'] = client_id handler_response = fixture_client.post('/auth0/recovery', json=payload) assert handler_response.status_code == 200 result = json.loads(handler_response.data.decode()) assert result == {} # Assert that send_password_reset was called with the correct positional args if client_id is None: auth0_client.send_password_reset.assert_called_with( email_to_logic, config.AUTH0_CONNECTION, ) else: auth0_client.send_password_reset.assert_called_with( email_to_logic, config.AUTH0_CONNECTION, client_id, ) @pytest.fixture() def test_create_user_with_auth0(mocker, fixture_client, request_context): """Test create_user_with_auth0 handler.""" from users import app expected_response = {'user': 'user data'} post_data = { 'name': 'Foo Bar User2', 'email': 'Foo.Bar2@theorchard.com', 'given_name': 'Foo Bar', 'family_name': 'User', 'user_metadata': {}, } mocker.patch.object( auth0_client, 'create_user', return_value=response.Response(expected_response) ) audit_user = 'test-audit-user-identity-id' with app.app.test_request_context(): request_context.identity_id = audit_user handler_response = fixture_client.post('/auth0/users', json=post_data) assert handler_response.status_code == 200 result = json.loads(handler_response.data.decode()) assert result == expected_response auth0_client.create_user.assert_called_with(post_data, audit_user) @pytest.mark.parametrize( ('auth0_id', 'identity_id'), [['6233ada3da', None], ['auth0|6233ada3da', 'a-uuid']] ) @patch('users.handlers.auth0.Neo4jSession.__enter__') @patch('users.handlers.auth0.Neo4jSession.__exit__') @patch('users.handlers.auth0.user_info') def test_set_auth0_primary_user(user_info_mock, _, __, fixture_client, auth0_id, identity_id): """Test PUT /users/auth0//primary handler.""" user_id = 1313901 user_info_mock.set_auth0_primary_user.return_value = response.Response() fixture_client.put( f'users/auth0/{auth0_id}/primary', json={'user_id': user_id, 'identity_id': identity_id} ) user_info_mock.set_auth0_primary_user.assert_called_with( auth0_id='auth0|6233ada3da', user_id=user_id, identity_id=identity_id, ) @pytest.mark.parametrize( ('test_email', 'read_user_response', 'update_user_response'), [ [ 'test@testemail.com', response.Response({'email': 'test@testemail.com'}), response.Response(), ], [ 'test@sonymusic-pde.com', response.Response({'email': 'test@sonymusic-pde.com'}), response.Response(), ], ], ) @patch('users.handlers.auth0.authorization.authorize_email_change', return_value=True) @patch('users.handlers.auth0.Neo4jSession.__enter__') @patch('users.handlers.auth0.Neo4jSession.__exit__') @patch('users.handlers.auth0.auth0_client') def test_update_user_with_auth0( auth0_client_mock, _, __, _authz, fixture_client, test_email, read_user_response, update_user_response, ): """Test PATCH /auth0/users/ handler.""" user_id = 'auth0|623' data = { 'name': 'Foo Bar User2', 'email': test_email, 'user_metadata': {}, } auth0_client_mock.read_user.return_value = read_user_response auth0_client_mock.update_user.return_value = update_user_response fixture_client.patch(f'/auth0/users/{user_id}', json=data) if update_user_response: auth0_client_mock.update_user.assert_called_with(user_id, data) else: auth0_client_mock.update_user.assert_not_called() def test_get_auth0_organization(mocker, fixture_client): """Test get_auth0_organization handler.""" org_id = 'org_123' expected_response = {'org': 'details'} mocker.patch.object( auth0_client, 'get_organization', return_value=response.Response(expected_response) ) handler_response = fixture_client.get(f'/auth0/organizations/{org_id}') assert handler_response.status_code == 200 result = json.loads(handler_response.data.decode()) assert result == expected_response auth0_client.get_organization.assert_called_with(org_id) @patch('users.handlers.auth0.Neo4jSession.__enter__') @patch('users.handlers.auth0.Neo4jSession.__exit__') @patch('users.handlers.auth0.auth0_client') @patch('users.handlers.auth0.authorization.authorize_email_change', return_value=False) def test_update_user_with_auth0_forbidden(authz_mock, auth0_client_mock, _, __, fixture_client): """PATCH /auth0/users/ returns 403 and skips the update when unauthorized.""" resp = fixture_client.patch('/auth0/users/auth0|623', json={'email': 'x@gmail.com'}) assert resp.status_code == 403 authz_mock.assert_called_once() auth0_client_mock.update_user.assert_not_called() # Tests moved from test_identity_handler.py @pytest.mark.parametrize( ('headers', 'expected_response'), [ [ { 'Orchard-Identity-Id': 'admin-uuid', 'Orchard-Profile-Id': '123', 'Orchard-Profile-Type': 'SettingsProfile', }, response.Response(message={'success': 'response'}), ], [{}, error_response.create_error_forbidden()], [ { 'Grass-Account-Type': 'vendor', 'Grass-Account-Id': 7123, 'Orchard-Identity-Id': 'alw:1234', }, error_response.create_error_forbidden(), ], ], ) @patch('users.handlers.auth0.Neo4jSession.__enter__') @patch('users.handlers.auth0.Neo4jSession.__exit__') def test_create_organization_invitation( neo4j_exit, neo4j_enter, headers, expected_response, mocker, fixture_client ): """Test create_organization_invitation.""" data = { 'brand': 'awal', 'email': 'foo@bar.com', 'admin_name': 'Insights Admin', 'auth0_application_name': 'insights-login', 'user_metadata': {'orchardIdentityId': 'uuid'}, } auth0_mock = mocker.patch('users.logic.auth0_client.create_organization_invitation') auth0_mock.return_value = response.Response(message={'success': 'response'}) handler_response = fixture_client.post( '/auth0/invite/organization-member', json=data, headers=headers ) assert handler_response.status_code == expected_response.status if expected_response: assert auth0_mock.called @pytest.mark.parametrize( ('data', 'expected_status', 'updated_data'), [ [ { # Test Case #1: (SUCCESS) brand in list matching case 'brand': 'orchArd', 'members': ['auth0|test1'], }, 200, {'brand': 'orchard', 'members': ['auth0|test1']}, ], [ { # Test Case #2: (SUCCESS) brand in list with different case 'brand': 'OrChArD', 'members': ['auth0|test1'], }, 200, {'brand': 'orchard', 'members': ['auth0|test1']}, ], [ { # Test Case #3: (FAILURE) brand not in list 'brand': 'test', 'members': ['auth0|test1'], }, 400, None, ], ], ) def test_create_organization_members(mocker, fixture_client, data, expected_status, updated_data): """Test create_organization_members.""" expected_response = response.Response(status=expected_status, message='') auth0_mock = mocker.patch('users.logic.auth0_client.create_organization_members') auth0_mock.return_value = expected_response handler_response = fixture_client.post('/auth0/add/organization-members', json=data) assert handler_response.status_code == expected_response.status if expected_status == 200: assert auth0_mock.called auth0_mock.assert_called_with(updated_data, None) else: assert auth0_mock.not_called @pytest.mark.parametrize( ('headers', 'expected_response'), [ [ { 'Orchard-Identity-Id': 'admin-uuid', 'Orchard-Profile-Id': 123, 'Orchard-Profile-Type': 'SettingsProfile', }, response.Response(message=['awal']), ], [{}, error_response.create_error_forbidden()], [ { 'Grass-Account-Type': 'vendor', 'Grass-Account-Id': 7123, 'Orchard-Identity-Id': 'alw:1234', }, error_response.create_error_forbidden(), ], ], ) def test_get_auth0_user_organizations(headers, expected_response, mocker, fixture_client): """Test get_auth0_user_organizations.""" auth0_user_id = 'auth0|12345abcdef' auth0_mock = mocker.patch('users.logic.auth0_client.list_user_organizations') auth0_mock.return_value = response.Response(message=['awal']) handler_response = fixture_client.get(f'/auth0/{auth0_user_id}/organizations', headers=headers) assert handler_response.status_code == expected_response.status if expected_response: assert auth0_mock.called @patch('users.handlers.auth0.authorization.authorize_email_change', return_value=True) @patch('users.handlers.auth0.Neo4jSession.__enter__') @patch('users.handlers.auth0.Neo4jSession.__exit__') @patch('users.handlers.auth0.identities_model') @patch('users.handlers.auth0.auth0_client') def test_update_user_email_already_in_use_is_blocked( auth0_client_mock, identities_mock, _exit, _enter, _authz, fixture_client ): """Changing to an email already in use by an existing identity is rejected (409)""" auth0_client_mock.read_user.return_value = response.Response({'email': 'current@example.com'}) identities_mock.get_identity_by_email.return_value = response.Response( {'id': 'other-identity-id'} ) resp = fixture_client.patch('/auth0/users/623', json={'email': 'taken@example.com'}) assert resp.status_code == 409 auth0_client_mock.update_user.assert_not_called() @patch('users.handlers.auth0.authorization.authorize_email_change', return_value=True) @patch('users.handlers.auth0.Neo4jSession.__enter__') @patch('users.handlers.auth0.Neo4jSession.__exit__') @patch('users.handlers.auth0.identities_model') @patch('users.handlers.auth0.auth0_client') def test_update_user_email_not_in_use_is_allowed( auth0_client_mock, identities_mock, _exit, _enter, _authz, fixture_client ): """The guard does not false-positive: an email no identity owns still goes through.""" auth0_client_mock.read_user.return_value = response.Response({'email': 'current@example.com'}) auth0_client_mock.update_user.return_value = response.Response({'email': 'unused@example.com'}) identities_mock.get_identity_by_email.return_value = response.create_not_found_response( message='Identity not found' ) data = {'email': 'unused@example.com'} resp = fixture_client.patch('/auth0/users/623', json=data) assert resp.status_code == 200 auth0_client_mock.update_user.assert_called_once_with('auth0|623', data)