[062892]: / Allura / allura / model / timeline.py  Maximize  Restore  History

Download this file

171 lines (135 with data), 5.5 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from __future__ import annotations
import bson
import logging
import typing
from ming.odm import Mapper
from tg import tmpl_context as c
from activitystream import ActivityDirector
from activitystream.base import NodeBase, ActivityObjectBase
from activitystream.managers import Aggregator as BaseAggregator
from allura.lib import security
from allura.tasks.activity_tasks import create_timelines
if typing.TYPE_CHECKING:
import allura.model as M
from activitystream.storage.mingstorage import Activity
log = logging.getLogger(__name__)
class Director(ActivityDirector):
"""Overrides the default ActivityDirector to kick off background
timeline aggregations after an activity is created.
"""
def create_activity(self, actor: ActivityNode, verb: str, obj: ActivityObject, target=None,
related_nodes=None, tags=None):
if c.project and c.project.notifications_disabled:
return
from allura.model.project import Project
super().create_activity(actor, verb, obj,
target=target,
related_nodes=related_nodes,
tags=tags)
# aggregate actor and follower's timelines
if actor.node_id:
create_timelines.post(actor.node_id)
# aggregate project and follower's timelines
for node in [obj, target] + (related_nodes or []):
if isinstance(node, Project):
create_timelines.post(node.node_id)
class Aggregator(BaseAggregator):
pass
class ActivityNode(NodeBase):
@property
def node_id(self):
return f"{self.__class__.__name__}:{self._id}"
class ActivityObject(ActivityObjectBase):
'''
Allura's base activity class.
'''
@property
def activity_name(self):
"""Override this for each Artifact type."""
return f"{self.__mongometa__.name.capitalize()} {self._id}"
@property
def activity_url(self):
return self.url()
@property
def activity_extras(self):
"""Return a BSON-serializable dict of extra stuff to store on the
activity.
"""
return {"allura_id": self.allura_id}
@property
def allura_id(self):
"""Return a string which uniquely identifies this object and which can
be used to retrieve the object from mongo.
"""
return f"{self.__class__.__name__}:{self._id}"
def has_activity_access(self, perm, user, activity):
"""Return True if user has perm access to this object, otherwise
return False.
"""
if self.project is None or getattr(self, 'deleted', False):
return False
return security.has_access(self, perm, user, self.project)
class TransientActor(NodeBase, ActivityObjectBase):
"""An activity actor which is not a persistent Node in the network.
"""
def __init__(self, activity_name):
NodeBase.__init__(self)
ActivityObjectBase.__init__(self)
self.activity_name = activity_name
def get_allura_id(activity_object_dict):
extras_dict = activity_object_dict.activity_extras
if not extras_dict:
return None
allura_id = extras_dict.get('allura_id')
if not allura_id:
return None
return allura_id
def get_activity_object(activity_object_dict):
"""Given a BSON-serialized activity object (e.g. activity.obj dict in a
timeline), return the corresponding :class:`ActivityObject`.
"""
allura_id = get_allura_id(activity_object_dict)
if not allura_id:
return None
return get_object_from_id(allura_id)
def get_object_from_id(node_id):
classname, _id = node_id.split(':', 1)
cls = Mapper.by_classname(classname).mapped_class
try:
_id = bson.ObjectId(_id)
except bson.errors.InvalidId:
pass
obj = cls.query.get(_id=_id)
return obj
def perm_check(user: M.User):
"""
Return a function that returns True if ``user`` has 'read' access to a given activity,
otherwise returns False.
"""
def _perm_check(activity: Activity):
if not get_allura_id(activity.obj):
# include activity records that do not have an allura object
return True
obj = get_activity_object(activity.obj)
if obj is None:
# Do not include if there's supposed to be an allura object, but it's missing
return False
# Finally, if there's an allura object, perform a permission check
return obj.has_activity_access('read', user, activity)
return _perm_check