Example #1
0
    def run(self, remove, group_name, user_name):
        """Add the user to the group."""
        if not isinstance(group_name, six.text_type):
            group_name = codecs.decode(group_name, 'utf-8')

        if not isinstance(user_name, six.text_type):
            user_name = codecs.decode(user_name, 'utf-8')

        group = Group.query.filter_by(name=group_name).first()
        user = User.query.filter_by(username=user_name).first()

        # Add or remove user from group
        if remove:
            try:
                user.groups.remove(group)
                sys.stdout.write('{0:s} removed from group {1:s}\n'.format(
                    user_name, group_name))
                db_session.commit()
            except ValueError:
                sys.stdout.write('{0:s} is not a member of group {1:s}\n'.
                                 format(user_name, group_name))
        else:
            user.groups.append(group)
            try:
                db_session.commit()
                sys.stdout.write('{0:s} added to group {1:s}\n'.format(
                    user_name, group_name))
            except IntegrityError:
                sys.stdout.write('{0:s} is already a member of group {1:s}\n'.
                                 format(user_name, group_name))
Example #2
0
    def run_wrapper(self):
        """A wrapper method to run the analyzer.

        This method is decorated to flush the bulk insert operation on the
        datastore. This makes sure that all events are indexed at exit.

        Returns:
            Return value of the run method.
        """
        result = self.run()

        # Update the searchindex description with analyzer result.
        # TODO: Don't overload the description field.
        searchindex = SearchIndex.query.filter_by(
            index_name=self.index_name).first()

        # Some code paths set the description equals to the name. Remove that
        # here to get a clean description with only analyzer results.
        if searchindex.description == searchindex.name:
            searchindex.description = ''

        # Append the analyzer result.
        if result:
            searchindex.description = '{0:s}\n{1:s}'.format(
                searchindex.description, result)
        db_session.add(searchindex)
        db_session.commit()

        return result
Example #3
0
def home():
    """Generates the home page view template.

    Returns:
        Template with context.
    """
    form = HiddenNameDescriptionForm()
    sketches = Sketch.all_with_acl().filter(
        not_(Sketch.Status.status == 'deleted'),
        Sketch.Status.parent).order_by(Sketch.updated_at.desc())
    # Only render upload button if it is configured.
    upload_enabled = current_app.config['UPLOAD_ENABLED']

    # Handle form for creating a new sketch.
    if form.validate_on_submit():
        sketch = Sketch(
            name=form.name.data,
            description=form.description.data,
            user=current_user)
        sketch.status.append(sketch.Status(user=None, status='new'))
        db_session.add(sketch)
        db_session.commit()

        # Give the requesting user permissions on the new sketch.
        sketch.grant_permission(permission='read', user=current_user)
        sketch.grant_permission(permission='write', user=current_user)
        sketch.grant_permission(permission='delete', user=current_user)
        return redirect(url_for('sketch_views.overview', sketch_id=sketch.id))

    return render_template(
        'home/home.html',
        sketches=sketches,
        form=form,
        upload_enabled=upload_enabled)
Example #4
0
def explore(sketch_id, view_id=None):
    """Generates the sketch explore view template.

    Returns:
        Template with context.
    """
    sketch = Sketch.query.get_with_acl(sketch_id)
    if view_id:
        view = View.query.get(view_id)
    else:
        view = View.query.filter(
            View.user == current_user,
            View.name == u'',
            View.sketch_id == sketch_id).order_by(
                View.created_at.desc()).first()
    if not view:
        view = View(
            user=current_user, name=u'', sketch=sketch, query_string=u'',
            query_filter=u'{}')
        db_session.add(view)
        db_session.commit()
    sketch_timelines = u','.join(
        [t.searchindex.index_name for t in sketch.timelines])
    view_form = SaveViewForm()

    return render_template(
        u'sketch/explore.html', sketch=sketch, view=view,
        timelines=sketch_timelines, view_form=view_form)
Example #5
0
def _set_timeline_status(index_name, status, error_msg=None):
    """Helper function to set status for searchindex and all related timelines.

    Args:
        index_name: Name of the datastore index.
        status: Status to set.
        error_msg: Error message.
    """
    searchindex = SearchIndex.query.filter_by(index_name=index_name).first()
    timelines = Timeline.query.filter_by(searchindex=searchindex).all()

    # Set status
    searchindex.set_status(status)
    for timeline in timelines:
        timeline.set_status(status)
        db_session.add(timeline)

    # Update description if there was a failure in ingestion
    if error_msg and status == 'fail':
        # TODO: Don't overload the description field.
        searchindex.description = error_msg

    # Commit changes to database
    db_session.add(searchindex)
    db_session.commit()
Example #6
0
    def add_view(self, view_name, analyzer_name, query_string=None,
                 query_dsl=None, query_filter=None):
        """Add saved view to the Sketch.

        Args:
            view_name: The name of the view.
            analyzer_name: The name of the analyzer.
            query_string: Elasticsearch query string.
            query_dsl: Dictionary with Elasticsearch DSL query.
            query_filter: Dictionary with Elasticsearch filters.

        Raises:
            ValueError: If both query_string an query_dsl are missing.

        Returns: An instance of a SQLAlchemy View object.
        """
        if not query_string or query_dsl:
            raise ValueError('Both query_string and query_dsl are missing.')

        if not query_filter:
            query_filter = {'indices': '_all'}

        name = '[{0:s}] {1:s}'.format(analyzer_name, view_name)
        view = View.get_or_create(name=name, sketch=self.sql_sketch, user=None)
        view.query_string = query_string
        view.query_filter = view.validate_filter(query_filter)
        view.query_dsl = query_dsl
        view.searchtemplate = None

        db_session.add(view)
        db_session.commit()
        return view
Example #7
0
def timeline(sketch_id, timeline_id):
    """Generates the sketch timeline view template.

    Returns:
        Template with context.
    """
    timeline_form = TimelineForm()
    sketch = Sketch.query.get_with_acl(sketch_id)
    sketch_timeline = Timeline.query.filter(
        Timeline.id == timeline_id, Timeline.sketch == sketch).first()
    if not sketch_timeline:
        abort(HTTP_STATUS_CODE_NOT_FOUND)

    if timeline_form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        sketch_timeline.name = timeline_form.name.data
        sketch_timeline.description = timeline_form.description.data
        sketch_timeline.color = timeline_form.color.data
        db_session.add(sketch_timeline)
        db_session.commit()
        return redirect(
            url_for(u'sketch_views.timeline', sketch_id=sketch.id,
                    timeline_id=sketch_timeline.id))

    return render_template(
        u'sketch/timeline.html', sketch=sketch, timeline=sketch_timeline,
        timeline_form=timeline_form)
