Exemple #1
0
    def has_management_permission(self, permission=None, explicit=False):
        """Checks whether a principal has a certain management permission.

        The check always succeeds if the user is a full manager; in
        that case the list of permissions is ignored.

        :param permission: The permission to check for or 'ANY' to check for any
                     management permission.
        :param explicit: Whether to check for the permission itself even if
                         the user has full management privileges.
        """
        if permission is None:
            if explicit:
                raise ValueError(
                    'permission must be specified if explicit=True')
            return self.full_access
        elif not explicit and self.full_access:
            return True
        valid_permissions = get_available_permissions(
            self.principal_for_obj).viewkeys()
        current_permissions = set(self.permissions) & valid_permissions
        if permission == 'ANY':
            return bool(current_permissions)
        assert permission in valid_permissions, "invalid permission '{}' for object '{}'".format(
            permission, self.principal_for_obj)
        return permission in current_permissions
Exemple #2
0
def update_principals_permissions(obj, current, new):
    """Handle the updates of permissions and creations/deletions of acl principals.
    :param obj: The object to update. Must have ``acl_entries``
    :param current: A dict mapping principals to a set with its current permissions
    :param new: A dict mapping principals to a set with its new permissions
    """
    user_selectable_permissions = {v.name for k, v in get_available_permissions(obj.__class__).viewitems()
                                   if v.user_selectable}
    for principal, permissions in current.viewitems():
        if principal not in new:
            permissions_kwargs = {
                'full_access': False,
                'read_access': False,
                'del_permissions': user_selectable_permissions
            }
            obj.update_principal(principal, **permissions_kwargs)
        elif permissions != new[principal]:
            full_access, read_access, permissions = get_split_permissions(new[principal])
            all_user_permissions = [set(entry.permissions) for entry in obj.acl_entries
                                    if entry.principal == principal][0]
            permissions_kwargs = {
                'full_access': full_access,
                'read_access': read_access,
                'permissions': (all_user_permissions - user_selectable_permissions) | permissions
            }
            obj.update_principal(principal, **permissions_kwargs)
    new_principals = set(new) - set(current)
    for p in new_principals:
        full_access, read_access, permissions = get_split_permissions(new[p])
        permissions_kwargs = {
            'full_access': full_access,
            'read_access': read_access,
            'add_permissions': permissions & user_selectable_permissions
        }
        obj.update_principal(p, **permissions_kwargs)
Exemple #3
0
 def _process(self):
     form = EventProtectionForm(obj=FormDefaults(**self._get_defaults()),
                                event=self.event)
     selectable_permissions = {
         k
         for k, v in get_available_permissions(Event).items()
         if v.user_selectable
     }
     user_permissions = [(p.principal, set(p.permissions))
                         for p in self.event.acl_entries]
     hidden_permissions = sorted(
         [(principal, sorted(perms))
          for principal, perms in user_permissions
          if perms and not (perms & selectable_permissions)],
         key=lambda x: (x[0].principal_order, x[0].name.lower()))
     form.permissions.hidden_permissions = [
         (p.name, perms) for p, perms in hidden_permissions
     ]
     if form.validate_on_submit():
         update_permissions(self.event, form)
         update_event_protection(
             self.event, {
                 'protection_mode': form.protection_mode.data,
                 'own_no_access_contact': form.own_no_access_contact.data,
                 'access_key': form.access_key.data,
                 'visibility': form.visibility.data,
                 'public_regform_access': form.public_regform_access.data
             })
         self._update_session_coordinator_privs(form)
         flash(_('Protection settings have been updated'), 'success')
         return redirect(url_for('.protection', self.event))
     return WPEventProtection.render_template('event_protection.html',
                                              self.event,
                                              'protection',
                                              form=form)
Exemple #4
0
 def hidden_permissions_info(self):
     all_permissions = get_available_permissions(
         PermissionsField.type_mapping[self.object_type])
     visible_permissions = get_permissions_info(
         PermissionsField.type_mapping[self.object_type])[0]
     return {
         k: all_permissions[k].friendly_name
         for k in set(all_permissions) - set(visible_permissions)
     }
