[Freeipa-devel] [PATCH] add group mgmt to useredit page

Kevin McCarthy kmccarth at redhat.com
Fri Sep 28 23:46:32 UTC 2007


This patch adds group management to the user edit page.  This allows you
to manage "the groups a user is in" in the same way as you manage "the
users/groups in a group".

It's pushed to demo, so feel free to take a peek.

Still need to add this to the usernew page.

-Kevin

-------------- next part --------------
# HG changeset patch
# User Kevin McCarthy <kmccarth at redhat.com>
# Date 1191020502 25200
# Node ID 0a3bb27f723b0d70253613460b0d266aa1acb36a
# Parent  2a638a7907c4ec9c5b2727ffc2ac787ec4960b01
Add group management to the user edit page.
Added a couple more API calls to make the inverse operations easier.

diff -r 2a638a7907c4 -r 0a3bb27f723b ipa-python/ipaclient.py
--- a/ipa-python/ipaclient.py	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-python/ipaclient.py	Fri Sep 28 16:01:42 2007 -0700
@@ -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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-python/rpcclient.py
--- a/ipa-python/rpcclient.py	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-python/rpcclient.py	Fri Sep 28 16:01:42 2007 -0700
@@ -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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-server/ipa-gui/ipagui/controllers.py
--- a/ipa-server/ipa-gui/ipagui/controllers.py	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/controllers.py	Fri Sep 28 16:01:42 2007 -0700
@@ -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!")
 
-        try:
-            client.set_krbccache(os.environ["KRB5CCNAME"])
+        client.set_krbccache(os.environ["KRB5CCNAME"])
+        try:
             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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-server/ipa-gui/ipagui/forms/user.py
--- a/ipa-server/ipa-gui/ipagui/forms/user.py	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/forms/user.py	Fri Sep 28 16:01:42 2007 -0700
@@ -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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-server/ipa-gui/ipagui/templates/useredit.kid
--- a/ipa-server/ipa-gui/ipagui/templates/useredit.kid	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid	Fri Sep 28 16:01:42 2007 -0700
@@ -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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-server/ipa-gui/ipagui/templates/usereditform.kid
--- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid	Fri Sep 28 16:01:42 2007 -0700
@@ -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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-server/xmlrpc-server/funcs.py
--- a/ipa-server/xmlrpc-server/funcs.py	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-server/xmlrpc-server/funcs.py	Fri Sep 28 16:01:42 2007 -0700
@@ -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 -r 2a638a7907c4 -r 0a3bb27f723b ipa-server/xmlrpc-server/ipaxmlrpc.py
--- a/ipa-server/xmlrpc-server/ipaxmlrpc.py	Fri Sep 28 14:29:12 2007 -0700
+++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py	Fri Sep 28 16:01:42 2007 -0700
@@ -339,6 +339,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)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 4054 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20070928/6ac3efd0/attachment.bin>


More information about the Freeipa-devel mailing list