Example #8
0
  def WriteHeader(self):
    """Sets up the Elasticsearch index and the Timesketch database object.

    Creates the Elasticsearch index with Timesketch specific settings and the
    Timesketch SearchIndex database object.
    """
    # This cannot be static because we use the value of self._document_type
    # from arguments.
    mappings = {
        self._document_type: {
            'properties': {
                'timesketch_label': {
                    'type': 'nested'
                }
            }
        }
    }

    # Get Elasticsearch host and port from Timesketch configuration.
    with self._timesketch.app_context():
      self._host = current_app.config['ELASTIC_HOST']
      self._port = current_app.config['ELASTIC_PORT']

    self._Connect()

    self._CreateIndexIfNotExists(self._index_name, mappings)

    user = None
    if self._timeline_owner:
      user = timesketch_user.User.query.filter_by(
          username=self._timeline_owner).first()
      if not user:
        raise RuntimeError(
            'Unknown Timesketch user: {0:s}'.format(self._timeline_owner))
    else:
      logger.warning('Timeline will be visible to all Timesketch users')

    with self._timesketch.app_context():
      search_index = timesketch_sketch.SearchIndex.get_or_create(
          name=self._timeline_name, description=self._timeline_name, user=user,
          index_name=self._index_name)

      # Grant the user read permission on the mapping object and set status.
      # If user is None the timeline becomes visible to all users.
      search_index.grant_permission(user=user, permission='read')

      # In case we have a user grant additional permissions.
      if user:
        search_index.grant_permission(user=user, permission='write')
        search_index.grant_permission(user=user, permission='delete')

      # Let the Timesketch UI know that the timeline is processing.
      search_index.set_status('processing')

      # Save the mapping object to the Timesketch database.
      timesketch_db_session.add(search_index)
      timesketch_db_session.commit()

    logger.debug('Adding events to Timesketch.')
Example #9
0
 def run(self, name):
     """Creates the group."""
     if not isinstance(name, six.text_type):
         name = codecs.decode(name, 'utf-8')
     group = Group.get_or_create(name=name)
     db_session.add(group)
     db_session.commit()
     sys.stdout.write('Group {0:s} created\n'.format(name))
Example #10
0
    def _commit_to_database(self, model):
        """Add object to the database session and commit.

        Args:
            model: Instance of timesketch.models.[model] object
        """
        db_session.add(model)
        db_session.commit()
Example #11
0
  def WriteHeader(self):
    """Setup the Elasticsearch index and the Timesketch database object.

    Creates the Elasticsearch index with Timesketch specific settings and the
    Timesketch SearchIndex database object.
    """
    # This cannot be static because we use the value of self._doc_type from
    # arguments.
    _document_mapping = {
        self._doc_type: {
            u'properties': {
                u'timesketch_label': {
                    u'type': u'nested'
                }
            }
        }
    }

    # Get Elasticsearch host and port from Timesketch configuration.
    with self._timesketch.app_context():
      _host = current_app.config[u'ELASTIC_HOST']
      _port = current_app.config[u'ELASTIC_PORT']

    self._elastic = ElasticSearchHelper(
        self._output_mediator, _host, _port, self._flush_interval,
        self._index_name, _document_mapping, self._doc_type)

    user = None
    if self._username:
      user = User.query.filter_by(username=self._username).first()
      if not user:
        raise RuntimeError(
            u'Unknown Timesketch user: {0:s}'.format(self._username))
    else:
      logging.warning(u'Timeline will be visible to all Timesketch users')

    with self._timesketch.app_context():
      search_index = SearchIndex.get_or_create(
          name=self._timeline_name, description=self._timeline_name, user=user,
          index_name=self._index_name)

      # Grant the user read permission on the mapping object and set status.
      # If user is None the timeline becomes visible to all users.
      search_index.grant_permission(user=user, permission=u'read')

      # In case we have a user grant additional permissions.
      if user:
        search_index.grant_permission(user=user, permission=u'write')
        search_index.grant_permission(user=user, permission=u'delete')

      # Let the Timesketch UI know that the timeline is processing.
      search_index.set_status(u'processing')

      # Save the mapping object to the Timesketch database.
      db_session.add(search_index)
      db_session.commit()

    logging.info(u'Adding events to Timesketch.')
Example #12
0
    def post(self, sketch_id):
        """Handles POST request to the resource.

        Args:
            sketch_id: Integer primary key for a sketch database model

        Returns:
            An annotation in JSON (instance of flask.wrappers.Response)
        """
        form = EventAnnotationForm.build(request)
        if form.validate_on_submit():
            sketch = Sketch.query.get_with_acl(sketch_id)
            indices = [t.searchindex.index_name for t in sketch.timelines]
            annotation_type = form.annotation_type.data
            searchindex_id = form.searchindex_id.data
            searchindex = SearchIndex.query.get(searchindex_id)
            event_id = form.event_id.data

            if searchindex_id not in indices:
                abort(HTTP_STATUS_CODE_BAD_REQUEST)

            def _set_label(label, toggle=False):
                """Set label on the event in the datastore."""
                self.datastore.set_label(
                    searchindex_id, event_id, sketch.id, current_user.id, label,
                    toggle=toggle)

            # Get or create an event in the SQL database to have something to
            # attach the annotation to.
            event = Event.get_or_create(
                sketch=sketch, searchindex=searchindex,
                document_id=event_id)

            # Add the annotation to the event object.
            if u'comment' in annotation_type:
                annotation = Event.Comment(
                    comment=form.annotation.data, user=current_user)
                event.comments.append(annotation)
                _set_label(u'__ts_comment')
            elif u'label' in annotation_type:
                annotation = Event.Label.get_or_create(
                    label=form.annotation.data, user=current_user)
                if annotation not in event.labels:
                    event.labels.append(annotation)
                toggle = False
                if u'__ts_star' in form.annotation.data:
                    toggle = True
                _set_label(form.annotation.data, toggle)
            else:
                abort(HTTP_STATUS_CODE_BAD_REQUEST)

            # Save the event to the database
            db_session.add(event)
            db_session.commit()

            return self.to_json(
                annotation, status_code=HTTP_STATUS_CODE_CREATED)
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Example #13
0
    def revoke_permission(self, user, permission):
        """Revoke permission to a user with the specific permission.

        Args:
            user: A user (Instance of timesketch.models.user.User)
            permission: Permission as string (read, write or delete)
        """
        for ace in self._get_ace(user, permission):
            self.acl.remove(ace)
        db_session.commit()
