Esempio n. 1
0
    def test_it_fetches_and_returns_the_annotation(self, db_session):
        annotation = Annotation(userid='luke')
        db_session.add(annotation)
        db_session.flush()

        actual = storage.fetch_annotation(db_session, annotation.id)
        assert annotation == actual
Esempio n. 2
0
    def test_it_fetches_and_returns_the_annotation(self, db_session):
        annotation = Annotation(userid='luke')
        db_session.add(annotation)
        db_session.flush()

        actual = storage.fetch_annotation(db_session, annotation.id)
        assert annotation == actual
Esempio n. 3
0
def test_fetch_annotation_elastic(postgres_enabled, ElasticAnnotation):
    postgres_enabled.return_value = False
    ElasticAnnotation.fetch.return_value = mock.Mock()

    actual = storage.fetch_annotation(DummyRequest(), '123')

    ElasticAnnotation.fetch.assert_called_once_with('123')
    assert ElasticAnnotation.fetch.return_value == actual
Esempio n. 4
0
    def test_elastic(self, postgres_enabled, models):
        postgres_enabled.return_value = False
        models.elastic.Annotation.fetch.return_value = mock.Mock()

        actual = storage.fetch_annotation(DummyRequest(), '123')

        models.elastic.Annotation.fetch.assert_called_once_with('123')
        assert models.elastic.Annotation.fetch.return_value == actual
Esempio n. 5
0
def test_fetch_annotation_elastic(postgres_enabled, ElasticAnnotation):
    postgres_enabled.return_value = False
    ElasticAnnotation.fetch.return_value = mock.Mock()

    actual = storage.fetch_annotation(DummyRequest(), '123')

    ElasticAnnotation.fetch.assert_called_once_with('123')
    assert ElasticAnnotation.fetch.return_value == actual
Esempio n. 6
0
    def test_elastic(self, postgres_enabled, models):
        postgres_enabled.return_value = False
        models.elastic.Annotation.fetch.return_value = mock.Mock()

        actual = storage.fetch_annotation(DummyRequest(), '123')

        models.elastic.Annotation.fetch.assert_called_once_with('123')
        assert models.elastic.Annotation.fetch.return_value == actual
Esempio n. 7
0
def handle_annotation_event(message, socket):
    """
    Get message about annotation event `message` to be sent to `socket`.

    Inspects the embedded annotation event and decides whether or not the
    passed socket should receive notification of the event.

    Returns None if the socket should not receive any message about this
    annotation event, otherwise a dict containing information about the event.
    """
    action = message['action']

    if action == 'read':
        return None

    if message['src_client_id'] == socket.client_id:
        return None

    # We don't send anything until we have received a filter from the client
    if socket.filter is None:
        return None

    notification = {
        'type': 'annotation-notification',
        'options': {
            'action': action
        },
    }
    id_ = message['annotation_id']

    # Return early when action is delete
    serialized = None
    if action == 'delete':
        serialized = message['annotation_dict']
    else:
        annotation = storage.fetch_annotation(socket.request.db, id_)
        if annotation is None:
            return None

        serialized = presenters.AnnotationJSONPresenter(
            socket.request, annotation).asdict()

    userid = serialized.get('user')
    if has_nipsa(userid) and socket.request.authenticated_userid != userid:
        return None

    permissions = serialized.get('permissions')
    if not _authorized_to_read(socket.request, permissions):
        return None

    if not socket.filter.match(serialized, action):
        return None

    notification['payload'] = [serialized]
    if action == 'delete':
        notification['payload'] = [{'id': id_}]
    return notification
