Example #1
0
class ContainsText(FilterCondition):
    unique_name = "field_contains_text"
    descriptive_name = "Field contains text"
    field_to_match = field_utils.CharField(label="Field to match", required=True)
    inverse = field_utils.BooleanField(label="Flip to inverse")

    def __init__(self, field_to_match, text, inverse=False):
        self.field_to_match = field_to_match
        self.text = text
        self.inverse = inverse

    def validate(self, permission, target):

        field = self.get_matching_field(self.field_to_match, permission=permission, target=target)
        if not field:
            return False, f"No field {self.field_to_match} on target"

        convertible = field.can_convert_to("CharField")
        if not field:
            return False, f"Field {self.field_to_match} cannot convert to text"

        return True

    def check(self, action):
        field = self.get_matched_field([action, action.target])
        if self.text in field:
            return True
        return False
Example #2
0
class ChangeInverseStateChange(BaseStateChange):
    """State change to toggle the inverse field of a permission."""

    descriptive_text = {
        "verb": "toggle",
        "default_string": "inverse field on permission",
        "detail_string": "inverse field on permission to {change_to}",
        "preposition": "for"
    }

    section = "Permissions"
    allowable_targets = [PermissionsItem]
    settable_classes = ["all_models"]

    change_to = field_utils.BooleanField(
        label="Change inverse field of permission to", required=True)

    def validate(self, actor, target):
        if not isinstance(self.change_to, bool):
            raise ValidationError(
                f"'change_to' must be True or False, not {type(self.change_to)}"
            )

    def implement(self, actor, target, **kwargs):
        target.inverse = self.change_to
        target.save()
        return target
Example #3
0
class EditPermissionStateChange(BaseStateChange):

    descriptive_text = {
        "verb": "edit",
        "default_string": "permission",
        "preposition": "on"
    }

    section = "Permissions"
    allowable_targets = [PermissionsItem]
    settable_classes = ["all_models"]
    model_based_validation = (PermissionsItem, ["anyone", "roles", "actors"])

    actors = field_utils.ActorListField(
        label="Actors who have this permission", null_value=list)
    roles = field_utils.RoleListField(label="Roles who have this permission",
                                      null_value=list)
    anyone = field_utils.BooleanField(label="Everyone has the permission",
                                      null_value=False)

    def validate(self, actor, target):
        if not self.actors and not self.roles and not self.anyone:
            raise ValidationError(
                "Must change at least one field to edit permission.")

    def implement(self, actor, target, **kwargs):
        field_dict = {}
        if self.actors: field_dict["actors"] = self.actors
        if self.roles: field_dict["roles"] = self.roles
        if self.anyone: field_dict["anyone"] = self.anyone
        target.set_fields(**field_dict)
        target.save()
        return target
Example #4
0
class ActorIsSameAs(FilterCondition):
    """
    Note: this replaces:
        'self only' (actor is the same as member_pk_list)
        'original creator only' (actor is the same as target.commented_on.creator)
        'commenter only' (actor is the same as target.creator)
    """
    unique_name = "actor_is_same_as"
    descriptive_name = "Actor is the same as"
    field_to_match = field_utils.CharField(label="Field to match", required=True)
    inverse = field_utils.BooleanField(label="Flip to inverse")

    def __init__(self, field_to_match, inverse=False):
        self.field_to_match = field_to_match
        self.inverse = inverse

    def validate(self, permission, target):

        field = self.get_matching_field(self.field_to_match, permission=permission, target=target)
        if not field:
            return False, f"No field found for '{self.field_to_match}'"

        convertible = field.can_convert_to("ActorField")

        if not field:
            return False, f"Field {self.field_to_match} cannot convert to Actor"

        return True

    def check(self, action):
        field = self.get_matching_field(self.field_to_match, action=action)
        if action.actor == field.to_ActorField:
            return True
        return False
Example #5
0
class FieldIs(FilterCondition):
    unique_name = "field_is"
    descriptive_name = "Field X is value Y"
    field_to_match = field_utils.CharField(label="Field to match", required=True)
    value_to_match = field_utils.CharField(label="Value to match", required=True)
    inverse = field_utils.BooleanField(label="Flip to inverse")

    def __init__(self, field_to_match, value_to_match, inverse):
        self.field_to_match = field_to_match
        self.value_to_match = value_to_match
        self.inverse = inverse

    def validate(self, permission, target):

        field = getattr(permission.change, self.field_to_match)
        if not field:
            return False, f"No field found for '{self.field_to_match}'"

        if not field.transform_to_valid_value(self.value_to_match):
            return False, f"{self.value_to_match} is not a valid value for {self.field_to_match}"

        return True, None

    def check(self, action):
        field = getattr(action.change, self.field_to_match)
        return field.value == self.value_to_match
