[cpif] r363 - in trunk: . backend frontend-web

svn at argo.es svn at argo.es
Thu Sep 13 13:41:56 CEST 2007


Author: jcea
Date: Thu Sep 13 13:41:52 2007
New Revision: 363

Log:
El alta de usuarios es configurable:

solo "master", libre, o libre con
confirmación del "master".



Added:
   trunk/frontend-web/url_new_user_confirmation.py
      - copied, changed from r362, /trunk/frontend-web/url_nuevo_usuario_POST.py
Modified:
   trunk/TODO
   trunk/backend/database.py
   trunk/backend/upgrade.py
   trunk/frontend-web/skins.py
   trunk/frontend-web/url_edit_config.py
   trunk/frontend-web/url_nuevo_usuario.py
   trunk/frontend-web/url_nuevo_usuario_POST.py
   trunk/frontend-web/url_perfil.py

Modified: trunk/TODO
==============================================================================
--- trunk/TODO	(original)
+++ trunk/TODO	Thu Sep 13 13:41:52 2007
@@ -357,3 +357,24 @@
   para poder meter la web como subseccion de otra. Ojo con
   las redirecciones...
 
+- 20070913: FRONTAL WEB: Cuando un usuario se da de alta (si
+  se configura el servidor para permitirlo), el usuario
+  no existe realmente hasta que pulsa un enlace que le llega
+  por correo electronico. Hay que controlar que la estructura
+  que contiene las altas "pendientes" no crece de forma
+  ilimitada.
+
+- 20070913: FRONTAL WEB: Ahora que los usuarios pueden darse de
+  alta directamente, hay que tener mucho cuidado con los 
+  ataques XSS, especialmente porque el administrador puede
+  listar esos perfiles.
+
+- 20070913: FRONTAL WEB: Si los usuarios pueden darse de alta,
+  pero el "master" debe confirmarla, el usuario recibe un
+  aviso de que se le notificara la decision por correo electronico...
+  pero no se envia nunca!!!.
+
+- 20070913: Poder dar de baja usuarios que no han hecho login
+  nunca y que no tienen ningun mensaje privado. El caso evidente
+  son nuevas altas.
+

Modified: trunk/backend/database.py
==============================================================================
--- trunk/backend/database.py	(original)
+++ trunk/backend/database.py	Thu Sep 13 13:41:52 2007
@@ -1,7 +1,7 @@
 # $Id$
 
 
-VERSION_DB="2007090603"
+VERSION_DB="2007091303"
 
 import globales
 
@@ -34,6 +34,7 @@
     usuarios=PersistentDict()
     usuarios["usuarios"]=BTree()
     usuarios["num_usuarios"]=0
+    usuarios["pending_sign_ins"]=BTree()
     root["usuarios"]=usuarios
 
     metatags=PersistentDict()
@@ -74,6 +75,7 @@
     conf["http_max_clients"]=16
     conf["http_initial_timeout"]=30
     conf["http_keep_alive_timeout"]=5
+    conf["user_sign_in_mode"]=0  # Only "master" can create new users
     conf["openid_support"]=False # Because You could not have the libs!
     conf["allow_bbcode"]=True
     conf["allow_html"]=True
@@ -103,6 +105,13 @@
     setattr(globales,k,v)
 
 
+def usuario_new_cookie(conn,nick) :
+  root=conn.get_root()
+  import random,sys
+  cookie=str(random.randint(0,sys.maxint))+str(random.randint(0,sys.maxint))
+  root["usuarios"]["usuarios"][normaliza_nick(nick)]["cookie"]=cookie
+  return cookie
+
 # Si suministramos una cookie, de ahi se saca el
 # usuario y se comprueba su identidad.
 # Si pasamos nick/clave, de ahi comprobamos tambien su identidad
@@ -114,9 +123,8 @@
   if clave or force_login :
     usuario=root["usuarios"]["usuarios"].get(normaliza_nick(nick),None)
     if usuario and ((clave==usuario["clave"]) or force_login) :
-      import random,sys
-      cookie=str(random.randint(0,sys.maxint))+str(random.randint(0,sys.maxint))
-      usuario["cookie"]=cookie
+      if "no_login" in usuario : return None
+      cookie=usuario_new_cookie(conn,nick)
       nick=usuario["nick"]
       cookie="%s-%s" %(nick,cookie)
       return nick,cookie
