diff options
-rw-r--r-- | ipa-python/ipaclient.py | 15 | ||||
-rw-r--r-- | ipa-python/rpcclient.py | 30 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/controllers.py | 114 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/forms/user.py | 4 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/useredit.kid | 2 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/usereditform.kid | 164 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 50 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 2 |
8 files changed, 373 insertions, 8 deletions
diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index d047f771..27ad1c24 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 cfeb3d20..9f02b374 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 45a94e02..c770f5c0 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 037205a1..561c7ef1 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 b5e8447d..e4d492b0 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 3f7db50d..36c65fb1 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 838f05f1..e4e2f40e 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 69b2740f..96d9299c 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) |