|
1 |
#!/usr/share/ucs-test/runner python |
1 |
#!/usr/share/ucs-test/runner python |
2 |
## -*- coding: utf-8 -*- |
2 |
## -*- coding: utf-8 -*- |
3 |
## desc: remove illegal characters from username (Bug 42313) |
3 |
## desc: test UsernameHandler with --dry-run (Bug 42465) |
4 |
## tags: [apptest,ucsschool,skip_in_multiserver,ucsschool_import] |
4 |
## tags: [apptest,ucsschool,skip_in_multiserver,ucsschool_import] |
5 |
## roles: [domaincontroller_master] |
5 |
## roles: [domaincontroller_master] |
6 |
## exposure: dangerous |
6 |
## exposure: dangerous |
7 |
## packages: |
7 |
## packages: |
8 |
## - ucs-school-import |
8 |
## - ucs-school-import |
9 |
## bugs: [42313] |
9 |
## bugs: [42465] |
10 |
|
10 |
|
11 |
import copy |
11 |
import copy |
12 |
import string |
12 |
import string |
|
18 |
from univention.testing.ucs_samba import wait_for_drs_replication |
18 |
from univention.testing.ucs_samba import wait_for_drs_replication |
19 |
from univention.admin.uldap import getAdminConnection |
19 |
from univention.admin.uldap import getAdminConnection |
20 |
from univention.admin.uexceptions import noObject, ldapError |
20 |
from univention.admin.uexceptions import noObject, ldapError |
21 |
from ucsschool.importer.utils.username_handler import UsernameHandler |
|
|
22 |
from ucsschool.importer.exceptions import FormatError |
23 |
from essential.importusers import Person |
21 |
from essential.importusers import Person |
24 |
from essential.importusers_cli_v2 import CLI_Import_v2_Tester |
22 |
from essential.importusers_cli_v2 import CLI_Import_v2_Tester |
25 |
|
23 |
|
|
46 |
self.log.error("DN %r -> %s", dn, exc) |
44 |
self.log.error("DN %r -> %s", dn, exc) |
47 |
super(Test, self).cleanup() |
45 |
super(Test, self).cleanup() |
48 |
|
46 |
|
49 |
def test(self): # formally test_create_with_illegal_chars_in_username() |
47 |
def test(self): |
50 |
""" |
48 |
""" |
51 |
Bug #42313: remove illegal characters from username |
49 |
Bug #42313: remove illegal characters from username |
52 |
* "Username must only contain numbers, letters and dots, and may not be 'admin'!" |
50 |
* "Username must only contain numbers, letters and dots, and may not be 'admin'!" |
|
62 |
name = name.strip(".") |
60 |
name = name.strip(".") |
63 |
return name.translate(None, bad_chars) |
61 |
return name.translate(None, bad_chars) |
64 |
|
62 |
|
65 |
for role in ('student', 'teacher', 'teacher_and_staff'): |
63 |
random_puncts = list(string.punctuation) |
66 |
random_puncts = list(string.punctuation) |
64 |
random.shuffle(random_puncts) |
67 |
random.shuffle(random_puncts) |
65 |
lastname = uts.random_username(20) |
68 |
lastnames = ["{}{}{}".format(uts.random_username(5), x, uts.random_username(5)) for x in random_puncts[:4]] |
66 |
self.unique_basenames_to_remove.append(lastname) |
69 |
lastnames.append(".{}".format(uts.random_username())) |
67 |
self.log.info("Testing with dry_run=False and lastname=%r which will be shortened to %r...", lastname, lastname[:12]) |
70 |
lastnames.append(".{}.{}.".format(uts.random_username(4), uts.random_username(4))) |
|
|
71 |
lastnames.append("{}[{}]{}".format(uts.random_username(3), uts.random_username(3), uts.random_username(3))) |
72 |
lastnames.append(uts.random_username(40)) |
73 |
self.unique_basenames_to_remove.extend(lastnames) |
74 |
self.log.info('*** Importing new users with role %r and the following lastnames:\n%r', role, lastnames) |
75 |
|
68 |
|
76 |
config = copy.deepcopy(self.default_config) |
69 |
role = 'student' |
77 |
source_uid = 'sourceUID-%s' % (uts.random_string(),) |
70 |
record_uid = uts.random_name() |
78 |
config.update_entry('sourceUID', source_uid) |
71 |
person = Person(self.ou_A.name, role) |
79 |
config.update_entry('scheme:recordUID', '<record_uid>') |
72 |
source_uid = 'sourceUID-%s' % (uts.random_string(),) |
80 |
config.update_entry('user_role', role) |
73 |
person.update(record_uid=record_uid, source_uid=source_uid, lastname=lastname) |
81 |
config.update_entry('csv:mapping:recordUID', 'record_uid') |
|
|
82 |
config.update_entry('scheme:username:default', "<lastname>[ALWAYSCOUNTER]") |
83 |
|
74 |
|
84 |
persons = list() |
75 |
config = copy.deepcopy(self.default_config) |
85 |
names = dict() |
76 |
config.update_entry('sourceUID', source_uid) |
86 |
for lastname in lastnames: |
77 |
config.update_entry('scheme:recordUID', '<record_uid>') |
87 |
record_uid = uts.random_name() |
78 |
config.update_entry('user_role', role) |
88 |
person = Person(self.ou_A.name, role) |
79 |
config.update_entry('csv:mapping:recordUID', 'record_uid') |
89 |
person.update(record_uid=record_uid, source_uid=source_uid, lastname=lastname) |
80 |
config.update_entry('scheme:username:default', "<lastname>[ALWAYSCOUNTER]") |
90 |
persons.append(person) |
81 |
fn_csv = self.create_csv_file(person_list=[person], mapping=config['csv']['mapping']) |
91 |
names[person] = lastname |
82 |
config.update_entry('input:filename', fn_csv) |
92 |
fn_csv = self.create_csv_file(person_list=persons, mapping=config['csv']['mapping']) |
83 |
fn_config = self.create_config_json(config=config) |
93 |
config.update_entry('input:filename', fn_csv) |
|
|
94 |
fn_config = self.create_config_json(config=config) |
95 |
|
84 |
|
96 |
# save ldap state for later comparison |
85 |
self.save_ldap_status() |
97 |
self.save_ldap_status() |
86 |
self.run_import(['-c', fn_config, '-i', fn_csv]) |
98 |
# start import |
87 |
utils.wait_for_replication() |
99 |
self.run_import(['-c', fn_config, '-i', fn_csv]) |
88 |
self.check_new_and_removed_users(1, 0) |
100 |
utils.wait_for_replication() |
89 |
person.update(username=remove_bad_chars("{}1".format(lastname[:12]))) |
101 |
# check for new users in LDAP |
90 |
utils.verify_ldap_object(person.dn, expected_attr={'uid': [person.username]}, strict=False, should_exist=True) |
102 |
self.check_new_and_removed_users(len(persons), 0) |
91 |
wait_for_drs_replication('cn={}'.format(escape_filter_chars(person.username))) # wait for creation before deletion |
103 |
# check usernames |
|
|
104 |
for person, lastname in names.items(): |
105 |
person.update(username=remove_bad_chars("{}1".format(lastname[:12]))) |
106 |
utils.verify_ldap_object(person.dn, expected_attr={'uid': [person.username]}, strict=False, should_exist=True) |
107 |
wait_for_drs_replication('cn={}'.format(escape_filter_chars(person.username))) # wait for creation before deletion |
108 |
|
92 |
|
109 |
# delete users |
93 |
# delete user |
110 |
self.log.info('*** Deleting users with role %r and the following lastnames:\n%r', role, lastnames) |
94 |
self.log.info('*** Deleting user with lastname=%r', lastname) |
111 |
fn_csv = self.create_csv_file(person_list=[], mapping=config['csv']['mapping']) |
95 |
fn_csv = self.create_csv_file(person_list=[], mapping=config['csv']['mapping']) |
112 |
config.update_entry('input:filename', fn_csv) |
96 |
config.update_entry('input:filename', fn_csv) |
113 |
fn_config = self.create_config_json(config=config) |
97 |
fn_config = self.create_config_json(config=config) |
114 |
self.save_ldap_status() |
98 |
self.save_ldap_status() |
115 |
self.run_import(['-c', fn_config, '-i', fn_csv]) |
99 |
self.run_import(['-c', fn_config, '-i', fn_csv]) |
116 |
utils.wait_for_replication() |
100 |
utils.wait_for_replication() |
117 |
|
101 |
|
118 |
# recreate users, but move position of COUNTER variable |
102 |
# recreate user with dry_run |
119 |
self.log.info( |
103 |
self.log.info('*** Importing user with the same lastname with --dry_run') |
120 |
'*** Importing users with role %r and the same lastnames, but move position of COUNTER variable:\n%r', |
104 |
# Prevent 'The email address is already taken by another user. Please change the email address.' |
121 |
role, |
105 |
# because of slow s4 replication. Username is the same, so it doesn't change what's tested. |
122 |
lastnames) |
106 |
person.mail = '{}{}'.format(uts.random_username(4), person.mail) |
123 |
config.update_entry('scheme:username:default', "[ALWAYSCOUNTER]<lastname>") |
107 |
fn_csv = self.create_csv_file(person_list=[person], mapping=config['csv']['mapping']) |
124 |
for person in persons: |
108 |
config.update_entry('input:filename', fn_csv) |
125 |
# Prevent 'The email address is already taken by another user. Please change the email address.' |
109 |
fn_config = self.create_config_json(config=config) |
126 |
# because of slow s4 replication. Username is the same, so it doesn't change what's tested. |
110 |
self.save_ldap_status() |
127 |
person.mail = '{}{}'.format(uts.random_username(4), person.mail) |
111 |
self.run_import(['-c', fn_config, '-i', fn_csv, '--dry-run']) |
128 |
fn_csv = self.create_csv_file(person_list=persons, mapping=config['csv']['mapping']) |
112 |
utils.wait_for_replication() |
129 |
config.update_entry('input:filename', fn_csv) |
113 |
utils.verify_ldap_object(person.dn, should_exist=False) |
130 |
fn_config = self.create_config_json(config=config) |
114 |
person.update(username=remove_bad_chars("{}2".format(lastname[:12]))) |
131 |
self.save_ldap_status() |
115 |
utils.verify_ldap_object(person.dn, should_exist=False) |
132 |
self.run_import(['-c', fn_config, '-i', fn_csv]) |
|
|
133 |
utils.wait_for_replication() |
134 |
for person, lastname in names.items(): |
135 |
person.update(username=remove_bad_chars("2{}".format(lastname[:12]))) |
136 |
utils.verify_ldap_object(person.dn, expected_attr={'uid': [person.username]}, strict=False, should_exist=True) |
137 |
|
116 |
|
138 |
self.log.info('*** Starting unit test for UsernameHandler.format_username() (1/5)') |
117 |
lo, po = getAdminConnection() |
139 |
unh = UsernameHandler(15) # 20 - len("exam-") |
118 |
utils.verify_ldap_object( |
140 |
name12 = uts.random_username(12) # 15 - 3 |
119 |
"cn={},cn=unique-usernames,cn=ucsschool,cn=univention,{}".format(escape_dn_chars(lastname[:12]), lo.base), |
141 |
usernames = { |
120 |
expected_attr={'ucsschoolUsernameNextNumber': ['2']}, |
142 |
".abc.def.": "abc.def", |
121 |
strict=False, |
143 |
"...abc...def...": "abc...def", |
122 |
should_exist=True) |
144 |
"": None, |
|
|
145 |
"..[ALWAYSCOUNTER]..": None, |
146 |
"[ALWAYSCOUNTER]": None, |
147 |
"[FOObar]": "FOObar", |
148 |
"{}.[COUNTER2]".format(name12): name12, |
149 |
".": None, |
150 |
'M'*14 + '.' + 'A': 'M' * 14, |
151 |
} |
152 |
for input_name, expected in usernames.items(): |
153 |
try: |
154 |
out = unh.format_username(input_name) |
155 |
self.unique_basenames_to_remove.append(expected) |
156 |
if out != expected: |
157 |
self.fail("UsernameHandler.format_username(%r) returned %r, expected %r." % (input_name, out, expected)) |
158 |
except FormatError: |
159 |
if expected is not None: |
160 |
self.fail("UsernameHandler.format_username(%r) raise a FormatError, expected it to return %r." % (input_name, expected)) |
161 |
continue |
162 |
self.log.info('*** Starting unit test for UsernameHandler.format_username() (2/5)') |
163 |
for i in range(1000): |
164 |
name = uts.random_username(15) |
165 |
self.unique_basenames_to_remove.append(name) |
166 |
out = unh.format_username(name) |
167 |
if out != name: |
168 |
self.fail("UsernameHandler.format_username(%r) returned %r." % (name, out)) |
169 |
self.log.info('*** Starting unit test for UsernameHandler.format_username() (3/5)') |
170 |
for i in range(1000): |
171 |
name = uts.random_name(20) |
172 |
self.unique_basenames_to_remove.append(name) |
173 |
out = unh.format_username(name) |
174 |
if out.startswith(".") or out.endswith(".") or len(out) > 15: |
175 |
self.fail("UsernameHandler.format_username(%r) returned %r." % (name, out)) |
176 |
self.log.info('*** Starting unit test for UsernameHandler.format_username() (4/5)') |
177 |
for i in range(1000): |
178 |
name = uts.random_name_special_characters(20) |
179 |
name = name.translate(None, "[]") # those are reserved for counter vars |
180 |
self.unique_basenames_to_remove.append(name) |
181 |
out = unh.format_username(name) |
182 |
if out.startswith(".") or out.endswith(".") or len(out) > 15: |
183 |
self.fail("UsernameHandler.format_username(%r) returned %r." % (name, out)) |
184 |
self.log.info('*** Starting unit test for UsernameHandler.format_username() (5/5)') |
185 |
usernames = [ |
186 |
('Max[ALWAYSCOUNTER].Mustermann', 'Max1.Mustermann'), |
187 |
('Max[ALWAYSCOUNTER].Mustermann', 'Max2.Mustermann'), |
188 |
('Max[ALWAYSCOUNTER].Mustermann', 'Max3.Mustermann'), |
189 |
('Max[ALWAYSCOUNTER].Mustermann', 'Max4.Mustermann'), |
190 |
('Maria[ALWAYSCOUNTER].Musterfrau', 'Maria1.Musterfrau'), |
191 |
('Moritz[COUNTER2]', 'Moritz'), |
192 |
('Moritz[COUNTER2]', 'Moritz2') |
193 |
] |
194 |
self.unique_basenames_to_remove.extend(["Max.Mustermann", "Maria.Musterfrau", "Moritz"]) |
195 |
unh = UsernameHandler(20) |
196 |
for input_name, expected in usernames: |
197 |
out = unh.format_username(input_name) |
198 |
if out != expected: |
199 |
self.fail("UsernameHandler.format_username(%r) returned %r, expected %r." % (input_name, out, expected)) |
200 |
|
123 |
|
|
|
124 |
self.log.info('*** OK: Import with --dry_run didn\'t raise the counter.') |
201 |
|
125 |
|
|
|
126 |
|
202 |
def main(): |
127 |
def main(): |
203 |
tester = Test() |
128 |
tester = Test() |
204 |
try: |
129 |
try: |