Exemple #5
0
def _mock_available_permissions(mocker):
    permissions = dict(get_available_permissions(Event),
                       foo=MagicMock(),
                       bar=MagicMock(),
                       foobar=MagicMock())
    mocker.patch(
        'indico.core.db.sqlalchemy.protection.get_available_permissions',
        return_value=permissions)
    mocker.patch(
        'indico.core.db.sqlalchemy.principals.get_available_permissions',
        return_value=permissions)
Exemple #6
0
def _mock_available_permissions(mocker):
    # The code we are testing only cares about the keys so we don't
    # need to actually create permissions for now.
    permissions = dict(get_available_permissions(Event),
                       foo=None,
                       bar=None,
                       foobar=None)
    mocker.patch(
        'indico.core.db.sqlalchemy.protection.get_available_permissions',
        return_value=permissions)
    mocker.patch(
        'indico.core.db.sqlalchemy.principals.get_available_permissions',
        return_value=permissions)
Exemple #7
0
def _log_acl_changes(sender, obj, principal, entry, is_new, old_data, quiet, **kwargs):
    if quiet:
        return

    user = session.user if session else None  # allow acl changes outside request context
    available_permissions = get_available_permissions(Event)

    def _format_permissions(permissions):
        permissions = set(permissions)
        return ', '.join(sorted(orig_string(p.friendly_name) for p in available_permissions.values()
                                if p.name in permissions))

    data = {}
    if principal.principal_type == PrincipalType.user:
        data['User'] = principal.full_name
    elif principal.principal_type == PrincipalType.email:
        data['Email'] = principal.email
    elif principal.principal_type == PrincipalType.local_group:
        data['Group'] = principal.name
    elif principal.principal_type == PrincipalType.multipass_group:
        data['Group'] = f'{principal.name} ({principal.provider_title})'
    elif principal.principal_type == PrincipalType.network:
        data['IP Network'] = principal.name
    elif principal.principal_type == PrincipalType.registration_form:
        data['Registration Form'] = principal.title
    elif principal.principal_type == PrincipalType.event_role:
        data['Event Role'] = principal.name
    if entry is None:
        data['Read Access'] = old_data['read_access']
        data['Manager'] = old_data['full_access']
        data['Permissions'] = _format_permissions(old_data['permissions'])
        obj.log(EventLogRealm.management, EventLogKind.negative, 'Protection', 'ACL entry removed', user, data=data)
    elif is_new:
        data['Read Access'] = entry.read_access
        data['Manager'] = entry.full_access
        if entry.permissions:
            data['Permissions'] = _format_permissions(entry.permissions)
        obj.log(EventLogRealm.management, EventLogKind.positive, 'Protection', 'ACL entry added', user, data=data)
    elif entry.current_data != old_data:
        data['Read Access'] = entry.read_access
        data['Manager'] = entry.full_access
        current_permissions = set(entry.permissions)
        added_permissions = current_permissions - old_data['permissions']
        removed_permissions = old_data['permissions'] - current_permissions
        if added_permissions:
            data['Permissions (added)'] = _format_permissions(added_permissions)
        if removed_permissions:
            data['Permissions (removed)'] = _format_permissions(removed_permissions)
        if current_permissions:
            data['Permissions'] = _format_permissions(current_permissions)
        obj.log(EventLogRealm.management, EventLogKind.change, 'Protection', 'ACL entry changed', user, data=data)
