diff --git a/cloudfile_ext/sso/tests/test_eap_oauth.py b/cloudfile_ext/sso/tests/test_eap_oauth.py index f9178d91b19..2e5fe8d2028 100644 --- a/cloudfile_ext/sso/tests/test_eap_oauth.py +++ b/cloudfile_ext/sso/tests/test_eap_oauth.py @@ -35,8 +35,11 @@ def test_changed_uid_or_disagreement_never_merges_accounts(profile, uid_rows): def test_cfadmin_is_never_reused_by_employee_identity(): + # Only the exact built-in account is reserved, not a customer's email prefix. with pytest.raises(IdentityConflict): - choose_native('123', '1022', 'cfadmin@external.com', ('cfadmin@external.com', '123'), []) + choose_native('123', '1022', 'cfadmin@auth.local', ('cfadmin@auth.local', '123'), []) + assert choose_native('123', '1022', 'cfadmin@external.com', + ('cfadmin@external.com', '123'), []) == 'cfadmin@external.com' @pytest.fixture diff --git a/cloudfile_ext/sso/tests/test_identity_bridge.py b/cloudfile_ext/sso/tests/test_identity_bridge.py index beddd80ae4f..6c53783b0b5 100644 --- a/cloudfile_ext/sso/tests/test_identity_bridge.py +++ b/cloudfile_ext/sso/tests/test_identity_bridge.py @@ -124,13 +124,20 @@ def test_employee_profile_does_not_override_exact_uid_binding(): @pytest.mark.parametrize('employee,native', [ ('cfadmin', 'native-admin'), ('CFADMIN', 'native-admin'), - ('emp-1', 'cfadmin@external.com'), ('emp-1', 'cfadmin@auth.local')]) + ('emp-1', 'cfadmin@auth.local')]) def test_system_admin_is_excluded_from_employee_projection(employee, native): # System administration remains independent even if a bad feed includes it. assert resolve_eap_pairs([{'user_id': 'uid-1', 'employee_no': employee}], fetch=lambda keys: [('uid-1', native)]) == {} +def test_external_admin_name_is_not_the_reserved_native_account(): + # Email prefix alone does not identify the built-in CE administrator. + assert resolve_eap_pairs([{'user_id': 'uid-1', 'employee_no': 'emp-1'}], + fetch=lambda keys: [('uid-1', 'cfadmin@external.com')]) == { + 'uid-1': 'cfadmin@external.com'} + + def test_employee_reused_for_different_uid_never_merges_native_accounts(): mapped = resolve_eap_pairs([ {'user_id': 'uid-1', 'employee_no': 'emp'}, diff --git a/cloudfile_ext/sso/tests/test_token_identity.py b/cloudfile_ext/sso/tests/test_token_identity.py index a274c16201f..57e634219c8 100644 --- a/cloudfile_ext/sso/tests/test_token_identity.py +++ b/cloudfile_ext/sso/tests/test_token_identity.py @@ -33,7 +33,7 @@ def test_legacy_channel_is_rejected(): resolve_employee_token('10220942@auth.local', source(context(), 'legacy')) -@pytest.mark.parametrize('rows', [[], [('381','one@auth.local'), ('381','two@auth.local')], [('381','cfadmin@external.com')]]) +@pytest.mark.parametrize('rows', [[], [('381','one@auth.local'), ('381','two@auth.local')], [('381','cfadmin@auth.local')]]) def test_missing_ambiguous_or_system_account_is_rejected(rows): with pytest.raises(TokenIdentityError): resolve_employee_token('10220942@auth.local', source(context()), lambda _: rows) @@ -46,3 +46,9 @@ def test_employee_recycling_cannot_reuse_old_uid(): # fail rather than inherit that user's token or library permissions. with pytest.raises(IdentityBridgeError): resolve_employee_token('10220942@auth.local', source(changed), lambda _: [('381','old@auth.local')]) + + +def test_external_admin_name_is_not_the_reserved_native_account(): + # Match the exact built-in account; unrelated customer domains remain valid. + assert resolve_employee_token('10220942@auth.local', source(context()), + lambda _: [('381', 'cfadmin@external.com')]) == 'cfadmin@external.com' diff --git a/cloudfile_ext/v2/tests/test_acl_http.py b/cloudfile_ext/v2/tests/test_acl_http.py index 54e72afd028..0dbecaf535c 100644 --- a/cloudfile_ext/v2/tests/test_acl_http.py +++ b/cloudfile_ext/v2/tests/test_acl_http.py @@ -39,6 +39,8 @@ def test_directory_rules_allow_native_repo_admin_without_system_staff(self): patch('cloudfile_ext.v2.acl_http.store') as rules, \ patch('seaserv.seafile_api.get_dir_id_by_path', return_value='d' * 40), \ patch('cloudfile_ext.v2.acl_http.settings') as settings: + # Explicit preferred setting avoids auto-created Mock namespace values. + settings.CF_DIRECTORY_PROVIDER = 'external' settings.CF_ACL_PROVIDER = 'external' rules.return_value.list_path.return_value = [] result = self.request(DirectoryRules, dict(repo_id=RID, path='/folder')) @@ -97,6 +99,8 @@ def test_public_rule_writer_uses_validated_three_permission_cas(self): with patch('cloudfile_ext.v2.acl_http.require_repo_manage'), \ patch('cloudfile_ext.v2.acl_http.store') as rules, \ patch('cloudfile_ext.v2.acl_http.settings') as settings: + # Explicit preferred setting avoids auto-created Mock namespace values. + settings.CF_DIRECTORY_PROVIDER = 'external' settings.CF_ACL_PROVIDER = 'external' rules.return_value.mutate.return_value = request['revision'] result = self.request(DirectoryRules, request, method='post') diff --git a/cloudfile_ext/v2/tests/test_acl_library_batch.py b/cloudfile_ext/v2/tests/test_acl_library_batch.py index e082d6467a6..660b4c8dc4c 100644 --- a/cloudfile_ext/v2/tests/test_acl_library_batch.py +++ b/cloudfile_ext/v2/tests/test_acl_library_batch.py @@ -35,6 +35,8 @@ def test_old_directory_route_and_additive_batch_get(self): with patch('cloudfile_ext.v2.acl_http.require_repo_manage') as auth, \ patch('cloudfile_ext.v2.acl_http.store') as rules, \ patch('cloudfile_ext.v2.acl_http.settings') as settings: + # Explicit preferred setting avoids auto-created Mock namespace values. + settings.CF_DIRECTORY_PROVIDER = 'external' settings.CF_ACL_PROVIDER = 'external' rules.return_value.provider_snapshot.return_value = ((RULE, REVISION),) result = self.request('get', {'repo_id': RID}) @@ -50,6 +52,8 @@ def test_batch_put_uses_server_provider_and_one_atomic_store_call(self): with patch('cloudfile_ext.v2.acl_http.require_repo_manage'), \ patch('cloudfile_ext.v2.acl_http.store') as rules, \ patch('cloudfile_ext.v2.acl_http.settings') as settings: + # Explicit preferred setting avoids auto-created Mock namespace values. + settings.CF_DIRECTORY_PROVIDER = 'external' settings.CF_ACL_PROVIDER = 'external' rules.return_value.replace_owned.return_value = dict( fingerprint='a' * 64, inserted=0, updated=1, deleted=0, unchanged=0) @@ -68,6 +72,8 @@ def test_batch_resource_preview_reuses_one_snapshot_and_checks_every_directory(s patch('cloudfile_ext.v2.acl_http.store') as rules, \ patch('seaserv.seafile_api.get_dir_id_by_path') as exists, \ patch('cloudfile_ext.v2.acl_http.settings') as settings: + # Explicit preferred setting avoids auto-created Mock namespace values. + settings.CF_DIRECTORY_PROVIDER = 'external' settings.CF_ACL_PROVIDER = 'external' exists.side_effect = lambda repo, path: 'd' * 40 if path == '/shared' else None rules.return_value.provider_snapshot.return_value = ((RULE, REVISION),) diff --git a/cloudfile_ext/v2/tests/test_enterprise_subject_adapter.py b/cloudfile_ext/v2/tests/test_enterprise_subject_adapter.py index 23d8be4b78a..7849340be68 100644 --- a/cloudfile_ext/v2/tests/test_enterprise_subject_adapter.py +++ b/cloudfile_ext/v2/tests/test_enterprise_subject_adapter.py @@ -23,9 +23,15 @@ def test_explicit_custom_provider_supports_its_own_directory_contract(self): CF_ACL_PROVIDER='corp', CF_ACL_SUBJECT_ENSURE='package.directory.refresh')), ('corp', 'package.directory.refresh')) + def test_directory_accepts_explicit_namespace_without_custom_loader(self): + # The built-in Directory adapter needs no custom refresh callback. + self.assertEqual(subject_adapter(SimpleNamespace( + CF_DIRECTORY_URL='https://directory.example.test', CF_ACL_PROVIDER='corp')), + ('corp', None)) + def test_half_configured_external_provider_never_downgrades_to_ce(self): for config in ( - SimpleNamespace(CF_DIRECTORY_URL='https://directory.example.test', CF_ACL_PROVIDER='corp'), + SimpleNamespace(CF_ACL_PROVIDER='corp'), SimpleNamespace(CF_ACL_SUBJECT_ENSURE='package.directory.refresh'), SimpleNamespace(CF_ACL_PROVIDER='native-local', CF_ACL_SUBJECT_ENSURE='package.directory.refresh'),