Example #6
0
class TargetType(FilterCondition):
    unique_name = "target_is_type"
    descriptive_name = "Target is of type"
    target_type = field_utils.PermissionedModelField(label="Limit targets to type", required=True)
    inverse = field_utils.BooleanField(label="Flip to inverse")

    def __init__(self, target_type, inverse=False):
        self.target_type = target_type
        self.inverse = inverse

    def check(self, action):
        if action.target.__class__.__name__ == self.target_type:
            return True
        return False
Example #7
0
class ActorMemberCondition(FilterCondition):
    unique_name = "actor_user_age"
    descriptive_name = "Actor has been member of community longer than"
    duration = field_utils.DurationField(label="Duration of membership required", required=True)
    inverse = field_utils.BooleanField(label="Flip to inverse (actor has been member of community less than...)")

    def __init__(self, duration, inverse=False):
        self.duration = duration
        self.inverse = inverse

    def check(self, action):
        date_joined = Client(target=action.target.get_owner().Community.user_joined(action.actor))
        if datetime.datetime.now() - date_joined > self.duration:
            return True
        return False
Example #8
0
class ActorUserCondition(FilterCondition):
    unique_name = "actor_user_age"
    descriptive_name = "Actor has been user longer than"
    duration = field_utils.DurationField(label="Length of time that must pass", required=True)
    inverse = field_utils.BooleanField(label="Flip to inverse (actor has been user less than...)")

    def __init__(self, duration=None, inverse=False):
        self.duration = duration
        self.inverse = inverse

    def description_for_passing_condition(self):
        units = utils.parse_duration_into_units(self.duration, measured_in="seconds")
        time_length = utils.display_duration_units(**units)
        return f"actor has been user longer than {time_length}"

    def check(self, action):
        if (datetime.now(timezone.utc) - action.actor.date_joined).seconds >= self.duration:
            return True
        return False
Example #9
0
class FieldContainsFilter(Filter):
    descriptive_name = "a field contains specific text"
    configured_name = "{field_to_match} {verb} '{value_to_match}'"

    field_to_match = field_utils.CharField(label="Field to look in", required=True)
    value_to_match = field_utils.CharField(label="Text to search for", required=True)
    inverse = field_utils.BooleanField(label="Reverse (only allowed if it does NOT contain above text)", default=False)

    # TODO: for now we can only match change object fields, probably should be more flexible - use crawl objects?
    # TODO: should either restrict this to text fields or find a coherent way of translating non-text fields to text

    def does_not_contain(self):
        if isinstance(self.inverse, bool):
            return self.inverse
        return False

    def validate(self, permission):
        change_obj = permission.get_state_change_object()
        if not hasattr(change_obj, self.field_to_match):
            return False, f"No field '{self.field_to_match}' on this permission"
        return True, None

    def check(self, *, action, **kwargs):
        """The contents of the action field should equal the custom text."""
        field_value = getattr(action.change, self.field_to_match)
        if self.does_not_contain:
            failure_msg = f"field '{self.field_to_match}' contains '{self.value_to_match.lower()}'"
            return self.value_to_match.lower() not in field_value.lower(), failure_msg
        failure_msg = f"field '{self.field_to_match}' does not contain '{self.value_to_match.lower()}'"
        return self.value_to_match.lower() in field_value.lower(), failure_msg

    def get_configured_name(self):
        if hasattr(self, "configured_name"):
            text_dict = self.get_input_field_values()
            text_dict["verb"] = "does not contain" if self.does_not_contain() else "contains"
            return self.configured_name.format(**text_dict)
        return self.get_descriptive_name()
