Exemplo n.º 1
0
 def __init__(self, user_id: str, user_type: str, token: str = None):
     self.user = Entity(user_id, user_type, token=token)
     self.token = token
     self.timeline_storage = MongoTimelineStorage(user_id, user_type)
     self.activity_storage = MongoActivityStorage()
     self.timeline = None
     self.cache = TTLCache(1000, 600)
Exemplo n.º 2
0
def test_entity_fetch_name_user(mock_valid_users):
    user_name = "Fake User"
    user_id = "fake"
    mock_valid_users({user_id: user_name})
    e = Entity(user_id, "user")
    e._fetch_name()
    assert e.name == user_name
Exemplo n.º 3
0
 def deserialize(cls, serial: str, token: str = None) -> N:
     """
     Deserializes and returns a new Notification instance.
     """
     try:
         assert serial
     except AssertionError:
         raise InvalidNotificationError(
             "Can't deserialize an input of 'None'")
     try:
         struct = json.loads(serial)
     except json.JSONDecodeError:
         raise InvalidNotificationError(
             "Can only deserialize a JSON string")
     required_keys = set(['a', 'v', 'o', 's', 'l', 't', 'c', 'i', 'e'])
     missing_keys = required_keys.difference(struct.keys())
     if missing_keys:
         raise InvalidNotificationError(
             'Missing keys: {}'.format(missing_keys))
     users = [Entity.from_str(u, token=token) for u in struct.get('u', [])]
     target = [Entity.from_str(t, token=token) for t in struct.get('t', [])]
     deserial = cls(Entity.from_str(struct['a'], token=token),
                    str(struct['v']),
                    Entity.from_str(struct['o'], token=token),
                    struct['s'],
                    level=str(struct['l']),
                    target=target,
                    context=struct.get('n'),
                    external_key=struct.get('x'),
                    users=users)
     deserial.created = struct['c']
     deserial.id = struct['i']
     deserial.expires = struct['e']
     return deserial
Exemplo n.º 4
0
def test_entity_to_dict(e_id, e_type, name):
    e = Entity(e_id, e_type, name=name)
    d = {"id": e_id, "type": e_type}
    d_name = d.copy()
    d_name["name"] = name
    assert e.to_dict() == d
    assert e.to_dict(with_name=True) == d_name
Exemplo n.º 5
0
 def update_entity_names(notes: List[N], token: str = None) -> None:
     entities = list()
     for n in notes:
         entities.append(n.actor)
         entities.append(n.object)
         entities = entities + n.target
         entities = entities + n.users
     Entity.fetch_entity_names(entities, token)
Exemplo n.º 6
0
def test_entity_validate_workspace_ok(mock_workspace_info):
    ws_id = 5
    info = [
        5, "A_Workspace", "owner", "Timestamp", 18, "a", "n", "unlocked", {}
    ]
    mock_workspace_info(info)
    e = Entity(ws_id, "workspace")
    assert e.validate() is True
Exemplo n.º 7
0
def test_entity_validate_narrative_ok(mock_workspace_info):
    ws_id = 5
    info = [
        5, "A_Workspace", "owner", "Timestamp", 18, "a", "n", "unlocked", {
            "narrative": "1",
            "narrative_nice_name": "My Narrative"
        }
    ]
    mock_workspace_info(info)
    e = Entity(ws_id, "narrative")
    assert e.validate() is True