@@ -133,6 +141,17 @@
 
   return None
 
+def set_usuario_no_login(conn,nick) :
+  root=conn.get_root()
+  usuario=root["usuarios"]["usuarios"][normaliza_nick(nick)]
+  usuario["no_login"]=True
+
+def unset_usuario_no_login(conn,nick) :
+  root=conn.get_root()
+  usuario=root["usuarios"]["usuarios"][normaliza_nick(nick)]
+  if "no_login" in usuario :
+    del usuario["no_login"]
+
 def get_static(conn,section,name) :
   root=conn.get_root()
   section=root["static"].get(section)
@@ -272,6 +291,34 @@
   usuario["email"]=email
   return True
 
+def get_usuario_pending(conn,nonce) :
+  root=conn.get_root()
+  usuarios_pending=root["usuarios"]["pending_sign_ins"]
+  v=usuarios_pending.get(nonce,None)
+  if not v : return None
+  del usuarios_pending[nonce]
+  return v[2:]  # We don't return the time
+
+# If we add new params, we must update "usuario_add".
+# Also, we would need to update the confirmation code (via URL).
+def usuario_add_pending(conn,nick,datos,clave=None,email="",OpenIDs=set()) :
+  root=conn.get_root()
+  usuarios=root["usuarios"]
+  usuarios_pending=usuarios["pending_sign_ins"]
+
+  nick_normalizado=normaliza_nick(nick)
+  assert nick_normalizado not in usuarios["usuarios"]
+
+  import time,random,sys
+  while True :
+    k=str(random.randint(0,sys.maxint))
+    if k not in usuarios_pending : break
+
+  usuarios_pending[k]=(time.time(),False,nick,datos,clave,email,OpenIDs)
+
+  return k
+
+# If we add new params, we must update "usuario_add_pending".
 def usuario_add(conn,nick,datos,clave=None,email="",OpenIDs=set()) :
   from durus.btree import BTree
   from durus.persistent_dict import PersistentDict

Modified: trunk/backend/upgrade.py
==============================================================================
--- trunk/backend/upgrade.py	(original)
+++ trunk/backend/upgrade.py	Thu Sep 13 13:41:52 2007
@@ -311,3 +311,15 @@
     root["static"]["icons"]=BTree()
     conn.commit()
 
+  if root["version del foro"]=="2007090603" :
+    print "Actualizando la base de datos: 2007090603 -> 2007091302"
+    root["version del foro"]="2007091302"
+    root["config"]["user_sign_in_mode"]=0  # Only "master" can create new users
+    conn.commit()
+
+  if root["version del foro"]=="2007091302" :
+    print "Actualizando la base de datos: 2007091302 -> 2007091303"
+    root["version del foro"]="2007091303"
+    root["usuarios"]["pending_sign_ins"]=BTree()
+    conn.commit()
+

Modified: trunk/frontend-web/skins.py
==============================================================================
--- trunk/frontend-web/skins.py	(original)
+++ trunk/frontend-web/skins.py	Thu Sep 13 13:41:52 2007
@@ -48,18 +48,19 @@
     }
 
 urls = {
-    '/':                    ['metatags'],
-    '/indice':              ['group_date', 'threads', 'threads'],
-    '/hilo':                ['thread_entries'],
-    '/nuevo_usuario':       ['new_user'],
-    '/login_err':           ['login_err'],
-    '/LOGIN':               ['login'],
-    '/error':               [],
-    '/stop':                [],
-    '/nuevo_usuario_POST':  [],
-    '/perfil_POST':         [],
-    '/toggle_readonly':     [],
-    '/toggle_sticky':       [],
+    '/':                       ['metatags'],
+    '/indice':                 ['group_date', 'threads', 'threads'],
+    '/hilo':                   ['thread_entries'],
+    '/nuevo_usuario':          ['new_user'],
+    '/login_err':              ['login_err'],
+    '/LOGIN':                  ['login'],
+    '/error':                  [],
+    '/stop':                   [],
+    '/nuevo_usuario_POST':     [],
+    '/perfil_POST':            [],
+    '/toggle_readonly':        [],
+    '/toggle_sticky':          [],
+    '/new_user_confirmation':  [],
     }
 
 import os