Example #14
0
def overview(sketch_id):
    """Generates the sketch overview template.

    Returns:
        Template with context.
    """
    sketch = Sketch.query.get_with_acl(sketch_id)
    sketch_form = NameDescriptionForm()
    permission_form = TogglePublic()
    status_form = StatusForm()
    trash_form = TrashForm()

    # Edit sketch form POST
    if sketch_form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        sketch.name = sketch_form.name.data
        sketch.description = sketch_form.description.data
        db_session.commit()
        return redirect(
            url_for(u'sketch_views.overview', sketch_id=sketch.id))

    # Toggle public/private form POST
    if permission_form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        if permission_form.permission.data == u'public':
            sketch.grant_permission(user=None, permission=u'read')
        else:
            sketch.revoke_permission(user=None, permission=u'read')
        db_session.commit()
        return redirect(
            url_for(u'sketch_views.overview', sketch_id=sketch.id))

    # Change status form POST
    if status_form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        sketch.set_status(status=status_form.status.data)
        return redirect(
            url_for(u'sketch_views.overview', sketch_id=sketch.id))

    # Trash form POST
    if trash_form.validate_on_submit():
        if not sketch.has_permission(current_user, u'delete'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        sketch.set_status(status=u'deleted')
        return redirect(
            url_for(u'home_views.home'))

    return render_template(
        u'sketch/overview.html', sketch=sketch, sketch_form=sketch_form,
        permission_form=permission_form, status_form=status_form,
        trash_form=trash_form)
Example #15
0
    def set_status(self, status):
        """
        Set status on object. Although this is a many-to-many relationship
        this makes sure that the parent object only has one status set.

        Args:
            status: Name of the status
        """
        for _status in self.status:
            self.status.remove(_status)
        self.status.append(self.Status(user=None, status=status))
        db_session.commit()
Example #16
0
    def grant_permission(self, user, permission):
        """Grant permission to a user with the specific permission.

        Args:
            user: A user (Instance of timesketch.models.user.User)
            permission: Permission as string (read, write or delete)
        """
        if not self._get_ace(user, permission):
            self.acl.append(
                self.AccessControlEntry(
                    user=user, permission=permission))
            db_session.commit()
Example #17
0
 def run(self, username, password):
     """Creates the user."""
     if not password:
         password = self.get_password_from_prompt()
     if not isinstance(password, six.text_type):
         password = codecs.decode(password, 'utf-8')
     username = codecs.decode(username, 'utf-8')
     user = User.get_or_create(username=username)
     user.set_password(plaintext=password)
     db_session.add(user)
     db_session.commit()
     sys.stdout.write('User {0:s} created/updated\n'.format(username))
Example #18
0
  def Close(self):
    """Closes the connection to TimeSketch Elasticsearch database.

    Sends the remaining events for indexing and removes the processing status on
    the Timesketch search index object.
    """
    self._elastic.AddEvent(None, force_flush=True)
    with self._timesketch.app_context():
      search_index = SearchIndex.query.filter_by(
          index_name=self._index_name).first()
      search_index.status.remove(search_index.status[0])
      db_session.add(search_index)
      db_session.commit()
Example #19
0
    def post(self):
        """Handles POST request to the resource.

        Returns:
            A search index in JSON (instance of flask.wrappers.Response)
        """
        form = forms.SearchIndexForm.build(request)
        searchindex_name = form.searchindex_name.data
        es_index_name = form.es_index_name.data
        public = form.public.data

        if not form.validate_on_submit():
            abort(HTTP_STATUS_CODE_BAD_REQUEST, 'Unable to validate form data')

        searchindex = SearchIndex.query.filter_by(
            index_name=es_index_name).first()
        metadata = {'created': True}

        if searchindex:
            metadata['created'] = False
            metadata['deleted'] = searchindex.get_status.status == 'deleted'
            status_code = HTTP_STATUS_CODE_OK
        else:
            searchindex = SearchIndex.get_or_create(
                name=searchindex_name,
                description=searchindex_name,
                user=current_user,
                index_name=es_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)

            if public:
                searchindex.grant_permission(permission='read', user=None)

            datastore = self.datastore

            if not datastore.client.indices.exists(index=es_index_name):
                # Create the index in Elasticsearch
                self.datastore.create_index(index_name=es_index_name,
                                            doc_type='generic_event')

            db_session.add(searchindex)
            db_session.commit()
            status_code = HTTP_STATUS_CODE_CREATED

        return self.to_json(searchindex,
                            meta=metadata,
                            status_code=status_code)
Example #20
0
  def Close(self):
    """Closes the connection to TimeSketch Elasticsearch database.

    Sends the remaining events for indexing and removes the processing status on
    the Timesketch search index object.
    """
    super(TimesketchOutputModule, self).Close()

    with self._timesketch.app_context():
      search_index = timesketch_sketch.SearchIndex.query.filter_by(
          index_name=self._index_name).first()
      search_index.status.remove(search_index.status[0])
      timesketch_db_session.add(search_index)
      timesketch_db_session.commit()
Example #21
0
def timelines(sketch_id):
    """Generates the sketch explore view template.

    Returns:
        Template with context.
    """
    sketch = Sketch.query.get_with_acl(sketch_id)
    searchindices_in_sketch = [t.searchindex.id for t in sketch.timelines]
    indices = SearchIndex.all_with_acl(current_user).order_by(
        desc(SearchIndex.created_at)).filter(
            not_(SearchIndex.id.in_(searchindices_in_sketch)))
    upload_enabled = current_app.config[u'UPLOAD_ENABLED']
    graphs_enabled = current_app.config[u'GRAPH_BACKEND_ENABLED']

    try:
        plaso_version = current_app.config[u'PLASO_VERSION']
    except KeyError:
        plaso_version = u'Unknown'

    # Setup the form
    form = AddTimelineForm()
    form.timelines.choices = set((i.id, i.name) for i in indices.all())

    # Create new timeline form POST
    if form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        for searchindex_id in form.timelines.data:
            searchindex = SearchIndex.query.get_with_acl(searchindex_id)
            if searchindex not in [t.searchindex for t in sketch.timelines]:
                _timeline = Timeline(
                    name=searchindex.name,
                    description=searchindex.description,
                    sketch=sketch,
                    user=current_user,
                    searchindex=searchindex)
                db_session.add(_timeline)
                sketch.timelines.append(_timeline)
        db_session.commit()
        return redirect(
            url_for(u'sketch_views.timelines', sketch_id=sketch.id))

    return render_template(
        u'sketch/timelines.html',
        sketch=sketch,
        timelines=indices.all(),
        form=form,
        upload_enabled=upload_enabled,
        plaso_version=plaso_version,
        graphs_enabled=graphs_enabled)
Example #22
0
def home():
    """Generates the home page view template.

    Returns:
        Template with context.
    """
    form = HiddenNameDescriptionForm()
    sketches = Sketch.all_with_acl().filter(
        not_(Sketch.Status.status == u'deleted'),
        Sketch.Status.parent).order_by(Sketch.updated_at.desc())
    query_filter = request.args.get(u'filter', u'')
    query = request.args.get(u'q', u'')
    # Only render upload button if it is configured.
    upload_enabled = current_app.config[u'UPLOAD_ENABLED']
    last_sketch = View.query.filter_by(user=current_user, name=u'').order_by(
        View.updated_at.desc()).first()

    if query_filter:
        if query_filter == u'user':
            sketches = sketches.filter(Sketch.user == current_user)
        elif query_filter == u'shared':
            sketches = sketches.filter(not_(Sketch.user == current_user))

    # TODO: Figure out a better way to handle this.
    if query:
        if query.startswith(u'*'):
            query = u''
        else:
            sketches = sketches.filter(Sketch.name.contains(query)).limit(100)

    # Handle form for creating a new sketch.
    if form.validate_on_submit():
        sketch = Sketch(name=form.name.data,
                        description=form.description.data,
                        user=current_user)
        sketch.status.append(sketch.Status(user=None, status=u'new'))
        # Give the requesting user permissions on the new sketch.
        sketch.grant_permission(current_user, u'read')
        sketch.grant_permission(current_user, u'write')
        sketch.grant_permission(current_user, u'delete')
        db_session.add(sketch)
        db_session.commit()
        return redirect(url_for(u'sketch_views.overview', sketch_id=sketch.id))

    return render_template(u'home/home.html',
                           sketches=sketches,
                           form=form,
                           query=query,
                           upload_enabled=upload_enabled,
                           last_sketch=last_sketch)
Example #23
0
    def Close(self):
        """Closes the connection to TimeSketch Elasticsearch database.

    Sends the remaining events for indexing and removes the processing status on
    the Timesketch search index object.
    """
        super(TimesketchOutputModule, self).Close()

        with self._timesketch.app_context():
            search_index = timesketch_sketch.SearchIndex.query.filter_by(
                index_name=self._index_name).first()
            search_index.status.remove(search_index.status[0])
            timesketch_db_session.add(search_index)
            timesketch_db_session.commit()
Example #24
0
    def WriteHeader(self):
        """Sets up the Elasticsearch index and the Timesketch database object.

    Creates the Elasticsearch index with Timesketch specific settings and the
    Timesketch SearchIndex database object.
    """
        # Get Elasticsearch host and port from Timesketch configuration.
        with self._timesketch.app_context():
            self._host = current_app.config['ELASTIC_HOST']
            self._port = current_app.config['ELASTIC_PORT']

        self._Connect()

        self._CreateIndexIfNotExists(self._index_name, self._mappings)

        user = None
        if self._timeline_owner:
            user = timesketch_user.User.query.filter_by(
                username=self._timeline_owner).first()
            if not user:
                raise RuntimeError('Unknown Timesketch user: {0:s}'.format(
                    self._timeline_owner))
        else:
            logger.warning('Timeline will be visible to all Timesketch users')

        with self._timesketch.app_context():
            search_index = timesketch_sketch.SearchIndex.get_or_create(
                name=self._timeline_name,
                description=self._timeline_name,
                user=user,
                index_name=self._index_name)

            # Grant the user read permission on the mapping object and set status.
            # If user is None the timeline becomes visible to all users.
            search_index.grant_permission(user=user, permission='read')

            # In case we have a user grant additional permissions.
            if user:
                search_index.grant_permission(user=user, permission='write')
                search_index.grant_permission(user=user, permission='delete')

            # Let the Timesketch UI know that the timeline is processing.
            search_index.set_status('processing')

            # Save the mapping object to the Timesketch database.
            timesketch_db_session.add(search_index)
            timesketch_db_session.commit()

        logger.debug('Adding events to Timesketch.')
Example #25
0
    def post(self, searchindex_id):
        """Handles POST request to the resource.

        Returns:
            A search index in JSON (instance of flask.wrappers.Response)
        """
        form = request.json
        if not form:
            form = request.data

        if not searchindex_id:
            abort(HTTP_STATUS_CODE_BAD_REQUEST,
                  "Need to define a search index ID")

        searchindex = SearchIndex.query.get_with_acl(searchindex_id)
        if not searchindex:
            abort(HTTP_STATUS_CODE_NOT_FOUND,
                  "No searchindex found with this ID.")

        if not searchindex.has_permission(current_user, "write"):
            abort(
                HTTP_STATUS_CODE_FORBIDDEN,
                ("User does not have sufficient access rights to "
                 "edit the search index."),
            )

        if searchindex.get_status.status == "deleted":
            abort(
                HTTP_STATUS_CODE_BAD_REQUEST,
                "Search index is marked deleted, unable to continue.",
            )

        commit_to_db = False
        new_status = form.get("status", "")
        valid_status = ("ready", "fail", "processing", "timeout")
        if new_status and new_status in valid_status:
            searchindex.set_status(status=new_status)
            commit_to_db = True

        description = form.get("description", "")
        if description:
            searchindex.description = description
            commit_to_db = True

        if commit_to_db:
            db_session.add(searchindex)
            db_session.commit()

        return self.to_json(searchindex)
Example #26
0
    def add_view(self,
                 view_name,
                 analyzer_name,
                 query_string=None,
                 query_dsl=None,
                 query_filter=None,
                 additional_fields=None):
        """Add saved view to the Sketch.

        Args:
            view_name: The name of the view.
            analyzer_name: The name of the analyzer.
            query_string: Elasticsearch query string.
            query_dsl: Dictionary with Elasticsearch DSL query.
            query_filter: Dictionary with Elasticsearch filters.
            additional_fields: A list with field names to include in the
                view output.

        Raises:
            ValueError: If both query_string an query_dsl are missing.

        Returns: An instance of a SQLAlchemy View object.
        """
        if not (query_string or query_dsl):
            raise ValueError('Both query_string and query_dsl are missing.')

        if not query_filter:
            query_filter = {'indices': '_all'}

        if additional_fields:
            query_filter['fields'] = [{
                'field': x.strip()
            } for x in additional_fields]

        description = 'analyzer: {0:s}'.format(analyzer_name)
        view = View.get_or_create(name=view_name,
                                  description=description,
                                  sketch=self.sql_sketch,
                                  user=None)
        view.description = description
        view.query_string = query_string
        view.query_filter = view.validate_filter(query_filter)
        view.query_dsl = query_dsl
        view.searchtemplate = None
        view.set_status(status='new')

        db_session.add(view)
        db_session.commit()
        return view
Example #27
0
    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)