Exemplo n.º 8
0
Arquivo: util.py Projeto: kbase/feeds
def parse_notification_params(params: dict, is_global: bool=False) -> dict:
    """
    Parses and verifies all the notification params are present.
    Raises a MissingParameter error otherwise.
    Returns the params after parsing (currently does nothing, but if
    transformations are needed in the future, here's where that happens).
    In total, can raise:
    MissingParameterError (if required params are missing)
    IllegalParameterError (if the wrong types are present)
    EntityValidationError (if an Entity is malformed)
    """
    # * `actor` - an Entity structure - gets turned into an Entity object when returned
    # * `type` - one of the type keywords
    # * `target` - optional, a list of Entity structures. This gets turned into a list of
    #   entity object on return
    # * `object` - object of the notice, an Entity structure. For invitations, the group to be
    #   invited to. For narratives, the narrative UPA. Gets turned into an Entity object
    #   when returned
    # * `users` - a list of Entity objects, should be of either type user or group
    # * `level` - alert, error, warning, or request.
    # * `context` - optional, context of the notification, otherwise it'll be
    #   autogenerated from the info above.

    if not isinstance(params, dict):
        raise IllegalParameterError('Expected a JSON object as an input.')
    required_list = ['verb', 'level']
    if not is_global:
        required_list = required_list + ['actor', 'source', 'object']
    missing = [r for r in required_list if r not in params or params.get(r) is None]
    if missing:
        raise MissingParameterError("Missing parameter{} - {}".format(
            "s" if len(missing) > 1 else '',
            ", ".join(missing)
        ))
    if not is_global:
        # do the entity transformations
        # If there are any EntityValidationErrors, they'll pop on up.
        params["actor"] = Entity.from_dict(params["actor"])
        params["object"] = Entity.from_dict(params["object"])
        if "target" in params:
            target = params["target"]
            if isinstance(target, list):
                params["target"] = [Entity.from_dict(t) for t in target]
            else:
                raise IllegalParameterError("Expected target to be a list of Entity structures.")
        if "users" in params:
            users = params["users"]
            if isinstance(users, list):
                params["users"] = [Entity.from_dict(u) for u in users]
            else:
                raise IllegalParameterError("Expected users to be a list of Entity structures.")
    return params
Exemplo n.º 9
0
def test_entity_from_dict():
    d = {"id": "foo", "type": "user"}
    e = Entity.from_dict(d)
    assert e.to_dict() == d
    assert e.id == d['id']
    assert e.type == d['type']

    d = {"id": "bar", "type": "group", "name": "Bar Group"}
    e = Entity.from_dict(d)
    assert e.to_dict(with_name=True) == d
    assert e.id == d['id']
    assert e.type == d['type']
    assert e.name == d['name']
Exemplo n.º 10
0
def test_entity_init_autovalidate(mock_valid_users):
    user_id = "fake"
    user_name = "Fake User"
    mock_valid_users({user_id: user_name})
    e = Entity(user_id, "user", auto_validate=True)
    assert e.id == user_id
    assert e.type == "user"
Exemplo n.º 11
0
def test_entity_from_str():
    eid = "foo"
    etype = "user"
    s = etype + STR_SEPARATOR + eid
    e = Entity.from_str(s)
    assert e.id == eid
    assert e.type == etype
Exemplo n.º 12
0
def add_global_notification():
    token = get_auth_token(request)
    if not is_feeds_admin(token):
        raise InvalidTokenError(
            "You do not have permission to create a global notification!")

    params = parse_notification_params(json.loads(request.get_data()),
                                       is_global=True)
    new_note = Notification(Entity('kbase', 'admin'),
                            params.get('verb'),
                            Entity('kbase', 'admin'),
                            'kbase',
                            level=params.get('level'),
                            context=params.get('context'),
                            expires=params.get('expires'))
    global_feed = NotificationFeed(cfg.global_feed, cfg.global_feed_type)
    global_feed.add_notification(new_note)
    return (flask.jsonify({'id': new_note.id}), 200)
Exemplo n.º 13
0
def test_entity_hash():
    """
    Don't really care what the hash is, as long as:
    1. it's an integer
    2. it's consistent
    """
    eid = "foo"
    etype = "user"
    eid2 = "bar"
    etype2 = "group"
    e1 = Entity(eid, etype)
    e1_copy = Entity(eid, etype)
    e2 = Entity(eid2, etype2)
    e3 = Entity(eid, etype2)
    assert isinstance(hash(e1), int)
    assert hash(e1) == hash(e1)
    assert hash(e1) == hash(e1_copy)
    assert hash(e1) != hash(e2)
    assert hash(e1) != hash(e3)
Exemplo n.º 14
0
def test_entity_fetch_name_narr(mock_workspace_info):
    ws_id = 7
    info = [
        7, "A_Workspace", "owner", "Timestamp", 18, "a", "n", "unlocked", {
            "narrative": "1",
            "narrative_nice_name": "My Narrative"
        }
    ]
    mock_workspace_info(info)
    e = Entity(ws_id, "narrative")
    assert e.name == info[8]["narrative_nice_name"]