Esempio n. 8
0
def handle_annotation_event(message, socket):
    """
    Get message about annotation event `message` to be sent to `socket`.

    Inspects the embedded annotation event and decides whether or not the
    passed socket should receive notification of the event.

    Returns None if the socket should not receive any message about this
    annotation event, otherwise a dict containing information about the event.
    """
    action = message['action']

    if action == 'read':
        return None

    if message['src_client_id'] == socket.client_id:
        return None

    # We don't send anything until we have received a filter from the client
    if socket.filter is None:
        return None

    notification = {
        'type': 'annotation-notification',
        'options': {'action': action},
    }
    id_ = message['annotation_id']

    # Return early when action is delete
    serialized = None
    if action == 'delete':
        serialized = message['annotation_dict']
    else:
        annotation = storage.fetch_annotation(socket.request.db, id_)
        if annotation is None:
            return None

        serialized = presenters.AnnotationJSONPresenter(
            socket.request, annotation).asdict()

    userid = serialized.get('user')
    nipsa_service = socket.request.find_service(name='nipsa')
    if nipsa_service.is_flagged(userid) and socket.request.authenticated_userid != userid:
        return None

    permissions = serialized.get('permissions')
    if not _authorized_to_read(socket.request, permissions):
        return None

    if not socket.filter.match(serialized, action):
        return None

    notification['payload'] = [serialized]
    if action == 'delete':
        notification['payload'] = [{'id': id_}]
    return notification
Esempio n. 9
0
def test_fetch_annotation_postgres(postgres_enabled):
    request = DummyRequest(db=db.Session)
    postgres_enabled.return_value = True

    annotation = Annotation(userid='luke')
    db.Session.add(annotation)
    db.Session.flush()

    actual = storage.fetch_annotation(request, annotation.id)
    assert annotation == actual
Esempio n. 10
0
    def test_postgres(self, postgres_enabled):
        request = DummyRequest(db=db.Session)
        postgres_enabled.return_value = True

        annotation = Annotation(userid='luke')
        db.Session.add(annotation)
        db.Session.flush()

        actual = storage.fetch_annotation(request, annotation.id)
        assert annotation == actual
Esempio n. 11
0
 def __getitem__(self, id):
     """
     Fetch Annotation subresource, if it exists.
     """
     model = storage.fetch_annotation(id)
     if model is None:
         raise KeyError()
     resource = self.factory(id, model)
     resource.__parent__ = self
     resource.__name__ = id
     return resource
Esempio n. 12
0
    def test_it_uses_elastic_if_postgres_arg_is_False(self,
                                                      postgres_enabled,
                                                      models):
        """If postgres=False it uses elastic even if the feature flag is on."""
        postgres_enabled.return_value = True  # The feature flag is on.
        models.elastic.Annotation.fetch.return_value = mock.Mock()

        actual = storage.fetch_annotation(
            DummyRequest(), '123', _postgres=False)

        models.elastic.Annotation.fetch.assert_called_once_with('123')
        assert models.elastic.Annotation.fetch.return_value == actual
Esempio n. 13
0
    def test_it_uses_postgres_if_postgres_arg_is_True(self, postgres_enabled):
        """If postgres=True it uses postgres even if feature flag is off."""
        request = DummyRequest(db=db.Session)
        postgres_enabled.return_value = False  # The feature flag is off.
        annotation = Annotation(userid='luke')
        db.Session.add(annotation)
        db.Session.flush()

        actual = storage.fetch_annotation(
            request, annotation.id, _postgres=True)

        assert annotation == actual
Esempio n. 14
0
    def test_it_uses_elastic_if_postgres_arg_is_False(self,
                                                      postgres_enabled,
                                                      models):
        """If postgres=False it uses elastic even if the feature flag is on."""
        postgres_enabled.return_value = True  # The feature flag is on.
        models.elastic.Annotation.fetch.return_value = mock.Mock()

        actual = storage.fetch_annotation(
            DummyRequest(), '123', _postgres=False)

        models.elastic.Annotation.fetch.assert_called_once_with('123')
        assert models.elastic.Annotation.fetch.return_value == actual
Esempio n. 15
0
    def test_it_uses_postgres_if_postgres_arg_is_True(self, postgres_enabled):
        """If postgres=True it uses postgres even if feature flag is off."""
        request = DummyRequest(db=db.Session)
        postgres_enabled.return_value = False  # The feature flag is off.
        annotation = Annotation(userid='luke')
        db.Session.add(annotation)
        db.Session.flush()

        actual = storage.fetch_annotation(
            request, annotation.id, _postgres=True)

        assert annotation == actual