Example #28
0
    def WriteHeader(self):
        """Setup the Elasticsearch index and the Timesketch database object.

    Creates the Elasticsearch index with Timesketch specific settings and the
    Timesketch SearchIndex database object.
    """
        # This cannot be static because we use the value of self._doc_type from
        # arguments.
        _document_mapping = {
            self._doc_type: {
                u'_timestamp': {
                    u'enabled': True,
                    u'path': u'datetime',
                    u'format': u'date_time_no_millis'
                },
                u'properties': {
                    u'timesketch_label': {
                        u'type': u'nested'
                    }
                }
            }
        }

        if not self._elastic_db.client.indices.exists(self._index_name):
            try:
                self._elastic_db.client.indices.create(
                    index=self._index_name,
                    body={u'mappings': _document_mapping})
            except elastic_exceptions.ConnectionError as exception:
                logging.error((
                    u'Unable to proceed, cannot connect to Timesketch backend '
                    u'with error: {0:s}.\nPlease verify connection.'
                ).format(exception))
                raise RuntimeError(u'Unable to connect to Timesketch backend.')

        with self._timesketch.app_context():
            search_index = SearchIndex.get_or_create(
                name=self._timeline_name,
                description=self._timeline_name,
                user=None,
                index_name=self._index_name)
            # Grant all users read permission on the mapping object and set status.
            search_index.grant_permission(None, u'read')
            search_index.set_status(u'processing')
            # Save the mapping object to the Timesketch database.
            db_session.add(search_index)
            db_session.commit()

        logging.info(u'Adding events to Timesketch..')
Example #29
0
def explore(sketch_id, view_id=None):
    """Generates the sketch explore view template.

    Returns:
        Template with context.
    """
    sketch = Sketch.query.get_with_acl(sketch_id)
    sketch_timelines = [t.searchindex.index_name for t in sketch.timelines]
    view_form = SaveViewForm()

    # Get parameters from the GET query
    url_query = request.args.get(u'q', u'')
    url_time_start = request.args.get(u'time_start', None)
    url_time_end = request.args.get(u'time_end', None)

    if view_id:
        view = View.query.get(view_id)

        # Check that this view belongs to the sketch
        if view.sketch_id != sketch.id:
            abort(HTTP_STATUS_CODE_NOT_FOUND)

        # Return 404 if view is deleted
        if view.get_status.status == u'deleted':
            return abort(HTTP_STATUS_CODE_NOT_FOUND)
    else:
        view = sketch.get_user_view(current_user)
        if url_query:
            view.query_string = url_query
            query_filter = json.loads(view.query_filter)
            query_filter[u'time_start'] = url_time_start
            query_filter[u'time_end'] = url_time_end
            view.query_filter = json.dumps(query_filter, ensure_ascii=False)

    if not view:
        query_filter = dict(indices=sketch_timelines)
        view = View(user=current_user,
                    name=u'',
                    sketch=sketch,
                    query_string=u'',
                    query_filter=json.dumps(query_filter, ensure_ascii=False))
        db_session.add(view)
        db_session.commit()

    return render_template(u'sketch/explore.html',
                           sketch=sketch,
                           view=view,
                           timelines=sketch_timelines,
                           view_form=view_form)