Exemplo n.º 15
0
def test_entity_validate_user_ok(mock_valid_users):
    mock_valid_users({"fake": "Fake User", "some_admin": "admin"})
    e = Entity("fake", "user")
    assert e.validate() is True

    e = Entity("some_admin", "admin")
    assert e.validate() is True
Exemplo n.º 16
0
def test_entity_fetch_name_ws(mock_workspace_info):
    ws_id = 6
    info = [
        6, "A_Workspace", "owner", "Timestamp", 18, "a", "n", "unlocked", {
            "narrative": "1",
            "narrative_nice_name": "My Narrative"
        }
    ]
    mock_workspace_info(info)
    e = Entity(ws_id, "workspace")
    # assert e.name == info[1]
    # workspace == narrative now. so test that name.
    assert e.name == info[8]['narrative_nice_name']
Exemplo n.º 17
0
 def from_dict(cls, serial: dict, token: str = None) -> N:
     """
     Returns a new Notification from a serialized dictionary (e.g. used in Mongo)
     """
     try:
         assert serial is not None and isinstance(serial, dict)
     except AssertionError:
         raise InvalidNotificationError(
             "Can only run 'from_dict' on a dict.")
     required_keys = set([
         'actor', 'verb', 'object', 'source', 'level', 'created', 'expires',
         'id'
     ])
     missing_keys = required_keys.difference(set(serial.keys()))
     if missing_keys:
         raise InvalidNotificationError(
             'Missing keys: {}'.format(missing_keys))
     deserial = cls(Entity.from_dict(serial['actor'], token=token),
                    str(serial['verb']),
                    Entity.from_dict(serial['object'], token=token),
                    serial['source'],
                    level=str(serial['level']),
                    target=[
                        Entity.from_dict(t, token=token)
                        for t in serial.get('target', [])
                    ],
                    context=serial.get('context'),
                    external_key=serial.get('external_key'),
                    seen=serial.get('seen', False),
                    users=[
                        Entity.from_dict(u, token=token)
                        for u in serial.get('users', [])
                    ])
     deserial.created = serial['created']
     deserial.expires = serial['expires']
     deserial.id = serial['id']
     return deserial
Exemplo n.º 18
0
 def set_seen(self, act_ids: List[str], user: Entity) -> None:
     """
     Setting seen just means removing the user from the list of unseens.
     The query should find all docs in the list of act_ids, where the user
     is in the list of users, AND the list of unseens.
     The update should remove the user from the list of unseens.
     """
     u = user.to_dict()
     coll = get_feeds_collection()
     coll.update_many({
         'id': {
             '$in': act_ids
         },
         'users': u,
         'unseen': u
     }, {'$pull': {
         'unseen': u
     }})
Exemplo n.º 19
0
 def set_unseen(self, act_ids: List[str], user: Entity) -> None:
     """
     Setting unseen means adding the user to the list of unseens. But we should only do that for
     docs that the user can't see anyway, so put that in the query.
     """
     u = user.to_dict()
     coll = get_feeds_collection()
     coll.update_many(
         {
             'id': {
                 '$in': act_ids
             },
             'users': u,
             'unseen': {
                 '$nin': [u]
             }
         }, {'$addToSet': {
             'unseen': u
         }})
Exemplo n.º 20
0
def test_entity_eq():
    eid1 = "foo"
    eid2 = "bar"
    etype1 = "user"
    etype2 = "group"
    e11 = Entity(eid1, etype1)
    e11_2 = Entity(eid1, etype1)
    e12 = Entity(eid1, etype2)
    e21 = Entity(eid2, etype1)
    e22 = Entity(eid2, etype2)
    assert e11 == e11
    assert e11 == e11_2
    assert e12 != e11
    assert e11 != e12
    assert e11 != e22
    assert e21 != e11
    assert e22 == Entity.from_dict({"id": eid2, "type": etype2})
Exemplo n.º 21
0
def test_entity_validate_narrative_fail(mock_workspace_info_invalid):
    ws_id = 5
    mock_workspace_info_invalid(ws_id)
    e = Entity(ws_id, "narrative")
    assert e.validate() is False
