changeset 46:c3ee630914ba

Account creation * browser side: - login dialog has been extended to manage subscription * server side: - SATActionIDHandler to manage replies to action - account is created on subscribtion form, a password is created, and 2 emails are send (one to user, one to administrator) - access of main microblog is set to open
author Goffi <goffi@goffi.org>
date Thu, 26 May 2011 16:43:30 +0200
parents 7f106052326f
children 7cabe3c1a5f2
files browser_side/register.py libervia.tac public/libervia.css
diffstat 3 files changed, 226 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/browser_side/register.py	Wed May 25 15:45:16 2011 +0200
+++ b/browser_side/register.py	Thu May 26 16:43:30 2011 +0200
@@ -28,9 +28,12 @@
 from pyjamas.ui.TextBox import TextBox
 from pyjamas.ui.FormPanel import FormPanel
 from pyjamas.ui.Button import Button
+from pyjamas.ui.Label import Label
+from pyjamas.ui.CheckBox import CheckBox
 from pyjamas.ui.DialogBox import DialogBox
 from pyjamas import Window
 from pyjamas.ui import HasAlignment
+import re
 
 
 
@@ -46,24 +49,89 @@
         self.setMethod(FormPanel.METHOD_POST)
         vPanel = VerticalPanel()
         vPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER)
-        self.loginBox = TextBox()
-        self.loginBox.setName("login")
-        self.passBox = PasswordTextBox()
-        self.passBox.setName("password")
-        grid = Grid(2, 2)
-        grid.setText(0,0,"Login:")
-        grid.setWidget(0,1, self.loginBox)
-        grid.setText(1,0, "Password:")
-        grid.setWidget(1,1, self.passBox)
-        vPanel.add(grid)
-        login_but = Button("Login", getattr(self, "onLogin"))
-        vPanel.add(login_but)
+        self.warning_msg = Label()
+        self.warning_msg.setVisible(False)
+        self.warning_msg.setStyleName('formWarning')
+        vPanel.add(self.warning_msg)
+
+        self.login_box = TextBox()
+        self.login_box.setName("login")
+        self.pass_box = PasswordTextBox()
+        self.pass_box.setName("password")
+        login_grid = Grid(2, 2)
+        login_grid.setText(0,0,"Login:")
+        login_grid.setWidget(0,1, self.login_box)
+        login_grid.setText(1,0, "Password:")
+        login_grid.setWidget(1,1, self.pass_box)
+        vPanel.add(login_grid)
+        
+        self.new_account = CheckBox('Register new account')
+        self.new_account.setName('new_account')
+        self.new_account.addClickListener(self.onNewAccountChecked)
+        vPanel.add(self.new_account)
+        
+        
+        self.email_box = TextBox()
+        self.email_box.setName("email")
+        self.register_grid = Grid(1, 2)
+        self.register_grid.setText(0,0,"email:")
+        self.register_grid.setWidget(0,1, self.email_box)
+        self.register_grid.setVisible(False)
+        vPanel.add(self.register_grid)
+        self.info_register_lb = Label('Your password will be sent to the given email')
+        self.info_register_lb.setVisible(False)
+        vPanel.add(self.register_grid)
+        vPanel.add(self.info_register_lb)
+        
+        self.login_but = Button("Login", getattr(self, "onLogin"))
+        vPanel.add(self.login_but)
+        self.register_but = Button("Register", getattr(self, "onRegister"))
+        self.register_but.setVisible(False)
+        vPanel.add(self.register_but)
         self.add(vPanel)
         self.addFormHandler(self)
         self.setAction('register_api/login')
 
+    def changeMode(self, mode):
+        """Change the configuration of the dialog
+        @param mode: "login" or "register"""
+        if mode == "register":
+            self.pass_box.setEnabled(False)
+            self.register_grid.setVisible(True)
+            self.info_register_lb.setVisible(True)
+            self.login_but.setVisible(False)
+            self.register_but.setVisible(True)
+            self.new_account.setChecked(True)
+        else:
+            self.pass_box.setEnabled(True)
+            self.register_grid.setVisible(False)
+            self.info_register_lb.setVisible(False)
+            self.login_but.setVisible(True)
+            self.register_but.setVisible(False)
+            self.new_account.setChecked(False)
+
+
+    def onNewAccountChecked(self, sender):
+        if sender.isChecked():
+            self.changeMode("register")
+        else:
+            self.changeMode("login")
+
     def onLogin(self):
         self.submit()
