summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins/automember.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib/plugins/automember.py')
-rw-r--r--ipalib/plugins/automember.py587
1 files changed, 587 insertions, 0 deletions
diff --git a/ipalib/plugins/automember.py b/ipalib/plugins/automember.py
new file mode 100644
index 000000000..db58a7aa3
--- /dev/null
+++ b/ipalib/plugins/automember.py
@@ -0,0 +1,587 @@
+# Authors:
+# Jr Aquino <jr.aquino@citrix.com>
+#
+# Copyright (C) 2011 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from ipalib import api, errors
+from ipalib import Str, StrEnum
+from ipalib.plugins.baseldap import *
+from ipalib import _, ngettext
+from ipalib.request import context
+from ipalib.dn import *
+import ldap as _ldap
+
+__doc__ = _("""
+Auto Membership Rule.
+
+Bring clarity to the membership of hosts and users by configuring inclusive
+or exclusive regex paterns, you can automatically assign a new entries into
+a group or hostgroup based upon attribute information.
+
+A rule is directly associated with a group by name, so you cannot create
+a rule without an accompanying group or hostgroup
+
+A condition is a regular expression used by 389-ds to match a new incoming
+entry with an automember rule. If it matches an inclusive rule then the
+entry is added to the appropriate group or hostgroup.
+
+EXAMPLES:
+
+ Create the initial group or hostgroup:
+ ipa hostgroup-add --desc="Web Servers" webservers
+ ipa group-add --desc="Developers" devel
+
+ Create the initial rule:
+ ipa automember-add --type=hostgroup webservers
+ ipa automember-add --type=group devel
+
+ Add a condition to the rule:
+ ipa automember-add-condition --key=fqdn --type=hostgroup --inclusive-regex=^web[1-9]+\.example\.com webservers
+ ipa automember-add-condition --key=manager --type=group --inclusive-regex=^uid=mscott devel
+
+ Add an exclusive condition to the rule to prevent auto assignment:
+ ipa automember-add-condition --key=fqdn --type=hostgroup --exclusive-regex=^web5\.example\.com webservers
+
+ Add a host:
+ ipa host-add web1.example.com
+
+ Add a user:
+ ipa user-add --first=Tim --last=User --password tuser1 --manager=mscott
+
+ Verify automembership:
+ ipa hostgroup-show webservers
+ Host-group: webservers
+ Description: Web Servers
+ Member hosts: web1.example.com
+
+ ipa group-show devel
+ Group name: devel
+ Description: Developers
+ GID: 1004200000
+ Member users: tuser
+
+ Remove a condition from the rule:
+ ipa automember-remove-condition --key=fqdn --type=hostgroup --inclusive-regex=^web[1-9]+\.example\.com webservers
+
+ Modify the automember rule:
+ ipa automember-mod
+
+ Set the default target group:
+ ipa automember-default-group-set --default-group=webservers --type=hostgroup
+ ipa automember-default-group-set --default-group=ipausers --type=group
+
+ Set the default target group:
+ ipa automember-default-group-remove --type=hostgroup
+ ipa automember-default-group-remove --type=group
+
+ Show the default target group:
+ ipa automember-default-group-show --type=hostgroup
+ ipa automember-default-group-show --type=group
+
+ Find all of the automember rules:
+ ipa automember-find
+
+ Display a automember rule:
+ ipa automember-show --type=hostgroup webservers
+ ipa automember-show --type=group devel
+
+ Delete an automember rule:
+ ipa automember-del --type=hostgroup webservers
+ ipa automember-del --type=group devel
+""")
+
+# Options used by Condition Add and Remove.
+INCLUDE_RE = 'automemberinclusiveregex'
+EXCLUDE_RE = 'automemberexclusiveregex'
+
+regex_attrs = (
+ List('automemberinclusiveregex?',
+ cli_name='inclusive_regex',
+ label=_('Inclusive Regex'),
+ doc=_('Inclusive Regex'),
+ multivalue=True,
+ alwaysask=True,
+ ),
+ List('automemberexclusiveregex?',
+ cli_name='exclusive_regex',
+ label=_('Exclusive Regex'),
+ doc=_('Exclusive Regex'),
+ multivalue=True,
+ alwaysask=True,
+ ),
+ Str('key',
+ label=_('Attribute Key'),
+ doc=_('Attribute to filter via regex. For example fqdn for a host, or manager for a user'),
+ flags=['no_create', 'no_update', 'no_search']
+ ),
+)
+
+group_type = (
+ StrEnum('type',
+ label=_('Grouping Type'),
+ doc=_('Grouping to which the rule applies'),
+ values=(u'group', u'hostgroup', ),
+ ),
+)
+
+automember_rule = (
+ Str('cn',
+ cli_name='automember_rule',
+ label=_('Automember Rule'),
+ doc=_('Automember Rule'),
+ normalizer=lambda value: value.lower(),
+ ),
+)
+
+class automember(LDAPObject):
+
+ """
+ Bring automember to a hostgroup with an Auto Membership Rule.
+ """
+
+ container_dn = api.env.container_automember
+
+ object_name = 'auto_member_rule'
+ object_name_plural = 'auto_member_rules'
+ object_class = ['top', 'automemberregexrule']
+ default_attributes = [
+ 'automemberinclusiveregex', 'automemberexclusiveregex',
+ 'cn', 'automembertargetgroup', 'description', 'automemberdefaultgroup'
+ ]
+
+ label = _('Auto Membership Rule')
+
+ takes_params = (
+ Str('description?',
+ cli_name='desc',
+ label=_('Description'),
+ doc=_('A description of this auto member rule'),
+ ),
+ Str('automemberdefaultgroup?',
+ cli_name='default_group',
+ label=_('Default Group'),
+ doc=_('Default group for entires to land'),
+ flags=['no_create', 'no_update', 'no_search']
+ ),
+ )
+
+ def dn_exists(self, grouptype, groupname, *keys):
+ ldap = self.api.Backend.ldap2
+ dn = self.api.Object[grouptype].get_dn(groupname)
+ try:
+ (gdn, entry_attrs) = ldap.get_entry(dn, [])
+ except errors.NotFound:
+ raise errors.NotFound(reason=_(u'Group: %s not found!' % groupname))
+ return gdn
+
+ def get_dn(self, *keys, **options):
+ if self.parent_object:
+ parent_dn = self.api.Object[self.parent_object].get_dn(*keys[:-1])
+ else:
+ parent_dn = self.container_dn
+ grouptype = options['type']
+ try:
+ ndn = DN(('cn', keys[-1]), ('cn', grouptype), DN(parent_dn))
+ except IndexError:
+ ndn = DN(('cn', grouptype), DN(parent_dn))
+ parent_dn = str(ndn)
+ return parent_dn
+
+ def check_attr(self, attr):
+ """
+ Verify that the user supplied key is a valid attribute in the schema
+ """
+ ldap = self.api.Backend.ldap2
+ if not ldap.schema:
+ ldap.get_schema()
+ obj = ldap.schema.get_obj(_ldap.schema.AttributeType, attr)
+ if obj is not None:
+ return obj
+ else:
+ raise errors.NotFound(reason=_('%s is not a valid attribute.' % attr))
+
+api.register(automember)
+
+
+def automember_container_exists(ldap):
+ try:
+ ldap.get_entry(api.env.container_automember, [])
+ except errors.NotFound:
+ return False
+ return True
+
+class automember_add(LDAPCreate):
+ __doc__ = _("""
+ Add an automember rule.
+ """)
+ takes_options = group_type
+ takes_args = automember_rule
+ msg_summary = _('Added automember rule "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+
+ entry_attrs['cn'] = keys[-1]
+ if not automember_container_exists(self.api.Backend.ldap2):
+ raise errors.NotFound(reason=_('Auto Membership is not configured'))
+ entry_attrs['automembertargetgroup'] = self.obj.dn_exists(options['type'], keys[-1])
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_add, self).execute(*keys, **options)
+ result['value'] = keys[-1]
+ return result
+
+api.register(automember_add)
+
+
+class automember_add_condition(LDAPUpdate):
+ __doc__ = _("""
+ Add conditions to an automember rule.
+ """)
+ has_output_params = (
+ Str('failed',
+ label=_('Failed to add'),
+ flags=['suppress_empty'],
+ ),
+ )
+
+ takes_options = regex_attrs + group_type
+ takes_args = automember_rule
+ msg_summary = _('Added condition(s) to "%(value)s"')
+
+ # Prepare the output to expect failed results
+ has_output = (
+ output.summary,
+ output.Entry('result'),
+ output.value,
+ output.Output('failed',
+ type=dict,
+ doc=_('Conditions that could not be added'),
+ ),
+ output.Output('completed',
+ type=int,
+ doc=_('Number of conditions added'),
+ ),
+ )
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ # Check to see if the automember rule exists
+ try:
+ (tdn, test_attrs) = ldap.get_entry(dn, [])
+ except errors.NotFound:
+ raise errors.NotFound(reason=_(u'Auto member rule: %s not found!' % keys[0]))
+ # Define container key
+ key = options['key']
+ # Check to see if the attribute is valid
+ self.obj.check_attr(key)
+
+ key = '%s=' % key
+ completed = 0
+ failed = {'failed': {}}
+
+ for attr in (INCLUDE_RE, EXCLUDE_RE):
+ failed['failed'][attr] = []
+ if attr in options and options[attr]:
+ entry_attrs[attr] = [key + condition for condition in options[attr]]
+ completed += len(entry_attrs[attr])
+ try:
+ (dn, old_entry) = ldap.get_entry(
+ dn, [attr], normalize=self.obj.normalize_dn)
+ for regex in old_entry:
+ if not isinstance(entry_attrs[regex], (list, tuple)):
+ entry_attrs[regex] = [entry_attrs[regex]]
+ duplicate = set(old_entry[regex]) & set(entry_attrs[regex])
+ if len(duplicate) > 0:
+ completed -= 1
+ else:
+ entry_attrs[regex] = list(entry_attrs[regex]) + old_entry[regex]
+ except errors.NotFound:
+ failed['failed'][attr].append(regex)
+
+ # Set failed and completed to they can be harvested in the execute super
+ setattr(context, 'failed', failed)
+ setattr(context, 'completed', completed)
+ setattr(context, 'entry_attrs', entry_attrs)
+
+ # Make sure to returned the failed results if there is nothing to remove
+ if completed == 0:
+ (dn, entry_attrs) = ldap.get_entry(
+ dn, attrs_list, normalize=self.obj.normalize_dn
+ )
+ raise errors.EmptyModlist
+ return dn
+
+ def execute(self, *keys, **options):
+ __doc__ = _("""
+ Override this so we can add completed and failed to the return result.
+ """)
+ try:
+ result = super(automember_add_condition, self).execute(*keys, **options)
+ except errors.EmptyModlist:
+ result = {'result': getattr(context, 'entry_attrs'), 'value': keys[-1]}
+ result['failed'] = getattr(context, 'failed')
+ result['completed'] = getattr(context, 'completed')
+ result['value'] = keys[-1]
+ return result
+
+api.register(automember_add_condition)
+
+
+class automember_remove_condition(LDAPUpdate):
+ __doc__ = _("""
+ Remove conditions from an automember rule.
+ """)
+ takes_options = regex_attrs + group_type
+ takes_args = automember_rule
+ msg_summary = _('Removed condition(s) to "%(value)s"')
+
+ # Prepare the output to expect failed results
+ has_output = (
+ output.summary,
+ output.Entry('result'),
+ output.value,
+ output.Output('failed',
+ type=dict,
+ doc=_('Conditions that could not be removed'),
+ ),
+ output.Output('completed',
+ type=int,
+ doc=_('Number of conditions removed'),
+ ),
+ )
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ # Check to see if the automember rule exists
+ try:
+ (tdn, test_attrs) = ldap.get_entry(dn, [])
+ except errors.NotFound:
+ raise errors.NotFound(reason=_(u'Auto member rule: %s not found!' % keys[0]))
+
+ # Define container key
+ type_attr_default = {'group': 'manager', 'hostgroup': 'fqdn'}
+ if 'key' in options:
+ key = options['key']
+ else:
+ key = type_attr_default[options['type']]
+
+ key = '%s=' % key
+ completed = 0
+ failed = {'failed': {}}
+
+ # Check to see if there are existing exclusive conditions present.
+ (dn, exclude_present) = ldap.get_entry(
+ dn, [EXCLUDE_RE], normalize=self.obj.normalize_dn)
+
+ for attr in (INCLUDE_RE, EXCLUDE_RE):
+ failed['failed'][attr] = []
+ if attr in options and options[attr]:
+ entry_attrs[attr] = [key + condition for condition in options[attr]]
+ (dn, entry_attrs_) = ldap.get_entry(
+ dn, [attr], normalize=self.obj.normalize_dn
+ )
+ old_entry = entry_attrs_.get(attr, [])
+ for regex in entry_attrs[attr]:
+ if regex in old_entry:
+ old_entry.remove(regex)
+ completed += 1
+ else:
+ failed['failed'][attr].append(regex)
+ entry_attrs[attr] = old_entry
+ # Set failed and completed to they can be harvested in the execute super
+ setattr(context, 'failed', failed)
+ setattr(context, 'completed', completed)
+ setattr(context, 'entry_attrs', entry_attrs)
+
+ # Make sure to returned the failed results if there is nothing to remove
+ if completed == 0:
+ (dn, entry_attrs) = ldap.get_entry(
+ dn, attrs_list, normalize=self.obj.normalize_dn
+ )
+ raise errors.EmptyModlist
+ return dn
+
+ def execute(self, *keys, **options):
+ __doc__ = _("""
+ Override this so we can set completed and failed.
+ """)
+ try:
+ result = super(automember_remove_condition, self).execute(*keys, **options)
+ except errors.EmptyModlist:
+ result = {'result': getattr(context, 'entry_attrs'), 'value': keys[-1]}
+ result['failed'] = getattr(context, 'failed')
+ result['completed'] = getattr(context, 'completed')
+ result['value'] = keys[-1]
+ return result
+
+api.register(automember_remove_condition)
+
+
+class automember_mod(LDAPUpdate):
+ __doc__ = _("""
+ Modify an automember rule.
+ """)
+ takes_args = automember_rule
+ takes_options = group_type
+ msg_summary = _('Modified automember rule "%(value)s"')
+
+ def execute(self, *keys, **options):
+ result = super(automember_mod, self).execute(*keys, **options)
+ result['value'] = keys[-1]
+ return result
+
+api.register(automember_mod)
+
+
+class automember_del(LDAPDelete):
+ __doc__ = _("""
+ Delete an automember rule.
+ """)
+ takes_args = automember_rule
+ takes_options = group_type
+ msg_summary = _('Deleted automember rule "%(value)s"')
+
+ def execute(self, *keys, **options):
+ result = super(automember_del, self).execute(*keys, **options)
+ result['value'] = keys[-1]
+ return result
+
+api.register(automember_del)
+
+
+class automember_find(LDAPSearch):
+ __doc__ = _("""
+ Search for automember rules.
+ """)
+ takes_options = group_type
+ has_output_params = LDAPSearch.has_output_params + automember_rule + regex_attrs
+
+ msg_summary = ngettext(
+ '%(count)d rules matched', '%(count)d rules matched', 0
+ )
+
+ def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
+ scope = ldap.SCOPE_SUBTREE
+ ndn = DN(('cn', options['type']), DN(base_dn))
+ base_dn = str(ndn)
+ return (filters, base_dn, scope)
+
+api.register(automember_find)
+
+
+class automember_show(LDAPRetrieve):
+ __doc__ = _("""
+ Display information about an automember rule.
+ """)
+ takes_args = automember_rule
+ takes_options = group_type
+ has_output_params = LDAPRetrieve.has_output_params + regex_attrs
+
+ def execute(self, *keys, **options):
+ result = super(automember_show, self).execute(*keys, **options)
+ result['value'] = keys[-1]
+ return result
+
+api.register(automember_show)
+
+
+class automember_default_group_set(LDAPUpdate):
+ __doc__ = _("""
+ Set default group for all unmatched entries.
+ """)
+
+ takes_options = (
+ Str('automemberdefaultgroup',
+ cli_name='default_group',
+ label=_('Default Group'),
+ doc=_('Default group for entires to land'),
+ flags=['no_create', 'no_update']
+ ),
+ ) + group_type
+ msg_summary = _('Set default group for automember "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ dn = DN(('cn', options['type']), api.env.container_automember)
+ dn = str(dn)
+ entry_attrs['automemberdefaultgroup'] = self.obj.dn_exists(options['type'], options['automemberdefaultgroup'])
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_default_group_set, self).execute(*keys, **options)
+ result['value'] = options['type']
+ return result
+
+api.register(automember_default_group_set)
+
+
+class automember_default_group_remove(LDAPUpdate):
+ __doc__ = _("""
+ Remove default group for all unmatched entries.
+ """)
+
+ takes_options = group_type
+ msg_summary = _('Removed default group for automember "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ dn = DN(('cn', options['type']), api.env.container_automember)
+ dn = str(dn)
+ attr = 'automemberdefaultgroup'
+
+ (dn, entry_attrs_) = ldap.get_entry(
+ dn, [attr], normalize=self.obj.normalize_dn
+ )
+
+ if attr not in entry_attrs_:
+ raise errors.NotFound(reason=_(u'No default group set'))
+ else:
+ entry_attrs[attr] = []
+ return dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ if 'automemberdefaultgroup' not in entry_attrs:
+ entry_attrs['automemberdefaultgroup'] = u'No default group set'
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_default_group_remove, self).execute(*keys, **options)
+ result['value'] = options['type']
+ return result
+
+api.register(automember_default_group_remove)
+
+
+class automember_default_group_show(LDAPRetrieve):
+ __doc__ = _("""
+ Display information about the default automember groups.
+ """)
+ takes_options = group_type
+
+ def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
+ dn = DN(('cn', options['type']), api.env.container_automember)
+ dn = str(dn)
+ return dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ if 'automemberdefaultgroup' not in entry_attrs:
+ entry_attrs['automemberdefaultgroup'] = u'No default group set'
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_default_group_show, self).execute(*keys, **options)
+ result['value'] = options['type']
+ return result
+
+api.register(automember_default_group_show)