Exemplo n.º 22
0
def test_entity_validate_group_ok(mock_valid_group):
    g_id = "mygroup"
    mock_valid_group(g_id)
    e = Entity(g_id, "group")
    assert e.validate() is True
Exemplo n.º 23
0
def test_entity_init_autovalidate_fail(mock_invalid_user):
    user_id = "bad_user"
    mock_invalid_user(user_id)
    with pytest.raises(EntityValidationError) as e:
        Entity(user_id, "user", auto_validate=True)
    assert "Entity of type user with id {} is not valid".format(user_id)
Exemplo n.º 24
0
class NotificationFeed(BaseFeed):
    def __init__(self, user_id: str, user_type: str, token: str = None):
        self.user = Entity(user_id, user_type, token=token)
        self.token = token
        self.timeline_storage = MongoTimelineStorage(user_id, user_type)
        self.activity_storage = MongoActivityStorage()
        self.timeline = None
        self.cache = TTLCache(1000, 600)

    def _update_timeline(self) -> None:
        """
        Updates a local user timeline cache. This is a list of activity ids
        that are used for fetching from activity storage (for now). Sorted
        by newest first.

        TODO: add metadata to timeline storage - type and verb, first.
        """
        logging.getLogger(__name__).info('Fetching timeline for '.format(
            self.user))
        self.timeline = self.timeline_storage.get_timeline()

    def get_group_notifications(self,
                                group: Dict[str, str],
                                count: int = 10,
                                include_seen: bool = False,
                                level=None,
                                verb=None,
                                reverse: bool = False) -> dict:
        """
        Returns all notifications (using the get_notifications fn.) for a user, filtered down to
        those that reference an Entity of type group with the given id.
        """
        notes = self.get_notifications(count=count,
                                       include_seen=include_seen,
                                       level=level,
                                       verb=verb,
                                       reverse=reverse)
        # group has id and name keys
        group_notes = {"unseen": 0, "name": group.get("name"), "feed": list()}
        gid = group["id"]

        def is_group(e: Entity) -> bool:
            return e.id == gid and e.type == "group"

        notes_list = list()
        for n in notes["feed"]:
            if is_group(n.actor) or is_group(n.object) or any(
                [is_group(t) for t in n.target]):
                notes_list.append(n)
                if not n.seen:
                    group_notes["unseen"] += 1
        Notification.update_entity_names(notes_list, token=self.token)
        for n in notes_list:
            group_notes["feed"].append(n.user_view())
        return group_notes

    def get_notifications(self,
                          count: int = 10,
                          include_seen: bool = False,
                          level=None,
                          verb=None,
                          reverse: bool = False,
                          user_view: bool = False) -> dict:
        """
        Fetches all activities matching the requested inputs.
        :param count: max number of most recent notifications to return. default=10
        :param include_seen: include notifications that have been seen in the response.
            default = False
        :param level: if not None, will only return notifications of the given level.
            default = None
        :param verb: if not None, will only return notifications made with the given verb.
            default = None
        :param reverse: if True, will reverse the order of the result (default False)
        :param user_view: if True, will return the user_view dict version of each Notification
            object. If False, will return a list of Notification objects instead. default False
        :return: a dict with the requested notifications, and a key with the total number in the
            feed that are marked unseen
        :rtype: dict
        :raises ValueError: if count <= 0
        """
        activities = self.get_activities(count=count,
                                         include_seen=include_seen,
                                         verb=verb,
                                         level=level,
                                         reverse=reverse,
                                         user_view=user_view)
        ret_struct = {
            "unseen": self.get_unseen_count(),
            "name": self.user.name
        }
        if user_view:
            ret_struct["feed"] = list()
            Notification.update_entity_names(activities, token=self.token)
            for act in activities:
                ret_struct["feed"].append(act.user_view())
        else:
            ret_struct["feed"] = activities
        return ret_struct

    def get_notification(self, note_id):
        """
        Returns a single notification.
        If it doesn't exist (either the user can't see it, or it's really not there), raises
        a NotificationNotFoundError.
        """
        note = self.timeline_storage.get_single_activity_from_timeline(note_id)
        if note is None:
            raise NotificationNotFoundError(
                "Cannot find notification with id {}.".format(note_id))
        else:
            return Notification.from_dict(note, self.token)

    def get_activities(self,
                       count=10,
                       include_seen=False,
                       level=None,
                       verb=None,
                       reverse=False,
                       user_view=False) -> List[Notification]:
        """
        Returns a selection of activities.
        :param count: Maximum number of Notifications to return (default 10)
        """
        # steps.
        # 0. If in cache, return them.  <-- later
        # 1. Get storage adapter.
        # 2. Query it for recent activities from this user.
        # 3. Cache them here.
        # 4. Return them.
        if count < 1 or not isinstance(count, int):
            raise ValueError("Count must be an integer > 0")
        serial_notes = self.timeline_storage.get_timeline(
            count=count,
            include_seen=include_seen,
            level=level,
            verb=verb,
            reverse=reverse)
        note_list = list()
        user_dict = self.user.to_dict()
        for note in serial_notes:
            if user_dict in note["unseen"]:
                note["seen"] = False
            else:
                note["seen"] = True
            note_list.append(Notification.from_dict(note, self.token))
        return note_list

    def mark_activities(self, activity_ids: List[str], seen=False) -> None:
        """
        Marks the given list of activities as either seen (True) or unseen (False).
        If the owner of this feed is not on the users list for an activity, nothing is
        changed for that activity.
        """
        if seen:
            self.activity_storage.set_seen(activity_ids, self.user)
        else:
            self.activity_storage.set_unseen(activity_ids, self.user)

    def add_notification(self, note) -> None:
        return self.add_activity(note)

    def add_activity(self, note) -> None:
        """
        Adds an activity to this user's feed
        """
        self.activity_storage.add_to_storage(note, [self.user])

    def get_unseen_count(self) -> int:
        """
        Returns the number of unread / unexpired notifications in this feed.
        """
        return self.timeline_storage.get_unseen_count()
