diff options
23 files changed, 420 insertions, 145 deletions
diff --git a/ipa-admintools/ipa-delgroup b/ipa-admintools/ipa-delgroup new file mode 100644 index 000000000..50967a49b --- /dev/null +++ b/ipa-admintools/ipa-delgroup @@ -0,0 +1,69 @@ +#! /usr/bin/python -E +# Authors: Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +from optparse import OptionParser +import ipa +import ipa.ipaclient as ipaclient +import ipa.config + +import xmlrpclib +import kerberos + +def usage(): + print "ipa-delgroup group" + sys.exit(1) + +def parse_options(): + parser = OptionParser() + parser.add_option("--usage", action="store_true", + help="Program usage") + + args = ipa.config.init_config(sys.argv) + options, args = parser.parse_args(args) + + return options, args + +def main(): + options, args = parse_options() + + if len(args) != 2: + usage() + + try: + client = ipaclient.IPAClient() + ret = client.delete_group(args[1]) + if (ret == "Success"): + print args[1] + " successfully deleted" + else: + print args[1] + " " + ret + except xmlrpclib.Fault, f: + print f.faultString + return 1 + except kerberos.GSSError, e: + print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) + return 1 + except xmlrpclib.ProtocolError, e: + print "Unable to connect to IPA server: %s" % (e.errmsg) + return 1 + + return 0 + +main() diff --git a/ipa-admintools/ipa-deluser b/ipa-admintools/ipa-deluser index 10d248062..bcee2afd9 100644 --- a/ipa-admintools/ipa-deluser +++ b/ipa-admintools/ipa-deluser @@ -28,7 +28,7 @@ import xmlrpclib import kerberos def usage(): - print "ipa-adduser user" + print "ipa-deluser user" sys.exit(1) def parse_options(): diff --git a/ipa-admintools/ipa-groupmod b/ipa-admintools/ipa-groupmod index eea96b43a..f3de92637 100644 --- a/ipa-admintools/ipa-groupmod +++ b/ipa-admintools/ipa-groupmod @@ -58,7 +58,6 @@ def main(): group=ipa.group.Group() options, args = parse_options() - print "len = ", len(args) if (options.add or options.remove) and (len(args) != 3): usage() if (options.desc and (len(args) != 2)): @@ -68,7 +67,7 @@ def main(): client = ipaclient.IPAClient() if options.add: client.add_user_to_group(args[1], args[2]) - print args[1] + " successfully added" + print args[1] + " successfully added to " + args[2] elif options.remove: client.remove_user_from_group(args[1], args[2]) print args[1] + " successfully removed" diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index 71def70fd..fcfb29f1d 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -94,12 +94,14 @@ class IPAClient: return result def find_users(self, criteria, sattrs=None): - """Find users whose uid matches the criteria. Wildcards are - acceptable. Returns a list of User objects.""" + """Return a list: counter followed by a User object for each user that + matches the criteria. If the results are truncated, counter will + be set to -1""" result = self.transport.find_users(criteria, sattrs) + counter = result[0] - users = [] - for attrs in result: + users = [counter] + for attrs in result[1:]: if attrs is not None: users.append(user.User(attrs)) @@ -113,6 +115,14 @@ class IPAClient: result = self.transport.update_user(user.origDataDict(), user.toDict()) return result + def delete_user(self,uid): + """Delete a user entry.""" + + realm = config.config.get_realm() + + result = self.transport.delete_user(uid) + return result + def mark_user_deleted(self,uid): """Set a user as inactive by uid.""" @@ -202,7 +212,17 @@ class IPAClient: def update_group(self,group): """Update a group entry.""" - realm = config.config.get_realm() + return self.transport.update_group(group.origDataDict(), group.toDict()) - result = self.transport.update_group(group.origDataDict(), group.toDict()) - return result + def delete_group(self,group_cn): + """Delete a group entry.""" + + return self.transport.delete_group(group_cn) + + def add_group_to_group(self, group_cn, tgroup_cn): + """Add a group to an existing group. + group_cn is a cn of the group to add + tgroup_cn is the cn of the group to be added to + """ + + return self.transport.add_group_to_group(group_cn, tgroup_cn) diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index 74f7cfff6..be6b037fe 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -25,9 +25,10 @@ class CIDict(dict): """ Case-insensitive but case-respecting dictionary. - Idea from python-ldap cidict, however this version extends 'dict' - so it works properly with TurboGears. + This code is derived from python-ldap's cidict.py module, + written by stroeder: http://python-ldap.sourceforge.net/ + This version extends 'dict' so it works properly with TurboGears. If you extend UserDict, isinstance(foo, dict) returns false. """ diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index 3e5bb113a..e0d6e2ee7 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -151,8 +151,9 @@ class RPCClient: return ipautil.unwrap_binary_data(result) def find_users (self, criteria, sattrs=None): - """Return a list containing a User object for each user that matches - the criteria.""" + """Return a list: counter followed by a User object for each user that + matches the criteria. If the results are truncated, counter will + be set to -1""" server = self.setup_server() try: @@ -181,6 +182,19 @@ class RPCClient: return ipautil.unwrap_binary_data(result) + def delete_user(self,uid): + """Delete a user. uid is the uid of the user to delete.""" + server = self.setup_server() + + try: + result = server.delete_user(uid) + except xmlrpclib.Fault, fault: + raise ipaerror.gen_exception(fault.faultCode, fault.faultString) + except socket.error, (value, msg): + raise xmlrpclib.Fault(value, msg) + + return result + def mark_user_deleted(self,uid): """Mark a user as deleted/inactive""" server = self.setup_server() @@ -344,3 +358,31 @@ class RPCClient: raise xmlrpclib.Fault(value, msg) return ipautil.unwrap_binary_data(result) + + def delete_group(self,group_cn): + """Delete a group. group_cn is the cn of the group to be deleted.""" + server = self.setup_server() + + try: + result = server.delete_group(group_cn) + 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 add_group_to_group(self, group_cn, tgroup_cn): + """Add a group to an existing group. + group_cn is a cn of the group to add + tgroup_cn is the cn of the group to be added to + """ + server = self.setup_server() + try: + result = server.add_group_to_group(group_cn, tgroup_cn) + 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) diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py index 7dff9c908..d767f2686 100644 --- a/ipa-server/ipa-gui/ipagui/controllers.py +++ b/ipa-server/ipa-gui/ipagui/controllers.py @@ -140,15 +140,21 @@ class Root(controllers.RootController): def userlist(self, **kw): """Retrieve a list of all users and display them in one huge list""" users = None + counter = 0 uid = kw.get('uid') if uid != None and len(uid) > 0: try: users = client.find_users(uid.encode('utf-8')) + counter = users[0] + users = users[1:] + if counter == -1: + turbogears.flash("These results are truncated.<br />" + + "Please refine your search and try again.") except ipaerror.IPAError, e: - turbogears.flash("User show failed: " + str(e)) + turbogears.flash("User list failed: " + str(e)) raise turbogears.redirect("/userlist") - return dict(users=users, fields=forms.user.UserFields()) + return dict(users=users, uid=uid, fields=forms.user.UserFields()) @expose("ipagui.templates.usershow") diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css index b5bd82807..3c260c21b 100644 --- a/ipa-server/ipa-gui/ipagui/static/css/style.css +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css @@ -33,7 +33,8 @@ body { #nav { background:#cc0000; color:#fff; - padding:5px; + min-height:3px; + max-height:3px; } #nav ul { @@ -58,15 +59,14 @@ body { background:#fff; float:right; width:85%; + min-height:500px; border-left: 1px solid #000; - padding-left: 15px; - padding-bottom: 15px; -/* color: black; - font-size: 127%; - background-color: white; - margin: 0 auto 0 auto; padding: 10px; - float: left; */ +} + +#main_content h1,h2 { + margin-top: 0px; + margin-bottom: 5px; } @@ -74,12 +74,8 @@ body { background:#ccc; /* should be same as #page */ float:left; width:10%; - /* border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: left; - font-size: 88%; */ + padding: 5px; + font-size: small; } #sidebar h2 { @@ -91,17 +87,25 @@ body { padding-left: 0; } +#sidebar a:visited, +#sidebar a:link { + color:#000; +} + #footer { background:#fff; clear:both; border-top: 1px solid #000; - /* color: #999; - background-color: white; - padding: 10px; - font-size: 80%; + padding-top: 10px; text-align: center; - margin: 0 auto 1em auto; */ + font-size: x-small; +} + +#footer a:visited, +#footer a:link { + color:#777; + text-decoration: none; } @@ -129,26 +133,32 @@ body { #status_block { margin: 0 auto 0.5em auto; padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; + background: #ecc; + border: 1px solid #c99; width: 450px; font-size: 120%; font-weight: bolder; } -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - .fielderror { - color: red; - font-weight: bold; + color: red; + font-weight: bold; } .requiredfield { - background: #eebbbb; + background: #eebbbb; +} + +#resultstable, +#resultstable th, +#resultstable td { + border-collapse: collapse; + border-style: solid; + border-width: 1px; + margin: 0px; + padding: 5px; +} + +#resultstable th { + background: #eee; } diff --git a/ipa-server/ipa-gui/ipagui/static/images/logo.png b/ipa-server/ipa-gui/ipagui/static/images/logo.png Binary files differnew file mode 100644 index 000000000..096c8813f --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/static/images/logo.png diff --git a/ipa-server/ipa-gui/ipagui/templates/groupindex.kid b/ipa-server/ipa-gui/ipagui/templates/groupindex.kid index 0bb70ac9a..84a138468 100644 --- a/ipa-server/ipa-gui/ipagui/templates/groupindex.kid +++ b/ipa-server/ipa-gui/ipagui/templates/groupindex.kid @@ -7,12 +7,5 @@ </head> <body> Groups go here. - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> </body> </html> diff --git a/ipa-server/ipa-gui/ipagui/templates/grouplayout.kid b/ipa-server/ipa-gui/ipagui/templates/grouplayout.kid index af05a8082..edaf716e9 100644 --- a/ipa-server/ipa-gui/ipagui/templates/grouplayout.kid +++ b/ipa-server/ipa-gui/ipagui/templates/grouplayout.kid @@ -6,17 +6,18 @@ <body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()"> <div id="main_content"> - <div id="status_block" class="flash" py:if="value_of('tg_flash', None)" py:content="tg_flash"></div> + <div id="status_block" py:if="value_of('tg_flash', None)" + py:content="XML(tg_flash)"></div> <div py:replace="[item.text]+item[:]"></div> </div> - <div id="sidebar"> +<!-- <div id="sidebar"> <h2>Tools</h2> <a href="${tg.url('/groupindex')}">Add Group</a><br/> <a href="${tg.url('/groupindex')}">Find Group</a><br/> <a href="${tg.url('/groupindex')}">List Groups</a><br/> - </div> + </div> --> </body> </html> diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid index a1979df09..8e8d26551 100644 --- a/ipa-server/ipa-gui/ipagui/templates/master.kid +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid @@ -25,22 +25,33 @@ <div id="page"> <div id="header"> - <h1>Free IPA</h1> + <a href="${tg.url('/')}"><img + src="${tg.url('/static/images/logo.png')}" + border="0" + /></a> </div> - <div id="nav"> - <ul> - <li><a href="${tg.url('/userindex')}">Users</a></li> - <li><a href="${tg.url('/groupindex')}">Groups</a></li> - <li><a href="${tg.url('/resindex')}">Resources</a></li> - </ul> + <div id="nav"><!-- + This used to have links. Keeping around in case we move them back... + --></div> + + <div id="sidebar"> + <h2>Tasks</h2> + <a href="${tg.url('/usernew')}">Add Person</a><br/> + <a href="${tg.url('/userlist')}">Find People</a><br/> + <br /> + <a href="${tg.url('/groupindex')}">Add Group</a><br/> + <a href="${tg.url('/groupindex')}">Find Groups</a><br/> + <br /> + <a href="${tg.url('/')}">Manage Policy</a><br/> + <a href="${tg.url('/')}">Self Service</a><br/> </div> <div py:replace="[item.text]+item[:]"></div> <div id="footer"> - This is the footer + <a href="http://www.freeipa.com/">Powered by FreeIPA</a> </div> </div> diff --git a/ipa-server/ipa-gui/ipagui/templates/resindex.kid b/ipa-server/ipa-gui/ipagui/templates/resindex.kid deleted file mode 100644 index 5cd06f7eb..000000000 --- a/ipa-server/ipa-gui/ipagui/templates/resindex.kid +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" - py:extends="'reslayout.kid'"> -<head> -<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> -<title>Resource Listing</title> -</head> -<body> - Resources go here. - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> - <br /> -</body> -</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/reslayout.kid b/ipa-server/ipa-gui/ipagui/templates/reslayout.kid deleted file mode 100644 index 5b9b35c12..000000000 --- a/ipa-server/ipa-gui/ipagui/templates/reslayout.kid +++ /dev/null @@ -1,22 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" - py:extends="'master.kid'"> -<head> -</head> - -<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()"> - <div id="main_content"> - <div id="status_block" class="flash" py:if="value_of('tg_flash', None)" py:content="tg_flash"></div> - - <div py:replace="[item.text]+item[:]"></div> - </div> - - <div id="sidebar"> - <h2>Tools</h2> - <a href="${tg.url('/resindex')}">Add Resource</a><br/> - <a href="${tg.url('/resindex')}">Find Resource</a><br/> - <a href="${tg.url('/resindex')}">List Resources</a><br/> - </div> -</body> - -</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/useredit.kid b/ipa-server/ipa-gui/ipagui/templates/useredit.kid index 781ff7ff1..db47ab298 100644 --- a/ipa-server/ipa-gui/ipagui/templates/useredit.kid +++ b/ipa-server/ipa-gui/ipagui/templates/useredit.kid @@ -3,10 +3,10 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> -<title>Edit a Person</title> +<title>Edit Person</title> </head> <body> - <h2>Edit User</h2> + <h2>Edit Person</h2> ${form.display(action="userupdate", value=user)} </body> diff --git a/ipa-server/ipa-gui/ipagui/templates/userlayout.kid b/ipa-server/ipa-gui/ipagui/templates/userlayout.kid index c49db5468..ecd49f3b9 100644 --- a/ipa-server/ipa-gui/ipagui/templates/userlayout.kid +++ b/ipa-server/ipa-gui/ipagui/templates/userlayout.kid @@ -6,16 +6,17 @@ <body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()"> <div id="main_content"> - <div id="status_block" class="flash" py:if="value_of('tg_flash', None)" py:content="tg_flash"></div> + <div id="status_block" py:if="value_of('tg_flash', None)" + py:content="XML(tg_flash)"></div> <div py:replace="[item.text]+item[:]"></div> </div> - <div id="sidebar"> +<!-- <div id="sidebar"> <h2>Tools</h2> <a href="${tg.url('/usernew')}">Add Person</a><br/> <a href="${tg.url('/userlist')}">Find People</a><br/> - </div> + </div> --> </body> </html> diff --git a/ipa-server/ipa-gui/ipagui/templates/userlist.kid b/ipa-server/ipa-gui/ipagui/templates/userlist.kid index df3e247e4..de4c4eb2f 100644 --- a/ipa-server/ipa-gui/ipagui/templates/userlist.kid +++ b/ipa-server/ipa-gui/ipagui/templates/userlist.kid @@ -3,19 +3,23 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> -<title>User Listing</title> +<title>Find People</title> </head> <body> + <h2>Find People</h2> <div id="search"> <form action="${tg.url('/userlist')}" method="post"> - Search by login/name: - <input type="text" name="uid" /> + Search: + <input id="uid" type="text" name="uid" value="${uid}" /> <input type="submit" /> </form> + <script type="text/javascript"> + document.getElementById("uid").focus(); + </script> </div> <div py:if='users != None'> - <h2>Results</h2> - <table py:if='len(users) > 0'> + <h2>${len(users)} results returned:</h2> + <table id="resultstable" py:if='len(users) > 0'> <tr> <th> <label class="fieldlabel" py:content="fields.uid.label" /> @@ -23,13 +27,37 @@ <th> Name </th> + <th> + Phone + </th> + <th> + Unit + </th> + <th> + Title + </th> + <th> + License Plate + </th> </tr> <tr py:for="user in users"> <td> <a href="${tg.url('/usershow',uid=user.uid)}">${user.uid}</a> </td> <td> - ${user.cn} + ${user.givenName} ${user.sn} + </td> + <td> + ${user.telephoneNumber} + </td> + <td> + ${user.ou} + </td> + <td> + ${user.title} + </td> + <td> + ${user.carLicense} </td> </tr> </table> @@ -37,10 +65,5 @@ No results found. </div> </div> - - <!-- fix for visual artifact of my crappy ui --> - <div> - <br /><br /><br /><br /> - </div> </body> </html> diff --git a/ipa-server/ipa-gui/ipagui/templates/usernew.kid b/ipa-server/ipa-gui/ipagui/templates/usernew.kid index 84b6029ae..37f5bb449 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usernew.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usernew.kid @@ -3,10 +3,10 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> - <title>Add a Person</title> + <title>Add Person</title> </head> <body> - <h2>Add New User</h2> + <h2>Add Person</h2> ${form.display(action="usercreate")} </body> diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid index 6d3fd5bb6..c21ff12bb 100644 --- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid +++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid @@ -3,10 +3,10 @@ py:extends="'userlayout.kid'"> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> - <title>View a Person</title> + <title>View Person</title> </head> <body> - <h2>View User</h2> + <h2>View Person</h2> <div class="formsection">Account Details</div> <table class="formtable" cellpadding="2" cellspacing="0" border="0"> diff --git a/ipa-server/ipa-gui/ipagui/templates/welcome.kid b/ipa-server/ipa-gui/ipagui/templates/welcome.kid index dc08c0b32..402468be7 100644 --- a/ipa-server/ipa-gui/ipagui/templates/welcome.kid +++ b/ipa-server/ipa-gui/ipagui/templates/welcome.kid @@ -6,11 +6,9 @@ <title>Welcome</title> </head> <body> - <div id="sidebar"> - <h2>Tools</h2> - </div> <div id="main_content"> - <div id="status_block" class="flash" py:if="value_of('tg_flash', None)" py:content="tg_flash"></div> + <div id="status_block" py:if="value_of('tg_flash', None)" + py:content="XML(tg_flash)"></div> <h1>Welcome to Free IPA</h1> </div> diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py index 344e6dc3a..4d0630eef 100644 --- a/ipa-server/ipaserver/ipaldap.py +++ b/ipa-server/ipaserver/ipaldap.py @@ -260,12 +260,12 @@ class IPAdmin(SimpleLDAPObject): try: res = self.search(*args) + type, obj = self.result(res) # res = self.search_ext(args[0], args[1], filterstr=args[2], attrlist=args[3], serverctrls=sctrl) except ldap.LDAPError, e: raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - type, obj = self.result(res) if not obj: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, "no such entry for " + str(args)) @@ -283,10 +283,13 @@ class IPAdmin(SimpleLDAPObject): try: res = self.search(*args) + type, obj = self.result(res) + except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED), e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, + "Too many results returned by search", e) except ldap.LDAPError, e: raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) - type, obj = self.result(res) if not obj: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, "no such entry for " + str(args)) @@ -297,6 +300,44 @@ class IPAdmin(SimpleLDAPObject): return all_users + def getListAsync(self,*args): + """This version performs an asynchronous search, to allow + results even if we hit a limit. + + It returns a list: counter followed by the results. + If the results are truncated, counter will be set to -1. + """ + + sctrl = self.__get_server_controls__() + if sctrl is not None: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + + entries = [] + partial = 0 + + try: + msgid = self.search_ext(*args) + type, result_list = self.result(msgid, 0) + while result_list: + for result in result_list: + entries.append(result) + type, result_list = self.result(msgid, 0) + except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED), e: + partial = 1 + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + + if not entries: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, + "no such entry for " + str(args)) + + if partial == 1: + counter = -1 + else: + counter = len(entries) + + return [counter] + entries + def addEntry(self,*args): """This wraps the add function. It assumes that the entry is already populated with all of the desired objectclasses and attributes""" @@ -389,6 +430,18 @@ class IPAdmin(SimpleLDAPObject): raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) return "Success" + def deleteEntry(self,*args): + """This wraps the delete function. Use with caution.""" + + sctrl = self.__get_server_controls__() + + try: + self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) + self.delete_s(*args) + except ldap.LDAPError, e: + raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + return "Success" + def __wrapmethods(self): """This wraps all methods of SimpleLDAPObject, so that we can intercept the methods that deal with entries. Instead of using a raw list of tuples diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 23576b358..8601bbf8a 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -372,9 +372,8 @@ class IPAServer: return users def find_users (self, criteria, sattrs=None, opts=None): - """Return a list containing a User object for each - existing user that matches the criteria. - """ + """Returns a list: counter followed by the results. + If the results are truncated, counter will be set to -1.""" global _LDAPPool if opts: @@ -385,7 +384,7 @@ class IPAServer: # Assume the list of fields to search will come from a central # configuration repository. A good format for that would be # a comma-separated list of fields - search_fields_conf_str = "uid,givenName,sn,telephoneNumber" + search_fields_conf_str = "uid,givenName,sn,telephoneNumber,ou,carLicense,title" search_fields = string.split(search_fields_conf_str, ",") criteria = self.__safe_filter(criteria) @@ -400,25 +399,36 @@ class IPAServer: m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) try: try: - exact_results = m1.getList(self.basedn, self.scope, + exact_results = m1.getListAsync(self.basedn, self.scope, exact_match_filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - exact_results = [] + exact_results = [0] try: - partial_results = m1.getList(self.basedn, self.scope, + partial_results = m1.getListAsync(self.basedn, self.scope, partial_match_filter, sattrs) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - partial_results = [] + partial_results = [0] finally: _LDAPPool.releaseConn(m1) + exact_counter = exact_results[0] + partial_counter = partial_results[0] + + exact_results = exact_results[1:] + partial_results = partial_results[1:] + # Remove exact matches from the partial_match list exact_dns = set(map(lambda e: e.dn, exact_results)) partial_results = filter(lambda e: e.dn not in exact_dns, partial_results) - users = [] + if (exact_counter == -1) or (partial_counter == -1): + counter = -1 + else: + counter = len(exact_results) + len(partial_results) + + users = [counter] for u in exact_results + partial_results: users.append(self.convert_entry(u)) @@ -466,6 +476,29 @@ class IPAServer: _LDAPPool.releaseConn(m1) return res + def delete_user (self, uid, opts=None): + """Delete a user. Not to be confused with inactivate_user. This + makes the entry go away completely. + + uid is the uid of the user to delete + + The memberOf plugin handles removing the user from any other + groups. + """ + if opts: + self.set_principal(opts['remoteuser']) + + dn = self.get_dn_from_principal(self.princ) + + user_dn = self.get_user_by_uid(uid, ['dn', 'uid', 'objectclass'], opts) + if user_dn is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + + m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + res = m1.deleteEntry(user_dn['dn']) + _LDAPPool.releaseConn(m1) + return res + # Group support def __is_group_unique(self, cn, opts): @@ -473,11 +506,10 @@ class IPAServer: cn = self.__safe_filter(cn) filter = "(&(cn=%s)(objectclass=posixGroup))" % cn - entry = self.__get_entry(self.basedn, filter, ['dn','cn'], opts) - - if entry is not None: + try: + entry = self.__get_entry(self.basedn, filter, ['dn','cn'], opts) return 0 - else: + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): return 1 def get_group_by_cn (self, cn, sattrs=None, opts=None): @@ -681,6 +713,59 @@ class IPAServer: """Update a group in LDAP""" return self.__update_entry(oldgroup, newgroup, opts) + def delete_group (self, group_cn, opts=None): + """Delete a group + group_cn is the cn of the group to delete + + The memberOf plugin handles removing the group from any other + groups. + """ + if opts: + self.set_principal(opts['remoteuser']) + + dn = self.get_dn_from_principal(self.princ) + + group = self.get_group_by_cn(group_cn, ['dn', 'cn'], opts) + + if len(group) != 1: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + + m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn) + res = m1.deleteEntry(group[0]['dn']) + _LDAPPool.releaseConn(m1) + return res + + def add_group_to_group(self, group, tgroup, opts=None): + """Add a user to an existing group. + group is a cn of the group to add + tgroup is the cn of the group to be added to + """ + + if opts: + self.set_principal(opts['remoteuser']) + + old_group = self.get_group_by_cn(tgroup, None, opts) + if old_group is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + new_group = copy.deepcopy(old_group) + + group_dn = self.get_group_by_cn(group, ['dn', 'cn', 'objectclass'], opts) + if group_dn is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + + if new_group.get('uniquemember') is not None: + if ((isinstance(new_group.get('uniquemember'), str)) or (isinstance(new_group.get('uniquemember'), unicode))): + new_group['uniquemember'] = [new_group['uniquemember']] + new_group['uniquemember'].append(group_dn['dn']) + else: + new_group['uniquemember'] = group_dn['dn'] + + try: + ret = self.__update_entry(old_group, new_group, opts) + except ipaerror.exception_for(ipaerror.LDAP_EMPTY_MODLIST): + raise + return ret + def ldap_search_escape(match): """Escapes out nasty characters from the ldap search. See RFC 2254.""" diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 16ced2cda..5dc60b51b 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -300,6 +300,7 @@ def handler(req, profiling=False): h.register_function(f.get_all_users) h.register_function(f.find_users) h.register_function(f.update_user) + h.register_function(f.delete_user) h.register_function(f.mark_user_deleted) h.register_function(f.get_group_by_cn) h.register_function(f.get_group_by_dn) @@ -307,9 +308,11 @@ def handler(req, profiling=False): h.register_function(f.find_groups) h.register_function(f.add_user_to_group) h.register_function(f.add_users_to_group) + 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.update_group) + h.register_function(f.delete_group) h.handle_request(req) finally: pass |