|
27 | 27 |
|
28 | 28 | """Unit tests for the griddaemons helper functions""" |
29 | 29 |
|
| 30 | +import binascii |
30 | 31 | import os |
31 | 32 | import time |
32 | 33 | import unittest |
33 | 34 |
|
34 | 35 | # Imports required for the unit test wrapping |
| 36 | +from mig.shared.base import client_id_dir, distinguished_name_to_user |
35 | 37 | from mig.shared.defaults import ( |
36 | | - # X509_USER_ID_FORMAT, UUID_USER_ID_FORMAT, |
37 | | - READ_WRITE_ACCESS, READ_ONLY_ACCESS, WRITE_ONLY_ACCESS |
| 38 | + X509_USER_ID_FORMAT, UUID_USER_ID_FORMAT, |
| 39 | + READ_WRITE_ACCESS, READ_ONLY_ACCESS, WRITE_ONLY_ACCESS, |
| 40 | + keyword_auto |
38 | 41 | ) |
39 | 42 | from mig.shared.useradm import ( |
40 | | - _ensure_dirs_needed_for_userdb # , generate_password_hash |
| 43 | + _ensure_dirs_needed_for_userdb, create_user # , generate_password_hash |
41 | 44 | ) |
42 | 45 |
|
43 | 46 | # Imports of the code under test |
|
67 | 70 | TEST_USER_PW_HASH = "PBKDF2$sha256$10000$XMZGaar/pU4PvWDr$w0dYjezF6JGtSiYPexyZMt3lM1234uxi" |
68 | 71 |
|
69 | 72 |
|
| 73 | +def _provision_uuid_test_user(configuration, client_id, client_overrides=None): |
| 74 | + """Helper to provision test users when UUID format is used""" |
| 75 | + # TODO: merge something like this version into standard _provision_test_user? |
| 76 | + # IMPORTANT: we need to use explicit create_user here for UUID format! |
| 77 | + user_dict = distinguished_name_to_user(client_id) |
| 78 | + # NOTE: generate unique and short id based on id to avoid test collisions |
| 79 | + user_dict["unique_id"] = binascii.hexlify( |
| 80 | + client_id.encode('utf8')).decode('ascii') |
| 81 | + user_dict["short_id"] = binascii.hexlify( |
| 82 | + user_dict["email"].encode('utf8')).decode('ascii') |
| 83 | + user_dict["comment"] = "This is the user account comment" |
| 84 | + user_dict["locality"] = "" |
| 85 | + user_dict["organizational_unit"] = "" |
| 86 | + user_dict["password"] = "" |
| 87 | + user_dict["password_hash"] = "" |
| 88 | + if client_overrides is not None: |
| 89 | + user_dict.update(client_overrides) |
| 90 | + |
| 91 | + create_user( |
| 92 | + user_dict, |
| 93 | + configuration, |
| 94 | + keyword_auto, |
| 95 | + default_renew=True, |
| 96 | + ask_renew=False, |
| 97 | + ) |
| 98 | + return user_dict |
| 99 | + |
| 100 | + |
70 | 101 | def _prepare_auth_files(home_path, auth_protos=None): |
71 | 102 | """Helper to create helper auth files for on eor more auth_protos. |
72 | 103 | If None is passed ssh, ftps and davs auth files will be made. |
@@ -760,6 +791,8 @@ def _provide_configuration(self): |
760 | 791 |
|
761 | 792 | def before_each(self): |
762 | 793 | """Set up test configuration and reset state before each test.""" |
| 794 | + # Force X509 user id format |
| 795 | + self.configuration.site_user_id_format = X509_USER_ID_FORMAT |
763 | 796 | # Ensure required directories exist |
764 | 797 | _ensure_dirs_needed_for_userdb(self.configuration) |
765 | 798 | self.expected_user_db_home = os.path.normpath( |
@@ -952,6 +985,231 @@ def test_refresh_user_creds_invalid_protocol(self): |
952 | 985 | self.assertEqual(len(changed_users), 0) |
953 | 986 | self.assertEqual(len(updated_conf['users']), 0) |
954 | 987 |
|
| 988 | +# TODO: merge X509 and UUID versions? |
| 989 | + |
| 990 | + |
| 991 | +class MigSharedGriddaemonsLogin__refresh_user_creds_uuid_user_id(MigTestCase, UserAssertMixin): |
| 992 | + """Unit tests for the griddaemons login refresh_user_creds helper with UUID users.""" |
| 993 | + |
| 994 | + def _provide_configuration(self): |
| 995 | + """Return a test configuration instance.""" |
| 996 | + return 'testconfig' |
| 997 | + |
| 998 | + def before_each(self): |
| 999 | + """Set up test configuration and reset state before each test.""" |
| 1000 | + # Force UUID user id format |
| 1001 | + self.configuration.site_user_id_format = UUID_USER_ID_FORMAT |
| 1002 | + # Ensure required directories exist |
| 1003 | + ensure_dirs_exist(self.configuration.mig_system_files) |
| 1004 | + _ensure_dirs_needed_for_userdb(self.configuration) |
| 1005 | + self.expected_user_db_home = os.path.normpath( |
| 1006 | + self.configuration.user_db_home |
| 1007 | + ) |
| 1008 | + self.expected_user_db_file = os.path.join( |
| 1009 | + self.expected_user_db_home, "MiG-users.db" |
| 1010 | + ) |
| 1011 | + # NOTE: we need to set a password_hash for https test to work with |
| 1012 | + overrides = {} |
| 1013 | + overrides["password_hash"] = TEST_USER_PW_HASH |
| 1014 | + |
| 1015 | + user_dict = _provision_uuid_test_user(self.configuration, TEST_USER_DN, |
| 1016 | + overrides) |
| 1017 | + self.test_user_id = user_id = user_dict['unique_id'] |
| 1018 | + client_dir = client_id_dir(TEST_USER_DN) |
| 1019 | + self.test_user_home = os.path.join(self.configuration.user_home, |
| 1020 | + user_id) |
| 1021 | + self.test_user_dir = os.path.basename(self.test_user_home) |
| 1022 | + self.test_user_home_x509 = os.path.join(self.configuration.user_home, |
| 1023 | + client_dir) |
| 1024 | + self.test_user_dir_x509 = os.path.basename(self.test_user_home_x509) |
| 1025 | + # Make sure X509 and alias links are provisioned as well |
| 1026 | + if not os.path.islink(self.test_user_home_x509): |
| 1027 | + os.symlink(self.test_user_home, self.test_user_home_x509) |
| 1028 | + alias_link_path = os.path.join(self.configuration.user_home, |
| 1029 | + TEST_USER_EMAIL) |
| 1030 | + if not os.path.islink(alias_link_path): |
| 1031 | + os.symlink(self.test_user_home_x509, alias_link_path) |
| 1032 | + |
| 1033 | + ALIAS_FIELD = 'email' |
| 1034 | + self.configuration.user_sftp_alias = ALIAS_FIELD |
| 1035 | + self.configuration.user_ftps_alias = ALIAS_FIELD |
| 1036 | + self.configuration.user_davs_alias = ALIAS_FIELD |
| 1037 | + |
| 1038 | + # Common daemon configuration |
| 1039 | + self.configuration.daemon_conf = {} |
| 1040 | + self.configuration.daemon_conf['time_stamp'] = 0 |
| 1041 | + self.configuration.daemon_conf['users'] = [] |
| 1042 | + self.configuration.daemon_conf['root_dir'] = self.configuration.user_home |
| 1043 | + self.configuration.daemon_conf['db_path'] = self.expected_user_db_file |
| 1044 | + self.configuration.daemon_conf['allow_publickey'] = True |
| 1045 | + self.configuration.daemon_conf['allow_password'] = True |
| 1046 | + self.configuration.daemon_conf['allow_digest'] = False |
| 1047 | + self.configuration.daemon_conf['user_alias'] = ALIAS_FIELD |
| 1048 | + |
| 1049 | + def test_refresh_user_creds_ssh_protocol(self): |
| 1050 | + """Test refreshing user credentials for SSH protocol.""" |
| 1051 | + username = TEST_USER_EMAIL |
| 1052 | + _prepare_auth_files(self.test_user_home, ['ssh']) |
| 1053 | + |
| 1054 | + # Call the function under test |
| 1055 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1056 | + configuration=self.configuration, |
| 1057 | + protocol='ssh', |
| 1058 | + username=username |
| 1059 | + ) |
| 1060 | + |
| 1061 | + # The user should be in the changed list |
| 1062 | + self.assertIn(username, changed_users) |
| 1063 | + |
| 1064 | + # Verify that Login objects were added to users |
| 1065 | + user_logins = [obj for obj in updated_conf['users'] |
| 1066 | + if obj.username == TEST_USER_DN or |
| 1067 | + obj.username == username] |
| 1068 | + # We expect at least one login (the main username) and possibly aliases |
| 1069 | + self.assertGreaterEqual(len(user_logins), 1) |
| 1070 | + |
| 1071 | + # Check that at least one login has the correct home directory |
| 1072 | + # TODO: is this X509 dir what we want here or the UUID one? |
| 1073 | + home_found = any(login.home == self.test_user_dir_x509 for login in |
| 1074 | + user_logins) |
| 1075 | + self.assertTrue(home_found) |
| 1076 | + |
| 1077 | + def test_refresh_user_creds_davs_protocol(self): |
| 1078 | + """Test refreshing user credentials for DAVS protocol.""" |
| 1079 | + username = TEST_USER_EMAIL |
| 1080 | + _prepare_auth_files(self.test_user_home, ['davs']) |
| 1081 | + |
| 1082 | + # Call the function under test |
| 1083 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1084 | + configuration=self.configuration, |
| 1085 | + protocol='davs', |
| 1086 | + username=username |
| 1087 | + ) |
| 1088 | + |
| 1089 | + # The user should be in the changed list |
| 1090 | + self.assertIn(username, changed_users) |
| 1091 | + |
| 1092 | + # Verify that Login objects were added to users |
| 1093 | + user_logins = [obj for obj in updated_conf['users'] |
| 1094 | + if obj.username == username] |
| 1095 | + self.assertEqual(len(user_logins), 1) |
| 1096 | + # TODO: is this X509 dir what we want here or the UUID one? |
| 1097 | + self.assertEqual(user_logins[0].home, self.test_user_dir_x509) |
| 1098 | + |
| 1099 | + def test_refresh_user_creds_ftps_protocol(self): |
| 1100 | + """Test refreshing user credentials for FTPS protocol.""" |
| 1101 | + username = TEST_USER_EMAIL |
| 1102 | + _prepare_auth_files(self.test_user_home, ['ftps']) |
| 1103 | + |
| 1104 | + # Call the function under test |
| 1105 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1106 | + configuration=self.configuration, |
| 1107 | + protocol='ftps', |
| 1108 | + username=username |
| 1109 | + ) |
| 1110 | + |
| 1111 | + # The user should be in the changed list |
| 1112 | + self.assertIn(username, changed_users) |
| 1113 | + |
| 1114 | + # Verify that Login objects were added to users |
| 1115 | + user_logins = [obj for obj in updated_conf['users'] |
| 1116 | + if obj.username == username] |
| 1117 | + self.assertEqual(len(user_logins), 1) |
| 1118 | + # TODO: is this X509 dir what we want here or the UUID one? |
| 1119 | + self.assertEqual(user_logins[0].home, self.test_user_dir_x509) |
| 1120 | + |
| 1121 | + def test_refresh_user_creds_https_protocol(self): |
| 1122 | + """Test refreshing user credentials for HTTPS protocol (uses user DB).""" |
| 1123 | + username = TEST_USER_EMAIL |
| 1124 | + |
| 1125 | + # Call the function under test |
| 1126 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1127 | + configuration=self.configuration, |
| 1128 | + protocol='https', |
| 1129 | + username=username |
| 1130 | + ) |
| 1131 | + |
| 1132 | + # The user alias should be in the changed list |
| 1133 | + self.assertIn(TEST_USER_EMAIL, changed_users) |
| 1134 | + |
| 1135 | + # Verify that Login objects were added to users for the username and its aliases |
| 1136 | + user_logins = [obj for obj in updated_conf['users'] |
| 1137 | + if obj.username == TEST_USER_DN or |
| 1138 | + obj.username == username] |
| 1139 | + # We expect at least the main username and possibly aliases |
| 1140 | + self.assertGreaterEqual(len(user_logins), 1) |
| 1141 | + |
| 1142 | + # Check that at least one login has the correct home directory |
| 1143 | + # TODO: is this X509 dir what we want here or the UUID one? |
| 1144 | + home_found = any(login.home == self.test_user_dir_x509 for login in |
| 1145 | + user_logins) |
| 1146 | + self.assertTrue(home_found) |
| 1147 | + |
| 1148 | + def test_refresh_user_creds_no_changes(self): |
| 1149 | + """Test that no changes are reported when credentials are unchanged.""" |
| 1150 | + username = TEST_USER_EMAIL |
| 1151 | + _prepare_auth_files(self.test_user_home, ['ssh']) |
| 1152 | + |
| 1153 | + # Pre-populate the users list with a Login object that has |
| 1154 | + # last_update set to the current time (simulating no changes) |
| 1155 | + current_time = time.time() |
| 1156 | + dummy_user = Login( |
| 1157 | + configuration=self.configuration, |
| 1158 | + username=username, |
| 1159 | + home=self.test_user_home, |
| 1160 | + password=TEST_USER_PW_HASH, |
| 1161 | + digest=None, |
| 1162 | + public_key=TEST_USER_PUB_KEY, |
| 1163 | + chroot=True, |
| 1164 | + access=None, |
| 1165 | + ip_addr=None, |
| 1166 | + user_dict=None) |
| 1167 | + dummy_user.last_update = current_time |
| 1168 | + self.configuration.daemon_conf['users'].append(dummy_user) |
| 1169 | + |
| 1170 | + # Call the function under test |
| 1171 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1172 | + configuration=self.configuration, |
| 1173 | + protocol='ssh', |
| 1174 | + username=username |
| 1175 | + ) |
| 1176 | + |
| 1177 | + # No changes should be reported |
| 1178 | + self.assertEqual(len(changed_users), 0) |
| 1179 | + # The user list should still contain only our dummy user |
| 1180 | + self.assertEqual(len(updated_conf['users']), 1) |
| 1181 | + self.assertEqual(updated_conf['users'][0].username, username) |
| 1182 | + |
| 1183 | + def test_refresh_user_creds_missing_user(self): |
| 1184 | + """Test that a missing user is skipped.""" |
| 1185 | + username = 'nosuchuser' |
| 1186 | + |
| 1187 | + # Call the function under test |
| 1188 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1189 | + configuration=self.configuration, |
| 1190 | + protocol='ssh', |
| 1191 | + username=username |
| 1192 | + ) |
| 1193 | + |
| 1194 | + # No changes should be reported because the home directory is missing |
| 1195 | + self.assertEqual(len(changed_users), 0) |
| 1196 | + self.assertEqual(len(updated_conf['users']), 0) |
| 1197 | + |
| 1198 | + def test_refresh_user_creds_invalid_protocol(self): |
| 1199 | + """Test that an invalid protocol returns early without changes.""" |
| 1200 | + username = TEST_USER_EMAIL |
| 1201 | + |
| 1202 | + # Call the function under test with an invalid protocol |
| 1203 | + (updated_conf, changed_users) = refresh_user_creds( |
| 1204 | + configuration=self.configuration, |
| 1205 | + protocol='invalid', |
| 1206 | + username=username |
| 1207 | + ) |
| 1208 | + |
| 1209 | + # No changes should be reported |
| 1210 | + self.assertEqual(len(changed_users), 0) |
| 1211 | + self.assertEqual(len(updated_conf['users']), 0) |
| 1212 | + |
955 | 1213 |
|
956 | 1214 | if __name__ == '__main__': |
957 | 1215 | unittest.main() |
0 commit comments