comparison twisted/plugins/pubsub.py @ 405:c56a728412f1

file organisation + setup refactoring: - `/src` has been renamed to `/sat_pubsub`, this is the recommended naming convention - revamped `setup.py` on the basis of SàT's `setup.py` - added a `VERSION` which is the unique place where version number will now be set - use same trick as in SàT to specify dev version (`D` at the end) - use setuptools_scm to retrieve Mercurial hash when in dev version
author Goffi <goffi@goffi.org>
date Fri, 16 Aug 2019 12:00:02 +0200
parents src/twisted/plugins/pubsub.py@aa3a464df605
children a58610ab2983
comparison
equal deleted inserted replaced
404:105a0772eedd 405:c56a728412f1
1 #!/usr/bin/python
2 #-*- coding: utf-8 -*-
3
4 # Copyright (c) 2012-2019 Jérôme Poisson
5 # Copyright (c) 2003-2011 Ralph Meijer
6
7
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
17
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # --
21
22 # This program is based on Idavoll (http://idavoll.ik.nu/),
23 # originaly written by Ralph Meijer (http://ralphm.net/blog/)
24 # It is sublicensed under AGPL v3 (or any later version) as allowed by the original
25 # license.
26
27 # --
28
29 # Here is a copy of the original license:
30
31 # Copyright (c) 2003-2011 Ralph Meijer
32
33 # Permission is hereby granted, free of charge, to any person obtaining
34 # a copy of this software and associated documentation files (the
35 # "Software"), to deal in the Software without restriction, including
36 # without limitation the rights to use, copy, modify, merge, publish,
37 # distribute, sublicense, and/or sell copies of the Software, and to
38 # permit persons to whom the Software is furnished to do so, subject to
39 # the following conditions:
40
41 # The above copyright notice and this permission notice shall be
42 # included in all copies or substantial portions of the Software.
43
44 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
45 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
46 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
47 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
48 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
49 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
50 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
51
52
53 import csv
54 import sat_pubsub
55 import sys
56 from twisted.application.service import IServiceMaker
57 from twisted.application import service
58 from twisted.python import usage, log
59 from twisted.words.protocols.jabber.jid import JID
60 from twisted.plugin import IPlugin
61
62 from wokkel.component import Component
63 from wokkel.disco import DiscoHandler
64 from wokkel.generic import FallbackHandler, VersionHandler
65 from wokkel.iwokkel import IPubSubResource
66 from wokkel import data_form
67 from wokkel import pubsub
68 from wokkel import rsm
69 from wokkel import mam
70 from zope.interface import implements
71
72 from sat_pubsub import const
73 from sat_pubsub import mam as pubsub_mam
74 from sat_pubsub import pubsub_admin
75 from sat_pubsub.backend import BackendService, ExtraDiscoHandler
76 from sat_pubsub.schema import SchemaHandler
77 from sat_pubsub.privilege import PrivilegesHandler
78 from sat_pubsub.delegation import DelegationsHandler
79 from os.path import expanduser, realpath
80 import ConfigParser
81
82
83 def coerceListType(value):
84 return csv.reader(
85 [value], delimiter=",", quotechar='"', skipinitialspace=True
86 ).next()
87
88
89 def coerceJidListType(value):
90 values = [JID(v) for v in coerceListType(value)]
91 if any((j.resource for j in values)):
92 raise ValueError(u"you must use bare jids")
93 return values
94
95
96
97 OPT_PARAMETERS_BOTH = [
98 ['jid', None, None, 'JID this component will be available at'],
99 ['xmpp_pwd', None, None, 'XMPP server component password'],
100 ['rhost', None, '127.0.0.1', 'XMPP server host'],
101 ['rport', None, '5347', 'XMPP server port'],
102 ['backend', None, 'pgsql', 'Choice of storage backend'],
103 ['db_user', None, None, 'Database user (pgsql backend)'],
104 ['db_name', None, 'pubsub', 'Database name (pgsql backend)'],
105 ['db_pass', None, None, 'Database password (pgsql backend)'],
106 ['db_host', None, None, 'Database host (pgsql backend)'],
107 ['db_port', None, None, 'Database port (pgsql backend)'],
108 ]
109 # here for future use
110 OPT_PARAMETERS_CFG = [
111 ["admins_jids_list", None, [], "List of administrators' bare jids",
112 coerceJidListType]
113 ]
114
115 CONFIG_FILENAME = u'sat'
116 # List of the configuration filenames sorted by ascending priority
117 CONFIG_FILES = [realpath(expanduser(path) + CONFIG_FILENAME + '.conf') for path in (
118 '/etc/', '/etc/{}/'.format(CONFIG_FILENAME),
119 '~/', '~/.',
120 '.config/', '.config/.',
121 '', '.')]
122 CONFIG_SECTION = 'pubsub'
123
124
125 class Options(usage.Options):
126 optParameters = OPT_PARAMETERS_BOTH
127
128 optFlags = [
129 ('verbose', 'v', 'Show traffic'),
130 ('hide-nodes', None, 'Hide all nodes for disco')
131 ]
132
133 def __init__(self):
134 """Read SàT Pubsub configuration file in order to overwrite the hard-coded default values.
135
136 Priority for the usage of the values is (from lowest to highest):
137 - hard-coded default values
138 - values from SàT configuration files
139 - values passed on the command line
140 """
141 # If we do it the reading later: after the command line options have been parsed, there's no good way to know
142 # if the options values are the hard-coded ones or if they have been passed on the command line.
143
144 # FIXME: must be refactored + code can be factorised with backend
145 config_parser = ConfigParser.SafeConfigParser()
146 config_parser.read(CONFIG_FILES)
147 for param in self.optParameters + OPT_PARAMETERS_CFG:
148 name = param[0]
149 try:
150 value = config_parser.get(CONFIG_SECTION, name)
151 if isinstance(value, unicode):
152 value = value.encode('utf-8')
153 try:
154 param[2] = param[4](value)
155 except IndexError: # the coerce method is optional
156 param[2] = value
157 except Exception as e:
158 log.err(u'Invalid value for setting "{name}": {msg}'.format(
159 name=name, msg=e))
160 sys.exit(1)
161 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
162 pass
163 usage.Options.__init__(self)
164 for opt_data in OPT_PARAMETERS_CFG:
165 self[opt_data[0]] = opt_data[2]
166
167 def postOptions(self):
168 if self['backend'] not in ['pgsql', 'memory']:
169 raise usage.UsageError, "Unknown backend!"
170 if self['backend'] == 'memory':
171 raise NotImplementedError('memory backend is not available at the moment')
172
173 self['jid'] = JID(self['jid']) if self['jid'] else None
174
175
176 class SatPubsubMaker(object):
177 implements(IServiceMaker, IPlugin)
178 tapname = "sat-pubsub"
179 description = u"Salut à Toi Publish-Subscribe Service Component".encode('utf-8')
180 options = Options
181
182 def makeService(self, config):
183 if not config['jid'] or not config['xmpp_pwd']:
184 raise usage.UsageError("You must specify jid and xmpp_pwd")
185 s = service.MultiService()
186
187 # Create backend service with storage
188
189 if config['backend'] == 'pgsql':
190 from twisted.enterprise import adbapi
191 from sat_pubsub.pgsql_storage import Storage
192 from psycopg2.extras import NamedTupleConnection
193 keys_map = {
194 'db_user': 'user',
195 'db_pass': 'password',
196 'db_name': 'database',
197 'db_host': 'host',
198 'db_port': 'port',
199 }
200 kwargs = {}
201 for config_k, k in keys_map.iteritems():
202 v = config.get(config_k)
203 if v is None:
204 continue
205 kwargs[k] = v
206 dbpool = adbapi.ConnectionPool('psycopg2',
207 cp_reconnect=True,
208 client_encoding='utf-8',
209 connection_factory=NamedTupleConnection,
210 **kwargs
211 )
212 st = Storage(dbpool)
213 elif config['backend'] == 'memory':
214 from sat_pubsub.memory_storage import Storage
215 st = Storage()
216
217 bs = BackendService(st, config)
218 bs.setName('backend')
219 bs.setServiceParent(s)
220
221 # Set up XMPP server-side component with publish-subscribe capabilities
222
223 cs = Component(config["rhost"], int(config["rport"]),
224 config["jid"].full(), config["xmpp_pwd"])
225 cs.setName('component')
226 cs.setServiceParent(s)
227
228 cs.factory.maxDelay = 900
229
230 if config["verbose"]:
231 cs.logTraffic = True
232
233 FallbackHandler().setHandlerParent(cs)
234 VersionHandler(u'SàT Pubsub', sat_pubsub.__version__).setHandlerParent(cs)
235 DiscoHandler().setHandlerParent(cs)
236
237 ph = PrivilegesHandler(config['jid'])
238 ph.setHandlerParent(cs)
239 bs.privilege = ph
240
241 resource = IPubSubResource(bs)
242 resource.hideNodes = config["hide-nodes"]
243 resource.serviceJID = config["jid"]
244
245 ps = (rsm if const.FLAG_ENABLE_RSM else pubsub).PubSubService(resource)
246 ps.setHandlerParent(cs)
247 resource.pubsubService = ps
248
249 if const.FLAG_ENABLE_MAM:
250 mam_resource = pubsub_mam.MAMResource(bs)
251 mam_s = mam.MAMService(mam_resource)
252 mam_s.addFilter(data_form.Field(var=const.MAM_FILTER_CATEGORY))
253 mam_s.setHandlerParent(cs)
254
255 pa = pubsub_admin.PubsubAdminHandler(bs)
256 pa.setHandlerParent(cs)
257
258 sh = SchemaHandler()
259 sh.setHandlerParent(cs)
260
261 # wokkel.pubsub doesn't handle non pubsub# disco
262 # and we need to announce other feature, so this is a workaround
263 # to add them
264 # FIXME: propose a patch upstream to fix this situation
265 ed = ExtraDiscoHandler()
266 ed.setHandlerParent(cs)
267
268 # XXX: delegation must be instancied at the end,
269 # because it does some MonkeyPatching on handlers
270 dh = DelegationsHandler()
271 dh.setHandlerParent(cs)
272 bs.delegation = dh
273
274 return s
275
276 serviceMaker = SatPubsubMaker()