diff salut.py @ 0:d1bc50b64974

initial commit
author Goffi <goffi@goffi.org>
date Tue, 25 Feb 2014 22:22:18 +0100
parents
children 92549e4336a6
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/salut.py	Tue Feb 25 22:22:18 2014 +0100
@@ -0,0 +1,207 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# SAT plugin for account creation (experimental)
+# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from twisted.words.xish import domish
+from wokkel.subprotocols import XMPPHandler, IQHandlerMixin
+from wokkel import disco, iwokkel, data_form
+from twisted.words.protocols.jabber import jid
+from zope.interface import implements
+import uuid
+import sqlite3
+from os import path
+from collections import OrderedDict
+import gettext
+gettext.install('sat', "i18n", unicode=True)
+
+DATABASE = "salut.db"
+ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list")
+NS_COMMANDS = "http://jabber.org/protocol/commands"
+NS_SEARCH = 'jabber:iq:search'
+QUERY_SEARCH = "/query[@xmlns='jabber:iq:search']"
+SUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='subscribe']"
+UNSUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='unsubscribe']"
+INSTRUCTIONS = _(u'This is a minimal directory for the Libervia demo (Salut à Toi project). For testing purpose only.')
+FORM_INSTRUCTIONS = [INSTRUCTIONS]
+
+SUBSCRIBE_TXT = _(u"Please give some words about you, then submit to be registered on the directory")
+SUBSCRIBE_DONE = _("You are now registered on the directory")
+UNSUBSCRIBE_DONE = _("You have been removed from the directory")
+DB_CREATE = ['PRAGMA user_version=0',
+             'CREATE TABLE directory (jid TEXT PRIMARY KEY, description TEXT)']
+
+class SalutGateway(XMPPHandler, IQHandlerMixin):
+    implements(iwokkel.IDisco)
+
+    def __init__(self, component=False):
+        XMPPHandler.__init__(self)
+        IQHandlerMixin.__init__(self)
+        self.component = component
+        self.discoHandler = disco.DiscoHandler()
+        new_db = not path.exists(DATABASE)
+        conn = sqlite3.connect(DATABASE)
+        self.db = conn.cursor() # we use SQLite in a blocking way, performance is not such a big deal here
+        if new_db:
+            for statement in DB_CREATE:
+                self.db.execute(statement)
+
+    def connectionMade(self):
+        print "Connected!"
+        self.xmlstream.addObserver("/iq[@type='get']" + QUERY_SEARCH, self.handleFieldsRequest)
+        self.xmlstream.addObserver("/iq[@type='set']" + QUERY_SEARCH, self.handleSearchRequest)
+        self.xmlstream.addObserver(SUBSCRIBE_CMD, self.onSubscribe)
+        self.xmlstream.addObserver(UNSUBSCRIBE_CMD, self.onUnsubscribe)
+        self.discoHandler.setHandlerParent(self.parent)
+
+    def connectionLost(self, reason):
+        print "Disconnected!"
+
+    def handleFieldsRequest(self, request):
+        result = domish.Element((None, 'iq'))
+        result['type'] = 'result'
+        result['id'] = request['id']
+        result['to'] = request['from']
+        query_elt = result.addElement('query', NS_SEARCH)
+        instructions_elt = query_elt.addElement('instructions', content=INSTRUCTIONS)
+        form = data_form.Form('form', title=_('Directory search'),
+                              instructions=FORM_INSTRUCTIONS,
+                              formNamespace=NS_SEARCH)
+        form.addField(data_form.Field('fixed', label=_('Enter part of description or jid to find somebody,')))
+        form.addField(data_form.Field('fixed', label=('let empty to have a full list of people')))
+        form.addField(data_form.Field('text-single', 'jid', label=_('jid')))
+        form.addField(data_form.Field('text-single', 'description', label=_('Description')))
+        query_elt.addChild(form.toElement())
+        self.xmlstream.send(result)
+
+    def handleSearchRequest(self, request):
+        query = ["SELECT jid, description FROM directory"]
+        args = OrderedDict()
+        try:
+            query_elt = request.elements(NS_SEARCH, 'query').next()
+            form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next()
+            parsed_form = data_form.Form.fromElement(form_elt)
+            for col in ('jid', 'description'):
+                value = parsed_form[col].strip()
+                if value:
+                    args[col] = value
+        except (StopIteration, KeyError):
+            raise ValueError # TODO: proper error handling
+
+        if args:
+            query.append("WHERE")
+            query.append(" AND ".join(("%s LIKE ?" % col for col in args)))
+
+        row_iter = self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()]))
+
+        result = domish.Element((None, 'iq'))
+        result['type'] = 'result'
+        result['id'] = request['id']
+        result['to'] = request['from']
+        query_elt = result.addElement('query', NS_SEARCH)
+        x_form = data_form.Form('result', formNamespace = NS_SEARCH)
+        x_form_elt = x_form.toElement()
+        reported_elt = x_form_elt.addElement('reported')
+        jid_field_elt = reported_elt.addElement('field')
+        jid_field_elt['label'] = 'Jabber ID'
+        jid_field_elt['var'] = 'jid'
+        description_field_elt = reported_elt.addElement('field')
+        description_field_elt['label'] = 'Description'
+        description_field_elt['var'] = 'description'
+        for row in row_iter:
+            for col, value in zip(('jid', 'description'), row):
+                item_elt = x_form_elt.addElement('item')
+                field_elt = item_elt.addElement('field')
+                field_elt['var'] = col
+                value_elt = field_elt.addElement('value', content=value)
+
+        query_elt.addChild(x_form_elt)
+        self.xmlstream.send(result)
+
+    def onSubscribe(self, request):
+        result = domish.Element((None, 'iq'))
+        result['type'] = 'result'
+        result['id'] = request['id']
+        result['to'] = request['from']
+        from_ = jid.JID(request['from'])
+        request_cmd = request.elements(NS_COMMANDS, 'command').next()
+        command_elt = result.addElement('command', NS_COMMANDS)
+        try:
+            session_id = request_cmd['sessionid']
+        except KeyError:
+            session_id = None
+
+        if session_id is None:
+            # first request, we send the form
+            command_elt['status'] = 'executing'
+            session_id = str(uuid.uuid4())
+            actions_elt = command_elt.addElement('actions')
+            actions_elt['execute'] = 'next'
+            actions_elt.addElement('next')
+            form = data_form.Form('form', instructions=FORM_INSTRUCTIONS, title=_('Directory subscription'))
+            infos = data_form.Field('fixed', value=SUBSCRIBE_TXT)
+            desc = data_form.Field('text-single', 'description', label=_(u"Some words about you"))
+            form.addField(infos)
+            form.addField(desc)
+            command_elt.addChild(form.toElement())
+        else:
+            req_forms = request_cmd.elements(data_form.NS_X_DATA, 'x')
+            try:
+                req_form = req_forms.next()
+                parsed_form = data_form.Form.fromElement(req_form)
+                description = parsed_form['description']
+            except (StopIteration, KeyError):
+                raise ValueError # TODO: properly cancel the command
+            self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_.userhost(), description))
+            self.db.connection.commit()
+            command_elt['status'] = 'completed'
+            note_elt = command_elt.addElement('note')
+            note_elt['type'] = 'info'
+            note_elt.addContent(SUBSCRIBE_DONE)
+        command_elt['sessionid'] = session_id
+        command_elt['node'] = request_cmd['node']
+        self.xmlstream.send(result)
+
+    def onUnsubscribe(self, request):
+        result = domish.Element((None, 'iq'))
+        result['type'] = 'result'
+        result['id'] = request['id']
+        result['to'] = request['from']
+        from_ = jid.JID(request['from'])
+        request_cmd = request.elements(NS_COMMANDS, 'command').next()
+        command_elt = result.addElement('command', NS_COMMANDS)
+        self.db.execute('DELETE FROM directory WHERE jid=?', (from_.userhost(),))
+        self.db.connection.commit()
+        command_elt['status'] = 'completed'
+        note_elt = command_elt.addElement('note')
+        note_elt['type'] = 'info'
+        note_elt.addContent(UNSUBSCRIBE_DONE)
+        command_elt['node'] = request_cmd['node']
+        self.xmlstream.send(result)
+
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+        return [disco.DiscoFeature(NS_SEARCH),
+                disco.DiscoIdentity(u"directory", u"user", u"salut"),
+                disco.DiscoFeature(NS_COMMANDS),
+                ID_CMD_LIST]
+
+    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+        ret = []
+        if nodeIdentifier == NS_COMMANDS:
+            ret.append(disco.DiscoItem(target, "subscribe", "Subscribe to the directory"))
+            ret.append(disco.DiscoItem(target, "unsubscribe", "Unsubscribe from the directory"))
+        return ret