diff --git a/ipa-admintools/ipa-adduser b/ipa-admintools/ipa-adduser
index bb97a0d67..e993bee5e 100644
--- a/ipa-admintools/ipa-adduser
+++ b/ipa-admintools/ipa-adduser
@@ -205,8 +205,6 @@ def main():
user.setValue('homedirectory', directory)
if shell:
user.setValue('loginshell', shell)
- else:
- user.setValue('loginshell', "/bin/sh")
try:
client = ipaclient.IPAClient()
diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py
index 659ff995d..f8c70974a 100644
--- a/ipa-python/ipaclient.py
+++ b/ipa-python/ipaclient.py
@@ -134,10 +134,14 @@ class IPAClient:
return all_users
- def get_add_schema(self):
- """Prototype for the GUI. Specify in the directory fields to
- be displayed and what data to get for new users."""
- result = self.transport.get_add_schema()
+ def get_custom_fields(self):
+ """Get custom user fields"""
+ result = self.transport.get_custom_fields()
+ return result
+
+ def set_custom_fields(self, schema):
+ """Set custom user fields"""
+ result = self.transport.set_custom_fields(schema)
return result
def find_users(self, criteria, sattrs=None, searchlimit=0, timelimit=-1):
@@ -331,3 +335,29 @@ class IPAClient:
entries.append(user.User(e))
return entries
+
+ def get_ipa_config(self):
+ """Get the IPA configuration"""
+ result = self.transport.get_ipa_config()
+ return entity.Entity(result)
+
+ def update_ipa_config(self, config):
+ """Updates the IPA configuration.
+
+ config is an Entity object.
+ """
+ result = self.transport.update_ipa_config(config.origDataDict(), config.toDict())
+ return result
+
+ def get_password_policy(self):
+ """Get the IPA password policy"""
+ result = self.transport.get_password_policy()
+ return entity.Entity(result)
+
+ def update_password_policy(self, policy):
+ """Updates the IPA password policy.
+
+ policy is an Entity object.
+ """
+ result = self.transport.update_password_policy(policy.origDataDict(), policy.toDict())
+ return result
diff --git a/ipa-python/ipaerror.py b/ipa-python/ipaerror.py
index f583322e6..5391b3fd4 100644
--- a/ipa-python/ipaerror.py
+++ b/ipa-python/ipaerror.py
@@ -123,6 +123,11 @@ LDAP_EMPTY_MODLIST = gen_error_code(
0x0006,
"No modifications to be performed")
+LDAP_NO_CONFIG = gen_error_code(
+ LDAP_CATEGORY,
+ 0x0007,
+ "IPA configuration not found")
+
#
# Input errors (sample - replace me)
#
diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py
index 871c37254..c4ca2ff3e 100644
--- a/ipa-python/rpcclient.py
+++ b/ipa-python/rpcclient.py
@@ -218,23 +218,32 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
- def get_add_schema(self):
- """Get the list of attributes we need to ask when adding a new
- user.
- """
+ def get_custom_fields(self):
+ """Get custom user fields."""
server = self.setup_server()
- # FIXME: Hardcoded and designed for the TurboGears GUI. Do we want
- # this for the CLI as well?
try:
- result = server.get_add_schema()
+ result = server.get_custom_fields()
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 set_custom_fields(self, schema):
+ """Set custom user fields."""
+ server = self.setup_server()
+
+ try:
+ result = server.set_custom_fields(schema)
+ 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_all_users (self):
"""Return a list containing a User object for each existing user."""
@@ -591,3 +600,51 @@ class RPCClient:
raise xmlrpclib.Fault(value, msg)
return ipautil.unwrap_binary_data(result)
+
+ def get_ipa_config(self):
+ """Get the IPA configuration"""
+ server = self.setup_server()
+ try:
+ result = server.get_ipa_config()
+ except xmlrpclib.Fault, fault:
+ raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
+
+ return ipautil.unwrap_binary_data(result)
+
+ def update_ipa_config(self, oldconfig, newconfig):
+ """Update the IPA configuration"""
+ server = self.setup_server()
+ try:
+ result = server.update_ipa_config(oldconfig, newconfig)
+ 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_password_policy(self):
+ """Get the IPA password policy"""
+ server = self.setup_server()
+ try:
+ result = server.get_password_policy()
+ except xmlrpclib.Fault, fault:
+ raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
+
+ return ipautil.unwrap_binary_data(result)
+
+ def update_password_policy(self, oldpolicy, newpolicy):
+ """Update the IPA password policy"""
+ server = self.setup_server()
+ try:
+ result = server.update_password_policy(oldpolicy, newpolicy)
+ except xmlrpclib.Fault, fault:
+ raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
+ except socket.error, (value, msg):
+ raise xmlrpclib.Fault(value, msg)
+
+ return ipautil.unwrap_binary_data(result)
diff --git a/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py b/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
index 660a34b68..ec0e8c6f8 100644
--- a/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
+++ b/ipa-server/ipa-gui/ipagui/forms/ipapolicy.py
@@ -2,25 +2,49 @@ import turbogears
from turbogears import validators, widgets
class IPAPolicyFields():
- searchlimit = widgets.TextField(name="searchlimit", label="Search Time Limit (sec.)", attrs=dict(size=6,maxlength=6))
- maxuidlength = widgets.TextField(name="maxuidlength", label="Max. UID Length", attrs=dict(size=3,maxlength=3))
- passwordnotif = widgets.TextField(name="passwordnotif", label="Password Expiration Notification (days)", attrs=dict(size=3,maxlength=3))
- homedir = widgets.TextField(name="homedir", label="Root for Home Directories")
- defaultshell = widgets.TextField(name="defaultshell", label="Default shell")
- defaultgroup = widgets.TextField(name="defaultgroup", label="Default Users group")
+ # From cn=ipaConfig
+ ipausersearchfields = widgets.TextField(name="ipausersearchfields", label="User Search Fields")
+ ipagroupsearchfields = widgets.TextField(name="ipagroupsearchfields", label="Group Search Fields")
+ ipasearchtimelimit = widgets.TextField(name="ipasearchtimelimit", label="Search Time Limit (sec.)", attrs=dict(size=6,maxlength=6))
+ ipasearchrecordslimit = widgets.TextField(name="ipasearchrecordslimit", label="Search Records Limit", attrs=dict(size=6,maxlength=6))
+ ipahomesrootdir = widgets.TextField(name="ipahomesrootdir", label="Root for Home Directories")
+ ipadefaultloginshell = widgets.TextField(name="ipadefaultloginshell", label="Default shell")
+ 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))
+
+ 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))
+ 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))
+
+ password_orig = widgets.HiddenField(name="password_orig")
class IPAPolicyValidator(validators.Schema):
- searchlimit = validators.Number(not_empty=True)
- maxuidlength = validators.Number(not_empty=True)
- passwordnotif = validators.Number(not_empty=True)
- homedir = validators.String(not_empty=True)
- defaultshell = validators.String(not_empty=True)
- defaultgroup = validators.String(not_empty=True)
+ ipausersearchfields = validators.String(not_empty=True)
+ ipagroupsearchfields = validators.String(not_empty=True)
+ ipasearchtimelimit = validators.Number(not_empty=True)
+ ipasearchrecordslimit = validators.Number(not_empty=True)
+ ipamaxusernamelength = validators.Number(not_empty=True)
+ ipapwdexpadvnotify = validators.Number(not_empty=True)
+ ipahomesrootdir = validators.String(not_empty=True)
+ ipadefaultloginshell = validators.String(not_empty=True)
+ ipadefaultprimarygroup = validators.String(not_empty=True)
+ krbmaxpwdlife = validators.Number(not_empty=True)
+ krbminpwdlife = validators.Number(not_empty=True)
+ krbpwdmindiffchars = validators.Number(not_empty=True)
+ krbpwdminlength = validators.Number(not_empty=True)
+ krbpwdhistorylength = validators.Number(not_empty=True)
class IPAPolicyForm(widgets.Form):
params = ['ipapolicy_fields']
hidden_fields = [
+ IPAPolicyFields.ipapolicy_orig, IPAPolicyFields.password_orig
]
validator = IPAPolicyValidator()
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py b/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
index 5d9024275..781ca35d4 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py
@@ -15,6 +15,7 @@ from turbogears import identity
from ipacontroller import IPAController
from ipa.entity import utf8_encode_values
from ipa import ipaerror
+import ipa.entity
import ipagui.forms.ipapolicy
import ldap.dn
@@ -34,16 +35,14 @@ class IPAPolicyController(IPAController):
@identity.require(identity.in_group("admins"))
def show(self, tg_errors=None):
"""Displays the one policy page"""
+ client = self.get_ipaclient()
+ config = client.get_ipa_config()
+ ipapolicy = config.toDict()
- # TODO: Get this dict from LDAP
- ipapolicy = {}
- ipapolicy['searchlimit'] = 2
- ipapolicy['maxuidlength'] = 3
- ipapolicy['passwordnotif'] = 4
- ipapolicy['homedir'] = "/home"
- ipapolicy['defaultgroup'] = "ipausers"
- ipapolicy['defaultshell'] = "/bin/bash"
- return dict(ipapolicy=ipapolicy,fields=ipagui.forms.ipapolicy.IPAPolicyFields())
+ ppolicy = client.get_password_policy()
+ password = ppolicy.toDict()
+
+ return dict(ipapolicy=ipapolicy,password=password,fields=ipagui.forms.ipapolicy.IPAPolicyFields())
@expose("ipagui.templates.ipapolicyedit")
@identity.require(identity.in_group("admins"))
@@ -54,18 +53,28 @@ class IPAPolicyController(IPAController):
"Please see the messages below for details.")
try:
- # TODO: Get this dict from LDAP
- ipapolicy_dict = {}
- ipapolicy_dict['searchlimit'] = 2
- ipapolicy_dict['maxuidlength'] = 3
- ipapolicy_dict['passwordnotif'] = 4
- ipapolicy_dict['homedir'] = "/home"
- ipapolicy_dict['defaultgroup'] = "ipausers"
- ipapolicy_dict['defaultshell'] = "/bin/bash"
+ client = self.get_ipaclient()
+ config = client.get_ipa_config()
+ ipapolicy_dict = config.toDict()
+
+ ppolicy = client.get_password_policy()
+ password_dict = ppolicy.toDict()
+
+ # store a copy of the original policy for the update later
+ ipapolicy_data = b64encode(dumps(ipapolicy_dict))
+ ipapolicy_dict['ipapolicy_orig'] = ipapolicy_data
+
+ # store a copy of the original policy for the update later
+ password_data = b64encode(dumps(password_dict))
+ password_dict['password_orig'] = password_data
+
+ # Combine the 2 dicts to make the form easier
+ ipapolicy_dict.update(password_dict)
+
return dict(form=ipapolicy_edit_form, ipapolicy=ipapolicy_dict)
except ipaerror.IPAError, e:
turbogears.flash("IPA Policy edit failed: " + str(e) + "
" + str(e.detail))
- raise turbogears.redirect('/group/show', uid=cn)
+ raise turbogears.redirect('/ipapolicy/show')
@expose()
@@ -86,16 +95,72 @@ class IPAPolicyController(IPAController):
return dict(form=ipapolicy_edit_form, ipapolicy=kw,
tg_template='ipagui.templates.ipapolicyedit')
- try:
+ policy_modified = False
+ password_modified = False
- # TODO: Actually save the data
+ try:
+ orig_ipapolicy_dict = loads(b64decode(kw.get('ipapolicy_orig')))
+ orig_password_dict = loads(b64decode(kw.get('password_orig')))
+
+ new_ipapolicy = ipa.entity.Entity(orig_ipapolicy_dict)
+ new_password = ipa.entity.Entity(orig_password_dict)
+
+ if str(new_ipapolicy.ipasearchtimelimit) != str(kw.get('ipasearchtimelimit')):
+ policy_modified = True
+ new_ipapolicy.setValue('ipasearchtimelimit', kw.get('ipasearchtimelimit'))
+ if str(new_ipapolicy.ipasearchrecordslimit) != str(kw.get('ipasearchrecordslimit')):
+ policy_modified = True
+ new_ipapolicy.setValue('ipasearchrecordslimit', kw.get('ipasearchrecordslimit'))
+ if new_ipapolicy.ipausersearchfields != kw.get('ipausersearchfields'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipausersearchfields', kw.get('ipausersearchfields'))
+ if new_ipapolicy.ipagroupsearchfields != kw.get('ipagroupsearchfields'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipagroupsearchfields', kw.get('ipagroupsearchfields'))
+ if str(new_ipapolicy.ipapwdexpadvnotify) != str(kw.get('ipapwdexpadvnotify')):
+ policy_modified = True
+ new_ipapolicy.setValue('ipapwdexpadvnotify', kw.get('ipapwdexpadvnotify'))
+ if str(new_ipapolicy.ipamaxusernamelength) != str(kw.get('ipamaxusernamelength')):
+ policy_modified = True
+ new_ipapolicy.setValue('ipamaxusernamelength', kw.get('ipamaxusernamelength'))
+ if new_ipapolicy.ipahomesrootdir != kw.get('ipahomesrootdir'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipahomesrootdir', kw.get('ipahomesrootdir'))
+ if new_ipapolicy.ipadefaultloginshell != kw.get('ipadefaultloginshell'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipadefaultloginshell', kw.get('ipadefaultloginshell'))
+ if new_ipapolicy.ipadefaultprimarygroup != kw.get('ipadefaultprimarygroup'):
+ policy_modified = True
+ new_ipapolicy.setValue('ipadefaultprimarygroup', kw.get('ipadefaultprimarygroup'))
+
+ if policy_modified:
+ rv = client.update_ipa_config(new_ipapolicy)
+
+ # Now check the password policy for updates
+ if str(new_password.krbmaxpwdlife) != str(kw.get('krbmaxpwdlife')):
+ password_modified = True
+ new_password.setValue('krbmaxpwdlife', str(kw.get('krbmaxpwdlife')))
+ if str(new_password.krbminpwdlife) != str(kw.get('krbminpwdlife')):
+ password_modified = True
+ new_password.setValue('krbminpwdlife', str(kw.get('krbminpwdlife')))
+ if str(new_password.krbpwdhistorylength) != str(kw.get('krbpwdhistorylength')):
+ password_modified = True
+ new_password.setValue('krbpwdhistorylength', str(kw.get('krbpwdhistorylength')))
+ if str(new_password.krbpwdmindiffchars) != str(kw.get('krbpwdmindiffchars')):
+ password_modified = True
+ new_password.setValue('krbpwdmindiffchars', str(kw.get('krbpwdmindiffchars')))
+ if str(new_password.krbpwdminlength) != str(kw.get('krbpwdminlength')):
+ password_modified = True
+ new_password.setValue('krbpwdminlength', str(kw.get('krbpwdminlength')))
+ if password_modified:
+ rv = client.update_password_policy(new_password)
turbogears.flash("IPA Policy updated")
raise turbogears.redirect('/ipapolicy/show')
- except (SyntaxError, ipaerror.IPAError), e:
- turbogears.flash("Policy update failed: " + str(e))
- return dict(form=policy_form, policy=kw,
- tg_template='ipagui.templates.policyindex')
+ except ipaerror.IPAError, e:
+ turbogears.flash("Policy update failed: " + str(e) + e.detail[0]['desc'])
+ return dict(form=ipapolicy_edit_form, ipapolicy=kw,
+ tg_template='ipagui.templates.ipapolicyedit')
@validate(form=ipapolicy_edit_form)
@identity.require(identity.not_anonymous())
diff --git a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
index 7d266f0d5..579379c43 100644
--- a/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
+++ b/ipa-server/ipa-gui/ipagui/subcontrollers/user.py
@@ -34,26 +34,48 @@ class UserController(IPAController):
def __init__(self, *args, **kw):
super(UserController,self).__init__(*args, **kw)
- self.load_custom_fields()
+# self.load_custom_fields()
def load_custom_fields(self):
- # client = self.get_ipaclient()
- # schema = client.get_user_custom_schema()
- schema = [
- { 'label': 'See Also',
- 'field': 'seeAlso',
- 'required': 'true', } ,
- { 'label': 'O O O',
- 'field': 'o',
- 'required': 'false', } ,
- ]
+
+ client = self.get_ipaclient()
+ schema = client.get_custom_fields()
+
+ # FIXME: Don't load from LDAP every single time it is called
+
+ # FIXME: Is removing the attributes on the fly thread-safe? Do we
+ # need to lock here?
for s in schema:
required=False
- if (s['required'] == "true"):
+ if (s['required'].lower() == "true"):
required=True
field = widgets.TextField(name=s['field'],label=s['label'])
validator = validators.String(not_empty=required)
+ # Don't allow dupes on the new form
+ try:
+ for i in range(len(user_new_form.custom_fields)):
+ if user_new_form.custom_fields[i].name == s['field']:
+ user_new_form.custom_fields.pop(i)
+ except:
+ pass
+
+ # Don't allow dupes on the edit form
+ try:
+ for i in range(len(user_edit_form.custom_fields)):
+ if user_edit_form.custom_fields[i].name == s['field']:
+ user_edit_form.custom_fields.pop(i)
+ except:
+ pass
+
+ # Don't allow dupes in the list of user fields
+ try:
+ for i in range(len(ipagui.forms.user.UserFields.custom_fields)):
+ if ipagui.forms.user.UserFields.custom_fields[i].name == s['field']:
+ ipagui.forms.user.UserFields.custom_fields.pop(i)
+ except:
+ pass
+
ipagui.forms.user.UserFields.custom_fields.append(field)
user_new_form.custom_fields.append(field)
user_edit_form.custom_fields.append(field)
@@ -99,6 +121,7 @@ class UserController(IPAController):
@identity.require(identity.in_any_group("admins","editors"))
def new(self, tg_errors=None):
"""Displays the new user form"""
+ self.load_custom_fields()
if tg_errors:
turbogears.flash("There were validation errors.
" +
"Please see the messages below for details.")
@@ -281,6 +304,7 @@ class UserController(IPAController):
@identity.require(identity.not_anonymous())
def edit(self, uid=None, principal=None, tg_errors=None):
"""Displays the edit user form"""
+ self.load_custom_fields()
if tg_errors:
turbogears.flash("There were validation errors.
" +
"Please see the messages below for details.")
@@ -581,6 +605,7 @@ class UserController(IPAController):
def show(self, uid):
"""Retrieve a single user for display"""
client = self.get_ipaclient()
+ self.load_custom_fields()
try:
user = client.get_user_by_uid(uid, user_fields)
diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
index bcdef8c27..106657636 100644
--- a/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid
@@ -24,12 +24,42 @@ from ipagui.helpers import ipahelper
@@ -38,56 +68,106 @@ from ipagui.helpers import ipahelper
-
+
diff --git a/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid b/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
index e14485f2a..089fb494e 100644
--- a/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid
@@ -20,9 +20,27 @@ edit_url = tg.url('/ipapolicy/edit')
@@ -30,36 +48,66 @@ edit_url = tg.url('/ipapolicy/edit')
diff --git a/ipa-server/ipa-gui/ipagui/templates/usershow.kid b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
index 6ab565cbc..8cc356b89 100644
--- a/ipa-server/ipa-gui/ipagui/templates/usershow.kid
+++ b/ipa-server/ipa-gui/ipagui/templates/usershow.kid
@@ -345,7 +345,7 @@ else: