Enable group inactivation by using the Class of Service plugin.

This adds 2 new groups: activated and inactivated.

If you, or a group you are a member of, is in inactivated then you are too.

If you, or a group you are a member of, is in the activated group, then you
are too.

In a fight between activated and inactivated, activated wins.

The DNs for doing this matching is case and white space sensitive.

The goal is to never have to actually set nsAccountLock in a user directly
but move them between these groups.

We need to decide where in the CLI this will happen. Right it is split
between ipa-deluser and ipa-usermod. To inactivate groups for now just
add the group to inactivate or active.
This commit is contained in:
Rob Crittenden 2007-11-20 22:45:29 -05:00
parent 56d67b86e1
commit f42f1f44c8
15 changed files with 301 additions and 44 deletions

View File

@ -57,11 +57,14 @@ def main():
ret = client.delete_user(args[1])
msg = "deleted"
else:
ret = client.mark_user_deleted(args[1])
if (ret == "Success"):
try:
ret = client.mark_user_inactive(args[1])
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_EMPTY_MODLIST):
print "User is already marked inactive"
return 0
except:
raise
print args[1] + " successfully %s" % msg
else:
print args[1] + " " + ret
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:
print "The IPA XML-RPC service is not responding."

View File

@ -32,7 +32,7 @@ import ldap
import errno
def usage():
print "ipa-usermod [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] [-s|--shell STRING] [--add attribute=value] [--del attribute] [--set attribute=value] user"
print "ipa-usermod [-a|--activate] [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] [-s|--shell STRING] [--add attribute=value] [--del attribute] [--set attribute=value] user"
sys.exit(1)
def set_add_usage(which):
@ -40,6 +40,8 @@ def set_add_usage(which):
def parse_options():
parser = OptionParser()
parser.add_option("-a", "--activate", dest="activate", action="store_true",
help="Activate the user")
parser.add_option("-c", "--gecos", dest="gecos",
help="Set the GECOS field")
parser.add_option("-d", "--directory", dest="directory",
@ -111,7 +113,7 @@ def main():
return 1
# If any options are set we use just those. Otherwise ask for all of them.
if options.gn or options.sn or options.directory or options.gecos or options.mail or options.shell or options.addattr or options.delattr or options.setattr:
if options.gn or options.sn or options.directory or options.gecos or options.mail or options.shell or options.addattr or options.delattr or options.setattr or options.activate:
givenname = options.gn
lastname = options.sn
gecos = options.gecos
@ -236,8 +238,16 @@ def main():
value = cvalue + [value]
user.setValue(attr, value)
try:
if options.activate:
try:
client.mark_user_active(user.getValues('uid'))
print "User activated successfully."
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_EMPTY_MODLIST):
print "User is already marked active"
return 0
except:
raise
client.update_user(user)
except xmlrpclib.Fault, fault:
if fault.faultCode == errno.ECONNREFUSED:

View File

@ -177,10 +177,16 @@ class IPAClient:
return result
def mark_user_deleted(self,uid):
def mark_user_active(self,uid):
"""Set a user as active by uid."""
result = self.transport.mark_user_active(uid)
return result
def mark_user_inactive(self,uid):
"""Set a user as inactive by uid."""
result = self.transport.mark_user_deleted(uid)
result = self.transport.mark_user_inactive(uid)
return result
# Groups support
@ -335,6 +341,20 @@ class IPAClient:
entries.append(user.User(e))
return entries
def mark_group_active(self,cn):
"""Set a group as active by cn."""
result = self.transport.mark_group_active(cn)
return result
def mark_group_inactive(self,cn):
"""Set a group as inactive by cn."""
result = self.transport.mark_group_inactive(cn)
return result
# Configuration
def get_ipa_config(self):
"""Get the IPA configuration"""

View File

@ -318,12 +318,12 @@ class RPCClient:
return result
def mark_user_deleted(self,uid):
"""Mark a user as deleted/inactive"""
def mark_user_active(self,uid):
"""Mark a user as active"""
server = self.setup_server()
try:
result = server.mark_user_deleted(uid)
result = server.mark_user_active(uid)
except xmlrpclib.Fault, fault:
raise ipaerror.gen_exception(fault.faultCode, fault.faultString)
except socket.error, (value, msg):
@ -331,6 +331,20 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
def mark_user_inactive(self,uid):
"""Mark a user as inactive"""
server = self.setup_server()
try:
result = server.mark_user_inactive(uid)
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)
# Group support
def get_groups_by_member(self,member_dn,sattrs=None):
@ -601,6 +615,34 @@ class RPCClient:
return ipautil.unwrap_binary_data(result)
def mark_group_active(self,cn):
"""Mark a group as active"""
server = self.setup_server()
try:
result = server.mark_group_active(cn)
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 mark_group_inactive(self,cn):
"""Mark a group as inactive"""
server = self.setup_server()
try:
result = server.mark_group_inactive(cn)
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)
# Configuration support
def get_ipa_config(self):
"""Get the IPA configuration"""
server = self.setup_server()

View File

@ -9,6 +9,10 @@ class GroupFields():
editprotected_hidden = widgets.HiddenField(name="editprotected")
nsAccountLock = widgets.SingleSelectField(name="nsAccountLock",
label="Group Status",
options = [("", "active"), ("true", "inactive")])
group_orig = widgets.HiddenField(name="group_orig")
member_data = widgets.HiddenField(name="member_data")
dn_to_info_json = widgets.HiddenField(name="dn_to_info_json")

View File

@ -22,7 +22,7 @@ log = logging.getLogger(__name__)
group_new_form = ipagui.forms.group.GroupNewForm()
group_edit_form = ipagui.forms.group.GroupEditForm()
group_fields = ['*']
group_fields = ['*', 'nsAccountLock']
class GroupController(IPAController):
@ -75,6 +75,9 @@ class GroupController(IPAController):
new_group.setValue('description', kw.get('description'))
rv = client.add_group(new_group)
if kw.get('nsAccountLock'):
client.mark_group_inactive(kw.get('cn'))
except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
turbogears.flash("Group with name '%s' already exists" %
kw.get('cn'))
@ -224,6 +227,12 @@ class GroupController(IPAController):
turbogears.flash("Edit group cancelled")
raise turbogears.redirect('/group/show', cn=cn[0])
if kw.get('editprotected') == '':
# if editprotected set these don't get sent in kw
orig_group_dict = loads(b64decode(kw.get('group_orig')))
kw['cn'] = orig_group_dict['cn']
kw['gidnumber'] = orig_group_dict['gidnumber']
# Decode the member data, in case we need to round trip
member_dicts = loads(b64decode(kw.get('member_data')))
@ -251,6 +260,17 @@ class GroupController(IPAController):
if new_group.gidnumber != new_gid:
group_modified = True
new_group.setValue('gidnumber', new_gid)
else:
new_group.setValue('gidnumber', orig_group_dict.get('gidnumber'))
new_group.setValue('cn', orig_group_dict.get('cn'))
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
if new_group.cn != kw.get('cn'):
group_modified = True
new_group.setValue('cn', kw['cn'])
@ -268,6 +288,17 @@ class GroupController(IPAController):
return dict(form=group_edit_form, group=kw, members=member_dicts,
tg_template='ipagui.templates.groupedit')
if kw.get('nsAccountLock') == '':
kw['nsAccountLock'] = "false"
modify_no_update = False
if kw.get('nsAccountLock') == "false" and new_group.getValues('nsaccountlock') == "true":
client.mark_group_active(kw.get('cn'))
modify_no_update = True
elif kw.get('nsAccountLock') == "true" and new_group.nsaccountlock != "true":
client.mark_group_inactive(kw.get('cn'))
modify_no_update = True
#
# Add members
#
@ -326,7 +357,7 @@ class GroupController(IPAController):
cn0 = kw['cn'][0]
else:
cn0 = kw['cn']
if group_modified == True:
if group_modified == True or modify_no_update == True:
turbogears.flash("%s updated!" % cn0)
else:
turbogears.flash("No modifications requested.")

View File

@ -197,14 +197,14 @@ class UserController(IPAController):
new_user.setValue('carlicense', kw.get('carlicense'))
new_user.setValue('labeleduri', kw.get('labeleduri'))
if kw.get('nsAccountLock'):
new_user.setValue('nsAccountLock', 'true')
for custom_field in user_new_form.custom_fields:
new_user.setValue(custom_field.name,
kw.get(custom_field.name, ''))
rv = client.add_user(new_user)
if kw.get('nsAccountLock'):
client.mark_user_inactive(kw.get('uid'))
except ipaerror.exception_for(ipaerror.LDAP_DUPLICATE):
turbogears.flash("User with login '%s' already exists" %
kw.get('uid'))
@ -482,12 +482,6 @@ class UserController(IPAController):
new_user.setValue('carlicense', kw.get('carlicense'))
new_user.setValue('labeleduri', kw.get('labeleduri'))
if kw.get('nsAccountLock'):
new_user.setValue('nsAccountLock', 'true')
else:
new_user.setValue('nsAccountLock', None)
if kw.get('editprotected') == 'true':
if kw.get('userpassword'):
password_change = True
@ -572,6 +566,20 @@ class UserController(IPAController):
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
if kw.get('nsAccountLock') == '':
kw['nsAccountLock'] = "false"
try:
if kw.get('nsAccountLock') == "false" and new_user.getValues('nsaccountlock') == "true":
client.mark_user_active(kw.get('uid'))
elif kw.get('nsAccountLock') == "true" and new_user.nsaccountlock != "true":
client.mark_user_inactive(kw.get('uid'))
except ipaerror.IPAError, e:
turbogears.flash("User status change failed: " + str(e) + "<br/>" + e.detail[0]['desc'])
return dict(form=user_edit_form, user=kw,
user_groups=user_groups_dicts,
tg_template='ipagui.templates.useredit')
turbogears.flash("%s updated!" % kw['uid'])
raise turbogears.redirect('/user/show', uid=kw['uid'])

View File

@ -112,6 +112,16 @@ from ipagui.helpers import ipahelper
</script>
</td>
</tr>
<tr>
<th>
<label class="fieldlabel" for="${group_fields.nsAccountLock.field_id}" py:content="group_fields.nsAccountLock.label" />:
</th>
<td>
<span py:replace="group_fields.nsAccountLock.display(value_for(group_fields.nsAccountLock))" />
<span py:if="tg.errors.get('nsAccountLock')" class="fielderror"
py:content="tg.errors.get('nsAccountLock')" />
</td>
</tr>
</table>
<div>

View File

@ -20,7 +20,7 @@
</div>
<div py:if='(groups != None) and (len(groups) > 0)'>
<h2>${len(groups)} results returned:</h2>
<table id="resultstable" class="details sortable resizable">
<table id="resultstable" class="details sortable resizable" cellspacing="0">
<thead>
<tr>
<th>
@ -32,7 +32,15 @@
</tr>
</thead>
<tbody>
<tr py:for="group in groups">
<tr py:for="group in groups" py:if="group.nsAccountLock != 'true'">
<td>
<a href="${tg.url('/group/show',cn=group.cn)}">${group.cn}</a>
</td>
<td>
${group.description}
</td>
</tr>
<tr id="inactive" py:for="group in groups" py:if="group.nsAccountLock == 'true'">
<td>
<a href="${tg.url('/group/show',cn=group.cn)}">${group.cn}</a>
</td>

View File

@ -11,6 +11,7 @@ cn = group.get('cn')
if isinstance(cn, list):
cn = cn[0]
edit_url = tg.url('/group/edit', cn=cn)
from ipagui.helpers import userhelper
?>
<div id="details">
<h1>View Group</h1>
@ -42,6 +43,12 @@ edit_url = tg.url('/group/edit', cn=cn)
</th>
<td>${group.get("gidnumber")}</td>
</tr>
<tr>
<th>
<label class="fieldlabel" py:content="fields.nsAccountLock.label" />:
</th>
<td>${userhelper.account_status_display(group.get("nsAccountLock"))}</td>
</tr>
</table>
<h2 class="formsection">Group Members</h2>

View File

@ -787,7 +787,7 @@ from ipagui.helpers import ipahelper
group_dn = group.get('dn')
group_dn_esc = ipahelper.javascript_string_escape(group_dn)
group_name = group.get('cn')[0]
group_name = group.get('cn')
group_descr = "[group]"
group_type = "group"

View File

@ -116,3 +116,42 @@ ipaDefaultLoginShell: /bin/sh
ipaDefaultPrimaryGroup: ipausers
ipaMaxUsernameLength: 8
ipaPwdExpAdvNotify: 4
dn: cn=account inactivation,cn=accounts,$SUFFIX
description: Lock accounts based on group membership
objectClass: top
objectClass: ldapsubentry
objectClass: cosSuperDefinition
objectClass: cosClassicDefinition
cosTemplateDn: cn=cosTemplates,cn=accounts,$SUFFIX
cosAttribute: nsAccountLock operational
cosAttribute: pager
cosSpecifier: memberOf
cn: Account Inactivation
dn: cn=cosTemplates,cn=accounts,$SUFFIX
objectclass: top
objectclass: nsContainer
cn: cosTemplates
dn: cn="cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX
objectClass: top
objectClass: cosTemplate
objectClass: extensibleobject
nsAccountLock: true
cosPriority: 1
dn: cn=inactivated,cn=account inactivation,cn=accounts,$SUFFIX
objectclass: top
objectclass: groupofuniquenames
dn: cn="cn=activated,cn=account inactivation,cn=accounts,$SUFFIX", cn=cosTemplates,cn=accounts,$SUFFIX
objectClass: top
objectClass: cosTemplate
objectClass: extensibleobject
nsAccountLock: false
cosPriority: 0
dn: cn=Activated,cn=Account Inactivation,cn=accounts,$SUFFIX
objectclass: top
objectclass: groupofuniquenames

View File

@ -119,7 +119,7 @@ class DsInstance(service.Service):
def __setup_sub_dict(self):
server_root = find_server_root()
self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid,
PASSWORD=self.dm_password, SUFFIX=self.suffix,
PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(),
REALM=self.realm_name, USER=self.ds_user,
SERVER_ROOT=server_root)

View File

