Ejemplo n.º 1
0
    def patch_draft(self, user, patch, **options):
        """Operation specific to draft, before applying generic patch."""
        try:
            params = dict(patch)
        except Exception as exc:
            log.info(exc)
            raise err.PatchError(message=exc.message)
        # silently remove unexpected props within patch if not in strict mode
        strict_patch = Configuration('global').get('apiV1.strict_patch', False)
        if not strict_patch:
            allowed_properties = [
                "body",
                "current_state",
                "user_identities",
                "message_id",
                "parent_id",
                "participants",
                "subject",
                "privacy_features",
            ]
            for key, value in params.items():
                if key not in allowed_properties:
                    del (params[key])

            for key, value in params["current_state"].items():
                if key not in allowed_properties:
                    del (params["current_state"][key])

        try:
            self.get_db()
            self.unmarshall_db()
        except Exception as exc:
            log.info("patch_draft() failed to get msg from db: {}".format(exc))
            raise exc

        if not self.is_draft:
            raise err.PatchUnprocessable(message="this message is not a draft")
        try:
            current_state = params.pop("current_state")
            draft_param = Draft(params, strict=strict_patch)
        except Exception as exc:
            log.info(exc)
            raise err.PatchError(message=exc.message)

        # add missing params to be able to check consistency
        self_dict = self.marshall_dict()
        if "message_id" not in params and self.message_id:
            draft_param.message_id = UUIDType().to_native(self.message_id)

        if "parent_id" not in params and self.parent_id:
            draft_param.parent_id = UUIDType().to_native(self.parent_id)

        if "subject" not in params:
            draft_param.subject = self.subject

        if "participants" not in params and self.participants:
            for participant in self_dict['participants']:
                indexed = IndexedParticipant(participant)
                draft_param.participants.append(indexed)

        if "user_identities" not in params and self.user_identities:
            draft_param.user_identities = self_dict["user_identities"]

        # make sure the <from> participant is present
        # and is consistent with selected user's identity
        try:
            new_discussion_id = draft_param.validate_consistency(user, False)
        except Exception as exc:
            log.info("consistency validation failed with err : {}".format(exc))
            raise err.PatchError(message=exc.message)

        validated_draft = draft_param.serialize()
        validated_params = copy.deepcopy(params)

        if "participants" in params:
            validated_params["participants"] = validated_draft["participants"]
        if new_discussion_id != self.discussion_id:
            # discussion_id has changed, update draft's discussion_id
            current_state["discussion_id"] = self.discussion_id
            validated_params["discussion_id"] = new_discussion_id

        # remove empty ids from current state if any
        if "parent_id" in current_state and current_state["parent_id"] == "":
            del (current_state["parent_id"])

        # handle body key mapping to body_plain or body_html
        # TODO: handle plain/html flag to map to right field
        if "body" in validated_params:
            validated_params["body_plain"] = validated_params["body"]
            del (validated_params["body"])
        if "body" in current_state:
            current_state["body_plain"] = current_state["body"]
            del (current_state["body"])

        # date should reflect last edit time
        current_state["date"] = self.date
        current_state["date_sort"] = self.date_sort
        validated_params["date"] = validated_params["date_sort"] = \
            datetime.datetime.now(tz=pytz.utc)

        validated_params["current_state"] = current_state

        if "participants" in current_state and self.participants:
            # replace participants' label and contact_ids
            # because frontend has up-to-date data for these properties
            # which are probably not the one stored in db
            db_parts = {}
            for p in self_dict['participants']:
                db_parts[p['protocol'] + p['type'] + p['address']] = p
            for i, p in enumerate(current_state['participants']):
                current_state['participants'][i] = db_parts[p['protocol'] +
                                                            p['type'] +
                                                            p['address']]

        try:
            self.apply_patch(validated_params, **options)
        except Exception as exc:
            log.info("apply_patch() failed with error : {}".format(exc))
            raise exc
Ejemplo n.º 2
0
    def apply_patch(self, patch, **options):
        """
        Update self attributes with patch rfc7396 and Caliopen's specifications
        if, and only if, patch is consistent with current obj db instance

        :param patch: json-dict object describing the patch to apply
                with a "current_state" key. see caliopen rfc for explanation
        :param options: whether patch should be propagated to db and/or index
        :return: Exception or None
        """
        if patch is None or "current_state" not in patch:
            raise main_errors.PatchUnprocessable()

        patch_current = patch.pop("current_state")

        # build 3 siblings : 2 from patch and last one from db
        obj_patch_new = self.__class__(user_id=self.user_id)
        obj_patch_old = self.__class__(user_id=self.user_id)
        try:
            obj_patch_new.unmarshall_json_dict(patch)
        except Exception as exc:
            log.info(exc)
            raise main_errors.PatchUnprocessable(message= \
                "unable to unmarshall patch into object <{}>".format(
                    exc))
        try:
            obj_patch_old.unmarshall_json_dict(patch_current)
        except Exception as exc:
            log.info(exc)
            raise main_errors.PatchUnprocessable(message= \
                "unable to unmarshall patch into object <{}>".format(
                    exc))
        self.get_db()

        # TODO : manage protected attributes, to prevent patch on them
        if "tags" in patch.keys():
            raise main_errors.ForbiddenAction(
                message="patching tags through parent object is forbidden")

        # check if patch is consistent with db current state
        # if it is, squash self attributes
        self.unmarshall_db()

        for key in patch.keys():
            current_attr = self._attrs[key]
            try:
                self._check_key_consistency(current_attr, key, obj_patch_old,
                                            obj_patch_new)
            except Exception as exc:
                log.info("key consistency checking failed: {}".format(exc))
                raise exc

            # all controls passed, we can actually set the new attribute
            create_sub_object = False
            if key not in patch_current.keys():
                create_sub_object = True
            else:
                if patch_current[key] in (None, [], {}):
                    create_sub_object = True
                if isinstance(
                        patch_current[key],
                        list) and len(patch[key]) > len(patch_current[key]):
                    create_sub_object = True

            if patch[key] is not None:
                unmarshall_item(patch, key, self, self._attrs[key],
                                create_sub_object)

        if "db" in options and options["db"] is True:
            # apply changes to db model and update db
            if "with_validation" in options and options[
                    "with_validation"] is True:
                d = self.marshall_dict()
                try:
                    self._json_model(d).validate()
                except Exception as exc:
                    log.info("document is not valid: {}".format(exc))
                    raise main_errors.PatchUnprocessable(
                        message="document is not valid,"
                        " can't insert it into db: <{}>".format(exc))

            self.marshall_db()
            try:
                self.update_db()
            except Exception as exc:
                log.info(exc)
                raise main_errors.PatchError(message="Error when updating db")
Ejemplo n.º 3
0
    def patch_draft(self, patch, **options):
        """Operation specific to draft, before applying generic patch."""
        try:
            params = dict(patch)
        except Exception as exc:
            log.info(exc)
            raise err.PatchError(message=exc.message)

        # silently remove unexpected props within patch if not in strict mode
        strict_patch = Configuration('global').get('apiV1.strict_patch', False)
        if not strict_patch:
            allowed_properties = [
                "body",
                "current_state",
                "identities",
                "message_id",
                "parent_id",
                "participants",
                "subject",
            ]
            for key, value in params.items():
                if key not in allowed_properties:
                    del (params[key])

            for key, value in params["current_state"].items():
                if key not in allowed_properties:
                    del (params["current_state"][key])

        try:
            self.get_db()
            self.unmarshall_db()
        except Exception as exc:
            log.info("patch_draft() failed to get msg from db: {}".format(exc))
            raise exc

        if not self.is_draft:
            raise err.PatchUnprocessable(message="this message is not a draft")
        try:
            current_state = params.pop("current_state")
            draft_param = Draft(params, strict=strict_patch)
        except Exception as exc:
            log.info(exc)
            raise err.PatchError(message=exc.message)

        # add missing params to be able to check consistency
        self_dict = self.marshall_dict()
        if "message_id" not in params and self.message_id:
            draft_param.message_id = UUIDType().to_native(self.message_id)

        if "discussion_id" not in params and self.discussion_id:
            draft_param.discussion_id = UUIDType().to_native(
                self.discussion_id)

        if "parent_id" not in params and self.parent_id:
            draft_param.parent_id = UUIDType().to_native(self.parent_id)

        if "subject" not in params:
            draft_param.subject = self.subject

        if "participants" not in params and self.participants:
            for participant in self_dict['participants']:
                draft_param.participants.append(
                    IndexedParticipant(participant))

        if "identities" not in params and self.identities:
            draft_param.identities = self_dict["identities"]

        try:
            draft_param.validate_consistency(str(self.user_id), False)
        except Exception as exc:
            log.info("consistency validation failed with err : {}".format(exc))
            raise err.PatchError(message=exc.message)

        # make sure the <from> participant is present
        # and is consistent with selected user's identity
        validated_draft = draft_param.serialize()
        validated_params = copy.deepcopy(params)
        if "participants" in params:
            validated_params["participants"] = validated_draft["participants"]

        # remove empty UUIDs from current state if any
        if "parent_id" in current_state and current_state["parent_id"] == "":
            del (current_state["parent_id"])
        if "discussion_id" in current_state and current_state[
                "discussion_id"] == "":
            del (current_state["discussion_id"])

        # handle body key mapping to body_plain or body_html
        # TODO: handle plain/html flag to map to right field
        if "body" in validated_params:
            validated_params["body_plain"] = validated_params["body"]
            del (validated_params["body"])
        if "body" in current_state:
            current_state["body_plain"] = current_state["body"]
            del (current_state["body"])

        # date should reflect last edit time
        current_state["date"] = self.date
        current_state["date_sort"] = self.date_sort
        validated_params["date"] = validated_params["date_sort"] = \
            datetime.datetime.now(tz=pytz.utc)

        validated_params["current_state"] = current_state
        try:
            self.apply_patch(validated_params, **options)
        except Exception as exc:
            log.info("apply_patch() failed with error : {}".format(exc))
            raise exc
Ejemplo n.º 4
0
    def _check_discussion_consistency(self, user):
        from caliopen_main.message.objects.message import Message
        new_discussion = False
        if not hasattr(self, 'discussion_id') or self.discussion_id == "" \
                or self.discussion_id is None:
            # no discussion_id provided. Try to find one with draft's parent_id
            # or create new discussion
            if hasattr(self, 'parent_id') \
                    and self.parent_id is not None \
                    and self.parent_id != "":
                parent_msg = Message(user, message_id=self.parent_id)
                try:
                    parent_msg.get_db()
                    parent_msg.unmarshall_db()
                except NotFound:
                    raise err.PatchError(message="parent message not found")
                self.discussion_id = parent_msg.discussion_id
            else:
                discussion = Discussion.create_from_message(user, self)
                self.discussion_id = discussion.discussion_id
                new_discussion = True
        if not new_discussion:
            dim = DIM(user)
            d_id = self.discussion_id
            last_message = dim.get_last_message(d_id, -10, 10, True)
            if last_message == {}:
                raise err.PatchError(
                    message='No such discussion {}'.format(d_id))
            is_a_reply = (str(last_message.message_id) != str(self.message_id))
            if is_a_reply:
                # check participants consistency
                if hasattr(self,
                           "participants") and len(self.participants) > 0:
                    participants = [p['address'] for p in self.participants]
                    last_msg_participants = [
                        p['address'] for p in last_message.participants
                    ]
                    if len(participants) != len(last_msg_participants):
                        raise err.PatchError(
                            message="list of participants "
                            "is not consistent for this discussion")
                    participants.sort()
                    last_msg_participants.sort()

                    for i, participant in enumerate(participants):
                        if participant != last_msg_participants[i]:
                            raise err.PatchConflict(
                                message="list of participants "
                                "is not consistent for this discussion")
                else:
                    self.build_participants_for_reply(user)

                # check parent_id consistency
                if 'parent_id' in self and self.parent_id != "" \
                        and self.parent_id is not None:
                    if not dim.message_belongs_to(
                            discussion_id=self.discussion_id,
                            message_id=self.parent_id):
                        raise err.PatchConflict(message="provided message "
                                                "parent_id does not belong "
                                                "to this discussion")
                else:
                    self.parent_id = last_message.parent_id

                self.update_external_references(user)

            else:
                last_message = None
        else:
            last_message = None

        return last_message
Ejemplo n.º 5
0
    def patch_draft(self, patch, **options):
        """Operation specific to draft, before applying generic patch."""
        try:
            self.get_db()
            self.unmarshall_db()
        except Exception as exc:
            log.info("patch_draft() failed to get msg from db: {}".format(exc))
            raise exc

        if not self.is_draft:
            raise err.PatchUnprocessable(message="this message is not a draft")
        try:
            params = dict(patch)
            current_state = params.pop("current_state")
            draft_param = Draft(params)
        except Exception as exc:
            log.info(exc)
            raise err.PatchError(message=exc.message)

        # add missing params to be able to check consistency
        self_dict = self.marshall_dict()
        if "message_id" not in params and self.message_id:
            draft_param.message_id = UUIDType().to_native(self.message_id)

        if "discussion_id" not in params and self.discussion_id:
            draft_param.discussion_id = UUIDType().to_native(
                self.discussion_id)

        if "parent_id" not in params and self.parent_id:
            draft_param.parent_id = UUIDType().to_native(self.parent_id)

        if "subject" not in params:
            draft_param.subject = self.subject

        if "participants" not in params and self.participants:
            for participant in self_dict['participants']:
                draft_param.participants.append(
                    IndexedParticipant(participant))

        if "identities" not in params and self.identities:
            draft_param.identities = self_dict["identities"]

        try:
            draft_param.validate_consistency(str(self.user_id), False)
        except Exception as exc:
            log.info("consistency validation failed with err : {}".format(exc))
            raise err.PatchError(message=exc.message)

        # make sure the <from> participant is present
        # and is consistent with selected user's identity
        validated_draft = draft_param.serialize()
        validated_params = copy.deepcopy(params)
        if "participants" in params:
            validated_params["participants"] = validated_draft["participants"]

        # handle body key mapping to body_plain or body_html
        # TODO: handle plain/html flag to map to right field
        if "body" in validated_params:
            validated_params["body_plain"] = validated_params["body"]
            del (validated_params["body"])
        if "body" in current_state:
            current_state["body_plain"] = current_state["body"]
            del (current_state["body"])

        validated_params["current_state"] = current_state

        try:
            self.apply_patch(validated_params, **options)
        except Exception as exc:
            log.info("apply_patch() failed with error : {}".format(exc))
            raise exc