diff options
author | Rob Crittenden <rcritten@redhat.com> | 2007-11-16 12:59:32 -0500 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2007-11-16 12:59:32 -0500 |
commit | 1967aafa3985fa87e02ae372164abe2524d9bd65 (patch) | |
tree | bfe6d2a5e39d60f5d3b7138bba281dbc770ee5ba | |
parent | 0a3ed697465db8179a15f3b64160d8d545710698 (diff) | |
download | freeipa-1967aafa3985fa87e02ae372164abe2524d9bd65.tar.gz freeipa-1967aafa3985fa87e02ae372164abe2524d9bd65.tar.xz freeipa-1967aafa3985fa87e02ae372164abe2524d9bd65.zip |
Implement the password policy UI and finish IPA policy UI
This includes a default password policy
Custom fields are now read from LDAP. The format is a list of
dicts with keys: label, field, required.
The LDAP-based configuration now specifies:
ipaUserSearchFields: uid,givenName,sn,telephoneNumber,ou,title
ipaGroupSearchFields: cn,description
ipaSearchTimeLimit: 2
ipaSearchRecordsLimit: 0
ipaCustomFields:
ipaHomesRootDir: /home
ipaDefaultLoginShell: /bin/sh
ipaDefaultPrimaryGroup: ipausers
ipaMaxUsernameLength: 8
ipaPwdExpAdvNotify: 4
This could use some optimization.
-rw-r--r-- | ipa-admintools/ipa-adduser | 2 | ||||
-rw-r--r-- | ipa-python/ipaclient.py | 38 | ||||
-rw-r--r-- | ipa-python/ipaerror.py | 5 | ||||
-rw-r--r-- | ipa-python/rpcclient.py | 73 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/forms/ipapolicy.py | 48 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/subcontrollers/ipapolicy.py | 113 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/subcontrollers/user.py | 49 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/ipapolicyeditform.kid | 130 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/ipapolicyshow.kid | 72 | ||||
-rw-r--r-- | ipa-server/ipa-gui/ipagui/templates/usershow.kid | 2 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/bootstrap-template.ldif | 21 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/default-aci.ldif | 11 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 195 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 7 |
14 files changed, 610 insertions, 156 deletions
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() + + ppolicy = client.get_password_policy() + password = ppolicy.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()) + 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) + "<br/>" + 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.<br/>" + "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.<br/>" + "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 <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr> <th> - <label class="fieldlabel" py:content="ipapolicy_fields.searchlimit.label" />: + <label class="fieldlabel" py:content="ipapolicy_fields.ipasearchtimelimit.label" />: </th> <td> - <span py:replace="ipapolicy_fields.searchlimit.display(value_for(ipapolicy_fields.searchlimit))" /> - <span py:if="tg.errors.get('searchlimit')" class="fielderror" - py:content="tg.errors.get('searchlimit')" /> + <span py:replace="ipapolicy_fields.ipasearchtimelimit.display(value_for(ipapolicy_fields.ipasearchtimelimit))" /> + <span py:if="tg.errors.get('ipasearchtimelimit')" class="fielderror" + py:content="tg.errors.get('ipasearchtimelimit')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipasearchrecordslimit.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipasearchrecordslimit.display(value_for(ipapolicy_fields.ipasearchrecordslimit))" /> + <span py:if="tg.errors.get('ipasearchrecordslimit')" class="fielderror" + py:content="tg.errors.get('ipasearchrecordslimit')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipausersearchfields.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipausersearchfields.display(value_for(ipapolicy_fields.ipausersearchfields))" /> + <span py:if="tg.errors.get('ipausersearchfields')" class="fielderror" + py:content="tg.errors.get('ipausersearchfields')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.ipagroupsearchfields.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipagroupsearchfields.display(value_for(ipapolicy_fields.ipagroupsearchfields))" /> + <span py:if="tg.errors.get('ipagroupsearchfields')" class="fielderror" + py:content="tg.errors.get('ipagroupsearchfields')" /> </td> </tr> </table> @@ -38,56 +68,106 @@ from ipagui.helpers import ipahelper <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr> <th> - <label class="fieldlabel" py:content="ipapolicy_fields.passwordnotif.label" />: + <label class="fieldlabel" py:content="ipapolicy_fields.ipapwdexpadvnotify.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.ipapwdexpadvnotify.display(value_for(ipapolicy_fields.ipapwdexpadvnotify))" /> + <span py:if="tg.errors.get('ipapwdexpadvnotify')" class="fielderror" + py:content="tg.errors.get('ipapwdexpadvnotify')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbminpwdlife.label" />: </th> <td> - <span py:replace="ipapolicy_fields.passwordnotif.display(value_for(ipapolicy_fields.passwordnotif))" /> - <span py:if="tg.errors.get('passwordnotif')" class="fielderror" - py:content="tg.errors.get('passwordnotif')" /> + <span py:replace="ipapolicy_fields.krbminpwdlife.display(value_for(ipapolicy_fields.krbminpwdlife))" /> + <span py:if="tg.errors.get('krbminpwdlife')" class="fielderror" + py:content="tg.errors.get('krbminpwdlife')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbmaxpwdlife.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbmaxpwdlife.display(value_for(ipapolicy_fields.krbmaxpwdlife))" /> + <span py:if="tg.errors.get('krbmaxpwdlife')" class="fielderror" + py:content="tg.errors.get('krbmaxpwdlife')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbpwdmindiffchars.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbpwdmindiffchars.display(value_for(ipapolicy_fields.krbpwdmindiffchars))" /> + <span py:if="tg.errors.get('krbpwdmindiffchars')" class="fielderror" + py:content="tg.errors.get('krbpwdmindiffchars')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbpwdminlength.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbpwdminlength.display(value_for(ipapolicy_fields.krbpwdminlength))" /> + <span py:if="tg.errors.get('krbpwdminlength')" class="fielderror" + py:content="tg.errors.get('krbpwdminlength')" /> + </td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="ipapolicy_fields.krbpwdhistorylength.label" />: + </th> + <td> + <span py:replace="ipapolicy_fields.krbpwdhistorylength.display(value_for(ipapolicy_fields.krbpwdhistorylength))" /> + <span py:if="tg.errors.get('krbpwdhistorylength')" class="fielderror" + py:content="tg.errors.get('krbpwdhistorylength')" /> </td> </tr> </table> - <h2 class="formsection">Password Policy</h2> + <h2 class="formsection">User Settings</h2> <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr> <th> - <label class="fieldlabel" py:content="ipapolicy_fields.maxuidlength.label" />: + <label class="fieldlabel" py:content="ipapolicy_fields.ipamaxusernamelength.label" />: </th> <td> - <span py:replace="ipapolicy_fields.maxuidlength.display(value_for(ipapolicy_fields.maxuidlength))" /> - <span py:if="tg.errors.get('maxuidlength')" class="fielderror" - py:content="tg.errors.get('maxuidlength')" /> + <span py:replace="ipapolicy_fields.ipamaxusernamelength.display(value_for(ipapolicy_fields.ipamaxusernamelength))" /> + <span py:if="tg.errors.get('ipamaxusernamelength')" class="fielderror" + py:content="tg.errors.get('ipamaxusernamelength')" /> </td> </tr> <tr> <th> - <label class="fieldlabel" py:content="ipapolicy_fields.homedir.label" />: + <label class="fieldlabel" py:content="ipapolicy_fields.ipahomesrootdir.label" />: </th> <td> - <span py:replace="ipapolicy_fields.homedir.display(value_for(ipapolicy_fields.homedir))" /> - <span py:if="tg.errors.get('homedir')" class="fielderror" - py:content="tg.errors.get('homedir')" /> + <span py:replace="ipapolicy_fields.ipahomesrootdir.display(value_for(ipapolicy_fields.ipahomesrootdir))" /> + <span py:if="tg.errors.get('ipahomesrootdir')" class="fielderror" + py:content="tg.errors.get('ipahomesrootdir')" /> </td> </tr> <tr> <th> - <label class="fieldlabel" py:content="ipapolicy_fields.defaultshell.label" />: + <label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultloginshell.label" />: </th> <td> - <span py:replace="ipapolicy_fields.defaultshell.display(value_for(ipapolicy_fields.defaultshell))" /> - <span py:if="tg.errors.get('defaultshell')" class="fielderror" - py:content="tg.errors.get('defaultshell')" /> + <span py:replace="ipapolicy_fields.ipadefaultloginshell.display(value_for(ipapolicy_fields.ipadefaultloginshell))" /> + <span py:if="tg.errors.get('ipadefaultloginshell')" class="fielderror" + py:content="tg.errors.get('ipadefaultloginshell')" /> </td> </tr> <tr> <th> - <label class="fieldlabel" py:content="ipapolicy_fields.defaultgroup.label" />: + <label class="fieldlabel" py:content="ipapolicy_fields.ipadefaultprimarygroup.label" />: </th> <td> - <span py:replace="ipapolicy_fields.defaultgroup.display(value_for(ipapolicy_fields.defaultgroup))" /> - <span py:if="tg.errors.get('defaultgroup')" class="fielderror" - py:content="tg.errors.get('defaultgroup')" /> + <span py:replace="ipapolicy_fields.ipadefaultprimarygroup.display(value_for(ipapolicy_fields.ipadefaultprimarygroup))" /> + <span py:if="tg.errors.get('ipadefaultprimarygroup')" class="fielderror" + py:content="tg.errors.get('ipadefaultprimarygroup')" /> </td> </tr> </table> 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') <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr> <th> - <label class="fieldlabel" py:content="fields.searchlimit.label" />: + <label class="fieldlabel" py:content="fields.ipasearchtimelimit.label" />: </th> - <td>${ipapolicy.get("searchlimit")}</td> + <td>${ipapolicy.get("ipasearchtimelimit")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipasearchrecordslimit.label" />: + </th> + <td>${ipapolicy.get("ipasearchrecordslimit")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipausersearchfields.label" />: + </th> + <td>${ipapolicy.get("ipausersearchfields")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.ipagroupsearchfields.label" />: + </th> + <td>${ipapolicy.get("ipagroupsearchfields")}</td> </tr> </table> @@ -30,36 +48,66 @@ edit_url = tg.url('/ipapolicy/edit') <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr> <th> - <label class="fieldlabel" py:content="fields.passwordnotif.label" />: + <label class="fieldlabel" py:content="fields.ipapwdexpadvnotify.label" />: + </th> + <td>${ipapolicy.get("ipapwdexpadvnotify")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbminpwdlife.label" />: + </th> + <td>${password.get("krbminpwdlife")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbmaxpwdlife.label" />: + </th> + <td>${password.get("krbmaxpwdlife")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbpwdmindiffchars.label" />: + </th> + <td>${password.get("krbpwdmindiffchars")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbpwdminlength.label" />: + </th> + <td>${password.get("krbpwdminlength")}</td> + </tr> + <tr> + <th> + <label class="fieldlabel" py:content="fields.krbpwdhistorylength.label" />: </th> - <td>${ipapolicy.get("passwordnotif")}</td> + <td>${password.get("krbpwdhistorylength")}</td> </tr> </table> <h2 class="formsection">User Settings</h2> <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr> <th> - <label class="fieldlabel" py:content="fields.maxuidlength.label" />: + <label class="fieldlabel" py:content="fields.ipamaxusernamelength.label" />: </th> - <td>${ipapolicy.get("maxuidlength")}</td> + <td>${ipapolicy.get("ipamaxusernamelength")}</td> </tr> <tr> <th> - <label class="fieldlabel" py:content="fields.homedir.label" />: + <label class="fieldlabel" py:content="fields.ipahomesrootdir.label" />: </th> - <td>${ipapolicy.get("homedir")}</td> + <td>${ipapolicy.get("ipahomesrootdir")}</td> </tr> <tr> <th> - <label class="fieldlabel" py:content="fields.defaultshell.label" />: + <label class="fieldlabel" py:content="fields.ipadefaultloginshell.label" />: </th> - <td>${ipapolicy.get("defaultshell")}</td> + <td>${ipapolicy.get("ipadefaultloginshell")}</td> </tr> <tr> <th> - <label class="fieldlabel" py:content="fields.defaultgroup.label" />: + <label class="fieldlabel" py:content="fields.ipadefaultprimarygroup.label" />: </th> - <td>${ipapolicy.get("defaultgroup")}</td> + <td>${ipapolicy.get("ipadefaultprimarygroup")}</td> </tr> </table> <hr /> 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: </table> <div py:if='len(fields.custom_fields) > 0'> - <div class="formsection" >Custom Fields</div> + <h2 class="formsection">Custom Fields</h2> <table class="formtable" cellpadding="2" cellspacing="0" border="0"> <tr py:for='custom_field in fields.custom_fields'> <th> diff --git a/ipa-server/ipa-install/share/bootstrap-template.ldif b/ipa-server/ipa-install/share/bootstrap-template.ldif index 8eb42b332..30f6fe8a0 100644 --- a/ipa-server/ipa-install/share/bootstrap-template.ldif +++ b/ipa-server/ipa-install/share/bootstrap-template.ldif @@ -9,6 +9,11 @@ changetype: add objectClass: top objectClass: nsContainer cn: accounts +krbMinPwdLife: 3600 +krbPwdMinDiffChars: 0 +krbPwdMinLength: 8 +krbPwdHistoryLength: 0 +krbMaxPwdLife: 864000 dn: cn=users,cn=accounts,$SUFFIX changetype: add @@ -95,3 +100,19 @@ objectClass: posixGroup gidNumber: 1003 description: Limited admins who can edit other users cn: editors + +dn: cn=ipaConfig,cn=etc,dc=greyoak,dc=com +changetype: add +objectClass: nsContainer +objectClass: top +objectClass: ipaGuiConfig +ipaUserSearchFields: uid,givenName,sn,telephoneNumber,ou,title +ipaGroupSearchFields: cn,description +ipaSearchTimeLimit: 2 +ipaSearchRecordsLimit: 0 +ipaCustomFields: +ipaHomesRootDir: /home +ipaDefaultLoginShell: /bin/sh +ipaDefaultPrimaryGroup: ipausers +ipaMaxUsernameLength: 8 +ipaPwdExpAdvNotify: 4 diff --git a/ipa-server/ipa-install/share/default-aci.ldif b/ipa-server/ipa-install/share/default-aci.ldif index 3eee2ae3e..6e68b0e24 100644 --- a/ipa-server/ipa-install/share/default-aci.ldif +++ b/ipa-server/ipa-install/share/default-aci.ldif @@ -9,3 +9,14 @@ aci: (targetattr="krbLastSuccessfulAuth || krbLastFailedAuth || krbLoginFailedCo aci: (targetattr="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword || krbPasswordExpiration || krbPwdHistory || krbLastPwdChange")(version 3.0; acl "Kpasswd access to passowrd hashes for passowrd changes"; allow (read, write) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) aci: (targetfilter="(|(objectClass=person)(objectClass=krbPrincipalAux)(objectClass=posixAccount)(objectClass=groupOfUniqueNames)(objectClass=posixGroup))")(targetattr="*")(version 3.0; acl "Account Admins can manage Users and Groups"; allow (add,delete,read,write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) aci: (targetattr = "givenName || sn || cn || displayName || initials || loginShell || homePhone || mobile || pager || facsimileTelephoneNumber || telephoneNumber || street || roomNumber || l || st || postalCode || manager || description || carLicense || labeledURI || inetUserHTTPURL || seeAlso || userPassword")(version 3.0;acl "Self service";allow (write) userdn="ldap:///self";) + +dn: cn=ipaConfig,cn=etc,dc=greyoak,dc=com +changetype: modify +add: aci +aci: (targetattr = "ipaUserSearchFields || ipaGroupSearchFields || ipaSearchTimeLimit || ipaSearchRecordsLimit || ipaCustomFields || ipaHomesRootDir || ipaDefaultLoginShell || ipaDefaultPrimaryGroup || ipaMaxUsernameLength || ipaPwdExpAdvNotify")(version 3.0;acl "Admins can write IPA policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) +replace: aci + +dn: cn=accounts,$SUFFIX +changetype: modify +add: aci +aci: (targetattr = "krbMaxPwdLife || krbMinPwdLife || krbPwdMinDiffChars || krbPwdMinLength || krbPwdHistoryLength")(version 3.0;acl "Admins can write password policy"; allow (write) groupdn="ldap:///cn=admins,cn=groups,cn=accounts,$SUFFIX";) diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 82c84855e..85d22993a 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -30,6 +30,7 @@ import xmlrpclib import copy import attrs from ipa import ipaerror +from urllib import quote,unquote import string from types import * @@ -420,17 +421,23 @@ class IPAServer: # FIXME: This should be dynamic and can include just about anything + # Get our configuration + config = self.get_ipa_config(opts) + # Let us add in some missing attributes if user.get('homedirectory') is None: - user['homedirectory'] = '/home/%s' % user.get('uid') + user['homedirectory'] = '%s/%s' % (config.get('ipahomesrootdir'), user.get('uid')) + user['homedirectory'] = user['homedirectory'].replace('//', '/') + user['homedirectory'] = user['homedirectory'].rstrip('/') + if user.get('loginshell') is None: + user['loginshell'] = config.get('ipadefaultloginshell') if user.get('gecos') is None: user['gecos'] = user['uid'] # If uidnumber is blank the the FDS dna_plugin will automatically # assign the next value. So we don't have to do anything with it. - # FIXME: put the default group in a config file - group_dn="cn=%s,%s,%s" % ("ipausers", DefaultGroupContainer, self.basedn) + group_dn="cn=%s,%s,%s" % (config.get('ipadefaultprimarygroup'), DefaultGroupContainer, self.basedn) try: default_group = self.get_entry_by_dn(group_dn, ['dn','gidNumber'], opts) if default_group: @@ -467,50 +474,67 @@ class IPAServer: self.releaseConnection(conn) return res - def get_add_schema (self): - """Get the list of fields to be used when adding users in the GUI.""" - - # FIXME: this needs to be pulled from LDAP - fields = [] - - field1 = { - "name": "uid" , - "label": "Login:", - "type": "text", - "validator": "text", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "givenName" , - "label": "First name:", - "type": "text", - "validator": "string", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "sn" , - "label": "Last name:", - "type": "text", - "validator": "string", - "required": "true" - } - fields.append(field1) - - field1 = { - "name": "mail" , - "label": "E-mail address:", - "type": "text", - "validator": "email", - "required": "false" - } - fields.append(field1) - - return fields + def get_custom_fields (self, opts=None): + """Get the list of custom user fields. + + A schema is a list of dict's of the form: + label: The label dispayed to the user + field: the attribute name + required: true/false + + It is displayed to the user in the order of the list. + """ + + config = self.get_ipa_config(opts) + + fields = config.get('ipacustomfields') + + if fields is None or fields == '': + return [] + + fl = fields.split('$') + schema = [] + for x in range(len(fl)): + vals = fl[x].split(',') + if len(vals) != 3: + # Raise? + print "Invalid field, skipping" + d = dict(label=unquote(vals[0]), field=unquote(vals[1]), required=unquote(vals[2])) + schema.append(d) + + return schema + def set_custom_fields (self, schema, opts=None): + """Set the list of custom user fields. + + A schema is a list of dict's of the form: + label: The label dispayed to the user + field: the attribute name + required: true/false + + It is displayed to the user in the order of the list. + """ + config = self.get_ipa_config(opts) + + # The schema is stored as: + # label,field,required$label,field,required$... + # quote() from urilib is used to ensure that it is easy to unparse + + stored_schema = "" + for i in range(len(schema)): + entry = schema[i] + entry = quote(entry.get('label')) + "," + quote(entry.get('field')) + "," + quote(entry.get('required')) + + if stored_schema != "": + stored_schema = stored_schema + "$" + entry + else: + stored_schema = entry + + new_config = copy.deepcopy(config) + new_config['ipacustomfields'] = stored_schema + + return self.update_entry(config, new_config, opts) + def get_all_users (self, args=None, opts=None): """Return a list containing a User object for each existing user. @@ -529,18 +553,21 @@ class IPAServer: return users - def find_users (self, criteria, sattrs=None, searchlimit=0, timelimit=-1, + def find_users (self, criteria, sattrs=None, 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.""" - # TODO - retrieve from config - timelimit = 2 + config = self.get_ipa_config(opts) + if timelimit < 0: + timelimit = float(config.get('ipasearchtimelimit')) + if searchlimit < 0: + searchlimit = float(config.get('ipasearchrecordslimit')) # Assume the list of fields to search will come from a central # configuration repository. A good format for that would be # a comma-separated list of fields - search_fields_conf_str = "uid,givenName,sn,telephoneNumber,ou,title" + search_fields_conf_str = config.get('ipausersearchfields') search_fields = string.split(search_fields_conf_str, ",") criteria = self.__safe_filter(criteria) @@ -763,16 +790,22 @@ class IPAServer: finally: self.releaseConnection(conn) - def find_groups (self, criteria, sattrs=None, searchlimit=0, timelimit=-1, + def find_groups (self, criteria, sattrs=None, searchlimit=-1, timelimit=-1, opts=None): """Return a list containing a User object for each existing group that matches the criteria. """ + config = self.get_ipa_config(opts) + if timelimit < 0: + timelimit = float(config.get('ipasearchtimelimit')) + if searchlimit < 0: + searchlimit = float(config.get('ipasearchrecordslimit')) + # Assume the list of fields to search will come from a central # configuration repository. A good format for that would be # a comma-separated list of fields - search_fields_conf_str = "cn,description" + search_fields_conf_str = config.get('ipagroupsearchfields') search_fields = string.split(search_fields_conf_str, ",") criteria = self.__safe_filter(criteria) @@ -1155,10 +1188,10 @@ class IPAServer: """Do a memberOf search of groupdn and return the attributes in attr_list (an empty list returns everything).""" - # TODO - retrieve from config - timelimit = 2 + config = self.get_ipa_config(opts) + timelimit = float(config.get('ipasearchtimelimit')) - searchlimit = 0 + searchlimit = float(config.get('ipasearchrecordslimit')) groupdn = self.__safe_filter(groupdn) filter = "(memberOf=%s)" % groupdn @@ -1182,6 +1215,58 @@ class IPAServer: return entries +# Configuration support + def get_ipa_config(self, opts=None): + """Retrieve the IPA configuration""" + try: + config = self.get_entry_by_cn("ipaconfig", None, opts) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + raise ipaerror.gen_exception(ipaerror.LDAP_NO_CONFIG) + + return config + + def update_ipa_config(self, oldconfig, newconfig, opts=None): + """Update the IPA configuration""" + + # The LDAP routines want strings, not ints, so convert a few + # things. Otherwise it sees a string -> int conversion as a change. + try: + newconfig['krbmaxpwdlife'] = str(newconfig.get('krbmaxpwdlife')) + newconfig['krbminpwdlife'] = str(newconfig.get('krbminpwdlife')) + newconfig['krbpwdmindiffchars'] = str(newconfig.get('krbpwdmindiffchars')) + newconfig['krbpwdminlength'] = str(newconfig.get('krbpwdminlength')) + newconfig['krbpwdhistorylength'] = str(newconfig.get('krbpwdhistorylength')) + except KeyError: + # These should all be there but if not, let things proceed + pass + return self.update_entry(oldconfig, newconfig, opts) + + def get_password_policy(self, opts=None): + """Retrieve the IPA password policy""" + try: + policy = self.get_entry_by_cn("accounts", None, opts) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + raise ipaerror.gen_exception(ipaerror.LDAP_NO_CONFIG) + + return policy + + def update_password_policy(self, oldpolicy, newpolicy, opts=None): + """Update the IPA configuration""" + + # 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')) + except KeyError: + # These should all be there but if not, let things proceed + pass + + return self.update_entry(oldpolicy, newpolicy, opts) + def ldap_search_escape(match): """Escapes out nasty characters from the ldap search. See RFC 2254.""" diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index 86f5fda02..23bdcec1e 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -326,7 +326,8 @@ def handler(req, profiling=False): h.register_function(f.get_user_by_email) h.register_function(f.get_users_by_manager) h.register_function(f.add_user) - h.register_function(f.get_add_schema) + h.register_function(f.get_custom_fields) + h.register_function(f.set_custom_fields) h.register_function(f.get_all_users) h.register_function(f.find_users) h.register_function(f.update_user) @@ -351,6 +352,10 @@ def handler(req, profiling=False): h.register_function(f.delete_group) h.register_function(f.attrs_to_labels) h.register_function(f.group_members) + h.register_function(f.get_ipa_config) + h.register_function(f.update_ipa_config) + h.register_function(f.get_password_policy) + h.register_function(f.update_password_policy) h.handle_request(req) finally: pass |