Esempio n. 16
0
def generate_notifications(request, annotation, action):
    # Only send notifications when new annotations are created
    if action != 'create':
        return

    # If the annotation doesn't have a parent, we can't find its parent, or we
    # have no idea who the author of the parent is, then we can't send a
    # notification email.
    parent_id = annotation.parent_id
    if parent_id is None:
        return
    parent = storage.fetch_annotation(request, parent_id)
    if parent is None or 'user' not in parent:
        return

    # We don't send replies to the author of the parent unless they're going to
    # be able to read it. That means there must be some overlap between the set
    # of effective principals of the parent's author, and the read permissions
    # of the reply.
    child_read_permissions = annotation.get('permissions', {}).get('read', [])
    parent_principals = auth.effective_principals(parent['user'], request)
    read_principals = translate_annotation_principals(child_read_permissions)
    if not set(parent_principals).intersection(read_principals):
        return

    # Store the parent values as additional data
    data = {
        'parent': parent
    }

    subscriptions = Subscriptions.get_active_subscriptions_for_a_type(
        types.REPLY_TYPE)
    for subscription in subscriptions:
        data['subscription'] = subscription.__json__(request)

        # Validate annotation
        if check_conditions(annotation, data):
            try:
                subject, text, html, recipients = render_reply_notification(
                    request,
                    annotation,
                    parent)
                yield subject, text, html, recipients
            # ToDo: proper exception handling here
            except TemplateRenderException:
                log.exception('Failed to render subscription'
                              ' template %s', subscription)
            except:
                log.exception('Unknown error when trying to render'
                              ' subscription template %s', subscription)
Esempio n. 17
0
def generate_notifications(request, annotation, action):
    # Only send notifications when new annotations are created
    if action != 'create':
        return

    # If the annotation doesn't have a parent, we can't find its parent, or we
    # have no idea who the author of the parent is, then we can't send a
    # notification email.
    parent_id = annotation.parent_id
    if parent_id is None:
        return
    parent = storage.fetch_annotation(request, parent_id)
    if parent is None or 'user' not in parent:
        return

    # We don't send replies to the author of the parent unless they're going to
    # be able to read it. That means there must be some overlap between the set
    # of effective principals of the parent's author, and the read permissions
    # of the reply.
    child_read_permissions = annotation.get('permissions', {}).get('read', [])
    parent_principals = auth.effective_principals(parent['user'], request)
    read_principals = translate_annotation_principals(child_read_permissions)
    if not set(parent_principals).intersection(read_principals):
        return

    # Store the parent values as additional data
    data = {
        'parent': parent
    }

    subscriptions = Subscriptions.get_active_subscriptions_for_a_type(
        types.REPLY_TYPE)
    for subscription in subscriptions:
        data['subscription'] = subscription.__json__(request)

        # Validate annotation
        if check_conditions(annotation, data):
            try:
                subject, text, html, recipients = render_reply_notification(
                    request,
                    annotation,
                    parent)
                yield subject, text, html, recipients
            # ToDo: proper exception handling here
            except TemplateRenderException:
                log.exception('Failed to render subscription'
                              ' template %s', subscription)
            except:
                log.exception('Unknown error when trying to render'
                              ' subscription template %s', subscription)