Example #30
0
def timelines(sketch_id):
    """Generates the sketch explore view template.

    Returns:
        Template with context.
    """
    TIMELINES_TO_SHOW = 20

    sketch = Sketch.query.get_with_acl(sketch_id)
    searchindices_in_sketch = [t.searchindex.id for t in sketch.timelines]
    query = request.args.get(u'q', None)
    indices = SearchIndex.all_with_acl(current_user).order_by(
        desc(SearchIndex.created_at)).filter(
            not_(SearchIndex.id.in_(searchindices_in_sketch)))
    filtered = False

    if query:
        indices = indices.filter(SearchIndex.name.contains(query)).limit(500)
        filtered = True
    if not filtered:
        indices = indices.limit(TIMELINES_TO_SHOW)

    # Setup the form
    form = AddTimelineForm()
    form.timelines.choices = set((i.id, i.name) for i in indices.all())

    # Create new timeline form POST
    if form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        for searchindex_id in form.timelines.data:
            searchindex = SearchIndex.query.get_with_acl(searchindex_id)
            if searchindex not in [t.searchindex for t in sketch.timelines]:
                _timeline = Timeline(name=searchindex.name,
                                     description=searchindex.description,
                                     sketch=sketch,
                                     user=current_user,
                                     searchindex=searchindex)
                db_session.add(_timeline)
                sketch.timelines.append(_timeline)
        db_session.commit()
        return redirect(url_for(u'sketch_views.timelines',
                                sketch_id=sketch.id))

    return render_template(u'sketch/timelines.html',
                           sketch=sketch,
                           timelines=indices.all(),
                           form=form,
                           filtered=filtered)
Example #31
0
    def add_aggregation(self,
                        name,
                        agg_name,
                        agg_params,
                        description='',
                        view_id=None,
                        chart_type=None,
                        label=''):
        """Add aggregation to the sketch.

        Args:
            name: the name of the aggregation run.
            agg_name: the name of the aggregation class to run.
            agg_params: a dictionary of the parameters for the aggregation.
            description: description of the aggregation, visible in the UI,
                this is optional.
            view_id: optional ID of the view to attach the aggregation to.
            chart_type: string representing the chart type.
            label: string with a label to attach to the aggregation.
        """
        if not agg_name:
            raise ValueError('Aggregator name needs to be defined.')
        if not agg_params:
            raise ValueError('Aggregator parameters have to be defined.')

        if view_id:
            view = View.query.get(view_id)
        else:
            view = None

        if chart_type:
            agg_params['supported_charts'] = chart_type

        agg_json = json.dumps(agg_params)
        aggregation = Aggregation.get_or_create(name=name,
                                                description=description,
                                                agg_type=agg_name,
                                                parameters=agg_json,
                                                chart_type=chart_type,
                                                user=None,
                                                sketch=self.sql_sketch,
                                                view=view)

        if label:
            aggregation.add_label(label)
        db_session.add(aggregation)
        db_session.commit()
        return aggregation
Example #32
0
    def get(self, sketch_id, view_id):
        """Handles GET 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)
        """
        sketch = Sketch.query.get_with_acl(sketch_id)
        if not sketch:
            abort(
                HTTP_STATUS_CODE_NOT_FOUND, 'No sketch found with this ID.')
        view = View.query.get(view_id)

        if not sketch.has_permission(current_user, 'read'):
            abort(HTTP_STATUS_CODE_FORBIDDEN,
                  'User does not have read access controls on sketch.')

        # Check that this view belongs to the sketch
        if view.sketch_id != sketch.id:
            abort(
                HTTP_STATUS_CODE_NOT_FOUND,
                'Sketch ID ({0:d}) does not match with the sketch ID '
                'that is defined in the view ({1:d})'.format(
                    view.sketch_id, sketch.id))

        # If this is a user state view, check that it
        # belongs to the current_user
        if view.name == '' and view.user != current_user:
            abort(
                HTTP_STATUS_CODE_FORBIDDEN,
                'Unable to view a state view that belongs to a '
                'different user.')

        # Check if view has been deleted
        if view.get_status.status == 'deleted':
            meta = dict(deleted=True, name=view.name)
            schema = dict(meta=meta, objects=[])
            return jsonify(schema)

        # Make sure we have all expected attributes in the query filter.
        view.query_filter = view.validate_filter()
        db_session.add(view)
        db_session.commit()

        return self.to_json(view)
Example #33
0
def home():
    """Generates the home page view template.

    Returns:
        Template with context.
    """
    form = HiddenNameDescriptionForm()
    sketches = Sketch.all_with_acl().filter(
        not_(Sketch.Status.status == u'deleted'),
        Sketch.Status.parent).order_by(Sketch.updated_at.desc())
    query_filter = request.args.get(u'filter', u'')
    query = request.args.get(u'q', u'')
    # Only render upload button if it is configured.
    upload_enabled = current_app.config[u'UPLOAD_ENABLED']
    last_sketch = View.query.filter_by(
        user=current_user, name=u'').order_by(
            View.updated_at.desc()).first()

    if query_filter:
        if query_filter == u'user':
            sketches = sketches.filter(Sketch.user == current_user)
        elif query_filter == u'shared':
            sketches = sketches.filter(not_(Sketch.user == current_user))

    # TODO: Figure out a better way to handle this.
    if query:
        if query.startswith(u'*'):
            query = u''
        else:
            sketches = sketches.filter(Sketch.name.contains(query)).limit(100)

    # Handle form for creating a new sketch.
    if form.validate_on_submit():
        sketch = Sketch(
            name=form.name.data, description=form.description.data,
            user=current_user)
        sketch.status.append(sketch.Status(user=None, status=u'new'))
        # Give the requesting user permissions on the new sketch.
        sketch.grant_permission(current_user, u'read')
        sketch.grant_permission(current_user, u'write')
        sketch.grant_permission(current_user, u'delete')
        db_session.add(sketch)
        db_session.commit()
        return redirect(url_for(u'sketch_views.overview', sketch_id=sketch.id))

    return render_template(
        u'home/home.html', sketches=sketches, form=form, query=query,
        upload_enabled=upload_enabled, last_sketch=last_sketch)
Example #34
0
    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)

        # Refresh cache if timelines have been added/removed from the sketch.
        if cache.graph_config:
            cache_graph_config = json.loads(cache.graph_config)
            if cache_graph_config:
                cache_graph_config = json.loads(cache.graph_config)
                cache_graph_filter = cache_graph_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()

        return self.to_json(cache)
Example #35
0
    def run(self, username, remove):
        """Adds the admin bit to a user."""
        user = User.query.filter_by(username=username).first()

        if not user:
            sys.stdout.write('User [{0:s}] does not exist.\n'.format(username))
            return
        user.admin = not remove
        db_session.add(user)
        db_session.commit()

        if remove:
            sys.stdout.write(
                'User {0:s} is no longer an admin.\n'.format(username))
        else:
            sys.stdout.write('User {0:s} is now an admin.\n'.format(username))
Example #36
0
    def post(self):
        """Handles POST request to the resource.

        Returns:
            A view in JSON (instance of flask.wrappers.Response)

        Raises:
            ApiHTTPError
        """
        UPLOAD_ENABLED = current_app.config[u'UPLOAD_ENABLED']
        UPLOAD_FOLDER = current_app.config[u'UPLOAD_FOLDER']

        form = UploadFileForm()
        if form.validate_on_submit() and UPLOAD_ENABLED:
            from timesketch.lib.tasks import run_plaso
            file_storage = form.file.data
            timeline_name = form.name.data
            # We do not need a human readable filename or
            # datastore index name, so we use UUIDs here.
            filename = unicode(uuid.uuid4().hex)
            index_name = unicode(uuid.uuid4().hex)

            file_path = os.path.join(UPLOAD_FOLDER, filename)
            file_storage.save(file_path)

            search_index = SearchIndex.get_or_create(name=timeline_name,
                                                     description=timeline_name,
                                                     user=current_user,
                                                     index_name=index_name)
            search_index.grant_permission(permission=u'read',
                                          user=current_user)
            search_index.grant_permission(permission=u'write',
                                          user=current_user)
            search_index.grant_permission(permission=u'delete',
                                          user=current_user)
            search_index.set_status(u'processing')
            db_session.add(search_index)
            db_session.commit()

            run_plaso.apply_async((file_path, timeline_name, index_name),
                                  task_id=index_name)

            return self.to_json(search_index,
                                status_code=HTTP_STATUS_CODE_CREATED)
        else:
            raise ApiHTTPError(message=form.errors[u'file'][0],
                               status_code=HTTP_STATUS_CODE_BAD_REQUEST)
