mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add group management to the user edit page.
Added a couple more API calls to make the inverse operations easier.
This commit is contained in:
parent
0cfccd0f8c
commit
dbf8c1aeb9
@ -266,6 +266,21 @@ class IPAClient:
|
||||
|
||||
return self.transport.remove_users_from_group(user_uids, group_cn)
|
||||
|
||||
def add_groups_to_user(self, group_dns, user_dn):
|
||||
"""Given a list of group dn's add them to the user.
|
||||
|
||||
Returns a list of the group dns that were not added.
|
||||
"""
|
||||
return self.transport.add_groups_to_user(group_dns, user_dn)
|
||||
|
||||
def remove_groups_from_user(self, group_dns, user_dn):
|
||||
"""Given a list of group dn's remove them from the user.
|
||||
|
||||
Returns a list of the group dns that were not removed.
|
||||
"""
|
||||
|
||||
return self.transport.remove_groups_from_user(group_dns, user_dn)
|
||||
|
||||
def update_group(self,group):
|
||||
"""Update a group entry."""
|
||||
|
||||
|
@ -441,6 +441,36 @@ class RPCClient:
|
||||
|
||||
return ipautil.unwrap_binary_data(result)
|
||||
|
||||
def add_groups_to_user(self, group_dns, user_dn):
|
||||
"""Given a list of group dn's add them to the user.
|
||||
|
||||
Returns a list of the group dns that were not added.
|
||||
"""
|
||||
server = self.setup_server()
|
||||
try:
|
||||
result = server.add_groups_to_user(group_dns, user_dn)
|
||||
except xmlrpclib.Fault, fault:
|
||||
raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
|
||||
except socket.error, (value, msg):
|
||||
raise xmlrpclib.Fault(value, msg)
|
||||
|
||||
return ipautil.unwrap_binary_data(result)
|
||||
|
||||
def remove_groups_from_user(self, group_dns, user_dn):
|
||||
"""Given a list of group dn's remove them from the user.
|
||||
|
||||
Returns a list of the group dns that were not removed.
|
||||
"""
|
||||
server = self.setup_server()
|
||||
try:
|
||||
result = server.remove_groups_from_user(group_dns, user_dn)
|
||||
except xmlrpclib.Fault, fault:
|
||||
raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
|
||||
except socket.error, (value, msg):
|
||||
raise xmlrpclib.Fault(value, msg)
|
||||
|
||||
return ipautil.unwrap_binary_data(result)
|
||||
|
||||
def update_group(self,oldgroup,newgroup):
|
||||
"""Update an existing group. oldgroup and newgroup are dicts of attributes"""
|
||||
server = self.setup_server()
|
||||
|
@ -79,6 +79,15 @@ def sort_group_member(a, b):
|
||||
else:
|
||||
return 1
|
||||
|
||||
def sort_by_cn(a, b):
|
||||
"""Comparator function used for sorting groups."""
|
||||
if a.get('cn', '') == b.get('cn', ''):
|
||||
return 0
|
||||
elif a.get('cn', '') < b.get('cn', ''):
|
||||
return -1
|
||||
else:
|
||||
return 1
|
||||
|
||||
class Root(controllers.RootController):
|
||||
|
||||
@expose(template="ipagui.templates.welcome")
|
||||
@ -144,6 +153,28 @@ class Root(controllers.RootController):
|
||||
turbogears.flash("User add failed: " + str(e))
|
||||
return dict(form=user_new_form, tg_template='ipagui.templates.usernew')
|
||||
|
||||
@expose("ipagui.templates.dynamiceditsearch")
|
||||
@identity.require(identity.not_anonymous())
|
||||
def useredit_search(self, **kw):
|
||||
"""Searches for groups and displays list of results in a table.
|
||||
This method is used for the ajax search on the user edit page."""
|
||||
client.set_krbccache(os.environ["KRB5CCNAME"])
|
||||
groups = []
|
||||
counter = 0
|
||||
searchlimit = 100
|
||||
criteria = kw.get('criteria')
|
||||
if criteria != None and len(criteria) > 0:
|
||||
try:
|
||||
groups = client.find_groups(criteria.encode('utf-8'), None,
|
||||
searchlimit)
|
||||
groups_counter = groups[0]
|
||||
groups = groups[1:]
|
||||
except ipaerror.IPAError, e:
|
||||
turbogears.flash("search failed: " + str(e))
|
||||
|
||||
return dict(users=None, groups=groups, criteria=criteria,
|
||||
counter=groups_counter)
|
||||
|
||||
|
||||
@expose("ipagui.templates.useredit")
|
||||
@identity.require(identity.not_anonymous())
|
||||
@ -152,18 +183,26 @@ class Root(controllers.RootController):
|
||||
if tg_errors:
|
||||
turbogears.flash("There was a problem with the form!")
|
||||
|
||||
client.set_krbccache(os.environ["KRB5CCNAME"])
|
||||
try:
|
||||
client.set_krbccache(os.environ["KRB5CCNAME"])
|
||||
user = client.get_user_by_uid(uid, user_fields)
|
||||
user_dict = user.toDict()
|
||||
# Edit shouldn't fill in the password field.
|
||||
if user_dict.has_key('userpassword'):
|
||||
del(user_dict['userpassword'])
|
||||
|
||||
user_groups = client.get_groups_by_member(user.dn, ['dn', 'cn'])
|
||||
user_groups_dicts = map(lambda group: group.toDict(), user_groups)
|
||||
user_groups_dicts.sort(sort_by_cn)
|
||||
user_groups_data = b64encode(dumps(user_groups_dicts))
|
||||
|
||||
# store a copy of the original user for the update later
|
||||
user_data = b64encode(dumps(user_dict))
|
||||
user_dict['user_orig'] = user_data
|
||||
return dict(form=user_edit_form, user=user_dict)
|
||||
user_dict['user_groups_data'] = user_groups_data
|
||||
|
||||
return dict(form=user_edit_form, user=user_dict,
|
||||
user_groups=user_groups_dicts)
|
||||
except ipaerror.IPAError, e:
|
||||
turbogears.flash("User edit failed: " + str(e))
|
||||
raise turbogears.redirect('/usershow', uid=kw.get('uid'))
|
||||
@ -178,12 +217,20 @@ class Root(controllers.RootController):
|
||||
turbogears.flash("Edit user cancelled")
|
||||
raise turbogears.redirect('/usershow', uid=kw.get('uid'))
|
||||
|
||||
# Decode the group data, in case we need to round trip
|
||||
user_groups_dicts = loads(b64decode(kw.get('user_groups_data')))
|
||||
|
||||
tg_errors, kw = self.userupdatevalidate(**kw)
|
||||
if tg_errors:
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
user_groups=user_groups_dicts,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
|
||||
password_change = False
|
||||
|
||||
#
|
||||
# Update the user itself
|
||||
#
|
||||
try:
|
||||
orig_user_dict = loads(b64decode(kw.get('user_orig')))
|
||||
|
||||
@ -210,22 +257,77 @@ class Root(controllers.RootController):
|
||||
new_user.getValue('sn')))
|
||||
|
||||
rv = client.update_user(new_user)
|
||||
#
|
||||
# If the user update succeeds, but below operations fail, we
|
||||
# need to make sure a subsequent submit doesn't try to update
|
||||
# the user again.
|
||||
#
|
||||
kw['user_orig'] = b64encode(dumps(new_user.toDict()))
|
||||
except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST), e:
|
||||
if not password_change:
|
||||
turbogears.flash("User update failed: " + str(e))
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
# could be a password change
|
||||
# could be groups change
|
||||
# too much work to figure out unless someone really screams
|
||||
pass
|
||||
except ipaerror.IPAError, e:
|
||||
turbogears.flash("User update failed: " + str(e))
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
user_groups=user_groups_dicts,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
|
||||
#
|
||||
# Password change
|
||||
#
|
||||
try:
|
||||
if password_change:
|
||||
rv = client.modifyPassword(kw['uid'], "", kw.get('userpassword'))
|
||||
except ipaerror.IPAError, e:
|
||||
turbogears.flash("User password change failed: " + str(e))
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
user_groups=user_groups_dicts,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
|
||||
#
|
||||
# Add groups
|
||||
#
|
||||
failed_adds = []
|
||||
try:
|
||||
dnadds = kw.get('dnadd')
|
||||
if dnadds != None:
|
||||
if not(isinstance(dnadds,list) or isinstance(dnadds,tuple)):
|
||||
dnadds = [dnadds]
|
||||
failed_adds = client.add_groups_to_user(
|
||||
utf8_encode_values(dnadds), new_user.dn)
|
||||
kw['dnadd'] = failed_adds
|
||||
except ipaerror.IPAError, e:
|
||||
turbogears.flash("Group update failed: " + str(e))
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
user_groups=user_groups_dicts,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
|
||||
#
|
||||
# Remove groups
|
||||
#
|
||||
failed_dels = []
|
||||
try:
|
||||
dndels = kw.get('dndel')
|
||||
if dndels != None:
|
||||
if not(isinstance(dndels,list) or isinstance(dndels,tuple)):
|
||||
dndels = [dndels]
|
||||
failed_dels = client.remove_groups_from_user(
|
||||
utf8_encode_values(dndels), new_user.dn)
|
||||
kw['dndel'] = failed_dels
|
||||
except ipaerror.IPAError, e:
|
||||
turbogears.flash("Group update failed: " + str(e))
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
user_groups=user_groups_dicts,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
|
||||
if (len(failed_adds) > 0) or (len(failed_dels) > 0):
|
||||
message = "There was an error updating groups.<br />"
|
||||
message += "Failures have been preserved in the add/remove lists."
|
||||
turbogears.flash(message)
|
||||
return dict(form=user_edit_form, user=kw,
|
||||
user_groups=user_groups_dicts,
|
||||
tg_template='ipagui.templates.useredit')
|
||||
|
||||
turbogears.flash("%s updated!" % kw['uid'])
|
||||
|
@ -24,6 +24,8 @@ class UserFields():
|
||||
editprotected_hidden = widgets.HiddenField(name="editprotected")
|
||||
|
||||
user_orig = widgets.HiddenField(name="user_orig")
|
||||
user_groups_data = widgets.HiddenField(name="user_groups_data")
|
||||
dn_to_info_json = widgets.HiddenField(name="dn_to_info_json")
|
||||
|
||||
class UserNewValidator(validators.Schema):
|
||||
uid = validators.PlainText(not_empty=True)
|
||||
@ -88,6 +90,8 @@ class UserEditForm(widgets.Form):
|
||||
UserFields.uidnumber, UserFields.gidnumber,
|
||||
UserFields.krbPasswordExpiration_hidden,
|
||||
UserFields.editprotected_hidden,
|
||||
UserFields.user_groups_data,
|
||||
UserFields.dn_to_info_json,
|
||||
]
|
||||
|
||||
validator = UserEditValidator()
|
||||
|
@ -35,6 +35,6 @@ else:
|
||||
Password has expired
|
||||
</div>
|
||||
|
||||
${form.display(action="userupdate", value=user)}
|
||||
${form.display(action="userupdate", value=user, user_groups=user_groups)}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,6 +1,16 @@
|
||||
<div xmlns:py="http://purl.org/kid/ns#"
|
||||
class="simpleroster">
|
||||
<form action="${action}" name="${name}" method="${method}" class="tableform">
|
||||
<form action="${action}" name="${name}" method="${method}" class="tableform"
|
||||
onsubmit="preSubmit()">
|
||||
|
||||
<?python
|
||||
from ipagui.helpers import ipahelper
|
||||
?>
|
||||
|
||||
<script type="text/javascript" charset="utf-8"
|
||||
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
|
||||
|
||||
<?python searchurl = tg.url('/useredit_search') ?>
|
||||
|
||||
<script type="text/javascript">
|
||||
function toggleProtectedFields(checkbox) {
|
||||
@ -22,6 +32,40 @@
|
||||
$('form_editprotected').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function enterDoSearch(e) {
|
||||
var keyPressed;
|
||||
if (window.event) {
|
||||
keyPressed = window.event.keyCode;
|
||||
} else {
|
||||
keyPressed = e.which;
|
||||
}
|
||||
|
||||
if (keyPressed == 13) {
|
||||
return doSearch();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
$('searchresults').update("Searching...");
|
||||
new Ajax.Updater('searchresults',
|
||||
'${searchurl}',
|
||||
{ asynchronous:true,
|
||||
parameters: { criteria: $('criteria').value },
|
||||
evalScripts: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
// override dynamicedit.js version
|
||||
// we don't need to show [group] nor italize groups
|
||||
function renderMemberInfo(newdiv, info) {
|
||||
if (info.type == "group") {
|
||||
newdiv.appendChild(document.createTextNode(
|
||||
info.name.escapeHTML() + " "));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -213,6 +257,81 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<div class="formsection">Groups</div>
|
||||
|
||||
<div class="floatlist">
|
||||
<div class="floatheader">To Remove:</div>
|
||||
<div id="delmembers">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<?python div_counter = 1 ?>
|
||||
<div py:for="group in user_groups" id="member-${div_counter}">
|
||||
<?python
|
||||
group_dn = group.get('dn')
|
||||
group_dn_esc = ipahelper.javascript_string_escape(group_dn)
|
||||
|
||||
group_name = group.get('cn')
|
||||
group_descr = "[group]"
|
||||
group_type = "group"
|
||||
|
||||
group_name_esc = ipahelper.javascript_string_escape(group_name)
|
||||
group_descr_esc = ipahelper.javascript_string_escape(group_descr)
|
||||
group_type_esc = ipahelper.javascript_string_escape(group_type)
|
||||
?>
|
||||
<span id="member-info-${div_counter}"></span>
|
||||
<script type="text/javascript">
|
||||
renderMemberInfo($('member-info-${div_counter}'),
|
||||
new MemberDisplayInfo('${group_name_esc}',
|
||||
'${group_descr_esc}',
|
||||
'${group_type_esc}'));
|
||||
</script>
|
||||
<a href="#"
|
||||
onclick="removememberHandler(this, '${group_dn_esc}',
|
||||
new MemberDisplayInfo('${group_name_esc}',
|
||||
'${group_descr_esc}',
|
||||
'${group_type_esc}'));
|
||||
return false;"
|
||||
>remove</a>
|
||||
<script type="text/javascript">
|
||||
dn_to_member_div_id['${group_dn_esc}'] = "member-${div_counter}";
|
||||
member_hash["${group_dn_esc}"] = 1;
|
||||
</script>
|
||||
<?python
|
||||
div_counter = div_counter + 1
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="clear:both">
|
||||
<div class="formsection">Add Groups</div>
|
||||
|
||||
<div class="floatlist">
|
||||
<div class="floatheader">To Add:</div>
|
||||
<div id="newmembers">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div id="search">
|
||||
<input id="criteria" type="text" name="criteria"
|
||||
onkeypress="return enterDoSearch(event);" />
|
||||
<input type="button" value="Find"
|
||||
onclick="return doSearch();"
|
||||
/>
|
||||
</div>
|
||||
<div id="searchresults">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<th>
|
||||
@ -232,9 +351,52 @@
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
/*
|
||||
* This section restores the contents of the add and remove lists
|
||||
* dynamically if we have to refresh the page
|
||||
*/
|
||||
if ($('form_dn_to_info_json').value != "") {
|
||||
dn_to_info_hash = new Hash($('form_dn_to_info_json').value.evalJSON());
|
||||
}
|
||||
|
||||
if ($('form_editprotected').value != "") {
|
||||
$('toggleprotected_checkbox').checked = true;
|
||||
toggleProtectedFields($('toggleprotected_checkbox'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<?python
|
||||
dnadds = value.get('dnadd', [])
|
||||
if not(isinstance(dnadds,list) or isinstance(dnadds,tuple)):
|
||||
dnadds = [dnadds]
|
||||
|
||||
dndels = value.get('dndel', [])
|
||||
if not(isinstance(dndels,list) or isinstance(dndels,tuple)):
|
||||
dndels = [dndels]
|
||||
?>
|
||||
|
||||
<script py:for="dnadd in dnadds">
|
||||
<?python
|
||||
dnadd_esc = ipahelper.javascript_string_escape(dnadd)
|
||||
?>
|
||||
var dn = "${dnadd_esc}";
|
||||
var info = dn_to_info_hash[dn];
|
||||
var newdiv = addmember(dn, info);
|
||||
if (newdiv != null) {
|
||||
newdiv.style.display = 'block';
|
||||
}
|
||||
</script>
|
||||
|
||||
<script py:for="dndel in dndels">
|
||||
<?python
|
||||
dndel_esc = ipahelper.javascript_string_escape(dndel)
|
||||
?>
|
||||
var dn = "${dndel_esc}";
|
||||
var info = dn_to_info_hash[dn];
|
||||
var newdiv = removemember(dn, info);
|
||||
newdiv.style.display = 'block';
|
||||
orig_div_id = dn_to_member_div_id[dn]
|
||||
$(orig_div_id).style.display = 'none';
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
@ -899,6 +899,56 @@ class IPAServer:
|
||||
|
||||
return failed
|
||||
|
||||
def add_groups_to_user(self, group_dns, user_dn, opts=None):
|
||||
"""Given a list of group dn's add them to the user.
|
||||
|
||||
Returns a list of the group dns that were not added.
|
||||
"""
|
||||
|
||||
failed = []
|
||||
|
||||
if (isinstance(group_dns, str)):
|
||||
group_dns = [group_dns]
|
||||
|
||||
for group_dn in group_dns:
|
||||
# TODO - change add_member_to_group to take a group_dn
|
||||
try:
|
||||
group = self.get_group_by_dn(group_dn, ['cn'], opts)
|
||||
self.add_member_to_group(user_dn, group.get('cn'), opts)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST):
|
||||
# User is already in the group
|
||||
failed.append(group_dn)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
|
||||
# User or the group does not exist
|
||||
failed.append(group_dn)
|
||||
|
||||
return failed
|
||||
|
||||
def remove_groups_from_user(self, group_dns, user_dn, opts=None):
|
||||
"""Given a list of group dn's remove them from the user.
|
||||
|
||||
Returns a list of the group dns that were not removed.
|
||||
"""
|
||||
|
||||
failed = []
|
||||
|
||||
if (isinstance(group_dns, str)):
|
||||
group_dns = [group_dns]
|
||||
|
||||
for group_dn in group_dns:
|
||||
# TODO - change remove_member_from_group to take a group_dn
|
||||
try:
|
||||
group = self.get_group_by_dn(group_dn, ['cn'], opts)
|
||||
self.remove_member_from_group(user_dn, group.get('cn'), opts)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST):
|
||||
# User is not in the group
|
||||
failed.append(group_dn)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
|
||||
# User or the group does not exist
|
||||
failed.append(group_dn)
|
||||
|
||||
return failed
|
||||
|
||||
def update_group (self, oldgroup, newgroup, opts=None):
|
||||
"""Update a group in LDAP"""
|
||||
return self.__update_entry(oldgroup, newgroup, opts)
|
||||
|
@ -342,6 +342,8 @@ def handler(req, profiling=False):
|
||||
h.register_function(f.add_group_to_group)
|
||||
h.register_function(f.remove_user_from_group)
|
||||
h.register_function(f.remove_users_from_group)
|
||||
h.register_function(f.add_groups_to_user)
|
||||
h.register_function(f.remove_groups_from_user)
|
||||
h.register_function(f.update_group)
|
||||
h.register_function(f.delete_group)
|
||||
h.handle_request(req)
|
||||
|
Loading…
Reference in New Issue
Block a user