Exemple #8
0
def _log_acl_changes(sender, obj, principal, entry, is_new, old_data, quiet, **kwargs):
    if quiet:
        return

    user = session.user if session else None  # allow acl changes outside request context
    available_permissions = get_available_permissions(Event)

    def _format_permissions(permissions):
        permissions = set(permissions)
        return ', '.join(sorted(orig_string(p.friendly_name) for p in available_permissions.itervalues()
                                if p.name in permissions))

    data = {}
    if principal.principal_type == PrincipalType.user:
        data['User'] = principal.full_name
    elif principal.principal_type == PrincipalType.email:
        data['Email'] = principal.email
    elif principal.principal_type == PrincipalType.local_group:
        data['Group'] = principal.name
    elif principal.principal_type == PrincipalType.multipass_group:
        data['Group'] = '{} ({})'.format(principal.name, principal.provider_title)
    elif principal.principal_type == PrincipalType.network:
        data['IP Network'] = principal.name
    elif principal.principal_type == PrincipalType.event_role:
        data['Event Role'] = principal.name
    if entry is None:
        data['Read Access'] = old_data['read_access']
        data['Manager'] = old_data['full_access']
        data['Permissions'] = _format_permissions(old_data['permissions'])
        obj.log(EventLogRealm.management, EventLogKind.negative, 'Protection', 'ACL entry removed', user, data=data)
    elif is_new:
        data['Read Access'] = entry.read_access
        data['Manager'] = entry.full_access
        if entry.permissions:
            data['Permissions'] = _format_permissions(entry.permissions)
        obj.log(EventLogRealm.management, EventLogKind.positive, 'Protection', 'ACL entry added', user, data=data)
    elif entry.current_data != old_data:
        data['Read Access'] = entry.read_access
        data['Manager'] = entry.full_access
        current_permissions = set(entry.permissions)
        added_permissions = current_permissions - old_data['permissions']
        removed_permissions = old_data['permissions'] - current_permissions
        if added_permissions:
            data['Permissions (added)'] = _format_permissions(added_permissions)
        if removed_permissions:
            data['Permissions (removed)'] = _format_permissions(removed_permissions)
        if current_permissions:
            data['Permissions'] = _format_permissions(current_permissions)
        obj.log(EventLogRealm.management, EventLogKind.change, 'Protection', 'ACL entry changed', user, data=data)
Exemple #9
0
 def has_management_permission(cls, permission=None, explicit=False):
     if permission is None:
         if explicit:
             raise ValueError('permission must be specified if explicit=True')
         return cls.full_access
     valid_permissions = get_available_permissions(cls.principal_for_obj).keys()
     if permission == 'ANY':
         crit = (cls.permissions.op('&&')(db.func.cast(valid_permissions, ARRAY(db.String))))
     else:
         assert permission in valid_permissions, \
             f"invalid permission '{permission}' for object '{cls.principal_for_obj}'"
         crit = (cls.permissions.op('&&')(db.func.cast([permission], ARRAY(db.String))))
     if explicit:
         return crit
     else:
         return cls.full_access | crit
Exemple #10
0
 def has_management_permission(cls, permission=None, explicit=False):
     if permission is None:
         if explicit:
             raise ValueError('permission must be specified if explicit=True')
         return cls.full_access
     valid_permissions = get_available_permissions(cls.principal_for_obj).viewkeys()
     if permission == 'ANY':
         crit = (cls.permissions.op('&&')(db.func.cast(valid_permissions, ARRAY(db.String))))
     else:
         assert permission in valid_permissions, \
             "invalid permission '{}' for object '{}'".format(permission, cls.principal_for_obj)
         crit = (cls.permissions.op('&&')(db.func.cast([permission], ARRAY(db.String))))
     if explicit:
         return crit
     else:
         return cls.full_access | crit
Exemple #11
0
def update_principals_permissions(obj, current, new):
    """Handle the updates of permissions and creations/deletions of acl principals.
    :param obj: The object to update. Must have ``acl_entries``
    :param current: A dict mapping principals to a set with its current permissions
    :param new: A dict mapping principals to a set with its new permissions
    """
    user_selectable_permissions = {
        v.name
        for k, v in get_available_permissions(obj.__class__).viewitems()
        if v.user_selectable
    }
    for principal, permissions in current.viewitems():
        if principal not in new:
            permissions_kwargs = {
                'full_access': False,
                'read_access': False,
                'del_permissions': user_selectable_permissions
            }
            obj.update_principal(principal, **permissions_kwargs)
        elif permissions != new[principal]:
            full_access, read_access, permissions = get_split_permissions(
                new[principal])
            all_user_permissions = [
                set(entry.permissions) for entry in obj.acl_entries
                if entry.principal == principal
            ][0]
            permissions_kwargs = {
                'full_access':
                full_access,
                'read_access':
                read_access,
                'permissions':
                (all_user_permissions - user_selectable_permissions)
                | permissions
            }
            obj.update_principal(principal, **permissions_kwargs)
    new_principals = set(new) - set(current)
    for p in new_principals:
        full_access, read_access, permissions = get_split_permissions(new[p])
        permissions_kwargs = {
            'full_access': full_access,
            'read_access': read_access,
            'add_permissions': permissions & user_selectable_permissions
        }
        obj.update_principal(p, **permissions_kwargs)
