|
1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """Core hooks: synchronize living session on persistent data changes""" |
|
19 |
|
20 __docformat__ = "restructuredtext en" |
|
21 from cubicweb import _ |
|
22 |
|
23 from cubicweb import UnknownProperty, BadConnectionId, validation_error |
|
24 from cubicweb.predicates import is_instance |
|
25 from cubicweb.server import hook |
|
26 |
|
27 |
|
28 def get_user_sessions(repo, ueid): |
|
29 for session in repo._sessions.values(): |
|
30 if ueid == session.user.eid: |
|
31 yield session |
|
32 |
|
33 |
|
34 class SyncSessionHook(hook.Hook): |
|
35 __abstract__ = True |
|
36 category = 'syncsession' |
|
37 |
|
38 |
|
39 # user/groups synchronisation ################################################# |
|
40 |
|
41 class _GroupOperation(hook.Operation): |
|
42 """base class for group operation""" |
|
43 cnxuser = None # make pylint happy |
|
44 |
|
45 def __init__(self, cnx, *args, **kwargs): |
|
46 """override to get the group name before actual groups manipulation: |
|
47 |
|
48 we may temporarily loose right access during a commit event, so |
|
49 no query should be emitted while comitting |
|
50 """ |
|
51 rql = 'Any N WHERE G eid %(x)s, G name N' |
|
52 result = cnx.execute(rql, {'x': kwargs['geid']}, build_descr=False) |
|
53 hook.Operation.__init__(self, cnx, *args, **kwargs) |
|
54 self.group = result[0][0] |
|
55 |
|
56 |
|
57 class _DeleteGroupOp(_GroupOperation): |
|
58 """synchronize user when a in_group relation has been deleted""" |
|
59 |
|
60 def postcommit_event(self): |
|
61 """the observed connections set has been commited""" |
|
62 groups = self.cnxuser.groups |
|
63 try: |
|
64 groups.remove(self.group) |
|
65 except KeyError: |
|
66 self.error('user %s not in group %s', self.cnxuser, self.group) |
|
67 |
|
68 |
|
69 class _AddGroupOp(_GroupOperation): |
|
70 """synchronize user when a in_group relation has been added""" |
|
71 def postcommit_event(self): |
|
72 """the observed connections set has been commited""" |
|
73 groups = self.cnxuser.groups |
|
74 if self.group in groups: |
|
75 self.warning('user %s already in group %s', self.cnxuser, |
|
76 self.group) |
|
77 else: |
|
78 groups.add(self.group) |
|
79 |
|
80 |
|
81 class SyncInGroupHook(SyncSessionHook): |
|
82 __regid__ = 'syncingroup' |
|
83 __select__ = SyncSessionHook.__select__ & hook.match_rtype('in_group') |
|
84 events = ('after_delete_relation', 'after_add_relation') |
|
85 |
|
86 def __call__(self): |
|
87 if self.event == 'after_delete_relation': |
|
88 opcls = _DeleteGroupOp |
|
89 else: |
|
90 opcls = _AddGroupOp |
|
91 for session in get_user_sessions(self._cw.repo, self.eidfrom): |
|
92 opcls(self._cw, cnxuser=session.user, geid=self.eidto) |
|
93 |
|
94 |
|
95 class _DelUserOp(hook.Operation): |
|
96 """close associated user's session when it is deleted""" |
|
97 def __init__(self, cnx, sessionid): |
|
98 self.sessionid = sessionid |
|
99 hook.Operation.__init__(self, cnx) |
|
100 |
|
101 def postcommit_event(self): |
|
102 """the observed connections set has been commited""" |
|
103 try: |
|
104 self.cnx.repo.close(self.sessionid) |
|
105 except BadConnectionId: |
|
106 pass # already closed |
|
107 |
|
108 |
|
109 class CloseDeletedUserSessionsHook(SyncSessionHook): |
|
110 __regid__ = 'closession' |
|
111 __select__ = SyncSessionHook.__select__ & is_instance('CWUser') |
|
112 events = ('after_delete_entity',) |
|
113 |
|
114 def __call__(self): |
|
115 """modify user permission, need to update users""" |
|
116 for session in get_user_sessions(self._cw.repo, self.entity.eid): |
|
117 _DelUserOp(self._cw, session.sessionid) |
|
118 |
|
119 |
|
120 # CWProperty hooks ############################################################# |
|
121 |
|
122 class _DelCWPropertyOp(hook.Operation): |
|
123 """a user's custom properties has been deleted""" |
|
124 cwpropdict = key = None # make pylint happy |
|
125 |
|
126 def postcommit_event(self): |
|
127 """the observed connections set has been commited""" |
|
128 try: |
|
129 del self.cwpropdict[self.key] |
|
130 except KeyError: |
|
131 self.error('%s has no associated value', self.key) |
|
132 |
|
133 |
|
134 class _ChangeCWPropertyOp(hook.Operation): |
|
135 """a user's custom properties has been added/changed""" |
|
136 cwpropdict = key = value = None # make pylint happy |
|
137 |
|
138 def postcommit_event(self): |
|
139 """the observed connections set has been commited""" |
|
140 self.cwpropdict[self.key] = self.value |
|
141 |
|
142 |
|
143 class _AddCWPropertyOp(hook.Operation): |
|
144 """a user's custom properties has been added/changed""" |
|
145 cwprop = None # make pylint happy |
|
146 |
|
147 def postcommit_event(self): |
|
148 """the observed connections set has been commited""" |
|
149 cwprop = self.cwprop |
|
150 if not cwprop.for_user: |
|
151 self.cnx.vreg['propertyvalues'][cwprop.pkey] = cwprop.value |
|
152 # if for_user is set, update is handled by a ChangeCWPropertyOp operation |
|
153 |
|
154 |
|
155 class AddCWPropertyHook(SyncSessionHook): |
|
156 __regid__ = 'addcwprop' |
|
157 __select__ = SyncSessionHook.__select__ & is_instance('CWProperty') |
|
158 events = ('after_add_entity',) |
|
159 |
|
160 def __call__(self): |
|
161 key, value = self.entity.pkey, self.entity.value |
|
162 if key.startswith('sources.'): |
|
163 return |
|
164 cnx = self._cw |
|
165 try: |
|
166 value = cnx.vreg.typed_value(key, value) |
|
167 except UnknownProperty: |
|
168 msg = _('unknown property key %s') |
|
169 raise validation_error(self.entity, {('pkey', 'subject'): msg}, (key,)) |
|
170 except ValueError as ex: |
|
171 raise validation_error(self.entity, |
|
172 {('value', 'subject'): str(ex)}) |
|
173 if not cnx.user.matching_groups('managers'): |
|
174 cnx.add_relation(self.entity.eid, 'for_user', cnx.user.eid) |
|
175 else: |
|
176 _AddCWPropertyOp(cnx, cwprop=self.entity) |
|
177 |
|
178 |
|
179 class UpdateCWPropertyHook(AddCWPropertyHook): |
|
180 __regid__ = 'updatecwprop' |
|
181 events = ('after_update_entity',) |
|
182 |
|
183 def __call__(self): |
|
184 entity = self.entity |
|
185 if not ('pkey' in entity.cw_edited or |
|
186 'value' in entity.cw_edited): |
|
187 return |
|
188 key, value = entity.pkey, entity.value |
|
189 if key.startswith('sources.'): |
|
190 return |
|
191 cnx = self._cw |
|
192 try: |
|
193 value = cnx.vreg.typed_value(key, value) |
|
194 except UnknownProperty: |
|
195 return |
|
196 except ValueError as ex: |
|
197 raise validation_error(entity, {('value', 'subject'): str(ex)}) |
|
198 if entity.for_user: |
|
199 for session in get_user_sessions(cnx.repo, entity.for_user[0].eid): |
|
200 _ChangeCWPropertyOp(cnx, cwpropdict=session.user.properties, |
|
201 key=key, value=value) |
|
202 else: |
|
203 # site wide properties |
|
204 _ChangeCWPropertyOp(cnx, cwpropdict=cnx.vreg['propertyvalues'], |
|
205 key=key, value=value) |
|
206 |
|
207 |
|
208 class DeleteCWPropertyHook(AddCWPropertyHook): |
|
209 __regid__ = 'delcwprop' |
|
210 events = ('before_delete_entity',) |
|
211 |
|
212 def __call__(self): |
|
213 eid = self.entity.eid |
|
214 cnx = self._cw |
|
215 for eidfrom, rtype, eidto in cnx.transaction_data.get('pendingrelations', ()): |
|
216 if rtype == 'for_user' and eidfrom == self.entity.eid: |
|
217 # if for_user was set, delete has already been handled |
|
218 break |
|
219 else: |
|
220 _DelCWPropertyOp(cnx, cwpropdict=cnx.vreg['propertyvalues'], |
|
221 key=self.entity.pkey) |
|
222 |
|
223 |
|
224 class AddForUserRelationHook(SyncSessionHook): |
|
225 __regid__ = 'addcwpropforuser' |
|
226 __select__ = SyncSessionHook.__select__ & hook.match_rtype('for_user') |
|
227 events = ('after_add_relation',) |
|
228 |
|
229 def __call__(self): |
|
230 cnx = self._cw |
|
231 eidfrom = self.eidfrom |
|
232 if not cnx.entity_metas(eidfrom)['type'] == 'CWProperty': |
|
233 return |
|
234 key, value = cnx.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V', |
|
235 {'x': eidfrom})[0] |
|
236 if cnx.vreg.property_info(key)['sitewide']: |
|
237 msg = _("site-wide property can't be set for user") |
|
238 raise validation_error(eidfrom, {('for_user', 'subject'): msg}) |
|
239 for session in get_user_sessions(cnx.repo, self.eidto): |
|
240 _ChangeCWPropertyOp(cnx, cwpropdict=session.user.properties, |
|
241 key=key, value=value) |
|
242 |
|
243 |
|
244 class DelForUserRelationHook(AddForUserRelationHook): |
|
245 __regid__ = 'delcwpropforuser' |
|
246 events = ('after_delete_relation',) |
|
247 |
|
248 def __call__(self): |
|
249 cnx = self._cw |
|
250 key = cnx.execute('Any K WHERE P eid %(x)s, P pkey K', |
|
251 {'x': self.eidfrom})[0][0] |
|
252 cnx.transaction_data.setdefault('pendingrelations', []).append( |
|
253 (self.eidfrom, self.rtype, self.eidto)) |
|
254 for session in get_user_sessions(cnx.repo, self.eidto): |
|
255 _DelCWPropertyOp(cnx, cwpropdict=session.user.properties, key=key) |