# HG changeset patch # User Goffi # Date 1306421010 -7200 # Node ID c3ee630914baffdfcdfee0b233b796e82a557a7e # Parent 7f106052326f2e8f3531d44c958810a3e80fdcf4 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 diff -r 7f106052326f -r c3ee630914ba browser_side/register.py --- 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) diff -r 7f106052326f -r c3ee630914ba libervia.tac --- 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 . """ +#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)) diff -r 7f106052326f -r c3ee630914ba public/libervia.css --- 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; }