summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin McCarthy <kmccarth@redhat.com>2007-10-12 15:11:55 -0700
committerKevin McCarthy <kmccarth@redhat.com>2007-10-12 15:11:55 -0700
commit63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d (patch)
tree72f1bd539e6fcbbce99a31f4b6695c149e828c2a
parentaf0a1d989b1eb483ae3e76fa5a3008fda3fafb5e (diff)
downloadfreeipa-63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d.tar.gz
freeipa-63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d.tar.xz
freeipa-63f7cdf7f7e1c39b791dad6951fa39d9a6d58c9d.zip
Adds delegation listing and creation to the GUI.
-rw-r--r--ipa-python/aci.py14
-rw-r--r--ipa-python/ipaclient.py8
-rw-r--r--ipa-python/rpcclient.py17
-rw-r--r--ipa-python/test/test_aci.py34
-rw-r--r--ipa-server/ipa-gui/ipagui/controllers.py2
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/delegate.py86
-rw-r--r--ipa-server/ipa-gui/ipagui/static/css/style.css17
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/delegation.py168
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/delegategroupsearch.kid31
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/delegatelayout.kid16
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/delegatelist.kid60
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/delegatenew.kid15
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/delegatenewform.kid154
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/master.kid3
-rw-r--r--ipa-server/xmlrpc-server/funcs.py9
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py1
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 &lt; 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)