Modified: trunk/frontend-web/url_edit_config.py
==============================================================================
--- trunk/frontend-web/url_edit_config.py	(original)
+++ trunk/frontend-web/url_edit_config.py	Thu Sep 13 13:41:52 2007
@@ -20,6 +20,11 @@
   texto.append('<tr><td>Simultaneous HTTP connections</td><td><input type="text" name="http_max_clients" size="5" value="%d" /></td></tr>' %globales.http_max_clients)
   texto.append('<tr><td>Specify initial HTTP timeout</td><td><input type="text" name="http_initial_timeout" size="5" value="%d" /></td></tr>' %globales.http_initial_timeout)
   texto.append('<tr><td>Keep-alive timeout<br/>(if zero or False, no keep-alive support)</td><td><input type="text" name="http_keep_alive_timeout" size="5" value="%d" /></td></tr>' %globales.http_keep_alive_timeout)
+
+  texto.append('<tr><td>New Users Sign In</td><td><input type="radio" name="user_sign_in_mode" value="0" %s />Only "master" can create new users<br/>' %("CHECKED" if globales.user_sign_in_mode==0 else ""))
+  texto.append('<input type="radio" name="user_sign_in_mode" value="1" %s />Users can sign in, but "master" must approve it<br/>' %("CHECKED" if globales.user_sign_in_mode==1 else ""))
+  texto.append('<input type="radio" name="user_sign_in_mode" value="2" %s />Users can sign in freely</td></tr>' %("CHECKED" if globales.user_sign_in_mode==2 else ""))
+
   texto.append('<tr><td>OpenID Support<br/>(Requires library. See docs)</td><td><input type="checkbox" name="openid_support" %s /></td></tr>' % ("CHECKED" if globales.openid_support else ""))
   texto.append('<tr><td>Allow BBcode?</td><td><input type="checkbox" name="allow_bbcode" %s /></td></tr>' % ("CHECKED" if globales.allow_bbcode else ""))
   texto.append('<tr><td>Allow HTML?</td><td><input type="checkbox" name="allow_html" %s /></td></tr>' % ("CHECKED" if globales.allow_html else ""))

Copied: trunk/frontend-web/url_new_user_confirmation.py (from r362, /trunk/frontend-web/url_nuevo_usuario_POST.py)
==============================================================================
--- /trunk/frontend-web/url_nuevo_usuario_POST.py	(original)
+++ trunk/frontend-web/url_new_user_confirmation.py	Thu Sep 13 13:41:52 2007
@@ -1,82 +1,59 @@
 # $Id$
 
 from globales import monitor
+from globales import user_sign_in_mode
 
 @monitor
 def gestiona_url(conn,handler,path,usuario) :
-  if not usuario : # Acceso anonimo
-    import url_LOGIN
-    return url_LOGIN.gestiona_url(handler,path,usuario)
-
-  if len(path)!=1 : return None
+  if len(path)!=2 : return None
+  nonce=path[1]
 
   import skins
   pagina = skins.Skin(path,usuario)
   pagina.load_dict({"page_title": "cpif - Alta exitosa"})
   
-  if usuario!="master" :
-    pagina.load_url(["error"])
-    pagina.load_dict({"generic_message": "<h1>Solo el usuario master puede crear nuevos usuarios</h1>"})
-    return (pagina.web())
-
-  import cgi
-  ctype,pdict=cgi.parse_header(handler.headers.getheader('content-type'))
-  cuerpo=cgi.FieldStorage(fp=handler.rfile,headers=handler.headers,environ={'REQUEST_METHOD':'POST'},keep_blank_values=1)
-  nick=cuerpo.getfirst("usuario")
-  clave1=cuerpo.getfirst("clave1")
-  clave2=cuerpo.getfirst("clave2")
-  email=cuerpo.getfirst("email")
-  try :
-    nick=nick.strip()
-    clave1=clave1.strip()
-    clave2=clave2.strip()
-    email=email.strip()
-  except :
-    nick="" # Generate an error
-
-  if not (nick and clave1 and clave2 and email) : # Comprobamos el caso de campos inexistentes
-    pagina.load_url(["error"])
-    pagina.load_dict({"generic_message": "<h1>No puedes dejar ningun campo no opcional en blanco</h1>"})
-    return pagina.web()
-
-  OpenIDs=set()
-  for i in xrange(1,6) :
-    OpenID=cuerpo.getfirst("OpenID%d" %i)
-    if OpenID : # Comprobamos el caso de campos inexistentes
-      OpenID=OpenID.strip()
-      if OpenID :
-        if '"' in OpenID : # XSS
-          pagina.load_url(["error"])
-          pagina.load_dict({"generic_message": "<h1>Caracteres extra&ntilde;os en los servidores OpenID</h1>"})
-          return pagina.web()
-        OpenIDs.add(OpenID)
-
-  for i in ['"',"'",'<','>','&'] :
-    if i in nick :
-      pagina.load_url(["error"])
-      pagina.load_dict({"generic_message": "<h1>Caracteres extra&ntilde;os en el nombre de usuario</h1>"})
-      return pagina.web()
+  import database
+  v=database.get_usuario_pending(conn,nonce)
 