+
+    def onRegister(self):
+        print self.login_box.getText()
+        if not re.match(r'^[a-z0-9_-]+$',self.login_box.getText(), re.IGNORECASE):
+            self.warning_msg.setText('Invaling login, valid characters are a-z A-Z 0-9 _ -')
+            self.warning_msg.setVisible(True)
+        elif not re.match(r'^.+@.+\..+', self.email_box.getText(), re.IGNORECASE):
+            self.warning_msg.setText('Invaling email address')
+            self.warning_msg.setVisible(True)
+        else:
+            self.warning_msg.setVisible(False)
+            self.submit()
+
     
     def onSubmit(self, event):
         pass
@@ -74,6 +142,14 @@
             Window.alert('You login and/or password is incorrect. Please try again')
         elif result == "LOGGED":
             self.callback()
+        elif result == "ALREADY EXISTS":
+            self.warning_msg.setText('This login already exists, please choose an other one')
+            self.warning_msg.setVisible(True)
+        elif result == "REGISTRATION":
+            self.warning_msg.setVisible(False)
+            self.changeMode('login')
+            self.pass_box.setText('')
+            Window.alert('An email has been sent to you with your login informations\nPlease remember that this is ONLY A TECHNICAL DEMO')
         else:
             Window.alert('Submit error: %s' % result)
 
--- a/libervia.tac	Wed May 25 15:45:16 2011 +0200
+++ b/libervia.tac	Thu May 26 16:43:30 2011 +0200
@@ -19,11 +19,19 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
+#You need do adapt the following consts to your server
+_REG_EMAIL_FROM = "NOREPLY@libervia.org"
+_REG_EMAIL_SERVER = "localhost"
+_REG_ADMIN_EMAIL = "goffi@goffi.org"
+_NEW_ACCOUNT_SERVER = "localhost"
+_NEW_ACCOUNT_DOMAIN = "tazar.int"
+_NEW_ACCOUNT_RESOURCE = "libervia"
+
 from twisted.application import internet, service
 from twisted.internet import glib2reactor
 glib2reactor.install()
 from twisted.internet import reactor, defer
-
+from twisted.mail.smtp import sendmail
 from twisted.web import server
 from twisted.web import error as weberror
 from twisted.web.static import File
@@ -33,6 +41,8 @@
 from txjsonrpc.web import jsonrpc
 from txjsonrpc import jsonrpclib
 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService
+from email.mime.text import MIMEText
+from logging import debug, info, warning, error
 import re
 import glob
 import os.path
@@ -75,6 +85,34 @@
         if not self.__lock:
             server.Session.touch(self)
 
+class SATActionIDHandler(object):
+    """Manage SàT action id lifecycle"""
+    ID_LIFETIME = 30 #after this time (in seconds), id will be suppressed and action result will be ignored
+
+    def __init__(self):
+        self.waiting_ids = {}
+
+    def waitForId(self, id, callback, *args, **kwargs):
+        """Wait for an action result
+        @param id: id to wait for
+        @param callback: method to call when action gave a result back
+        @param *args: additional argument to pass to callback
+        @param **kwargs: idem"""
+        self.waiting_ids[id] = (callback, args, kwargs)
+        reactor.callLater(self.ID_LIFETIME, self.purgeID, id)
+
+    def purgeID(self, id):
+        """Called when an id has not be handled in time"""
+        if id in self.waiting_ids:
+            warning ("action of id %s has not been managed, id is now ignored" % id)
+            del self.waiting_ids[id]
+
+    def actionResultCb(self, answer_type, id, data):
+        """Manage the actionResult signal"""
+        if id in self.waiting_ids:
+            callback, args, kwargs = self.waiting_ids[id]
+            del self.waiting_ids[id]
+            callback(answer_type, id, data, *args, **kwargs)
 
 class MethodHandler(jsonrpc.JSONRPC):
 
