summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins/pwpolicy.py
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2009-10-02 09:30:16 -0400
committerJason Gerard DeRose <jderose@redhat.com>2009-10-05 13:29:55 -0600
commitdac224c25a2ff8a1400d0a746f600f81cfad6901 (patch)
treebbdb2882717b5f47e39810b8e5d2c504f08f0e6e /ipalib/plugins/pwpolicy.py
parent97dfa586dea42d44ad8e1d9148547fd805abd046 (diff)
downloadfreeipa-dac224c25a2ff8a1400d0a746f600f81cfad6901.tar.gz
freeipa-dac224c25a2ff8a1400d0a746f600f81cfad6901.tar.xz
freeipa-dac224c25a2ff8a1400d0a746f600f81cfad6901.zip
Add support for per-group kerberos password policy.
Use a Class of Service template to do per-group password policy. The design calls for non-overlapping groups but with cospriority we can still make sense of things. The password policy entries stored under the REALM are keyed only on the group name because the MIT ldap plugin can't handle quotes in the DN. It also can't handle spaces between elements in the DN.
Diffstat (limited to 'ipalib/plugins/pwpolicy.py')
-rw-r--r--ipalib/plugins/pwpolicy.py234
1 files changed, 217 insertions, 17 deletions
diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py
index e7078f5e9..5a07c880a 100644
--- a/ipalib/plugins/pwpolicy.py
+++ b/ipalib/plugins/pwpolicy.py
@@ -22,11 +22,13 @@
Password policy
"""
-from ipalib import api, errors
-from ipalib import Command
-from ipalib import Int
+from ipalib import api, crud, errors
+from ipalib import Command, Object
+from ipalib import Int, Str
+from ldap.functions import explode_dn
_fields = {
+ 'group': 'Group policy',
'krbminpwdlife': 'Minimum lifetime (in hours)',
'krbmaxpwdlife': 'Maximum lifetime (in days)',
'krbpwdmindiffchars': 'Minimum number of characters classes',
@@ -35,6 +37,7 @@ _fields = {
}
def _convert_time_for_output(entry_attrs):
+ # Convert seconds to hours and days for displaying to user
if 'krbmaxpwdlife' in entry_attrs:
entry_attrs['krbmaxpwdlife'][0] = str(
int(entry_attrs['krbmaxpwdlife'][0]) / 86400
@@ -44,12 +47,63 @@ def _convert_time_for_output(entry_attrs):
int(entry_attrs['krbminpwdlife'][0]) / 3600
)
+def _convert_time_on_input(entry_attrs):
+ # Convert hours and days to seconds for writing to LDAP
+ if 'krbmaxpwdlife' in entry_attrs:
+ entry_attrs['krbmaxpwdlife'] = entry_attrs['krbmaxpwdlife'] * 86400
+ if 'krbminpwdlife' in entry_attrs:
+ entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600
-class pwpolicy_mod(Command):
+def make_cos_entry(group, cospriority=None):
"""
- Modify password policy.
+ Make the CoS dn and entry for this group.
+
+ Returns (cos_dn, cos_entry) where:
+ cos_dn = DN of the new CoS entry
+ cos_entry = entry representing this new object
"""
- takes_options = (
+
+ try:
+ (groupdn, group_attrs) = api.Command['group_show'](group)
+ except errors.NotFound:
+ raise errors.NotFound(reason="group '%s' does not exist" % group)
+
+ cos_entry = {}
+ if cospriority:
+ cos_entry['cospriority'] = cospriority
+ cos_entry['objectclass'] = ['top', 'costemplate', 'extensibleobject', 'krbcontainer']
+ cos_dn = 'cn=\"%s\", cn=cosTemplates, cn=accounts, %s' % (groupdn, api.env.basedn)
+
+ return (cos_dn, cos_entry)
+
+def make_policy_entry(group_cn, policy_entry):
+ """
+ Make the krbpwdpolicy dn and entry for this group.
+
+ Returns (policy_dn, policy_entry) where:
+ policy_dn = DN of the new password policy entry
+ policy_entry = entry representing this new object
+ """
+
+ # This DN must *NOT* have spaces between elements
+ policy_dn = "cn=%s,cn=%s,cn=kerberos,%s" % (group_cn, api.env.realm, api.env.basedn)
+
+ # Create the krb password policy entry. This MUST be located
+ # in the same container as the REALM or the kldap plugin won't
+ # recognize it. The usual CoS trick of putting the whole DN into
+ # the dn won't work either because the kldap plugin doesn't like
+ # quotes in the DN.
+ policy_entry['objectclass'] = ['top', 'nscontainer', 'krbpwdpolicy']
+ policy_entry['cn'] = group_cn
+
+ return (policy_dn, policy_entry)
+
+class pwpolicy(Object):
+ """
+ Password Policy object.
+ """
+
+ takes_params = (
Int('krbmaxpwdlife?',
cli_name='maxlife',
doc='Max. Password Lifetime (days)',
@@ -82,21 +136,96 @@ class pwpolicy_mod(Command):
),
)
+api.register(pwpolicy)
+
+class pwpolicy_add(crud.Create):
+ """
+ Create a new password policy associated with a group.
+ """
+
+ takes_options = (
+ Str('group',
+ doc='Group to set policy for',
+ attribute=False,
+ ),
+ Int('cospriority',
+ cli_name='priority',
+ doc='Priority of the policy. Higher number equals higher priority',
+ minvalue=0,
+ attribute=True,
+ ),
+ )
+
def execute(self, *args, **options):
- assert 'dn' not in options
ldap = self.api.Backend.ldap2
- entry_attrs = self.args_options_2_entry(*args, **options)
- dn = self.api.env.container_accounts
+ group_cn = options['group']
+
+ # Create the CoS template
+ (cos_dn, cos_entry) = make_cos_entry(group_cn, options.get('cospriority', None))
+ if 'cospriority' in options:
+ del options['cospriority']
+
+ # Create the new password policy
+ policy_entry = self.args_options_2_entry(*args, **options)
+ (policy_dn, policy_entry) = make_policy_entry(group_cn, policy_entry)
+ _convert_time_on_input(policy_entry)
+
+ # Link the two entries together
+ cos_entry['krbpwdpolicyreference'] = policy_dn
- # Convert hours and days to seconds
- if 'krbmaxpwdlife' in entry_attrs:
- entry_attrs['krbmaxpwdlife'] = entry_attrs['krbmaxpwdlife'] * 86400
- del entry_attrs['krbmaxpwdlife']
- if 'krbminpwdlife' in entry_attrs:
- entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600
- del entry_attrs['krbminpwdlife']
+ ldap.add_entry(policy_dn, policy_entry, normalize=False)
+ ldap.add_entry(cos_dn, cos_entry, normalize=False)
+ # The policy is what is interesting, return that
+ (dn, entry_attrs) = ldap.get_entry(policy_dn, policy_entry.keys())
+
+ _convert_time_for_output(entry_attrs)
+
+ return (dn, entry_attrs)
+
+ def output_for_cli(self, textui, result, *args, **options):
+# textui.print_name(self.name)
+# textui.print_dashed("Added policy for '%s'." % options['group'])
+ (dn, entry_attrs) = result
+
+ textui.print_name(self.name)
+ textui.print_plain('Password policy:')
+ for (k, v) in _fields.iteritems():
+ if k in entry_attrs:
+ textui.print_attribute(v, entry_attrs[k])
+ textui.print_dashed('Modified password policy.')
+
+api.register(pwpolicy_add)
+
+class pwpolicy_mod(crud.Update):
+ """
+ Modify password policy.
+ """
+ takes_options = (
+ Str('group?',
+ doc='Group to set policy for',
+ attribute=False,
+ ),
+ Int('cospriority?',
+ cli_name='priority',
+ doc='Priority of the policy. Higher number equals higher priority',
+ minvalue=0,
+ attribute=True,
+ ),
+ )
+
+ def execute(self, *args, **options):
+ assert 'dn' not in options
+ ldap = self.api.Backend.ldap2
+
+ if not 'group' in options:
+ dn = self.api.env.container_accounts
+ entry_attrs = self.args_options_2_entry(*args, **options)
+ else:
+ entry_attrs = self.args_options_2_entry(*args, **options)
+ (dn, entry_attrs) = make_policy_entry(options['group'], entry_attrs)
+ _convert_time_on_input(entry_attrs)
try:
ldap.update_entry(dn, entry_attrs)
except errors.EmptyModlist:
@@ -120,17 +249,88 @@ class pwpolicy_mod(Command):
api.register(pwpolicy_mod)
+class pwpolicy_del(crud.Delete):
+ """
+ Delete a group password policy.
+ """
+ takes_options = (
+ Str('group',
+ doc='Group to remove policy from',
+ ),
+ )
+
+ def execute(self, *args, **options):
+ assert 'dn' not in options
+ ldap = self.api.Backend.ldap2
+
+ group_cn = options['group']
+
+ # Get the DN of the CoS template to delete
+ try:
+ (cos_dn, cos_entry) = make_cos_entry(group_cn, None)
+ except errors.NotFound:
+ # Ok, perhaps the group was deleted, try to make the group DN
+ rdn = ldap.make_rdn_from_attr('cn', group_cn)
+ group_dn = ldap.make_dn_from_rdn(rdn, api.env.container_group)
+ cos_dn = 'cn=\"%s\", cn=cosTemplates, cn=accounts, %s' % (group_dn, api.env.basedn)
+ policy_entry = self.args_options_2_entry(*args, **options)
+ (policy_dn, policy_entry) = make_policy_entry(group_cn, policy_entry)
+
+ ldap.delete_entry(policy_dn, normalize=False)
+ ldap.delete_entry(cos_dn, normalize=False)
+
+ return True
+
+ def output_for_cli(self, textui, result, *args, **options):
+ textui.print_name(self.name)
+ textui.print_dashed('Deleted policy "%s".' % options['group'])
+
+api.register(pwpolicy_del)
+
class pwpolicy_show(Command):
"""
Display password policy.
"""
+ takes_options = (
+ Str('group?',
+ doc='Group to display policy',
+ ),
+ Str('user?',
+ doc='Display policy applied to a given user',
+ ),
+ )
def execute(self, *args, **options):
ldap = self.api.Backend.ldap2
- dn = self.api.env.container_accounts
+ dn = None
+ group = None
+
+ if 'user' in options:
+ rdn = ldap.make_rdn_from_attr('uid', options['user'])
+ user_dn = ldap.make_dn_from_rdn(rdn, api.env.container_user)
+ try:
+ (user_dn, user_attrs) = ldap.get_entry(user_dn, ['krbpwdpolicyreference'])
+ if 'krbpwdpolicyreference' in user_attrs:
+ dn = user_attrs['krbpwdpolicyreference'][0]
+ rdn = explode_dn(dn)
+ group = rdn[0].replace('cn=','')
+ except errors.NotFound:
+ raise errors.NotFound(reason="user '%s' not found" % options['user'])
+
+ if dn is None:
+ if not 'group' in options:
+ dn = self.api.env.container_accounts
+ else:
+ policy_entry = self.args_options_2_entry(*args, **options)
+ (dn, policy_entry) = make_policy_entry(options['group'], policy_entry)
(dn, entry_attrs) = ldap.get_entry(dn)
+ if 'user' in options:
+ if group:
+ entry_attrs['group'] = group
+ else:
+ entry_attrs['group'] = 'global'
_convert_time_for_output(entry_attrs)
return (dn, entry_attrs)