-  if clave1!=clave2 :
+  if not v :
     pagina.load_url(["error"])
-    pagina.load_dict({"generic_message": "<h1>Las claves introducidas no coinciden</h1>"})
+    pagina.load_dict({"generic_message": "<h1>URL incorrecta. Si ya ha confirmado su alta, no necesita volver a hacerlo.</h1>"})
     return (pagina.web())
 
-  for i in ['"',"'",'<','>','&',' ','\t'] : # Deberiamos poner realmente una lista de los permitidos
-    if i in email:
-      error="<h1>Caracteres extra&ntilde;os en la direccion</h1>"
-      pagina.load_url(["error"])
-      pagina.load_dict({"generic_message": "<h1>Caracteres extra&ntilde;os en la direcci&oacute;n de correo</h1>"})
-      return pagina.web()
+  nick,datos,clave,email,OpenIDs=v
 
-  import database
   if database.usuario_verifica(conn,nick=nick) :
     pagina.load_url(["error"])
     pagina.load_dict({"generic_message": "<h1>El usuario ya existe</h1>"})
     return (pagina.web())
 
-  database.usuario_add(conn,nick,None,clave=clave1,email=email,OpenIDs=OpenIDs)
+  database.usuario_add(conn,nick,None,clave=clave,email=email,OpenIDs=OpenIDs)
+  if user_sign_in_mode==1 :
+    database.set_usuario_no_login(conn,nick) # We need the master aproval
+
+    from globales import smtp_email_sender,base_url,admin_email_address
+    texto=["From: %s" %smtp_email_sender]
+    texto.append("To: %s" %admin_email_address)
+    texto.append("Subject: Confirmacion de alta de usuario")
+    texto.append("")
+    texto.append("Un usuario ha confirmado el alta, pero se requiere")
+    texto.append("tambien la confirmacion del administrador.")
+    texto.append("")
+    texto.append("Como administrador, puede ver el perfil del usuario")
+    texto.append("pulsando sobre el siguiente enlace:")
+    texto.append("")
+    texto.append("%s/perfil/%s" %(base_url,nick))
+
+    import smtp
+    smtp.enqueue(conn,[admin_email_address],"\r\n".join(texto))
+
+    pagina.load_dict({"generic_message": "<h1>Alta exitosa, pero se requiere la confirmacion del administrador. Recibir&aacute; un mensaje de correo electr&oacute;nico con la decisi&oacute;n.</h1>"})
+    return (pagina.web())
 
-  pagina.load_dict({"generic_message": "<h1>Alta exitosa</h1>"})
-  return (pagina.web())
+  resultado=database.usuario_verifica(conn,nick=nick,force_login=True)
+  assert resultado
+  nick,cookie=resultado
+  if cookie :
+    cookie="cpif_auth=%s; path=/;  expires=Wed, 01-Jan-2030 00:00:00 GMT;" %cookie
+  return (302,{"Set-Cookie":cookie,"Location":"/"},"")
 

Modified: trunk/frontend-web/url_nuevo_usuario.py
==============================================================================
--- trunk/frontend-web/url_nuevo_usuario.py	(original)
+++ trunk/frontend-web/url_nuevo_usuario.py	Thu Sep 13 13:41:52 2007
@@ -1,6 +1,7 @@
 # $Id$
 
 from globales import monitor