@ -36,6 +36,7 @@ import string
from types import *
import os
import re
import logging
try:
from threading import Lock
@ -49,6 +50,11 @@ ACIContainer = "cn=accounts"
DefaultUserContainer = "cn=users,cn=accounts"
DefaultGroupContainer = "cn=groups,cn=accounts"
# FIXME: need to check the ipadebug option in ipa.conf
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(message)s',
stream=sys.stderr)
#
# Apache runs in multi-process mode so each process will have its own
# connection. This could theoretically drive the total number of connections
@ -674,26 +680,80 @@ class IPAServer:
else:
raise
def mark_user_deleted (self, uid, opts=None):
"""Mark a user as inactive in LDAP. We aren't actually deleting
users here, just making it so they can't log in, etc."""
user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts)
def mark_entry_active (self, dn, opts=None):
"""Mark an entry as active in LDAP."""
# Are we doing an add or replace operation?
if user.has_key('nsaccountlock'):
if user['nsaccountlock'] == "true":
return "already marked as deleted"
has_key = True
else:
has_key = False
# This can be tricky. The entry itself can be marked inactive
# by being in the inactivated group. It can also be inactivated by
# being the member of an inactive group.
#
# First we try to remove the entry from the inactivated group. Then
# if it is still inactive we have to add it to the activated group
# which will override the group membership.
logging.debug("IPA: activating entry %s" % dn)
res = ""
# First, check the entry status
entry = self.get_entry_by_dn(dn, ['dn', 'nsAccountlock'], opts)
if entry.get('nsaccountlock', 'false') == "false":
logging.debug("IPA: already active")
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
group = self.get_entry_by_cn("inactivated", None, opts)
res = self.remove_member_from_group(entry.get('dn'), group.get('dn'), opts)
# Now they aren't a member of inactivated directly, what is the status
# now?
entry = self.get_entry_by_dn(dn, ['dn', 'nsAccountlock'], opts)
if entry.get('nsaccountlock', 'false') == "false":
# great, we're done
logging.debug("IPA: removing from inactivated did it.")
return res
# So still inactive, add them to activated
group = self.get_entry_by_cn("activated", None, opts)
res = self.add_member_to_group(dn, group.get('dn'), opts)
logging.debug("IPA: added to activated.")
conn = self.getConnection(opts)
try:
res = conn.inactivateEntry(user['dn'], has_key)
finally:
self.releaseConnection(conn)
return res
def mark_entry_inactive (self, dn, opts=None):
"""Mark an entry as inactive in LDAP."""
logging.debug("IPA: inactivating entry %s" % dn)
entry = self.get_entry_by_dn(dn, ['dn', 'nsAccountlock', 'memberOf'], opts)
if entry.get('nsaccountlock', 'false') == "true":
logging.debug("IPA: already marked as inactive")
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
# First see if they are in the activated group as this will override
# the our inactivation.
group = self.get_entry_by_cn("activated", None, opts)
self.remove_member_from_group(dn, group.get('dn'), opts)
# Now add them to inactivated
group = self.get_entry_by_cn("inactivated", None, opts)
res = self.add_member_to_group(dn, group.get('dn'), opts)
return res
def mark_user_active(self, uid, opts=None):
"""Mark a user as active"""
user = self.get_user_by_uid(uid, ['dn', 'uid'], opts)
return self.mark_entry_active(user.get('dn'))
def mark_user_inactive(self, uid, opts=None):
"""Mark a user as inactive"""
user = self.get_user_by_uid(uid, ['dn', 'uid'], opts)
return self.mark_entry_inactive(user.get('dn'))
def delete_user (self, uid, opts=None):
"""Delete a user. Not to be confused with inactivate_user. This
makes the entry go away completely.
@ -1215,6 +1275,18 @@ class IPAServer:
return entries
def mark_group_active(self, cn, opts=None):
"""Mark a group as active"""
group = self.get_entry_by_cn(cn, ['dn', 'cn'], opts)
return self.mark_entry_active(group.get('dn'))
def mark_group_inactive(self, cn, opts=None):
"""Mark a group as inactive"""
group = self.get_entry_by_cn(cn, ['dn', 'uid'], opts)
return self.mark_entry_inactive(group.get('dn'))
# Configuration support
def get_ipa_config(self, opts=None):
"""Retrieve the IPA configuration"""

View File

@ -332,7 +332,10 @@ def handler(req, profiling=False):
h.register_function(f.find_users)
h.register_function(f.update_user)
h.register_function(f.delete_user)
h.register_function(f.mark_user_deleted)
h.register_function(f.mark_user_active)
h.register_function(f.mark_user_inactive)
h.register_function(f.mark_group_active)
h.register_function(f.mark_group_inactive)
h.register_function(f.modifyPassword)
h.register_function(f.get_groups_by_member)
h.register_function(f.add_group)