Esempio n. 18
0
def generate_notifications(request, annotation, action):
    # Only send notifications when new annotations are created
    if action != 'create':
        return

    # If the annotation doesn't have a parent, we can't find its parent, or we
    # have no idea who the author of the parent is, then we can't send a
    # notification email.
    parent_id = annotation.parent_id
    if parent_id is None:
        return
    parent = storage.fetch_annotation(request, parent_id)
    if parent is None or not annotation.userid:
        return

    # Don't send reply notifications to the author of the parent annotation if
    # the author doesn't have permission to read the reply.
    if not auth.has_permission(request, annotation, parent.userid, 'read'):
        return

    # Store the parent values as additional data
    data = {
        'parent': parent
    }

    subscriptions = Subscriptions.get_active_subscriptions_for_a_type(
        types.REPLY_TYPE)
    for subscription in subscriptions:
        data['subscription'] = subscription.__json__(request)

        # Validate annotation
        if check_conditions(annotation, data):
            try:
                subject, text, html, recipients = render_reply_notification(
                    request,
                    annotation,
                    parent)
                yield subject, text, html, recipients
            # ToDo: proper exception handling here
            except TemplateRenderException:
                log.exception('Failed to render subscription'
                              ' template %s', subscription)
            except:
                log.exception('Unknown error when trying to render'
                              ' subscription template %s', subscription)
Esempio n. 19
0
def generate_notifications(request, annotation, action):
    # Only send notifications when new annotations are created
    if action != 'create':
        return

    # If the annotation doesn't have a parent, we can't find its parent, or we
    # have no idea who the author of the parent is, then we can't send a
    # notification email.
    parent_id = annotation.parent_id
    if parent_id is None:
        return
    parent = storage.fetch_annotation(request, parent_id)
    if parent is None or not annotation.userid:
        return

    # Don't send reply notifications to the author of the parent annotation if
    # the author doesn't have permission to read the reply.
    if not auth.has_permission(request, annotation, parent.userid, 'read'):
        return

    # Store the parent values as additional data
    data = {'parent': parent}

    subscriptions = Subscriptions.get_active_subscriptions_for_a_type(
        types.REPLY_TYPE)
    for subscription in subscriptions:
        data['subscription'] = subscription.__json__(request)

        # Validate annotation
        if check_conditions(annotation, data):
            try:
                subject, text, html, recipients = render_reply_notification(
                    request, annotation, parent)
                yield subject, text, html, recipients
            # ToDo: proper exception handling here
            except TemplateRenderException:
                log.exception('Failed to render subscription'
                              ' template %s', subscription)
            except:
                log.exception(
                    'Unknown error when trying to render'
                    ' subscription template %s', subscription)
Esempio n. 20
0
def send_reply_notifications(event,
                             get_notification=reply.get_notification,
                             generate_mail=emails.reply_notification.generate,
                             send=mailer.send.delay):
    """Queue any reply notification emails triggered by an annotation event."""
    request = event.request
    annotation = storage.fetch_annotation(event.request.db,
                                          event.annotation_id)
    action = event.action
    try:
        notification = get_notification(request, annotation, action)
        if notification is None:
            return
        send_params = generate_mail(request, notification)
        send(*send_params)
    # We don't want any exceptions thrown by this code to cause the annotation
    # CRUD action to fail, but we do want to collect the error in Sentry, so we
    # explicitly wrap this here.
    #
    # FIXME: Fix the underlying bugs and remove this try/except.
    except Exception:
        event.request.sentry.captureException()
        if event.request.debug:
            raise
Esempio n. 21
0
 def test_it_does_not_crash_if_id_is_invalid(self, db_session):
     assert storage.fetch_annotation(db_session, 'foo') is None
Esempio n. 22
0
 def test_it_does_not_crash_if_id_is_invalid(self, db_session):
     assert storage.fetch_annotation(db_session, 'foo') is None
Esempio n. 23
0
 def __getitem__(self, id):
     annotation = storage.fetch_annotation(self.request, id)
     if annotation is None:
         raise KeyError()
     return annotation