Example #10
0
class AddPermissionStateChange(BaseStateChange):
    """State change to add a permission to something."""

    descriptive_text = {  # note that description_present_tense and past tense are overridden below
        "verb": "add",
        "default_string": "permission"
    }

    section = "Permissions"
    model_based_validation = (PermissionsItem,
                              ["change_type", "anyone", "inverse"])

    change_type = field_utils.CharField(
        label="Type of action the permission covers", required=True)
    actors = field_utils.ActorListField(
        label="Actors who have this permission", null_value=list)
    roles = field_utils.RoleListField(label="Roles who have this permission",
                                      null_value=list)
    anyone = field_utils.BooleanField(label="Everyone has the permission",
                                      null_value=False)
    inverse = field_utils.BooleanField(
        label="Do the inverse of this permission", null_value=False)
    condition_data = field_utils.CharField(
        label="Condition for this permission")

    def description_present_tense(self):
        return f"add permission '{get_verb_given_permission_type(self.change_type)}'"

    def description_past_tense(self):
        return f"added permission '{get_verb_given_permission_type(self.change_type)}'"

    def is_conditionally_foundational(self, action):
        """Some state changes are only foundational in certain conditions. Those state changes override this
        method to apply logic and determine whether a specific instance is foundational or not."""
        from concord.utils.lookups import get_state_change_object
        change_object = get_state_change_object(self.change_type)
        return action.change.is_foundational

    def validate(self, actor, target):

        permission = get_state_change_object(self.change_type)

        # check that target is a valid class for the permission to be set on
        if target.__class__ not in permission.get_settable_classes():
            settable_classes_str = ", ".join(
                [str(option) for option in permission.get_settable_classes()])
            raise ValidationError(
                f"This kind of permission cannot be set on target {target} of class "
                + f"{target.__class__}, must be {settable_classes_str}")

        # validate condition data
        if self.condition_data:
            for condition in self.condition_data:
                is_valid, message = validate_condition(
                    condition["condition_type"], condition["condition_data"],
                    condition["permission_data"], target)
                if not is_valid:
                    raise ValidationError(message)

    def implement(self, actor, target, **kwargs):

        permission = PermissionsItem()
        permission.set_fields(owner=target.get_owner(),
                              permitted_object=target,
                              anyone=self.anyone,
                              change_type=self.change_type,
                              inverse=self.inverse,
                              actors=self.actors,
                              roles=self.roles)
        permission.save()

        # if condition, add and save
        if self.condition_data:

            # create initial manager
            owner = permission.get_owner()
            manager = ConditionManager.objects.create(owner=owner,
                                                      community=owner.pk,
                                                      set_on="permission")
            permission.condition = manager

            # add conditions
            for condition in self.condition_data:
                data = {
                    "condition_type": condition["condition_type"],
                    "condition_data": condition["condition_data"],
                    "permission_data": condition["permission_data"]
                }
                manager.add_condition(data_for_condition=data)
                manager.save()

        return permission
Example #11
0
class ApplyTemplateStateChange(BaseStateChange):
    """State change object for applying a template."""

    descriptive_text = {
        "verb": "apply",
        "past_tense": "applied",
        "default_string": "template",
        "preposition": "for"
    }

    pass_action = True
    linked_filters = ["CreatorFilter"]

    # Fields
    template_model_pk = field_utils.IntegerField(
        label="PK of Template to apply", required=True)
    supplied_fields = field_utils.DictField(
        label="Fields to supply when applying template", null_value=dict)
    template_is_foundational = field_utils.BooleanField(
        label="Template makes foundational changes")

    def validate(self, actor, target):

        # check template_model_pk is valid
        template = TemplateModel.objects.filter(pk=self.template_model_pk)
        if not template:
            raise ValidationError(
                f"No template in database with ID {self.template_model_pk}")

        # check that supplied fields match template's siupplied fields
        needed_field_keys = set(
            [key for key, value in template[0].get_supplied_fields().items()])
        supplied_field_keys = set(
            [key for key, value in self.supplied_fields.items()])
        if needed_field_keys - supplied_field_keys:
            missing_fields = ', '.join(
                list(needed_field_keys - supplied_field_keys))
            raise ValidationError(
                f"Template needs values for fields {missing_fields}")

        # attempt to apply actions (but rollback commit regardless)
        mock_action = MockAction(actor=actor, target=target, change=self)
        result = template[0].template_data.apply_template(
            actor=actor,
            target=target,
            trigger_action=mock_action,
            supplied_fields=self.supplied_fields,
            rollback=True)
        if "errors" in result:
            raise ValidationError(
                f"Template errors: {'; '.join([error for error in result['errors']])}"
            )

    def implement(self, actor, target, **kwargs):
        """Implements the given template, relies on logic in apply_template."""
        action = kwargs.get("action", None)
        template_model = TemplateModel.objects.get(pk=self.template_model_pk)
        return template_model.template_data.apply_template(
            actor=actor,
            target=target,
            trigger_action=action,
            supplied_fields=self.supplied_fields)