summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipa-python/ipaclient.py15
-rw-r--r--ipa-python/rpcclient.py30
-rw-r--r--ipa-server/ipa-gui/ipagui/controllers.py114
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/user.py4
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/useredit.kid2
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usereditform.kid164
-rw-r--r--ipa-server/xmlrpc-server/funcs.py50
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py2
8 files changed, 373 insertions, 8 deletions
diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py
index d047f7717..27ad1c246 100644
--- a/ipa-python/ipaclient.py
+++ b/ipa-python/ipaclient.py
@@ -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."""
diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py
index cfeb3d20c..9f02b374f 100644
--- a/ipa-python/rpcclient.py
+++ b/ipa-python/rpcclient.py
@@ -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()
diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py
index 45a94e023..c770f5c02 100644
--- a/ipa-server/ipa-gui/ipagui/controllers.py
+++ b/ipa-server/ipa-gui/ipagui/controllers.py
@@ -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'])
diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py
index 037205a17..561c7ef18 100644
--- a/ipa-server/ipa-gui/ipagui/forms/user.py
+++ b/ipa-server/ipa-gui/ipagui/forms/user.py
@@ -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()
diff --git a/ipa-server/ipa-gui/ipagui/templates/useredit.kid b/ipa-server/ipa-gui/ipagui/templates/useredit.kid
index b5e8447d2..e4d492b0a 100644
--- a/ipa-server/ipa-gui/ipagui/templates/useredit.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid
@@ -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>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
index 3f7db50d7..36c65fb16 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
@@ -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>
diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py
index 838f05f12..e4e2f40e2 100644
--- a/ipa-server/xmlrpc-server/funcs.py
+++ b/ipa-server/xmlrpc-server/funcs.py
@@ -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)
diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py
index 69b2740fc..96d9299c2 100644
--- a/ipa-server/xmlrpc-server/ipaxmlrpc.py
+++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py
@@ -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)