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
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
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))
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
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")
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
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