@@ -232,6 +270,9 @@
             - ALREADY WAITING: a request has already be made for this profile
             - server.NOT_DONE_YET: the profile is being processed, the return value will be given by self._logged or self._logginError
         """ 
+        if 'new_account' in request.args:
+            return self._registerNewAccount(request.args)
+
         try:
             _login = request.args['login'][0]
             if _login.startswith('@'):
@@ -256,6 +297,94 @@
         self.sat_host.bridge.connect(_login) 
         return server.NOT_DONE_YET
 
+    def _postAccountCreation(self, answer_type, id, data, profile):
+        """Called when a account has just been created,
+        setup stuff has microblog access"""
+        def _connected(ignore):
+            mblog_d = defer.Deferred()
+            self.sat_host.bridge.setMicroblogAccess("open", profile, lambda: mblog_d.callback(None), mblog_d.errback)
+            mblog_d.addBoth(lambda ignore: self.sat_host.bridge.disconnect(profile))
+       
+        d = defer.Deferred()
+        self.sat_host.bridge.asyncConnect(profile, lambda: d.callback(None), d.errback)
+        d.addCallback(_connected)
+
+    def _registerNewAccount(self, args):
+        """Create a new account, or return error
+        @param args: dict of args as given by the form
+        @return: "REGISTRATION" in case of success"""
+        try:
+            profile = login = args['login'][0]
+            email = args['email'][0]
+        except KeyError:
+            return "BAD REQUEST"
+        if not re.match(r'^[a-z0-9_-]+$', login, re.IGNORECASE) or \
+           not re.match(r'^.+@.+\..+', email, re.IGNORECASE):
+            return "BAD REQUEST"
+        #_charset = [chr(i) for i in range(0x21,0x7F)] #XXX: this charset seems to have some issues with openfire
+        _charset = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
+        import random
+        random.seed()
+        password = ''.join([random.choice(_charset) for i in range(15)])
+
+        if login in self.sat_host.bridge.getProfilesList(): #FIXME: must use a deferred + create a new profile check method
+            return "ALREADY EXISTS"
+
+        #we now create the profile
+        self.sat_host.bridge.createProfile(login)
+        #FIXME: values must be in a config file instead of hardcoded
+        self.sat_host.bridge.setParam("JabberID", "%s@%s/%s" % (login, _NEW_ACCOUNT_DOMAIN, _NEW_ACCOUNT_RESOURCE), "Connection", profile) 
+        self.sat_host.bridge.setParam("Server", _NEW_ACCOUNT_SERVER, "Connection", profile)
+        self.sat_host.bridge.setParam("Password", password, "Connection", profile)
+        #and the account
+        action_id = self.sat_host.bridge.registerNewAccount(login, password, email, "tazar.int", 5222)
+        self.sat_host.action_handler.waitForId(action_id, self._postAccountCreation, profile)
+
+        #time to send the email
+
+        _email_host = _REG_EMAIL_SERVER
+        _email_from = _REG_EMAIL_FROM
+
+        def email_ok(ignore):
+            print ("Account creation email sent to %s" % email)
+
+        def email_ko(ignore):
+            #TODO: return error code to user
+            error ("Failed to send email to %s" % email)
+        
+        body = (u"""Welcome to Libervia, a Salut à Toi project part
+
+/!\\ WARNING, THIS IS ONLY A TECHNICAL DEMO, DON'T USE THIS ACCOUNT FOR ANY SERIOUS PURPOSE /!\\
+
+Here are your connection informations:
+login: %(login)s
+password: %(password)s
+
+Any feedback welcome
+
+Cheers
+Goffi""" % { 'login': login, 'password': password }).encode('utf-8')
+        msg = MIMEText(body, 'plain', 'UTF-8')
+        msg['Subject'] = 'Libervia account created'
+        msg['From'] = _email_from
+        msg['To'] = email
+
+        d = sendmail(_email_host, _email_from, email, msg.as_string())
+        d.addCallbacks(email_ok, email_ko)
+
+        #email to the administrator
+
+        body = (u"""New account created: %(login)s [%(email)s]""" % { 'login': login, 'email': email }).encode('utf-8')
+        msg = MIMEText(body, 'plain', 'UTF-8')
+        msg['Subject'] = 'Libervia new account created'
+        msg['From'] = _email_from
+        msg['To'] = _REG_ADMIN_EMAIL
+
+        d = sendmail(_email_host, _email_from, email, msg.as_string())
+        d.addCallbacks(email_ok, email_ko)
+        print "rturn REGISTRATION"
+        return "REGISTRATION"
+
     def __cleanWaiting(self, login):
         """Remove login from waiting queue"""
         try:
@@ -405,6 +534,7 @@
         self.signal_handler.plugRegister(_register)
         self.sessions = {} #key = session value = user
         self.prof_connected = set() #Profiles connected
+        self.action_handler = SATActionIDHandler()
         ## bridge ##
         try:
             self.bridge=DBusBridgeFrontend()
@@ -413,6 +543,7 @@
             sys.exit(1)
         self.bridge.register("connected", self.signal_handler.connected)
         self.bridge.register("connectionError", self.signal_handler.connectionError)
+        self.bridge.register("actionResult", self.action_handler.actionResultCb, "request") 
         for signal_name in ['presenceUpdate', 'personalEvent', 'newMessage', 'roomJoined', 'roomUserJoined', 'roomUserLeft', 'tarotGameStarted', 'tarotGameNew',
                             'tarotGameChooseContrat', 'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore']:
             self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name))
--- a/public/libervia.css	Wed May 25 15:45:16 2011 +0200
+++ b/public/libervia.css	Thu May 26 16:43:30 2011 +0200
@@ -140,6 +140,12 @@
 }
 
 /* Custom Dialogs */
+
+.formWarning { /* used when a form is not valid and must be corrected before submission */
+    font-weight: bold;
+    color: red;
+}
+
 .contactsChooser {
     text-align: center;
 }