diff options
author | Kevin McCarthy <kmccarth@redhat.com> | 2007-10-12 15:11:55 -0700 |
---|---|---|
committer | Kevin McCarthy <kmccarth@redhat.com> | 2007-10-12 15:11:55 -0700 |
commit | 63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d (patch) | |
tree | 72f1bd539e6fcbbce99a31f4b6695c149e828c2a | |
parent | af0a1d989b1eb483ae3e76fa5a3008fda3fafb5e (diff) | |
download | freeipa-63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d.tar.gz freeipa-63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d.tar.xz freeipa-63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d.zip |
Adds delegation listing and creation to the GUI.
-rw-r--r-- | ipa-python/aci.py | 14 | ||||
-rw-r--r-- | ipa-python/ipaclient.py | 8 | ||||
-rw-r--r-- | ipa-python/rpcclient.py | 17 | ||||
-rw-r--r-- | ipa-python/test/test_aci.py | 34 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/controllers.py | 2 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/forms/delegate.py | 86 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/static/css/style.css | 17 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py | 168 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/delegategroupsearch.kid | 31 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/delegatelayout.kid | 16 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/delegatelist.kid | 60 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/delegatenew.kid | 15 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/delegatenewform.kid | 154 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/master.kid | 3 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 9 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 1 |
16 files changed, 625 insertions, 10 deletions
diff --git a/ipa-python/aci.py b/ipa-python/aci.py index d834f899..137d9ee1 100644 --- a/ipa-python/aci.py +++ b/ipa-python/aci.py @@ -16,6 +16,7 @@ # import re +import urllib class ACI: """ @@ -25,10 +26,10 @@ class ACI: """ def __init__(self,acistr=None): + self.name = '' self.source_group = '' self.dest_group = '' self.attrs = [] - self.name = '' if acistr is not None: self.parse_acistr(acistr) @@ -40,15 +41,15 @@ class ACI: # dn's aren't typed in, but searched for, and the search results # will return escaped dns - acistr = ('(targetattr = "%s")' + + acistr = ('(targetattr="%s")' + '(targetfilter="(memberOf=%s)")' + '(version 3.0;' + 'acl "%s";' + 'allow (write) ' + - 'groupdn="%s";)') % (attrs_str, + 'groupdn="ldap:///%s";)') % (attrs_str, self.dest_group, self.name, - self.source_group) + urllib.quote(self.source_group, "/=, ")) return acistr def _match(self, prefix, inputstr): @@ -89,7 +90,7 @@ class ACI: def parse_acistr(self, acistr): """Parses the acistr. If the string isn't recognized, a SyntaxError is raised.""" - acistr = self._match('(targetattr = ', acistr) + acistr = self._match('(targetattr=', acistr) (attrstr, acistr) = self._match_str(acistr) self.attrs = attrstr.split(' || ') @@ -107,7 +108,8 @@ class ACI: acistr = self._match(';allow (write) groupdn=', acistr) (src_dn_str, acistr) = self._match_str(acistr) - self.source_group = src_dn_str + src_dn_str = self._match('ldap:///', src_dn_str) + self.source_group = urllib.unquote(src_dn_str) acistr = self._match(';)', acistr) if len(acistr) > 0: diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index 3a6e1305..cf2e355a 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -54,6 +54,14 @@ class IPAClient: if self.local: self.transport.set_krbccache(krbccache) +# Higher-level API + + def get_aci_entry(self, sattrs=None): + """Returns the entry containing access control ACIs.""" + + result = self.transport.get_aci_entry(sattrs) + return entity.Entity(result) + # General searches def get_entry_by_dn(self,dn,sattrs=None): diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index 8bc288b4..ae26d707 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -67,6 +67,23 @@ class RPCClient: return obj +# Higher-level API + + def get_aci_entry(self, sattrs=None): + """Returns the entry containing access control ACIs.""" + server = self.setup_server() + if sattrs is None: + sattrs = "__NONE__" + try: + result = server.get_aci_entry(sattrs) + 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) + + # General searches def get_entry_by_dn(self,dn,sattrs=None): diff --git a/ipa-python/test/test_aci.py b/ipa-python/test/test_aci.py index ffe2d071..5556deb3 100644 --- a/ipa-python/test/test_aci.py +++ b/ipa-python/test/test_aci.py @@ -22,15 +22,16 @@ sys.path.insert(0, ".") import unittest import aci +import urllib class TestACI(unittest.TestCase): - acitemplate = ('(targetattr = "%s")' + + acitemplate = ('(targetattr="%s")' + '(targetfilter="(memberOf=%s)")' + '(version 3.0;' + 'acl "%s";' + 'allow (write) ' + - 'groupdn="%s";)') + 'groupdn="ldap:///%s";)') def setUp(self): self.aci = aci.ACI() @@ -52,6 +53,20 @@ class TestACI(unittest.TestCase): self.assertEqual(aci, exportaci) + def testURLEncodedExport(self): + self.aci.source_group = 'cn=foo " bar, dc=freeipa, dc=org' + self.aci.dest_group = 'cn=bar, dc=freeipa, dc=org' + self.aci.name = 'this is a "name' + self.aci.attrs = ['field1', 'field2', 'field3'] + + exportaci = self.aci.export_to_string() + aci = TestACI.acitemplate % ('field1 || field2 || field3', + self.aci.dest_group, + 'this is a "name', + urllib.quote(self.aci.source_group, "/=, ")) + + self.assertEqual(aci, exportaci) + def testSimpleParse(self): attr_str = 'field3 || field4 || field5' dest_dn = 'cn=dest\\"group, dc=freeipa, dc=org' @@ -66,6 +81,21 @@ class TestACI(unittest.TestCase): self.assertEqual(name, self.aci.name) self.assertEqual(src_dn, self.aci.source_group) + def testUrlEncodedParse(self): + attr_str = 'field3 || field4 || field5' + dest_dn = 'cn=dest\\"group, dc=freeipa, dc=org' + name = 'my name' + src_dn = 'cn=src " group, dc=freeipa, dc=org' + + acistr = TestACI.acitemplate % (attr_str, dest_dn, name, + urllib.quote(src_dn, "/=, ")) + self.aci.parse_acistr(acistr) + + self.assertEqual(['field3', 'field4', 'field5'], self.aci.attrs) + self.assertEqual(dest_dn, self.aci.dest_group) + self.assertEqual(name, self.aci.name) + self.assertEqual(src_dn, self.aci.source_group) + def testInvalidParse(self): try: self.aci.parse_acistr('foo bar') diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py index 340d6f9f..f2b7bb90 100644 --- a/ipa-server/ipa-gui/ipagui/controllers.py +++ b/ipa-server/ipa-gui/ipagui/controllers.py @@ -14,12 +14,14 @@ import ipa.ipaclient from subcontrollers.user import UserController from subcontrollers.group import GroupController +from subcontrollers.delegation import DelegationController ipa.config.init_config() class Root(controllers.RootController): user = UserController() group = GroupController() + delegate = DelegationController() @expose(template="ipagui.templates.welcome") @identity.require(identity.not_anonymous()) diff --git a/ipa-server/ipa-gui/ipagui/forms/delegate.py b/ipa-server/ipa-gui/ipagui/forms/delegate.py new file mode 100644 index 00000000..3b4967d6 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/forms/delegate.py @@ -0,0 +1,86 @@ +import turbogears +from turbogears import validators, widgets + +from ipagui.forms.user import UserFields + +# TODO - get from config or somewhere +aci_attrs = [ + UserFields.givenname, + UserFields.sn, + UserFields.cn, + UserFields.title, + UserFields.displayname, + UserFields.initials, + UserFields.uid, + UserFields.userpassword, + UserFields.uidnumber, + UserFields.gidnumber, + UserFields.homedirectory, + UserFields.loginshell, + UserFields.gecos, + UserFields.mail, + UserFields.telephonenumber, + UserFields.facsimiletelephonenumber, + UserFields.mobile, + UserFields.pager, + UserFields.homephone, + UserFields.street, + UserFields.l, + UserFields.st, + UserFields.postalcode, + UserFields.ou, + UserFields.businesscategory, + UserFields.description, + UserFields.employeetype, + UserFields.manager, + UserFields.roomnumber, + UserFields.secretary, + UserFields.carlicense, + UserFields.labeleduri, +] + +aci_checkbox_attrs = [(field.name, field.label) for field in aci_attrs] + +class DelegateFields(): + name = widgets.TextField(name="name", label="ACI Name") + + source_group_dn = widgets.HiddenField(name="source_group_dn") + dest_group_dn = widgets.HiddenField(name="dest_group_dn") + + source_group_cn = widgets.HiddenField(name="source_group_cn", + label="People in Group") + dest_group_cn = widgets.HiddenField(name="dest_group_cn", + label="For People in Group") + + attrs = widgets.CheckBoxList(name="attrs", label="Can Modify", + options=aci_checkbox_attrs, validator=validators.NotEmpty) + +class DelegateNewValidator(validators.Schema): + name = validators.String(not_empty=True) + source_group_dn = validators.String(not_empty=True, + messages = { 'empty': _("Please choose a group"), }) + dest_group_dn = validators.String(not_empty=True, + messages = { 'empty': _("Please choose a group"), }) + attrs = validators.NotEmpty( + messages = { 'empty': _("Please select at least one value"), }) + +class DelegateNewForm(widgets.Form): + params = ['delegate', 'attr_list'] + + hidden_fields = [ + DelegateFields.source_group_dn, + DelegateFields.dest_group_dn, + DelegateFields.source_group_cn, + DelegateFields.dest_group_cn, + ] + + validator = DelegateNewValidator() + + def __init__(self, *args, **kw): + super(DelegateNewForm,self).__init__(*args, **kw) + (self.template_c, self.template) = widgets.meta.load_kid_template( + "ipagui.templates.delegatenewform") + self.delegate = DelegateFields + + def update_params(self, params): + super(DelegateNewForm,self).update_params(params) diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css index ae845e86..fb97a67a 100644 --- a/ipa-server/ipa-gui/ipagui/static/css/style.css +++ b/ipa-server/ipa-gui/ipagui/static/css/style.css @@ -77,7 +77,7 @@ body { #main_content { background:#fff; float:right; - width:85%; + width:82%; min-height:500px; border-left: 1px solid #000; padding: 10px; @@ -92,7 +92,7 @@ body { #sidebar { background:#ccc; /* should be same as #page */ float:left; - width:10%; + width:13%; padding: 5px; font-size: medium; } @@ -207,6 +207,19 @@ body { } /* + * Used for checkboxlist of aci attributes + */ +ul.requiredfield { + background: #ffffff; +} + +ul.checkboxlist { + padding: 0px; + margin: 0px; + list-style: none; +} + +/* * TableKit css */ diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py b/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py new file mode 100644 index 00000000..8adbc7da --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py @@ -0,0 +1,168 @@ +import os +from pickle import dumps, loads +from base64 import b64encode, b64decode + +import cherrypy +import turbogears +from turbogears import controllers, expose, flash +from turbogears import validators, validate +from turbogears import widgets, paginate +from turbogears import error_handler +from turbogears import identity + +from ipacontroller import IPAController +from ipa.entity import utf8_encode_values +from ipa import ipaerror +import ipagui.forms.delegate +import ipa.aci + +import ldap.dn + +aci_fields = ['*', 'aci'] + +delegate_new_form = ipagui.forms.delegate.DelegateNewForm() + +class DelegationController(IPAController): + + @expose() + @identity.require(identity.not_anonymous()) + def index(self, tg_errors=None): + raise turbogears.redirect("/delegate/list") + + @expose("ipagui.templates.delegatenew") + @identity.require(identity.not_anonymous()) + def new(self): + """Display delegate page""" + client = self.get_ipaclient() + delegate = {} + delegate['source_group_cn'] = "Please choose" + delegate['dest_group_cn'] = "Please choose" + + return dict(form=delegate_new_form, delegate=delegate) + + @expose() + @identity.require(identity.not_anonymous()) + def create(self, **kw): + """Creates a new delegation""" + client = self.get_ipaclient() + + tg_errors, kw = self.delegatecreatevalidate(**kw) + if tg_errors: + return dict(form=delegate_new_form, delegate=kw, + tg_template='ipagui.templates.delegatenew') + + try: + new_aci = ipa.aci.ACI() + new_aci.name = kw.get('name') + new_aci.source_group = kw.get('source_group_dn') + new_aci.dest_group = kw.get('dest_group_dn') + new_aci.attrs = kw.get('attrs') + + # not pulling down existing aci attributes + aci_entry = client.get_aci_entry(['dn']) + aci_entry.setValue('aci', new_aci.export_to_string()) + + # TODO - add a client.update_entry() call instead + client.update_group(aci_entry) + except ipaerror.IPAError, e: + turbogears.flash("Delgate add failed: " + str(e)) + return dict(form=delegate_new_form, delegate=kw, + tg_template='ipagui.templates.delegatenew') + + turbogears.flash("delegate created") + raise turbogears.redirect('/delegate/list') +# +# @expose("ipagui.templates.delegateedit") +# @identity.require(identity.not_anonymous()) +# def edit(self): +# """Display delegate page""" +# client = self.get_ipaclient() +# +# return dict(userfields=ipagui.forms.user.UserFields()) +# +# @expose() +# @identity.require(identity.not_anonymous()) +# def update(self, **kw): +# """Display delegate page""" +# client = self.get_ipaclient() +# +# turbogears.flash("delegate updated") +# raise turbogears.redirect('/delegate/list') + + @expose("ipagui.templates.delegatelist") + @identity.require(identity.not_anonymous()) + def list(self): + """Display delegate page""" + client = self.get_ipaclient() + + aci_entry = client.get_aci_entry(aci_fields) + aci_str_list = aci_entry.getValues('aci') + if aci_str_list is None: + aci_str_list = [] + + aci_list = [] + for aci_str in aci_str_list: + try: + aci = ipa.aci.ACI(aci_str) + aci_list.append(aci) + except SyntaxError: + # ignore aci_str's that ACI can't parse + pass + group_dn_to_cn = self.extract_group_cns(aci_list, client) + + return dict(aci_list=aci_list, group_dn_to_cn=group_dn_to_cn) + + @expose("ipagui.templates.delegategroupsearch") + @identity.require(identity.not_anonymous()) + def group_search(self, **kw): + """Searches for groups and displays list of results in a table. + This method is used for the ajax search on the delegation pages.""" + client = self.get_ipaclient() + + groups = [] + 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(groups=groups, criteria=criteria, + which_group=kw.get('which_group'), + counter=groups_counter) + + @validate(form=delegate_new_form) + @identity.require(identity.not_anonymous()) + def delegatecreatevalidate(self, tg_errors=None, **kw): + return tg_errors, kw + + def extract_group_cns(self, aci_list, client): + """Extracts all the cn's from a list of aci's and returns them as a hash + from group_dn to group_cn. + + It first tries to cheat by looking at the first rdn for the + group dn. If that's not cn for some reason, it looks up the group.""" + group_dn_to_cn = {} + for aci in aci_list: + for dn in (aci.source_group, aci.dest_group): + if not group_dn_to_cn.has_key(dn): + rdn_list = ldap.dn.str2dn(dn) + first_rdn = rdn_list[0] + for (type,value,junk) in first_rdn: + if type == "cn": + group_dn_to_cn[dn] = value + break; + else: + try: + group = client.get_entry_by_dn(dn, ['cn']) + group_dn_to_cn[dn] = group.getValue('cn') + except ipaerror.IPAError, e: + group_dn_to_cn[dn] = 'unknown' + + return group_dn_to_cn + diff --git a/ipa-server/ipa-gui/ipagui/templates/delegategroupsearch.kid b/ipa-server/ipa-gui/ipagui/templates/delegategroupsearch.kid new file mode 100644 index 00000000..f97355f8 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/delegategroupsearch.kid @@ -0,0 +1,31 @@ +<div xmlns:py="http://purl.org/kid/ns#"> + +<?python +from ipagui.helpers import ipahelper +?> + <div py:if='(groups != None) and (len(groups) > 0)'> + <div id="search-results-count"> + ${len(groups)} results returned: + <span py:if="counter < 0"> + (truncated) + </span> + </div> + + <div py:for="group in groups"> + <?python + group_dn_esc = ipahelper.javascript_string_escape(group.dn) + group_cn_esc = ipahelper.javascript_string_escape(group.cn) + which_group_esc = ipahelper.javascript_string_escape(which_group) + ?> + + ${group.cn} + <a href="" + onclick="selectGroup('${which_group_esc}', '${group_dn_esc}', '${group_cn_esc}'); + return false;" + >select</a> + </div> + </div> + <div py:if='(groups != None) and (len(groups) == 0)'> + No results found for "${criteria}" + </div> +</div> diff --git a/ipa-server/ipa-gui/ipagui/templates/delegatelayout.kid b/ipa-server/ipa-gui/ipagui/templates/delegatelayout.kid new file mode 100644 index 00000000..6cec389c --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/delegatelayout.kid @@ -0,0 +1,16 @@ +<!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" py:if="value_of('tg_flash', None)" + py:content="XML(tg_flash)"></div> + + <div py:replace="[item.text]+item[:]"></div> + </div> +</body> + +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/delegatelist.kid b/ipa-server/ipa-gui/ipagui/templates/delegatelist.kid new file mode 100644 index 00000000..c88b6e31 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/delegatelist.kid @@ -0,0 +1,60 @@ +<!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="'delegatelayout.kid'"> +<head> +<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> +<title>Delegations</title> +</head> +<body> + <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script> + + <h2>Delegations</h2> + + <table id="resultstable" class="sortable resizable"> + <thead> + <tr> + <th>Name</th> + <th>People in Group</th> + <th>Can Modify</th> + <th>For People in Group</th> + <th>Action</th> + </tr> + </thead> + <tbody> + <tr py:for='aci in aci_list'> + <?python + source_cn = group_dn_to_cn.get(aci.source_group) + dest_cn = group_dn_to_cn.get(aci.dest_group) + ?> + <td> + ${aci.name} + </td> + <td> + <a href="${tg.url('/group/show', cn=source_cn)}" + >${source_cn}</a> + </td> + <td> + ${", ".join(aci.attrs)} + </td> + <td> + <a href="${tg.url('/group/show', cn=dest_cn)}" + >${dest_cn}</a> + </td> + <td> + <a href="${tg.url('/delegate/edit')}">edit</a> (TODO)<br /> + </td> + </tr> + </tbody> + </table> + + <table border="0"> + <tbody> + <tr> + <td> + <a href="${tg.url('/delegate/new')}">add new delegation</a><br /> + </td> + </tr> + </tbody> + </table> +</body> +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/delegatenew.kid b/ipa-server/ipa-gui/ipagui/templates/delegatenew.kid new file mode 100644 index 00000000..71d9e7e2 --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/delegatenew.kid @@ -0,0 +1,15 @@ +<!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="'delegatelayout.kid'"> +<head> +<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/> +<title>Add Delegation</title> +</head> +<body> + + <h2>Add Delegation</h2> + + ${form.display(action=tg.url("/delegate/create"), value=delegate)} + +</body> +</html> diff --git a/ipa-server/ipa-gui/ipagui/templates/delegatenewform.kid b/ipa-server/ipa-gui/ipagui/templates/delegatenewform.kid new file mode 100644 index 00000000..95f93b5b --- /dev/null +++ b/ipa-server/ipa-gui/ipagui/templates/delegatenewform.kid @@ -0,0 +1,154 @@ +<div xmlns:py="http://purl.org/kid/ns#" + class="simpleroster"> + + <?python searchurl = tg.url('/delegate/group_search') ?> + + <script type="text/javascript"> + + function enterDoSearch(e, which_group) { + var keyPressed; + if (window.event) { + keyPressed = window.event.keyCode; + } else { + keyPressed = e.which; + } + + if (keyPressed == 13) { + return doSearch(which_group); + } else { + return true; + } + } + + function doSearch(which_group) { + $(which_group + '_searchresults').update("Searching..."); + new Ajax.Updater(which_group + '_searchresults', + '${searchurl}', + { asynchronous:true, + parameters: { criteria: $(which_group + '_criteria').value, + which_group: which_group}, + evalScripts: true }); + return false; + } + + function selectGroup(which_group, group_dn, group_cn) { + group_dn_field = $('form_' + which_group + '_group_dn'); + group_cn_field = $('form_' + which_group + '_group_cn'); + group_cn_span = $(which_group + '_group_cn'); + + group_dn_field.value = group_dn; + group_cn_field.value = group_cn; + group_cn_span.update(group_cn); + + new Effect.Fade($(which_group + '_searcharea'), {duration: 0.25}); + new Effect.Appear($(which_group + '_change_link'), {duration: 0.25}); + } + </script> + + <form action="${action}" name="${name}" method="${method}" class="tableform"> + + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <td> + <input type="submit" class="submitbutton" name="submit" + value="Add Delegation"/> + </td> + </tr> + </table> + + <div py:for="field in hidden_fields" + py:replace="field.display(value_for(field), **params_for(field))" + /> + + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <th valign="top"> + <label class="fieldlabel" for="${delegate.name.field_id}" + py:content="delegate.name.label" />: + </th> + <td> + <span py:replace="delegate.name.display(value_for(delegate.name))" /> + <span py:if="tg.errors.get('name')" class="fielderror" + py:content="tg.errors.get('name')" /> + </td> + </tr> + <tr> + <th valign="top"> + <label class="fieldlabel" for="${delegate.source_group_cn.field_id}" + py:content="delegate.source_group_cn.label" />: + </th> + <td> + <div> + <span id='source_group_cn'>${value_for(delegate.source_group_cn)}</span> + <a href="#" id='source_change_link' + onclick="new Effect.Appear($('source_searcharea'), {duration: 0.25}); + new Effect.Fade(this, {duration: 0.25}); + return false;">change</a> + <span py:if="tg.errors.get('source_group_dn')" class="fielderror" + py:content="tg.errors.get('source_group_dn')" /> + </div> + <div id="source_searcharea" style="display:none"> + <div> + <input id="source_criteria" type="text" + onkeypress="return enterDoSearch(event, 'source');" /> + <input type="button" value="Find" + onclick="return doSearch('source');" + /> + </div> + <div id="source_searchresults"> + </div> + </div> + </td> + </tr> + <tr> + <th valign="top"> + <label class="fieldlabel" for="${delegate.attrs.field_id}" + py:content="delegate.attrs.label" />: + </th> + <td valign="top"> + <span py:if="tg.errors.get('attrs')" class="fielderror" + py:content="tg.errors.get('attrs')" /> + <span py:replace="delegate.attrs.display(value_for(delegate.attrs))" /> + </td> + </tr> + <tr> + <th valign="top"> + <label class="fieldlabel" for="${delegate.dest_group_cn.field_id}" + py:content="delegate.dest_group_cn.label" />: + </th> + <td> + <div> + <span id='dest_group_cn'>${value_for(delegate.dest_group_cn)}</span> + <a href="#" id='dest_change_link' + onclick="new Effect.Appear($('dest_searcharea'), {duration: 0.25}); + new Effect.Fade(this, {duration: 0.25}); + return false;">change</a> + <span py:if="tg.errors.get('dest_group_dn')" class="fielderror" + py:content="tg.errors.get('dest_group_dn')" /> + </div> + <div id="dest_searcharea" style="display:none"> + <div> + <input id="dest_criteria" type="text" + onkeypress="return enterDoSearch(event, 'dest');" /> + <input type="button" value="Find" + onclick="return doSearch('dest');" + /> + </div> + <div id="dest_searchresults"> + </div> + </div> + </td> + </tr> + </table> + + <table class="formtable" cellpadding="2" cellspacing="0" border="0"> + <tr> + <td> + <input type="submit" class="submitbutton" name="submit" + value="Add Delegation"/> + </td> + </tr> + </table> + + </form> +</div> diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid index 52b88e37..2926c4f9 100644 --- a/ipa-server/ipa-gui/ipagui/templates/master.kid +++ b/ipa-server/ipa-gui/ipagui/templates/master.kid @@ -77,6 +77,9 @@ <a href="${tg.url('/')}">Manage Policy</a><br/> <a href="${tg.url('/')}">Self Service</a><br/> </p> + <p> + <a href="${tg.url('/delegate/list')}">Delegation Mgmt</a><br/> + </p> </div> <div py:replace="[item.text]+item[:]"></div> diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 517d54a7..0dd0c2c5 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -43,6 +43,7 @@ except ImportError: # Need a global to store this between requests _LDAPPool = None +ACIContainer = "cn=accounts" DefaultUserContainer = "cn=users,cn=accounts" DefaultGroupContainer = "cn=groups,cn=accounts" @@ -315,6 +316,14 @@ class IPAServer: return (exact_match_filter, partial_match_filter) +# Higher-level API + + def get_aci_entry(self, sattrs=None, opts=None): + """Returns the entry containing access control ACIs.""" + + dn="%s,%s" % (ACIContainer, self.basedn) + return self.get_entry_by_dn(dn, sattrs, opts) + # General searches def get_entry_by_dn (self, dn, sattrs=None, opts=None): diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 805dbf07..3872ee21 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -317,6 +317,7 @@ def handler(req, profiling=False): try: f = funcs.IPAServer() h = ModXMLRPCRequestHandler() + h.register_function(f.get_aci_entry) h.register_function(f.get_entry_by_dn) h.register_function(f.get_entry_by_cn) h.register_function(f.get_user_by_uid) |