Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make createuser work for new installations and add basic coverage. #120

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 94 additions & 35 deletions mig/server/createuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from builtins import input
from getpass import getpass
import datetime
import errno
import getopt
import os
import sys
Expand Down Expand Up @@ -91,8 +92,7 @@ def usage(name='createuser.py'):
""" % {'name': name, 'cert_warn': cert_warn})


if '__main__' == __name__:
(args, app_dir, db_path) = init_user_adm()
def main(_main, args, cwd, db_path=keyword_auto):
conf_path = None
auth_type = 'custom'
expire = None
Expand All @@ -111,6 +111,7 @@ def usage(name='createuser.py'):
user_dict = {}
override_fields = {}
opt_args = 'a:c:d:e:fhi:o:p:rR:s:u:v'

try:
(opts, args) = getopt.getopt(args, opt_args)
except getopt.GetoptError as err:
Expand Down Expand Up @@ -138,13 +139,8 @@ def usage(name='createuser.py'):
parsed = True
break
except ValueError:
pass
if parsed:
override_fields['expire'] = expire
override_fields['status'] = 'temporal'
else:
print('Failed to parse expire value: %s' % val)
sys.exit(1)
print('Failed to parse expire value: %s' % val)
sys.exit(1)
elif opt == '-f':
force = True
elif opt == '-h':
Expand All @@ -154,17 +150,13 @@ def usage(name='createuser.py'):
user_id = val
elif opt == '-o':
short_id = val
override_fields['short_id'] = short_id
elif opt == '-p':
peer_pattern = val
override_fields['peer_pattern'] = peer_pattern
override_fields['status'] = 'temporal'
elif opt == '-r':
default_renew = True
ask_renew = False
elif opt == '-R':
role = val
override_fields['role'] = role
elif opt == '-s':
# Translate slack days into seconds as
slack_secs = int(float(val)*24*3600)
Expand All @@ -178,7 +170,12 @@ def usage(name='createuser.py'):
print('Error: %s not supported!' % opt)
sys.exit(1)

if conf_path and not os.path.isfile(conf_path):
if not conf_path:
# explicitly set the default value of keyword_auto if no option was
# provided since it is unconditionally passed inward as a keyword arg
# and thus the fallback would accidentally be ignored
conf_path = keyword_auto
elif not os.path.isfile(conf_path):
print('Failed to read configuration file: %s' % conf_path)
sys.exit(1)

Expand All @@ -190,30 +187,76 @@ def usage(name='createuser.py'):
if verbose:
print('using configuration from MIG_CONF (or default)')

configuration = get_configuration_object(config_file=conf_path)
ret = _main(None, args,
conf_path=conf_path,
db_path=db_path,
expire=expire,
force=force,
verbose=verbose,
ask_renew=ask_renew,
default_renew=default_renew,
ask_change_pw=ask_change_pw,
user_file=user_file,
user_id=user_id,
short_id=short_id,
role=role,
peer_pattern=peer_pattern,
slack_secs=slack_secs,
hash_password=hash_password
)

if ret == errno.ENOTSUP:
usage()
sys.exit(1)

sys.exit(ret)


def _main(configuration, args,
conf_path=keyword_auto,
db_path=keyword_auto,
auth_type='custom',
expire=None,
force=False,
verbose=False,
ask_renew=True,
default_renew=False,
ask_change_pw=True,
user_file=None,
user_id=None,
short_id=None,
role=None,
peer_pattern=None,
slack_secs=0,
hash_password=True,
_generate_salt=None
):
if configuration is None:
if conf_path == keyword_auto:
config_file = None
else:
config_file = conf_path
configuration = get_configuration_object(config_file=config_file)

logger = configuration.logger

# NOTE: we need explicit db_path lookup here for load_user_dict call
if db_path == keyword_auto:
db_path = default_db_path(configuration)

if user_file and args:
print('Error: Only one kind of user specification allowed at a time')
usage()
sys.exit(1)
return errno.ENOTSUP

if auth_type not in valid_auth_types:
print('Error: invalid account auth type %r requested (allowed: %s)' %
(auth_type, ', '.join(valid_auth_types)))
usage()
sys.exit(1)
return errno.ENOTSUP

# NOTE: renew requires original password
if auth_type == 'cert':
hash_password = False

if expire is None:
expire = default_account_expire(configuration, auth_type)

raw_user = {}
if args:
try:
Expand All @@ -229,8 +272,7 @@ def usage(name='createuser.py'):
except IndexError:
print('Error: too few arguments given (expected 7 got %d)'
% len(args))
usage()
sys.exit(1)
return errno.ENOTSUP
# Force user ID fields to canonical form for consistency
# Title name, lowercase email, uppercase country and state, etc.
user_dict = canonical_user(configuration, raw_user, raw_user.keys())
Expand All @@ -239,14 +281,12 @@ def usage(name='createuser.py'):
user_dict = load(user_file)
except Exception as err:
print('Error in user name extraction: %s' % err)
usage()
sys.exit(1)
return errno.ENOTSUP
elif default_renew and user_id:
saved = load_user_dict(logger, user_id, db_path, verbose)
if not saved:
print('Error: no such user in user db: %s' % user_id)
usage()
sys.exit(1)
return errno.ENOTSUP
user_dict.update(saved)
del user_dict['expire']
elif not configuration.site_enable_gdp:
Expand All @@ -268,13 +308,13 @@ def usage(name='createuser.py'):
print("Error: Missing one or more of the arguments: "
+ "[FULL_NAME] [ORGANIZATION] [STATE] [COUNTRY] "
+ "[EMAIL] [COMMENT] [PASSWORD]")
sys.exit(1)
return 1

# Encode password if set but not already encoded

if user_dict['password']:
if hash_password:
user_dict['password_hash'] = make_hash(user_dict['password'])
user_dict['password_hash'] = make_hash(user_dict['password'], _generate_salt=_generate_salt)
user_dict['password'] = ''
else:
salt = configuration.site_password_salt
Expand All @@ -291,9 +331,19 @@ def usage(name='createuser.py'):

fill_user(user_dict)

# Make sure account expire is set with local certificate or OpenID login

# assemble the fields to be explicitly overriden
override_fields = {}
if peer_pattern:
override_fields['peer_pattern'] = peer_pattern
override_fields['status'] = 'temporal'
if role:
override_fields['role'] = role
if short_id:
override_fields['short_id'] = short_id
if 'expire' not in user_dict:
# Make sure account expire is set with local certificate or OpenID login
if not expire:
expire = default_account_expire(configuration, auth_type)
override_fields['expire'] = expire

# NOTE: let non-ID command line values override loaded values
Expand All @@ -305,8 +355,10 @@ def usage(name='createuser.py'):
if verbose:
print('using user dict: %s' % user_dict)
try:
create_user(user_dict, conf_path, db_path, force, verbose, ask_renew,
default_renew, verify_peer=peer_pattern,
conf_path = configuration.config_file
create_user(user_dict, conf_path, db_path, configuration, force, verbose, ask_renew,
default_renew,
verify_peer=peer_pattern,
peer_expire_slack=slack_secs, ask_change_pw=ask_change_pw)
if configuration.site_enable_gdp:
(success_here, msg) = ensure_gdp_user(configuration,
Expand All @@ -319,10 +371,17 @@ def usage(name='createuser.py'):
print("Error creating user: %s" % exc)
import traceback
logger.warning("Error creating user: %s" % traceback.format_exc())
sys.exit(1)
return 1
print('Created or updated %s in user database and in file system' %
user_dict['distinguished_name'])
if user_file:
if verbose:
print('Cleaning up tmp file: %s' % user_file)
os.remove(user_file)

return 0


if __name__ == '__main__':
(args, cwd, db_path) = init_user_adm()
main(_main, args, cwd, db_path=db_path)
4 changes: 4 additions & 0 deletions mig/shared/_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import os

MIG_BASE = os.path.realpath(os.path.join(os.path.dirname(__file__), '../..'))
MIG_ENV = os.getenv('MIG_ENV', 'default')
1 change: 1 addition & 0 deletions mig/shared/accountstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from __future__ import absolute_import
from past.builtins import basestring

from past.builtins import basestring
import os
import time

Expand Down
32 changes: 30 additions & 2 deletions mig/shared/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import re

# IMPORTANT: do not import any other MiG modules here - to avoid import loops
from mig.shared.compat import PY2
from mig.shared.defaults import default_str_coding, default_fs_coding, \
keyword_all, keyword_auto, sandbox_names, _user_invisible_files, \
_user_invisible_dirs, _vgrid_xgi_scripts, cert_field_order, csrf_field, \
Expand Down Expand Up @@ -294,7 +295,9 @@ def canonical_user(configuration, user_dict, limit_fields):
if key == 'full_name':
# IMPORTANT: we get utf8 coded bytes here and title() treats such
# chars as word termination. Temporarily force to unicode.
val = force_utf8(force_unicode(val).title())
val = force_unicode(val).title()
if PY2:
val = force_utf8(val)
elif key == 'email':
val = val.lower()
elif key == 'country':
Expand Down Expand Up @@ -496,7 +499,7 @@ def is_unicode(val):
return (type(u"") == type(val))


def force_utf8(val, highlight=''):
def _force_utf8_py2(val, highlight=''):
"""Internal helper to encode unicode strings to utf8 version. Actual
changes are marked out with the highlight string if given.
"""
Expand All @@ -507,6 +510,31 @@ def force_utf8(val, highlight=''):
return val
return "%s%s%s" % (highlight, val.encode("utf8"), highlight)

def _force_utf8_py3(val, highlight='', stringify=True):
"""Internal helper to encode unicode strings to utf8 version. Actual
changes are marked out with the highlight string if given.
The optional stringify turns ALL values including numbers into string.
"""
# We run into all kind of nasty encoding problems if we mix
if not isinstance(val, basestring):
if stringify:
val = "%s" % val
else:
return val
if not is_unicode(val):
return val
if is_unicode(highlight):
hl_utf = highlight.encode("utf8")
else:
hl_utf = highlight
return (b"%s%s%s" % (hl_utf, val.encode("utf8"), hl_utf))


if PY2:
force_utf8 = _force_utf8_py2
else:
force_utf8 = _force_utf8_py3


def force_utf8_rec(input_obj, highlight=''):
"""Recursive object conversion from unicode to utf8: useful to convert e.g.
Expand Down
7 changes: 7 additions & 0 deletions mig/shared/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ def _is_unicode(val):
return (type(val) == _TYPE_UNICODE)