Exemplo n.º 25
0
def test_entity_validate_workspace_false(mock_workspace_info_invalid):
    ws_id = 5
    mock_workspace_info_invalid(ws_id)
    e = Entity(ws_id, "workspace")
    assert e.validate() is False
Exemplo n.º 26
0
def test_entity_validate_workspace_fail(mock_workspace_info_error):
    ws_id = 5
    mock_workspace_info_error(ws_id)
    e = Entity(ws_id, "workspace")
    assert e.validate() is False
Exemplo n.º 27
0
def test_entity_init_ok(e_id, e_type):
    e = Entity(e_id, e_type)
    assert e.id == e_id
    assert e.type == e_type
Exemplo n.º 28
0
    assert_is_uuid,
    test_config
)
from feeds.exceptions import (
    MissingVerbError,
    MissingLevelError,
    InvalidExpirationError,
    InvalidNotificationError
)
from feeds.entity.entity import Entity

cfg = test_config()

# some dummy "good" inputs for testing
actor_d = {"id": "test_actor", "type": "user"}
actor = Entity.from_dict(actor_d)
verb_inf = "invite"
verb_past = "invited"
verb_id = 1
object_d = {"id": "foo", "type": "workspace"}
note_object = Entity.from_dict(object_d)
source = "groups"
level_name = "warning"
level_id = 2
target_d = {"id": "target_actor", "type": "user"}
target = [Entity.from_dict(target_d)]
context = {"some": "context"}
expires = epoch_ms() + (10 * 24 * 60 * 60 * 1000) # 10 days
external_key = "an_external_key"
user_d = {"id": "user_actor", "type": "user"}
users = [Entity.from_dict(user_d)]
Exemplo n.º 29
0
Arquivo: kbase.py Projeto: kbase/feeds
 def get_target_users(self):
     cfg = get_config()
     return [Entity(cfg.global_feed, cfg.global_feed_type)]
Exemplo n.º 30
0
Arquivo: base.py Projeto: kbase/feeds
 def __init__(self, user_id, user_type):
     assert user_id
     assert user_type
     self.user_id = user_id
     self.user_type = user_type  # should align with entity types
     self.user = Entity(user_id, user_type)