|
1 """Core hooks: synchronize living session on persistent data changes |
|
2 |
|
3 :organization: Logilab |
|
4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
|
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
|
7 """ |
|
8 __docformat__ = "restructuredtext en" |
|
9 |
|
10 from cubicweb import UnknownProperty, ValidationError, BadConnectionId |
|
11 from cubicweb.selectors import entity_implements |
|
12 from cubicweb.server.hook import Hook, match_rtype |
|
13 from cubicweb.server.pool import Operation |
|
14 from cubicweb.server.hookhelper import get_user_sessions |
|
15 |
|
16 |
|
17 # user/groups synchronisation ################################################# |
|
18 |
|
19 class _GroupOperation(Operation): |
|
20 """base class for group operation""" |
|
21 geid = None |
|
22 def __init__(self, session, *args, **kwargs): |
|
23 """override to get the group name before actual groups manipulation: |
|
24 |
|
25 we may temporarily loose right access during a commit event, so |
|
26 no query should be emitted while comitting |
|
27 """ |
|
28 rql = 'Any N WHERE G eid %(x)s, G name N' |
|
29 result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False) |
|
30 Operation.__init__(self, session, *args, **kwargs) |
|
31 self.group = result[0][0] |
|
32 |
|
33 |
|
34 class _DeleteGroupOp(_GroupOperation): |
|
35 """synchronize user when a in_group relation has been deleted""" |
|
36 def commit_event(self): |
|
37 """the observed connections pool has been commited""" |
|
38 groups = self.cnxuser.groups |
|
39 try: |
|
40 groups.remove(self.group) |
|
41 except KeyError: |
|
42 self.error('user %s not in group %s', self.cnxuser, self.group) |
|
43 return |
|
44 |
|
45 |
|
46 class _AddGroupOp(_GroupOperation): |
|
47 """synchronize user when a in_group relation has been added""" |
|
48 def commit_event(self): |
|
49 """the observed connections pool has been commited""" |
|
50 groups = self.cnxuser.groups |
|
51 if self.group in groups: |
|
52 self.warning('user %s already in group %s', self.cnxuser, |
|
53 self.group) |
|
54 return |
|
55 groups.add(self.group) |
|
56 |
|
57 |
|
58 class SyncInGroupHook(Hook): |
|
59 __id__ = 'syncingroup' |
|
60 __select__ = Hook.__select__ & match_rtype('in_group') |
|
61 events = ('after_delete_relation', 'after_add_relation') |
|
62 category = 'syncsession' |
|
63 |
|
64 def __call__(self): |
|
65 if self.event == 'after_delete_relation': |
|
66 opcls = _DeleteGroupOp |
|
67 else: |
|
68 opcls = _AddGroupOp |
|
69 for session in get_user_sessions(self.cw_req.repo, self.eidfrom): |
|
70 opcls(self.cw_req, cnxuser=session.user, geid=self.eidto) |
|
71 |
|
72 |
|
73 class _DelUserOp(Operation): |
|
74 """close associated user's session when it is deleted""" |
|
75 def __init__(self, session, cnxid): |
|
76 self.cnxid = cnxid |
|
77 Operation.__init__(self, session) |
|
78 |
|
79 def commit_event(self): |
|
80 """the observed connections pool has been commited""" |
|
81 try: |
|
82 self.repo.close(self.cnxid) |
|
83 except BadConnectionId: |
|
84 pass # already closed |
|
85 |
|
86 |
|
87 class CloseDeletedUserSessionsHook(Hook): |
|
88 __id__ = 'closession' |
|
89 __select__ = Hook.__select__ & entity_implements('CWUser') |
|
90 events = ('after_delete_entity',) |
|
91 category = 'syncsession' |
|
92 |
|
93 def __call__(self): |
|
94 """modify user permission, need to update users""" |
|
95 for session in get_user_sessions(self.cw_req.repo, self.entity.eid): |
|
96 _DelUserOp(self.cw_req, session.id) |
|
97 |
|
98 |
|
99 # CWProperty hooks ############################################################# |
|
100 |
|
101 |
|
102 class _DelCWPropertyOp(Operation): |
|
103 """a user's custom properties has been deleted""" |
|
104 |
|
105 def commit_event(self): |
|
106 """the observed connections pool has been commited""" |
|
107 try: |
|
108 del self.epropdict[self.key] |
|
109 except KeyError: |
|
110 self.error('%s has no associated value', self.key) |
|
111 |
|
112 |
|
113 class _ChangeCWPropertyOp(Operation): |
|
114 """a user's custom properties has been added/changed""" |
|
115 |
|
116 def commit_event(self): |
|
117 """the observed connections pool has been commited""" |
|
118 self.epropdict[self.key] = self.value |
|
119 |
|
120 |
|
121 class _AddCWPropertyOp(Operation): |
|
122 """a user's custom properties has been added/changed""" |
|
123 |
|
124 def commit_event(self): |
|
125 """the observed connections pool has been commited""" |
|
126 eprop = self.eprop |
|
127 if not eprop.for_user: |
|
128 self.repo.vreg.eprop_values[eprop.pkey] = eprop.value |
|
129 # if for_user is set, update is handled by a ChangeCWPropertyOp operation |
|
130 |
|
131 |
|
132 class AddCWPropertyHook(Hook): |
|
133 __id__ = 'addcwprop' |
|
134 __select__ = Hook.__select__ & entity_implements('CWProperty') |
|
135 category = 'syncsession' |
|
136 events = ('after_add_entity',) |
|
137 |
|
138 def __call__(self): |
|
139 key, value = self.entity.pkey, self.entity.value |
|
140 session = self.cw_req |
|
141 try: |
|
142 value = session.vreg.typed_value(key, value) |
|
143 except UnknownProperty: |
|
144 raise ValidationError(self.entity.eid, |
|
145 {'pkey': session._('unknown property key')}) |
|
146 except ValueError, ex: |
|
147 raise ValidationError(self.entity.eid, |
|
148 {'value': session._(str(ex))}) |
|
149 if not session.user.matching_groups('managers'): |
|
150 session.add_relation(entity.eid, 'for_user', session.user.eid) |
|
151 else: |
|
152 _AddCWPropertyOp(session, eprop=entity) |
|
153 |
|
154 |
|
155 class UpdateCWPropertyHook(AddCWPropertyHook): |
|
156 __id__ = 'updatecwprop' |
|
157 events = ('after_update_entity',) |
|
158 |
|
159 def __call__(self): |
|
160 entity = self.entity |
|
161 if not ('pkey' in entity.edited_attributes or |
|
162 'value' in entity.edited_attributes): |
|
163 return |
|
164 key, value = entity.pkey, entity.value |
|
165 session = self.cw_req |
|
166 try: |
|
167 value = session.vreg.typed_value(key, value) |
|
168 except UnknownProperty: |
|
169 return |
|
170 except ValueError, ex: |
|
171 raise ValidationError(entity.eid, {'value': session._(str(ex))}) |
|
172 if entity.for_user: |
|
173 for session_ in get_user_sessions(session.repo, entity.for_user[0].eid): |
|
174 _ChangeCWPropertyOp(session, epropdict=session_.user.properties, |
|
175 key=key, value=value) |
|
176 else: |
|
177 # site wide properties |
|
178 _ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values, |
|
179 key=key, value=value) |
|
180 |
|
181 |
|
182 class DeleteCWPropertyHook(AddCWPropertyHook): |
|
183 __id__ = 'delcwprop' |
|
184 events = ('before_delete_entity',) |
|
185 |
|
186 def __call__(self): |
|
187 eid = self.entity.eid |
|
188 session = self.cw_req |
|
189 for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()): |
|
190 if rtype == 'for_user' and eidfrom == self.entity.eid: |
|
191 # if for_user was set, delete has already been handled |
|
192 break |
|
193 else: |
|
194 _DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=entity.pkey) |
|
195 |
|
196 |
|
197 class AddForUserRelationHook(Hook): |
|
198 __id__ = 'addcwpropforuser' |
|
199 __select__ = Hook.__select__ & match_rtype('for_user') |
|
200 events = ('after_add_relation',) |
|
201 category = 'syncsession' |
|
202 |
|
203 def __call__(self): |
|
204 session = self.cw_req |
|
205 eidfrom = self.eidfrom |
|
206 if not session.describe(eidfrom)[0] == 'CWProperty': |
|
207 return |
|
208 key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V', |
|
209 {'x': eidfrom}, 'x')[0] |
|
210 if session.vreg.property_info(key)['sitewide']: |
|
211 raise ValidationError(eidfrom, |
|
212 {'for_user': session._("site-wide property can't be set for user")}) |
|
213 for session_ in get_user_sessions(session.repo, self.eidto): |
|
214 _ChangeCWPropertyOp(session, epropdict=session_.user.properties, |
|
215 key=key, value=value) |
|
216 |
|
217 |
|
218 class DelForUserRelationHook(AddForUserRelationHook): |
|
219 __id__ = 'delcwpropforuser' |
|
220 events = ('after_delete_relation',) |
|
221 |
|
222 def __call__(self): |
|
223 session = self.cw_req |
|
224 key = session.execute('Any K WHERE P eid %(x)s, P pkey K', |
|
225 {'x': self.eidfrom}, 'x')[0][0] |
|
226 session.transaction_data.setdefault('pendingrelations', []).append( |
|
227 (self.eidfrom, self.rtype, self.eidto)) |
|
228 for session_ in get_user_sessions(session.repo, self.eidto): |
|
229 _DelCWPropertyOp(session, epropdict=session_.user.properties, key=key) |