def _unicode_string_to_escaped_unicode(unicode_string):
"""Convert utf8 bytes to escaped unicode string."""

utf8_bytes = dn_utf8_bytes = codecs.encode(unicode_string, 'utf8')
return codecs.decode(utf8_bytes, 'unicode_escape')


def ensure_native_string(string_or_bytes):
"""Given a supplied input which can be either a string or bytes
return a representation providing string operations while ensuring that
Expand Down
13 changes: 6 additions & 7 deletions mig/shared/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import os
import sys

from mig.shared._env import MIG_BASE
from mig.shared.fileio import unpickle


Expand All @@ -43,16 +44,14 @@ def get_configuration_object(config_file=None, skip_log=False,
"""
from mig.shared.configuration import Configuration
if config_file:
# use config file path passed explicitly to the function
_config_file = config_file
elif os.environ.get('MIG_CONF', None):
# use config file explicitly set in the environment
_config_file = os.environ['MIG_CONF']
else:
app_dir = os.path.dirname(sys.argv[0])
if not app_dir:
_config_file = '../server/MiGserver.conf'
else:
_config_file = os.path.join(app_dir, '..', 'server',
'MiGserver.conf')
# find config file relative to the directory in which the scrip resides
_config_file = os.path.join(MIG_BASE, 'server/MiGserver.conf')
configuration = Configuration(_config_file, False, skip_log,
disable_auth_log)
return configuration
Expand All @@ -61,7 +60,7 @@ def get_configuration_object(config_file=None, skip_log=False,
def get_resource_configuration(resource_home, unique_resource_name,
logger):
"""Load a resource configuration from file"""

# open the configuration file

resource_config_file = resource_home + '/' + unique_resource_name\
Expand Down
2 changes: 1 addition & 1 deletion mig/shared/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ def reload_config(self, verbose, skip_log=False, disable_auth_log=False,
else:
print("""Could not find your configuration file (%s). You might
need to point the MIG_CONF environment to your actual MiGserver.conf
location.""" % self.config_file)
location.""" % _config_file)
raise IOError

config = ConfigParser()
Expand Down
3 changes: 1 addition & 2 deletions mig/shared/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@
import os
import sys

MIG_BASE = os.path.realpath(os.path.join(os.path.dirname(__file__), '../..'))
MIG_ENV = os.getenv('MIG_ENV', 'default')
from mig.shared._env import MIG_BASE, MIG_ENV

# NOTE: python3 switched strings to use unicode by default in contrast to bytes
# in python2. File systems remain with utf8 however so we need to
Expand Down
Loading
Loading