Exemple #12
0
    def has_management_permission(self, permission=None, explicit=False):
        """Checks whether a principal has a certain management permission.

        The check always succeeds if the user is a full manager; in
        that case the list of permissions is ignored.

        :param permission: The permission to check for or 'ANY' to check for any
                     management permission.
        :param explicit: Whether to check for the permission itself even if
                         the user has full management privileges.
        """
        if permission is None:
            if explicit:
                raise ValueError('permission must be specified if explicit=True')
            return self.full_access
        elif not explicit and self.full_access:
            return True
        valid_permissions = get_available_permissions(self.principal_for_obj).viewkeys()
        current_permissions = set(self.permissions) & valid_permissions
        if permission == 'ANY':
            return bool(current_permissions)
        assert permission in valid_permissions, "invalid permission '{}' for object '{}'".format(permission,
                                                                                                 self.principal_for_obj)
        return permission in current_permissions
Exemple #13
0
    def update_principal(self, principal, read_access=None, full_access=None, permissions=None, add_permissions=None,
                         del_permissions=None, quiet=False):
        """Updates access privileges for the given principal.

        If the principal is not in the ACL, it will be added if
        necessary.  If the changes remove all its privileges, it
        will be removed from the ACL.

        :param principal: A `User`, `GroupProxy` or `EmailPrincipal` instance.
        :param read_access: If the principal should have explicit read
                            access to the object.  This does not grant
                            any management permissions - it simply
                            grants access to an otherwise protected
                            object.
        :param full_access: If the principal should have full management
                            access.
        :param permissions: set -- The management permissions to grant.
                            Any existing permissions will be replaced.
        :param add_permissions: set -- Management permissions to add.
        :param del_permissions: set -- Management permissions to remove.
        :param quiet: Whether the ACL change should happen silently.
                      This indicates to acl change signal handlers
                      that the change should not be logged, trigger
                      emails or result in similar notifications.
        :return: The ACL entry for the given principal or ``None`` if
                 he was removed (or not added).
        """
        if permissions is not None and (add_permissions or del_permissions):
            raise ValueError('add_permissions/del_permissions and permissions are mutually exclusive')
        principal = _resolve_principal(principal)
        principal_class, entry = _get_acl_data(self, principal)
        new_entry = False
        if entry is None:
            if not permissions and not add_permissions and not full_access and not read_access:
                # not in ACL and no permissions to add
                return None
            entry = principal_class(principal=principal, read_access=False, full_access=False, permissions=[])
            self.acl_entries.add(entry)
            new_entry = True
        old_data = entry.current_data
        # update permissions
        new_permissions = set(entry.permissions)
        if permissions is not None:
            new_permissions = permissions
        else:
            if add_permissions:
                new_permissions |= add_permissions
            if del_permissions:
                new_permissions -= del_permissions
        invalid_permissions = new_permissions - get_available_permissions(type(self)).viewkeys()
        if invalid_permissions:
            raise ValueError('Invalid permissions: {}'.format(', '.join(invalid_permissions)))
        entry.permissions = sorted(new_permissions)
        # update read privs
        if read_access is not None:
            entry.read_access = read_access
        # update full management privs
        if full_access is not None:
            entry.full_access = full_access
        # remove entry from acl if no privileges
        if not entry.read_access and not entry.full_access and not entry.permissions:
            self.acl_entries.remove(entry)
            # Flush in case the same principal is added back afterwards.
            # Not flushing in other cases (adding/modifying) is intentional
            # as this might happen on a newly created object which is not yet
            # flushable due to missing data
            db.session.flush()
            signals.acl.entry_changed.send(type(self), obj=self, principal=principal, entry=None, is_new=False,
                                           old_data=old_data, quiet=quiet)
            return None
        signals.acl.entry_changed.send(type(self), obj=self, principal=principal, entry=entry, is_new=new_entry,
                                       old_data=old_data, quiet=quiet)
        return entry
Exemple #14
0
def _mock_available_permissions(mocker):
    # The code we are testing only cares about the keys so we don't
    # need to actually create permissions for now.
    permissions = dict(get_available_permissions(Event), foo=None, bar=None, foobar=None)
    mocker.patch('indico.core.db.sqlalchemy.protection.get_available_permissions', return_value=permissions)
    mocker.patch('indico.core.db.sqlalchemy.principals.get_available_permissions', return_value=permissions)
