hooks/syncsession.py
changeset 2835 04034421b072
child 2841 107ba1c45227
equal deleted inserted replaced
2834:7df3494ae657 2835:04034421b072
       
     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)