def execute(self, aciname, aciprefix, **options): """ Execute the aci-delete operation. :param aciname: The name of the ACI being deleted. :param aciprefix: The ACI prefix. """ ldap = self.api.Backend.ldap2 entry = ldap.get_entry(self.api.env.basedn, ['aci']) acistrs = entry.get('aci', []) acis = _convert_strings_to_acis(acistrs) aci = _find_aci_by_name(acis, aciprefix, aciname) for a in acistrs: candidate = ACI(a) if aci.isequal(candidate): acistrs.remove(a) break entry['aci'] = acistrs ldap.update_entry(entry) return dict( result=True, value=pkey_to_value(aciname, options), )
def _convert_strings_to_acis(acistrs): acis = [] for a in acistrs: try: acis.append(ACI(a)) except SyntaxError, e: root_logger.warning("Failed to parse: %s" % a)
def make_test_aci(): a = ACI() a.name = "foo" a.set_target_attr(['title', 'givenname'], "!=") a.set_bindrule_keyword("groupdn") a.set_bindrule_operator("=") a.set_bindrule_expression( "\"ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com\"") a.permissions = ['read', 'write', 'add'] return a
def remove_anonymous_read_aci(self, ldap, anonymous_read_aci): base_entry = ldap.get_entry(self.api.env.basedn, ['aci']) acistrs = base_entry.get('aci', []) for acistr in acistrs: if ACI(acistr).isequal(anonymous_read_aci): logger.debug('Removing anonymous ACI: %s', acistr) acistrs.remove(acistr) break else: return ldap.update_entry(base_entry)
def test_aci_equality(): a = make_test_aci() print(a) b = ACI() b.name = "foo" b.set_target_attr(['givenname', 'title'], "!=") b.set_bindrule_keyword("groupdn") b.set_bindrule_operator("=") b.set_bindrule_expression( "\"ldap:///cn=foo,cn=groups,cn=accounts,dc=example,dc=com\"") b.permissions = ['add', 'read', 'write'] print(b) assert a.isequal(b) assert a == b assert not a != b # pylint: disable=unneeded-not
def get_upgrade_attr_lists(self, current_acistring, default_acistrings): """Compute included and excluded attributes for a new permission :param current_acistring: ACI is in LDAP currently :param default_acistrings: List of all default ACIs IPA historically used for this permission :return: (ipapermincludedattr, ipapermexcludedattr) for the upgraded permission An attribute will be included if the user has it in LDAP but it does not appear in *any* historic ACI. It will be excluded if it is in *all* historic ACIs but not in LDAP. Rationale: When we don't know which version of an ACI the user is upgrading from, we only consider attributes where all the versions agree. For other attrs we'll use the default from the new managed perm. If the ACIs differ in something else than the list of attributes, raise IncompatibleACIModification. This means manual action is needed (either delete the old permission or change it to resemble the default again, then re-run ipa-ldap-updater). In case there are multiple historic default ACIs, and some of them are compatible with the current but other ones aren't, we deduce that the user is upgrading from one of the compatible ones. The incompatible ones are removed from consideration, both for compatibility and attribute lists. """ assert default_acistrings def _pop_targetattr(aci): """Return the attr list it as a set, clear it in the ACI object """ targetattr = aci.target.get('targetattr') if targetattr: attrs = targetattr['expression'] targetattr['expression'] = [] return set(t.lower() for t in attrs) else: return set() current_aci = ACI(current_acistring) current_attrs = _pop_targetattr(current_aci) logger.debug("Current ACI for '%s': %s", current_aci.name, current_acistring) attrs_in_all_defaults = None attrs_in_any_defaults = set() all_incompatible = True for default_acistring in default_acistrings: default_aci = ACI(default_acistring) default_attrs = _pop_targetattr(default_aci) logger.debug("Default ACI for '%s': %s", default_aci.name, default_acistring) if current_aci != default_aci: logger.debug('ACIs not compatible') continue all_incompatible = False if attrs_in_all_defaults is None: attrs_in_all_defaults = set(default_attrs) else: attrs_in_all_defaults &= attrs_in_all_defaults attrs_in_any_defaults |= default_attrs if all_incompatible: logger.debug('All old default ACIs are incompatible') raise(IncompatibleACIModification()) included = current_attrs - attrs_in_any_defaults excluded = attrs_in_all_defaults - current_attrs return included, excluded
def update_permission(self, ldap, obj, name, template, anonymous_read_aci): """Update the given permission and the corresponding ACI""" assert name.startswith('System:') dn = self.api.Object[permission].get_dn(name) permission_plugin = self.api.Object[permission] try: attrs_list = list(permission_plugin.default_attributes) attrs_list.remove('memberindirect') entry = ldap.get_entry(dn, attrs_list) is_new = False except errors.NotFound: entry = ldap.make_entry(dn) is_new = True self.update_entry(obj, entry, template, anonymous_read_aci, is_new=is_new) remove_legacy = False if 'replaces' in template: sub_dict = { 'SUFFIX': str(self.api.env.basedn), 'REALM': str(self.api.env.realm), } legacy_acistrs = [ipautil.template_str(r, sub_dict) for r in template['replaces']] legacy_aci = ACI(legacy_acistrs[0]) prefix, sep, legacy_name = legacy_aci.name.partition(':') assert prefix == 'permission' and sep legacy_dn = permission_plugin.get_dn(legacy_name) try: legacy_entry = ldap.get_entry(legacy_dn, ['ipapermissiontype', 'cn']) except errors.NotFound: logger.debug("Legacy permission %s not found", legacy_name) else: if 'ipapermissiontype' not in legacy_entry: if is_new: _acientry, acistr = ( permission_plugin._get_aci_entry_and_string( legacy_entry, notfound_ok=True)) try: included, excluded = self.get_upgrade_attr_lists( acistr, legacy_acistrs) except IncompatibleACIModification: logger.error( "Permission '%s' has been modified from its " "default; not updating it to '%s'.", legacy_name, name) return else: logger.debug("Merging attributes from legacy " "permission '%s'", legacy_name) logger.debug("Included attrs: %s", ', '.join(sorted(included))) logger.debug("Excluded attrs: %s", ', '.join(sorted(excluded))) entry['ipapermincludedattr'] = list(included) entry['ipapermexcludedattr'] = list(excluded) remove_legacy = True else: logger.debug("Ignoring attributes in legacy " "permission '%s' because '%s' exists", legacy_name, name) remove_legacy = True else: logger.debug("Ignoring V2 permission named '%s'", legacy_name) update_aci = True logger.debug('Updating managed permission: %s', name) if is_new: ldap.add_entry(entry) else: try: ldap.update_entry(entry) except errors.EmptyModlist: logger.debug('No changes to permission: %s', name) update_aci = False if update_aci: logger.debug('Updating ACI for managed permission: %s', name) permission_plugin.update_aci(entry) if remove_legacy: logger.debug("Removing legacy permission '%s'", legacy_name) self.api.Command[permission_del](unicode(legacy_name)) for name in template.get('replaces_system', ()): name = unicode(name) try: entry = ldap.get_entry(permission_plugin.get_dn(name), ['ipapermissiontype']) except errors.NotFound: logger.debug("Legacy permission '%s' not found", name) else: flags = entry.get('ipapermissiontype', []) if list(flags) == ['SYSTEM']: logger.debug("Removing legacy permission '%s'", name) self.api.Command[permission_del](name, force=True) else: logger.debug("Ignoring V2 permission '%s'", name)
def check_aci_parsing(source, expected): a = ACI(source) print('ACI was: ', a) print('Expected:', expected) assert str(ACI(source)) == expected
def _make_aci(ldap, current, aciname, kw): """ Given a name and a set of keywords construct an ACI. """ # Do some quick and dirty validation. checked_args = [ 'type', 'filter', 'subtree', 'targetgroup', 'attrs', 'memberof' ] valid = {} for arg in checked_args: if arg in kw: valid[arg] = kw[arg] is not None else: valid[arg] = False if valid['type'] + valid['filter'] + valid['subtree'] + valid[ 'targetgroup'] > 1: raise errors.ValidationError( name='target', error=_( 'type, filter, subtree and targetgroup are mutually exclusive') ) if 'aciprefix' not in kw: raise errors.ValidationError(name='aciprefix', error=_('ACI prefix is required')) if sum(valid.values()) == 0: raise errors.ValidationError( name='target', error= _('at least one of: type, filter, subtree, targetgroup, attrs or memberof are required' )) if valid['filter'] + valid['memberof'] > 1: raise errors.ValidationError( name='target', error=_('filter and memberof are mutually exclusive')) group = 'group' in kw permission = 'permission' in kw selfaci = 'selfaci' in kw and kw['selfaci'] == True if group + permission + selfaci > 1: raise errors.ValidationError( name='target', error=_('group, permission and self are mutually exclusive')) elif group + permission + selfaci == 0: raise errors.ValidationError( name='target', error=_('One of group, permission or self is required')) # Grab the dn of the group we're granting access to. This group may be a # permission or a user group. entry_attrs = [] if permission: # This will raise NotFound if the permission doesn't exist try: entry_attrs = api.Command['permission_show']( kw['permission'])['result'] except errors.NotFound as e: if 'test' in kw and not kw.get('test'): raise e else: entry_attrs = { 'dn': DN(('cn', kw['permission']), api.env.container_permission, api.env.basedn), } elif group: # Not so friendly with groups. This will raise try: group_dn = api.Object['group'].get_dn_if_exists(kw['group']) entry_attrs = {'dn': group_dn} except errors.NotFound: raise errors.NotFound(reason=_("Group '%s' does not exist") % kw['group']) try: a = ACI(current) a.name = _make_aci_name(kw['aciprefix'], aciname) a.permissions = kw['permissions'] if 'selfaci' in kw and kw['selfaci']: a.set_bindrule('userdn = "ldap:///self"') else: dn = entry_attrs['dn'] a.set_bindrule('groupdn = "ldap:///%s"' % dn) if valid['attrs']: a.set_target_attr(kw['attrs']) if valid['memberof']: try: api.Object['group'].get_dn_if_exists(kw['memberof']) except errors.NotFound: raise api.Object['group'].handle_not_found(kw['memberof']) groupdn = _group_from_memberof(kw['memberof']) a.set_target_filter('memberOf=%s' % groupdn) if valid['filter']: # Test the filter by performing a simple search on it. The # filter is considered valid if either it returns some entries # or it returns no entries, otherwise we let whatever exception # happened be raised. if kw['filter'] in ('', None, u''): raise errors.BadSearchFilter(info=_('empty filter')) try: ldap.find_entries(filter=kw['filter']) except errors.NotFound: pass a.set_target_filter(kw['filter']) if valid['type']: target = _type_map[kw['type']] a.set_target(target) if valid['targetgroup']: # Purposely no try here so we'll raise a NotFound group_dn = api.Object['group'].get_dn_if_exists(kw['targetgroup']) target = 'ldap:///%s' % group_dn a.set_target(target) if valid['subtree']: # See if the subtree is a full URI target = kw['subtree'] if not target.startswith('ldap:///'): target = 'ldap:///%s' % target a.set_target(target) except SyntaxError as e: raise errors.ValidationError(name='target', error=_('Syntax Error: %(error)s') % dict(error=str(e))) return a
entry_attrs = { 'dn': DN(('cn', kw['permission']), api.env.container_permission, api.env.basedn), } elif group: # Not so friendly with groups. This will raise try: group_dn = api.Object['group'].get_dn_if_exists(kw['group']) entry_attrs = {'dn': group_dn} except errors.NotFound: raise errors.NotFound(reason=_("Group '%s' does not exist") % kw['group']) try: a = ACI(current) a.name = _make_aci_name(kw['aciprefix'], aciname) a.permissions = kw['permissions'] if 'selfaci' in kw and kw['selfaci']: a.set_bindrule('userdn = "ldap:///self"') else: dn = entry_attrs['dn'] a.set_bindrule('groupdn = "ldap:///%s"' % dn) if valid['attrs']: a.set_target_attr(kw['attrs']) if valid['memberof']: try: api.Object['group'].get_dn_if_exists(kw['memberof']) except errors.NotFound: api.Object['group'].handle_not_found(kw['memberof']) groupdn = _group_from_memberof(kw['memberof'])