Exemple #15
0
    def update_principal(self,
                         principal,
                         read_access=None,
                         full_access=None,
                         permissions=None,
                         add_permissions=None,
                         del_permissions=None,
                         quiet=False):
        """Update access privileges for the given principal.

        If the principal is not in the ACL, it will be added if
        necessary.  If the changes remove all its privileges, it
        will be removed from the ACL.

        :param principal: A `User`, `GroupProxy` or `EmailPrincipal` instance.
        :param read_access: If the principal should have explicit read
                            access to the object.  This does not grant
                            any management permissions - it simply
                            grants access to an otherwise protected
                            object.
        :param full_access: If the principal should have full management
                            access.
        :param permissions: set -- The management permissions to grant.
                            Any existing permissions will be replaced.
        :param add_permissions: set -- Management permissions to add.
        :param del_permissions: set -- Management permissions to remove.
        :param quiet: Whether the ACL change should happen silently.
                      This indicates to acl change signal handlers
                      that the change should not be logged, trigger
                      emails or result in similar notifications.
        :return: The ACL entry for the given principal or ``None`` if
                 there is no corresponding entry in the end.
        """
        if permissions is not None and (add_permissions or del_permissions):
            raise ValueError(
                'add_permissions/del_permissions and permissions are mutually exclusive'
            )
        principal = _resolve_principal(principal)
        principal_class, entry = _get_acl_data(self, principal)
        new_entry = False
        if entry is None:
            if not permissions and not add_permissions and not full_access and not read_access:
                # not in ACL and no permissions to add
                return None
            entry = principal_class(principal=principal,
                                    read_access=False,
                                    full_access=False,
                                    permissions=[])
            self.acl_entries.add(entry)
            new_entry = True
        old_data = entry.current_data
        # update permissions
        new_permissions = set(entry.permissions)
        if permissions is not None:
            new_permissions = permissions
        else:
            if add_permissions:
                new_permissions |= add_permissions
            if del_permissions:
                new_permissions -= del_permissions
        invalid_permissions = new_permissions - get_available_permissions(
            type(self)).keys()
        if invalid_permissions:
            raise ValueError('Invalid permissions: {}'.format(
                ', '.join(invalid_permissions)))
        entry.permissions = sorted(new_permissions)
        # update read privs
        if read_access is not None:
            entry.read_access = read_access
        # update full management privs
        if full_access is not None:
            entry.full_access = full_access
        # remove entry from acl if no privileges
        if not entry.read_access and not entry.full_access and not entry.permissions:
            self.acl_entries.remove(entry)
            # Flush in case the same principal is added back afterwards.
            # Not flushing in other cases (adding/modifying) is intentional
            # as this might happen on a newly created object which is not yet
            # flushable due to missing data
            db.session.flush()
            signals.acl.entry_changed.send(type(self),
                                           obj=self,
                                           principal=principal,
                                           entry=None,
                                           is_new=False,
                                           old_data=old_data,
                                           quiet=quiet)
            return None
        signals.acl.entry_changed.send(type(self),
                                       obj=self,
                                       principal=principal,
                                       entry=entry,
                                       is_new=new_entry,
                                       old_data=old_data,
                                       quiet=quiet)
        return entry
