mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-22 01:22:00 +00:00
Implement a first attempt at '/users/' to manage authorization and authentication
This commit is contained in:
parent
4ad8c6ff40
commit
1b7a18c108
@ -313,7 +313,7 @@ def setvalues(attribs):
|
||||
else: # an actual resource
|
||||
resource = attribs[0]
|
||||
attribs = attribs[1:]
|
||||
keydata = paramaterize_attribs(attribs)
|
||||
keydata = parameterize_attribs(attribs)
|
||||
if not keydata:
|
||||
return
|
||||
targpath = fullpath_target(resource)
|
||||
|
@ -92,26 +92,6 @@ def authorize(name, element, tenant=False, access='rw'):
|
||||
return None
|
||||
|
||||
|
||||
def set_user_passphrase(name, passphrase, tenant=None):
|
||||
"""Set user passphrase
|
||||
|
||||
:param name: The unique shortname of the user
|
||||
:param passphrase: The passphrase to set for given user
|
||||
:param tenant: The tenant to which the user belongs.
|
||||
"""
|
||||
# TODO(jbjohnso): WORKERPOOL
|
||||
# When worker pool implemented, hand off the
|
||||
# PBKDF2 to a worker instead of blocking
|
||||
user, tenant = _get_usertenant(name, tenant)
|
||||
_passcache[(user, tenant)] = passphrase
|
||||
salt = os.urandom(8)
|
||||
crypted = kdf.PBKDF2(passphrase, salt, 32, 10000,
|
||||
lambda p, s: hash.HMAC.new(p, s, hash.SHA256).digest()
|
||||
)
|
||||
cfm = configmanager.ConfigManager(tenant)
|
||||
cfm.set_user(name, {'cryptpass': (salt, crypted)})
|
||||
|
||||
|
||||
def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
"""Check a a login name and passphrase for authenticity and authorization
|
||||
|
||||
|
@ -61,6 +61,11 @@ nic = {
|
||||
},
|
||||
}
|
||||
|
||||
user = {
|
||||
'passphrase': {
|
||||
'description': 'The passphrase used to authenticate this user'
|
||||
},
|
||||
}
|
||||
|
||||
# 'node', which can be considered a 'system' or a 'vm'
|
||||
node = {
|
||||
|
@ -98,6 +98,7 @@ def _mkpath(pathname):
|
||||
def _derive_keys(passphrase, salt):
|
||||
#implement our specific combination of pbkdf2 transforms to get at
|
||||
#key. We bump the iterations up because we can afford to
|
||||
#TODO: WORKERPOOL PBKDF2 is expensive
|
||||
tmpkey = kdf.PBKDF2(passphrase, salt, 32, 50000,
|
||||
lambda p, s: HMAC.new(p, s, SHA256).digest())
|
||||
finalkey = kdf.PBKDF2(tmpkey, salt, 32, 50000,
|
||||
@ -246,7 +247,8 @@ def set_global(globalname, value):
|
||||
|
||||
|
||||
def _mark_dirtykey(category, key, tenant=None):
|
||||
key = key.encode('utf-8')
|
||||
if type(key) in (str, unicode):
|
||||
key = key.encode('utf-8')
|
||||
with _dirtylock:
|
||||
if 'dirtykeys' not in _cfgstore:
|
||||
_cfgstore['dirtykeys'] = {}
|
||||
@ -261,11 +263,11 @@ def _generate_new_id():
|
||||
# generate a random id outside the usual ranges used for norml users in
|
||||
# /etc/passwd. Leave an equivalent amount of space near the end disused,
|
||||
# just in case
|
||||
id = confluent.util.securerandomnumber(65537, 4294901759)
|
||||
if 'idmap' not in _cfgstore:
|
||||
id = str(confluent.util.securerandomnumber(65537, 4294901759))
|
||||
if 'idmap' not in _cfgstore['main']:
|
||||
return id
|
||||
while id in _cfgstore['idmap']:
|
||||
id = confluent.util.securerandomnumber(65537, 4294901759)
|
||||
while id in _cfgstore['main']['idmap']:
|
||||
id = str(confluent.util.securerandomnumber(65537, 4294901759))
|
||||
return id
|
||||
|
||||
class _ExpressionFormat(string.Formatter):
|
||||
@ -483,6 +485,9 @@ class ConfigManager(object):
|
||||
del self._notifierids[watcher]
|
||||
|
||||
|
||||
def list_users(self):
|
||||
return self._cfgstore['users'].iterkeys()
|
||||
|
||||
def get_user(self, name):
|
||||
"""Get user information from DB
|
||||
|
||||
@ -509,11 +514,21 @@ class ConfigManager(object):
|
||||
user = self._cfgstore['users'][name]
|
||||
_mark_dirtykey('users', name, self.tenant)
|
||||
for attribute in attributemap:
|
||||
user[attribute] = attributemap[attribute]
|
||||
if attribute == 'passphrase':
|
||||
salt = os.urandom(8)
|
||||
#TODO: WORKERPOOL, offload password set to a worker
|
||||
crypted = kdf.PBKDF2(
|
||||
attributemap[attribute], salt, 32, 10000,
|
||||
lambda p, s: HMAC.new(p, s, SHA256).digest()
|
||||
)
|
||||
user['cryptpass'] = (salt, crypted)
|
||||
else:
|
||||
user[attribute] = attributemap[attribute]
|
||||
self._bg_sync_to_file()
|
||||
|
||||
def create_user(self, name,
|
||||
role="Administrator", id=None, displayname=None):
|
||||
role="Administrator", id=None, displayname=None,
|
||||
attributemap=None):
|
||||
"""Create a new user
|
||||
|
||||
:param name: The login name of the user
|
||||
@ -526,7 +541,7 @@ class ConfigManager(object):
|
||||
if id is None:
|
||||
id = _generate_new_id()
|
||||
else:
|
||||
if id in _cfgstore['idmap']:
|
||||
if id in _cfgstore['main']['idmap']:
|
||||
raise Exception("Duplicate id requested")
|
||||
if 'users' not in self._cfgstore:
|
||||
self._cfgstore['users'] = { }
|
||||
@ -537,13 +552,15 @@ class ConfigManager(object):
|
||||
self._cfgstore['users'][name] = {'id': id}
|
||||
if displayname is not None:
|
||||
self._cfgstore['users'][name]['displayname'] = displayname
|
||||
if 'idmap' not in _cfgstore:
|
||||
_cfgstore['idmap'] = {}
|
||||
if 'idmap' not in _cfgstore['main']:
|
||||
_cfgstore['main']['idmap'] = {}
|
||||
_mark_dirtykey('idmap', id)
|
||||
_cfgstore['idmap'][id] = {
|
||||
_cfgstore['main']['idmap'][id] = {
|
||||
'tenant': self.tenant,
|
||||
'username': name
|
||||
}
|
||||
if attributemap is not None:
|
||||
self.set_user(name, attributemap)
|
||||
self._bg_sync_to_file()
|
||||
|
||||
def is_node(self, node):
|
||||
@ -555,7 +572,7 @@ class ConfigManager(object):
|
||||
def get_groups(self):
|
||||
return self._cfgstore['groups'].iterkeys()
|
||||
|
||||
def get_nodes(self):
|
||||
def list_nodes(self):
|
||||
return self._cfgstore['nodes'].iterkeys()
|
||||
|
||||
def get_nodegroup_attributes(self, nodegroup, attributes=[]):
|
||||
|
@ -164,7 +164,7 @@ class ChildCollection(LinkRelation):
|
||||
def get_input_message(path, operation, inputdata, nodes=None):
|
||||
if path[0] == 'power' and path[1] == 'state' and operation != 'retrieve':
|
||||
return InputPowerMessage(path, nodes, inputdata)
|
||||
elif path[0] == 'attributes' and operation != 'retrieve':
|
||||
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
|
||||
return InputAttributes(path, inputdata, nodes)
|
||||
elif path == ['boot', 'nextdevice'] and operation != 'retrieve':
|
||||
return InputBootDevice(path, nodes, inputdata)
|
||||
@ -326,7 +326,7 @@ class PowerState(ConfluentChoiceMessage):
|
||||
|
||||
|
||||
class Attributes(ConfluentMessage):
|
||||
def __init__(self, node=None, kv=None, desc=''):
|
||||
def __init__(self, name=None, kv=None, desc=''):
|
||||
self.desc = desc
|
||||
nkv = {}
|
||||
for key in kv.iterkeys():
|
||||
@ -334,11 +334,11 @@ class Attributes(ConfluentMessage):
|
||||
nkv[key] = {'value': kv[key]}
|
||||
else:
|
||||
nkv[key] = kv[key]
|
||||
if node is None:
|
||||
if name is None:
|
||||
self.kvpairs = nkv
|
||||
else:
|
||||
self.kvpairs = {
|
||||
node: nkv
|
||||
name: nkv
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
# functions. Console is special and just get's passed through
|
||||
# see API.txt
|
||||
|
||||
import confluent.config.attributes as attrscheme
|
||||
import confluent.interface.console as console
|
||||
import confluent.exceptions as exc
|
||||
import confluent.messages as msg
|
||||
@ -73,7 +74,7 @@ def load_plugins():
|
||||
pluginmap[plugin] = tmpmod
|
||||
|
||||
|
||||
rootcollections = ['nodes/', 'groups/']
|
||||
rootcollections = ['nodes/', 'groups/', 'users/']
|
||||
|
||||
|
||||
class PluginRoute(object):
|
||||
@ -117,15 +118,47 @@ nodegroupresources = {
|
||||
}
|
||||
|
||||
|
||||
def create_user(inputdata, configmanager):
|
||||
try:
|
||||
username = inputdata['name']
|
||||
del inputdata['name']
|
||||
except (KeyError, ValueError):
|
||||
raise exc.InvalidArgumentException()
|
||||
configmanager.create_user(username, attributemap=inputdata)
|
||||
|
||||
|
||||
def update_user(name, attribmap, configmanager):
|
||||
try:
|
||||
configmanager.set_user(name, attribmap)
|
||||
except ValueError:
|
||||
raise exc.InvalidArgumentException()
|
||||
|
||||
def show_user(name, configmanager):
|
||||
userobj = configmanager.get_user(name)
|
||||
rv = {}
|
||||
for attr in attrscheme.user.iterkeys():
|
||||
rv[attr] = None
|
||||
if attr == 'passphrase':
|
||||
if 'cryptpass' in userobj:
|
||||
rv['passphrase'] = {'cryptvalue': True}
|
||||
yield msg.CryptedAttributes(kv={'passphrase': rv['passphrase']},
|
||||
desc=attrscheme.user[attr]['description'])
|
||||
else:
|
||||
if attr in userobj:
|
||||
rv[attr] = userobj[attr]
|
||||
yield msg.Attributes(kv={attr: rv[attr]},
|
||||
desc=attrscheme.user[attr]['description'])
|
||||
|
||||
|
||||
def stripnode(iterablersp, node):
|
||||
for i in iterablersp:
|
||||
i.strip_node(node)
|
||||
yield i
|
||||
|
||||
|
||||
def iterate_collections(iterable):
|
||||
def iterate_collections(iterable, forcecollection=True):
|
||||
for coll in iterable:
|
||||
if coll[-1] != '/':
|
||||
if forcecollection and coll[-1] != '/':
|
||||
coll = coll + '/'
|
||||
yield msg.ChildCollection(coll, candelete=True)
|
||||
|
||||
@ -166,7 +199,7 @@ def enumerate_nodegroup_collection(collectionpath, configmanager):
|
||||
|
||||
def enumerate_node_collection(collectionpath, configmanager):
|
||||
if collectionpath == ['nodes']: # it is just '/node/', need a list of nodes
|
||||
return iterate_collections(configmanager.get_nodes())
|
||||
return iterate_collections(configmanager.list_nodes())
|
||||
node = collectionpath[1]
|
||||
if not configmanager.is_node(node):
|
||||
raise exc.NotFoundException("Invalid element requested")
|
||||
@ -256,7 +289,7 @@ def handle_path(path, operation, configmanager, inputdata=None):
|
||||
if operation == "create":
|
||||
inputdata = msg.InputAttributes(pathcomponents, inputdata)
|
||||
create_node(inputdata.attribs, configmanager)
|
||||
return iterate_collections(configmanager.get_nodes())
|
||||
return iterate_collections(configmanager.list_nodes())
|
||||
if len(pathcomponents) == 2:
|
||||
iscollection = True
|
||||
if iscollection:
|
||||
@ -296,5 +329,24 @@ def handle_path(path, operation, configmanager, inputdata=None):
|
||||
return passvalue
|
||||
else:
|
||||
return stripnode(passvalue, node)
|
||||
elif pathcomponents[0] == 'users':
|
||||
try:
|
||||
user = pathcomponents[1]
|
||||
except IndexError: # it's just users/
|
||||
if operation == 'create':
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata)
|
||||
create_user(inputdata.attribs, configmanager)
|
||||
return iterate_collections(configmanager.list_users(), forcecollection=False)
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata)
|
||||
if operation == 'retrieve':
|
||||
return show_user(user, configmanager)
|
||||
elif operation == 'delete':
|
||||
delete_user(user, configmanager)
|
||||
return show_user(user, configmanager)
|
||||
elif operation == 'update':
|
||||
update_user(user, inputdata.attribs, configmanager)
|
||||
return show_user(user, configmanager)
|
||||
else:
|
||||
raise exc.NotFoundException()
|
||||
|
Loading…
Reference in New Issue
Block a user