hooks/syncsession.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     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)