diff --git a/confluent_server/confluent/auth.py b/confluent_server/confluent/auth.py index c3b01fc8..6e3985f3 100644 --- a/confluent_server/confluent/auth.py +++ b/confluent_server/confluent/auth.py @@ -26,6 +26,7 @@ import Cryptodome.Protocol.KDF as KDF import hashlib import hmac import multiprocessing +import confluent.userutil as userutil try: import PAM except ImportError: @@ -121,6 +122,11 @@ def authorize(name, element, tenant=False, operation='create', if skipuserobj: return None, manager, user, tenant, skipuserobj userobj = manager.get_user(user) + if not userobj: + for group in userutil.grouplist(user): + userobj = manager.get_usergroup(group) + if userobj: + break if userobj: # returning return userobj, manager, user, tenant, skipuserobj return None @@ -160,6 +166,14 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False): credobj = Credentials(user, passphrase) cfm = configmanager.ConfigManager(tenant, username=user) ucfg = cfm.get_user(user) + if ucfg is None: + try: + for group in userutil.grouplist(user): + ucfg = cfm.get_usergroup(group) + if ucfg: + break + except KeyError: + pass if ucfg is None: eventlet.sleep(0.05) return None diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 8cd3b061..5a662a11 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -169,6 +169,24 @@ def _do_notifier(cfg, watcher, callback): logException() + +def _rpc_master_del_usergroup(tenant, name, attributemap): + ConfigManager(tenant).set_user(name, attributemap) + + +def _rpc_del_usergroup(tenant, name, attributemap): + ConfigManager(tenant)._true_set_user(name, attributemap) + + + +def _rpc_master_set_usergroup(tenant, name, attributemap): + ConfigManager(tenant).set_user(name, attributemap) + + +def _rpc_set_usergroup(tenant, name, attributemap): + ConfigManager(tenant)._true_set_user(name, attributemap) + + def _rpc_master_set_user(tenant, name, attributemap): ConfigManager(tenant).set_user(name, attributemap) @@ -220,9 +238,19 @@ def _rpc_del_user(tenant, name): def _rpc_master_create_user(tenant, *args): ConfigManager(tenant).create_user(*args) + +def _rpc_master_create_usergroup(tenant, *args): + ConfigManager(tenant).create_usergroup(*args) + + def _rpc_create_user(tenant, *args): ConfigManager(tenant)._true_create_user(*args) + +def _rpc_create_usergroup(tenant, *args): + ConfigManager(tenant)._true_create_usergroup(*args) + + def _rpc_master_del_groups(tenant, groups): ConfigManager(tenant).del_groups(groups) @@ -1182,6 +1210,12 @@ class ConfigManager(object): except KeyError: return [] + def list_usergroups(self): + try: + return list(self._cfgstore['usergroups']) + except KeyError: + return [] + def get_user(self, name): """Get user information from DB @@ -1219,12 +1253,37 @@ class ConfigManager(object): :param groupname: the name of teh group to modify :param attributemap: The mapping of keys to values to set """ + if cfgleader: + return exec_on_leader('_rpc_master_set_usergroup', self.tenant, + groupname, attributemap) + if cfgstreams: + exec_on_followers('_rpc_set_usergroup', self.tenant, groupname, + attributemap) + self._true_set_usergroup(groupname, attributemap) + def _true_set_usergroup(self, groupname, attributemap): for attribute in attributemap: self._cfgstore['usergroups'][attribute] = attributemap[attribute] _mark_dirtykey('usergroups', groupname, self.tenant) + self._bg_sync_to_file() def create_usergroup(self, groupname, role="Administrator"): + """Create a new user + + :param groupname: The name of the user group + :param role: The role the user should be considered. Can be + "Administrator" or "Technician", defaults to + "Administrator" + """ + if cfgleader: + return exec_on_leader('_rpc_master_create_usergroup', self.tenant, + groupname, role) + if cfgstreams: + exec_on_followers('_rpc_create_usergroup', self.tenant, groupname, + role) + self._true_create_usergroup(groupname, role) + + def _true_create_usergroup(self, groupname, role="Administrator"): if 'usergroups' not in self._cfgstore: self._cfgstore['usergroups'] = {} groupname = groupname.encode('utf-8') @@ -1232,6 +1291,20 @@ class ConfigManager(object): raise Exception("Duplicate groupname requested") self._cfgstore['usergroups'][groupname] = {'role': role} _mark_dirtykey('usergroups', groupname, self.tenant) + self._bg_sync_to_file() + + def del_usergroup(self, name): + if cfgleader: + return exec_on_leader('_rpc_master_del_usergroup', self.tenant, name) + if cfgstreams: + exec_on_followers('_rpc_del_usergroup', self.tenant, name) + self._true_del_usergroup(name) + + def _true_del_usergroup(self, name): + if name in self._cfgstore['usergroups']: + del self._cfgstore['usergroups'][name] + _mark_dirtykey('usergroups', name, self.tenant) + self._bg_sync_to_file() def set_user(self, name, attributemap): """Set user attribute(s) diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 7b897b46..0e802ce6 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -124,7 +124,7 @@ def load_plugins(): rootcollections = ['discovery/', 'events/', 'networking/', - 'noderange/', 'nodes/', 'nodegroups/', 'users/', 'version'] + 'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' , 'users/', 'version'] class PluginRoute(object): @@ -396,6 +396,21 @@ def create_user(inputdata, configmanager): configmanager.create_user(username, attributemap=inputdata) +def create_usergroup(inputdata, configmanager): + try: + groupname = inputdata['name'] + del inputdata['name'] + except (KeyError, ValueError): + raise exc.InvalidArgumentException() + configmanager.create_usergroup(groupname) + + +def update_usergroup(groupname, attribmap, configmanager): + try: + configmanager.set_usergroup(name, attribmap) + except ValueError: + raise exc.InvalidArgumentException() + def update_user(name, attribmap, configmanager): try: configmanager.set_user(name, attribmap) @@ -403,6 +418,9 @@ def update_user(name, attribmap, configmanager): raise exc.InvalidArgumentException() +def show_usergroup(groupname, configmanager): + return [] + def show_user(name, configmanager): userobj = configmanager.get_user(name) rv = {} @@ -451,6 +469,10 @@ def delete_user(user, configmanager): configmanager.del_user(user) yield msg.DeletedResource(user) +def delete_usergroup(usergroup, configmanager): + configmanager.del_usergroup(usergroup) + yield msg.DeletedResource(usergroup) + def delete_nodegroup_collection(collectionpath, configmanager): if len(collectionpath) == 2: # just the nodegroup @@ -1005,6 +1027,31 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True): configmanager, inputdata, operation, pathcomponents) elif pathcomponents[0] == 'version': return (msg.Attributes(kv={'version': confluent.__version__}),) + elif pathcomponents[0] == 'usergroups': + # TODO: when non-administrator accounts exist, + # they must only be allowed to see their own user + try: + usergroup = pathcomponents[1] + except IndexError: # it's just users/ + if operation == 'create': + inputdata = msg.get_input_message( + pathcomponents, operation, inputdata, + configmanager=configmanager) + create_usergroup(inputdata.attribs, configmanager) + return iterate_collections(configmanager.list_usergroups(), + forcecollection=False) + if usergroup not in configmanager.list_usergroups(): + raise exc.NotFoundException("Invalid usergroup %s" % usergroup) + if operation == 'retrieve': + return show_usergroup(usergroup, configmanager) + elif operation == 'delete': + return delete_usergroup(usergroup, configmanager) + elif operation == 'update': + inputdata = msg.get_input_message( + pathcomponents, operation, inputdata, + configmanager=configmanager) + update_usergroup(usergroup, inputdata.attribs, configmanager) + return show_usergroup(usergroup, configmanager) elif pathcomponents[0] == 'users': # TODO: when non-administrator accounts exist, # they must only be allowed to see their own user diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index a90ba1bc..8f8701b8 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -401,7 +401,7 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False, return InputExpression(path, inputdata, nodes) elif path == ['attributes', 'rename']: return InputConfigChangeSet(path, inputdata, nodes, configmanager) - elif path[0] in ('attributes', 'users') and operation != 'retrieve': + elif path[0] in ('attributes', 'users', 'usergroups') and operation != 'retrieve': return InputAttributes(path, inputdata, nodes) elif path == ['boot', 'nextdevice'] and operation != 'retrieve': return InputBootDevice(path, nodes, inputdata)