def update_cycle_task_child_state(obj): """Update child attributes state of cycle task Args: obj: Cycle task instance """ status_order = (None, 'Assigned', 'InProgress', 'Declined', 'Finished', 'Verified') status = obj.status children_attrs = _cycle_task_children_attr.get(type(obj), []) for children_attr in children_attrs: if children_attr: children = getattr(obj, children_attr, None) for child in children: if status == 'Declined' or \ status_order.index(status) > status_order.index(child.status): if is_allowed_update(child.__class__.__name__, child.id, child.context.id): old_status = child.status child.status = status db.session.add(child) Signals.status_change.send(child.__class__, obj=child, new_status=child.status, old_status=old_status) update_cycle_task_child_state(child)
def update_cycle_task_child_state(obj): """Update child attributes state of cycle task Args: obj: Cycle task instance """ status_order = (None, 'Assigned', obj.IN_PROGRESS, 'Declined', 'Finished', 'Verified') status = obj.status children_attrs = _cycle_task_children_attr.get(type(obj), []) for children_attr in children_attrs: if children_attr: children = getattr(obj, children_attr, None) for child in children: if status == 'Declined' or \ status_order.index(status) > status_order.index(child.status): if is_allowed_update(child.__class__.__name__, child.id, child.context.id): old_status = child.status child.status = status Signals.status_change.send( child.__class__, objs=[ Signals.StatusChangeSignalObjectContext( instance=child, new_status=child.status, old_status=old_status, ) ] ) update_cycle_task_child_state(child)
def _check_post_permissions(self, objects): for obj in objects: if obj.type == "Snapshot" and\ not permissions.is_allowed_update(obj.child_type, obj.child_id, obj.context_id): db.session.expunge_all() raise Forbidden() super(RelationshipResource, self)._check_post_permissions(objects)
def contribute_to_program_view(sender, obj=None, context=None): if obj.context_id != None and \ permissions.is_allowed_read('Role', 1) and \ permissions.is_allowed_read('UserRole', obj.context_id) and \ permissions.is_allowed_create('UserRole', obj.context_id) and \ permissions.is_allowed_update('UserRole', obj.context_id) and \ permissions.is_allowed_delete('UserRole', obj.context_id): return 'permissions/programs/_role_assignments.haml' return None
def contribute_to_program_view(sender, obj=None, context=None): if obj.context_id is not None and \ rbac_permissions.is_allowed_read('Role', None, 1) and \ rbac_permissions.is_allowed_read('UserRole', None, obj.context_id) and \ rbac_permissions.is_allowed_create('UserRole', None, obj.context_id) and \ rbac_permissions.is_allowed_update('UserRole', None, obj.context_id) and \ rbac_permissions.is_allowed_delete('UserRole', None, obj.context_id): return 'permissions/programs/_role_assignments.haml' return None
def generate_automappings(self, relationship): """Generate Automappings for a given relationship""" # pylint: disable=protected-access self.auto_mappings = set() with benchmark("Automapping generate_automappings"): # initial relationship is special since it is already created and # processing it would abort the loop so we manually enqueue the # neighborhood src = Stub.from_source(relationship) dst = Stub.from_destination(relationship) self._step(src, dst) self._step(dst, src) while self.queue: if len(self.auto_mappings) > self.COUNT_LIMIT: break src, dst = entry = self.queue.pop() if {src.type, dst.type} != {"Audit", "Issue"}: # Auditor doesn't have edit (+map) permission on the Audit, # but the Auditor should be allowed to Raise an Issue. # Since Issue-Assessment-Audit is the only rule that # triggers Issue to Audit mapping, we should skip the # permission check for it if not (permissions.is_allowed_update( src.type, src.id, None) and permissions.is_allowed_update( dst.type, dst.id, None)): continue created = self._ensure_relationship(src, dst) self.processed.add(entry) if not created: # If the edge already exists it means that auto mappings for it have # already been processed and it is safe to cut here. continue self._step(src, dst) self._step(dst, src) if len(self.auto_mappings) <= self.COUNT_LIMIT: self._flush(relationship) else: relationship._json_extras = { # pylint: disable=protected-access 'automapping_limit_exceeded': True }
def generate_automappings(self, relationship): """Generate Automappings for a given relationship""" self.auto_mappings = set() # initial relationship is special since it is already created and # processing it would abort the loop so we manually enqueue the # neighborhood src = Stub.from_source(relationship) dst = Stub.from_destination(relationship) self._step(src, dst) self._step(dst, src) while self.queue: if len(self.auto_mappings) > self.COUNT_LIMIT: break src, dst = entry = self.queue.pop() if {src.type, dst.type} not in self._AUTOMAP_WITHOUT_PERMISSION: # Mapping between some objects should be created even if there is no # permission to edit (+map) this objects. Thus permissions check for # them should be skipped. if not (permissions.is_allowed_update(src.type, src.id, None) and permissions.is_allowed_update( dst.type, dst.id, None)): continue created = self._ensure_relationship(src, dst) self.processed.add(entry) if not created: # If the edge already exists it means that auto mappings for it have # already been processed and it is safe to cut here. continue self._step(src, dst) self._step(dst, src) if len(self.auto_mappings) <= self.COUNT_LIMIT: if self.auto_mappings: logger.info("Automapping count: count=%s", len(self.auto_mappings)) self._flush(relationship) else: logger.error("Automapping limit exceeded: limit=%s, count=%s", self.COUNT_LIMIT, len(self.auto_mappings))
def update_cycle_task_parent_state(obj): # noqa """Propagate changes to obj's parents""" if not is_allowed_update(obj.__class__.__name__, obj.id, obj.context.id): return def update_parent(parent, old_status, new_status): """Update a parent element and emit a signal about the change""" parent.status = new_status db.session.add(parent) Signals.status_change.send( parent.__class__, obj=parent, old_status=old_status, new_status=new_status, ) update_cycle_task_parent_state(parent) parent_attrs = _cycle_task_parent_attr.get(type(obj), []) for parent_attr in parent_attrs: if not parent_attr: continue parent = getattr(obj, parent_attr, None) old_status = parent.status if not parent: continue # Don't propagate changes to CycleTaskGroup if it's a part of backlog wf if isinstance(parent, models.CycleTaskGroup) \ and parent.cycle.workflow.kind == "Backlog": continue # If any child is `InProgress`, then parent should be `InProgress` if obj.status in {"InProgress", "Declined" } and old_status != "InProgress": new_status = "InProgress" update_parent(parent, old_status, new_status) # If all children are `Finished` or `Verified`, then parent should be same elif obj.status in {"Finished", "Verified", "Assigned"}: children_attrs = _cycle_task_children_attr.get(type(parent), []) for children_attr in children_attrs: children = getattr(parent, children_attr, None) if not children: continue children_statues = [c.status for c in children] unique_statuses = set(children_statues) for status in ["Verified", "Finished", "Assigned"]: # Check if all elements match a certain state if children_statues[0] == status and len( unique_statuses) == 1: new_status = status update_parent(parent, old_status, new_status)
def generate_automappings(self, relationship): """Generate Automappings for a given relationship""" # pylint: disable=protected-access self.auto_mappings = set() with benchmark("Automapping generate_automappings"): # initial relationship is special since it is already created and # processing it would abort the loop so we manually enqueue the # neighborhood src = Stub.from_source(relationship) dst = Stub.from_destination(relationship) self._step(src, dst) self._step(dst, src) while self.queue: if len(self.auto_mappings) > self.COUNT_LIMIT: break src, dst = entry = self.queue.pop() if {src.type, dst.type} != {"Audit", "Issue"}: # Auditor doesn't have edit (+map) permission on the Audit, # but the Auditor should be allowed to Raise an Issue. # Since Issue-Assessment-Audit is the only rule that # triggers Issue to Audit mapping, we should skip the # permission check for it if not (permissions.is_allowed_update(src.type, src.id, None) and permissions.is_allowed_update(dst.type, dst.id, None)): continue created = self._ensure_relationship(src, dst) self.processed.add(entry) if not created: # If the edge already exists it means that auto mappings for it have # already been processed and it is safe to cut here. continue self._step(src, dst) self._step(dst, src) if len(self.auto_mappings) <= self.COUNT_LIMIT: self._flush(relationship) else: logger.error("Automapping limit exceeded: limit=%s, count=%s", self.COUNT_LIMIT, len(self.auto_mappings))
def update_cycle_task_parent_state(obj): # noqa """Propagate changes to obj's parents""" if not is_allowed_update(obj.__class__.__name__, obj.id, obj.context.id): return def update_parent(parent, old_status, new_status): """Update a parent element and emit a signal about the change""" parent.status = new_status db.session.add(parent) Signals.status_change.send( parent.__class__, obj=parent, old_status=old_status, new_status=new_status, ) update_cycle_task_parent_state(parent) parent_attrs = _cycle_task_parent_attr.get(type(obj), []) for parent_attr in parent_attrs: if not parent_attr: continue parent = getattr(obj, parent_attr, None) old_status = parent.status if not parent: continue # Don't propagate changes to CycleTaskGroup if it's a part of backlog wf if isinstance(parent, models.CycleTaskGroup) \ and parent.cycle.workflow.kind == "Backlog": continue # If any child is `InProgress`, then parent should be `InProgress` if obj.status in {"InProgress", "Declined"} and old_status != "InProgress": new_status = "InProgress" update_parent(parent, old_status, new_status) # If all children are `Finished` or `Verified`, then parent should be same elif obj.status in {"Finished", "Verified", "Assigned"}: children_attrs = _cycle_task_children_attr.get(type(parent), []) for children_attr in children_attrs: children = getattr(parent, children_attr, None) if not children: continue children_statues = [c.status for c in children] unique_statuses = set(children_statues) for status in ["Verified", "Finished", "Assigned"]: # Check if all elements match a certain state if children_statues[0] == status and len(unique_statuses) == 1: new_status = status update_parent(parent, old_status, new_status)
def _can_map_to(obj, parent_relationship): """True if the current user can edit obj in parent_relationship.context.""" context_id = None if parent_relationship.context: context_id = parent_relationship.context.id elif parent_relationship.context_id: logger.warning( "context is unset but context_id is set on a " "relationship %r: context=%r, context_id=%r", parent_relationship, parent_relationship.context, parent_relationship.context_id) context_id = parent_relationship.context_id return is_allowed_update(obj.type, obj.id, context_id)
def _ensure_has_permissions(obj): """Ensure user has permissions, otherwise raise error""" model_name = obj.__class__.__name__ if permissions.is_allowed_update(model_name, obj.id, obj.context_id): return if permissions.has_conditions('update', model_name): return if permissions.is_allowed_update_for(obj): return raise exceptions.Forbidden()
def _update_parent_state(obj, parent, child_statuses): """Util function, update status of sent parent, if it's allowed. New status based on sent object status and sent child_statuses""" if not is_allowed_update(obj.__class__.__name__, obj.id, obj.context.id): return old_status = parent.status if obj.status in {"InProgress", "Declined"}: new_status = "InProgress" elif obj.status in {"Finished", "Verified", "Assigned"}: in_same_status = len(child_statuses) == 1 new_status = child_statuses.pop() if in_same_status else old_status if old_status == new_status: return parent.status = new_status db.session.add(parent) Signals.status_change.send( parent.__class__, obj=parent, old_status=old_status, new_status=new_status, )
def update_cycle_task_child_state(obj): status_order = (None, 'Assigned', 'InProgress', 'Declined', 'Finished', 'Verified') status = obj.status children_attrs = _cycle_task_children_attr.get(type(obj), []) for children_attr in children_attrs: if children_attr: children = getattr(obj, children_attr, None) for child in children: if status == 'Declined' or \ status_order.index(status) > status_order.index(child.status): if is_allowed_update(child.__class__.__name__, child.id, child.context.id): old_status = child.status child.status = status db.session.add(child) Signals.status_change.send( child.__class__, obj=child, new_status=child.status, old_status=old_status ) update_cycle_task_child_state(child)
def _can_map_to(self, obj, parent_relationship): return is_allowed_update(obj.type, obj.id, parent_relationship.context)
class Resource(ModelView): """View base class for Views handling. Will typically be registered with an application following a collection style for routes. Collection `GET` and `POST` will have a route like `/resources` while collection member resource routes will have routes likej `/resources/<pk:pk_type>`. To register a Resource subclass FooCollection with a Flask application: .. FooCollection.add_to(app, '/foos') By default will only support the `application/json` content-type. """ def dispatch_request(self, *args, **kwargs): method = request.method.lower() if method == 'get': if self.pk in kwargs and kwargs[self.pk] is not None: return self.get(*args, **kwargs) else: return self.collection_get() elif method == 'post': if self.pk in kwargs and kwargs[self.pk] is not None: return self.post(*args, **kwargs) else: return self.collection_post() elif method == 'put': return self.put(*args, **kwargs) elif method == 'delete': return self.delete(*args, **kwargs) else: raise NotImplementedError() def post(*args, **kwargs): raise NotImplementedError() # Default JSON request handlers def get(self, id): obj = self.get_object(id) if obj is None: return self.not_found_response() if 'Accept' in self.request.headers and \ 'application/json' not in self.request.headers['Accept']: return current_app.make_response( ('application/json', 406, [('Content-Type', 'text/plain')])) if not permissions.is_allowed_read(self.model.__name__, obj.context_id): raise Forbidden() object_for_json = self.object_for_json(obj) if 'If-None-Match' in self.request.headers and \ self.request.headers['If-None-Match'] == self.etag(object_for_json): return current_app.make_response( ('', 304, [('Etag', self.etag(object_for_json))])) return self.json_success_response(self.object_for_json(obj), self.modified_at(obj)) def validate_headers_for_put_or_delete(self, obj): missing_headers = [] if 'If-Match' not in self.request.headers: missing_headers.append('If-Match') if 'If-Unmodified-Since' not in self.request.headers: missing_headers.append('If-Unmodified-Since') if missing_headers: # rfc 6585 defines a new status code for missing required headers return current_app.make_response(('If-Match is required.', 428, [ ('Content-Type', 'text/plain') ])) if request.headers['If-Match'] != self.etag(self.object_for_json(obj)) or \ request.headers['If-Unmodified-Since'] != \ self.http_timestamp(self.modified_at(obj)): return current_app.make_response(( 'The resource has been changed. The conflict must be resolved and ' 'the request resubmitted with an up to date Etag for If-Match ' 'header.', 409, [('Content-Type', 'text/plain')])) return None def put(self, id): obj = self.get_object(id) if obj is None: return self.not_found_response() if self.request.headers['Content-Type'] != 'application/json': return current_app.make_response( ('Content-Type must be application/json', 415, [])) header_error = self.validate_headers_for_put_or_delete(obj) if header_error: return header_error src = UnicodeSafeJsonWrapper(self.request.json) root_attribute = self.model._inflector.table_singular try: src = src[root_attribute] except KeyError, e: return current_app.make_response( ('Required attribute "{0}" not found'.format(root_attribute), 400, [])) if not permissions.is_allowed_update(self.model.__name__, obj.context_id): raise Forbidden() ggrc.builder.json.update(obj, src) #FIXME Fake the modified_by_id until we have that information in session. obj.modified_by_id = get_current_user_id() db.session.add(obj) db.session.commit() obj = self.get_object(id) get_indexer().update_record(fts_record_for(obj)) return self.json_success_response(self.object_for_json(obj), self.modified_at(obj))
def _can_map_to(obj, parent_relationship): return is_allowed_update(obj.type, obj.id, parent_relationship.context)