Esempio n. 1
0
 def validate_uuid(self, user_id):
     if not self.message_id or not isinstance(self.message_id, uuid.UUID):
         raise err.PatchUnprocessable(
             message="missing or invalid message_id")
     try:
         ModelMessage.get(user_id=user_id, message_id=self.message_id)
         raise err.PatchUnprocessable(message="message_id not unique")
     except NotFound:
         pass
Esempio n. 2
0
    def _add_from_participant(self, user):

        if 'user_identities' not in self:
            raise err.PatchUnprocessable

        if len(self['user_identities']) != 1:
            raise err.PatchUnprocessable

        user_identity = UserIdentity(user,
                                     identity_id=str(
                                         self['user_identities'][0]))
        try:
            user_identity.get_db()
            user_identity.unmarshall_db()
        except NotFound:
            raise err.PatchUnprocessable(message="identity not found")

        # add 'from' participant with local identity's identifier
        if not hasattr(self, 'participants'):
            self.participants = []
        else:
            if len(self.participants) > 0:
                for i, participant in enumerate(self.participants):
                    if re.match("from", participant['type'], re.IGNORECASE):
                        self.participants.pop(i)

        from_participant = Participant()
        from_participant.address = user_identity.identifier
        from_participant.label = user_identity.display_name
        from_participant.protocol = "email"
        from_participant.type = "From"
        from_participant.contact_ids = [user.contact.contact_id]
        self.participants.append(from_participant)
        return from_participant
Esempio n. 3
0
    def _check_key_consistency(self, current_attr, key, obj_patch_old,
                               patch_current):
        """
        check if a key provided in patch is consistent with current state

        """

        if key not in self._attrs.keys():
            raise main_errors.PatchUnprocessable(
                message="unknown key in patch")
        old_val = getattr(obj_patch_old, key)
        cur_val = getattr(self, key)
        msg = "Patch current_state not consistent with db, step {} key {}"

        if isinstance(current_attr, types.ListType):
            if not isinstance(cur_val, types.ListType):
                raise main_errors.PatchConflict(messag=msg.format(0, key))

        if key not in patch_current.keys():
            # means patch wants to add the key.
            # Value in db should be null or empty
            if cur_val not in (None, [], {}):
                raise main_errors.PatchConflict(message=msg.format(0.5, key))
        else:
            if isinstance(current_attr, types.ListType):
                if old_val == [] and cur_val != []:
                    raise main_errors.PatchConflict(message=msg.format(1, key))
                if cur_val == [] and old_val != []:
                    raise main_errors.PatchConflict(message=msg.format(2, key))
                for old in old_val:
                    for elem in cur_val:
                        if issubclass(current_attr[0], CaliopenObject):
                            if elem.__dict__ == old.__dict__:
                                break
                        else:
                            if elem == old:
                                break
                    else:
                        raise main_errors.PatchConflict(
                            message=msg.format(3, key))
            elif issubclass(self._attrs[key], types.DictType):
                if cmp(old_val, cur_val) != 0:
                    raise main_errors.PatchConflict(message=msg.format(4, key))
            else:
                # XXX ugly patch but else compare 2 distinct objects
                # and not their representation
                if hasattr(old_val, 'marshall_dict') and \
                        hasattr(cur_val, 'marshall_dict'):
                    old_val = old_val.marshall_dict()
                    cur_val = cur_val.marshall_dict()
                if old_val != cur_val:
                    raise main_errors.PatchConflict(message=msg.format(5, key))
Esempio n. 4
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
Esempio n. 5
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")
Esempio n. 6
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
Esempio n. 7
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