From aa3983bf9152a78bf2b577a1a4f3e51ecc387c64 Mon Sep 17 00:00:00 2001 From: Jelle Scholtalbers Date: Tue, 5 Dec 2017 20:29:43 +0100 Subject: [PATCH 1/9] fix #5129: auto create ldap role(&group) and assign to newly auto-registered user --- config/auth_conf.xml.sample | 7 +++++ lib/galaxy/auth/__init__.py | 17 ++++++++-- lib/galaxy/auth/providers/ldap_ad.py | 21 ++++++++++++- lib/galaxy/security/__init__.py | 31 +++++++++++++++++++ lib/galaxy/webapps/galaxy/controllers/user.py | 26 ++++++++++++++-- lib/tool_shed/util/admin_util.py | 23 ++------------ 6 files changed, 97 insertions(+), 28 deletions(-) diff --git a/config/auth_conf.xml.sample b/config/auth_conf.xml.sample index 70d7db0e7a0..af2e5d26802 100644 --- a/config/auth_conf.xml.sample +++ b/config/auth_conf.xml.sample @@ -25,6 +25,11 @@ + + @@ -88,12 +93,14 @@ {password} {sAMAccountName} {mail} + gidNumber --> - + + + + diff --git a/lib/galaxy/auth/__init__.py b/lib/galaxy/auth/__init__.py index 2ae96e2da40..186ccfa0600 100644 --- a/lib/galaxy/auth/__init__.py +++ b/lib/galaxy/auth/__init__.py @@ -111,6 +111,11 @@ class AuthManager(object): auth_return["auto_reg"] = string_as_bool(options.get('auto-register', False)) auth_return["email"] = auto_email auth_return["username"] = auto_username + auth_return["auto_create_roles"] = string_as_bool(options.get('auto-create-roles', False)) + auth_return["auto_create_groups"] = string_as_bool(options.get('auto-create-groups', False)) + auth_return["auto_assign_roles_to_groups_only"] = string_as_bool( + options.get('auto-assign-roles-to-groups-only', False)) + if len(auth_results) == 4: auth_return["attributes"] = auth_results[3] return auth_return diff --git a/lib/galaxy/auth/providers/ldap_ad.py b/lib/galaxy/auth/providers/ldap_ad.py index 7bf7eee74b4..4505010464a 100644 --- a/lib/galaxy/auth/providers/ldap_ad.py +++ b/lib/galaxy/auth/providers/ldap_ad.py @@ -93,8 +93,18 @@ class LDAP(AuthProvider): return (failure_mode, '', '') auto_create_roles = string_as_bool(options.get('auto-create-roles', False)) + auto_create_groups = string_as_bool(options.get('auto-create-groups', False)) + auto_create_roles_or_groups = auto_create_roles or auto_create_groups + auto_assign_roles_to_groups_only = string_as_bool(options.get('auto-assign-roles-to-groups-only', False)) + if auto_assign_roles_to_groups_only and not (auto_create_roles and auto_create_groups): + raise ConfigurationError("If 'auto-assign-roles-to-groups-only' is True, auto-create-roles and " + "auto-create-groups have to be True as well.") + role_search_option = 'auto-register-roles' role_search_attribute = options.get(role_search_option, None) + if auto_create_roles_or_groups and role_search_attribute is None: + raise ConfigurationError("If 'auto-create-roles' or 'auto-create-groups' is True, a '%s' attribute has to" + " be provided." % role_search_option) try: import ldap @@ -136,7 +146,7 @@ class LDAP(AuthProvider): # setup search attributes = [_.strip().format(**params) for _ in options['search-fields'].split(',')] - if auto_create_roles and role_search_attribute not in attributes: + if auto_create_roles_or_groups and role_search_attribute not in attributes: attributes.append(role_search_attribute) suser = l.search_ext_s(_get_subs(options, 'search-base', params), ldap.SCOPE_SUBTREE, @@ -156,7 +166,7 @@ class LDAP(AuthProvider): params[attr] = str(attrs[attr][0]) else: params[attr] = "" - if auto_create_roles: + if auto_create_roles_or_groups: if role_search_attribute in attrs: params[role_search_option] = attrs[role_search_attribute] else: @@ -193,7 +203,7 @@ class LDAP(AuthProvider): log.debug('LDAP authentication successful') attributes = {} - if auto_create_roles: + if auto_create_roles_or_groups: attributes['roles'] = params[role_search_option] return (True, _get_subs(options, 'auto-register-email', params), diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 77b8b3a81ca..ed758d26c4b 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -788,19 +788,16 @@ class GalaxyRBACAgent(RBACAgent): self.sa_session.add(role) # Create the UserRoleAssociations for user in [self.sa_session.query(self.model.User).get(x) for x in in_users]: - ura = self.model.UserRoleAssociation(user, role) - self.sa_session.add(ura) + self.associate_user_role(user, role) # Create the GroupRoleAssociations for group in [self.sa_session.query(self.model.Group).get(x) for x in in_groups]: - gra = self.model.GroupRoleAssociation(group, role) - self.sa_session.add(gra) + self.associate_group_role(group, role) if create_group_for_role: # Create the group group = self.model.Group(name=name) self.sa_session.add(group) # Associate the group with the role - gra = self.model.GroupRoleAssociation(group, role) - self.sa_session.add(gra) + self.associate_group_role(group, role) num_in_groups = len(in_groups) + 1 else: num_in_groups = len(in_groups) diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index 3660b823839..2d4b60e858d 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -501,6 +501,41 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create form_input_auto_focus=True, active_view="user") + def __handle_role_and_group_auto_creation(self, trans, user, roles, auto_create_roles=False, + auto_create_groups=False, auto_assign_roles_to_groups_only=False): + for role_name in roles: + role = None + group = None + if auto_create_roles: + try: + # first try to find the role + role = trans.app.security_agent.get_admin_role(role_name) + except NoResultFound: + # or create it + role, num_in_groups = trans.app.security_agent.create_admin_role( + role_name, "Auto created upon user registration", [], [], + create_group_for_role=auto_create_groups) + if auto_create_groups: + trans.log_event("Created role and group for auto-registered user.") + else: + trans.log_event("Created role for auto-registered user.") + if auto_create_groups: + # only create a group if not existing yet + try: + group = self.sa_session.query(trans.app.model.Group).filter( + trans.app.model.Group.table.c.name == role_name).first() + except NoResultFound: + group = self.model.Group(name=role_name) + self.sa_session.add(group) + trans.app.security_agent.associate_user_group(user, group) + + if auto_assign_roles_to_groups_only and group and role: + trans.log_event("Assigning role to group only") + trans.app.security_agent.associate_group_role(group, role) + elif not auto_assign_roles_to_groups_only and role: + trans.log_event("Assigning role to newly created user") + trans.app.security_agent.associate_user_role(user, role) + def __validate_login(self, trans, **kwd): """Validates numerous cases that might happen during the login time.""" status = kwd.get('status', 'error') @@ -530,22 +565,11 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create trans.log_event("User (auto) created a new account") trans.log_event("User logged in") if "attributes" in autoreg and "roles" in autoreg["attributes"]: - for role_name in autoreg["attributes"]["roles"]: - created = False - try: - # first try to find the role - role = trans.app.security_agent.get_admin_role(role_name) - except NoResultFound: - # or create it - role, num_in_groups = trans.app.security_agent.create_admin_role( - role_name, "Auto created upon user registration", [user.id], [], - create_group_for_role=True) - created = True - trans.log_event("Created role and group for auto-registered user.") - if not created: - # assign role to user - trans.log_event("Assigning existing role to newly created user") - trans.app.security_agent.associate_user_role(user, role) + self.__handle_role_and_group_auto_creation( + trans, user, autoreg["attributes"]["roles"], + auto_create_groups=autoreg["auto_create_groups"], + auto_create_roles=autoreg["auto_create_roles"], + auto_assign_roles_to_groups_only=autoreg["auto_assign_roles_to_groups_only"]) else: message = "Auto-registration failed, contact your local Galaxy administrator. %s" % message else: From 79aaa3492d30dc4a612988274e0c727318c95cdd Mon Sep 17 00:00:00 2001 From: Jelle Scholtalbers Date: Wed, 20 Dec 2017 15:01:34 +0100 Subject: [PATCH 5/9] Also catch empty string - e.g. upon create user from the admin page --- lib/galaxy/auth/providers/ldap_ad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/auth/providers/ldap_ad.py b/lib/galaxy/auth/providers/ldap_ad.py index 4505010464a..2a60d6df69f 100644 --- a/lib/galaxy/auth/providers/ldap_ad.py +++ b/lib/galaxy/auth/providers/ldap_ad.py @@ -84,11 +84,11 @@ class LDAP(AuthProvider): failure_mode = None # reject and do not continue if string_as_bool(options.get('login-use-username', False)): - if username is None: + if not username: log.debug('LDAP authenticate: username must be used to login, cannot be None') return (failure_mode, '', '') else: - if email is None: + if not email: log.debug('LDAP authenticate: email must be used to login, cannot be None') return (failure_mode, '', '') From a6435eade58071798ba76ac8c22b01927e768ed5 Mon Sep 17 00:00:00 2001 From: Jelle Scholtalbers Date: Wed, 20 Dec 2017 14:50:16 +0100 Subject: [PATCH 6/9] On top of 5129, this allows to pre-populate an ldap user from the admin interface. An option is added to show/hide the pre-populate form in the admin register user form. --- config/galaxy.ini.sample | 4 + lib/galaxy/auth/__init__.py | 81 ++-------- lib/galaxy/auth/providers/ldap_ad.py | 140 +++++++++++------- lib/galaxy/auth/util.py | 74 +++++++++ lib/galaxy/config.py | 1 + lib/galaxy/webapps/galaxy/controllers/user.py | 84 +++++++---- templates/user/register.mako | 24 +++ 7 files changed, 260 insertions(+), 148 deletions(-) create mode 100644 lib/galaxy/auth/util.py diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index 8deb3317110..5688f3035da 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -1045,6 +1045,10 @@ use_interactive = True # Allow administrators to log in as other users (useful for debugging) #allow_user_impersonation = False +# When using LDAP for authentication, allow administrators to pre-populate users +# using an additional form on 'Create new user' +#show_prepopulate_form = False + # Allow users to remove their datasets from disk immediately (otherwise, # datasets will be removed after a time period specified by an administrator in # the cleanup scripts run via cron) diff --git a/lib/galaxy/auth/__init__.py b/lib/galaxy/auth/__init__.py index 186ccfa0600..2c800dc6414 100644 --- a/lib/galaxy/auth/__init__.py +++ b/lib/galaxy/auth/__init__.py @@ -3,11 +3,10 @@ Contains implementations of the authentication logic. """ import logging -import xml.etree.ElementTree -from collections import namedtuple -from galaxy.security.validate_user_input import validate_publicname -from galaxy.util import plugin_config, string_as_bool +from galaxy.auth.util import get_authenticators, update_auth_return +from galaxy.exceptions import Conflict +from galaxy.util import string_as_bool log = logging.getLogger(__name__) @@ -16,39 +15,7 @@ class AuthManager(object): def __init__(self, app): self.__app = app - import galaxy.auth.providers - self.__plugins_dict = plugin_config.plugins_dict(galaxy.auth.providers, 'plugin_type') - auth_config_file = app.config.auth_config_file - # parse XML - ct = xml.etree.ElementTree.parse(auth_config_file) - conf_root = ct.getroot() - - authenticators = [] - # process authenticators - for auth_elem in conf_root: - type_elem = auth_elem.find('type') - plugin = self.__plugins_dict.get(type_elem.text)() - - # check filterelem - filter_elem = auth_elem.find('filter') - if filter_elem is not None: - filter_template = str(filter_elem.text) - else: - filter_template = None - - # extract options - options_elem = auth_elem.find('options') - options = {} - if options_elem is not None: - for opt in options_elem: - options[opt.tag] = opt.text - authenticator = Authenticator( - plugin=plugin, - filter_template=filter_template, - options=options, - ) - authenticators.append(authenticator) - self.authenticators = authenticators + self.authenticators = get_authenticators(app.config.auth_config_file) def check_registration_allowed(self, email, username, password): """Checks if the provided email/username is allowed to register.""" @@ -72,7 +39,7 @@ class AuthManager(object): break return message, status - def check_auto_registration(self, trans, login, password): + def check_auto_registration(self, trans, login, password, no_password_check=False): """ Checks the username/email & password using auth providers in order. If a match is found, returns the 'auto-register' option for that provider. @@ -92,34 +59,17 @@ class AuthManager(object): if provider is None: log.debug("Unable to find module: %s" % options) else: + options['no_password_check'] = no_password_check auth_results = provider.authenticate(email, username, password, options) - auth_result, auto_email, auto_username = auth_results[:3] - auto_email = str(auto_email).lower() - auto_username = str(auto_username).lower() - if auth_result is True: - # make username unique - if validate_publicname(trans, auto_username) != '': - i = 1 - while i <= 10: # stop after 10 tries - if validate_publicname(trans, "%s-%i" % (auto_username, i)) == '': - auto_username = "%s-%i" % (auto_username, i) - break - i += 1 - else: - break # end for loop if we can't make a unique username - log.debug("Email: %s, auto-register with username: %s" % (auto_email, auto_username)) - auth_return["auto_reg"] = string_as_bool(options.get('auto-register', False)) - auth_return["email"] = auto_email - auth_return["username"] = auto_username - auth_return["auto_create_roles"] = string_as_bool(options.get('auto-create-roles', False)) - auth_return["auto_create_groups"] = string_as_bool(options.get('auto-create-groups', False)) - auth_return["auto_assign_roles_to_groups_only"] = string_as_bool( - options.get('auto-assign-roles-to-groups-only', False)) - - if len(auth_results) == 4: - auth_return["attributes"] = auth_results[3] + if auth_results[0] is True: + try: + update_auth_return(trans, auth_return, auth_results, options) + except Conflict: + break return auth_return - elif auth_result is None: + elif auth_results[0] is None: + auto_email = str(auth_results[1]).lower() + auto_username = str(auth_results[2]).lower() log.debug("Email: %s, Username %s, stopping due to failed non-continue" % (auto_email, auto_username)) break # end authentication (skip rest) return auth_return @@ -173,9 +123,6 @@ class AuthManager(object): raise -Authenticator = namedtuple('Authenticator', ['plugin', 'filter_template', 'options']) - - def _get_allow_register(d): s = d.get('allow-register', True) lower_s = str(s).lower() diff --git a/lib/galaxy/auth/providers/ldap_ad.py b/lib/galaxy/auth/providers/ldap_ad.py index 2a60d6df69f..d1d817952d8 100644 --- a/lib/galaxy/auth/providers/ldap_ad.py +++ b/lib/galaxy/auth/providers/ldap_ad.py @@ -71,49 +71,57 @@ class LDAP(AuthProvider): """ plugin_type = 'ldap' - def authenticate(self, email, username, password, options): - """ - See abstract method documentation. - """ - log.debug("LDAP authenticate: email is %s" % email) - log.debug("LDAP authenticate: username is %s" % username) - log.debug("LDAP authenticate: options are %s" % options) + def __init__(self): + super(LDAP, self).__init__() + self.auto_create_roles_or_groups = False + self.role_search_attribute = None + self.role_search_option = 'auto-register-roles' + def check_config(self, username, email, options): + ok = True failure_mode = False # reject but continue if options.get('continue-on-failure', 'False') == 'False': failure_mode = None # reject and do not continue - if string_as_bool(options.get('login-use-username', False)): - if not username: - log.debug('LDAP authenticate: username must be used to login, cannot be None') - return (failure_mode, '', '') - else: - if not email: - log.debug('LDAP authenticate: email must be used to login, cannot be None') - return (failure_mode, '', '') - - auto_create_roles = string_as_bool(options.get('auto-create-roles', False)) - auto_create_groups = string_as_bool(options.get('auto-create-groups', False)) - auto_create_roles_or_groups = auto_create_roles or auto_create_groups - auto_assign_roles_to_groups_only = string_as_bool(options.get('auto-assign-roles-to-groups-only', False)) - if auto_assign_roles_to_groups_only and not (auto_create_roles and auto_create_groups): - raise ConfigurationError("If 'auto-assign-roles-to-groups-only' is True, auto-create-roles and " - "auto-create-groups have to be True as well.") - - role_search_option = 'auto-register-roles' - role_search_attribute = options.get(role_search_option, None) - if auto_create_roles_or_groups and role_search_attribute is None: - raise ConfigurationError("If 'auto-create-roles' or 'auto-create-groups' is True, a '%s' attribute has to" - " be provided." % role_search_option) - try: import ldap except ImportError: log.debug('LDAP authenticate: could not load ldap module') - return (failure_mode, '', '') + ok = False + return ok, failure_mode - # do LDAP search (if required) - params = {'email': email, 'username': username, 'password': password} + if string_as_bool(options.get('login-use-username', False)): + if not username: + log.debug('LDAP authenticate: username must be used to login, cannot be None') + return ok, failure_mode + else: + if not email: + log.debug('LDAP authenticate: email must be used to login, cannot be None') + return ok, failure_mode + + auto_create_roles = string_as_bool(options.get('auto-create-roles', False)) + auto_create_groups = string_as_bool(options.get('auto-create-groups', False)) + self.auto_create_roles_or_groups = auto_create_roles or auto_create_groups + auto_assign_roles_to_groups_only = string_as_bool(options.get('auto-assign-roles-to-groups-only', False)) + if auto_assign_roles_to_groups_only and not (auto_create_roles and auto_create_groups): + raise ConfigurationError("If 'auto-assign-roles-to-groups-only' is True, auto-create-roles and " + "auto-create-groups have to be True as well.") + + self.role_search_attribute = options.get(self.role_search_option, None) + return ok, failure_mode + + def ldap_search(self, email, username, options): + config_ok, failure_mode = self.check_config(username, email, options) + if not config_ok: + return failure_mode, None + + import ldap + + if self.auto_create_roles_or_groups and self.role_search_attribute is None: + raise ConfigurationError("If 'auto-create-roles' or 'auto-create-groups' is True, a '%s' attribute has to" + " be provided." % self.role_search_option) + + params = {'email': email, 'username': username} try: ldap_options_raw = _get_subs(options, 'ldap-options', params) @@ -130,7 +138,7 @@ class LDAP(AuthProvider): ldap.set_option(*opt) except Exception: log.exception('LDAP authenticate: set_option exception') - return (failure_mode, '', '') + return (failure_mode, None) if 'search-fields' in options: try: @@ -146,8 +154,8 @@ class LDAP(AuthProvider): # setup search attributes = [_.strip().format(**params) for _ in options['search-fields'].split(',')] - if auto_create_roles_or_groups and role_search_attribute not in attributes: - attributes.append(role_search_attribute) + if self.auto_create_roles_or_groups and self.role_search_attribute not in attributes: + attributes.append(self.role_search_attribute) suser = l.search_ext_s(_get_subs(options, 'search-base', params), ldap.SCOPE_SUBTREE, _get_subs(options, 'search-filter', params), attributes, @@ -156,7 +164,7 @@ class LDAP(AuthProvider): # parse results if suser is None or len(suser) == 0: log.warning('LDAP authenticate: search returned no results') - return (failure_mode, '', '') + return (failure_mode, None) dn, attrs = suser[0] log.debug(("LDAP authenticate: dn is %s" % dn)) log.debug(("LDAP authenticate: search attributes are %s" % attrs)) @@ -166,22 +174,52 @@ class LDAP(AuthProvider): params[attr] = str(attrs[attr][0]) else: params[attr] = "" - if auto_create_roles_or_groups: - if role_search_attribute in attrs: - params[role_search_option] = attrs[role_search_attribute] + if self.auto_create_roles_or_groups: + if self.role_search_attribute in attrs: + params[self.role_search_option] = attrs[self.role_search_attribute] else: hint = "" - if role_search_attribute.startswith('{'): - hint = "Note: '%s' value should not be surrounded by brackets." % role_search_option + if self.role_search_attribute.startswith('{'): + hint = "Note: '%s' value should not be surrounded by brackets." % self.role_search_option raise ConfigurationError("Missing '%s' parameter in LDAP options. %s" % - (role_search_attribute, hint)) + (self.role_search_attribute, hint)) params['dn'] = dn except Exception: log.exception('LDAP authenticate: search exception') - return (failure_mode, '', '') + return (failure_mode, None) # end search - # bind as user to check their credentials + return failure_mode, params + + def authenticate(self, email, username, password, options): + """ + See abstract method documentation. + """ + log.debug("LDAP authenticate: email is %s" % email) + log.debug("LDAP authenticate: username is %s" % username) + log.debug("LDAP authenticate: options are %s" % options) + + failure_mode, params = self.ldap_search(email, username, options) + if not params: + return failure_mode, '', '' + + # allow to skip authentication to allow for pre-populating users + if not options.get('no_password_check', False): + params['password'] = password + if not self._authenticate(params, options): + return failure_mode, '', '' + + attributes = {} + if self.auto_create_roles_or_groups: + attributes['roles'] = params[self.role_search_option] + return (True, + _get_subs(options, 'auto-register-email', params), + _get_subs(options, 'auto-register-username', params), + attributes) + + # do the actual authentication by binding as the user to check their credentials + def _authenticate(self, params, options): + import ldap try: l = ldap.initialize(_get_subs(options, 'server', params)) l.protocol_version = 3 @@ -199,16 +237,10 @@ class LDAP(AuthProvider): raise RuntimeError('LDAP authenticate: anonymous bind') except Exception: log.warning('LDAP authenticate: bind exception', exc_info=True) - return (failure_mode, '', '') - + return False log.debug('LDAP authentication successful') - attributes = {} - if auto_create_roles_or_groups: - attributes['roles'] = params[role_search_option] - return (True, - _get_subs(options, 'auto-register-email', params), - _get_subs(options, 'auto-register-username', params), - attributes) + return True + def authenticate_user(self, user, password, options): """ diff --git a/lib/galaxy/auth/util.py b/lib/galaxy/auth/util.py new file mode 100644 index 00000000000..ce3a70f5a4c --- /dev/null +++ b/lib/galaxy/auth/util.py @@ -0,0 +1,74 @@ +import logging +import xml.etree.ElementTree +from collections import namedtuple + +import galaxy.auth.providers +from galaxy.exceptions import Conflict +from galaxy.security.validate_user_input import validate_publicname +from galaxy.util import plugin_config, string_as_bool + + +log = logging.getLogger(__name__) + +Authenticator = namedtuple('Authenticator', ['plugin', 'filter_template', 'options']) + + +def get_authenticators(auth_config_file): + __plugins_dict = plugin_config.plugins_dict(galaxy.auth.providers, 'plugin_type') + # parse XML + ct = xml.etree.ElementTree.parse(auth_config_file) + conf_root = ct.getroot() + + authenticators = [] + # process authenticators + for auth_elem in conf_root: + type_elem = auth_elem.find('type') + plugin = __plugins_dict.get(type_elem.text)() + + # check filterelem + filter_elem = auth_elem.find('filter') + if filter_elem is not None: + filter_template = str(filter_elem.text) + else: + filter_template = None + + # extract options + options_elem = auth_elem.find('options') + options = {} + if options_elem is not None: + for opt in options_elem: + options[opt.tag] = opt.text + authenticator = Authenticator( + plugin=plugin, + filter_template=filter_template, + options=options, + ) + authenticators.append(authenticator) + return authenticators + + +def update_auth_return(trans, auth_return, auth_results, options): + auth_result, auto_email, auto_username = auth_results[:3] + auto_email = str(auto_email).lower() + auto_username = str(auto_username).lower() + # make username unique + if validate_publicname(trans, auto_username) != '': + i = 1 + while i <= 10: # stop after 10 tries + if validate_publicname(trans, "%s-%i" % (auto_username, i)) == '': + auto_username = "%s-%i" % (auto_username, i) + break + i += 1 + else: + raise Conflict("Cannot make unique username") + log.debug("Email: %s, auto-register with username: %s" % (auto_email, auto_username)) + auth_return["auto_reg"] = string_as_bool(options.get('auto-register', False)) + auth_return["email"] = auto_email + auth_return["username"] = auto_username + auth_return["auto_create_roles"] = string_as_bool(options.get('auto-create-roles', False)) + auth_return["auto_create_groups"] = string_as_bool(options.get('auto-create-groups', False)) + auth_return["auto_assign_roles_to_groups_only"] = string_as_bool( + options.get('auto-assign-roles-to-groups-only', False)) + + if len(auth_results) == 4: + auth_return["attributes"] = auth_results[3] diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index cf751d0edb9..bd5f506d76a 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -283,6 +283,7 @@ class Configuration(object): self.allow_user_deletion = string_as_bool(kwargs.get("allow_user_deletion", "False")) self.allow_user_dataset_purge = string_as_bool(kwargs.get("allow_user_dataset_purge", "True")) self.allow_user_impersonation = string_as_bool(kwargs.get("allow_user_impersonation", "False")) + self.show_prepopulate_form = string_as_bool(kwargs.get("show_prepopulate_form", "False")) self.new_user_dataset_access_role_default_private = string_as_bool(kwargs.get("new_user_dataset_access_role_default_private", "False")) self.collect_outputs_from = [x.strip() for x in kwargs.get('collect_outputs_from', 'new_file_path,job_working_directory').lower().split(',')] self.template_path = resolve_path(kwargs.get("template_path", "templates"), self.root) diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index 2d4b60e858d..0c1882f0774 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -536,6 +536,43 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create trans.log_event("Assigning role to newly created user") trans.app.security_agent.associate_user_role(user, role) + def __autoregistration(self, trans, login, password, status, kwd, no_password_check=False, cntrller=None): + """ + Does the autoregistration if enabled. Returns a message + """ + no_login_handling = cntrller == 'admin' and trans.user_is_admin() + log.warning(no_login_handling) + autoreg = trans.app.auth_manager.check_auto_registration(trans, login, password, no_password_check=no_password_check) + user = None + success = False + if autoreg["auto_reg"]: + kwd['email'] = autoreg["email"] + kwd['username'] = autoreg["username"] + message = " ".join([validate_email(trans, kwd['email']), + validate_publicname(trans, kwd['username'])]).rstrip() + if not message: + message, status, user, success = self.__register(trans, cntrller, False, **kwd) + if success: + # The handle_user_login() method has a call to the history_set_default_permissions() method + # (needed when logging in with a history), user needs to have default permissions set before logging in + if not no_login_handling: + trans.handle_user_login(user) + trans.log_event("User (auto) created a new account") + trans.log_event("User logged in") + if "attributes" in autoreg and "roles" in autoreg["attributes"]: + self.__handle_role_and_group_auto_creation( + trans, user, autoreg["attributes"]["roles"], + auto_create_groups=autoreg["auto_create_groups"], + auto_create_roles=autoreg["auto_create_roles"], + auto_assign_roles_to_groups_only=autoreg["auto_assign_roles_to_groups_only"]) + else: + message = "Auto-registration failed, contact your local Galaxy administrator. %s" % message + else: + message = "Auto-registration failed, contact your local Galaxy administrator. %s" % message + else: + message = "No such user or invalid password" + return message, status, user, success + def __validate_login(self, trans, **kwd): """Validates numerous cases that might happen during the login time.""" status = kwd.get('status', 'error') @@ -550,32 +587,8 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create )).first() log.debug("trans.app.config.auth_config_file: %s" % trans.app.config.auth_config_file) if not user: - autoreg = trans.app.auth_manager.check_auto_registration(trans, login, password) - if autoreg["auto_reg"]: - kwd['email'] = autoreg["email"] - kwd['username'] = autoreg["username"] - message = " ".join([validate_email(trans, kwd['email']), - validate_publicname(trans, kwd['username'])]).rstrip() - if not message: - message, status, user, success = self.__register(trans, 'user', False, **kwd) - if success: - # The handle_user_login() method has a call to the history_set_default_permissions() method - # (needed when logging in with a history), user needs to have default permissions set before logging in - trans.handle_user_login(user) - trans.log_event("User (auto) created a new account") - trans.log_event("User logged in") - if "attributes" in autoreg and "roles" in autoreg["attributes"]: - self.__handle_role_and_group_auto_creation( - trans, user, autoreg["attributes"]["roles"], - auto_create_groups=autoreg["auto_create_groups"], - auto_create_roles=autoreg["auto_create_roles"], - auto_assign_roles_to_groups_only=autoreg["auto_assign_roles_to_groups_only"]) - else: - message = "Auto-registration failed, contact your local Galaxy administrator. %s" % message - else: - message = "Auto-registration failed, contact your local Galaxy administrator. %s" % message - else: - message = "No such user or invalid password" + message, status, user, success = self.__autoregistration(trans, login, password, status, kwd) + elif user.deleted: message = "This account has been marked deleted, contact your local Galaxy administrator to restore the account." if trans.app.config.error_email_to is not None: @@ -727,12 +740,28 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create subscribe_checked = CheckboxField.is_checked(subscribe) referer = trans.request.referer or '' redirect = kwd.get('redirect', referer).strip() - is_admin = cntrller == 'admin' and trans.user_is_admin + is_admin = cntrller == 'admin' and trans.user_is_admin() + show_prepopulate_form = is_admin and trans.app.config.show_prepopulate_form if not trans.app.config.allow_user_creation and not trans.user_is_admin(): message = 'User registration is disabled. Please contact your local Galaxy administrator for an account.' if trans.app.config.error_email_to is not None: message += ' Contact: %s' % trans.app.config.error_email_to status = 'error' + elif show_prepopulate_form and params.get('prepopulate_user_button', False): + # pre-populate the user through a provider like ldap + csrf_check = trans.check_csrf_token() + if csrf_check: + return csrf_check + login = username if username else email + message, status, user, success = self.__autoregistration(trans, login, '', status, kwd, + no_password_check=True, cntrller=cntrller) + if success: + message = 'Prepopulated new user account (%s)' % escape(user.email) + trans.response.send_redirect(web.url_for(controller='admin', + action='users', + cntrller=cntrller, + message=message, + status=status)) else: # check user is allowed to register message, status = trans.app.auth_manager.check_registration_allowed(email, username, password) @@ -786,6 +815,7 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create email=email, username=transform_publicname(trans, username), subscribe_checked=subscribe_checked, + show_prepopulate_form=show_prepopulate_form, use_panels=use_panels, redirect=redirect, redirect_url=redirect_url, diff --git a/templates/user/register.mako b/templates/user/register.mako index 659e32a0ebd..ad4548d08ab 100644 --- a/templates/user/register.mako +++ b/templates/user/register.mako @@ -127,6 +127,30 @@ def inherit(context):
+ ## only display the prepopulate form to admins + %if show_prepopulate_form: +
+ +
Pre-populate an account through LDAP
+
+ + + +
+
+ + +
+ Depending on your LDAP configuration in your auth_conf.xml you can either provide an + email address or username. +
+
+
+ +
+
+
+ %endif
Create account
From 1128e8a4ee094d7b471730f7513b182fb135b647 Mon Sep 17 00:00:00 2001 From: Jelle Scholtalbers Date: Wed, 20 Dec 2017 21:48:53 +0100 Subject: [PATCH 7/9] some fixes to ensure groups and roles are created on pre-populating users --- lib/galaxy/auth/__init__.py | 4 ++-- lib/galaxy/auth/util.py | 4 +++- lib/galaxy/webapps/galaxy/controllers/user.py | 15 +++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/auth/__init__.py b/lib/galaxy/auth/__init__.py index 2c800dc6414..52ceb7e7d2d 100644 --- a/lib/galaxy/auth/__init__.py +++ b/lib/galaxy/auth/__init__.py @@ -4,7 +4,7 @@ Contains implementations of the authentication logic. import logging -from galaxy.auth.util import get_authenticators, update_auth_return +from galaxy.auth.util import get_authenticators, parse_auth_results from galaxy.exceptions import Conflict from galaxy.util import string_as_bool @@ -63,7 +63,7 @@ class AuthManager(object): auth_results = provider.authenticate(email, username, password, options) if auth_results[0] is True: try: - update_auth_return(trans, auth_return, auth_results, options) + auth_return = parse_auth_results(trans, auth_results, options) except Conflict: break return auth_return diff --git a/lib/galaxy/auth/util.py b/lib/galaxy/auth/util.py index ce3a70f5a4c..b256fc29ec2 100644 --- a/lib/galaxy/auth/util.py +++ b/lib/galaxy/auth/util.py @@ -47,7 +47,8 @@ def get_authenticators(auth_config_file): return authenticators -def update_auth_return(trans, auth_return, auth_results, options): +def parse_auth_results(trans, auth_results, options): + auth_return = {} auth_result, auto_email, auto_username = auth_results[:3] auto_email = str(auto_email).lower() auto_username = str(auto_username).lower() @@ -72,3 +73,4 @@ def update_auth_return(trans, auth_return, auth_results, options): if len(auth_results) == 4: auth_return["attributes"] = auth_results[3] + return auth_return diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index 0c1882f0774..b1b03d4988c 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -540,8 +540,7 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create """ Does the autoregistration if enabled. Returns a message """ - no_login_handling = cntrller == 'admin' and trans.user_is_admin() - log.warning(no_login_handling) + skip_login_handling = cntrller == 'admin' and trans.user_is_admin() autoreg = trans.app.auth_manager.check_auto_registration(trans, login, password, no_password_check=no_password_check) user = None success = False @@ -551,14 +550,14 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create message = " ".join([validate_email(trans, kwd['email']), validate_publicname(trans, kwd['username'])]).rstrip() if not message: - message, status, user, success = self.__register(trans, cntrller, False, **kwd) + message, status, user, success = self.__register(trans, cntrller, False, no_redirect=skip_login_handling, **kwd) if success: # The handle_user_login() method has a call to the history_set_default_permissions() method # (needed when logging in with a history), user needs to have default permissions set before logging in - if not no_login_handling: + if not skip_login_handling: trans.handle_user_login(user) - trans.log_event("User (auto) created a new account") - trans.log_event("User logged in") + trans.log_event("User (auto) created a new account") + trans.log_event("User logged in") if "attributes" in autoreg and "roles" in autoreg["attributes"]: self.__handle_role_and_group_auto_creation( trans, user, autoreg["attributes"]["roles"], @@ -824,7 +823,7 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create message=message, status=status) - def __register(self, trans, cntrller, subscribe_checked, **kwd): + def __register(self, trans, cntrller, subscribe_checked, no_redirect=False, **kwd): email = util.restore_text(kwd.get('email', '')) password = kwd.get('password', '') username = util.restore_text(kwd.get('username', '')) @@ -856,7 +855,7 @@ class User(BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Create trans.handle_user_login(user) trans.log_event("User created a new account") trans.log_event("User logged in") - elif not error: + elif not error and not no_redirect: trans.response.send_redirect(web.url_for(controller='admin', action='users', message='Created new user account (%s)' % user.email, From aa4ab1cc0565b56c25da421fe89f5df9406f4cbd Mon Sep 17 00:00:00 2001 From: Jelle Scholtalbers Date: Thu, 21 Dec 2017 08:25:36 +0100 Subject: [PATCH 8/9] fix linting errors --- lib/galaxy/auth/providers/ldap_ad.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/auth/providers/ldap_ad.py b/lib/galaxy/auth/providers/ldap_ad.py index d1d817952d8..07aede23cf0 100644 --- a/lib/galaxy/auth/providers/ldap_ad.py +++ b/lib/galaxy/auth/providers/ldap_ad.py @@ -83,13 +83,6 @@ class LDAP(AuthProvider): if options.get('continue-on-failure', 'False') == 'False': failure_mode = None # reject and do not continue - try: - import ldap - except ImportError: - log.debug('LDAP authenticate: could not load ldap module') - ok = False - return ok, failure_mode - if string_as_bool(options.get('login-use-username', False)): if not username: log.debug('LDAP authenticate: username must be used to login, cannot be None') @@ -112,10 +105,14 @@ class LDAP(AuthProvider): def ldap_search(self, email, username, options): config_ok, failure_mode = self.check_config(username, email, options) - if not config_ok: + try: + import ldap + except ImportError: + log.debug('LDAP authenticate: could not load ldap module') return failure_mode, None - import ldap + if not config_ok: + return failure_mode, None if self.auto_create_roles_or_groups and self.role_search_attribute is None: raise ConfigurationError("If 'auto-create-roles' or 'auto-create-groups' is True, a '%s' attribute has to" @@ -187,7 +184,6 @@ class LDAP(AuthProvider): except Exception: log.exception('LDAP authenticate: search exception') return (failure_mode, None) - # end search return failure_mode, params @@ -217,8 +213,10 @@ class LDAP(AuthProvider): _get_subs(options, 'auto-register-username', params), attributes) - # do the actual authentication by binding as the user to check their credentials def _authenticate(self, params, options): + """ + Do the actual authentication by binding as the user to check their credentials + """ import ldap try: l = ldap.initialize(_get_subs(options, 'server', params)) @@ -241,7 +239,6 @@ class LDAP(AuthProvider): log.debug('LDAP authentication successful') return True - def authenticate_user(self, user, password, options): """ See abstract method documentation. From 8be4addaa995b6a4cf2c9bad1d2d4b88a4b0ce78 Mon Sep 17 00:00:00 2001 From: Jelle Scholtalbers Date: Fri, 19 Jan 2018 11:25:04 +0100 Subject: [PATCH 9/9] update ldap role creation based on comments on PR #5136 and PR #5136 --- config/auth_conf.xml.sample | 4 +-- config/galaxy.ini.sample | 2 +- lib/galaxy/auth/providers/ldap_ad.py | 35 ++++++++++--------- lib/galaxy/config.py | 2 +- lib/galaxy/security/__init__.py | 10 +++--- lib/galaxy/webapps/galaxy/controllers/user.py | 10 +++--- lib/tool_shed/util/admin_util.py | 2 +- templates/user/register.mako | 2 +- 8 files changed, 35 insertions(+), 32 deletions(-) diff --git a/config/auth_conf.xml.sample b/config/auth_conf.xml.sample index 55aa38ec505..34d30693b72 100644 --- a/config/auth_conf.xml.sample +++ b/config/auth_conf.xml.sample @@ -104,14 +104,14 @@ {password} {sAMAccountName} {mail} - gidNumber + {gidNumber} -->