comparison libervia.tac @ 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 72c51a4839cc
comparison
equal deleted inserted replaced
45:7f106052326f 46:c3ee630914ba
17 17
18 You should have received a copy of the GNU Affero General Public License 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/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 #You need do adapt the following consts to your server
23 _REG_EMAIL_FROM = "NOREPLY@libervia.org"
24 _REG_EMAIL_SERVER = "localhost"
25 _REG_ADMIN_EMAIL = "goffi@goffi.org"
26 _NEW_ACCOUNT_SERVER = "localhost"
27 _NEW_ACCOUNT_DOMAIN = "tazar.int"
28 _NEW_ACCOUNT_RESOURCE = "libervia"
29
22 from twisted.application import internet, service 30 from twisted.application import internet, service
23 from twisted.internet import glib2reactor 31 from twisted.internet import glib2reactor
24 glib2reactor.install() 32 glib2reactor.install()
25 from twisted.internet import reactor, defer 33 from twisted.internet import reactor, defer
26 34 from twisted.mail.smtp import sendmail
27 from twisted.web import server 35 from twisted.web import server
28 from twisted.web import error as weberror 36 from twisted.web import error as weberror
29 from twisted.web.static import File 37 from twisted.web.static import File
30 from twisted.web.resource import Resource 38 from twisted.web.resource import Resource
31 from twisted.python.components import registerAdapter 39 from twisted.python.components import registerAdapter
32 from twisted.words.protocols.jabber.jid import JID 40 from twisted.words.protocols.jabber.jid import JID
33 from txjsonrpc.web import jsonrpc 41 from txjsonrpc.web import jsonrpc
34 from txjsonrpc import jsonrpclib 42 from txjsonrpc import jsonrpclib
35 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService 43 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService
44 from email.mime.text import MIMEText
45 from logging import debug, info, warning, error
36 import re 46 import re
37 import glob 47 import glob
38 import os.path 48 import os.path
39 import sys 49 import sys
40 from server_side.blog import MicroBlog 50 from server_side.blog import MicroBlog
73 83
74 def touch(self): 84 def touch(self):
75 if not self.__lock: 85 if not self.__lock:
76 server.Session.touch(self) 86 server.Session.touch(self)
77 87
88 class SATActionIDHandler(object):
89 """Manage SàT action id lifecycle"""
90 ID_LIFETIME = 30 #after this time (in seconds), id will be suppressed and action result will be ignored
91
92 def __init__(self):
93 self.waiting_ids = {}
94
95 def waitForId(self, id, callback, *args, **kwargs):
96 """Wait for an action result
97 @param id: id to wait for
98 @param callback: method to call when action gave a result back
99 @param *args: additional argument to pass to callback
100 @param **kwargs: idem"""
101 self.waiting_ids[id] = (callback, args, kwargs)
102 reactor.callLater(self.ID_LIFETIME, self.purgeID, id)
103
104 def purgeID(self, id):
105 """Called when an id has not be handled in time"""
106 if id in self.waiting_ids:
107 warning ("action of id %s has not been managed, id is now ignored" % id)
108 del self.waiting_ids[id]
109
110 def actionResultCb(self, answer_type, id, data):
111 """Manage the actionResult signal"""
112 if id in self.waiting_ids:
113 callback, args, kwargs = self.waiting_ids[id]
114 del self.waiting_ids[id]
115 callback(answer_type, id, data, *args, **kwargs)
78 116
79 class MethodHandler(jsonrpc.JSONRPC): 117 class MethodHandler(jsonrpc.JSONRPC):
80 118
81 def __init__(self, sat_host): 119 def __init__(self, sat_host):
82 jsonrpc.JSONRPC.__init__(self) 120 jsonrpc.JSONRPC.__init__(self)
230 - BAD REQUEST: something is wrong in the request (bad arguments, profile_key for login) 268 - BAD REQUEST: something is wrong in the request (bad arguments, profile_key for login)
231 - AUTH ERROR: either the profile or the password is wrong 269 - AUTH ERROR: either the profile or the password is wrong
232 - ALREADY WAITING: a request has already be made for this profile 270 - ALREADY WAITING: a request has already be made for this profile
233 - server.NOT_DONE_YET: the profile is being processed, the return value will be given by self._logged or self._logginError 271 - server.NOT_DONE_YET: the profile is being processed, the return value will be given by self._logged or self._logginError
234 """ 272 """
273 if 'new_account' in request.args:
274 return self._registerNewAccount(request.args)
275
235 try: 276 try:
236 _login = request.args['login'][0] 277 _login = request.args['login'][0]
237 if _login.startswith('@'): 278 if _login.startswith('@'):
238 raise Exception('No profile_key allowed') 279 raise Exception('No profile_key allowed')
239 _pass = request.args['password'][0] 280 _pass = request.args['password'][0]
253 return self._logged(_login, request, finish=False) 294 return self._logged(_login, request, finish=False)
254 295
255 self.profiles_waiting[_login] = request 296 self.profiles_waiting[_login] = request
256 self.sat_host.bridge.connect(_login) 297 self.sat_host.bridge.connect(_login)
257 return server.NOT_DONE_YET 298 return server.NOT_DONE_YET
299
300 def _postAccountCreation(self, answer_type, id, data, profile):
301 """Called when a account has just been created,
302 setup stuff has microblog access"""
303 def _connected(ignore):
304 mblog_d = defer.Deferred()
305 self.sat_host.bridge.setMicroblogAccess("open", profile, lambda: mblog_d.callback(None), mblog_d.errback)
306 mblog_d.addBoth(lambda ignore: self.sat_host.bridge.disconnect(profile))
307
308 d = defer.Deferred()
309 self.sat_host.bridge.asyncConnect(profile, lambda: d.callback(None), d.errback)
310 d.addCallback(_connected)
311
312 def _registerNewAccount(self, args):
313 """Create a new account, or return error
314 @param args: dict of args as given by the form
315 @return: "REGISTRATION" in case of success"""
316 try:
317 profile = login = args['login'][0]
318 email = args['email'][0]
319 except KeyError:
320 return "BAD REQUEST"
321 if not re.match(r'^[a-z0-9_-]+$', login, re.IGNORECASE) or \
322 not re.match(r'^.+@.+\..+', email, re.IGNORECASE):
323 return "BAD REQUEST"
324 #_charset = [chr(i) for i in range(0x21,0x7F)] #XXX: this charset seems to have some issues with openfire
325 _charset = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
326 import random
327 random.seed()
328 password = ''.join([random.choice(_charset) for i in range(15)])
329
330 if login in self.sat_host.bridge.getProfilesList(): #FIXME: must use a deferred + create a new profile check method
331 return "ALREADY EXISTS"
332
333 #we now create the profile
334 self.sat_host.bridge.createProfile(login)
335 #FIXME: values must be in a config file instead of hardcoded
336 self.sat_host.bridge.setParam("JabberID", "%s@%s/%s" % (login, _NEW_ACCOUNT_DOMAIN, _NEW_ACCOUNT_RESOURCE), "Connection", profile)
337 self.sat_host.bridge.setParam("Server", _NEW_ACCOUNT_SERVER, "Connection", profile)
338 self.sat_host.bridge.setParam("Password", password, "Connection", profile)
339 #and the account
340 action_id = self.sat_host.bridge.registerNewAccount(login, password, email, "tazar.int", 5222)
341 self.sat_host.action_handler.waitForId(action_id, self._postAccountCreation, profile)
342
343 #time to send the email
344
345 _email_host = _REG_EMAIL_SERVER
346 _email_from = _REG_EMAIL_FROM
347
348 def email_ok(ignore):
349 print ("Account creation email sent to %s" % email)
350
351 def email_ko(ignore):
352 #TODO: return error code to user
353 error ("Failed to send email to %s" % email)
354
355 body = (u"""Welcome to Libervia, a Salut à Toi project part
356
357 /!\\ WARNING, THIS IS ONLY A TECHNICAL DEMO, DON'T USE THIS ACCOUNT FOR ANY SERIOUS PURPOSE /!\\
358
359 Here are your connection informations:
360 login: %(login)s
361 password: %(password)s
362
363 Any feedback welcome
364
365 Cheers
366 Goffi""" % { 'login': login, 'password': password }).encode('utf-8')
367 msg = MIMEText(body, 'plain', 'UTF-8')
368 msg['Subject'] = 'Libervia account created'
369 msg['From'] = _email_from
370 msg['To'] = email
371
372 d = sendmail(_email_host, _email_from, email, msg.as_string())
373 d.addCallbacks(email_ok, email_ko)
374
375 #email to the administrator
376
377 body = (u"""New account created: %(login)s [%(email)s]""" % { 'login': login, 'email': email }).encode('utf-8')
378 msg = MIMEText(body, 'plain', 'UTF-8')
379 msg['Subject'] = 'Libervia new account created'
380 msg['From'] = _email_from
381 msg['To'] = _REG_ADMIN_EMAIL
382
383 d = sendmail(_email_host, _email_from, email, msg.as_string())
384 d.addCallbacks(email_ok, email_ko)
385 print "rturn REGISTRATION"
386 return "REGISTRATION"
258 387
259 def __cleanWaiting(self, login): 388 def __cleanWaiting(self, login):
260 """Remove login from waiting queue""" 389 """Remove login from waiting queue"""
261 try: 390 try:
262 del self.profiles_waiting[login] 391 del self.profiles_waiting[login]
403 self.signal_handler = SignalHandler(self) 532 self.signal_handler = SignalHandler(self)
404 _register = Register(self) 533 _register = Register(self)
405 self.signal_handler.plugRegister(_register) 534 self.signal_handler.plugRegister(_register)
406 self.sessions = {} #key = session value = user 535 self.sessions = {} #key = session value = user
407 self.prof_connected = set() #Profiles connected 536 self.prof_connected = set() #Profiles connected
537 self.action_handler = SATActionIDHandler()
408 ## bridge ## 538 ## bridge ##
409 try: 539 try:
410 self.bridge=DBusBridgeFrontend() 540 self.bridge=DBusBridgeFrontend()
411 except BridgeExceptionNoService: 541 except BridgeExceptionNoService:
412 print(u"Can't connect to SàT backend, are you sure it's launched ?") 542 print(u"Can't connect to SàT backend, are you sure it's launched ?")
413 sys.exit(1) 543 sys.exit(1)
414 self.bridge.register("connected", self.signal_handler.connected) 544 self.bridge.register("connected", self.signal_handler.connected)
415 self.bridge.register("connectionError", self.signal_handler.connectionError) 545 self.bridge.register("connectionError", self.signal_handler.connectionError)
546 self.bridge.register("actionResult", self.action_handler.actionResultCb, "request")
416 for signal_name in ['presenceUpdate', 'personalEvent', 'newMessage', 'roomJoined', 'roomUserJoined', 'roomUserLeft', 'tarotGameStarted', 'tarotGameNew', 547 for signal_name in ['presenceUpdate', 'personalEvent', 'newMessage', 'roomJoined', 'roomUserJoined', 'roomUserLeft', 'tarotGameStarted', 'tarotGameNew',
417 'tarotGameChooseContrat', 'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore']: 548 'tarotGameChooseContrat', 'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore']:
418 self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name)) 549 self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name))
419 root.putChild('json_signal_api', self.signal_handler) 550 root.putChild('json_signal_api', self.signal_handler)
420 root.putChild('json_api', MethodHandler(self)) 551 root.putChild('json_api', MethodHandler(self))