def handle_relationship_creation(session, flush_context): """Create relations for mapped objects.""" # pylint: disable=unused-argument base_objects = defaultdict(set) related_objects = defaultdict(set) snapshot_ids = {} for obj in session.new: if isinstance(obj, all_models.Relationship) and ( issubclass(type(obj.source), Assignable) or issubclass(type(obj.destination), Assignable)): assign_obj, other = obj.source, obj.destination if not issubclass(type(obj.source), Assignable): assign_obj, other = other, assign_obj for acl in assign_obj.access_control_list: acr_id = acl.ac_role.id if acl.ac_role else acl.ac_role_id ac_role = get_custom_roles_for(acl.object_type).get( acr_id, None) if ac_role in assign_obj.ASSIGNEE_TYPES: assign_stub = Stub(assign_obj.type, assign_obj.id) other_stub = Stub(other.type, other.id) base_objects[assign_stub].add(acl) related_objects[assign_stub].add(other_stub) if other.type == "Snapshot": snapshot_ids[other.id] = assign_stub if base_objects: if snapshot_ids: add_related_snapshots(snapshot_ids, related_objects) create_related_roles(base_objects, related_objects)
def _flush(self, parent_relationship): """Manually INSERT generated automappings.""" if not self.auto_mappings: return with benchmark("Automapping flush"): current_user_id = login.get_current_user_id() automapping_result = db.session.execute( Automapping.__table__.insert().values( relationship_id=parent_relationship.id, source_id=parent_relationship.source_id, source_type=parent_relationship.source_type, destination_id=parent_relationship.destination_id, destination_type=parent_relationship.destination_type, modified_by_id=current_user_id, ) ) automapping_id = automapping_result.inserted_primary_key[0] self.automapping_ids.add(automapping_id) now = datetime.utcnow() # We are doing an INSERT IGNORE INTO here to mitigate a race condition # that happens when multiple simultaneous requests create the same # automapping. If a relationship object fails our unique constraint # it means that the mapping was already created by another request # and we can safely ignore it. inserter = Relationship.__table__.insert().prefix_with("IGNORE") original = self.order(Stub.from_source(parent_relationship), Stub.from_destination(parent_relationship)) db.session.execute(inserter.values([{ "id": None, "modified_by_id": current_user_id, "created_at": now, "updated_at": now, "source_id": src.id, "source_type": src.type, "destination_id": dst.id, "destination_type": dst.type, "context_id": None, "status": None, "parent_id": parent_relationship.id, "automapping_id": automapping_id, "is_external": False} for src, dst in self.auto_mappings if (src, dst) != original])) # (src, dst) is sorted self._set_audit_id_for_issues(automapping_id) cache = Cache.get_cache(create=True) if cache: # Add inserted relationships into new objects collection of the cache, # so that they will be logged within event and appropriate revisions # will be created. cache.new.update( (relationship, relationship.log_json()) for relationship in Relationship.query.filter_by( automapping_id=automapping_id, ) )
def _flush(self, parent_relationship): """Manually INSERT generated automappings.""" if not self.auto_mappings: return with benchmark("Automapping flush"): current_user_id = login.get_current_user_id() automapping_result = db.session.execute( Automapping.__table__.insert().values( relationship_id=parent_relationship.id, source_id=parent_relationship.source_id, source_type=parent_relationship.source_type, destination_id=parent_relationship.destination_id, destination_type=parent_relationship.destination_type, ) ) automapping_id = automapping_result.inserted_primary_key[0] self.automapping_ids.add(automapping_id) now = datetime.utcnow() # We are doing an INSERT IGNORE INTO here to mitigate a race condition # that happens when multiple simultaneous requests create the same # automapping. If a relationship object fails our unique constraint # it means that the mapping was already created by another request # and we can safely ignore it. inserter = Relationship.__table__.insert().prefix_with("IGNORE") original = self.order(Stub.from_source(parent_relationship), Stub.from_destination(parent_relationship)) db.session.execute(inserter.values([{ "id": None, "modified_by_id": current_user_id, "created_at": now, "updated_at": now, "source_id": src.id, "source_type": src.type, "destination_id": dst.id, "destination_type": dst.type, "context_id": None, "status": None, "parent_id": parent_relationship.id, "automapping_id": automapping_id, "is_external": False} for src, dst in self.auto_mappings if (src, dst) != original])) # (src, dst) is sorted self._set_audit_id_for_issues(automapping_id) cache = Cache.get_cache(create=True) if cache: # Add inserted relationships into new objects collection of the cache, # so that they will be logged within event and appropriate revisions # will be created. cache.new.update( (relationship, relationship.log_json()) for relationship in Relationship.query.filter_by( automapping_id=automapping_id, ) )
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 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 add_related_snapshots(snapshot_ids, related_objects): """Get Stubs of Objective and Regulations snapshots mapped to snapshot of Control Args: snapshot_ids(dict): Ids of control snapshots with Stub of assigned object. related_objects(dict): Dict of base assigned objects with Stubs of related. """ related_regulations = related_regulation_snaps(snapshot_ids.keys()) for base_snap, related_snap in related_regulations: rel_snap_stub = Stub("Snapshot", related_snap) related_objects[snapshot_ids[base_snap]].add(rel_snap_stub)
def handle_acl_creation(session): """Create relations for mapped objects.""" base_objects = defaultdict(set) for obj in session.new: if isinstance(obj, all_models.AccessControlList): acr_id = obj.ac_role.id if obj.ac_role else obj.ac_role_id acr_name = get_custom_roles_for(obj.object_type).get(acr_id) if acr_name in Assignable.ASSIGNEE_TYPES: base_objects[Stub(obj.object_type, obj.object_id)].add(obj) if base_objects: related_objects = related(base_objects.keys(), RelationshipsCache()) snapshot_ids = collect_snapshot_ids(related_objects) if snapshot_ids: add_related_snapshots(snapshot_ids, related_objects) create_related_roles(base_objects, related_objects)
def handle_relationships(self, propagation, acl): """Hanle relationships""" relationship_cache = self.relationship_cache role_map = self.program_roles acl_manager = self.access_control_list_manager program_stub = Stub(acl.object_type, acl.object_id) related_stubs = related([program_stub], relationship_cache) for stub in related_stubs[program_stub]: if not (propagation["type"] == "any" or stub.type in propagation["type"].split(",")): continue role_id = role_map[ROLE_PROPAGATION[self._get_acr_name(acl)]] child = acl_manager.get_or_create(stub, acl, acl.person, role_id) if "propagate" in propagation: self.handle_propagation(propagation["propagate"], child)
def _create_mapped_acls(self, acl, role_map): """Helper to propagate roles for auditors and captains""" audit = acl.object assert isinstance(audit, all_models.Audit), \ "`{}` role assigned to a non Audit object.".format(acl.ac_role.name) # Add Audit Captains Mapped role to all the objects in the audit snapshots_cache = self.caches["snapshots_cache"] acl_manager = self.caches["access_control_list_manager"] relationship_cache = self.caches["relationship_cache"] if audit.id not in snapshots_cache: snapshots_cache[audit.id] = all_models.Snapshot.query.filter( all_models.Snapshot.parent_id == audit.id, all_models.Snapshot.parent_type == "Audit").options( load_only("id")).all() for snapshot in snapshots_cache[audit.id]: acl_manager.get_or_create(snapshot, acl, acl.person, role_map["Snapshot"]) # Add Audit Captains Mapped to all related audit_stub = Stub(acl.object_type, acl.object_id) related_stubs = related([audit_stub], relationship_cache) for stub in related_stubs[audit_stub]: if stub.type not in ("Assessment", "AssessmentTemplate", "Issue", "Comment", "Document"): continue acl_manager.get_or_create(stub, acl, acl.person, role_map[stub.type]) # Add Audit Captains Mapped to all realted comments and documents mapped_stubs = related(related_stubs[audit_stub], relationship_cache) for parent in mapped_stubs: for stub in mapped_stubs[parent]: if stub.type not in ("Comment", "Document"): continue acl_manager.get_or_create(stub, acl, acl.person, role_map[stub.type])