Exemple #16
0
    def can_manage(self,
                   user,
                   permission=None,
                   allow_admin=True,
                   check_parent=True,
                   explicit_permission=False):
        """Check if the user can manage the object.

        :param user: The :class:`.User` to check. May be None if the
                     user is not logged in.
        :param: permission: The management permission that is needed for
                            the check to succeed.  If not specified, full
                            management privs are required.  May be set to
                            the string ``'ANY'`` to check if the user has
                            any management privileges.  If the user has
                            `full_access` privileges, he's assumed to have
                            all possible permissions.
        :param allow_admin: If admin users should always have access
        :param check_parent: If the parent object should be checked.
                             In this case the permission is ignored; only
                             full management access is inherited to
                             children.
        :param explicit_permission: If the specified permission should be checked
                                    explicitly instead of short-circuiting
                                    the check for Indico admins or managers.
                                    When this option is set to ``True``, the
                                    values of `allow_admin` and `check_parent`
                                    are ignored.  This also applies if `permission`
                                    is None in which case this argument being
                                    set to ``True`` is equivalent to
                                    `allow_admin` and `check_parent` being set
                                    to ``False``.
        """
        if permission is not None and permission != 'ANY' and permission not in get_available_permissions(
                type(self)):
            raise ValueError(
                "permission '{}' is not valid for '{}' objects".format(
                    permission,
                    type(self).__name__))

        if user is None:
            # An unauthorized user is never allowed to perform management operations.
            # Not even signals may override this since management code generally
            # expects session.user to be not None.
            return False
        if user.is_system:
            # A system user has no email and thus access checks (against groups) may fail
            return False

        # Trigger signals for protection overrides
        rv = values_from_signal(signals.acl.can_manage.send(
            type(self),
            obj=self,
            user=user,
            permission=permission,
            allow_admin=allow_admin,
            check_parent=check_parent,
            explicit_permission=explicit_permission),
                                single_value=True)
        if rv:
            # in case of contradictory results (shouldn't happen at all)
            # we stay on the safe side and deny access
            return all(rv)

        # Usually admins can access everything, so no need for checks
        if not explicit_permission and allow_admin and type(
                self).is_user_admin(user):
            return True

        if any(user in entry.principal for entry in iter_acl(self.acl_entries)
               if entry.has_management_permission(
                   permission,
                   explicit=(explicit_permission and permission is not None))):
            return True

        if not check_parent or explicit_permission:
            return False

        # the parent can be either an object inheriting from this
        # mixin or a legacy object with an AccessController
        parent = self.protection_parent
        if parent is None:
            # This should be the case for the top-level object,
            # i.e. the root category
            return False
        elif hasattr(parent, 'can_manage'):
            return parent.can_manage(user, allow_admin=allow_admin)
        else:
            raise TypeError(
                'protection_parent of {} is of invalid type {} ({})'.format(
                    self, type(parent), parent))
Exemple #17
0
    def can_manage(self, user, permission=None, allow_admin=True, check_parent=True, explicit_permission=False):
        """Checks if the user can manage the object.

        :param user: The :class:`.User` to check. May be None if the
                     user is not logged in.
        :param: permission: The management permission that is needed for
                            the check to succeed.  If not specified, full
                            management privs are required.  May be set to
                            the string ``'ANY'`` to check if the user has
                            any management privileges.  If the user has
                            `full_access` privileges, he's assumed to have
                            all possible permissions.
        :param allow_admin: If admin users should always have access
        :param check_parent: If the parent object should be checked.
                             In this case the permission is ignored; only
                             full management access is inherited to
                             children.
        :param explicit_permission: If the specified permission should be checked
                                    explicitly instead of short-circuiting
                                    the check for Indico admins or managers.
                                    When this option is set to ``True``, the
                                    values of `allow_admin` and `check_parent`
                                    are ignored.  This also applies if `permission`
                                    is None in which case this argument being
                                    set to ``True`` is equivalent to
                                    `allow_admin` and `check_parent` being set
                                    to ``False``.
        """
        if permission is not None and permission != 'ANY' and permission not in get_available_permissions(type(self)):
            raise ValueError("permission '{}' is not valid for '{}' objects".format(permission, type(self).__name__))

        if user is None:
            # An unauthorized user is never allowed to perform management operations.
            # Not even signals may override this since management code generally
            # expects session.user to be not None.
            return False

        # Trigger signals for protection overrides
        rv = values_from_signal(signals.acl.can_manage.send(type(self), obj=self, user=user, permission=permission,
                                                            allow_admin=allow_admin, check_parent=check_parent,
                                                            explicit_permission=explicit_permission),
                                single_value=True)
        if rv:
            # in case of contradictory results (shouldn't happen at all)
            # we stay on the safe side and deny access
            return all(rv)

        # Usually admins can access everything, so no need for checks
        if not explicit_permission and allow_admin and user.is_admin:
            return True

        if any(user in entry.principal
               for entry in iter_acl(self.acl_entries)
               if entry.has_management_permission(permission,
                                                  explicit=(explicit_permission and permission is not None))):
            return True

        if not check_parent or explicit_permission:
            return False

        # the parent can be either an object inheriting from this
        # mixin or a legacy object with an AccessController
        parent = self.protection_parent
        if parent is None:
            # This should be the case for the top-level object,
            # i.e. the root category
            return False
        elif hasattr(parent, 'can_manage'):
            return parent.can_manage(user, allow_admin=allow_admin)
        else:
            raise TypeError('protection_parent of {} is of invalid type {} ({})'.format(self, type(parent), parent))