Skip to content

Commit ecd8bb7

Browse files
committed
Make createuser work for new installations and add basic coverage.
Allow user creation to provision the user_db_home directory as well as the file itself which is the situation after config generation is run to completion for a new installation. Cover the very asic operation of createuser ensuring that a user db and lock file are correctly create upon a call to create a test user.
1 parent bc56206 commit ecd8bb7

File tree

6 files changed

+142
-5
lines changed

6 files changed

+142
-5
lines changed

mig/server/createuser.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ def usage(name='createuser.py'):
9191
""" % {'name': name, 'cert_warn': cert_warn})
9292

9393

94-
if '__main__' == __name__:
95-
(args, app_dir, db_path) = init_user_adm()
94+
def main(args, cwd, configuration=None, db_path=keyword_auto):
9695
conf_path = None
9796
auth_type = 'custom'
9897
expire = None
@@ -190,8 +189,11 @@ def usage(name='createuser.py'):
190189
if verbose:
191190
print('using configuration from MIG_CONF (or default)')
192191

193-
configuration = get_configuration_object(config_file=conf_path)
192+
if configuration is None:
193+
configuration = get_configuration_object(config_file=conf_path)
194+
194195
logger = configuration.logger
196+
195197
# NOTE: we need explicit db_path lookup here for load_user_dict call
196198
if db_path == keyword_auto:
197199
db_path = default_db_path(configuration)
@@ -326,3 +328,7 @@ def usage(name='createuser.py'):
326328
if verbose:
327329
print('Cleaning up tmp file: %s' % user_file)
328330
os.remove(user_file)
331+
332+
if __name__ == '__main__':
333+
(args, cwd, db_path) = init_user_adm()
334+
main(args, cwd, db_path=db_path)

mig/shared/accountstate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from __future__ import print_function
3333
from __future__ import absolute_import
3434

35+
from past.builtins import basestring
3536
import os
3637
import time
3738

mig/shared/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import re
3737

3838
# IMPORTANT: do not import any other MiG modules here - to avoid import loops
39+
from mig.shared.compat import PY2
3940
from mig.shared.defaults import default_str_coding, default_fs_coding, \
4041
keyword_all, keyword_auto, sandbox_names, _user_invisible_files, \
4142
_user_invisible_dirs, _vgrid_xgi_scripts, cert_field_order, csrf_field, \
@@ -294,7 +295,9 @@ def canonical_user(configuration, user_dict, limit_fields):
294295
if key == 'full_name':
295296
# IMPORTANT: we get utf8 coded bytes here and title() treats such
296297
# chars as word termination. Temporarily force to unicode.
297-
val = force_utf8(force_unicode(val).title())
298+
val = force_unicode(val).title()
299+
if PY2:
300+
val = force_utf8(val)
298301
elif key == 'email':
299302
val = val.lower()
300303
elif key == 'country':

mig/shared/useradm.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
from __future__ import print_function
3131
from __future__ import absolute_import
3232

33+
from past.builtins import basestring
3334
from email.utils import parseaddr
35+
import codecs
3436
import datetime
3537
import fnmatch
3638
import os
@@ -466,8 +468,24 @@ def create_user_in_db(configuration, db_path, client_id, user, now, authorized,
466468
flock = None
467469
user_db = {}
468470
renew = default_renew
471+
472+
retry_lock = False
469473
if do_lock:
474+
try:
475+
flock = lock_user_db(db_path)
476+
except FileNotFoundError as e:
477+
user_db_home = os.path.dirname(db_path)
478+
479+
if not os.path.exists(db_path) and not os.path.exists(user_db_home):
480+
os.mkdir(user_db_home)
481+
retry_lock = True
482+
else:
483+
raise Exception("Failed to lock user DB: '%s'" % db_path)
484+
485+
if retry_lock:
470486
flock = lock_user_db(db_path)
487+
if not flock:
488+
raise Exception("Failed to lock user DB: '%s'" % db_path)
471489

472490
if not os.path.exists(db_path):
473491
# Auto-create missing user DB if either auto_create_db or force is set
@@ -858,7 +876,8 @@ def create_user_in_fs(configuration, client_id, user, now, renew, force, verbose
858876
# match in htaccess
859877

860878
dn_plain = info['distinguished_name']
861-
dn_enc = dn_plain.encode('string_escape')
879+
dn_utf8_bytes = codecs.encode(dn_plain, 'utf8')
880+
dn_enc = codecs.decode(dn_utf8_bytes, 'unicode_escape')
862881

863882
def upper_repl(match):
864883
"""Translate hex codes to upper case form"""

tests/support/__init__.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import sys
4141
from unittest import TestCase, main as testmain
4242

43+
from tests.support.configsupp import FakeConfiguration
4344
from tests.support.suppconst import MIG_BASE, TEST_BASE, TEST_FIXTURE_DIR, \
4445
TEST_OUTPUT_DIR
4546

@@ -48,6 +49,19 @@
4849
# force defaults to a local environment
4950
os.environ['MIG_ENV'] = 'local'
5051

52+
# expose the configuraed environment as a constant
53+
MIG_ENV = os.environ['MIG_ENV']
54+
55+
if MIG_ENV == 'local':
56+
# force testconfig as the conig file path
57+
is_py2 = PY2
58+
_conf_dir_suffix = "-py%s" % ('2' if is_py2 else '3',)
59+
_conf_dir = "testconfs%s" % (_conf_dir_suffix,)
60+
_local_conf = os.path.join(MIG_BASE, 'envhelp/output', _conf_dir, 'MiGserver.conf')
61+
_config_file = os.getenv('MIG_CONF', None)
62+
if _config_file is None:
63+
os.environ['MIG_CONF'] = _local_conf
64+
5165
# All MiG related code will at some point include bits from the mig module
5266
# namespace. Rather than have this knowledge spread through every test file,
5367
# make the sole responsbility of test files to find the support file and
@@ -103,6 +117,7 @@ def __init__(self, *args):
103117
super(MigTestCase, self).__init__(*args)
104118
self._cleanup_checks = list()
105119
self._cleanup_paths = set()
120+
self._configuration = None
106121
self._logger = None
107122
self._skip_logging = False
108123

@@ -153,6 +168,18 @@ def _reset_logging(self, stream):
153168
root_handler = root_logger.handlers[0]
154169
root_handler.stream = stream
155170

171+
# testcase defaults
172+
173+
def _provide_configuration(self):
174+
return FakeConfiguration()
175+
176+
@property
177+
def configuration(self):
178+
"""Init a fake configuration if not already done"""
179+
if self._configuration is None:
180+
self._configuration = self._provide_configuration()
181+
return self._configuration
182+
156183
@property
157184
def logger(self):
158185
"""Init a fake logger if not already done"""
@@ -199,6 +226,10 @@ def assertPathExists(self, relative_path):
199226
assert not os.path.isabs(
200227
relative_path), "expected relative path within output folder"
201228
absolute_path = os.path.join(TEST_OUTPUT_DIR, relative_path)
229+
return MigTestCase._absolute_path_kind(absolute_path)
230+
231+
@staticmethod
232+
def _absolute_path_kind(absolute_path):
202233
stat_result = os.lstat(absolute_path)
203234
if stat.S_ISLNK(stat_result.st_mode):
204235
return "symlink"

tests/test_mig_server_createuser.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# --- BEGIN_HEADER ---
4+
#
5+
# test_mig_server-createuser - unit tests for the migrid createuser CLI
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
23+
# USA.
24+
#
25+
# --- END_HEADER ---
26+
#
27+
28+
"""Unit tests for the migrid createuser CLI"""
29+
30+
from __future__ import print_function
31+
import os
32+
import sys
33+
34+
from tests.support import MIG_BASE, TEST_OUTPUT_DIR, MigTestCase, testmain
35+
36+
from mig.shared.conf import get_configuration_object
37+
from mig.server.createuser import main as createuser
38+
39+
class TestBooleans(MigTestCase):
40+
def before_each(self):
41+
configuration = self._provide_configuration()
42+
test_user_db_home = os.path.join(configuration.state_path, 'user_db_home')
43+
try:
44+
os.rmdir(test_user_db_home)
45+
except:
46+
pass
47+
self.expected_user_db_home = test_user_db_home
48+
49+
def _provide_configuration(self):
50+
return get_configuration_object()
51+
52+
53+
def test_user_db_is_created_and_user_is_added(self):
54+
args = [
55+
"-r",
56+
"Test User",
57+
"Test Org",
58+
"NA",
59+
"DK",
60+
"dummy-user",
61+
"This is the create comment",
62+
"password"
63+
]
64+
createuser(args, TEST_OUTPUT_DIR, configuration=self.configuration)
65+
66+
# presence of user home
67+
path_kind = MigTestCase._absolute_path_kind(self.expected_user_db_home)
68+
self.assertEqual(path_kind, 'dir')
69+
70+
# presence of user db
71+
expected_user_db_file = os.path.join(self.expected_user_db_home, 'MiG-users.db')
72+
path_kind = MigTestCase._absolute_path_kind(expected_user_db_file)
73+
self.assertEqual(path_kind, 'file')
74+
75+
76+
if __name__ == '__main__':
77+
testmain()

0 commit comments

Comments
 (0)