2307
+ − 1 #!/usr/bin/env python2
+ − 2 # -*- coding: utf-8 -*-
+ − 3
+ − 4 # SAT plugin for Pubsub Hooks
+ − 5 # Copyright (C) 2009-2017 Jérôme Poisson (goffi@goffi.org)
+ − 6
+ − 7 # This program is free software: you can redistribute it and/or modify
+ − 8 # it under the terms of the GNU Affero General Public License as published by
+ − 9 # the Free Software Foundation, either version 3 of the License, or
+ − 10 # (at your option) any later version.
+ − 11
+ − 12 # This program is distributed in the hope that it will be useful,
+ − 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
+ − 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ − 15 # GNU Affero General Public License for more details.
+ − 16
+ − 17 # You should have received a copy of the GNU Affero General Public License
+ − 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
+ − 19
+ − 20 from sat.core.i18n import _
+ − 21 from sat.core.constants import Const as C
+ − 22 from sat.core import exceptions
+ − 23 from sat.core.log import getLogger
+ − 24 from sat.memory import persistent
+ − 25 from twisted.words.protocols.jabber import jid
+ − 26 from twisted.internet import defer
+ − 27 log = getLogger ( __name__ )
+ − 28
+ − 29 NS_PUBSUB_HOOK = 'PUBSUB_HOOK'
+ − 30
+ − 31 PLUGIN_INFO = {
+ − 32 C . PI_NAME : "PubSub Hook" ,
+ − 33 C . PI_IMPORT_NAME : NS_PUBSUB_HOOK ,
+ − 34 C . PI_TYPE : "EXP" ,
+ − 35 C . PI_PROTOCOLS : [],
+ − 36 C . PI_DEPENDENCIES : [ "XEP-0060" ],
+ − 37 C . PI_MAIN : "PubsubHook" ,
+ − 38 C . PI_HANDLER : "no" ,
+ − 39 C . PI_DESCRIPTION : _ ( """Experimental plugin to launch on action on Pubsub notifications""" )
+ − 40 }
+ − 41
+ − 42 # python module
+ − 43 HOOK_TYPE_PYTHON = u 'python'
+ − 44 # python file path
+ − 45 HOOK_TYPE_PYTHON_FILE = u 'python_file'
+ − 46 # python code directly
+ − 47 HOOK_TYPE_PYTHON_CODE = u 'python_code'
+ − 48 HOOK_TYPES = ( HOOK_TYPE_PYTHON , HOOK_TYPE_PYTHON_FILE , HOOK_TYPE_PYTHON_CODE )
+ − 49
+ − 50
+ − 51 class PubsubHook ( object ):
+ − 52
+ − 53 def __init__ ( self , host ):
+ − 54 log . info ( _ ( u "PubSub Hook initialization" ))
+ − 55 self . host = host
+ − 56 self . node_hooks = {} # keep track of the number of hooks per node (for all profiles)
+ − 57 host . bridge . addMethod ( "psHookAdd" , ".plugin" ,
+ − 58 in_sign = 'ssssbs' , out_sign = '' ,
+ − 59 method = self . _addHook
+ − 60 )
+ − 61 host . bridge . addMethod ( "psHookRemove" , ".plugin" ,
+ − 62 in_sign = 'sssss' , out_sign = 'i' ,
+ − 63 method = self . _removeHook
+ − 64 )
+ − 65 host . bridge . addMethod ( "psHookList" , ".plugin" ,
+ − 66 in_sign = 's' , out_sign = 'aa {ss} ' ,
+ − 67 method = self . _listHooks
+ − 68 )
+ − 69
+ − 70 @defer . inlineCallbacks
+ − 71 def profileConnected ( self , client ):
+ − 72 hooks = client . _hooks = persistent . PersistentBinaryDict ( NS_PUBSUB_HOOK , client . profile )
+ − 73 client . _hooks_temporary = {}
+ − 74 yield hooks . load ()
+ − 75 for node in hooks :
+ − 76 self . _installNodeManager ( client , node )
+ − 77
+ − 78 def profileDisconnected ( self , client ):
+ − 79 for node in client . _hooks :
+ − 80 self . _removeNodeManager ( client , node )
+ − 81
+ − 82 def _installNodeManager ( self , client , node ):
+ − 83 if node in self . node_hooks :
+ − 84 log . debug ( _ ( u "node manager already set for {node} " ) . format ( node = node ))
+ − 85 self . node_hooks [ node ] += 1
+ − 86 else :
+ − 87 # first hook on this node
+ − 88 self . host . plugins [ 'XEP-0060' ] . addManagedNode ( node , items_cb = self . _itemsReceived )
+ − 89 self . node_hooks [ node ] = 0
+ − 90 log . info ( _ ( u "node manager installed on {node} " ) . format (
+ − 91 node = node ))
+ − 92
+ − 93 def _removeNodeManager ( self , client , node ):
+ − 94 try :
+ − 95 self . node_hooks [ node ] -= 1
+ − 96 except KeyError :
+ − 97 log . error ( _ ( u "trying to remove a {node} without hook" ) . format ( node = node ))
+ − 98 else :
+ − 99 if self . node_hooks [ node ] == 0 :
+ − 100 del self . node_hooks [ node ]
+ − 101 self . host . plugins [ 'XEP-0060' ] . removeManagedNode ( node , self . _itemsReceived )
+ − 102 log . debug ( _ ( u "hook removed" ))
+ − 103 else :
+ − 104 log . debug ( _ ( u "node still needed for an other hook" ))
+ − 105
+ − 106 def installHook ( self , client , service , node , hook_type , hook_arg , persistent ):
+ − 107 if hook_type not in HOOK_TYPES :
+ − 108 raise exceptions . DataError ( _ ( u ' {hook_type} is not handled' ) . format ( hook_type = hook_type ))
+ − 109 if hook_type != HOOK_TYPE_PYTHON_FILE :
+ − 110 raise NotImplementedError ( _ ( u ' {hook_type} hook type not implemented yet' ) . format ( hook_type = hook_type ))
+ − 111 self . _installNodeManager ( client , node )
+ − 112 hook_data = { 'service' : service ,
+ − 113 'type' : hook_type ,
+ − 114 'arg' : hook_arg
+ − 115 }
+ − 116
+ − 117 if persistent :
+ − 118 hooks_list = client . _hooks . setdefault ( node ,[])
+ − 119 hooks_list . append ( hook_data )
+ − 120 client . _hooks . force ( node )
+ − 121 else :
+ − 122 hooks_list = client . _hooks_temporary . setdefault ( node ,[])
+ − 123 hooks_list . append ( hook_data )
+ − 124
+ − 125 log . info ( _ ( u " {persistent} hook installed on {node} for {profile} " ) . format (
+ − 126 persistent = _ ( u 'persistent' ) if persistent else _ ( u 'temporary' ),
+ − 127 node = node ,
+ − 128 profile = client . profile ))
+ − 129
+ − 130 def _itemsReceived ( self , client , itemsEvent ):
+ − 131 node = itemsEvent . nodeIdentifier
+ − 132 for hooks in ( client . _hooks , client . _hooks_temporary ):
+ − 133 if node not in hooks :
+ − 134 continue
+ − 135 hooks_list = hooks [ node ]
+ − 136 for hook_data in hooks_list [:]:
+ − 137 if hook_data [ 'service' ] != itemsEvent . sender . userhostJID ():
+ − 138 continue
+ − 139 try :
+ − 140 callback = hook_data [ 'callback' ]
+ − 141 except KeyError :
+ − 142 # first time we get this hook, we create the callback
+ − 143 hook_type = hook_data [ 'type' ]
+ − 144 try :
+ − 145 if hook_type == HOOK_TYPE_PYTHON_FILE :
+ − 146 hook_globals = {}
+ − 147 execfile ( hook_data [ 'arg' ], hook_globals )
+ − 148 callback = hook_globals [ 'hook' ]
+ − 149 else :
+ − 150 raise NotImplementedError ( _ ( u ' {hook_type} hook type not implemented yet' ) . format (
+ − 151 hook_type = hook_type ))
+ − 152 except Exception as e :
+ − 153 log . warning ( _ ( u "Can't load Pubsub hook at node {node} , it will be removed: {reason} " ) . format (
+ − 154 node = node , reason = e ))
+ − 155 hooks_list . remove ( hook_data )
+ − 156 continue
+ − 157
+ − 158 for item in itemsEvent . items :
+ − 159 try :
+ − 160 callback ( self . host , client , item )
+ − 161 except Exception as e :
+ − 162 log . warning ( _ ( u "Error while running Pubsub hook for node {node} : {msg} " ) . format (
+ − 163 node = node ,
+ − 164 msg = e ))
+ − 165
+ − 166 def _addHook ( self , service , node , hook_type , hook_arg , persistent , profile ):
+ − 167 client = self . host . getClient ( profile )
+ − 168 service = jid . JID ( service ) if service else client . jid . userhostJID ()
+ − 169 return self . addHook ( client , service , unicode ( node ), unicode ( hook_type ), unicode ( hook_arg ), persistent )
+ − 170
+ − 171 def addHook ( self , client , service , node , hook_type , hook_arg , persistent ):
+ − 172 r """Add a hook which will be triggered on a pubsub notification
+ − 173
+ − 174 @param service(jid.JID): service of the node
+ − 175 @param node(unicode): Pubsub node
+ − 176 @param hook_type(unicode): type of the hook, one of:
+ − 177 - HOOK_TYPE_PYTHON: a python module (must be in path)
+ − 178 module must have a "hook" method which will be called
+ − 179 - HOOK_TYPE_PYTHON_FILE: a python file
+ − 180 file must have a "hook" method which will be called
+ − 181 - HOOK_TYPE_PYTHON_CODE: direct python code
+ − 182 /!\ Python hooks will be executed in SàT context,
+ − 183 with host, client and item as arguments, it means that:
+ − 184 - they can do whatever they wants, so don't run untrusted hooks
+ − 185 - they MUST NOT BLOCK, they are run in Twisted async environment and blocking would block whole SàT process
+ − 186 - item are domish.Element
+ − 187 @param hook_arg(unicode): argument of the hook, depending on the hook_type
+ − 188 can be a module path, file path, python code
+ − 189 """
+ − 190 assert service is not None
+ − 191 return self . installHook ( client , service , node , hook_type , hook_arg , persistent )
+ − 192
+ − 193 def _removeHook ( self , service , node , hook_type , hook_arg , profile ):
+ − 194 client = self . host . getClient ( profile )
+ − 195 service = jid . JID ( service ) if service else client . jid . userhostJID ()
+ − 196 return self . removeHook ( client , service , node , hook_type or None , hook_arg or None )
+ − 197
+ − 198 def removeHook ( self , client , service , node , hook_type = None , hook_arg = None ):
+ − 199 """Remove a persistent or temporaty root
+ − 200
+ − 201 @param service(jid.JID): service of the node
+ − 202 @param node(unicode): Pubsub node
+ − 203 @param hook_type(unicode, None): same as for [addHook]
+ − 204 match all if None
+ − 205 @param hook_arg(unicode, None): same as for [addHook]
+ − 206 match all if None
+ − 207 @return(int): number of hooks removed
+ − 208 """
+ − 209 removed = 0
+ − 210 for hooks in ( client . _hooks , client . _hooks_temporary ):
+ − 211 if node in hooks :
+ − 212 for hook_data in hooks [ node ]:
+ − 213 if ( service != hook_data [ u 'service' ]
+ − 214 or hook_type is not None and hook_type != hook_data [ u 'type' ]
+ − 215 or hook_arg is not None and hook_arg != hook_data [ u 'arg' ]):
+ − 216 continue
+ − 217 hooks [ node ] . remove ( hook_data )
+ − 218 removed += 1
+ − 219 if not hooks [ node ]:
+ − 220 # no more hooks, we can remove the node
+ − 221 del hooks [ node ]
+ − 222 self . _removeNodeManager ( client , node )
+ − 223 else :
+ − 224 if hooks == client . _hooks :
+ − 225 hooks . force ( node )
+ − 226 return removed
+ − 227
+ − 228 def _listHooks ( self , profile ):
+ − 229 hooks_list = self . listHooks ( self . host . getClient ( profile ))
+ − 230 for hook in hooks_list :
+ − 231 hook [ u 'service' ] = hook [ u 'service' ] . full ()
+ − 232 hook [ u 'persistent' ] = C . boolConst ( hook [ u 'persistent' ])
+ − 233 return hooks_list
+ − 234
+ − 235 def listHooks ( self , client ):
+ − 236 """return list of registered hooks"""
+ − 237 hooks_list = []
+ − 238 for hooks in ( client . _hooks , client . _hooks_temporary ):
+ − 239 persistent = hooks is client . _hooks
+ − 240 for node , hooks_data in hooks . iteritems ():
+ − 241 for hook_data in hooks_data :
+ − 242 hooks_list . append ({ u 'service' : hook_data [ u 'service' ],
+ − 243 u 'node' : node ,
+ − 244 u 'type' : hook_data [ u 'type' ],
+ − 245 u 'arg' : hook_data [ u 'arg' ],
+ − 246 u 'persistent' : persistent
+ − 247 })
+ − 248 return hooks_list
+ − 249