Example #37
0
    def delete(self, searchindex_id):
        """Handles DELETE request to the resource."""
        searchindex = SearchIndex.query.get_with_acl(searchindex_id)
        if not searchindex:
            abort(HTTP_STATUS_CODE_NOT_FOUND,
                  "No searchindex found with this ID.")

        if not searchindex.has_permission(current_user, "delete"):
            abort(
                HTTP_STATUS_CODE_FORBIDDEN,
                ("User does not have sufficient access rights to "
                 "delete the search index."),
            )

        if searchindex.get_status.status == "deleted":
            abort(HTTP_STATUS_CODE_BAD_REQUEST,
                  "Search index already deleted.")

        timelines = Timeline.query.filter_by(searchindex=searchindex).all()
        sketches = [
            t.sketch for t in timelines
            if t.sketch and t.sketch.get_status.status != "deleted"
        ]

        if sketches:
            error_strings = ["WARNING: This timeline is in use by:"]
            for sketch in sketches:
                error_strings.append(" * {0:s}".format(sketch.name))
            abort(HTTP_STATUS_CODE_FORBIDDEN, "\n".join(error_strings))

        searchindex.set_status(status="deleted")
        db_session.commit()

        other_indexes = SearchIndex.query.filter_by(
            index_name=searchindex.index_name).all()
        if len(other_indexes) > 1:
            logger.warning("Search index: {0:s} belongs to more than one "
                           "db entry.".format(searchindex.index_name))
            return HTTP_STATUS_CODE_OK

        try:
            self.datastore.client.indices.close(index=searchindex.index_name)
        except opensearchpy.NotFoundError:
            logger.warning("Unable to close index: {0:s}, the index wasn't "
                           "found.".format(searchindex.index_name))

        return HTTP_STATUS_CODE_OK
Example #38
0
    def post(self):
        """Handles POST request to the resource.

        Returns:
            A view in JSON (instance of flask.wrappers.Response)

        Raises:
            ApiHTTPError
        """
        UPLOAD_ENABLED = current_app.config[u'UPLOAD_ENABLED']
        UPLOAD_FOLDER = current_app.config[u'UPLOAD_FOLDER']

        form = UploadFileForm()
        if form.validate_on_submit() and UPLOAD_ENABLED:
            from timesketch.lib.tasks import run_plaso
            file_storage = form.file.data
            timeline_name = form.name.data
            # We do not need a human readable filename or
            # datastore index name, so we use UUIDs here.
            filename = unicode(uuid.uuid4().hex)
            index_name = unicode(uuid.uuid4().hex)

            file_path = os.path.join(UPLOAD_FOLDER, filename)
            file_storage.save(file_path)

            search_index = SearchIndex.get_or_create(
                name=timeline_name, description=timeline_name,
                user=current_user, index_name=index_name)
            search_index.grant_permission(permission=u'read', user=current_user)
            search_index.grant_permission(
                permission=u'write', user=current_user)
            search_index.grant_permission(
                permission=u'delete', user=current_user)
            search_index.set_status(u'processing')
            db_session.add(search_index)
            db_session.commit()

            run_plaso.apply_async(
                (file_path, timeline_name, index_name), task_id=index_name)

            return self.to_json(
                search_index, status_code=HTTP_STATUS_CODE_CREATED)
        else:
            raise ApiHTTPError(
                message=form.errors[u'file'][0],
                status_code=HTTP_STATUS_CODE_BAD_REQUEST)
Example #39
0
    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
Example #40
0
    def add_story(self, title):
        """Add a story to the Sketch.

        Args:
            title: The name of the view.

        Raises:
            ValueError: If both query_string an query_dsl are missing.

        Returns:
            An instance of a Story object.
        """
        story = SQLStory.get_or_create(
            title=title, content='[]', sketch=self.sql_sketch, user=None)
        db_session.add(story)
        db_session.commit()
        return Story(story)
Example #41
0
    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)
Example #42
0
    def post(self, searchindex_id):
        """Handles POST request to the resource.

        Returns:
            A search index in JSON (instance of flask.wrappers.Response)
        """
        form = request.json
        if not form:
            form = request.data

        if not searchindex_id:
            abort(HTTP_STATUS_CODE_BAD_REQUEST,
                  'Need to define a search index ID')

        searchindex = SearchIndex.query.get_with_acl(searchindex_id)
        if not searchindex:
            abort(HTTP_STATUS_CODE_NOT_FOUND,
                  'No searchindex found with this ID.')

        if not searchindex.has_permission(current_user, 'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN,
                  ('User does not have sufficient access rights to '
                   'edit the search index.'))

        if searchindex.get_status.status == 'deleted':
            abort(HTTP_STATUS_CODE_BAD_REQUEST,
                  'Search index is marked deleted, unable to continue.')

        commit_to_db = False
        new_status = form.get('status', '')
        valid_status = ('ready', 'fail', 'processing', 'timeout')
        if new_status and new_status in valid_status:
            searchindex.set_status(status=new_status)
            commit_to_db = True

        description = form.get('description', '')
        if description:
            searchindex.description = description
            commit_to_db = True

        if commit_to_db:
            db_session.add(searchindex)
            db_session.commit()

        return self.to_json(searchindex)
Example #43
0
def explore(sketch_id, view_id=None):
    """Generates the sketch explore view template.

    Returns:
        Template with context.
    """
    sketch = Sketch.query.get_with_acl(sketch_id)
    sketch_timelines = [t.searchindex.index_name for t in sketch.timelines]
    view_form = SaveViewForm()

    # Get parameters from the GET query
    url_query = request.args.get(u'q', u'')
    url_time_start = request.args.get(u'time_start', None)
    url_time_end = request.args.get(u'time_end', None)

    if view_id:
        view = View.query.get(view_id)

        # Check that this view belongs to the sketch
        if view.sketch_id != sketch.id:
            abort(HTTP_STATUS_CODE_NOT_FOUND)

        # Return 404 if view is deleted
        if view.get_status.status == u'deleted':
            return abort(HTTP_STATUS_CODE_NOT_FOUND)
    else:
        view = sketch.get_user_view(current_user)
        if url_query:
            view.query_string = url_query
            query_filter = json.loads(view.query_filter)
            query_filter[u'time_start'] = url_time_start
            query_filter[u'time_end'] = url_time_end
            view.query_filter = json.dumps(query_filter, ensure_ascii=False)

    if not view:
        query_filter = dict(indices=sketch_timelines)
        view = View(
            user=current_user, name=u'', sketch=sketch, query_string=u'',
            query_filter=json.dumps(query_filter, ensure_ascii=False))
        db_session.add(view)
        db_session.commit()

    return render_template(
        u'sketch/explore.html', sketch=sketch, view=view,
        timelines=sketch_timelines, view_form=view_form)
