summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2007-12-07 17:38:15 -0500
committerSimo Sorce <ssorce@redhat.com>2007-12-07 17:38:15 -0500
commit0b51e326996dd6a01fdca8a0b5a5160ca03c660b (patch)
tree157119af9167d60cda95ba565355159d043c0a6d
parent11559e9a69e2131dc620ca7de7af8544e23cbb46 (diff)
parentb3fa02225a8cf58c6283d122d5a48cad506d2660 (diff)
downloadfreeipa-0b51e326996dd6a01fdca8a0b5a5160ca03c660b.tar.gz
freeipa-0b51e326996dd6a01fdca8a0b5a5160ca03c660b.tar.xz
freeipa-0b51e326996dd6a01fdca8a0b5a5160ca03c660b.zip
merge from upstream
-rw-r--r--ipa-admintools/ipa-findgroup3
-rw-r--r--ipa-admintools/ipa-finduser3
-rw-r--r--ipa-python/ipaclient.py17
-rw-r--r--ipa-python/ipaerror.py5
-rw-r--r--ipa-python/ipautil.py16
-rw-r--r--ipa-python/rpcclient.py18
-rw-r--r--ipa-server/ipa-gui/ipagui/controllers.py2
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/group.py3
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/ipapolicy.py12
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/principal.py39
-rw-r--r--ipa-server/ipa-gui/ipagui/forms/user.py9
-rw-r--r--ipa-server/ipa-gui/ipagui/helpers/ipahelper.py31
-rw-r--r--ipa-server/ipa-gui/ipagui/proxyprovider.py2
-rw-r--r--ipa-server/ipa-gui/ipagui/static/css/style.css21
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/group.py12
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py20
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/principal.py153
-rw-r--r--ipa-server/ipa-gui/ipagui/subcontrollers/user.py89
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/delegateform.kid6
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/groupnewform.kid4
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid75
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid43
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/master.kid6
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/principallayout.kid19
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/principallist.kid64
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/principalnew.kid13
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/principalnewform.kid102
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usereditform.kid20
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usernewform.kid4
-rw-r--r--ipa-server/ipa-gui/ipagui/templates/usershow.kid4
-rw-r--r--ipa-server/ipa-install/ipa-server-install2
-rw-r--r--ipa-server/ipa-install/share/60ipaconfig.ldif8
-rw-r--r--ipa-server/ipa-install/share/bootstrap-template.ldif12
-rwxr-xr-xipa-server/ipa-server.spec2
-rw-r--r--ipa-server/ipaserver/dsinstance.py75
-rw-r--r--ipa-server/ipaserver/ipaldap.py21
-rw-r--r--ipa-server/xmlrpc-server/funcs.py191
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py1
38 files changed, 980 insertions, 147 deletions
diff --git a/ipa-admintools/ipa-findgroup b/ipa-admintools/ipa-findgroup
index 9f809aa0b..d84a2c62f 100644
--- a/ipa-admintools/ipa-findgroup
+++ b/ipa-admintools/ipa-findgroup
@@ -65,6 +65,9 @@ def main():
if counter == 0:
print "No entries found for", args[1]
return 2
+ elif counter == -1:
+ print "These results are truncated."
+ print "Please revine your search and try again."
for ent in groups:
try:
diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser
index 6dc4d56c8..81e8898a2 100644
--- a/ipa-admintools/ipa-finduser
+++ b/ipa-admintools/ipa-finduser
@@ -90,6 +90,9 @@ def main():
if counter == 0:
print "No entries found for", args[1]
return 2
+ elif counter == -1:
+ print "These results are truncated."
+ print "Please revine your search and try again."
for ent in users:
attr = ent.attrList()
diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py
index c551f0435..426f66817 100644
--- a/ipa-python/ipaclient.py
+++ b/ipa-python/ipaclient.py
@@ -116,9 +116,6 @@ class IPAClient:
user_dict = user.toDict()
- # dn is set on the server-side
- del user_dict['dn']
-
# convert to a regular dict before sending
result = self.transport.add_user(user_dict, user_container)
return result
@@ -385,6 +382,20 @@ class IPAClient:
def add_service_principal(self, princ_name):
return self.transport.add_service_principal(princ_name)
+ def find_service_principal(self, criteria, sattrs=None, searchlimit=0, timelimit=-1):
+ """Return a list: counter followed by a Entity object for each host that
+ matches the criteria. If the results are truncated, counter will
+ be set to -1"""
+ result = self.transport.find_service_principal(criteria, sattrs, searchlimit, timelimit)
+ counter = result[0]
+
+ hosts = [counter]
+ for attrs in result[1:]:
+ if attrs is not None:
+ hosts.append(entity.Entity(attrs))
+
+ return hosts
+
def get_keytab(self, princ_name):
return self.transport.get_keytab(princ_name)
diff --git a/ipa-python/ipaerror.py b/ipa-python/ipaerror.py
index 2f9a98363..e34963365 100644
--- a/ipa-python/ipaerror.py
+++ b/ipa-python/ipaerror.py
@@ -177,3 +177,8 @@ CONFIG_DEFAULT_GROUP = gen_error_code(
CONFIGURATION_CATEGORY,
0x0002,
"You cannot remove the default users group.")
+
+CONFIG_INVALID_OC = gen_error_code(
+ CONFIGURATION_CATEGORY,
+ 0x0003,
+ "Invalid object class.")
diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py
index 9b8412d23..2dc9b0c9d 100644
--- a/ipa-python/ipautil.py
+++ b/ipa-python/ipautil.py
@@ -27,9 +27,7 @@ from random import Random
from time import gmtime
import os
import stat
-import socket
-from string import lower
import re
import xmlrpclib
import datetime
@@ -79,7 +77,7 @@ def run(args, stdin=None):
logging.info(stderr)
if p.returncode != 0:
- raise self.CalledProcessError(p.returncode, ' '.join(args))
+ raise CalledProcessError(p.returncode, ' '.join(args))
def file_exists(filename):
try:
@@ -118,24 +116,24 @@ class CIDict(dict):
self.update(default or {})
def __getitem__(self,key):
- return super(CIDict,self).__getitem__(lower(key))
+ return super(CIDict,self).__getitem__(string.lower(key))
def __setitem__(self,key,value):
- lower_key = lower(key)
+ lower_key = string.lower(key)
self._keys[lower_key] = key
- return super(CIDict,self).__setitem__(lower(key),value)
+ return super(CIDict,self).__setitem__(string.lower(key),value)
def __delitem__(self,key):
- lower_key = lower(key)
+ lower_key = string.lower(key)
del self._keys[lower_key]
- return super(CIDict,self).__delitem__(lower(key))
+ return super(CIDict,self).__delitem__(string.lower(key))
def update(self,dict):
for key in dict.keys():
self[key] = dict[key]
def has_key(self,key):
- return super(CIDict, self).has_key(lower(key))
+ return super(CIDict, self).has_key(string.lower(key))
def get(self,key,failobj=None):
try:
diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py
index d7ff97405..de32e9beb 100644
--- a/ipa-python/rpcclient.py
+++ b/ipa-python/rpcclient.py
@@ -703,6 +703,24 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
+ def find_service_principal (self, criteria, sattrs=None, searchlimit=0, timelimit=-1):
+ """Return a list: counter followed by a Entity object for each host that
+ matches the criteria. If the results are truncated, counter will
+ be set to -1"""
+
+ server = self.setup_server()
+ try:
+ # None values are not allowed in XML-RPC
+ if sattrs is None:
+ sattrs = "__NONE__"
+ result = server.find_service_principal(criteria, sattrs, searchlimit, timelimit)
+ 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 get_keytab(self, princ_name):
server = self.setup_server()
diff --git a/ipa-server/ipa-gui/ipagui/controllers.py b/ipa-server/ipa-gui/ipagui/controllers.py
index d1ee22e01..70a29246a 100644
--- a/ipa-server/ipa-gui/ipagui/controllers.py
+++ b/ipa-server/ipa-gui/ipagui/controllers.py
@@ -19,6 +19,7 @@ from subcontrollers.group import GroupController
from subcontrollers.delegation import DelegationController
from subcontrollers.policy import PolicyController
from subcontrollers.ipapolicy import IPAPolicyController
+from subcontrollers.principal import PrincipalController
ipa.config.init_config()
@@ -31,6 +32,7 @@ class Root(controllers.RootController):
delegate = DelegationController()
policy = PolicyController()
ipapolicy = IPAPolicyController()
+ principal = PrincipalController()
@expose(template="ipagui.templates.welcome")
@identity.require(identity.not_anonymous())
diff --git a/ipa-server/ipa-gui/ipagui/forms/group.py b/ipa-server/ipa-gui/ipagui/forms/group.py
index fa3a0988a..afb63073e 100644
--- a/ipa-server/ipa-gui/ipagui/forms/group.py
+++ b/ipa-server/ipa-gui/ipagui/forms/group.py
@@ -41,11 +41,12 @@ class GroupNewForm(widgets.Form):
class GroupEditValidator(validators.Schema):
- cn = validators.String(not_empty=True)
+ cn = validators.String(not_empty=False)
gidnumber = validators.Int(not_empty=False)
description = validators.String(not_empty=False)
pre_validators = [
+ validators.RequireIfPresent(required='cn', present='editprotected'),
validators.RequireIfPresent(required='gidnumber', present='editprotected'),
]
diff --git a/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py b/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
index 6cd967a97..1d48f8f33 100644
--- a/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
+++ b/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
@@ -1,5 +1,6 @@
import turbogears
from turbogears import validators, widgets
+from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
class IPAPolicyFields(object):
# From cn=ipaConfig
@@ -12,12 +13,16 @@ class IPAPolicyFields(object):
ipadefaultprimarygroup = widgets.TextField(name="ipadefaultprimarygroup", label="Default Users group")
ipamaxusernamelength = widgets.TextField(name="ipamaxusernamelength", label="Max. Username Length", attrs=dict(size=3,maxlength=3))
ipapwdexpadvnotify = widgets.TextField(name="ipapwdexpadvnotify", label="Password Expiration Notification (days)", attrs=dict(size=3,maxlength=3))
+ ipauserobjectclasses = widgets.TextField(name="ipauserobjectclasses", label="Default User Object Classes", attrs=dict(size=50))
+ userobjectclasses = ExpandingForm(name="userobjectclasses", label="Default User Object Classes", fields=[ipauserobjectclasses])
+ ipagroupobjectclasses = widgets.TextField(name="ipagroupobjectclasses", label="Default Group Object Classes", attrs=dict(size=50))
+ groupobjectclasses = ExpandingForm(name="groupobjectclasses", label="Default User Object Classes", fields=[ipagroupobjectclasses])
ipapolicy_orig = widgets.HiddenField(name="ipapolicy_orig")
# From cn=accounts
- krbmaxpwdlife = widgets.TextField(name="krbmaxpwdlife", label="Max. Password Lifetime", attrs=dict(size=3,maxlength=3))
- krbminpwdlife = widgets.TextField(name="krbminpwdlife", label="Min. Password Lifetime", attrs=dict(size=3,maxlength=3))
+ krbmaxpwdlife = widgets.TextField(name="krbmaxpwdlife", label="Max. Password Lifetime (days)", attrs=dict(size=3,maxlength=3))
+ krbminpwdlife = widgets.TextField(name="krbminpwdlife", label="Min. Password Lifetime (hours)", attrs=dict(size=3,maxlength=3))
krbpwdmindiffchars = widgets.TextField(name="krbpwdmindiffchars", label="Min. number of character classes", attrs=dict(size=3,maxlength=3))
krbpwdminlength = widgets.TextField(name="krbpwdminlength", label="Min. Length of password", attrs=dict(size=3,maxlength=3))
krbpwdhistorylength = widgets.TextField(name="krbpwdhistorylength", label="Password History size", attrs=dict(size=3,maxlength=3))
@@ -34,6 +39,9 @@ class IPAPolicyValidator(validators.Schema):
ipahomesrootdir = validators.String(not_empty=True)
ipadefaultloginshell = validators.String(not_empty=True)
ipadefaultprimarygroup = validators.String(not_empty=True)
+ ipauserobjectclasses = validators.ForEach(validators.String(not_empty=True))
+ ipagroupobjectclasses = validators.ForEach(validators.String(not_empty=True))
+
krbmaxpwdlife = validators.Number(not_empty=True)
krbminpwdlife = validators.Number(not_empty=True)
krbpwdmindiffchars = validators.Number(not_empty=True)
diff --git a/ipa-server/ipa-gui/ipagui/forms/principal.py b/ipa-server/ipa-gui/ipagui/forms/principal.py
new file mode 100644
index 000000000..a830c8a34
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/forms/principal.py
@@ -0,0 +1,39 @@
+import turbogears
+from turbogears import validators, widgets
+from tg_expanding_form_widget.tg_expanding_form_widget import ExpandingForm
+
+class PrincipalFields(object):
+ hostname = widgets.TextField(name="hostname", label="Host Name")
+ service = widgets.SingleSelectField(name="service",
+ label="Service Type",
+ options = [
+ ("cifs", "cifs"),
+ ("dhcp", "dhcp"),
+ ("dns", "dns"),
+ ("host", "host"),
+ ("HTTP", "HTTP"),
+ ("ldap", "ldap"),
+ ("other", "other"),
+ ("rpc", "rpc"),
+ ("snmp", "snmp")
+ ],
+ attrs=dict(onchange="toggleOther(this.id)"))
+ other = widgets.TextField(name="other", label="Other Service", attrs=dict(size=10))
+
+class PrincipalNewValidator(validators.Schema):
+ hostname = validators.String(not_empty=True)
+ service = validators.String(not_empty=True)
+ other = validators.String(not_empty=False)
+
+class PrincipalNewForm(widgets.Form):
+ params = ['principal_fields']
+
+ validator = PrincipalNewValidator()
+
+ def __init__(self, *args, **kw):
+ super(PrincipalNewForm,self).__init__(*args, **kw)
+ (self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.principalnewform")
+ self.principal_fields = PrincipalFields
+
+ def update_params(self, params):
+ super(PrincipalNewForm,self).update_params(params)
diff --git a/ipa-server/ipa-gui/ipagui/forms/user.py b/ipa-server/ipa-gui/ipagui/forms/user.py
index f1bf48365..b0c4d0aa7 100644
--- a/ipa-server/ipa-gui/ipagui/forms/user.py
+++ b/ipa-server/ipa-gui/ipagui/forms/user.py
@@ -11,7 +11,7 @@ class UserFields(object):
displayname = widgets.TextField(name="displayname", label="Display Name")
initials = widgets.TextField(name="initials", label="Initials")
- uid = widgets.TextField(name="uid", label="Login")
+ uid = widgets.TextField(name="uid", label="Login", attrs=dict(onchange="warnRDN(this.id)"))
userpassword = widgets.PasswordField(name="userpassword", label="Password")
userpassword_confirm = widgets.PasswordField(name="userpassword_confirm",
label="Confirm Password")
@@ -56,9 +56,7 @@ class UserFields(object):
label="Account Status",
options = [("", "active"), ("true", "inactive")])
- uid_hidden = widgets.HiddenField(name="uid")
- uidnumber_hidden = widgets.HiddenField(name="uidnumber")
- gidnumber_hidden = widgets.HiddenField(name="gidnumber")
+ uid_hidden = widgets.HiddenField(name="uid_hidden")
krbPasswordExpiration_hidden = widgets.HiddenField(name="krbPasswordExpiration")
editprotected_hidden = widgets.HiddenField(name="editprotected")
@@ -111,11 +109,12 @@ class UserEditValidator(validators.Schema):
givenname = validators.String(not_empty=True)
sn = validators.String(not_empty=True)
cn = validators.ForEach(validators.String(not_empty=True))
- mail = validators.Email(not_empty=True)
+ mail = validators.Email(not_empty=False)
uidnumber = validators.Int(not_empty=False)
gidnumber = validators.Int(not_empty=False)
pre_validators = [
+ validators.RequireIfPresent(required='uid', present='editprotected'),
validators.RequireIfPresent(required='uidnumber', present='editprotected'),
validators.RequireIfPresent(required='gidnumber', present='editprotected'),
]
diff --git a/ipa-server/ipa-gui/ipagui/helpers/ipahelper.py b/ipa-server/ipa-gui/ipagui/helpers/ipahelper.py
index 9ea6b48ab..e5c2bd378 100644
--- a/ipa-server/ipa-gui/ipagui/helpers/ipahelper.py
+++ b/ipa-server/ipa-gui/ipagui/helpers/ipahelper.py
@@ -7,3 +7,34 @@ def javascript_string_escape(input):
return re.sub(r'[\'\"\\]',
lambda match: "\\%s" % match.group(),
input)
+
+def setup_mv_fields(field, fieldname):
+ """Given a field (must be a list) and field name, convert that
+ field into a list of dictionaries of the form:
+ [ { fieldname : v1}, { fieldname : v2 }, .. ]
+
+ This is how we pre-fill values for multi-valued fields.
+ """
+ mvlist = []
+ if field:
+ for v in field:
+ if v:
+ mvlist.append({ fieldname : v } )
+ if len(mvlist) == 0:
+ # We need to return an empty value so something can be
+ # displayed on the edit page. Otherwise only an Add link
+ # will show, not an empty field.
+ mvlist.append({ fieldname : '' } )
+ return mvlist
+
+def fix_incoming_fields(fields, fieldname, multifieldname):
+ """This is called by the update() function. It takes the incoming
+ list of dictionaries and converts it into back into the original
+ field, then removes the multiple field.
+ """
+ fields[fieldname] = []
+ for i in range(len(fields[multifieldname])):
+ fields[fieldname].append(fields[multifieldname][i][fieldname])
+ del(fields[multifieldname])
+
+ return fields
diff --git a/ipa-server/ipa-gui/ipagui/proxyprovider.py b/ipa-server/ipa-gui/ipagui/proxyprovider.py
index bd9cf87a8..485a0f3b8 100644
--- a/ipa-server/ipa-gui/ipagui/proxyprovider.py
+++ b/ipa-server/ipa-gui/ipagui/proxyprovider.py
@@ -83,7 +83,7 @@ class ProxyIdentity(object):
return self._user.groups
except AttributeError:
# Groups haven't been computed yet
- return None
+ return []
groups= property(_get_groups)
def logout(self):
diff --git a/ipa-server/ipa-gui/ipagui/static/css/style.css b/ipa-server/ipa-gui/ipagui/static/css/style.css
index 1a7cbb1fb..6d68e8e37 100644
--- a/ipa-server/ipa-gui/ipagui/static/css/style.css
+++ b/ipa-server/ipa-gui/ipagui/static/css/style.css
@@ -339,14 +339,18 @@ table.formtable td input[type="text"], input#criteria {
border: 1px inset #dcdcdc;
font-size: medium;
padding: 2px 1px;
+/*
background-color: #f5faff;
+*/
}
table.formtable td select {
border: 1px inset #dcdcdc;
font-size: small;
padding: 2px 1px;
+/*
background-color: #f5faff;
+*/
}
p.empty-message {
@@ -402,3 +406,20 @@ ul.checkboxlist li input {
.sortdesc {
background-image: url(/static/images/down.gif) !important;
}
+
+.warning_message {
+ font-size: 120%;
+/*
+ color: #ee0000;
+*/
+ font-weight: bolder;
+}
+
+.fielderror {
+ color: red !important;
+ font-weight: bold;
+}
+
+.requiredfield {
+ background-color: #eebbbb !important;
+}
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/group.py b/ipa-server/ipa-gui/ipagui/subcontrollers/group.py
index dbcc77b9a..cc2944b22 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/group.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/group.py
@@ -271,14 +271,6 @@ class GroupController(IPAController):
rv = client.update_group(new_group)
#
# If the group update succeeds, but below operations fail, we
- if new_group.cn != kw.get('cn'):
- group_modified = True
- new_group.setValue('cn', kw['cn'])
-
- if group_modified:
- rv = client.update_group(new_group)
- #
- # If the group update succeeds, but below operations fail, we
# need to make sure a subsequent submit doesn't try to update
# the group again.
#
@@ -313,7 +305,7 @@ class GroupController(IPAController):
kw['dnadd'] = failed_adds
group_modified = True
except ipaerror.IPAError, e:
- turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
+ turbogears.flash("Updating group membership failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
@@ -331,7 +323,7 @@ class GroupController(IPAController):
kw['dndel'] = failed_dels
group_modified = True
except ipaerror.IPAError, e:
- turbogears.flash("Group update failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
+ turbogears.flash("Updating group membership failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py b/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
index a82b98888..d8237331b 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
@@ -17,6 +17,7 @@ from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ipa.entity
import ipagui.forms.ipapolicy
+from ipagui.helpers import ipahelper
import ldap.dn
@@ -71,6 +72,15 @@ class IPAPolicyController(IPAController):
# Combine the 2 dicts to make the form easier
ipapolicy_dict.update(password_dict)
+ # Load potential multi-valued fields
+ if isinstance(ipapolicy_dict.get('ipauserobjectclasses',''), str):
+ ipapolicy_dict['ipauserobjectclasses'] = [ipapolicy_dict.get('ipauserobjectclasses')]
+ ipapolicy_dict['userobjectclasses'] = ipahelper.setup_mv_fields(ipapolicy_dict.get('ipauserobjectclasses'), 'ipauserobjectclasses')
+
+ if isinstance(ipapolicy_dict.get('ipagroupobjectclasses',''), str):
+ ipapolicy_dict['ipagroupobjectclasses'] = [ipapolicy_dict.get('ipagroupobjectclasses')]
+ ipapolicy_dict['groupobjectclasses'] = ipahelper.setup_mv_fields(ipapolicy_dict.get('ipagroupobjectclasses'), 'ipagroupobjectclasses')
+
return dict(form=ipapolicy_edit_form, ipapolicy=ipapolicy_dict)
except ipaerror.IPAError, e:
turbogears.flash("IPA Policy edit failed: " + str(e) + "<br/>" + str(e.detail))
@@ -88,6 +98,10 @@ class IPAPolicyController(IPAController):
turbogears.flash("Edit policy cancelled")
raise turbogears.redirect('/ipapolicy/show')
+ # Fix incoming multi-valued fields we created for the form
+ kw = ipahelper.fix_incoming_fields(kw, 'ipauserobjectclasses', 'userobjectclasses')
+ kw = ipahelper.fix_incoming_fields(kw, 'ipagroupobjectclasses', 'groupobjectclasses')
+
tg_errors, kw = self.ipapolicyupdatevalidate(**kw)
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
@@ -132,6 +146,12 @@ class IPAPolicyController(IPAController):
if new_ipapolicy.ipadefaultprimarygroup != kw.get('ipadefaultprimarygroup'):
policy_modified = True
new_ipapolicy.setValue('ipadefaultprimarygroup', kw.get('ipadefaultprimarygroup'))
+ if new_ipapolicy.ipauserobjectclasses != kw.get('ipauserobjectclasses'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipauserobjectclasses', kw.get('ipauserobjectclasses'))
+ if new_ipapolicy.ipagroupobjectclasses != kw.get('ipagroupobjectclasses'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipagroupobjectclasses', kw.get('ipagroupobjectclasses'))
if policy_modified:
rv = client.update_ipa_config(new_ipapolicy)
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/principal.py b/ipa-server/ipa-gui/ipagui/subcontrollers/principal.py
new file mode 100644
index 000000000..1b2ad6942
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/principal.py
@@ -0,0 +1,153 @@
+import os
+from pickle import dumps, loads
+from base64 import b64encode, b64decode
+import copy
+import logging
+
+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.principal
+
+import ldap.dn
+
+log = logging.getLogger(__name__)
+
+principal_new_form = ipagui.forms.principal.PrincipalNewForm()
+principal_fields = ['*']
+
+class PrincipalController(IPAController):
+
+ @expose()
+ @identity.require(identity.in_group("admins"))
+ def index(self, tg_errors=None):
+ raise turbogears.redirect("/principal/list")
+
+ @expose("ipagui.templates.principalnew")
+ @identity.require(identity.in_group("admins"))
+ def new(self, tg_errors=None):
+ """Displays the new service principal form"""
+ if tg_errors:
+ turbogears.flash("There were validation errors.<br/>" +
+ "Please see the messages below for details.")
+
+ client = self.get_ipaclient()
+
+ return dict(form=principal_new_form, principal={})
+
+ @expose()
+ @identity.require(identity.in_group("admins"))
+ def create(self, **kw):
+ """Creates a service principal group"""
+ self.restrict_post()
+ client = self.get_ipaclient()
+
+ if kw.get('submit') == 'Cancel':
+ turbogears.flash("Add principal cancelled")
+ raise turbogears.redirect('/')
+
+ tg_errors, kw = self.principalcreatevalidate(**kw)
+ if tg_errors:
+ turbogears.flash("There were validation errors.<br/>" +
+ "Please see the messages below for details.")
+ return dict(form=principal_new_form, principal=kw,
+ tg_template='ipagui.templates.principalnew')
+
+ principal_name = ""
+ hostname = kw.get('hostname')
+ #
+ # Create the principal itself
+ #
+ try:
+ if kw.get('service') == "other":
+ service = kw.get('other')
+ if not service:
+ turbogears.flash("Service type must be provided")
+ return dict(form=principal_new_form, principal=kw,
+ tg_template='ipagui.templates.principalnew')
+ else:
+ service = kw.get('service')
+
+ # The realm is added by add_service_principal
+ principal_name = utf8_encode_values(service + "/" + kw.get('hostname'))
+
+ rv = client.add_service_principal(principal_name)
+ except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
+ turbogears.flash("Service principal '%s' already exists" %
+ principal_name)
+ return dict(form=principal_new_form, principal=kw,
+ tg_template='ipagui.templates.principalnew')
+ except ipaerror.IPAError, e:
+ turbogears.flash("Service principal add failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
+ return dict(form=principal_new_form, principal=kw,
+ tg_template='ipagui.templates.principalnew')
+
+ turbogears.flash("%s added!" % principal_name)
+ raise turbogears.redirect('/principal/list', hostname=hostname)
+
+ @expose("ipagui.templates.principallist")
+ @identity.require(identity.not_anonymous())
+ def list(self, **kw):
+ """Searches for service principals and displays list of results"""
+ client = self.get_ipaclient()
+
+ principals = None
+ counter = 0
+ hostname = kw.get('hostname')
+ if hostname != None and len(hostname) > 0:
+ try:
+ principals = client.find_service_principal(hostname.encode('utf-8'), principal_fields, 0, 2)
+ counter = principals[0]
+ principals = principals[1:]
+
+ if counter == -1:
+ turbogears.flash("These results are truncated.<br />" +
+ "Please refine your search and try again.")
+
+ # For each entry break out service type and hostname
+ for i in range(len(principals)):
+ (service,host) = principals[i].krbprincipalname.split('/')
+ h = host.split('@')
+ principals[i].setValue('service', service)
+ principals[i].setValue('hostname', h[0])
+
+ except ipaerror.IPAError, e:
+ turbogears.flash("principal list failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
+ raise turbogears.redirect("/principal/list")
+
+ return dict(principals=principals, hostname=hostname, fields=ipagui.forms.principal.PrincipalFields())
+
+ @expose()
+ @identity.require(identity.not_anonymous())
+ def show(self, **kw):
+ """Returns the keytab for a given principal"""
+ client = self.get_ipaclient()
+
+ principal = kw.get('principal')
+ if principal != None and len(principal) > 0:
+ try:
+ p = principal.split('@')
+ keytab = client.get_keytab(p[0].encode('utf-8'))
+
+ cherrypy.response.headers['Content-Type'] = "application/x-download"
+ cherrypy.response.headers['Content-Disposition'] = 'attachment; filename=krb5.keytab'
+ cherrypy.response.headers['Content-Length'] = len(keytab)
+ cherrypy.response.body = keytab
+ return cherrypy.response.body
+ except ipaerror.IPAError, e:
+ turbogears.flash("keytab retrieval failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
+ raise turbogears.redirect("/principal/list")
+ raise turbogears.redirect("/principal/list")
+
+ @validate(form=principal_new_form)
+ @identity.require(identity.not_anonymous())
+ def principalcreatevalidate(self, tg_errors=None, **kw):
+ return tg_errors, kw
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
index 39343b595..ea7735842 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
@@ -18,6 +18,7 @@ from ipa.entity import utf8_encode_values
from ipa import ipaerror
import ipagui.forms.user
import ipa.config
+from ipagui.helpers import ipahelper
log = logging.getLogger(__name__)
@@ -83,36 +84,6 @@ class UserController(IPAController):
user_new_form.validator.add_field(s['field'], validator)
user_edit_form.validator.add_field(s['field'], validator)
- def setup_mv_fields(self, field, fieldname):
- """Given a field (must be a list) and field name, convert that
- field into a list of dictionaries of the form:
- [ { fieldname : v1}, { fieldname : v2 }, .. ]
-
- This is how we pre-fill values for multi-valued fields.
- """
- mvlist = []
- if field is not None:
- for v in field:
- mvlist.append({ fieldname : v } )
- else:
- # We need to return an empty value so something can be
- # displayed on the edit page. Otherwise only an Add link
- # will show, not an empty field.
- mvlist.append({ fieldname : '' } )
- return mvlist
-
- def fix_incoming_fields(self, fields, fieldname, multifieldname):
- """This is called by the update() function. It takes the incoming
- list of dictionaries and converts it into back into the original
- field, then removes the multiple field.
- """
- fields[fieldname] = []
- for i in range(len(fields[multifieldname])):
- fields[fieldname].append(fields[multifieldname][i][fieldname])
- del(fields[multifieldname])
-
- return fields
-
@expose()
def index(self):
raise turbogears.redirect("/user/list")
@@ -142,12 +113,12 @@ class UserController(IPAController):
tg_errors, kw = self.usercreatevalidate(**kw)
# Fix incoming multi-valued fields we created for the form
- kw = self.fix_incoming_fields(kw, 'cn', 'cns')
- kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
- kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
- kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles')
- kw = self.fix_incoming_fields(kw, 'pager', 'pagers')
- kw = self.fix_incoming_fields(kw, 'homephone', 'homephones')
+ kw = ipahelper.fix_incoming_fields(kw, 'cn', 'cns')
+ kw = ipahelper.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
+ kw = ipahelper.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
+ kw = ipahelper.fix_incoming_fields(kw, 'mobile', 'mobiles')
+ kw = ipahelper.fix_incoming_fields(kw, 'pager', 'pagers')
+ kw = ipahelper.fix_incoming_fields(kw, 'homephone', 'homephones')
if tg_errors:
turbogears.flash("There were validation errors.<br/>" +
@@ -325,32 +296,34 @@ class UserController(IPAController):
# Load potential multi-valued fields
if isinstance(user_dict['cn'], str):
user_dict['cn'] = [user_dict['cn']]
- user_dict['cns'] = self.setup_mv_fields(user_dict['cn'], 'cn')
+ user_dict['cns'] = ipahelper.setup_mv_fields(user_dict['cn'], 'cn')
if isinstance(user_dict.get('telephonenumber',''), str):
- user_dict['telephonenumber'] = [user_dict.get('telephonenumber'),'']
- user_dict['telephonenumbers'] = self.setup_mv_fields(user_dict.get('telephonenumber'), 'telephonenumber')
+ user_dict['telephonenumber'] = [user_dict.get('telephonenumber')]
+ user_dict['telephonenumbers'] = ipahelper.setup_mv_fields(user_dict.get('telephonenumber'), 'telephonenumber')
if isinstance(user_dict.get('facsimiletelephonenumber',''), str):
- user_dict['facsimiletelephonenumber'] = [user_dict.get('facsimiletelephonenumber'),'']
- user_dict['facsimiletelephonenumbers'] = self.setup_mv_fields(user_dict.get('facsimiletelephonenumber'), 'facsimiletelephonenumber')
+ user_dict['facsimiletelephonenumber'] = [user_dict.get('facsimiletelephonenumber')]
+ user_dict['facsimiletelephonenumbers'] = ipahelper.setup_mv_fields(user_dict.get('facsimiletelephonenumber'), 'facsimiletelephonenumber')
if isinstance(user_dict.get('mobile',''), str):
- user_dict['mobile'] = [user_dict.get('mobile'),'']
- user_dict['mobiles'] = self.setup_mv_fields(user_dict.get('mobile'), 'mobile')
+ user_dict['mobile'] = [user_dict.get('mobile')]
+ user_dict['mobiles'] = ipahelper.setup_mv_fields(user_dict.get('mobile'), 'mobile')
if isinstance(user_dict.get('pager',''), str):
- user_dict['pager'] = [user_dict.get('pager'),'']
- user_dict['pagers'] = self.setup_mv_fields(user_dict.get('pager'), 'pager')
+ user_dict['pager'] = [user_dict.get('pager')]
+ user_dict['pagers'] = ipahelper.setup_mv_fields(user_dict.get('pager'), 'pager')
if isinstance(user_dict.get('homephone',''), str):
- user_dict['homephone'] = [user_dict.get('homephone'),'']
- user_dict['homephones'] = self.setup_mv_fields(user_dict.get('homephone'), 'homephone')
+ user_dict['homephone'] = [user_dict.get('homephone')]
+ user_dict['homephones'] = ipahelper.setup_mv_fields(user_dict.get('homephone'), 'homephone')
# Edit shouldn't fill in the password field.
if user_dict.has_key('userpassword'):
del(user_dict['userpassword'])
+ user_dict['uid_hidden'] = user_dict.get('uid')
+
user_groups = client.get_groups_by_member(user.dn, ['dn', 'cn'])
user_groups.sort(self.sort_by_cn)
user_groups_dicts = map(lambda group: group.toDict(), user_groups)
@@ -398,17 +371,20 @@ class UserController(IPAController):
self.restrict_post()
client = self.get_ipaclient()
+ if not kw.get('uid'):
+ kw['uid'] = kw.get('uid_hidden')
+
if kw.get('submit') == 'Cancel Edit':
turbogears.flash("Edit user cancelled")
raise turbogears.redirect('/user/show', uid=kw.get('uid'))
# Fix incoming multi-valued fields we created for the form
- kw = self.fix_incoming_fields(kw, 'cn', 'cns')
- kw = self.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
- kw = self.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
- kw = self.fix_incoming_fields(kw, 'mobile', 'mobiles')
- kw = self.fix_incoming_fields(kw, 'pager', 'pagers')
- kw = self.fix_incoming_fields(kw, 'homephone', 'homephones')
+ kw = ipahelper.fix_incoming_fields(kw, 'cn', 'cns')
+ kw = ipahelper.fix_incoming_fields(kw, 'telephonenumber', 'telephonenumbers')
+ kw = ipahelper.fix_incoming_fields(kw, 'facsimiletelephonenumber', 'facsimiletelephonenumbers')
+ kw = ipahelper.fix_incoming_fields(kw, 'mobile', 'mobiles')
+ kw = ipahelper.fix_incoming_fields(kw, 'pager', 'pagers')
+ kw = ipahelper.fix_incoming_fields(kw, 'homephone', 'homephones')
# admins and editors can update anybody. A user can only update
# themselves. We need this check because it is very easy to guess
@@ -430,6 +406,12 @@ class UserController(IPAController):
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
+ # We don't want to inadvertantly add this to a record
+ try:
+ del kw['uid_hidden']
+ except KeyError:
+ pass
+
password_change = False
user_modified = False
@@ -488,6 +470,7 @@ class UserController(IPAController):
new_user.setValue('uidnumber', str(kw.get('uidnumber')))
new_user.setValue('gidnumber', str(kw.get('gidnumber')))
new_user.setValue('homedirectory', str(kw.get('homedirectory')))
+ new_user.setValue('uid', str(kw.get('uid')))
for custom_field in user_edit_form.custom_fields:
new_user.setValue(custom_field.name,
diff --git a/ipa-server/ipa-gui/ipagui/templates/delegateform.kid b/ipa-server/ipa-gui/ipagui/templates/delegateform.kid
index 62cc710f0..4eb846d53 100644
--- a/ipa-server/ipa-gui/ipagui/templates/delegateform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/delegateform.kid
@@ -180,4 +180,10 @@
</script>
</form>
+
+
+ <script type="text/javascript">
+ document.getElementById("form_name").focus();
+ </script>
+
</div>
diff --git a/ipa-server/ipa-gui/ipagui/templates/groupnewform.kid b/ipa-server/ipa-gui/ipagui/templates/groupnewform.kid
index 2b6e2ebb2..04a9a9e70 100644
--- a/ipa-server/ipa-gui/ipagui/templates/groupnewform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/groupnewform.kid
@@ -98,6 +98,10 @@ from ipagui.helpers import ipahelper
</form>
<script type="text/javascript">
+ document.getElementById("form_cn").focus();
+ </script>
+
+ <script type="text/javascript">
/*
* This section restores the contents of the add and remove lists
* dynamically if we have to refresh the page
diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
index 106657636..9584e4457 100644
--- a/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
@@ -15,6 +15,8 @@ from ipagui.helpers import ipahelper
<script type="text/javascript" charset="utf-8"
src="${tg.url('/static/javascript/dynamicedit.js')}"></script>
+ <script type="text/javascript" charset="utf-8"
+ src="${tg.url('/tg_widgets/tg_expanding_form_widget/javascript/expanding_form.js')}"></script>
<div py:for="field in hidden_fields"
py:replace="field.display(value_for(field), **params_for(field))"
@@ -170,7 +172,80 @@ from ipagui.helpers import ipahelper
py:content="tg.errors.get('ipadefaultprimarygroup')" />
</td>
</tr>
+ <tr>
+ <th>
+ <label class="fieldlabel" for="${ipapolicy_fields.userobjectclasses.field_id}"
+ py:content="ipapolicy_fields.userobjectclasses.label" />:
+ </th>
+ <td colspan="3">
+ <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${ipapolicy_fields.userobjectclasses.field_id}">
+ <tbody>
+ <?python repetition = 0
+ fld_index = 0
+ fld_error = tg.errors.get('ipauserobjectclasses')
+ ?>
+ <tr py:for="fld in value_for(ipapolicy_fields.ipauserobjectclasses)"
+ id="${ipapolicy_fields.userobjectclasses.field_id}_${repetition}"
+ class="${ipapolicy_fields.userobjectclasses.field_class}">
+
+ <td py:for="field in ipapolicy_fields.userobjectclasses.fields">
+ <span><input class="textfield" type="text" id="${ipapolicy_fields.userobjectclasses.field_id}_${repetition}_ipauserobjectclasses" name="userobjectclasses-${repetition}.ipauserobjectclasses" value="${fld}"/></span>
+ <span py:if="fld_error and fld_error[fld_index]" class="fielderror"
+ py:content="tg.errors.get('ipauserobjectclasses')" />
+ </td>
+ <?python fld_index = fld_index + 1 ?>
+ <td>
+ <a
+ href="javascript:ExpandingForm.removeItem('${ipapolicy_fields.userobjectclasses.field_id}_${repetition}')">Remove</a>
+ </td>
+ <?python repetition = repetition + 1?>
+ </tr>
+ </tbody>
+ </table>
+ <a id="${ipapolicy_fields.userobjectclasses.field_id}_doclink" href="javascript:ExpandingForm.addItem('${ipapolicy_fields.userobjectclasses.field_id}');">Add User Object Class</a>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <label class="fieldlabel" for="${ipapolicy_fields.groupobjectclasses.field_id}"
+ py:content="ipapolicy_fields.groupobjectclasses.label" />:
+ </th>
+ <td colspan="3">
+ <table class="formtable" cellpadding="2" cellspacing="0" border="0" id="${ipapolicy_fields.groupobjectclasses.field_id}">
+ <tbody>
+ <?python repetition = 0
+ fld_index = 0
+ fld_error = tg.errors.get('ipagroupobjectclasses')
+ ?>
+ <tr py:for="fld in value_for(ipapolicy_fields.ipagroupobjectclasses)"
+ id="${ipapolicy_fields.groupobjectclasses.field_id}_${repetition}"
+ class="${ipapolicy_fields.groupobjectclasses.field_class}">
+
+ <td py:for="field in ipapolicy_fields.groupobjectclasses.fields">
+ <span><input class="textfield" type="text" id="${ipapolicy_fields.groupobjectclasses.field_id}_${repetition}_ipagroupobjectclasses" name="groupobjectclasses-${repetition}.ipagroupobjectclasses" value="${fld}"/></span>
+ <span py:if="fld_error and fld_error[fld_index]" class="fielderror"
+ py:content="tg.errors.get('ipagroupobjectclasses')" />
+ </td>
+ <?python fld_index = fld_index + 1 ?>
+ <td>
+ <a
+ href="javascript:ExpandingForm.removeItem('${ipapolicy_fields.groupobjectclasses.field_id}_${repetition}')">Remove</a>
+ </td>
+ <?python repetition = repetition + 1?>
+ </tr>
+ </tbody>
+ </table>
+ <a id="${ipapolicy_fields.groupobjectclasses.field_id}_doclink" href="javascript:ExpandingForm.addItem('${ipapolicy_fields.groupobjectclasses.field_id}');">Add Group Object Class</a>
+ </td>
+ </tr>
</table>
+
+ <hr/>
+
+ <input type="submit" class="submitbutton" name="submit"
+ value="Update Policy"/>
+ <input type="submit" class="submitbutton" name="submit"
+ value="Cancel Edit" />
</form>
</div>
diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
index 089fb494e..50c7d6d8a 100644
--- a/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
@@ -15,6 +15,9 @@ edit_url = tg.url('/ipapolicy/edit')
<script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
<h1>Manage IPA Policy</h1>
+ <input class="submitbutton" type="button"
+ onclick="document.location.href='${edit_url}'"
+ value="Edit Policy" />
<h2 class="formsection">Search</h2>
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
@@ -109,6 +112,46 @@ edit_url = tg.url('/ipapolicy/edit')
</th>
<td>${ipapolicy.get("ipadefaultprimarygroup")}</td>
</tr>
+ <tr>
+ <th>
+ <label class="fieldlabel" py:content="fields.ipauserobjectclasses.label" />:
+ </th>
+ <td>
+ <table cellpadding="2" cellspacing="0" border="0">
+ <tbody>
+ <?python
+ index = 0
+ values = ipapolicy.get("ipauserobjectclasses", '')
+ if isinstance(values, str):
+ values = [values]
+ ?>
+ <tr py:for="index in range(len(values))">
+ <td>${values[index]}</td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <label class="fieldlabel" py:content="fields.ipagroupobjectclasses.label" />:
+ </th>
+ <td>
+ <table cellpadding="2" cellspacing="0" border="0">
+ <tbody>
+ <?python
+ index = 0
+ values = ipapolicy.get("ipagroupobjectclasses", '')
+ if isinstance(values, str):
+ values = [values]
+ ?>
+ <tr py:for="index in range(len(values))">
+ <td>${values[index]}</td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
</table>
<hr />
<input class="submitbutton" type="button"
diff --git a/ipa-server/ipa-gui/ipagui/templates/master.kid b/ipa-server/ipa-gui/ipagui/templates/master.kid
index 12e54fa1d..e11497546 100644
--- a/ipa-server/ipa-gui/ipagui/templates/master.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/master.kid
@@ -78,10 +78,14 @@
<li><a href="${tg.url('/group/list')}">Find Groups</a></li>
</ul>
<ul py:if="'admins' in tg.identity.groups">
+ <li><a href="${tg.url('/principal/new')}">Add Service Principal</a></li>
+ <li><a href="${tg.url('/principal/list')}">Find Service Principal</a></li>
+ </ul>
+ <ul py:if="'admins' in tg.identity.groups">
<li><a href="${tg.url('/policy/index')}">Manage Policy</a></li>
</ul>
<ul>
- <li><a href="${tg.url('/user/edit/', principal=tg.identity.user.display_name)}">Self Service</a></li>
+ <li py:if="not tg.identity.anonymous"><a href="${tg.url('/user/edit/', principal=tg.identity.user.display_name)}">Self Service</a></li>
</ul>
<ul py:if="'admins' in tg.identity.groups">
<li><a href="${tg.url('/delegate/list')}">Delegations</a></li>
diff --git a/ipa-server/ipa-gui/ipagui/templates/principallayout.kid b/ipa-server/ipa-gui/ipagui/templates/principallayout.kid
new file mode 100644
index 000000000..1e7bef2e7
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/templates/principallayout.kid
@@ -0,0 +1,19 @@
+<!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="details">
+ <div id="alertbox" py:if="value_of('tg_flash', None)">
+ <p py:content="XML(tg_flash)"></p></div>
+
+ <div py:replace="[item.text]+item[:]"></div>
+ </div>
+
+ </div>
+</body>
+
+</html>
diff --git a/ipa-server/ipa-gui/ipagui/templates/principallist.kid b/ipa-server/ipa-gui/ipagui/templates/principallist.kid
new file mode 100644
index 000000000..dcd9dd4b8
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/templates/principallist.kid
@@ -0,0 +1,64 @@
+<!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="'principallayout.kid'">
+<head>
+<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
+<title>Find Service Principals</title>
+</head>
+<body>
+ <h1>Find Service Principals</h1>
+ <script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
+ <div id="search">
+ <form action="${tg.url('/principal/list')}" method="get">
+ <input id="hostname" type="text" name="hostname" value="${hostname}" />
+ <input class="searchbutton" type="submit" value="Find Hosts"/>
+ </form>
+ <script type="text/javascript">
+ document.getElementById("hostname").focus();
+ </script>
+ </div>
+ <div py:if='(principals != None) and (len(principals) > 0)'>
+ <h2>${len(principals)} results returned:</h2>
+ <table id="resultstable" class="details sortable resizable" cellspacing="0">
+ <thead>
+ <tr>
+ <th>
+ Hostname
+ </th>
+ <th>
+ Service
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr py:for="principal in principals">
+ <td>
+ <a href="${tg.url('/principal/show',principal=principal.krbprincipalname)}"
+ >${principal.hostname}</a>
+ </td>
+ <td>
+ ${principal.service}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div id="alertbox" py:if='(principals != None) and (len(principals) == 0)'>
+ <p id="alertbox">No results found for "${hostname}"</p>
+ </div>
+
+ <div class="instructions" py:if='principals == None'>
+ <p>
+ Exact matches are listed first, followed by partial matches. If your search
+ is too broad, you will get a warning that the search returned too many
+ results. Try being more specific.
+ </p>
+ <p>
+ The results that come back are sortable. Simply click on a column
+ header to sort on that header. A triangle will indicate the sorted
+ column, along with its direction. Clicking and dragging between headers
+ will allow you to resize the header.
+ </p>
+ </div>
+</body>
+</html>
diff --git a/ipa-server/ipa-gui/ipagui/templates/principalnew.kid b/ipa-server/ipa-gui/ipagui/templates/principalnew.kid
new file mode 100644
index 000000000..b4ba3179c
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/templates/principalnew.kid
@@ -0,0 +1,13 @@
+<!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="'principallayout.kid'">
+<head>
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
+ <title>Add Service Principal</title>
+</head>
+<body>
+ <h1>Add Service Principal</h1>
+
+ ${form.display(action=tg.url('/principal/create'), value=principal)}
+</body>
+</html>
diff --git a/ipa-server/ipa-gui/ipagui/templates/principalnewform.kid b/ipa-server/ipa-gui/ipagui/templates/principalnewform.kid
new file mode 100644
index 000000000..25b54621e
--- /dev/null
+++ b/ipa-server/ipa-gui/ipagui/templates/principalnewform.kid
@@ -0,0 +1,102 @@
+<div xmlns:py="http://purl.org/kid/ns#"
+ class="simpleroster">
+ <form action="${action}" name="${name}" method="${method}" class="tableform"
+ onsubmit="preSubmit()" >
+
+ <input type="submit" class="submitbutton" name="submit" value="Add Principal"/>
+
+<?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('/principal/edit_search') ?>
+
+ <script type="text/javascript">
+ function toggleOther(field) {
+ otherField = document.getElementById('form_other');
+ var e=document.getElementById(field).value;
+ if ( e == "other") {
+ otherField.disabled = false;
+ } else {
+ otherField.disabled =true;
+ }
+ }
+
+ function doSearch() {
+ $('searchresults').update("Searching...");
+ new Ajax.Updater('searchresults',
+ '${searchurl}',
+ { asynchronous:true,
+ parameters: { criteria: $('criteria').value },
+ evalScripts: true });
+ return false;
+ }
+ </script>
+
+ <div py:for="field in hidden_fields"
+ py:replace="field.display(value_for(field), **params_for(field))"
+ />
+
+ <h2 class="formsection">Service Principal Details</h2>
+ <table class="formtable" cellpadding="2" cellspacing="0" border="0">
+ <tr>
+ <th>
+ <label class="fieldlabel" for="${principal_fields.hostname.field_id}"
+ py:content="principal_fields.hostname.label" />:
+ </th>
+ <td>
+ <span py:replace="principal_fields.hostname.display(value_for(principal_fields.hostname))" />
+ <span py:if="tg.errors.get('hostname')" class="fielderror"
+ py:content="tg.errors.get('hostname')" />
+
+ </td>
+ </tr>
+
+ <tr>
+ <th>
+ <label class="fieldlabel" for="${principal_fields.service.field_id}"
+ py:content="principal_fields.service.label" />:
+ </th>
+ <td>
+ <span py:replace="principal_fields.service.display(value_for(principal_fields.service))" />
+ <span py:if="tg.errors.get('service')" class="fielderror"
+ py:content="tg.errors.get('service')" />
+
+ </td>
+ </tr>
+
+ <tr>
+ <th>
+ <label class="fieldlabel" for="${principal_fields.other.field_id}"
+ py:content="principal_fields.other.label" />:
+ </th>
+ <td>
+ <span py:replace="principal_fields.other.display(value_for(principal_fields.other))" />
+ <span py:if="tg.errors.get('other')" class="fielderror"
+ py:content="tg.errors.get('other')" />
+ <script type="text/javascript">
+ var e=document.getElementById('form_service').value;
+ if ( e != "other") {
+ document.getElementById('form_other').disabled = true;
+ }
+ </script>
+
+ </td>
+ </tr>
+
+ </table>
+
+<hr />
+
+ <input type="submit" class="submitbutton" name="submit" value="Add Principal"/>
+
+ </form>
+
+ <script type="text/javascript">
+ document.getElementById("form_hostname").focus();
+ </script>
+
+</div>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
index 88b778d8c..5bf533432 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usereditform.kid
@@ -38,12 +38,14 @@ from ipagui.helpers import ipahelper
function toggleProtectedFields(checkbox) {
passwordField = document.getElementById('form_userpassword');
passwordConfirmField = document.getElementById('form_userpassword_confirm');
+ uidField = document.getElementById('form_uid');
uidnumberField = document.getElementById('form_uidnumber');
gidnumberField = document.getElementById('form_gidnumber');
homedirectoryField = document.getElementById('form_homedirectory');
if (checkbox.checked) {
passwordField.disabled = false;
passwordConfirmField.disabled = false;
+ uidField.disabled = false;
uidnumberField.disabled = false;
gidnumberField.disabled = false;
homedirectoryField.disabled = false;
@@ -51,6 +53,7 @@ from ipagui.helpers import ipahelper
} else {
passwordField.disabled = true;
passwordConfirmField.disabled = true;
+ uidField.disabled = true;
uidnumberField.disabled = true;
gidnumberField.disabled = true;
homedirectoryField.disabled = true;
@@ -58,6 +61,13 @@ from ipagui.helpers import ipahelper
}
}
+ function warnRDN() {
+ if (confirm("Are you sure you want to change the login name?<br/>This can have unexpected results. A password change is required.")) {
+ return true;
+ }
+ return false;
+ }
+
function doSearch() {
$('searchresults').update("Searching...");
new Ajax.Updater('searchresults',
@@ -215,13 +225,21 @@ from ipagui.helpers import ipahelper
py:content="tg.errors.get('nsAccountLock')" />
</td>
</tr>
+
<tr>
<th>
<label class="fieldlabel" for="${user_fields.uid.field_id}"
py:content="user_fields.uid.label" />:
</th>
<td>
- ${value_for(user_fields.uid)}
+ <span py:replace="user_fields.uid.display(
+ value_for(user_fields.uid))" />
+ <span py:if="tg.errors.get('uid')" class="fielderror"
+ py:content="tg.errors.get('uid')" />
+
+ <script type="text/javascript">
+ document.getElementById('form_uid').disabled = true;
+ </script>
</td>
</tr>
diff --git a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
index 97be52732..83dc64634 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usernewform.kid
@@ -791,6 +791,10 @@ from ipagui.helpers import ipahelper
</form>
<script type="text/javascript">
+ document.getElementById("form_title").focus();
+ </script>
+
+ <script type="text/javascript">
/*
* This section restores the contents of the add and remove lists
* dynamically if we have to refresh the page
diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
index 614988729..053721230 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
@@ -11,7 +11,7 @@ edit_url = tg.url('/user/edit', uid=user.get('uid'))
?>
<h1>View User</h1>
- <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
+ <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups or tg.identity.display_name == user.get('uid')"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit User" />
@@ -374,7 +374,7 @@ else:
<br/>
<hr />
- <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups"
+ <input py:if="'editors' in tg.identity.groups or 'admins' in tg.identity.groups or tg.identity.display_name == user.get('uid')"
class="submitbutton" type="button"
onclick="document.location.href='${edit_url}'"
value="Edit User" />
diff --git a/ipa-server/ipa-install/ipa-server-install b/ipa-server/ipa-install/ipa-server-install
index eb1c37bb0..646512d51 100644
--- a/ipa-server/ipa-install/ipa-server-install
+++ b/ipa-server/ipa-install/ipa-server-install
@@ -226,7 +226,7 @@ def read_dm_password():
print "Certain directory server operations require an administrative user."
print "This user is referred to as the Directory Manager and has full access"
print "to the Directory for system management tasks."
- print "The password must be at least 8 characters long, and contain no spaces."
+ print "The password must be at least 8 characters long."
print ""
#TODO: provide the option of generating a random password
dm_password = read_password("Directory Manager")
diff --git a/ipa-server/ipa-install/share/60ipaconfig.ldif b/ipa-server/ipa-install/share/60ipaconfig.ldif
index e15d4a417..552120991 100644
--- a/ipa-server/ipa-install/share/60ipaconfig.ldif
+++ b/ipa-server/ipa-install/share/60ipaconfig.ldif
@@ -27,11 +27,15 @@ attributetypes: ( 2.16.840.1.113730.3.8.1.7 NAME 'ipaDefaultLoginShell' EQUALITY
attributetypes: ( 2.16.840.1.113730.3.8.1.8 NAME 'ipaDefaultPrimaryGroup' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE)
## ipaMaxUsernameLength - maximum username length to allow in the UI
attributetypes: ( 2.16.840.1.113730.3.8.1.9 NAME 'ipaMaxUsernameLength' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
-## ipaPwdExpAdvNotify - time in days to send out paswwrod expiration notification before passwpord actually expires
+## ipaPwdExpAdvNotify - time in days to send out paswword expiration notification before passwpord actually expires
attributetypes: ( 2.16.840.1.113730.3.8.1.10 NAME 'ipaPwdExpAdvNotify' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)
+# ipaUserObjectClasses - required objectclasses for users
+attributetypes: ( 2.16.840.1.113730.3.8.1.11 NAME 'ipaUserObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
+# ipaGroupObjectClasses - required objectclasses for groups
+attributetypes: ( 2.16.840.1.113730.3.8.1.12 NAME 'ipaGroupObjectClasses' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
###############################################
##
## ObjectClasses
##
## ipaGuiConfig - GUI config parameters objectclass
-objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify ) )
+objectClasses: ( 2.16.840.1.113730.3.8.2.1 NAME 'ipaGuiConfig' AUXILIARY MAY ( ipaUserSearchFields $ ipaGroupSearchFields $ ipaSearchTimeLimit $ ipaSearchRecordsLimit $ ipaCustomFields $ ipaHomesRootDir $ ipaDefaultLoginShell $ ipaDefaultPrimaryGroup $ ipaMaxUsernameLength $ ipaPwdExpAdvNotify $ ipaUserObjectClasses $ ipaGroupObjectClasses) )
diff --git a/ipa-server/ipa-install/share/bootstrap-template.ldif b/ipa-server/ipa-install/share/bootstrap-template.ldif
index fb124a790..3b79dfb62 100644
--- a/ipa-server/ipa-install/share/bootstrap-template.ldif
+++ b/ipa-server/ipa-install/share/bootstrap-template.ldif
@@ -123,6 +123,18 @@ ipaDefaultLoginShell: /bin/sh
ipaDefaultPrimaryGroup: ipausers
ipaMaxUsernameLength: 8
ipaPwdExpAdvNotify: 4
+ipaGroupObjectClasses: top
+ipaGroupObjectClasses: groupofnames
+ipaGroupObjectClasses: posixGroup
+ipaGroupObjectClasses: inetUser
+ipaUserObjectClasses: top
+ipaUserObjectClasses: person
+ipaUserObjectClasses: organizationalPerson
+ipaUserObjectClasses: inetOrgPerson
+ipaUserObjectClasses: inetUser
+ipaUserObjectClasses: posixAccount
+ipaUserObjectClasses: krbPrincipalAux
+ipaUserObjectClasses: radiusprofile
dn: cn=account inactivation,cn=accounts,$SUFFIX
changetype: add
diff --git a/ipa-server/ipa-server.spec b/ipa-server/ipa-server.spec
index 81cf7d3d2..f84affe86 100755
--- a/ipa-server/ipa-server.spec
+++ b/ipa-server/ipa-server.spec
@@ -15,6 +15,7 @@ BuildRequires: openssl-devel
BuildRequires: openldap-devel
BuildRequires: krb5-devel
BuildRequires: nss-devel
+BuildRequires: libcap-devel
Requires: ipa-python
Requires: ipa-admintools
@@ -38,6 +39,7 @@ Requires: python-tgexpandingformwidget
Requires: acl
Requires: freeradius
Requires: pyasn1
+Requires: libcap
%define httpd_conf /etc/httpd/conf.d
%define plugin_dir %{_libdir}/dirsrv/plugins
diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py
index 8735ebdaa..08b86035e 100644
--- a/ipa-server/ipaserver/dsinstance.py
+++ b/ipa-server/ipaserver/dsinstance.py
@@ -18,16 +18,15 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-import subprocess
-import string
-import tempfile
import shutil
import logging
import pwd
import glob
import sys
+import os
+
+from ipa import ipautil
-from ipa.ipautil import *
import service
import installutils
@@ -36,7 +35,7 @@ SERVER_ROOT_32 = "/usr/lib/dirsrv"
def ldap_mod(fd, dn, pwd):
args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", "-D", dn, "-w", pwd, "-f", fd.name]
- run(args)
+ ipautil.run(args)
def realm_to_suffix(realm_name):
s = realm_name.split(".")
@@ -44,7 +43,7 @@ def realm_to_suffix(realm_name):
return ",".join(terms)
def find_server_root():
- if dir_exists(SERVER_ROOT_64):
+ if ipautil.dir_exists(SERVER_ROOT_64):
return SERVER_ROOT_64
else:
return SERVER_ROOT_32
@@ -83,7 +82,7 @@ def check_existing_installation():
sys.exit(1)
try:
- run(["/sbin/service", "dirsrv", "stop"])
+ ipautil.run(["/sbin/service", "dirsrv", "stop"])
except:
pass
for d in dirs:
@@ -185,25 +184,25 @@ class DsInstance(service.Service):
logging.debug("adding ds user %s" % self.ds_user)
args = ["/usr/sbin/useradd", "-c", "DS System User", "-d", "/var/lib/dirsrv", "-M", "-r", "-s", "/sbin/nologin", self.ds_user]
try:
- run(args)
+ ipautil.run(args)
logging.debug("done adding user")
except ipautil.CalledProcessError, e:
logging.critical("failed to add user %s" % e)
def __create_instance(self):
self.step("creating directory server instance")
- inf_txt = template_str(INF_TEMPLATE, self.sub_dict)
+ inf_txt = ipautil.template_str(INF_TEMPLATE, self.sub_dict)
logging.debug(inf_txt)
- inf_fd = write_tmp_file(inf_txt)
+ inf_fd = ipautil.write_tmp_file(inf_txt)
logging.debug("writing inf template")
- if file_exists("/usr/sbin/setup-ds.pl"):
+ if ipautil.file_exists("/usr/sbin/setup-ds.pl"):
args = ["/usr/sbin/setup-ds.pl", "--silent", "--logfile", "-", "-f", inf_fd.name]
logging.debug("calling setup-ds.pl")
else:
args = ["/usr/bin/ds_newinst.pl", inf_fd.name]
logging.debug("calling ds_newinst.pl")
try:
- run(args)
+ ipautil.run(args)
logging.debug("completed creating ds instance")
except ipautil.CalledProcessError, e:
logging.critical("failed to restart ds instance %s" % e)
@@ -217,19 +216,19 @@ class DsInstance(service.Service):
def __add_default_schemas(self):
self.step("adding default schema")
- shutil.copyfile(SHARE_DIR + "60kerberos.ldif",
+ shutil.copyfile(ipautil.SHARE_DIR + "60kerberos.ldif",
schema_dirname(self.realm_name) + "60kerberos.ldif")
- shutil.copyfile(SHARE_DIR + "60samba.ldif",
+ shutil.copyfile(ipautil.SHARE_DIR + "60samba.ldif",
schema_dirname(self.realm_name) + "60samba.ldif")
- shutil.copyfile(SHARE_DIR + "60radius.ldif",
+ shutil.copyfile(ipautil.SHARE_DIR + "60radius.ldif",
schema_dirname(self.realm_name) + "60radius.ldif")
- shutil.copyfile(SHARE_DIR + "60ipaconfig.ldif",
+ shutil.copyfile(ipautil.SHARE_DIR + "60ipaconfig.ldif",
schema_dirname(self.realm_name) + "60ipaconfig.ldif")
def __add_memberof_module(self):
self.step("enabling memboerof plugin")
- memberof_txt = template_file(SHARE_DIR + "memberof-conf.ldif", self.sub_dict)
- memberof_fd = write_tmp_file(memberof_txt)
+ memberof_txt = ipautil.template_file(ipautil.SHARE_DIR + "memberof-conf.ldif", self.sub_dict)
+ memberof_fd = ipautil.write_tmp_file(memberof_txt)
try:
ldap_mod(memberof_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@@ -238,8 +237,8 @@ class DsInstance(service.Service):
def __init_memberof(self):
self.step("initializing group membership")
- memberof_txt = template_file(SHARE_DIR + "memberof-task.ldif", self.sub_dict)
- memberof_fd = write_tmp_file(memberof_txt)
+ memberof_txt = ipautil.template_file(ipautil.SHARE_DIR + "memberof-task.ldif", self.sub_dict)
+ memberof_fd = ipautil.write_tmp_file(memberof_txt)
try:
ldap_mod(memberof_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@@ -248,8 +247,8 @@ class DsInstance(service.Service):
def __add_referint_module(self):
self.step("enabling referential integrity plugin")
- referint_txt = template_file(SHARE_DIR + "referint-conf.ldif", self.sub_dict)
- referint_fd = write_tmp_file(referint_txt)
+ referint_txt = ipautil.template_file(ipautil.SHARE_DIR + "referint-conf.ldif", self.sub_dict)
+ referint_fd = ipautil.write_tmp_file(referint_txt)
try:
ldap_mod(referint_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@@ -258,8 +257,8 @@ class DsInstance(service.Service):
def __add_dna_module(self):
self.step("enabling distributed numeric assignment plugin")
- dna_txt = template_file(SHARE_DIR + "dna-conf.ldif", self.sub_dict)
- dna_fd = write_tmp_file(dna_txt)
+ dna_txt = ipautil.template_file(ipautil.SHARE_DIR + "dna-conf.ldif", self.sub_dict)
+ dna_fd = ipautil.write_tmp_file(dna_txt)
try:
ldap_mod(dna_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@@ -268,8 +267,8 @@ class DsInstance(service.Service):
def __config_uidgid_gen_first_master(self):
self.step("configuring Posix uid/gid generation as first master")
- dna_txt = template_file(SHARE_DIR + "dna-posix.ldif", self.sub_dict)
- dna_fd = write_tmp_file(dna_txt)
+ dna_txt = ipautil.template_file(ipautil.SHARE_DIR + "dna-posix.ldif", self.sub_dict)
+ dna_fd = ipautil.write_tmp_file(dna_txt)
try:
ldap_mod(dna_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@@ -278,8 +277,8 @@ class DsInstance(service.Service):
def __add_master_entry_first_master(self):
self.step("adding master entry as first master")
- master_txt = template_file(SHARE_DIR + "master-entry.ldif", self.sub_dict)
- master_fd = write_tmp_file(master_txt)
+ master_txt = ipautil.template_file(ipautil.SHARE_DIR + "master-entry.ldif", self.sub_dict)
+ master_fd = ipautil.write_tmp_file(master_txt)
try:
ldap_mod(master_fd, "cn=Directory Manager", self.dm_password)
except ipautil.CalledProcessError, e:
@@ -292,20 +291,20 @@ class DsInstance(service.Service):
args = ["/usr/share/ipa/ipa-server-setupssl", self.dm_password,
dirname, self.host_name]
try:
- run(args)
+ ipautil.run(args)
logging.debug("done configuring ssl for ds instance")
except ipautil.CalledProcessError, e:
logging.critical("Failed to configure ssl in ds instance %s" % e)
def __add_default_layout(self):
self.step("adding default layout")
- txt = template_file(SHARE_DIR + "bootstrap-template.ldif", self.sub_dict)
- inf_fd = write_tmp_file(txt)
+ txt = ipautil.template_file(ipautil.SHARE_DIR + "bootstrap-template.ldif", self.sub_dict)
+ inf_fd = ipautil.write_tmp_file(txt)
logging.debug("adding default dfrom ipa.ipautil import *s layout")
args = ["/usr/bin/ldapmodify", "-xv", "-D", "cn=Directory Manager",
"-w", self.dm_password, "-f", inf_fd.name]
try:
- run(args)
+ ipautil.run(args)
logging.debug("done adding default ds layout")
except ipautil.CalledProcessError, e:
print "Failed to add default ds layout", e
@@ -313,13 +312,13 @@ class DsInstance(service.Service):
def __create_indeces(self):
self.step("creating indeces")
- txt = template_file(SHARE_DIR + "indeces.ldif", self.sub_dict)
- inf_fd = write_tmp_file(txt)
+ txt = ipautil.template_file(ipautil.SHARE_DIR + "indeces.ldif", self.sub_dict)
+ inf_fd = ipautil.write_tmp_file(txt)
logging.debug("adding/updating indeces")
args = ["/usr/bin/ldapmodify", "-xv", "-D", "cn=Directory Manager",
"-w", self.dm_password, "-f", inf_fd.name]
try:
- run(args)
+ ipautil.run(args)
logging.debug("done adding/updating indeces")
except ipautil.CalledProcessError, e:
logging.critical("Failed to add/update indeces %s" % str(e))
@@ -327,7 +326,7 @@ class DsInstance(service.Service):
def __certmap_conf(self):
self.step("configuring certmap.conf")
dirname = config_dirname(self.realm_name)
- certmap_conf = template_file(SHARE_DIR+"certmap.conf.template", self.sub_dict)
+ certmap_conf = ipautil.template_file(ipautil.SHARE_DIR + "certmap.conf.template", self.sub_dict)
certmap_fd = open(dirname+"certmap.conf", "w+")
certmap_fd.write(certmap_conf)
certmap_fd.close()
@@ -335,7 +334,7 @@ class DsInstance(service.Service):
def change_admin_password(self, password):
logging.debug("Changing admin password")
dirname = config_dirname(self.realm_name)
- if dir_exists("/usr/lib64/mozldap"):
+ if ipautil.dir_exists("/usr/lib64/mozldap"):
app = "/usr/lib64/mozldap/ldappasswd"
else:
app = "/usr/lib/mozldap/ldappasswd"
@@ -344,7 +343,7 @@ class DsInstance(service.Service):
"-P", dirname+"/cert8.db", "-ZZZ", "-s", password,
"uid=admin,cn=sysaccounts,cn=etc,"+self.suffix]
try:
- run(args)
+ ipautil.run(args)
logging.debug("ldappasswd done")
except ipautil.CalledProcessError, e:
print "Unable to set admin password", e
diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py
index ef8becec5..b1a9ea56c 100644
--- a/ipa-server/ipaserver/ipaldap.py
+++ b/ipa-server/ipaserver/ipaldap.py
@@ -356,13 +356,13 @@ class IPAdmin(SimpleLDAPObject):
type, obj = self.result(res)
except ldap.NO_SUCH_OBJECT:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
- "no such entry for " + str(args))
+ notfound(args))
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
if not obj:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
- "no such entry for " + str(args))
+ notfound(args))
elif isinstance(obj,Entry):
return obj
else: # assume list/tuple
@@ -386,7 +386,7 @@ class IPAdmin(SimpleLDAPObject):
if not obj:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
- "no such entry for " + str(args))
+ notfound(args))
all_users = []
for s in obj:
@@ -424,7 +424,7 @@ class IPAdmin(SimpleLDAPObject):
if not entries:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
- "no such entry for " + str(args))
+ notfound(args))
if partial == 1:
counter = -1
@@ -717,3 +717,16 @@ class IPAdmin(SimpleLDAPObject):
"""Returns True if the given string is a DN, False otherwise."""
return (dn.find("=") > 0)
is_a_dn = staticmethod(is_a_dn)
+
+
+def notfound(args):
+ """Return a string suitable for displaying as an error when a
+ search returns no results.
+
+ This just returns whatever is after the equals sign"""
+ filter = args[2]
+ try:
+ target = re.match(r'\(.*=(.*)\)', filter).group(1)
+ except:
+ target = filter
+ return "%s not found" % str(target)
diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py
index 032f6b422..485b6e255 100644
--- a/ipa-server/xmlrpc-server/funcs.py
+++ b/ipa-server/xmlrpc-server/funcs.py
@@ -329,6 +329,32 @@ class IPAServer:
return (exact_match_filter, partial_match_filter)
+ def __get_schema(self, opts=None):
+ """Retrieves the current LDAP schema from the LDAP server."""
+
+ schema_entry = self.__get_base_entry("", "objectclass=*", ['dn','subschemasubentry'], opts)
+ schema_cn = schema_entry.get('subschemasubentry')
+ schema = self.__get_base_entry(schema_cn, "objectclass=*", ['*'], opts)
+
+ return schema
+
+ def __get_objectclasses(self, opts=None):
+ """Returns a list of available objectclasses that the LDAP
+ server supports. This parses out the syntax, attributes, etc
+ and JUST returns a lower-case list of the names."""
+
+ schema = self.__get_schema(opts)
+
+ objectclasses = schema.get('objectclasses')
+
+ # Convert this list into something more readable
+ result = []
+ for i in range(len(objectclasses)):
+ oc = objectclasses[i].lower().split(" ")
+ result.append(oc[3].replace("'",""))
+
+ return result
+
# Higher-level API
def get_aci_entry(self, sattrs, opts=None):
@@ -423,6 +449,19 @@ class IPAServer:
if self.__is_user_unique(user['uid'], opts) == 0:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
+ # dn is set here, not by the user
+ try:
+ del user['dn']
+ except KeyError:
+ pass
+
+ # No need to set empty fields, and they can cause issues when they
+ # get to LDAP, like:
+ # TypeError: ('expected a string in the list', None)
+ for k in user.keys():
+ if not user[k] or len(user[k]) == 0 or (len(user[k]) == 1 and '' in user[k]):
+ del user[k]
+
dn="uid=%s,%s,%s" % (ldap.dn.escape_dn_chars(user['uid']),
user_container,self.basedn)
entry = ipaserver.ipaldap.Entry(dn)
@@ -467,8 +506,7 @@ class IPAServer:
del user['gn']
# some required objectclasses
- entry.setValues('objectClass', 'top', 'person', 'organizationalPerson',
- 'inetOrgPerson', 'inetUser', 'posixAccount', 'krbPrincipalAux', 'radiusprofile')
+ entry.setValues('objectClass', (config.get('ipauserobjectclasses')))
# fill in our new entry with everything sent by the user
for u in user:
@@ -476,8 +514,16 @@ class IPAServer:
conn = self.getConnection(opts)
try:
- res = conn.addEntry(entry)
- self.add_user_to_group(user.get('uid'), group_dn, opts)
+ try:
+ res = conn.addEntry(entry)
+ except TypeError, e:
+ raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, "There is a problem with one of the data types.")
+ except Exception, e:
+ raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, e)
+ try:
+ self.add_user_to_group(user.get('uid'), group_dn, opts)
+ except Exception, e:
+ raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, "The user was created but adding to group %s failed" % group_dn)
finally:
self.releaseConnection(conn)
return res
@@ -672,6 +718,12 @@ class IPAServer:
finally:
self.releaseConnection(conn)
+ # Get our configuration
+ config = self.get_ipa_config(opts)
+
+ # Make sure we have the latest object classes
+ newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipauserobjectclasses'))
+
try:
rv = self.update_entry(oldentry, newentry, opts)
return rv
@@ -831,13 +883,15 @@ class IPAServer:
if self.__is_group_unique(group['cn'], opts) == 0:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
+ # Get our configuration
+ config = self.get_ipa_config(opts)
+
dn="cn=%s,%s,%s" % (ldap.dn.escape_dn_chars(group['cn']),
group_container,self.basedn)
entry = ipaserver.ipaldap.Entry(dn)
# some required objectclasses
- entry.setValues('objectClass', 'top', 'groupofnames', 'posixGroup',
- 'inetUser')
+ entry.setValues('objectClass', (config.get('ipagroupobjectclasses')))
# No need to explicitly set gidNumber. The dna_plugin will do this
# for us if the value isn't provided by the user.
@@ -1162,23 +1216,32 @@ class IPAServer:
try:
res = conn.updateRDN(oldentry.get('dn'), "cn=" + newcn[0])
newdn = oldentry.get('dn')
+ newcn = newentry.get('cn')
+ if isinstance(newcn, str):
+ newcn = [newcn]
# Ick. Need to find the exact cn used in the old DN so we'll
# walk the list of cns and skip the obviously bad ones:
for c in oldentry.get('dn').split("cn="):
if c and c != "groups" and not c.startswith("accounts"):
- newdn = newdn.replace("cn=%s" % c, "uid=%s" % newentry.get('cn')[0])
+ newdn = newdn.replace("cn=%s" % c, "cn=%s," % newcn[0])
break
# Now fix up the dns and cns so they aren't seen as having
# changed.
oldentry['dn'] = newdn
newentry['dn'] = newdn
- oldentry['cn'] = newentry['cn']
+ oldentry['cn'] = newentry.get('cn')
newrdn = 1
finally:
self.releaseConnection(conn)
+ # Get our configuration
+ config = self.get_ipa_config(opts)
+
+ # Make sure we have the latest object classes
+ newentry['objectclass'] = uniq_list(newentry.get('objectclass') + config.get('ipagroupobjectclasses'))
+
try:
rv = self.update_entry(oldentry, newentry, opts)
return rv
@@ -1332,7 +1395,78 @@ class IPAServer:
finally:
self.releaseConnection(conn)
return res
-
+
+ def find_service_principal(self, criteria, sattrs, searchlimit=-1,
+ timelimit=-1, opts=None):
+ """Returns a list: counter followed by the results.
+ If the results are truncated, counter will be set to -1."""
+
+ config = self.get_ipa_config(opts)
+ if timelimit < 0:
+ timelimit = float(config.get('ipasearchtimelimit'))
+ if searchlimit < 0:
+ searchlimit = float(config.get('ipasearchrecordslimit'))
+
+ search_fields = ["krbprincipalname"]
+
+ criteria = self.__safe_filter(criteria)
+ criteria_words = re.split(r'\s+', criteria)
+ criteria_words = filter(lambda value:value!="", criteria_words)
+ if len(criteria_words) == 0:
+ return [0]
+
+ (exact_match_filter, partial_match_filter) = self.__generate_match_filters(
+ search_fields, criteria_words)
+
+ #
+ # further constrain search to just the objectClass
+ # TODO - need to parameterize this into generate_match_filters,
+ # and work it into the field-specification search feature
+ #
+ exact_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % exact_match_filter
+ partial_match_filter = "(&(objectclass=krbPrincipalAux)(!(objectClass=person))(!(krbprincipalname=kadmin/*))%s)" % partial_match_filter
+ print exact_match_filter
+ print partial_match_filter
+
+ conn = self.getConnection(opts)
+ try:
+ try:
+ exact_results = conn.getListAsync(self.basedn, self.scope,
+ exact_match_filter, sattrs, 0, None, None, timelimit,
+ searchlimit)
+ except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
+ exact_results = [0]
+
+ try:
+ partial_results = conn.getListAsync(self.basedn, self.scope,
+ partial_match_filter, sattrs, 0, None, None, timelimit,
+ searchlimit)
+ except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
+ partial_results = [0]
+ finally:
+ self.releaseConnection(conn)
+
+ 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)
+
+ if (exact_counter == -1) or (partial_counter == -1):
+ counter = -1
+ else:
+ counter = len(exact_results) + len(partial_results)
+
+ entries = [counter]
+ for e in exact_results + partial_results:
+ entries.append(self.convert_entry(e))
+
+ return entries
def get_keytab(self, name, opts=None):
"""get a keytab"""
@@ -1397,6 +1531,19 @@ class IPAServer:
except:
raise
+ # Run through the list of User and Group object classes to make
+ # sure they are all valid. This doesn't handle dependencies but it
+ # will at least catch typos.
+ classes = self.__get_objectclasses(opts)
+ oc = newconfig['ipauserobjectclasses']
+ for i in range(len(oc)):
+ if not oc[i].lower() in classes:
+ raise ipaerror.gen_exception(ipaerror.CONFIG_INVALID_OC)
+ oc = newconfig['ipagroupobjectclasses']
+ for i in range(len(oc)):
+ if not oc[i].lower() in classes:
+ raise ipaerror.gen_exception(ipaerror.CONFIG_INVALID_OC)
+
return self.update_entry(oldconfig, newconfig, opts)
def get_password_policy(self, opts=None):
@@ -1406,6 +1553,10 @@ class IPAServer:
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
raise ipaerror.gen_exception(ipaerror.LDAP_NO_CONFIG)
+ # convert some values for display purposes
+ policy['krbmaxpwdlife'] = str(int(policy.get('krbmaxpwdlife')) / 86400)
+ policy['krbminpwdlife'] = str(int(policy.get('krbminpwdlife')) / 3600)
+
return policy
def update_password_policy(self, oldpolicy, newpolicy, opts=None):
@@ -1414,11 +1565,18 @@ class IPAServer:
# The LDAP routines want strings, not ints, so convert a few
# things. Otherwise it sees a string -> int conversion as a change.
try:
- newpolicy['krbmaxpwdlife'] = str(newpolicy.get('krbmaxpwdlife'))
- newpolicy['krbminpwdlife'] = str(newpolicy.get('krbminpwdlife'))
- newpolicy['krbpwdhistorylength'] = str(newpolicy.get('krbpwdhistorylength'))
- newpolicy['krbpwdmindiffchars'] = str(newpolicy.get('krbpwdmindiffchars'))
- newpolicy['krbpwdminlength'] = str(newpolicy.get('krbpwdminlength'))
+ for k in oldpolicy.iterkeys():
+ if k.startswith("krb", 0, 3):
+ oldpolicy[k] = str(oldpolicy[k])
+ for k in newpolicy.iterkeys():
+ if k.startswith("krb", 0, 3):
+ newpolicy[k] = str(newpolicy[k])
+
+ # Convert hours and days to seconds
+ oldpolicy['krbmaxpwdlife'] = str(int(oldpolicy.get('krbmaxpwdlife')) * 86400)
+ oldpolicy['krbminpwdlife'] = str(int(oldpolicy.get('krbminpwdlife')) * 3600)
+ newpolicy['krbmaxpwdlife'] = str(int(newpolicy.get('krbmaxpwdlife')) * 86400)
+ newpolicy['krbminpwdlife'] = str(int(newpolicy.get('krbminpwdlife')) * 3600)
except KeyError:
# These should all be there but if not, let things proceed
pass
@@ -1448,3 +1606,8 @@ def ldap_search_escape(match):
return r'\00'
else:
return value
+
+def uniq_list(x):
+ """Return a unique list, preserving order and ignoring case"""
+ set = {}
+ return [set.setdefault(e,e) for e in x if e.lower() not in set]
diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py
index c6f0ec2ce..31cfbae69 100644
--- a/ipa-server/xmlrpc-server/ipaxmlrpc.py
+++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py
@@ -360,6 +360,7 @@ def handler(req, profiling=False):
h.register_function(f.get_password_policy)
h.register_function(f.update_password_policy)
h.register_function(f.add_service_principal)
+ h.register_function(f.find_service_principal)
h.register_function(f.get_keytab)
h.handle_request(req)
finally: