def on_update(self, updates, original): """ Called on the patch request to mark a activity/notification/comment as having been read and nothing else :param updates: :param original: :return: """ user = getattr(g, "user", None) if not user: raise SuperdeskApiError.notFoundError("Can not determine user") user_id = str(user.get("_id")) # make sure that the user making the read notification is in the notification list if user_id not in updates.get("read").keys(): raise SuperdeskApiError.forbiddenError("User is not in the notification list") # make sure the transition is from not read to read if not (updates.get("read")[user_id] == 1 and original.get("read")[user_id] == 0): raise SuperdeskApiError.forbiddenError("Can not set notification as read") # make sure that no other users are being marked as read for read_entry in updates.get("read"): if read_entry != user_id: if updates.get("read")[read_entry] != original.get("read")[read_entry]: raise SuperdeskApiError.forbiddenError("Can not set other users notification as read") # make sure that no other fields are being up dated just read and _updated if len(updates) != 2: raise SuperdeskApiError.forbiddenError("Can not update")
def on_update(self, updates, original): """Called on the patch request to mark a activity/notification/comment as read and nothing else :param updates: :param original: :return: """ user = getattr(g, 'user', None) if not user: raise SuperdeskApiError.notFoundError('Can not determine user') user_id = user.get('_id') # make sure that the user making the read notification is in the notification list if not self.is_recipient(updates, user_id): raise SuperdeskApiError.forbiddenError('User is not in the notification list') # make sure the transition is from not read to read if not self.is_read(updates, user_id) and self.is_read(original, user_id): raise SuperdeskApiError.forbiddenError('Can not set notification as read') # make sure that no other users are being marked as read for recipient in updates.get('recipients', []): if recipient['user_id'] != user_id: if self.is_read(updates, recipient['user_id']) != self.is_read(original, recipient['user_id']): raise SuperdeskApiError.forbiddenError('Can not set other users notification as read') # make sure that no other fields are being up dated just read and _updated if len(updates) != 2: raise SuperdeskApiError.forbiddenError('Can not update')
def _validate_user(self, doc_user_id, doc_is_global): session_user = get_user(required=True) if str(session_user['_id']) != str(doc_user_id): if not doc_is_global: raise SuperdeskApiError.forbiddenError('Unauthorized to modify other user\'s local search.') elif not current_user_has_privilege('global_saved_searches'): raise SuperdeskApiError.forbiddenError('Unauthorized to modify global search.')
def on_update(self, updates, original): user = get_user() if 'unique_name' in updates and not is_admin(user) \ and (user['active_privileges'].get('metadata_uniquename', 0) == 0): raise SuperdeskApiError.forbiddenError("Unauthorized to modify Unique Name") remove_unwanted(updates) if self.__is_req_for_save(updates): update_state(original, updates) lock_user = original.get('lock_user', None) force_unlock = updates.get('force_unlock', False) original_creator = updates.get('original_creator', None) if not original_creator: updates['original_creator'] = original['original_creator'] str_user_id = str(user.get('_id')) if lock_user and str(lock_user) != str_user_id and not force_unlock: raise SuperdeskApiError.forbiddenError('The item was locked by another user') updates['versioncreated'] = utcnow() set_item_expiry(updates, original) updates['version_creator'] = str_user_id update_word_count(updates) if force_unlock: del updates['force_unlock']
def on_delete(self, docs): if docs.get('is_default'): raise SuperdeskApiError.forbiddenError('Cannot delete the default role') # check if there are any users in the role user = get_resource_service('users').find_one(req=None, role=docs.get('_id')) if user: raise SuperdeskApiError.forbiddenError('Cannot delete the role, it still has users in it!')
def on_delete(self, doc): """ Checks if deleting the stage would not violate data integrity, raises an exception if it does. 1/ Can't delete the default incomming stage 2/ The stage must have no unspiked documents 3/ The stage can not be refered to by a ingest routing rule :param doc: :return: """ if doc['default_incoming'] is True: desk_id = doc.get('desk', None) if desk_id and superdesk.get_resource_service('desks').find_one(req=None, _id=desk_id): raise SuperdeskApiError.forbiddenError(message='Cannot delete a default stage.') # check if the stage has any documents in it items = self._get_unspiked_stage_documents(str(doc['_id'])) if items.count() > 0: # cannot delete raise SuperdeskApiError.forbiddenError(message='Only empty stages can be deleted.') # check if the stage is refered to in a ingest routing rule rules = self._stage_in_rule(doc['_id']) if rules.count() > 0: rule_names = ', '.join(rule.get('name') for rule in rules) raise SuperdeskApiError.forbiddenError( message='Stage is refered to by Ingest Routing Schemes : {}'.format(rule_names))
def on_update(self, updates, original): if updates.get('content_expiry') == 0: updates['content_expiry'] = app.settings['CONTENT_EXPIRY_MINUTES'] super().on_update(updates, original) if updates.get('content_expiry', None): docs = self.get_stage_documents(str(original[config.ID_FIELD])) for doc in docs: expiry = get_expiry_date(updates['content_expiry'], doc['versioncreated']) item_model = get_model(ItemModel) item_model.update({'_id': doc[config.ID_FIELD]}, {'expiry': expiry}) if updates.get('working_stage', False): if not original.get('working_stage'): self.remove_old_default(original.get('desk'), 'working_stage') self.set_desk_ref(original, 'working_stage') else: if original.get('working_stage') and 'working_stage' in updates: raise SuperdeskApiError.forbiddenError(message='Must have one working stage in a desk') if updates.get('default_incoming', False): if not original.get('default_incoming'): self.remove_old_default(original.get('desk'), 'default_incoming') self.set_desk_ref(original, 'incoming_stage') else: if original.get('default_incoming') and 'default_incoming' in updates: raise SuperdeskApiError.forbiddenError(message='Must have one incoming stage in a desk')
def on_delete(self, doc): if doc['default_incoming'] is True: desk_id = doc.get('desk', None) if desk_id and superdesk.get_resource_service('desks').find_one(req=None, _id=desk_id): raise SuperdeskApiError.forbiddenError(message='Cannot delete a default stage.') else: # check if the stage has any documents in it items = self.get_stage_documents(str(doc['_id'])) if items.count() > 0: # cannot delete raise SuperdeskApiError.forbiddenError(message='Only empty stages can be deleted.')
def on_update(self, updates, original): is_update_allowed(original) user = get_user() if "publish_schedule" in updates and original["state"] == "scheduled": # this is an descheduling action self.deschedule_item(updates, original) return if updates.get("publish_schedule"): if datetime.datetime.fromtimestamp(False).date() == updates.get("publish_schedule").date(): # publish_schedule field will be cleared updates["publish_schedule"] = None else: # validate the schedule self.validate_schedule(updates.get("publish_schedule")) if ( "unique_name" in updates and not is_admin(user) and (user["active_privileges"].get("metadata_uniquename", 0) == 0) ): raise SuperdeskApiError.forbiddenError("Unauthorized to modify Unique Name") remove_unwanted(updates) if self.__is_req_for_save(updates): update_state(original, updates) lock_user = original.get("lock_user", None) force_unlock = updates.get("force_unlock", False) updates.setdefault("original_creator", original.get("original_creator")) str_user_id = str(user.get("_id")) if user else None if lock_user and str(lock_user) != str_user_id and not force_unlock: raise SuperdeskApiError.forbiddenError("The item was locked by another user") updates["versioncreated"] = utcnow() set_item_expiry(updates, original) updates["version_creator"] = str_user_id set_sign_off(updates, original) update_word_count(updates) if force_unlock: del updates["force_unlock"] if original["type"] == "composite": self.packageService.on_update(updates, original) update_version(updates, original)
def check_for_duplicates(self, package, associations): counter = Counter() package_id = package[config.ID_FIELD] for itemRef in [assoc[ITEM_REF] for assoc in associations if assoc.get(ITEM_REF)]: if itemRef == package_id: message = 'Trying to self reference as an association.' logger.error(message) raise SuperdeskApiError.forbiddenError(message=message) counter[itemRef] += 1 if any(itemRef for itemRef, value in counter.items() if value > 1): message = 'Content associated multiple times' logger.error(message) raise SuperdeskApiError.forbiddenError(message=message)
def on_update(self, updates, original): updates[ITEM_OPERATION] = ITEM_UPDATE is_update_allowed(original) user = get_user() if 'publish_schedule' in updates and original['state'] == 'scheduled': # this is an descheduling action self.deschedule_item(updates, original) return if updates.get('publish_schedule'): if datetime.datetime.fromtimestamp(False).date() == updates.get('publish_schedule').date(): # publish_schedule field will be cleared updates['publish_schedule'] = None else: # validate the schedule self.validate_schedule(updates.get('publish_schedule')) if 'unique_name' in updates and not is_admin(user) \ and (user['active_privileges'].get('metadata_uniquename', 0) == 0): raise SuperdeskApiError.forbiddenError("Unauthorized to modify Unique Name") remove_unwanted(updates) if self.__is_req_for_save(updates): update_state(original, updates) lock_user = original.get('lock_user', None) force_unlock = updates.get('force_unlock', False) updates.setdefault('original_creator', original.get('original_creator')) str_user_id = str(user.get('_id')) if user else None if lock_user and str(lock_user) != str_user_id and not force_unlock: raise SuperdeskApiError.forbiddenError('The item was locked by another user') updates['versioncreated'] = utcnow() set_item_expiry(updates, original) updates['version_creator'] = str_user_id set_sign_off(updates, original) update_word_count(updates) if force_unlock: del updates['force_unlock'] if original['type'] == 'composite': self.packageService.on_update(updates, original) update_version(updates, original)
def on_delete(self, deleted_theme): global_default_theme = get_resource_service('global_preferences').get_global_prefs()['theme'] # raise an exception if the removed theme is the default one if deleted_theme['name'] == global_default_theme: raise SuperdeskApiError.forbiddenError('This is a default theme and can not be deleted') # raise an exception if the removed theme has children if self.get(req=None, lookup={'extends': deleted_theme['name']}).count() > 0: raise SuperdeskApiError.forbiddenError('This theme has children. It can\'t be removed') # update all the blogs using the removed theme and assign the default theme blogs_service = get_resource_service('blogs') default_theme = blogs_service.get_theme_snapshot(global_default_theme) blogs = blogs_service.get(req=None, lookup={'theme._id': deleted_theme['_id']}) for blog in blogs: # will assign the default theme to this blog blogs_service.system_update(ObjectId(blog['_id']), {'theme': default_theme}, blog)
def check_root_group(self, docs): for groups in [doc.get('groups') for doc in docs if doc.get('groups')]: self.check_all_groups_have_id_set(groups) root_groups = [group for group in groups if group.get('id') == 'root'] if len(root_groups) == 0: message = 'Root group is missing.' logger.error(message) raise SuperdeskApiError.forbiddenError(message=message) if len(root_groups) > 1: message = 'Only one root group is allowed.' logger.error(message) raise SuperdeskApiError.forbiddenError(message=message) self.check_that_all_groups_are_referenced_in_root(root_groups[0], groups)
def unlock(self, item_filter, user_id, session_id, etag): item_model = get_model(ItemModel) item = item_model.find_one(item_filter) if not item: raise SuperdeskApiError.notFoundError() if not item.get(LOCK_USER): raise SuperdeskApiError.badRequestError(message="Item is not locked.") can_user_unlock, error_message = self.can_unlock(item, user_id) if can_user_unlock: self.app.on_item_unlock(item, user_id) # delete the item if nothing is saved so far if item['_version'] == 1 and item['state'] == 'draft': superdesk.get_resource_service('archive').delete(lookup={'_id': item['_id']}) return updates = {LOCK_USER: None, LOCK_SESSION: None, 'lock_time': None, 'force_unlock': True} item_model.update(item_filter, updates) self.app.on_item_unlocked(item, user_id) push_notification('item:unlock', item=str(item_filter.get(config.ID_FIELD)), user=str(user_id), lock_session=str(session_id)) else: raise SuperdeskApiError.forbiddenError(message=error_message) item = item_model.find_one(item_filter) return item
def unlock(self, item_filter, user_id, session_id, etag): item_model = get_model(ItemModel) item = item_model.find_one(item_filter) if not item: raise SuperdeskApiError.notFoundError() if not item.get(LOCK_USER): raise SuperdeskApiError.badRequestError(message="Item is not locked.") can_user_unlock, error_message = self.can_unlock(item, user_id) if can_user_unlock: self.app.on_item_unlock(item, user_id) # delete the item if nothing is saved so far # version 0 created on lock item if item[config.VERSION] == 0 and item["state"] == "draft": superdesk.get_resource_service("archive").delete(lookup={"_id": item["_id"]}) return updates = {LOCK_USER: None, LOCK_SESSION: None, "lock_time": None, "force_unlock": True} item_model.update(item_filter, updates) self.app.on_item_unlocked(item, user_id) push_notification( "item:unlock", item=str(item_filter.get(config.ID_FIELD)), user=str(user_id), lock_session=str(session_id), ) else: raise SuperdeskApiError.forbiddenError(message=error_message) item = item_model.find_one(item_filter) return item
def create(self, docs, **kwargs): target_id = request.view_args['target_id'] doc = docs[0] link_id = doc.get('link_id') desk_id = doc.get('desk') service = get_resource_service(ARCHIVE) target = service.find_one(req=None, _id=target_id) self._validate_link(target, target_id) link = {} if is_genre(target, BROADCAST_GENRE): raise SuperdeskApiError.badRequestError("Cannot add new take to the story with genre as broadcast.") if desk_id: link = {'task': {'desk': desk_id}} user = get_user() lookup = {'_id': desk_id, 'members.user': user['_id']} desk = get_resource_service('desks').find_one(req=None, **lookup) if not desk: raise SuperdeskApiError.forbiddenError("No privileges to create new take on requested desk.") link['task']['stage'] = desk['working_stage'] if link_id: link = service.find_one(req=None, _id=link_id) linked_item = self.packageService.link_as_next_take(target, link) doc.update(linked_item) build_custom_hateoas(CUSTOM_HATEOAS, doc) return [linked_item['_id']]
def create(self, docs, **kwargs): target_id = request.view_args['target_id'] doc = docs[0] link_id = doc.get('link_id') desk_id = doc.get('desk') service = get_resource_service(ARCHIVE) target = service.find_one(req=None, _id=target_id) self._validate_link(target, target_id) link = {} if desk_id: link = {'task': {'desk': desk_id}} user = get_user() lookup = {'_id': desk_id, 'members.user': user['_id']} desk = get_resource_service('desks').find_one(req=None, **lookup) if not desk: raise SuperdeskApiError.forbiddenError("No privileges to create new take on requested desk.") link['task']['stage'] = desk['working_stage'] if link_id: link = service.find_one(req=None, _id=link_id) linked_item = self.packageService.link_as_next_take(target, link) insert_into_versions(id_=linked_item[config.ID_FIELD]) doc.update(linked_item) build_custom_hateoas(CUSTOM_HATEOAS, doc) return [linked_item['_id']]
def on_delete(self, doc): """ Overriding to check if the search provider being requested to delete has been used to fetch items. """ if doc.get('last_item_update'): raise SuperdeskApiError.forbiddenError("Deleting a Search Provider after receiving items is prohibited.")
def on_delete(self, doc): """ Overriding to check if the Ingest Source which has received item being deleted. """ if doc.get('last_item_update'): raise SuperdeskApiError.forbiddenError("Deleting an Ingest Source after receiving items is prohibited.")
def delete(self, lookup): """Delete user session. Deletes all the records from auth and corresponding session_preferences from user collections If there are any orphan session_preferences exist they get deleted as well """ users_service = get_resource_service('users') user_id = request.view_args['user_id'] user = users_service.find_one(req=None, _id=user_id) sessions = get_resource_service('auth').get(req=None, lookup={'user': user_id}) error_message = self.__can_clear_sessions(user) if error_message: raise SuperdeskApiError.forbiddenError(message=error_message) # Delete all the sessions for session in sessions: get_resource_service('auth').delete_action({config.ID_FIELD: str(session[config.ID_FIELD])}) # Check if any orphan session_preferences exist for the user if user.get('session_preferences'): # Delete the orphan sessions users_service.patch(user[config.ID_FIELD], {'session_preferences': {}}) return [{'complete': True}]
def lock(self, item_filter, user_id, session_id, etag): item_model = get_model(ItemModel) item = item_model.find_one(item_filter) if not item: raise SuperdeskApiError.notFoundError() can_user_lock, error_message = self.can_lock(item, user_id, session_id) if can_user_lock: self.app.on_item_lock(item, user_id) updates = {LOCK_USER: user_id, LOCK_SESSION: session_id, 'lock_time': utcnow()} item_model.update(item_filter, updates) if item.get(TASK): item[TASK]['user'] = user_id else: item[TASK] = {'user': user_id} superdesk.get_resource_service('tasks').assign_user(item[config.ID_FIELD], item[TASK]) self.app.on_item_locked(item, user_id) push_notification('item:lock', item=str(item.get(config.ID_FIELD)), item_version=str(item.get(config.VERSION)), user=str(user_id), lock_time=updates['lock_time'], lock_session=str(session_id)) else: raise SuperdeskApiError.forbiddenError(message=error_message) item = item_model.find_one(item_filter) return item
def unlock(self, item_filter, user_id, session_id, etag): item_model = get_model(ItemModel) item = item_model.find_one(item_filter) if not item: raise SuperdeskApiError.notFoundError() if not item.get(LOCK_USER): raise SuperdeskApiError.badRequestError(message="Item is not locked.") can_user_unlock, error_message = self.can_unlock(item, user_id) if can_user_unlock: self.app.on_item_unlock(item, user_id) # delete the item if nothing is saved so far # version 0 created on lock item if item.get(config.VERSION, 0) == 0 and item[ITEM_STATE] == CONTENT_STATE.DRAFT: superdesk.get_resource_service('archive').delete_action(lookup={'_id': item['_id']}) push_content_notification([item]) else: updates = {LOCK_USER: None, LOCK_SESSION: None, 'lock_time': None, 'force_unlock': True} item_model.update(item_filter, updates) self.app.on_item_unlocked(item, user_id) push_notification('item:unlock', item=str(item_filter.get(config.ID_FIELD)), item_version=str(item.get(config.VERSION)), state=item.get(ITEM_STATE), user=str(user_id), lock_session=str(session_id)) else: raise SuperdeskApiError.forbiddenError(message=error_message) item = item_model.find_one(item_filter) return item
def _validate(self, doc_in_archive, doc, guid_to_duplicate): """Validates if the given archived_doc is still eligible to be duplicated. Rules: 1. Is the item requested found in archive collection? 2. Is workflow transition valid? 3. Is item locked by another user? :param doc_in_archive: object representing the doc in archive collection :type doc_in_archive: dict :param doc: object received as part of request :type doc: dict :param guid_to_duplicate: GUID of the item to duplicate :type guid_to_duplicate: str :raises SuperdeskApiError.notFoundError: If doc_in_archive is None SuperdeskApiError.forbiddenError: if item is locked InvalidStateTransitionError: if workflow transition is invalid """ if not doc_in_archive: raise SuperdeskApiError.notFoundError('Fail to found item with guid: %s' % guid_to_duplicate) if not is_workflow_state_transition_valid('duplicate', doc_in_archive[ITEM_STATE]): raise InvalidStateTransitionError() lock_user = doc_in_archive.get('lock_user', None) force_unlock = doc_in_archive.get('force_unlock', False) user = get_user() str_user_id = str(user.get(config.ID_FIELD)) if user else None if lock_user and str(lock_user) != str_user_id and not force_unlock: raise SuperdeskApiError.forbiddenError('The item was locked by another user')
def lock(self, item_filter, user_id, session_id, action): item_model = get_model(ItemModel) item = item_model.find_one(item_filter) # set the lock_id it per item lock_id = "item_lock {}".format(item.get(config.ID_FIELD)) if not item: raise SuperdeskApiError.notFoundError() # get the lock it not raise forbidden exception if not lock(lock_id, expire=5): raise SuperdeskApiError.forbiddenError(message="Item is locked by another user.") try: can_user_lock, error_message = self.can_lock(item, user_id, session_id) if can_user_lock: self.app.on_item_lock(item, user_id) updates = {LOCK_USER: user_id, LOCK_SESSION: session_id, 'lock_time': utcnow()} if action: updates['lock_action'] = action item_model.update(item_filter, updates) if item.get(TASK): item[TASK]['user'] = user_id else: item[TASK] = {'user': user_id} superdesk.get_resource_service('tasks').assign_user(item[config.ID_FIELD], item[TASK]) self.app.on_item_locked(item, user_id) item = item_model.find_one(item_filter) push_notification('item:lock', item=str(item.get(config.ID_FIELD)), item_version=str(item.get(config.VERSION)), user=str(user_id), lock_time=updates['lock_time'], lock_session=str(session_id), _etag=item.get(config.ETAG)) else: raise SuperdeskApiError.forbiddenError(message=error_message) item = item_model.find_one(item_filter) return item finally: # unlock the lock :) unlock(lock_id, remove=True)
def check_permissions(self, resource, method, user): """Checks user permissions. 1. If there's no user associated with the request or HTTP Method is GET then return True. 2. Get User's Privileges 3. Intrinsic Privileges: Check if resource has intrinsic privileges. If it has then check if HTTP Method is allowed. Return True if `is_authorized()` on the resource service returns True. Otherwise, raise ForbiddenError. HTTP Method not allowed continue No intrinsic privileges continue 4. User's Privileges Get Resource Privileges and validate it against user's privileges. Return True if validation is successful. Otherwise continue. 5. If method didn't return True, then user is not authorized to perform the requested operation on the resource. """ # Step 1: if not user: return True # Step 2: Get User's Privileges get_resource_service('users').set_privileges(user, flask.g.role) if method == 'GET': return True # Step 3: Intrinsic Privileges intrinsic_privileges = get_intrinsic_privileges() if intrinsic_privileges.get(resource) and method in intrinsic_privileges[resource]: service = get_resource_service(resource) authorized = service.is_authorized(user_id=str(user.get('_id')), _id=request.view_args.get('_id')) if not authorized: raise SuperdeskApiError.forbiddenError() return authorized # Step 4: User's privileges privileges = user.get('active_privileges', {}) resource_privileges = get_resource_privileges(resource).get(method, None) if privileges.get(resource_privileges, False): return True # Step 5: raise SuperdeskApiError.forbiddenError()
def create(self, docs, **kwargs): """Generate highlights text item for given package. If doc.preview is True it won't save the item, only return. """ service = superdesk.get_resource_service('archive') for doc in docs: preview = doc.get('preview', False) package = service.find_one(req=None, _id=doc['package']) if not package: superdesk.abort(404) export = doc.get('export') template = get_template(package.get('highlight')) stringTemplate = None if template and 'body_html' in template.get('data', {}): stringTemplate = template['data']['body_html'] doc.clear() doc[ITEM_TYPE] = CONTENT_TYPE.TEXT doc['family_id'] = package.get('guid') doc[ITEM_STATE] = CONTENT_STATE.SUBMITTED doc[config.VERSION] = 1 for field in package: if field not in PACKAGE_FIELDS: doc[field] = package[field] items = [] for group in package.get('groups', []): for ref in group.get('refs', []): if 'residRef' in ref: item = service.find_one(req=None, _id=ref.get('residRef')) if item: if not (export or preview) and \ (item.get('lock_session') or item.get('state') != 'published'): message = 'Locked or not published items in highlight list.' raise SuperdeskApiError.forbiddenError(message) items.append(item) if not preview: app.on_archive_item_updated( {'highlight_id': package.get('highlight'), 'highlight_name': get_highlight_name(package.get('highlight'))}, item, ITEM_EXPORT_HIGHLIGHT) if stringTemplate: doc['body_html'] = render_template_string(stringTemplate, package=package, items=items) else: doc['body_html'] = render_template('default_highlight_template.txt', package=package, items=items) if preview: return ['' for doc in docs] else: ids = service.post(docs, **kwargs) for id in ids: app.on_archive_item_updated( {'highlight_id': package.get('highlight'), 'highlight_name': get_highlight_name(package.get('highlight'))}, {'_id': id}, ITEM_CREATE_HIGHLIGHT) return ids
def check_get_access_privilege(self): if not hasattr(g, 'user'): return privileges = g.user.get('active_privileges', {}) resource_privileges = get_resource_privileges(self.datasource).get('GET', None) if privileges.get(resource_privileges, 0) == 0: raise SuperdeskApiError.forbiddenError()
def on_update(self, updates, original): """ Overriding the method to prevent a user without 'User Management' privilege from changing a role. """ if 'role' in updates and 'active_privileges' in flask.g.user: if not get_resource_privileges('users')['PATCH'] in flask.g.user['active_privileges']: raise SuperdeskApiError.forbiddenError("Insufficient privileges to change the role")
def find_one(self, req, **lookup): item = super().find_one(req, **lookup) if item and str(item.get('task', {}).get('stage', '')) in \ get_resource_service('users').get_invisible_stages_ids(get_user().get('_id')): raise SuperdeskApiError.forbiddenError("User does not have permissions to read the item.") return item
def is_update_allowed(archive_doc): """ Checks if the archive_doc is valid to be updated. If invalid then the method raises ForbiddenError. For instance, a published item shouldn't be allowed to update. """ if archive_doc.get(ITEM_STATE) == CONTENT_STATE.KILLED: raise SuperdeskApiError.forbiddenError("Item isn't in a valid state to be updated.")
def _validate_updates(self, original, updates, user): """Validates updates to the article for the below conditions. If any of these conditions are met then exception is raised: 1. Is article locked by another user other than the user requesting for update 2. Is state of the article is Killed or Recalled? 3. Is user trying to update the package with Public Service Announcements? 4. Is user authorized to update unique name of the article? 5. Is user trying to update the genre of a broadcast article? 6. Is article being scheduled and is in a package? 7. Is article being scheduled and schedule timestamp is invalid? 8. Does article has valid crops if the article type is a picture? 9. Is article a valid package if the article type is a package? 10. Does article has a valid Embargo? 11. Make sure that there are no duplicate anpa_category codes in the article. 12. Make sure there are no duplicate subjects in the upadte 13. Item is on readonly stage. :raises: SuperdeskApiError.forbiddenError() - if state of the article is killed or user is not authorized to update unique name or if article is locked by another user SuperdeskApiError.badRequestError() - if Public Service Announcements are being added to a package or genre is being updated for a broadcast, is invalid for scheduling, the updates contain duplicate anpa_category or subject codes """ updated = original.copy() updated.update(updates) self._test_readonly_stage(original, updates) lock_user = original.get('lock_user', None) force_unlock = updates.get('force_unlock', False) str_user_id = str(user.get(config.ID_FIELD)) if user else None if lock_user and str(lock_user) != str_user_id and not force_unlock: raise SuperdeskApiError.forbiddenError( _('The item was locked by another user')) if original.get(ITEM_STATE) in { CONTENT_STATE.KILLED, CONTENT_STATE.RECALLED }: raise SuperdeskApiError.forbiddenError( _("Item isn't in a valid state to be updated.")) if updates.get('body_footer') and is_normal_package(original): raise SuperdeskApiError.badRequestError( _("Package doesn't support Public Service Announcements")) if 'unique_name' in updates and not is_admin(user) \ and (user['active_privileges'].get('metadata_uniquename', 0) == 0) \ and not force_unlock: raise SuperdeskApiError.forbiddenError( _("Unauthorized to modify Unique Name")) # if broadcast then update to genre is not allowed. if original.get('broadcast') and updates.get('genre') and \ any(genre.get('qcode', '').lower() != BROADCAST_GENRE.lower() for genre in updates.get('genre')): raise SuperdeskApiError.badRequestError( _('Cannot change the genre for broadcast content.')) if PUBLISH_SCHEDULE in updates or "schedule_settings" in updates: if is_item_in_package(original) and not force_unlock: raise SuperdeskApiError.badRequestError( _('This item is in a package and it needs to be removed before the item can be scheduled!' )) update_schedule_settings(updated, PUBLISH_SCHEDULE, updated.get(PUBLISH_SCHEDULE)) if updates.get(PUBLISH_SCHEDULE): validate_schedule( updated.get(SCHEDULE_SETTINGS, {}).get('utc_{}'.format(PUBLISH_SCHEDULE))) updates[SCHEDULE_SETTINGS] = updated.get(SCHEDULE_SETTINGS, {}) if original[ITEM_TYPE] == CONTENT_TYPE.COMPOSITE: self.packageService.on_update(updates, original) if original[ITEM_TYPE] == CONTENT_TYPE.PICTURE and not force_unlock: CropService().validate_multiple_crops(updates, original) # update the embargo date update_schedule_settings(updated, EMBARGO, updated.get(EMBARGO)) # Do the validation after Circular Reference check passes in Package Service if not force_unlock: self.validate_embargo(updated) if EMBARGO in updates or "schedule_settings" in updates: updates[SCHEDULE_SETTINGS] = updated.get(SCHEDULE_SETTINGS, {}) # Ensure that there are no duplicate categories in the update category_qcodes = [ q['qcode'] for q in updates.get('anpa_category', []) or [] ] if category_qcodes and len(category_qcodes) != len( set(category_qcodes)): raise SuperdeskApiError.badRequestError( _("Duplicate category codes are not allowed")) # Ensure that there are no duplicate subjects in the update subject_qcodes = [q['qcode'] for q in updates.get('subject', []) or []] if subject_qcodes and len(subject_qcodes) != len(set(subject_qcodes)): raise SuperdeskApiError.badRequestError( _("Duplicate subjects are not allowed"))
def on_delete(self, doc): events_using_file = get_resource_service("events").find( where={'files': doc.get("_id")}) if events_using_file.count() > 0: raise SuperdeskApiError.forbiddenError( 'Delete failed. File still used by other events.')
def on_create(self, docs): subscription = SUBSCRIPTION_LEVEL if subscription in SUBSCRIPTION_MAX_THEMES: all = self.find() if (all.count() + len(docs) > SUBSCRIPTION_MAX_THEMES[subscription]): raise SuperdeskApiError.forbiddenError(message='Cannot add another theme.')
def unlock(self, item_filter, user_id, session_id, etag): item_model = get_model(ItemModel) item = item_model.find_one(item_filter) if not item: raise SuperdeskApiError.notFoundError() if not item.get(LOCK_USER): raise SuperdeskApiError.badRequestError( message=_("Item is not locked.")) can_user_unlock, error_message = self.can_unlock(item, user_id) if can_user_unlock: self.app.on_item_unlock(item, user_id) updates = {} # delete the item if nothing is saved so far # version 0 created on lock item if item.get(config.VERSION, 0) == 0 and item[ITEM_STATE] == CONTENT_STATE.DRAFT: if item.get(ITEM_TYPE) == CONTENT_TYPE.COMPOSITE: # if item is composite then update referenced items in package. PackageService().update_groups({}, item) superdesk.get_resource_service('archive').delete_action( lookup={'_id': item['_id']}) push_content_notification([item]) else: updates = { LOCK_USER: None, LOCK_SESSION: None, LOCK_TIME: None, LOCK_ACTION: None, 'force_unlock': True } autosave = superdesk.get_resource_service( 'archive_autosave').find_one(req=None, _id=item['_id']) if autosave and item[ITEM_STATE] not in PUBLISH_STATES: if not hasattr( flask.g, 'user'): # user is not set when session expires flask.g.user = superdesk.get_resource_service( 'users').find_one(req=None, _id=user_id) autosave.update(updates) resolve_document_version(autosave, 'archive', 'PATCH', item) superdesk.get_resource_service('archive').patch( item['_id'], autosave) item = superdesk.get_resource_service('archive').find_one( req=None, _id=item['_id']) insert_versioning_documents('archive', item) else: item_model.update(item_filter, updates) item = item_model.find_one(item_filter) self.app.on_item_unlocked(item, user_id) push_unlock_notification(item, user_id, session_id) else: raise SuperdeskApiError.forbiddenError(message=error_message) return item
def send_to(doc, update=None, desk_id=None, stage_id=None, user_id=None, default_stage='incoming_stage'): """Send item to given desk and stage. Applies the outgoing and incoming macros of current and destination stages :param doc: original document to be sent :param update: updates for the document :param desk: id of desk where item should be sent :param stage: optional stage within the desk :param default_stage: if no stage_id is passed then it determines the stage in that desk the doc is assigned, either the the incomming stage or the working stage. """ original_task = doc.setdefault('task', {}) current_stage = None if original_task.get('stage'): current_stage = get_resource_service('stages').find_one( req=None, _id=original_task.get('stage')) desk = destination_stage = None task = { 'desk': desk_id, 'stage': stage_id, 'user': original_task.get('user') if user_id is None else user_id } if current_stage: apply_stage_rule(doc, update, current_stage, MACRO_OUTGOING) if desk_id: desk = superdesk.get_resource_service('desks').find_one(req=None, _id=desk_id) if not desk: raise SuperdeskApiError.notFoundError( _('Invalid desk identifier {desk_id}').format(desk_id=desk_id)) if not current_user_has_privilege('move') and \ str(user_id) not in [str(x.get('user', '')) for x in desk.get('members', [])]: raise SuperdeskApiError.forbiddenError( _('User is not member of desk: {desk_id}').format( desk_id=desk_id)) task['desk'] = desk_id if not stage_id: task['stage'] = desk.get(default_stage) destination_stage = get_resource_service('stages').find_one( req=None, _id=desk.get(default_stage)) if stage_id: destination_stage = get_resource_service('stages').find_one( req=None, _id=stage_id) if not destination_stage: raise SuperdeskApiError.notFoundError( _('Invalid stage identifier {stage_id}').format( stage_id=stage_id)) task['desk'] = destination_stage['desk'] task['stage'] = stage_id if destination_stage: apply_stage_rule(doc, update, destination_stage, MACRO_INCOMING, desk=desk, task=task) if destination_stage.get('task_status'): task['status'] = destination_stage['task_status'] if update: update.setdefault('task', {}) update['task'].update(task) update['expiry'] = get_item_expiry(desk=desk, stage=destination_stage) else: doc['task'].update(task) doc['expiry'] = get_item_expiry(desk=desk, stage=destination_stage) superdesk.get_resource_service('desks').apply_desk_metadata(doc, doc)
def _validate(self, doc): assignments_service = get_resource_service('assignments') assignment = assignments_service.find_one(req=None, _id=doc.get('assignment_id')) user = get_user(required=True) user_id = user.get(config.ID_FIELD) session = get_auth() session_id = session.get(config.ID_FIELD) if not assignment: raise SuperdeskApiError.badRequestError('Assignment not found.') if not assignments_service.is_text_assignment(assignment): raise SuperdeskApiError.badRequestError( 'Cannot unlink media assignments.') if assignment.get(LOCK_USER): if str(assignment.get(LOCK_USER)) != str(user_id): raise SuperdeskApiError.forbiddenError( 'Assignment is locked by another user. Cannot unlink assignment and content.' ) if str(assignment.get(LOCK_SESSION)) != str(session_id): raise SuperdeskApiError.forbiddenError( 'Assignment is locked by you in another session. Cannot unlink assignment and content.' ) item = get_resource_service('archive').find_one(req=None, _id=doc.get('item_id')) # try looking in the archived content if not item: item = get_resource_service('archived').find_one( req=None, _id=doc.get('item_id')) if not item: raise SuperdeskApiError.badRequestError( 'Content item not found.') # If the item is locked, then check to see if it is locked by the # current user in their current session if item.get(LOCK_USER): if str(item.get(LOCK_USER)) != str(user_id): raise SuperdeskApiError.forbiddenError( 'Item is locked by another user. Cannot unlink assignment and content.' ) if str(item.get(LOCK_SESSION)) != str(session_id): raise SuperdeskApiError.forbiddenError( 'Item is locked by you in another session. Cannot unlink assignment and content.' ) if not item.get('assignment_id'): raise SuperdeskApiError.badRequestError( 'Content not linked to an assignment. Cannot unlink assignment and content.' ) if str(item.get('assignment_id')) != str( assignment.get(config.ID_FIELD)): raise SuperdeskApiError.badRequestError( 'Assignment and Content are not linked.') deliveries = get_resource_service('delivery').get( req=None, lookup={'assignment_id': assignment.get(config.ID_FIELD)}) # Match the passed item_id in doc or if the item is archived the archived item_id delivery = [ d for d in deliveries if d.get('item_id') == item.get('item_id', doc.get('item_id')) ] if len(delivery) <= 0: raise SuperdeskApiError.badRequestError( 'Content doesnt exist for the assignment. Cannot unlink assignment and content.' )
def _validate_user(self, doc_user_id, doc_is_global): session_user = get_user(required=True) if str(session_user['_id']) != doc_user_id and \ not (current_user_has_privilege('global_saved_searches') and doc_is_global): raise SuperdeskApiError.forbiddenError( 'Unauthorized to modify global search.')
def lock(self, item, user_id, session_id, action, resource): if not item: raise SuperdeskApiError.notFoundError() item_service = get_resource_service(resource) item_id = item.get(config.ID_FIELD) # lock_id will be: # 1 - Recurrence Id for items part of recurring series (event or planning) # 2 - event_item for planning with associated event # 3 - item's _id for all other cases lock_id_field = config.ID_FIELD if item.get('recurrence_id'): lock_id_field = 'recurrence_id' elif item.get('type') != 'event' and item.get('event_item'): lock_id_field = 'event_item' # set the lock_id it per item lock_id = "item_lock {}".format(item.get(lock_id_field)) # get the lock it not raise forbidden exception if not lock(lock_id, expire=5): raise SuperdeskApiError.forbiddenError( message="Item is locked by another user.") try: can_user_lock, error_message = self.can_lock( item, user_id, session_id, resource) if can_user_lock: # following line executes handlers attached to function: # on_lock_'resource' - ex. on_lock_planning, on_lock_event getattr(self.app, 'on_lock_%s' % resource)(item, user_id) updates = { LOCK_USER: user_id, LOCK_SESSION: session_id, 'lock_time': utcnow() } if action: updates['lock_action'] = action item_service.update(item.get(config.ID_FIELD), updates, item) push_notification(resource + ':lock', item=str(item.get(config.ID_FIELD)), user=str(user_id), lock_time=updates['lock_time'], lock_session=str(session_id), lock_action=updates.get('lock_action'), etag=updates['_etag']) else: raise SuperdeskApiError.forbiddenError(message=error_message) item = item_service.find_one(req=None, _id=item_id) # following line executes handlers attached to function: # on_locked_'resource' - ex. on_locked_planning, on_locked_event getattr(self.app, 'on_locked_%s' % resource)(item, user_id) return item finally: # unlock the lock :) unlock(lock_id, remove=True)
def on_update(self, updates, original): updates[ITEM_OPERATION] = ITEM_UPDATE is_update_allowed(original) user = get_user() if 'publish_schedule' in updates and original['state'] == 'scheduled': # this is an deschedule action self.deschedule_item(updates, original) # check if there is a takes package and deschedule the takes package. package = TakesPackageService().get_take_package(original) if package and package.get('state') == 'scheduled': package_updates = { 'publish_schedule': None, 'groups': package.get('groups') } self.patch(package.get(config.ID_FIELD), package_updates) return if updates.get('publish_schedule'): if datetime.datetime.fromtimestamp(0).date() == updates.get( 'publish_schedule').date(): # publish_schedule field will be cleared updates['publish_schedule'] = None else: # validate the schedule if is_item_in_package(original): raise SuperdeskApiError.\ badRequestError(message='This item is in a package' + ' it needs to be removed before the item can be scheduled!') package = TakesPackageService().get_take_package( original) or {} validate_schedule(updates.get('publish_schedule'), package.get(SEQUENCE, 1)) if 'unique_name' in updates and not is_admin(user) \ and (user['active_privileges'].get('metadata_uniquename', 0) == 0): raise SuperdeskApiError.forbiddenError( "Unauthorized to modify Unique Name") remove_unwanted(updates) if self.__is_req_for_save(updates): update_state(original, updates) lock_user = original.get('lock_user', None) force_unlock = updates.get('force_unlock', False) updates.setdefault('original_creator', original.get('original_creator')) str_user_id = str(user.get('_id')) if user else None if lock_user and str(lock_user) != str_user_id and not force_unlock: raise SuperdeskApiError.forbiddenError( 'The item was locked by another user') updates['versioncreated'] = utcnow() set_item_expiry(updates, original) updates['version_creator'] = str_user_id set_sign_off(updates, original=original) update_word_count(updates) if force_unlock: del updates['force_unlock'] # create crops crop_service = ArchiveCropService() crop_service.validate_multiple_crops(updates, original) crop_service.create_multiple_crops(updates, original) if original[ITEM_TYPE] == CONTENT_TYPE.COMPOSITE: self.packageService.on_update(updates, original) update_version(updates, original) # Do the validation after Circular Reference check passes in Package Service updated = original.copy() updated.update(updates) self.validate_embargo(updated)
def _check_max_active(self, increment): subscription = SUBSCRIPTION_LEVEL if subscription in SUBSCRIPTION_MAX_ACTIVE_BLOGS: active = self.find({'blog_status': 'open'}) if active.count() + increment > SUBSCRIPTION_MAX_ACTIVE_BLOGS[subscription]: raise SuperdeskApiError.forbiddenError(message='Cannot add another active blog.')
def on_delete(self, doc): if self.backend.find_one('ingest_providers', req=None, rule_set=doc['_id']): raise SuperdeskApiError.forbiddenError('rule set is in use')
def check_all_groups_have_id_set(self, groups): if any(group for group in groups if not group.get('id')): message = 'Group is missing id.' logger.error(message) raise SuperdeskApiError.forbiddenError(message=message)
def check_permissions(self, resource, method, user): """Checks user permissions. 1. If there's no user associated with the request or HTTP Method is GET or the Resource is a Flask Blueprint then return True. 2. Get User's Privileges 3. Intrinsic Privileges: Check if resource has intrinsic privileges. If it has then check if HTTP Method is allowed. Return True if `is_authorized()` on the resource service returns True. Otherwise, raise ForbiddenError. HTTP Method not allowed continue No intrinsic privileges continue 4. User's Privileges Get Resource Privileges and validate it against user's privileges. Return True if validation is successful. Otherwise continue. 5. If method didn't return True, then user is not authorized to perform the requested operation on the resource. """ # Step 1: if not user: return True if resource == "_blueprint": return True # Step 2: Get User's Privileges get_resource_service("users").set_privileges(user, flask.g.role) try: resource_privileges = get_resource_privileges(resource).get( method, None) except KeyError: resource_privileges = None if method == "GET" and not resource_privileges: return True # Step 3: Intrinsic Privileges message = _("Insufficient privileges for the requested operation.") intrinsic_privileges = get_intrinsic_privileges() if intrinsic_privileges.get( resource) and method in intrinsic_privileges[resource]: service = get_resource_service(resource) authorized = service.is_authorized( user_id=str(user.get("_id")), _id=request.view_args.get("_id"), method=method) if not authorized: raise SuperdeskApiError.forbiddenError(message=message) return authorized # Step 4: User's privileges privileges = user.get("active_privileges", {}) if not resource_privileges and get_no_resource_privileges(resource): return True if privileges.get(resource_privileges, False): return True # Step 5: raise SuperdeskApiError.forbiddenError(message=message)