def delete(self, sketch_id, view_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model view_id: Integer primary key for a view database model """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') view = View.query.get(view_id) if not view: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No view found with this ID.') # Check that this view belongs to the sketch if view.sketch_id != sketch.id: abort( HTTP_STATUS_CODE_NOT_FOUND, 'The view does not belong to the sketch ({0:d} vs ' '{1:d})'.format(view.sketch_id, sketch.id)) if not sketch.has_permission(user=current_user, permission='write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write permission on sketch.') view.set_status(status='deleted') # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def delete(self, sketch_id, group_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model. group_id: Integer primary key for an aggregation group database model. """ sketch = Sketch.query.get_with_acl(sketch_id) group = AggregationGroup.query.get(group_id) if not group: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No Group found with this ID.') if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') # Check that this group belongs to the sketch if group.sketch_id != sketch.id: msg = ('The sketch ID ({0:d}) does not match with the aggregation ' 'group sketch ID ({1:d})'.format(sketch.id, group.sketch_id)) abort(HTTP_STATUS_CODE_FORBIDDEN, msg) if not sketch.has_permission(user=current_user, permission='write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') db_session.delete(group) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def post(self, sketch_id): """Handles POST request to the resource. Args: sketch_id: Integer primary key for a sketch database model Returns: A view in JSON (instance of flask.wrappers.Response) """ form = forms.SaveViewForm.build(request) if not form.validate_on_submit(): abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to save view, not able to validate form data.') sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') view = self.create_view_from_form(sketch, form) # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(view, status_code=HTTP_STATUS_CODE_CREATED)
def post(self, sketch_id): """Handles POST request to the resource. Args: sketch_id: Integer primary key for a sketch database model Returns: An aggregation in JSON (instance of flask.wrappers.Response) """ form = request.json if not form: form = request.data if not form: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to validate form data.') sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(user=current_user, permission='write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') aggregation = self.create_aggregation_from_form(sketch, form) # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(aggregation, status_code=HTTP_STATUS_CODE_CREATED)
def get(self, sketch_id, group_id): """Handles GET request to the resource. Args: sketch_id: Integer primary key for a sketch database model. group_id: Integer primary key for an aggregation group database """ sketch = Sketch.query.get_with_acl(sketch_id) group = AggregationGroup.query.get(group_id) if not group: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No Group found with this ID.') if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(user=current_user, permission='read'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have read permission on the sketch.') # Check that this group belongs to the sketch if group.sketch_id != sketch.id: msg = ('The sketch ID ({0:d}) does not match with the aggregation ' 'group sketch ID ({1:d})'.format(sketch.id, group.sketch_id)) abort(HTTP_STATUS_CODE_FORBIDDEN, msg) _, objects, meta = utils.run_aggregator_group(group, sketch_id=sketch.id) schema = {'meta': meta, 'objects': objects} # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return jsonify(schema)
def delete(self, sketch_id, story_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model story_id: Integer primary key for a story database model """ sketch = Sketch.query.get_with_acl(sketch_id) story = Story.query.get(story_id) if not story: msg = 'No Story found with this ID.' abort(HTTP_STATUS_CODE_NOT_FOUND, msg) if not sketch: msg = 'No sketch found with this ID.' abort(HTTP_STATUS_CODE_NOT_FOUND, msg) # Check that this timeline belongs to the sketch if story.sketch_id != sketch.id: msg = ('The sketch ID ({0:d}) does not match with the story' 'sketch ID ({1:d})'.format(sketch.id, story.sketch_id)) abort(HTTP_STATUS_CODE_FORBIDDEN, msg) if not sketch.has_permission(user=current_user, permission='write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') sketch.stories.remove(story) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def post(self, sketch_id): """Handles POST request to the resource. Args: sketch_id: Integer primary key for a sketch database model Returns: A view in JSON (instance of flask.wrappers.Response) """ form = forms.StoryForm.build(request) if not form.validate_on_submit(): abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to validate form data.') sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') title = '' if form.title.data: title = form.title.data story = Story(title=title, content='[]', sketch=sketch, user=current_user) db_session.add(story) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(story, status_code=HTTP_STATUS_CODE_CREATED)
def get(self, sketch_id): """Handles GET request to the resource. Handler for /api/v1/sketches/<int:sketch_id>/aggregation/ Args: sketch_id: Integer primary key for a sketch database model Returns: Views in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'read'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have read access controls on sketch.') aggregations = sketch.get_named_aggregations # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(aggregations)
def post(self, sketch_id): """Handles POST request to the resource. Args: sketch_id: Integer primary key for a sketch database model Returns: An aggregation in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(user=current_user, permission='write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') form = request.json if not form: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'No JSON data, unable to process request to create ' 'a new aggregation group.') aggregation_string = form.get('aggregations', '') if not aggregation_string: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to create an empty group.') agg_list = json.loads(aggregation_string) if not isinstance(agg_list, (list, tuple)): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Aggregations needs to be a list of IDs.') named_aggs = sketch.get_named_aggregations aggregations = [agg for agg in named_aggs if agg.id in agg_list] # Create the aggregation in the database aggregation_group = AggregationGroup( name=form.get('name', 'No Group Name'), description=form.get('description', ''), parameters=form.get('parameters', ''), aggregations=aggregations, orientation=form.get('orientation', 'layer'), user=current_user, sketch=sketch, view=form.get('view_id') ) db_session.add(aggregation_group) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json( aggregation_group, status_code=HTTP_STATUS_CODE_CREATED)
def post(self): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) """ upload_enabled = current_app.config['UPLOAD_ENABLED'] if not upload_enabled: abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Upload not enabled') form = request.get_data(parse_form_data=True) if not form: form = request.form sketch_id = form.get('sketch_id', None) if not sketch_id: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to upload data without supplying a ' 'sketch to associated it with.') if not isinstance(sketch_id, int): sketch_id = int(sketch_id) sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if sketch.get_status.status == 'archived': abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to upload a file to an archived sketch.') if not sketch.has_permission(current_user, 'write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'Unable to upload data to a sketch, user does not have ' 'write access.') utils.update_sketch_last_activity(sketch) index_name = form.get('index_name', '') file_storage = request.files.get('file') if file_storage: chunk_index_name = form.get('chunk_index_name', uuid.uuid4().hex) return self._upload_file( file_storage=file_storage, chunk_index_name=chunk_index_name, form=form, sketch=sketch, index_name=index_name) events = form.get('events') if not events: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to upload data, no file uploaded nor any events.') return self._upload_events( events=events, form=form, sketch=sketch, index_name=index_name)
def post(self, sketch_id): """Handles POST request to the resource. Returns: Graph in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') form = request.json plugin_name = form.get('plugin') graph_config = form.get('config') refresh = form.get('refresh') sketch_indices = [ timeline.searchindex.index_name for timeline in sketch.active_timelines ] cache = GraphCache.get_or_create( sketch=sketch, graph_plugin=plugin_name) if graph_config: cache_config = graph_config else: cache_config = cache.graph_config if isinstance(cache_config, str): cache_config = json.loads(cache_config) # Refresh cache if timelines have been added/removed from the sketch. if cache_config: cache_graph_filter = cache_config.get('filter', {}) cache_filter_indices = cache_graph_filter.get('indices', []) if set(sketch_indices) ^ set(cache_filter_indices): refresh = True if cache.graph_elements and not refresh: return self.to_json(cache) graph_class = manager.GraphManager.get_graph(plugin_name) graph = graph_class(sketch=sketch) cytoscape_json = graph.generate().to_cytoscape() if cytoscape_json: cache.graph_elements = json.dumps(cytoscape_json) cache.graph_config = json.dumps(graph_config) cache.update_modification_time() db_session.add(cache) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(cache)
def post(self, sketch_id, graph_id): """Handles GET request to the resource. Returns: List of graphs in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') graph = Graph.query.get(graph_id) if not graph: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No graph found with this ID.') if not sketch.id == graph.sketch.id: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Graph does not belong to this sketch.') form = request.json if not form: form = request.data name = form.get('name') if name: graph.name = name description = form.get('description') if description: graph.description = description elements = form.get('elements') if elements: graph.graph_elements = json.dumps(elements) graph_config = form.get('graph_config') graph_json = '' if graph_config: if isinstance(graph_config, dict): graph_json = json.dumps(graph_config) elif isinstance(graph_config, str): graph_json = graph_config if graph_json: graph.graph_config = graph_json db_session.add(graph) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(graph, status_code=HTTP_STATUS_CODE_CREATED)
def post(self, sketch_id, story_id): """Handles POST request to the resource. Args: sketch_id: Integer primary key for a sketch database model story_id: Integer primary key for a story database model Returns: A view in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) story = Story.query.get(story_id) if not story: msg = "No Story found with this ID." abort(HTTP_STATUS_CODE_NOT_FOUND, msg) if not sketch: msg = "No sketch found with this ID." abort(HTTP_STATUS_CODE_NOT_FOUND, msg) if story.sketch_id != sketch.id: abort( HTTP_STATUS_CODE_NOT_FOUND, "Sketch ID ({0:d}) does not match with the ID in " "the story ({1:d})".format(sketch.id, story.sketch_id), ) if not sketch.has_permission(current_user, "write"): abort( HTTP_STATUS_CODE_FORBIDDEN, "User does not have write access controls on sketch.", ) form = request.json if not form: form = request.data if form and form.get("export_format"): export_format = form.get("export_format") return jsonify( story=self._export_story( story=story, sketch_id=sketch_id, export_format=export_format ) ) story.title = form.get("title", "") story.content = form.get("content", "[]") db_session.add(story) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(story, status_code=HTTP_STATUS_CODE_CREATED)
def get(self, sketch_id, graph_id): """Handles GET request to the resource. Returns: List of graphs in JSON (instance of flask.wrappers.Response) """ args = self.parser.parse_args() output_format = args.get('format', None) sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') graph = Graph.query.get(graph_id) if not graph: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No graph found with this ID.') if not sketch.id == graph.sketch.id: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Graph does not belong to this sketch.') # If requested use a format suitable for the CytoscapeJS frontend. if output_format == 'cytoscape': response = self.to_json(graph).json response['objects'][0]['graph_elements'] = graph.graph_elements return jsonify(response) # Reformat elements to work with networkx python library. # TODO: Change frontend to save directed and multigraph attributes. graph_elements = json.loads(graph.graph_elements) formatted_graph = { 'data': [], 'directed': True, 'multigraph': True, 'elements': { 'nodes': [], 'edges': [] } } for element in graph_elements: group = element['group'] element_data = {'data': element['data']} formatted_graph['elements'][group].append(element_data) response = self.to_json(graph).json response['objects'][0]['graph_elements'] = formatted_graph response['objects'][0]['graph_config'] = graph.graph_config # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return jsonify(response)
def post(self): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) """ upload_enabled = current_app.config['UPLOAD_ENABLED'] if not upload_enabled: abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Upload not enabled') form = request.get_data(parse_form_data=True) if not form: form = request.form sketch_id = form.get('sketch_id', None) if not isinstance(sketch_id, int): sketch_id = int(sketch_id) sketch = None if sketch_id: sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if sketch.get_status.status == 'archived': abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to upload a file to an archived sketch.') index_name = form.get('index_name', uuid.uuid4().hex) if not isinstance(index_name, six.text_type): index_name = codecs.decode(index_name, 'utf-8') file_storage = request.files.get('file') if file_storage: return self._upload_file(file_storage, form, sketch, index_name) events = form.get('events') if not events: abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to upload data, no file uploaded nor any events.') if sketch: # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self._upload_events(events, form, sketch, index_name)
def delete(self, sketch_id, timeline_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model timeline_id: Integer primary key for a timeline database model """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') timeline = Timeline.query.get(timeline_id) # Check that this timeline belongs to the sketch if timeline.sketch_id != sketch.id: if not timeline: msg = 'No timeline found with this ID.' elif not sketch: msg = 'No sketch found with this ID.' else: msg = ( 'The sketch ID ({0:d}) does not match with the timeline ' 'sketch ID ({1:d})'.format(sketch.id, timeline.sketch_id)) abort(HTTP_STATUS_CODE_NOT_FOUND, msg) if not sketch.has_permission(user=current_user, permission='write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') not_delete_labels = current_app.config.get( 'LABELS_TO_PREVENT_DELETION', []) for label in not_delete_labels: if timeline.has_label(label): abort( HTTP_STATUS_CODE_FORBIDDEN, 'Timelines with label [{0:s}] cannot be deleted.'.format( label)) sketch.timelines.remove(timeline) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def post(self, sketch_id): """Handles POST request to the resource.""" sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, "No sketch found with this ID.") if not sketch.has_permission(current_user, "write"): abort( HTTP_STATUS_CODE_FORBIDDEN, "User does not have write access on sketch." ) form = request.json name = form.get("name") description = form.get("description") elements = form.get("elements") graph_config = form.get("graph_config") if graph_config: if isinstance(graph_config, dict): graph_json = json.dumps(graph_config) elif isinstance(graph_config, str): graph_json = graph_config else: graph_json = "" logger.warning("Graph config not of the correct value, not saving.") else: graph_json = "" graph = Graph( user=current_user, sketch=sketch, name=str(name), graph_config=graph_json, description=description, graph_elements=json.dumps(elements), ) db_session.add(graph) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(graph, status_code=HTTP_STATUS_CODE_CREATED)
def get(self, sketch_id, aggregation_id): # pylint: disable=unused-argument """Handles GET request to the resource. Handler for /api/v1/sketches/:sketch_id/aggregation/:aggregation_id Args: sketch_id: Integer primary key for a sketch database model. aggregation_id: Integer primary key for an aggregation database model. Returns: JSON with aggregation results """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, "No sketch found with this ID.") if not sketch.has_permission(current_user, "read"): abort( HTTP_STATUS_CODE_FORBIDDEN, "User does not have read access controls on sketch.", ) aggregation = Aggregation.query.get(aggregation_id) # Check that this aggregation belongs to the sketch if aggregation.sketch_id != sketch.id: abort( HTTP_STATUS_CODE_NOT_FOUND, "The sketch ID ({0:d}) does not match with the defined " "sketch in the aggregation ({1:d})".format( aggregation.sketch_id, sketch.id ), ) # If this is a user state view, check that it # belongs to the current_user if aggregation.name == "" and aggregation.user != current_user: abort( HTTP_STATUS_CODE_FORBIDDEN, ("A user state view can only be viewed by the user it " "belongs to."), ) # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(aggregation)
def get(self, sketch_id, group_id): """Handles GET request to the resource. Args: sketch_id: Integer primary key for a sketch database model. group_id: Integer primary key for an aggregation group database """ sketch = Sketch.query.get_with_acl(sketch_id) group = AggregationGroup.query.get(group_id) if not group: abort(HTTP_STATUS_CODE_NOT_FOUND, "No Group found with this ID.") if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, "No sketch found with this ID.") if not sketch.has_permission(user=current_user, permission="read"): abort( HTTP_STATUS_CODE_FORBIDDEN, "The user does not have read permission on the sketch.", ) # Check that this group belongs to the sketch if group.sketch_id != sketch.id: msg = ( "The sketch ID ({0:d}) does not match with the aggregation " "group sketch ID ({1:d})".format(sketch.id, group.sketch_id) ) abort(HTTP_STATUS_CODE_FORBIDDEN, msg) _, objects, meta = utils.run_aggregator_group(group, sketch_id=sketch.id) group_fields = self.fields_registry[group.__tablename__] group_dict = marshal(group, group_fields) group_dict["agg_ids"] = [a.id for a in group.aggregations] objects[0].update(group_dict) schema = {"meta": meta, "objects": objects} # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return jsonify(schema)
def get(self, sketch_id): """Handles GET request to the resource. Handler for /api/v1/sketches/<int:sketch_id>/aggregation/group/ Args: sketch_id: Integer primary key for a sketch database model Returns: Views in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(user=current_user, permission='read'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have read permission on the sketch.') groups = AggregationGroup.query.filter_by( sketch_id=sketch_id).all() meta = { 'command': 'list_groups', } objects = [] for group in groups: group_dict = { 'id': group.id, 'name': group.name, 'parameters': group.parameters, 'orientation': group.orientation, 'description': group.description, 'agg_ids': json.dumps([x.id for x in group.aggregations]) } objects.append(group_dict) response = jsonify({'meta': meta, 'objects': objects}) response.status_code = HTTP_STATUS_CODE_OK # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return response
def delete(self, sketch_id, graph_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model graph_id: Integer primary key for a graph database model """ sketch = Sketch.query.get_with_acl(sketch_id) graph = Graph.query.get(graph_id) if not graph: msg = "No Graph found with this ID." abort(HTTP_STATUS_CODE_NOT_FOUND, msg) if not sketch: msg = "No sketch found with this ID." abort(HTTP_STATUS_CODE_NOT_FOUND, msg) # Check that this graph belongs to the sketch if graph.sketch.id != sketch.id: msg = ( f"The sketch ID ({sketch.id}) does not match with the story" f"sketch ID ({graph.sketch.id})" ) abort(HTTP_STATUS_CODE_FORBIDDEN, msg) if not sketch.has_permission(user=current_user, permission="write"): abort( HTTP_STATUS_CODE_FORBIDDEN, "The user does not have write permission on the sketch.", ) sketch.graphs.remove(graph) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def delete(self, sketch_id, aggregation_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model. group_id: Integer primary key for an aggregation group database model. """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, "No sketch found with this ID.") aggregation = Aggregation.query.get(aggregation_id) if not aggregation: abort(HTTP_STATUS_CODE_NOT_FOUND, "No aggregation found with this ID.") if not sketch.has_permission(user=current_user, permission="write"): abort( HTTP_STATUS_CODE_FORBIDDEN, "The user does not have write permission on the sketch.", ) # Check that this aggregation belongs to the sketch if aggregation.sketch_id != sketch.id: msg = ( "The sketch ID ({0:d}) does not match with the aggregation " "sketch ID ({1:d})".format(sketch.id, aggregation.sketch_id) ) abort(HTTP_STATUS_CODE_FORBIDDEN, msg) db_session.delete(aggregation) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def post(self, sketch_id, view_id): """Handles POST request to the resource. Args: sketch_id: Integer primary key for a sketch database model view_id: Integer primary key for a view database model Returns: A view in JSON (instance of flask.wrappers.Response) """ form = forms.SaveViewForm.build(request) if not form.validate_on_submit(): abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to update view, not able to validate form data') sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') view = View.query.get(view_id) if not view: abort(HTTP_STATUS_CODE_NOT_FOUND, 'No view found with this ID.') if view.sketch.id != sketch.id: abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to update view, view not attached to sketch.') view.query_string = form.query.data description = form.description.data if description: view.description = description query_filter = form.filter.data # Stripping potential pagination from views before saving it. if 'from' in query_filter: del query_filter['from'] view.query_filter = json.dumps(query_filter, ensure_ascii=False) view.query_dsl = json.dumps(form.dsl.data, ensure_ascii=False) name = form.name.data if name: view.name = name view.user = current_user view.sketch = sketch if form.dsl.data: view.query_string = '' db_session.add(view) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(view, status_code=HTTP_STATUS_CODE_CREATED)
def post(self): """Handles POST request to the resource. Returns: A view in JSON (instance of flask.wrappers.Response) """ upload_enabled = current_app.config['UPLOAD_ENABLED'] if not upload_enabled: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Failed to create timeline, upload not enabled') form = forms.CreateTimelineForm() if not form.validate_on_submit(): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Failed to create timeline, form data not validated') sketch_id = form.sketch_id.data timeline_name = form.name.data sketch = None if sketch_id: sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') # We do not need a human readable filename or # datastore index name, so we use UUIDs here. index_name = uuid.uuid4().hex if not isinstance(index_name, six.text_type): index_name = codecs.decode(index_name, 'utf-8') # Create the search index in the Timesketch database searchindex = SearchIndex.get_or_create( name=timeline_name, description=timeline_name, user=current_user, index_name=index_name) searchindex.grant_permission(permission='read', user=current_user) searchindex.grant_permission(permission='write', user=current_user) searchindex.grant_permission( permission='delete', user=current_user) searchindex.set_status('processing') db_session.add(searchindex) db_session.commit() timeline = None if sketch and sketch.has_permission(current_user, 'write'): timeline = Timeline( name=searchindex.name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex) sketch.timelines.append(timeline) db_session.add(timeline) db_session.commit() # Return Timeline if it was created. # pylint: disable=no-else-return if timeline: return self.to_json( timeline, status_code=HTTP_STATUS_CODE_CREATED) # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json( searchindex, status_code=HTTP_STATUS_CODE_CREATED)
def post(self, sketch_id, aggregation_id): """Handles POST request to the resource. Handler for /api/v1/sketches/:sketch_id/aggregation/:aggregation_id Args: sketch_id: Integer primary key for a sketch database model aggregation_id: Integer primary key for an aggregation database model """ form = request.json if not form: form = request.data if not form: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to validate form data.') sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') aggregation = Aggregation.query.get(aggregation_id) if not aggregation: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No aggregation found with this ID.') if not sketch.has_permission(user=current_user, permission='write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') aggregation.name = form.get('name', '') aggregation.description = form.get('description', '') aggregation.agg_type = form.get('agg_type', aggregation.agg_type) aggregation.chart_type = form.get('chart_type', aggregation.chart_type) aggregation.user = current_user aggregation.sketch = sketch labels = form.get('labels', '') if labels: for label in json.loads(labels): if aggregation.has_label(label): continue aggregation.add_label(label) if form.get('parameters'): aggregation.parameters = json.dumps( form.get('parameters'), ensure_ascii=False) if form.get('view_id'): aggregation.view = form.get('view_id') db_session.add(aggregation) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(aggregation, status_code=HTTP_STATUS_CODE_CREATED)
def post(self, sketch_id, timeline_id): """Handles GET request to the resource. Args: sketch_id: Integer primary key for a sketch database model timeline_id: Integer primary key for a timeline database model """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') timeline = Timeline.query.get(timeline_id) if not timeline: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No timeline found with this ID.') if timeline.sketch is None: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch associated with this timeline.') # Check that this timeline belongs to the sketch if timeline.sketch.id != sketch.id: abort( HTTP_STATUS_CODE_NOT_FOUND, 'The sketch ID ({0:d}) does not match with the timeline ' 'sketch ID ({1:d})'.format(sketch.id, timeline.sketch.id)) if not sketch.has_permission(user=current_user, permission='write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') form = forms.TimelineForm.build(request) if not form.validate_on_submit(): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to validate form data.') if form.labels.data: label_string = form.labels.data labels = json.loads(label_string) if not isinstance(labels, (list, tuple)): abort( HTTP_STATUS_CODE_BAD_REQUEST, ( 'Label needs to be a JSON string that ' 'converts to a list of strings.')) if not all([isinstance(x, str) for x in labels]): abort( HTTP_STATUS_CODE_BAD_REQUEST, ( 'Label needs to be a JSON string that ' 'converts to a list of strings (not all strings)')) label_action = form.label_action.data if label_action not in ('add', 'remove'): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Label action needs to be either add or remove.') changed = False if label_action == 'add': changes = [] for label in labels: changes.append( self._add_label(timeline=timeline, label=label)) changed = any(changes) elif label_action == 'remove': changes = [] for label in labels: changes.append( self._remove_label(timeline=timeline, label=label)) changed = any(changes) if not changed: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Label [{0:s}] not {1:s}'.format( ', '.join(labels), label_action)) db_session.add(timeline) db_session.commit() return HTTP_STATUS_CODE_OK timeline.name = form.name.data timeline.description = form.description.data timeline.color = form.color.data db_session.add(timeline) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def delete(self, sketch_id, timeline_id): """Handles DELETE request to the resource. Args: sketch_id: Integer primary key for a sketch database model timeline_id: Integer primary key for a timeline database model """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') timeline = Timeline.query.get(timeline_id) if not timeline: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No timeline found with this ID.') if timeline.sketch is None: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch associated with this timeline.') # Check that this timeline belongs to the sketch if timeline.sketch.id != sketch.id: if not timeline: msg = 'No timeline found with this ID.' elif not sketch: msg = 'No sketch found with this ID.' else: sketch_use = sketch.id or 'No sketch ID' sketch_string = str(sketch_use) timeline_use = timeline.sketch.id or ( 'No sketch associated with the timeline.') timeline_string = str(timeline_use) msg = ( 'The sketch ID ({0:s}) does not match with the timeline ' 'sketch ID ({1:s})'.format(sketch_string, timeline_string)) abort(HTTP_STATUS_CODE_NOT_FOUND, msg) if not sketch.has_permission(user=current_user, permission='write'): abort( HTTP_STATUS_CODE_FORBIDDEN, 'The user does not have write permission on the sketch.') not_delete_labels = current_app.config.get( 'LABELS_TO_PREVENT_DELETION', []) for label in not_delete_labels: if timeline.has_label(label): abort( HTTP_STATUS_CODE_FORBIDDEN, 'Timelines with label [{0:s}] cannot be deleted.'.format( label)) # Check if this searchindex is used in other sketches. close_index = True searchindex = timeline.searchindex index_name = searchindex.index_name search_indices = SearchIndex.query.filter_by( index_name=index_name).all() timelines = [] for index in search_indices: timelines.extend(index.timelines) for timeline_ in timelines: if timeline_.sketch is None: continue if timeline_.sketch.id != sketch.id: close_index = False break if timeline_.id != timeline_id: # There are more than a single timeline using this index_name, # we can't close it (unless this timeline is archived). if timeline_.get_status.status != 'archived': close_index = False break if close_index: try: self.datastore.client.indices.close( index=searchindex.index_name) except elasticsearch.NotFoundError: logger.error( 'Unable to close index: {0:s} - index not ' 'found'.format(searchindex.index_name)) searchindex.set_status(status='archived') timeline.set_status(status='archived') sketch.timelines.remove(timeline) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return HTTP_STATUS_CODE_OK
def post(self, sketch_id): """Handles POST request to the resource. Returns: Graph in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort(HTTP_STATUS_CODE_NOT_FOUND, "No sketch found with this ID.") form = request.json plugin_name = form.get("plugin") graph_config = form.get("config") refresh = form.get("refresh") timeline_ids = form.get("timeline_ids", None) if timeline_ids and not isinstance(timeline_ids, (list, tuple)): abort( HTTP_STATUS_CODE_BAD_REQUEST, "Timeline IDs if supplied need to be a list.", ) if timeline_ids and not all([isinstance(x, int) for x in timeline_ids]): abort( HTTP_STATUS_CODE_BAD_REQUEST, "Timeline IDs needs to be a list of integers.", ) sketch_indices = [ timeline.searchindex.index_name for timeline in sketch.active_timelines ] cache = GraphCache.get_or_create(sketch=sketch, graph_plugin=plugin_name) if graph_config: cache_config = graph_config else: cache_config = cache.graph_config if isinstance(cache_config, str): cache_config = json.loads(cache_config) # Refresh cache if timelines have been added/removed from the sketch. if cache_config: cache_graph_filter = cache_config.get("filter", {}) cache_filter_indices = cache_graph_filter.get("indices", []) if set(sketch_indices) ^ set(cache_filter_indices): refresh = True if cache.graph_elements and not refresh: return self.to_json(cache) graph_class = manager.GraphManager.get_graph(plugin_name) graph = graph_class(sketch=sketch, timeline_ids=timeline_ids) cytoscape_json = graph.generate().to_cytoscape() if cytoscape_json: cache.graph_elements = json.dumps(cytoscape_json) cache.graph_config = json.dumps(graph_config) cache.update_modification_time() db_session.add(cache) db_session.commit() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json(cache)
def post(self, sketch_id): """Handles POST request to the resource. Returns: A sketch in JSON (instance of flask.wrappers.Response) """ sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'write'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have write access controls on sketch.') form = request.json if not form: form = request.data metadata = {'created': True} searchindex_id = form.get('timeline', 0) if isinstance(searchindex_id, str) and searchindex_id.isdigit(): searchindex_id = int(searchindex_id) if not isinstance(searchindex_id, int): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'The timeline (searchindex id) needs to be an integer.') searchindex = SearchIndex.query.get_with_acl(searchindex_id) if searchindex.get_status.status == 'deleted': abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to create a timeline using a deleted search index') timeline_id = [ t.searchindex.id for t in sketch.timelines if t.searchindex.id == searchindex_id ] if not timeline_id: return_code = HTTP_STATUS_CODE_CREATED timeline_name = form.get('timeline_name', searchindex.name) timeline = Timeline( name=timeline_name, description=searchindex.description, sketch=sketch, user=current_user, searchindex=searchindex) sketch.timelines.append(timeline) labels_to_prevent_deletion = current_app.config.get( 'LABELS_TO_PREVENT_DELETION', []) for label in sketch.get_labels: if label not in labels_to_prevent_deletion: continue timeline.add_label(label) searchindex.add_label(label) # Set status to ready so the timeline can be queried. timeline.set_status('ready') db_session.add(timeline) db_session.commit() else: metadata['created'] = False return_code = HTTP_STATUS_CODE_OK timeline = Timeline.query.get(timeline_id) # Run sketch analyzers when timeline is added. Import here to avoid # circular imports. # pylint: disable=import-outside-toplevel if current_app.config.get('AUTO_SKETCH_ANALYZERS'): # pylint: disable=import-outside-toplevel from timesketch.lib import tasks sketch_analyzer_group, _ = tasks.build_sketch_analysis_pipeline( sketch_id, searchindex_id, current_user.id, timeline_id=timeline_id) if sketch_analyzer_group: pipeline = (tasks.run_sketch_init.s( [searchindex.index_name]) | sketch_analyzer_group) pipeline.apply_async() # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return self.to_json( timeline, meta=metadata, status_code=return_code)
def post(self, sketch_id): """Handles POST request to the resource. Handler for /api/v1/sketches/<int:sketch_id>/aggregation/explore/ Args: sketch_id: Integer primary key for a sketch database model Returns: JSON with aggregation results """ form = forms.AggregationExploreForm.build(request) if not form.validate_on_submit(): abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Not able to run aggregation, unable to validate form data.') sketch = Sketch.query.get_with_acl(sketch_id) if not sketch: abort( HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.') if not sketch.has_permission(current_user, 'read'): abort(HTTP_STATUS_CODE_FORBIDDEN, 'User does not have read access controls on sketch.') if sketch.get_status.status == 'archived': abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Not able to run aggregation on an archived sketch.') sketch_indices = { t.searchindex.index_name for t in sketch.timelines if t.get_status.status.lower() == 'ready' } aggregation_dsl = form.aggregation_dsl.data aggregator_name = form.aggregator_name.data if aggregator_name: if isinstance(form.aggregator_parameters.data, dict): aggregator_parameters = form.aggregator_parameters.data else: aggregator_parameters = json.loads( form.aggregator_parameters.data) agg_class = aggregator_manager.AggregatorManager.get_aggregator( aggregator_name) if not agg_class: return {} if not aggregator_parameters: aggregator_parameters = {} indices = aggregator_parameters.pop('index', sketch_indices) indices, timeline_ids = lib_utils.get_validated_indices( indices, sketch) aggregator = agg_class( sketch_id=sketch_id, indices=indices, timeline_ids=timeline_ids) chart_type = aggregator_parameters.pop('supported_charts', None) chart_color = aggregator_parameters.pop('chart_color', '') chart_title = aggregator_parameters.pop( 'chart_title', aggregator.chart_title) time_before = time.time() try: result_obj = aggregator.run(**aggregator_parameters) except NotFoundError: abort( HTTP_STATUS_CODE_NOT_FOUND, 'Attempting to run an aggregation on a non-existing ' 'Elastic index, index: {0:s} and parameters: {1!s}'.format( ','.join(indices), aggregator_parameters)) except ValueError as exc: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to run the aggregation, with error: {0!s}'.format( exc)) time_after = time.time() aggregator_description = aggregator.describe buckets = result_obj.to_dict() buckets['buckets'] = buckets.pop('values') result = { 'aggregation_result': { aggregator_name: buckets } } meta = { 'method': 'aggregator_run', 'chart_type': chart_type, 'name': aggregator_description.get('name'), 'description': aggregator_description.get('description'), 'es_time': time_after - time_before, } if chart_type: meta['vega_spec'] = result_obj.to_chart( chart_name=chart_type, chart_title=chart_title, color=chart_color) meta['vega_chart_title'] = chart_title elif aggregation_dsl: # pylint: disable=unexpected-keyword-arg result = self.datastore.client.search( index=','.join(sketch_indices), body=aggregation_dsl, size=0) meta = { 'es_time': result.get('took', 0), 'es_total_count': result.get('hits', {}).get('total', 0), 'timed_out': result.get('timed_out', False), 'method': 'aggregator_query', 'max_score': result.get('hits', {}).get('max_score', 0.0) } else: abort( HTTP_STATUS_CODE_BAD_REQUEST, 'An aggregation DSL or a name for an aggregator name needs ' 'to be provided!') result_keys = set(result.keys()) - self.REMOVE_FIELDS objects = [result[key] for key in result_keys] schema = {'meta': meta, 'objects': objects} # Update the last activity of a sketch. utils.update_sketch_last_activity(sketch) return jsonify(schema)