Esempio n. 24
0
File: reply.py Progetto: bZichett/h
def get_notification(request, annotation, action):
    """
    Check if the passed annotation and action pair should send a notification.

    Checks to see if the annotation event represented by the passed annotation
    and action should trigger a notification. If it should, this function
    returns the relevant :py:class:`~h.notification.reply.Notification` object.
    Otherwise, it returns None.

    :param request: the current request object
    :type request: pyramid.request.Request
    :param annotation: the reply annotation
    :type annotation: h.api.models.elastic.Annotation or h.models.Annotation
    :param action: the event action
    :type action: str

    :returns: a :py:class:`~h.notification.reply.Notification`, or None
    """
    # Only send notifications when new annotations are created
    if action != 'create':
        return

    # If the annotation doesn't have a parent, or we can't find its parent,
    # then we can't send a notification email.
    parent_id = annotation.parent_id
    if parent_id is None:
        return

    # Now we know we're dealing with a reply
    reply = annotation

    parent = storage.fetch_annotation(request.db, parent_id)
    if parent is None:
        return

    # If the parent user doesn't exist (anymore), we can't send an email.
    parent_user = accounts.get_user(parent.userid, request)
    if parent_user is None:
        return

    # If the reply user doesn't exist (anymore), we can't send an email, but
    # this would be super weird, so log a warning.
    reply_user = accounts.get_user(reply.userid, request)
    if reply_user is None:
        log.warn('user who just replied no longer exists: %s', reply.userid)
        return

    # Do not notify users about their own replies
    if parent_user == reply_user:
        return

    # Don't send reply notifications to the author of the parent annotation if
    # the reply was private.
    if not reply.shared:
        return

    # FIXME: we should be retrieving the document from the root annotation, not
    # the reply, and dealing with the possibility that we have no document
    # metadata.
    if reply.document is None:
        return

    # Bail if there is no active 'reply' subscription for the user being
    # replied to.
    sub = request.db.query(Subscriptions).filter_by(active=True,
                                                    type='reply',
                                                    uri=parent.userid).first()
    if sub is None:
        return

    return Notification(reply, reply_user, parent, parent_user, reply.document)
Esempio n. 25
0
def add_annotation(id_):
    annotation = storage.fetch_annotation(celery.request.db, id_)
    index(celery.request.es, annotation, celery.request)
Esempio n. 26
0
def get_notification(request, annotation, action):
    """
    Check if the passed annotation and action pair should send a notification.

    Checks to see if the annotation event represented by the passed annotation
    and action should trigger a notification. If it should, this function
    returns the relevant :py:class:`~h.notification.reply.Notification` object.
    Otherwise, it returns None.

    :param request: the current request object
    :type request: pyramid.request.Request
    :param annotation: the reply annotation
    :type annotation: h.api.models.elastic.Annotation or h.models.Annotation
    :param action: the event action
    :type action: str

    :returns: a :py:class:`~h.notification.reply.Notification`, or None
    """
    # Only send notifications when new annotations are created
    if action != 'create':
        return

    # If the annotation doesn't have a parent, or we can't find its parent,
    # then we can't send a notification email.
    parent_id = annotation.parent_id
    if parent_id is None:
        return

    # Now we know we're dealing with a reply
    reply = annotation

    parent = storage.fetch_annotation(request.db, parent_id)
    if parent is None:
        return

    # If the parent user doesn't exist (anymore), we can't send an email.
    parent_user = accounts.get_user(parent.userid, request)
    if parent_user is None:
        return

    # If the reply user doesn't exist (anymore), we can't send an email, but
    # this would be super weird, so log a warning.
    reply_user = accounts.get_user(reply.userid, request)
    if reply_user is None:
        log.warn('user who just replied no longer exists: %s', reply.userid)
        return

    # Do not notify users about their own replies
    if parent_user == reply_user:
        return

    # Don't send reply notifications to the author of the parent annotation if
    # the reply was private.
    if not reply.shared:
        return

    # FIXME: we should be retrieving the document from the root annotation, not
    # the reply, and dealing with the possibility that we have no document
    # metadata.
    if reply.document is None:
        return

    # Bail if there is no active 'reply' subscription for the user being
    # replied to.
    sub = request.db.query(Subscriptions).filter_by(active=True,
                                                    type='reply',
                                                    uri=parent.userid).first()
    if sub is None:
        return

    return Notification(reply, reply_user, parent, parent_user, reply.document)