Example #44
0
def timelines(sketch_id):
    """Generates the sketch explore view template.

    Returns:
        Template with context.
    """
    TIMELINES_TO_SHOW = 20

    sketch = Sketch.query.get_with_acl(sketch_id)
    searchindices_in_sketch = [t.searchindex.id for t in sketch.timelines]
    query = request.args.get(u'q', None)
    indices = SearchIndex.all_with_acl(
        current_user).order_by(
            desc(SearchIndex.created_at)).filter(
                not_(SearchIndex.id.in_(searchindices_in_sketch)))
    filtered = False

    if query:
        indices = indices.filter(SearchIndex.name.contains(query)).limit(500)
        filtered = True
    if not filtered:
        indices = indices.limit(TIMELINES_TO_SHOW)

    # Setup the form
    form = AddTimelineForm()
    form.timelines.choices = set((i.id, i.name) for i in indices.all())

    # Create new timeline form POST
    if form.validate_on_submit():
        if not sketch.has_permission(current_user, u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)
        for searchindex_id in form.timelines.data:
            searchindex = SearchIndex.query.get_with_acl(searchindex_id)
            if searchindex not in [t.searchindex for t in sketch.timelines]:
                _timeline = Timeline(
                    name=searchindex.name, description=searchindex.description,
                    sketch=sketch, user=current_user, searchindex=searchindex)
                db_session.add(_timeline)
                sketch.timelines.append(_timeline)
        db_session.commit()
        return redirect(url_for(u'sketch_views.timelines', sketch_id=sketch.id))

    return render_template(
        u'sketch/timelines.html', sketch=sketch, timelines=indices.all(),
        form=form, filtered=filtered)
Example #45
0
    def delete(self, searchindex_id):
        """Handles DELETE request to the resource."""
        searchindex = SearchIndex.query.get_with_acl(searchindex_id)
        if not searchindex:
            abort(HTTP_STATUS_CODE_NOT_FOUND,
                  'No searchindex found with this ID.')

        if not searchindex.has_permission(current_user, 'delete'):
            abort(HTTP_STATUS_CODE_FORBIDDEN,
                  ('User does not have sufficient access rights to '
                   'delete the search index.'))

        if searchindex.get_status.status == 'deleted':
            abort(HTTP_STATUS_CODE_BAD_REQUEST,
                  'Search index already deleted.')

        timelines = Timeline.query.filter_by(searchindex=searchindex).all()
        sketches = [
            t.sketch for t in timelines
            if t.sketch and t.sketch.get_status.status != 'deleted'
        ]

        if sketches:
            error_strings = ['WARNING: This timeline is in use by:']
            for sketch in sketches:
                error_strings.append(' * {0:s}'.format(sketch.name))
            abort(HTTP_STATUS_CODE_FORBIDDEN, '\n'.join(error_strings))

        searchindex.set_status(status='deleted')
        db_session.commit()

        other_indexes = SearchIndex.query.filter_by(
            index_name=searchindex.index_name).all()
        if len(other_indexes) > 1:
            logger.warning('Search index: {0:s} belongs to more than one '
                           'db entry.'.format(searchindex.index_name))
            return HTTP_STATUS_CODE_OK

        try:
            self.datastore.client.indices.close(index=searchindex.index_name)
        except elasticsearch.NotFoundError:
            logger.warning('Unable to close index: {0:s}, the index wasn\'t '
                           'found.'.format(searchindex.index_name))

        return HTTP_STATUS_CODE_OK
Example #46
0
def remove_group_member(group_name, username):
    """Remove a user from a group."""
    group = Group.query.filter_by(name=group_name).first()
    if not group:
        print("No such group.")
        return

    user = User.query.filter_by(username=username).first()
    if not user:
        print("User does not exist.")
        return

    try:
        user.groups.remove(group)
        db_session.commit()
        print("Removed user from group.")
    except ValueError:
        print("User is not a member of the group.")
Example #47
0
    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 = StoryForm.build(request)
        if form.validate_on_submit():
            sketch = Sketch.query.get_with_acl(sketch_id)
            story = Story(
                title=u'', content=u'', sketch=sketch, user=current_user)
            db_session.add(story)
            db_session.commit()
            return self.to_json(story, status_code=HTTP_STATUS_CODE_CREATED)
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Example #48
0
def add_group_member(group_name, username):
    """Add a user to a group."""
    group = Group.query.filter_by(name=group_name).first()
    if not group:
        print("No such group.")
        return

    user = User.query.filter_by(username=username).first()
    if not user:
        print("User does not exist.")
        return

    try:
        user.groups.append(group)
        db_session.commit()
        print("Added user to group.")
    except IntegrityError:
        print("User is already a member of the group.")
Example #49
0
    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 = StoryForm.build(request)
        if form.validate_on_submit():
            sketch = Sketch.query.get_with_acl(sketch_id)
            story = Story(
                title=u'', content=u'', sketch=sketch, user=current_user)
            db_session.add(story)
            db_session.commit()
            return self.to_json(story, status_code=HTTP_STATUS_CODE_CREATED)
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Example #50
0
def run_csv(source_file_path, timeline_name, index_name, username=None):
    """Create a Celery task for processing a CSV file.

    Args:
        source_file_path: Path to CSV file.
        timeline_name: Name of the Timesketch timeline.
        index_name: Name of the datastore index.
        username: Username of the user who will own the timeline.

    Returns:
        Dictionary with count of processed events.
    """
    flush_interval = 1000  # events to queue before bulk index
    event_type = u'generic_event'  # Document type for Elasticsearch
    app = create_app()

    # Log information to Celery
    logging.info(u'Index name: %s', index_name)
    logging.info(u'Timeline name: %s', timeline_name)
    logging.info(u'Flush interval: %d', flush_interval)
    logging.info(u'Document type: %s', event_type)
    logging.info(u'Owner: %s', username)

    es = ElasticsearchDataStore(
        host=current_app.config[u'ELASTIC_HOST'],
        port=current_app.config[u'ELASTIC_PORT'])

    es.create_index(index_name=index_name, doc_type=event_type)
    for event in read_and_validate_csv(source_file_path):
        es.import_event(
            flush_interval, index_name, event_type, event)

    # Import the remaining events
    total_events = es.import_event(flush_interval, index_name, event_type)

    # We are done so let's remove the processing status flag
    with app.app_context():
        search_index = SearchIndex.query.filter_by(
            index_name=index_name).first()
        search_index.status.remove(search_index.status[0])
        db_session.add(search_index)
        db_session.commit()

    return {u'Events processed': total_events}
Example #51
0
    def create_aggregation_from_form(sketch, form):
        """Creates an aggregation from form data.

        Args:
            sketch: Instance of timesketch.models.sketch.Sketch
            form: Instance of timesketch.lib.forms.SaveAggregationForm

        Returns:
            An aggregation (instance of timesketch.models.sketch.Aggregation)
        """
        # Default to user supplied data
        name = form.get('name', '')
        description = form.get('description', '')
        agg_type = form.get('agg_type', '')
        parameter_data = form.get('parameters', {})
        parameters = json.dumps(parameter_data, ensure_ascii=False)
        chart_type = form.get('chart_type', '')
        view_id = form.get('view_id')

        # Create the aggregation in the database
        aggregation = Aggregation(
            name=name,
            description=description,
            agg_type=agg_type,
            parameters=parameters,
            chart_type=chart_type,
            user=current_user,
            sketch=sketch,
            view=view_id
        )

        labels = form.get('labels', '')
        if labels:
            for label in json.loads(labels):
                if aggregation.has_label(label):
                    continue
                aggregation.add_label(label)

        db_session.add(aggregation)
        db_session.commit()

        return aggregation
