mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 00:31:09 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 123a002b6c | |||
| 2721f2bd7b | |||
| 43f26d8cbb | |||
| 906c671d90 | |||
| c86ac2885f | |||
| 952fa3d022 | |||
| 90e0f93d37 |
@@ -23,9 +23,11 @@ import confluent.config.configmanager as configmanager
|
||||
import eventlet
|
||||
import eventlet.tpool
|
||||
import Cryptodome.Protocol.KDF as KDF
|
||||
from fnmatch import fnmatch
|
||||
import hashlib
|
||||
import hmac
|
||||
import multiprocessing
|
||||
import confluent.userutil as userutil
|
||||
try:
|
||||
import PAM
|
||||
except ImportError:
|
||||
@@ -39,7 +41,53 @@ _passchecking = {}
|
||||
authworkers = None
|
||||
authcleaner = None
|
||||
|
||||
_allowedbyrole = {
|
||||
'Operator': {
|
||||
'retrieve': ['*'],
|
||||
'create': [
|
||||
'/node*/media/uploads/',
|
||||
'/node*/inventory/firmware/updates/*',
|
||||
'/node*/suppport/servicedata*',
|
||||
'/node*/attributes/expression',
|
||||
],
|
||||
'update': [
|
||||
'/discovery/*',
|
||||
'/networking/macs/rescan',
|
||||
'/node*/power/state',
|
||||
'/node*/power/reseat',
|
||||
'/node*/attributes/*',
|
||||
'/node*/media/*tach',
|
||||
'/node*/boot/nextdevice',
|
||||
'/node*/identify',
|
||||
'/node*/configuration/*',
|
||||
],
|
||||
'start': [
|
||||
'/nodes/*/console/session*',
|
||||
'/nodes/*/shell/sessions*',
|
||||
],
|
||||
'delete': [
|
||||
'/node*/*/events/hardware/log',
|
||||
],
|
||||
},
|
||||
'Monitor': {
|
||||
'retrieve': [
|
||||
'/node*/health/hardware',
|
||||
'/node*/power/state',
|
||||
'/node*/sensors/*',
|
||||
'/nodes/',
|
||||
'/',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
_deniedbyrole = {
|
||||
# This supersedes the above and is only consulted after the allowed has happened
|
||||
'Operator': {
|
||||
'update': [
|
||||
'/node*/configuration/management_controller/users/*',
|
||||
]
|
||||
}
|
||||
}
|
||||
class Credentials(object):
|
||||
def __init__(self, username, passphrase):
|
||||
self.username = username
|
||||
@@ -112,21 +160,37 @@ def authorize(name, element, tenant=False, operation='create',
|
||||
and the relevant ConfigManager object for the context of the
|
||||
request.
|
||||
"""
|
||||
if operation not in ('create', 'start', 'update', 'retrieve', 'delete'):
|
||||
return None
|
||||
# skipuserobj is a leftover from the now abandoned plan to use pam session
|
||||
# to do authorization and authentication. Now confluent always does authorization
|
||||
# even if pam does authentication.
|
||||
if operation not in ('create', 'start', 'update', 'retrieve', 'delete', None):
|
||||
return False
|
||||
user, tenant = _get_usertenant(name, tenant)
|
||||
if tenant is not None and not configmanager.is_tenant(tenant):
|
||||
return None
|
||||
return False
|
||||
manager = configmanager.ConfigManager(tenant, username=user)
|
||||
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
|
||||
role = userobj.get('role', 'Administrator')
|
||||
if element and role != 'Administrator':
|
||||
for rule in _allowedbyrole.get(role, {}).get(operation, []):
|
||||
if fnmatch(element, rule):
|
||||
break
|
||||
else:
|
||||
return False
|
||||
for rule in _deniedbyrole.get(role, {}).get(operation, []):
|
||||
if fnmatch(element, rule):
|
||||
return False
|
||||
return userobj, manager, user, tenant, skipuserobj
|
||||
return None
|
||||
return False
|
||||
|
||||
|
||||
def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
def check_user_passphrase(name, passphrase, operation=None, element=None, tenant=False):
|
||||
"""Check a a login name and passphrase for authenticity and authorization
|
||||
|
||||
The function combines authentication and authorization into one function.
|
||||
@@ -160,12 +224,20 @@ 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
|
||||
if (user, tenant) in _passcache:
|
||||
if hashlib.sha256(passphrase).digest() == _passcache[(user, tenant)]:
|
||||
return authorize(user, element, tenant)
|
||||
return authorize(user, element, tenant, operation=operation)
|
||||
else:
|
||||
# In case of someone trying to guess,
|
||||
# while someone is legitimately logged in
|
||||
@@ -200,7 +272,7 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
# delay as well
|
||||
if crypt == crypted:
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant)
|
||||
return authorize(user, element, tenant, operation)
|
||||
try:
|
||||
pammy = PAM.pam()
|
||||
pammy.start(_pamservice, user, credobj.pam_conv)
|
||||
@@ -208,7 +280,7 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
pammy.acct_mgmt()
|
||||
del pammy
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant, skipuserobj=False)
|
||||
return authorize(user, element, tenant, operation, skipuserobj=False)
|
||||
except NameError:
|
||||
pass
|
||||
except PAM.error:
|
||||
|
||||
@@ -99,6 +99,7 @@ _attraliases = {
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
'switchpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
_validroles = ('Administrator', 'Operator', 'Monitor')
|
||||
|
||||
def _mkpath(pathname):
|
||||
try:
|
||||
@@ -169,6 +170,24 @@ def _do_notifier(cfg, watcher, callback):
|
||||
logException()
|
||||
|
||||
|
||||
|
||||
def _rpc_master_del_usergroup(tenant, name):
|
||||
ConfigManager(tenant).del_usergroup(name)
|
||||
|
||||
|
||||
def _rpc_del_usergroup(tenant, name):
|
||||
ConfigManager(tenant)._true_del_usergroup(name)
|
||||
|
||||
|
||||
|
||||
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 +239,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 +1211,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 +1254,46 @@ 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]
|
||||
if attribute == 'role':
|
||||
therole = None
|
||||
for candrole in _validroles:
|
||||
if candrole.lower().startswith(attributemap[attribute].lower()):
|
||||
therole = candrole
|
||||
if therole not in _validroles:
|
||||
raise ValueError(
|
||||
'Unrecognized role "{0}" (valid roles: {1})'.format(attributemap[attribute], ','.join(_validroles)))
|
||||
attributemap[attribute] = therole
|
||||
self._cfgstore['usergroups'][groupname][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 +1301,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)
|
||||
@@ -1249,6 +1332,15 @@ class ConfigManager(object):
|
||||
def _true_set_user(self, name, attributemap):
|
||||
user = self._cfgstore['users'][name]
|
||||
for attribute in attributemap:
|
||||
if attribute == 'role':
|
||||
therole = None
|
||||
for candrole in _validroles:
|
||||
if candrole.lower().startswith(attributemap[attribute].lower()):
|
||||
therole = candrole
|
||||
if therole not in _validroles:
|
||||
raise ValueError(
|
||||
'Unrecognized role "{0}" (valid roles: {1})'.format(attributemap[attribute], ','.join(_validroles)))
|
||||
attributemap[attribute] = therole
|
||||
if attribute == 'password':
|
||||
salt = os.urandom(8)
|
||||
#TODO: WORKERPOOL, offload password set to a worker
|
||||
@@ -1525,7 +1617,7 @@ class ConfigManager(object):
|
||||
del attribmap[group][attr]
|
||||
if 'noderange' in attribmap[group]:
|
||||
if len(attribmap[group]) > 1:
|
||||
raise ValueErorr('noderange attribute must be set by itself')
|
||||
raise ValueError('noderange attribute must be set by itself')
|
||||
for attr in attribmap[group]:
|
||||
if attr in _attraliases:
|
||||
newattr = _attraliases[attr]
|
||||
@@ -2077,6 +2169,9 @@ class ConfigManager(object):
|
||||
self.set_node_attributes(tmpconfig[confarea], True)
|
||||
elif confarea == 'nodegroups':
|
||||
self.set_group_attributes(tmpconfig[confarea], True)
|
||||
elif confarea == 'usergroups':
|
||||
for usergroup in tmpconfig[confarea]:
|
||||
self.create_usergroup(usergroup)
|
||||
elif confarea == 'users':
|
||||
for user in tmpconfig[confarea]:
|
||||
uid = tmpconfig[confarea].get('id', None)
|
||||
|
||||
@@ -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,13 +396,33 @@ 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(groupname, attribmap)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
|
||||
def update_user(name, attribmap, configmanager):
|
||||
try:
|
||||
configmanager.set_user(name, attribmap)
|
||||
except ValueError:
|
||||
raise exc.InvalidArgumentException()
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
|
||||
|
||||
def show_usergroup(groupname, configmanager):
|
||||
groupinfo = configmanager.get_usergroup(groupname)
|
||||
for attr in groupinfo:
|
||||
yield msg.Attributes(kv={attr: groupinfo[attr]})
|
||||
|
||||
def show_user(name, configmanager):
|
||||
userobj = configmanager.get_user(name)
|
||||
rv = {}
|
||||
@@ -419,6 +439,10 @@ def show_user(name, configmanager):
|
||||
rv[attr] = userobj[attr]
|
||||
yield msg.Attributes(kv={attr: rv[attr]},
|
||||
desc=attrscheme.user[attr]['description'])
|
||||
if 'role' in userobj:
|
||||
yield msg.Attributes(kv={'role': userobj['role']})
|
||||
|
||||
|
||||
|
||||
|
||||
def stripnode(iterablersp, node):
|
||||
@@ -451,6 +475,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 +1033,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
|
||||
|
||||
@@ -269,6 +269,9 @@ def _authorize_request(env, operation):
|
||||
name = ''
|
||||
sessionid = None
|
||||
cookie = Cookie.SimpleCookie()
|
||||
element = env['PATH_INFO']
|
||||
if element.startswith('/sessions/current/'):
|
||||
element = None
|
||||
if 'HTTP_COOKIE' in env:
|
||||
#attempt to use the cookie. If it matches
|
||||
cc = RobustCookie()
|
||||
@@ -290,7 +293,7 @@ def _authorize_request(env, operation):
|
||||
httpsessions[sessionid]['expiry'] = time.time() + 90
|
||||
name = httpsessions[sessionid]['name']
|
||||
authdata = auth.authorize(
|
||||
name, element=None,
|
||||
name, element=element, operation=operation,
|
||||
skipuserobj=httpsessions[sessionid]['skipuserobject'])
|
||||
if (not authdata) and 'HTTP_AUTHORIZATION' in env:
|
||||
if env['PATH_INFO'] == '/sessions/current/logout':
|
||||
@@ -303,8 +306,10 @@ def _authorize_request(env, operation):
|
||||
return ('logout',)
|
||||
name, passphrase = base64.b64decode(
|
||||
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
|
||||
authdata = auth.check_user_passphrase(name, passphrase, element=None)
|
||||
if not authdata:
|
||||
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
|
||||
if authdata is False:
|
||||
return {'code': 403}
|
||||
elif not authdata:
|
||||
return {'code': 401}
|
||||
sessid = util.randomstring(32)
|
||||
while sessid in httpsessions:
|
||||
@@ -341,15 +346,10 @@ def _authorize_request(env, operation):
|
||||
if 'csrftoken' in httpsessions[sessid]:
|
||||
authinfo['authtoken'] = httpsessions[sessid]['csrftoken']
|
||||
return authinfo
|
||||
else:
|
||||
elif authdata is None:
|
||||
return {'code': 401}
|
||||
# TODO(jbjohnso): actually evaluate the request for authorization
|
||||
# In theory, the x509 or http auth stuff will get translated and then
|
||||
# passed on to the core authorization function in an appropriate form
|
||||
# expresses return in the form of http code
|
||||
# 401 if there is no known identity
|
||||
# 403 if valid identity, but no access
|
||||
# going to run 200 just to get going for now
|
||||
else:
|
||||
return {'code': 403}
|
||||
|
||||
|
||||
def _pick_mimetype(env):
|
||||
@@ -429,7 +429,7 @@ def resourcehandler_backend(env, start_response):
|
||||
return
|
||||
if authorized['code'] == 403:
|
||||
start_response('403 Forbidden', badauth)
|
||||
yield 'authorization failed'
|
||||
yield 'Forbidden'
|
||||
return
|
||||
if authorized['code'] != 200:
|
||||
raise Exception("Unrecognized code from auth engine")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -120,13 +120,15 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
|
||||
cfm = configmanager.ConfigManager(tenant=None, username=authname)
|
||||
elif authname:
|
||||
authdata = auth.authorize(authname, element=None)
|
||||
if authdata is not None:
|
||||
if authdata:
|
||||
cfm = authdata[1]
|
||||
authenticated = True
|
||||
send_data(connection, "Confluent -- v0 --")
|
||||
while not authenticated: # prompt for name and passphrase
|
||||
send_data(connection, {'authpassed': 0})
|
||||
response = tlvdata.recv(connection)
|
||||
if not response:
|
||||
return
|
||||
if 'collective' in response:
|
||||
return collective.handle_connection(connection, cert,
|
||||
response['collective'])
|
||||
@@ -143,7 +145,7 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
|
||||
# element path, that authorization will need to be called
|
||||
# per request the user makes
|
||||
authdata = auth.check_user_passphrase(authname, passphrase)
|
||||
if authdata is None:
|
||||
if not authdata:
|
||||
auditlog.log(
|
||||
{'operation': 'connect', 'user': authname, 'allowed': False})
|
||||
else:
|
||||
@@ -210,7 +212,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
}
|
||||
if not skipauth:
|
||||
authdata = auth.authorize(authdata[2], path, authdata[3], operation)
|
||||
if authdata is None:
|
||||
if not authdata:
|
||||
auditmsg['allowed'] = False
|
||||
auditlog.log(auditmsg)
|
||||
raise exc.ForbiddenRequest()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from ctypes import *
|
||||
from ctypes.util import find_library
|
||||
import grp
|
||||
import pwd
|
||||
import os
|
||||
libc = cdll.LoadLibrary(find_library('libc'))
|
||||
_getgrouplist = libc.getgrouplist
|
||||
_getgrouplist.restype = c_int32
|
||||
|
||||
|
||||
class TooSmallException(Exception):
|
||||
def __init__(self, count):
|
||||
self.count = count
|
||||
super(TooSmallException, self).__init__()
|
||||
|
||||
|
||||
def getgrouplist(name, gid, ng=32):
|
||||
_getgrouplist.argtypes = [c_char_p, c_uint, POINTER(c_uint * ng), POINTER(c_int)]
|
||||
glist = (c_uint * ng)()
|
||||
nglist = c_int(ng)
|
||||
count = _getgrouplist(name, gid, byref(glist), byref(nglist))
|
||||
if count < 0:
|
||||
raise TooSmallException(nglist.value)
|
||||
for gidx in range(count):
|
||||
gent = glist[gidx]
|
||||
yield grp.getgrgid(gent).gr_name
|
||||
|
||||
|
||||
def grouplist(username):
|
||||
pent = pwd.getpwnam(username)
|
||||
try:
|
||||
groups = getgrouplist(pent.pw_name, pent.pw_gid)
|
||||
except TooSmallException as e:
|
||||
groups = getgrouplist(pent.pw_name, pent.pw_gid, e.count)
|
||||
return list(groups)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
print(repr(grouplist(sys.argv[1])))
|
||||
|
||||
Reference in New Issue
Block a user