0
|
1 #!/usr/bin/env python2 |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 # SAT plugin for account creation (experimental) |
1
|
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Jérôme Poisson (goffi@goffi.org) |
0
|
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 twisted.words.xish import domish |
|
21 from wokkel.subprotocols import XMPPHandler, IQHandlerMixin |
|
22 from wokkel import disco, iwokkel, data_form |
|
23 from twisted.words.protocols.jabber import jid |
|
24 from zope.interface import implements |
|
25 import uuid |
|
26 import sqlite3 |
|
27 from os import path |
|
28 from collections import OrderedDict |
|
29 import gettext |
|
30 gettext.install('sat', "i18n", unicode=True) |
|
31 |
|
32 DATABASE = "salut.db" |
|
33 ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list") |
|
34 NS_COMMANDS = "http://jabber.org/protocol/commands" |
|
35 NS_SEARCH = 'jabber:iq:search' |
|
36 QUERY_SEARCH = "/query[@xmlns='jabber:iq:search']" |
|
37 SUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='subscribe']" |
|
38 UNSUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='unsubscribe']" |
|
39 INSTRUCTIONS = _(u'This is a minimal directory for the Libervia demo (Salut à Toi project). For testing purpose only.') |
|
40 FORM_INSTRUCTIONS = [INSTRUCTIONS] |
|
41 |
|
42 SUBSCRIBE_TXT = _(u"Please give some words about you, then submit to be registered on the directory") |
|
43 SUBSCRIBE_DONE = _("You are now registered on the directory") |
|
44 UNSUBSCRIBE_DONE = _("You have been removed from the directory") |
|
45 DB_CREATE = ['PRAGMA user_version=0', |
|
46 'CREATE TABLE directory (jid TEXT PRIMARY KEY, description TEXT)'] |
|
47 |
|
48 class SalutGateway(XMPPHandler, IQHandlerMixin): |
|
49 implements(iwokkel.IDisco) |
|
50 |
|
51 def __init__(self, component=False): |
|
52 XMPPHandler.__init__(self) |
|
53 IQHandlerMixin.__init__(self) |
|
54 self.component = component |
|
55 self.discoHandler = disco.DiscoHandler() |
|
56 new_db = not path.exists(DATABASE) |
|
57 conn = sqlite3.connect(DATABASE) |
|
58 self.db = conn.cursor() # we use SQLite in a blocking way, performance is not such a big deal here |
|
59 if new_db: |
|
60 for statement in DB_CREATE: |
|
61 self.db.execute(statement) |
|
62 |
|
63 def connectionMade(self): |
|
64 print "Connected!" |
|
65 self.xmlstream.addObserver("/iq[@type='get']" + QUERY_SEARCH, self.handleFieldsRequest) |
|
66 self.xmlstream.addObserver("/iq[@type='set']" + QUERY_SEARCH, self.handleSearchRequest) |
|
67 self.xmlstream.addObserver(SUBSCRIBE_CMD, self.onSubscribe) |
|
68 self.xmlstream.addObserver(UNSUBSCRIBE_CMD, self.onUnsubscribe) |
|
69 self.discoHandler.setHandlerParent(self.parent) |
|
70 |
|
71 def connectionLost(self, reason): |
|
72 print "Disconnected!" |
|
73 |
|
74 def handleFieldsRequest(self, request): |
|
75 result = domish.Element((None, 'iq')) |
|
76 result['type'] = 'result' |
|
77 result['id'] = request['id'] |
|
78 result['to'] = request['from'] |
|
79 query_elt = result.addElement('query', NS_SEARCH) |
|
80 instructions_elt = query_elt.addElement('instructions', content=INSTRUCTIONS) |
|
81 form = data_form.Form('form', title=_('Directory search'), |
|
82 instructions=FORM_INSTRUCTIONS, |
|
83 formNamespace=NS_SEARCH) |
|
84 form.addField(data_form.Field('fixed', label=_('Enter part of description or jid to find somebody,'))) |
|
85 form.addField(data_form.Field('fixed', label=('let empty to have a full list of people'))) |
|
86 form.addField(data_form.Field('text-single', 'jid', label=_('jid'))) |
|
87 form.addField(data_form.Field('text-single', 'description', label=_('Description'))) |
|
88 query_elt.addChild(form.toElement()) |
|
89 self.xmlstream.send(result) |
|
90 |
|
91 def handleSearchRequest(self, request): |
|
92 query = ["SELECT jid, description FROM directory"] |
|
93 args = OrderedDict() |
|
94 try: |
|
95 query_elt = request.elements(NS_SEARCH, 'query').next() |
|
96 form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next() |
|
97 parsed_form = data_form.Form.fromElement(form_elt) |
|
98 for col in ('jid', 'description'): |
|
99 value = parsed_form[col].strip() |
|
100 if value: |
|
101 args[col] = value |
|
102 except (StopIteration, KeyError): |
|
103 raise ValueError # TODO: proper error handling |
|
104 |
|
105 if args: |
|
106 query.append("WHERE") |
|
107 query.append(" AND ".join(("%s LIKE ?" % col for col in args))) |
|
108 |
|
109 row_iter = self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()])) |
|
110 |
|
111 result = domish.Element((None, 'iq')) |
|
112 result['type'] = 'result' |
|
113 result['id'] = request['id'] |
|
114 result['to'] = request['from'] |
|
115 query_elt = result.addElement('query', NS_SEARCH) |
|
116 x_form = data_form.Form('result', formNamespace = NS_SEARCH) |
|
117 x_form_elt = x_form.toElement() |
|
118 reported_elt = x_form_elt.addElement('reported') |
|
119 jid_field_elt = reported_elt.addElement('field') |
|
120 jid_field_elt['label'] = 'Jabber ID' |
|
121 jid_field_elt['var'] = 'jid' |
|
122 description_field_elt = reported_elt.addElement('field') |
|
123 description_field_elt['label'] = 'Description' |
|
124 description_field_elt['var'] = 'description' |
|
125 for row in row_iter: |
|
126 for col, value in zip(('jid', 'description'), row): |
|
127 item_elt = x_form_elt.addElement('item') |
|
128 field_elt = item_elt.addElement('field') |
|
129 field_elt['var'] = col |
|
130 value_elt = field_elt.addElement('value', content=value) |
|
131 |
|
132 query_elt.addChild(x_form_elt) |
|
133 self.xmlstream.send(result) |
|
134 |
|
135 def onSubscribe(self, request): |
|
136 result = domish.Element((None, 'iq')) |
|
137 result['type'] = 'result' |
|
138 result['id'] = request['id'] |
|
139 result['to'] = request['from'] |
|
140 from_ = jid.JID(request['from']) |
|
141 request_cmd = request.elements(NS_COMMANDS, 'command').next() |
|
142 command_elt = result.addElement('command', NS_COMMANDS) |
|
143 try: |
|
144 session_id = request_cmd['sessionid'] |
|
145 except KeyError: |
|
146 session_id = None |
|
147 |
|
148 if session_id is None: |
|
149 # first request, we send the form |
|
150 command_elt['status'] = 'executing' |
|
151 session_id = str(uuid.uuid4()) |
|
152 actions_elt = command_elt.addElement('actions') |
|
153 actions_elt['execute'] = 'next' |
|
154 actions_elt.addElement('next') |
|
155 form = data_form.Form('form', instructions=FORM_INSTRUCTIONS, title=_('Directory subscription')) |
|
156 infos = data_form.Field('fixed', value=SUBSCRIBE_TXT) |
|
157 desc = data_form.Field('text-single', 'description', label=_(u"Some words about you")) |
|
158 form.addField(infos) |
|
159 form.addField(desc) |
|
160 command_elt.addChild(form.toElement()) |
|
161 else: |
|
162 req_forms = request_cmd.elements(data_form.NS_X_DATA, 'x') |
|
163 try: |
|
164 req_form = req_forms.next() |
|
165 parsed_form = data_form.Form.fromElement(req_form) |
|
166 description = parsed_form['description'] |
|
167 except (StopIteration, KeyError): |
|
168 raise ValueError # TODO: properly cancel the command |
|
169 self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_.userhost(), description)) |
|
170 self.db.connection.commit() |
|
171 command_elt['status'] = 'completed' |
|
172 note_elt = command_elt.addElement('note') |
|
173 note_elt['type'] = 'info' |
|
174 note_elt.addContent(SUBSCRIBE_DONE) |
|
175 command_elt['sessionid'] = session_id |
|
176 command_elt['node'] = request_cmd['node'] |
|
177 self.xmlstream.send(result) |
|
178 |
|
179 def onUnsubscribe(self, request): |
|
180 result = domish.Element((None, 'iq')) |
|
181 result['type'] = 'result' |
|
182 result['id'] = request['id'] |
|
183 result['to'] = request['from'] |
|
184 from_ = jid.JID(request['from']) |
|
185 request_cmd = request.elements(NS_COMMANDS, 'command').next() |
|
186 command_elt = result.addElement('command', NS_COMMANDS) |
|
187 self.db.execute('DELETE FROM directory WHERE jid=?', (from_.userhost(),)) |
|
188 self.db.connection.commit() |
|
189 command_elt['status'] = 'completed' |
|
190 note_elt = command_elt.addElement('note') |
|
191 note_elt['type'] = 'info' |
|
192 note_elt.addContent(UNSUBSCRIBE_DONE) |
|
193 command_elt['node'] = request_cmd['node'] |
|
194 self.xmlstream.send(result) |
|
195 |
|
196 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): |
|
197 return [disco.DiscoFeature(NS_SEARCH), |
|
198 disco.DiscoIdentity(u"directory", u"user", u"salut"), |
|
199 disco.DiscoFeature(NS_COMMANDS), |
|
200 ID_CMD_LIST] |
|
201 |
|
202 def getDiscoItems(self, requestor, target, nodeIdentifier=''): |
|
203 ret = [] |
|
204 if nodeIdentifier == NS_COMMANDS: |
|
205 ret.append(disco.DiscoItem(target, "subscribe", "Subscribe to the directory")) |
|
206 ret.append(disco.DiscoItem(target, "unsubscribe", "Unsubscribe from the directory")) |
|
207 return ret |