Example #52
0
    def grant_permission(self, permission, user=None, group=None):
        """Grant permission to a user or group  with the specific permission.

        Args:
            permission: Permission as string (read, write or delete)
            user: A user (Instance of timesketch.models.user.User)
            group: A group (Instance of timesketch.models.user.Group)
        """
        # Grant permission to a group.
        if group and not self._get_ace(permission, group=group):
            self.acl.append(
                self.AccessControlEntry(permission=permission, group=group))
            db_session.commit()
            return

        # Grant permission to a user.
        if not self._get_ace(permission, user=user, check_group=False):
            self.acl.append(
                self.AccessControlEntry(permission=permission, user=user))
            db_session.commit()
Example #53
0
    def post(self):
        """Handles POST request to the resource.

        Returns:
            A sketch in JSON (instance of flask.wrappers.Response)
        """
        form = NameDescriptionForm.build(request)
        if form.validate_on_submit():
            sketch = Sketch(
                name=form.name.data, description=form.description.data,
                user=current_user)
            sketch.status.append(sketch.Status(user=None, status=u'new'))
            # Give the requesting user permissions on the new sketch.
            sketch.grant_permission(permission=u'read', user=current_user)
            sketch.grant_permission(permission=u'write', user=current_user)
            sketch.grant_permission(permission=u'delete', user=current_user)
            db_session.add(sketch)
            db_session.commit()
            return self.to_json(sketch, status_code=HTTP_STATUS_CODE_CREATED)
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Example #54
0
    def grant_permission(self, permission, user=None, group=None):
        """Grant permission to a user or group  with the specific permission.

        Args:
            permission: Permission as string (read, write or delete)
            user: A user (Instance of timesketch.models.user.User)
            group: A group (Instance of timesketch.models.user.Group)
        """
        # Grant permission to a group.
        if group and not self._get_ace(permission, group=group):
            self.acl.append(
                self.AccessControlEntry(permission=permission, group=group))
            db_session.commit()
            return

        # Grant permission to a user.
        if not self._get_ace(permission, user=user, check_group=False):
            self.acl.append(
                self.AccessControlEntry(permission=permission, user=user))
            db_session.commit()
Example #55
0
    def post(self):
        """Handles POST request to the resource.

        Returns:
            A sketch in JSON (instance of flask.wrappers.Response)
        """
        form = NameDescriptionForm.build(request)
        if form.validate_on_submit():
            sketch = Sketch(name=form.name.data,
                            description=form.description.data,
                            user=current_user)
            sketch.status.append(sketch.Status(user=None, status=u'new'))
            # Give the requesting user permissions on the new sketch.
            sketch.grant_permission(permission=u'read', user=current_user)
            sketch.grant_permission(permission=u'write', user=current_user)
            sketch.grant_permission(permission=u'delete', user=current_user)
            db_session.add(sketch)
            db_session.commit()
            return self.to_json(sketch, status_code=HTTP_STATUS_CODE_CREATED)
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Example #56
0
    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)
        timeline = Timeline.query.get(timeline_id)

        # Check that this timeline belongs to the sketch
        if timeline.sketch_id != sketch.id:
            abort(HTTP_STATUS_CODE_NOT_FOUND)

        if not sketch.has_permission(user=current_user, permission=u'write'):
            abort(HTTP_STATUS_CODE_FORBIDDEN)

        sketch.timelines.remove(timeline)
        db_session.commit()
        return HTTP_STATUS_CODE_OK
Example #57
0
    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 = SaveViewForm.build(request)
        if form.validate_on_submit():
            sketch = Sketch.query.get_with_acl(sketch_id)
            view = View(
                name=form.name.data, sketch=sketch, user=current_user,
                query_string=form.query.data,
                query_filter=json.dumps(form.filter.data, ensure_ascii=False))
            db_session.add(view)
            db_session.commit()
            return self.to_json(view, status_code=HTTP_STATUS_CODE_CREATED)
        return abort(HTTP_STATUS_CODE_BAD_REQUEST)
Example #58
0
  def WriteHeader(self):
    """Setup the Elasticsearch index and the Timesketch database object.

    Creates the Elasticsearch index with Timesketch specific settings and the
    Timesketch SearchIndex database object.
    """
    # This cannot be static because we use the value of self._doc_type from
    # arguments.
    _document_mapping = {
        self._doc_type: {
            u'_timestamp': {
                u'enabled': True,
                u'path': u'datetime',
                u'format': u'date_time_no_millis'
            },
            u'properties': {u'timesketch_label': {u'type': u'nested'}}
        }
    }

    if not self._elastic_db.client.indices.exists(self._index_name):
      try:
        self._elastic_db.client.indices.create(
            index=self._index_name, body={u'mappings': _document_mapping})
      except elastic_exceptions.ConnectionError as exception:
        logging.error((
            u'Unable to proceed, cannot connect to Timesketch backend '
            u'with error: {0:s}.\nPlease verify connection.').format(exception))
        raise RuntimeError(u'Unable to connect to Timesketch backend.')

    with self._timesketch.app_context():
      search_index = SearchIndex.get_or_create(
          name=self._timeline_name, description=self._timeline_name, user=None,
          index_name=self._index_name)
      # Grant all users read permission on the mapping object and set status.
      search_index.grant_permission(None, u'read')
      search_index.set_status(u'processing')
      # Save the mapping object to the Timesketch database.
      db_session.add(search_index)
      db_session.commit()

    logging.info(u'Adding events to Timesketch..')
Example #59
0
  def Close(self):
    """Closes the connection to TimeSketch Elasticsearch database.

    Sends the remaining events for indexing and adds the timeline to Timesketch.
    """
    self._FlushEventsToElasticsearch()

    with self._timesketch.app_context():
      # Get Timesketch user object, or None if user do not exist. This is a
      # SQLAlchemy query against the Timesketch database.
      user_query = User.query.filter_by(username=self._timeline_owner)
      user = user_query.first()
      search_index = SearchIndex(
          name=self._timeline_name, description=self._timeline_name, user=user,
          index_name=self._index_name)

    # Grant all users read permission on the mapping object.
    search_index.grant_permission(None, u'read')
    # Save the mapping object to the Timesketch database.
    db_session.add(search_index)
    db_session.commit()
Example #60
0
    def post(self, sketch_id):
        """Handles POST request to the resource.

        Returns:
            A sketch in JSON (instance of flask.wrappers.Response)

        Raises:
            ApiHTTPError
        """
        sketch = Sketch.query.get_with_acl(sketch_id)
        searchindices_in_sketch = [t.searchindex.id for t in sketch.timelines]
        indices = SearchIndex.all_with_acl(
            current_user).order_by(
                desc(SearchIndex.created_at)).filter(
                    not_(SearchIndex.id.in_(searchindices_in_sketch)))

        add_timeline_form = AddTimelineForm.build(request)
        add_timeline_form.timelines.choices = set(
            (i.id, i.name) for i in indices.all())

        if add_timeline_form.validate_on_submit():
            if not sketch.has_permission(current_user, u'write'):
                abort(HTTP_STATUS_CODE_FORBIDDEN)
            for searchindex_id in add_timeline_form.timelines.data:
                searchindex = SearchIndex.query.get_with_acl(searchindex_id)
                if searchindex not in [t.searchindex for t in sketch.timelines]:
                    _timeline = Timeline(
                        name=searchindex.name,
                        description=searchindex.description,
                        sketch=sketch,
                        user=current_user,
                        searchindex=searchindex)
                    db_session.add(_timeline)
                    sketch.timelines.append(_timeline)
            db_session.commit()
            return self.to_json(sketch, status_code=HTTP_STATUS_CODE_CREATED)
        else:
            raise ApiHTTPError(
                message=add_timeline_form.errors,
                status_code=HTTP_STATUS_CODE_BAD_REQUEST)