+from globales import user_sign_in_mode
 
 def gestiona_url(handler,path,usuario) :
   if len(path)!=1 : return None
@@ -8,7 +9,7 @@
   pagina = skins.Skin(path,usuario)
   pagina.load_dict({"page_title": "cpif - A&ntilde;adir nuevo usuario"})
 
-  if usuario!="master" :
+  if (usuario!="master") and (user_sign_in_mode==0) :
     pagina.load_url(["error"])
     pagina.load_dict({"generic_message": "<h1>Solo el usuario 'master' puede dar de alta nuevos usuarios</h1>"})
 

Modified: trunk/frontend-web/url_nuevo_usuario_POST.py
==============================================================================
--- trunk/frontend-web/url_nuevo_usuario_POST.py	(original)
+++ trunk/frontend-web/url_nuevo_usuario_POST.py	Thu Sep 13 13:41:52 2007
@@ -1,20 +1,17 @@
 # $Id$
 
 from globales import monitor
+from globales import user_sign_in_mode
 
 @monitor
 def gestiona_url(conn,handler,path,usuario) :
-  if not usuario : # Acceso anonimo
-    import url_LOGIN
-    return url_LOGIN.gestiona_url(handler,path,usuario)
-
   if len(path)!=1 : return None
 
   import skins
   pagina = skins.Skin(path,usuario)
   pagina.load_dict({"page_title": "cpif - Alta exitosa"})
   
-  if usuario!="master" :
+  if (usuario!="master") and (user_sign_in_mode==0) :
     pagina.load_url(["error"])
     pagina.load_dict({"generic_message": "<h1>Solo el usuario master puede crear nuevos usuarios</h1>"})
     return (pagina.web())
@@ -75,8 +72,26 @@
     pagina.load_dict({"generic_message": "<h1>El usuario ya existe</h1>"})
     return (pagina.web())
 
-  database.usuario_add(conn,nick,None,clave=clave1,email=email,OpenIDs=OpenIDs)
+  if usuario=="master" :
+    database.usuario_add(conn,nick,None,clave=clave1,email=email,OpenIDs=OpenIDs)
+    pagina.load_dict({"generic_message": "<h1>Alta exitosa</h1>"})
+  else :
+    nonce=database.usuario_add_pending(conn,nick,None,clave=clave1,email=email,OpenIDs=OpenIDs)
+
+    from globales import smtp_email_sender,base_url
+    texto=["From: %s" %smtp_email_sender]
+    texto.append("To: %s" %email)
+    texto.append("Subject: Confirmacion de alta de usuario")
+    texto.append("")
+    texto.append("Para confirmar tu usuario, pulsa sobre el")
+    texto.append("siguiente enlace:")
+    texto.append("")
+    texto.append("%s/new_user_confirmation/%s" %(base_url,nonce))
+
+    import smtp
+    smtp.enqueue(conn,[email],"\r\n".join(texto))
+
+    pagina.load_dict({"generic_message": "<h1>Se le ha enviado un mensaje de correo electr&oacute;nico. Siga sus instrucciones.</h1>"})
 
-  pagina.load_dict({"generic_message": "<h1>Alta exitosa</h1>"})
   return (pagina.web())
 

Modified: trunk/frontend-web/url_perfil.py
==============================================================================
--- trunk/frontend-web/url_perfil.py	(original)
+++ trunk/frontend-web/url_perfil.py	Thu Sep 13 13:41:52 2007
@@ -26,7 +26,7 @@
   if not u :
     return None
 
-  texto=["<tr><td>Usuario:</td><td>%s</td></tr>" %usuario_pedido]
+  texto=["<tr><td>Usuario:</td><td>%s%s</td></tr>" %(usuario_pedido, " <b><i>&iexcl;&iexcl;NO LOGIN!!</i></b>" if "no_login" in u else "")]
   texto.append('<tr><td>Clave nueva:</td><td><input type="password" name="clave1" size="25" value="" /></td></tr>')
   texto.append('<tr><td>Confirma clave nueva:</td><td><input type="password" name="clave2" size="25" value="" /></td></tr>')
   a="(Se ha solicitado un cambio de direcci&oacute;n de correo electr&oacute;nico, y est&aacute; pendiente de confirmaci&oacute;n)" if "new email" in u else ""



More information about the cpif mailing list