|
Lines 42-54
from ucsschool.importer.utils.logging import get_logger
Link Here
|
| 42 |
|
42 |
|
| 43 |
|
43 |
|
| 44 |
class UsernameHandler(object): |
44 |
class UsernameHandler(object): |
| 45 |
replacement_variable_pattern = re.compile(r"\[.*?\]") |
45 |
""" |
|
|
46 |
>>> BAD_CHARS = ''.join(set(map(chr, range(128))) - set('.1032547698ACBEDGFIHKJMLONQPSRUTWVYXZacbedgfihkjmlonqpsrutwvyxz')) |
| 47 |
>>> UsernameHandler(20).format_username('Max.Mustermann') |
| 48 |
'Max.Mustermann' |
| 49 |
>>> UsernameHandler(20).format_username('Foo[COUNTER2][COUNTER2]') # doctest: +IGNORE_EXCEPTION_DETAIL |
| 50 |
Traceback (most recent call last): |
| 51 |
... |
| 52 |
FormatError: |
| 53 |
>>> UsernameHandler(20).format_username('.') # doctest: +IGNORE_EXCEPTION_DETAIL |
| 54 |
Traceback (most recent call last): |
| 55 |
... |
| 56 |
FormatError: |
| 57 |
>>> UsernameHandler(20).format_username('.Max.Mustermann.') |
| 58 |
'Max.Mustermann' |
| 59 |
>>> UsernameHandler(4).format_username('Max.Mustermann') |
| 60 |
'Max' |
| 61 |
>>> for c in BAD_CHARS: |
| 62 |
... assert 'Max' == UsernameHandler(20).format_username('Ma%sx' % (c,)) |
| 63 |
... |
| 64 |
>>> UsernameHandler(20).format_username('Max.Mustermann12.4567890') |
| 65 |
'Max.Mustermann12.456' |
| 66 |
>>> for c in '.1032547698ACBEDGFIHKJMLONQPSRUTWVYXZacbedgfihkjmlonqpsrutwvyxz': |
| 67 |
... assert 'Ma%sx' % (c,) == UsernameHandler(20).format_username('Ma%sx' % (c,)) |
| 68 |
... |
| 69 |
>>> UsernameHandler(20).format_username('Max[Muster]Mann') |
| 70 |
'MaxMusterMann' |
| 71 |
>>> UsernameHandler(20).format_username('Max[ALWAYSCOUNTER].Mustermann') |
| 72 |
'Max1.Mustermann' |
| 73 |
>>> UsernameHandler(20).format_username('Max[ALWAYSCOUNTER].Mustermann') |
| 74 |
'Max2.Mustermann' |
| 75 |
>>> UsernameHandler(20).format_username('Max[ALWAYSCOUNTER].Mustermann') |
| 76 |
'Max3.Mustermann' |
| 77 |
>>> UsernameHandler(20).format_username('Max[COUNTER2].Mustermann') |
| 78 |
'Max4.Mustermann' |
| 79 |
>>> UsernameHandler(20).format_username('Maria[ALWAYSCOUNTER].Musterfrau') |
| 80 |
'Maria1.Musterfrau' |
| 81 |
>>> UsernameHandler(20).format_username('Moritz[COUNTER2]') |
| 82 |
'Moritz' |
| 83 |
>>> UsernameHandler(20).format_username('Moritz[COUNTER2]') |
| 84 |
'Moritz2' |
| 85 |
>>> for i, c in enumerate(BAD_CHARS, 1): |
| 86 |
... assert 'Foo%d' % (i) == UsernameHandler(20).format_username('Fo%so[ALWAYSCOUNTER]' % (c,)) |
| 87 |
>>> UsernameHandler(8).format_username('aaaa[COUNTER2]bbbbcccc') |
| 88 |
'aaaab' |
| 89 |
>>> UsernameHandler(8).format_username('aaaa[COUNTER2]bbbbcccc') |
| 90 |
'aaaa2b' |
| 91 |
>>> UsernameHandler(8).format_username('bbbb[ALWAYSCOUNTER]ccccdddd') |
| 92 |
'bbbb1c' |
| 93 |
>>> UsernameHandler(8).format_username('bbbb[ALWAYSCOUNTER]ccccdddd') |
| 94 |
'bbbb2c' |
| 95 |
""" |
| 96 |
|
| 46 |
allowed_chars = string.ascii_letters + string.digits + "." |
97 |
allowed_chars = string.ascii_letters + string.digits + "." |
| 47 |
|
98 |
|
| 48 |
def __init__(self, username_max_length): |
99 |
def __init__(self, username_max_length): |
| 49 |
self.username_max_length = username_max_length |
100 |
self.username_max_length = username_max_length |
| 50 |
self.logger = get_logger() |
101 |
self.logger = get_logger() |
| 51 |
self.connection, self.position = get_admin_connection() |
102 |
self.connection, self.position = get_admin_connection() |
|
|
103 |
self.replacement_variable_pattern = re.compile(r'(%s)' % '|'.join(map(re.escape, self.counter_variable_to_function.keys())), flags=re.I) |
| 52 |
|
104 |
|
| 53 |
def add_to_ldap(self, username, first_number): |
105 |
def add_to_ldap(self, username, first_number): |
| 54 |
assert isinstance(username, basestring) |
106 |
assert isinstance(username, basestring) |
|
Lines 92-100
class UsernameHandler(object):
Link Here
|
| 92 |
:param name: str: username to check |
144 |
:param name: str: username to check |
| 93 |
:return: str: copy of input, possibly modified |
145 |
:return: str: copy of input, possibly modified |
| 94 |
""" |
146 |
""" |
| 95 |
bad_chars = "".join(sorted(set(name).difference(set(self.allowed_chars)))) |
147 |
bad_chars = ''.join(set(name).difference(set(self.allowed_chars))) |
| 96 |
if bad_chars: |
148 |
if bad_chars: |
| 97 |
self.logger.warn("Removing disallowed characters %r from username %r.", bad_chars, name) |
149 |
self.logger.warn("Removing disallowed characters %r from username %r.", ''.join(sorted(bad_chars)), name) |
| 98 |
if name.startswith(".") or name.endswith("."): |
150 |
if name.startswith(".") or name.endswith("."): |
| 99 |
self.logger.warn("Removing disallowed dot from start and end of username %r.", name) |
151 |
self.logger.warn("Removing disallowed dot from start and end of username %r.", name) |
| 100 |
name = name.strip(".") |
152 |
name = name.strip(".") |
|
Lines 116-177
class UsernameHandler(object):
Link Here
|
| 116 |
:return: str: unique username |
168 |
:return: str: unique username |
| 117 |
""" |
169 |
""" |
| 118 |
assert isinstance(name, basestring) |
170 |
assert isinstance(name, basestring) |
| 119 |
ori_name = name |
171 |
PATTERN_FUNC_MAXLENGTH = 3 # maximum a counter function can produce is len('999') |
| 120 |
cut_pos = self.username_max_length - 3 # numbers >999 are not supported |
172 |
username = name |
| 121 |
|
173 |
|
| 122 |
match = self.replacement_variable_pattern.search(name) |
174 |
match = self.replacement_variable_pattern.search(name) |
| 123 |
if not match: |
175 |
if match: |
| 124 |
# no counter variable used, just check characters and length |
176 |
func = self.counter_variable_to_function[match.group().upper()] |
| 125 |
name = self.remove_bad_chars(name) |
177 |
cut_pos = self.username_max_length - PATTERN_FUNC_MAXLENGTH |
| 126 |
if len(name) > self.username_max_length: |
178 |
|
| 127 |
res = name[:self.username_max_length] |
179 |
# it's not allowed to have two [COUNTER] patterns |
| 128 |
self.logger.warn("Username %r too long, shortened to %r.", name, res) |
180 |
if len(self.replacement_variable_pattern.findall(name)) >= 2: |
| 129 |
else: |
181 |
raise FormatError("More than one counter variable found in username scheme '{}'.".format(name), name, name) |
| 130 |
res = name |
182 |
|
| 131 |
return res |
183 |
# the variable must no be the [COUNTER] pattern |
| 132 |
|
184 |
without_pattern = self.replacement_variable_pattern.sub('', name) |
| 133 |
if len(self.replacement_variable_pattern.split(name)) > 2: |
185 |
|
| 134 |
raise FormatError("More than one counter variable found in username scheme '{}'.".format(name), name, name) |
186 |
username = name |
| 135 |
|
187 |
if len(without_pattern) > cut_pos: |
| 136 |
# need username without counter variable to calculate length |
188 |
without_pattern = self.remove_bad_chars(without_pattern)[:cut_pos] |
| 137 |
_base_name = "".join(self.replacement_variable_pattern.split(name)) |
189 |
start, end = without_pattern[:match.start()], without_pattern[match.start():] |
| 138 |
base_name = self.remove_bad_chars(_base_name) |
190 |
username = '%s[%s]%s' % (start, match.group(), end) |
| 139 |
if _base_name != base_name: |
191 |
username = self.replacement_variable_pattern.sub(func(without_pattern), username) |
| 140 |
# recalculate position of pattern |
192 |
|
| 141 |
name = "{}{}{}".format(base_name[:match.start()], match.group(), base_name[match.end():]) |
193 |
username = self.remove_bad_chars(username) |
| 142 |
match = self.replacement_variable_pattern.search(name) |
194 |
|
| 143 |
|
195 |
if not match and len(name) > self.username_max_length: |
| 144 |
variable = match.group() |
196 |
username = username[:self.username_max_length] |
| 145 |
start = match.start() |
197 |
self.logger.warn("Username %r too long, shortened to %r.", name, username) |
| 146 |
end = match.end() |
198 |
|
| 147 |
|
199 |
username = username.strip('.') |
| 148 |
if start == 0 and end == len(name): |
200 |
if not username: |
| 149 |
raise FormatError("No username in '{}'.".format(name), ori_name, ori_name) |
201 |
raise FormatError("No username in '{}'.".format(name), name, name) |
| 150 |
|
202 |
return username |
| 151 |
# get counter function |
|
|
| 152 |
try: |
| 153 |
func = self.counter_variable_to_function[variable.upper()] |
| 154 |
except KeyError as exc: |
| 155 |
raise FormatError("Unknown variable name '{}' in username scheme '{}': '{}' not in known variables: '{}'".format( |
| 156 |
variable, ori_name, exc, self.counter_variable_to_function.keys()), variable, name) |
| 157 |
except AttributeError as exc: |
| 158 |
raise FormatError("No method '{}' can be found for variable name '{}' in username scheme '{}': {}".format( |
| 159 |
self.counter_variable_to_function[variable], variable, name, exc), variable, ori_name) |
| 160 |
|
| 161 |
if len(base_name) > cut_pos: |
| 162 |
# base name without variable to long, we have to shorten it |
| 163 |
# numbers will only be appended, no inserting possible anymore |
| 164 |
res = base_name[:cut_pos] |
| 165 |
insert_position = cut_pos |
| 166 |
self.logger.warn("Username %r too long, shortened to %r.", base_name, res) |
| 167 |
res = self.remove_bad_chars(res) # dot from middle might be at end now |
| 168 |
else: |
| 169 |
insert_position = start |
| 170 |
res = u"{}{}".format(name[:start], name[end:]) |
| 171 |
|
| 172 |
counter = func(res) # get counter number to insert/append |
| 173 |
ret = "{}{}{}".format(res[:insert_position], counter, res[insert_position:]) |
| 174 |
return ret |
| 175 |
|
203 |
|
| 176 |
@property |
204 |
@property |
| 177 |
def counter_variable_to_function(self): |
205 |
def counter_variable_to_function(self): |
|
Lines 217-219
class UsernameHandler(object):
Link Here
|
| 217 |
num = first_time |
245 |
num = first_time |
| 218 |
self.add_to_ldap(name_base, "2") |
246 |
self.add_to_ldap(name_base, "2") |
| 219 |
return num |
247 |
return num |
|
|
248 |
|
| 249 |
|
| 250 |
if __name__ == '__main__': |
| 251 |
import doctest |
| 252 |
doctest.testmod() |