2007-07-16 20:22:38 +00:00
|
|
|
import re
|
|
|
|
import cherrypy
|
2007-09-26 23:49:27 +00:00
|
|
|
from datetime import datetime, timedelta
|
2007-07-16 20:22:38 +00:00
|
|
|
from model.User import User
|
|
|
|
from model.Notebook import Notebook
|
2007-07-17 01:21:31 +00:00
|
|
|
from model.Note import Note
|
2007-09-26 23:49:27 +00:00
|
|
|
from model.Password_reset import Password_reset
|
2007-07-16 20:22:38 +00:00
|
|
|
from Scheduler import Scheduler
|
|
|
|
from Expose import expose
|
2007-08-27 22:37:22 +00:00
|
|
|
from Validate import validate, Valid_string, Valid_bool, Validation_error
|
2007-07-16 20:22:38 +00:00
|
|
|
from Database import Valid_id
|
|
|
|
from Updater import update_client, wait_for_update
|
|
|
|
from Expire import strongly_expire
|
|
|
|
from Async import async
|
|
|
|
from view.Json import Json
|
2007-09-26 23:49:27 +00:00
|
|
|
from view.Main_page import Main_page
|
|
|
|
from view.Redeem_reset_note import Redeem_reset_note
|
2007-07-16 20:22:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
USERNAME_PATTERN = re.compile( "^[a-zA-Z0-9]+$" )
|
|
|
|
EMAIL_ADDRESS_PATTERN = re.compile( "^[\w.+]+@\w+(\.\w+)+$" )
|
|
|
|
|
|
|
|
|
|
|
|
def valid_username( username ):
|
|
|
|
if USERNAME_PATTERN.search( username ) is None:
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
return username
|
|
|
|
|
|
|
|
valid_username.message = u"can only contain letters and digits"
|
|
|
|
|
|
|
|
|
|
|
|
def valid_email_address( email_address ):
|
|
|
|
if email_address == "" or EMAIL_ADDRESS_PATTERN.search( email_address ) is None:
|
|
|
|
raise ValueError()
|
|
|
|
|
|
|
|
return email_address
|
|
|
|
|
|
|
|
|
|
|
|
class Signup_error( Exception ):
|
|
|
|
def __init__( self, message ):
|
|
|
|
Exception.__init__( self, message )
|
|
|
|
self.__message = message
|
|
|
|
|
|
|
|
def to_dict( self ):
|
|
|
|
return dict(
|
|
|
|
error = self.__message
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class Authentication_error( Exception ):
|
|
|
|
def __init__( self, message ):
|
|
|
|
Exception.__init__( self, message )
|
|
|
|
self.__message = message
|
|
|
|
|
|
|
|
def to_dict( self ):
|
|
|
|
return dict(
|
|
|
|
error = self.__message
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2007-09-26 23:49:27 +00:00
|
|
|
class Password_reset_error( Exception ):
|
|
|
|
def __init__( self, message ):
|
|
|
|
Exception.__init__( self, message )
|
|
|
|
self.__message = message
|
|
|
|
|
|
|
|
def to_dict( self ):
|
|
|
|
return dict(
|
|
|
|
error = self.__message
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2007-07-16 20:22:38 +00:00
|
|
|
def grab_user_id( function ):
|
|
|
|
"""
|
|
|
|
A decorator to grab the current logged in user id from the cherrypy session and pass it as a
|
|
|
|
user_id argument to the decorated function. This decorator must be used from within the main
|
|
|
|
cherrypy request thread.
|
|
|
|
"""
|
|
|
|
def get_id( *args, **kwargs ):
|
|
|
|
arg_names = list( function.func_code.co_varnames )
|
|
|
|
if "user_id" in arg_names:
|
|
|
|
arg_index = arg_names.index( "user_id" )
|
|
|
|
args[ arg_index ] = cherrypy.session.get( "user_id" )
|
|
|
|
else:
|
|
|
|
kwargs[ "user_id" ] = cherrypy.session.get( "user_id" )
|
|
|
|
|
|
|
|
return function( *args, **kwargs )
|
|
|
|
|
|
|
|
return get_id
|
|
|
|
|
|
|
|
|
|
|
|
def update_auth( function ):
|
|
|
|
"""
|
|
|
|
Based on the return value of the decorated function, update the current session's authentication
|
|
|
|
status. This decorator must be used from within the main cherrypy request thread.
|
|
|
|
|
|
|
|
If the return value of the decorated function (which is expected to be a dictionary) contains an
|
|
|
|
"authenticated" key with a User value, then mark the user as logged in. If the return value of the
|
|
|
|
decorated function contains a "deauthenticated" key with any value, then mark the user as logged
|
|
|
|
out.
|
|
|
|
"""
|
|
|
|
def handle_result( *args, **kwargs ):
|
|
|
|
result = function( *args, **kwargs )
|
|
|
|
|
|
|
|
# peek in the function's return value to see if we should tweak authentication status
|
|
|
|
user = result.get( "authenticated" )
|
|
|
|
if user:
|
|
|
|
cherrypy.session[ u"user_id" ] = user.object_id
|
|
|
|
cherrypy.session[ u"username" ] = user.username
|
|
|
|
|
|
|
|
if result.get( "deauthenticated" ):
|
|
|
|
cherrypy.session.pop( u"user_id", None )
|
|
|
|
cherrypy.session.pop( u"username", None )
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
return handle_result
|
|
|
|
|
|
|
|
|
|
|
|
class Users( object ):
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
|
|
|
Controller for dealing with users, corresponding to the "/users" URL.
|
|
|
|
"""
|
2007-09-26 23:49:27 +00:00
|
|
|
def __init__( self, scheduler, database, http_url, https_url, support_email, rate_plans ):
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
|
|
|
Create a new Users object.
|
|
|
|
|
|
|
|
@type scheduler: controller.Scheduler
|
|
|
|
@param scheduler: scheduler to use for asynchronous calls
|
|
|
|
@type database: controller.Database
|
|
|
|
@param database: database that users are stored in
|
|
|
|
@type http_url: unicode
|
|
|
|
@param http_url: base URL to use for non-SSL http requests, or an empty string
|
2007-08-30 23:57:56 +00:00
|
|
|
@type https_url: unicode
|
|
|
|
@param https_url: base URL to use for SSL http requests, or an empty string
|
2007-09-26 23:49:27 +00:00
|
|
|
@type support_email: unicode
|
|
|
|
@param support_email: email address for support requests
|
2007-09-20 20:36:19 +00:00
|
|
|
@type rate_plans: [ { "name": unicode, "storage_quota_bytes": int } ]
|
|
|
|
@param rate_plans: list of configured rate plans
|
2007-08-09 19:44:26 +00:00
|
|
|
@rtype: Users
|
|
|
|
@return: newly constructed Users
|
|
|
|
"""
|
2007-07-16 20:22:38 +00:00
|
|
|
self.__scheduler = scheduler
|
|
|
|
self.__database = database
|
2007-08-02 18:52:20 +00:00
|
|
|
self.__http_url = http_url
|
2007-08-30 23:57:56 +00:00
|
|
|
self.__https_url = https_url
|
2007-09-26 23:49:27 +00:00
|
|
|
self.__support_email = support_email
|
2007-09-20 20:36:19 +00:00
|
|
|
self.__rate_plans = rate_plans
|
2007-07-16 20:22:38 +00:00
|
|
|
|
|
|
|
@expose( view = Json )
|
|
|
|
@update_auth
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
@validate(
|
|
|
|
username = ( Valid_string( min = 1, max = 30 ), valid_username ),
|
|
|
|
password = Valid_string( min = 1, max = 30 ),
|
|
|
|
password_repeat = Valid_string( min = 1, max = 30 ),
|
|
|
|
email_address = ( Valid_string( min = 1, max = 60 ), valid_email_address ),
|
|
|
|
signup_button = unicode,
|
|
|
|
)
|
|
|
|
def signup( self, username, password, password_repeat, email_address, signup_button ):
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
|
|
|
Create a new User based on the given information. Start that user with their own Notebook and a
|
|
|
|
"welcome to your wiki" Note. For convenience, login the newly created user as well.
|
|
|
|
|
|
|
|
@type username: unicode (alphanumeric only)
|
|
|
|
@param username: username to use for this new user
|
|
|
|
@type password: unicode
|
|
|
|
@param password: password to use
|
|
|
|
@type password_repeat: unicode
|
|
|
|
@param password_repeat: password to use, again
|
|
|
|
@type email_address: unicode
|
|
|
|
@param email_address: user's email address
|
|
|
|
@type signup_button: unicode
|
|
|
|
@param signup_button: ignored
|
|
|
|
@rtype: json dict
|
|
|
|
@return: { 'redirect': url, 'authenticated': userdict }
|
|
|
|
@raise Signup_error: passwords don't match or the username is unavailable
|
|
|
|
@raise Validation_error: one of the arguments is invalid
|
|
|
|
"""
|
2007-07-16 20:22:38 +00:00
|
|
|
if password != password_repeat:
|
|
|
|
raise Signup_error( u"The passwords you entered do not match. Please try again." )
|
|
|
|
|
2007-07-20 20:05:02 +00:00
|
|
|
self.__database.load( "User %s" % username, self.__scheduler.thread )
|
2007-07-16 20:22:38 +00:00
|
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if user is not None:
|
|
|
|
raise Signup_error( u"Sorry, that username is not available. Please try something else." )
|
|
|
|
|
2007-08-03 21:12:17 +00:00
|
|
|
# create a notebook for this user, along with a trash for that notebook
|
|
|
|
self.__database.next_id( self.__scheduler.thread )
|
|
|
|
trash_id = ( yield Scheduler.SLEEP )
|
|
|
|
trash = Notebook( trash_id, u"trash" )
|
|
|
|
|
2007-07-16 20:22:38 +00:00
|
|
|
self.__database.next_id( self.__scheduler.thread )
|
|
|
|
notebook_id = ( yield Scheduler.SLEEP )
|
2007-08-03 21:12:17 +00:00
|
|
|
notebook = Notebook( notebook_id, u"my notebook", trash )
|
2007-07-16 20:22:38 +00:00
|
|
|
|
2007-07-17 01:21:31 +00:00
|
|
|
# create a startup note for this user's notebook
|
2007-07-16 20:22:38 +00:00
|
|
|
self.__database.next_id( self.__scheduler.thread )
|
2007-07-17 01:21:31 +00:00
|
|
|
note_id = ( yield Scheduler.SLEEP )
|
|
|
|
note = Note( note_id, file( u"static/html/welcome to your wiki.html" ).read() )
|
|
|
|
notebook.add_note( note )
|
|
|
|
notebook.add_startup_note( note )
|
2007-07-16 20:22:38 +00:00
|
|
|
|
|
|
|
# actually create the new user
|
|
|
|
self.__database.next_id( self.__scheduler.thread )
|
|
|
|
user_id = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
user = User( user_id, username, password, email_address, notebooks = [ notebook ] )
|
|
|
|
self.__database.save( user )
|
|
|
|
|
2007-09-26 23:49:27 +00:00
|
|
|
# add the new user to the user list
|
|
|
|
self.__database.load( u"User_list all", self.scheduler.thread )
|
|
|
|
user_list = ( yield Scheduler.SLEEP )
|
|
|
|
if user_list:
|
|
|
|
user_list.add_user( user )
|
|
|
|
self.__database.save( user_list )
|
|
|
|
|
2007-07-16 20:22:38 +00:00
|
|
|
redirect = u"/notebooks/%s" % notebook.object_id
|
|
|
|
|
|
|
|
yield dict(
|
|
|
|
redirect = redirect,
|
|
|
|
authenticated = user,
|
|
|
|
)
|
|
|
|
|
|
|
|
@expose( view = Json )
|
|
|
|
@update_auth
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
@validate(
|
|
|
|
username = ( Valid_string( min = 1, max = 30 ), valid_username ),
|
|
|
|
password = Valid_string( min = 1, max = 30 ),
|
|
|
|
login_button = unicode,
|
|
|
|
)
|
|
|
|
def login( self, username, password, login_button ):
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
|
|
|
Attempt to authenticate the user. If successful, associate the given user with the current
|
|
|
|
session.
|
|
|
|
|
|
|
|
@type username: unicode (alphanumeric only)
|
|
|
|
@param username: username to login
|
|
|
|
@type password: unicode
|
|
|
|
@param password: the user's password
|
|
|
|
@rtype: json dict
|
|
|
|
@return: { 'redirect': url, 'authenticated': userdict }
|
|
|
|
@raise Authentication_error: invalid username or password
|
|
|
|
@raise Validation_error: one of the arguments is invalid
|
|
|
|
"""
|
2007-07-20 20:05:02 +00:00
|
|
|
self.__database.load( "User %s" % username, self.__scheduler.thread )
|
2007-07-16 20:22:38 +00:00
|
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if user is None or user.check_password( password ) is False:
|
|
|
|
raise Authentication_error( u"Invalid username or password." )
|
|
|
|
|
|
|
|
# redirect to the user's first notebook (if any)
|
|
|
|
if len( user.notebooks ) > 0:
|
|
|
|
redirect = u"/notebooks/%s" % user.notebooks[ 0 ].object_id
|
|
|
|
else:
|
|
|
|
redirect = u"/"
|
|
|
|
|
|
|
|
yield dict(
|
|
|
|
redirect = redirect,
|
|
|
|
authenticated = user,
|
|
|
|
)
|
|
|
|
|
|
|
|
@expose( view = Json )
|
|
|
|
@update_auth
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
def logout( self ):
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
|
|
|
Deauthenticate the user and log them out of their current session.
|
|
|
|
|
|
|
|
@rtype: json dict
|
|
|
|
@return: { 'redirect': url, 'deauthenticated': True }
|
|
|
|
"""
|
2007-07-16 20:22:38 +00:00
|
|
|
yield dict(
|
2007-08-02 18:57:53 +00:00
|
|
|
redirect = self.__http_url + u"/",
|
2007-07-16 20:22:38 +00:00
|
|
|
deauthenticated = True,
|
|
|
|
)
|
|
|
|
|
|
|
|
@expose( view = Json )
|
|
|
|
@strongly_expire
|
|
|
|
@grab_user_id
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
@validate(
|
2007-08-27 22:37:22 +00:00
|
|
|
include_startup_notes = Valid_bool(),
|
2007-07-16 20:22:38 +00:00
|
|
|
user_id = Valid_id( none_okay = True ),
|
|
|
|
)
|
2007-08-27 22:37:22 +00:00
|
|
|
def current( self, include_startup_notes, user_id ):
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
|
|
|
Return information on the currently logged-in user. If not logged in, default to the anonymous
|
|
|
|
user.
|
|
|
|
|
2007-08-27 22:37:22 +00:00
|
|
|
@type include_startup_notes: bool
|
|
|
|
@param include_startup_notes: True to return startup notes for the first notebook
|
2007-08-09 19:44:26 +00:00
|
|
|
@type user_id: unicode
|
|
|
|
@param user_id: id of current logged-in user (if any), determined by @grab_user_id
|
|
|
|
@rtype: json dict
|
2007-09-20 20:36:19 +00:00
|
|
|
@return: {
|
|
|
|
'user': userdict or None,
|
|
|
|
'notebooks': notebooksdict,
|
|
|
|
'startup_notes': noteslist,
|
|
|
|
'http_url': url,
|
|
|
|
'login_url': url,
|
|
|
|
'rate_plan': rateplandict,
|
|
|
|
}
|
2007-09-26 23:49:27 +00:00
|
|
|
@raise Validation_error: one of the arguments is invalid
|
2007-08-09 19:44:26 +00:00
|
|
|
"""
|
2007-07-16 20:22:38 +00:00
|
|
|
# if there's no logged-in user, default to the anonymous user
|
2007-07-20 20:05:02 +00:00
|
|
|
self.__database.load( user_id or u"User anonymous", self.__scheduler.thread )
|
2007-07-16 20:22:38 +00:00
|
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not user:
|
|
|
|
yield dict(
|
|
|
|
user = None,
|
|
|
|
notebooks = None,
|
2007-08-09 19:44:26 +00:00
|
|
|
http_url = u"",
|
2007-07-16 20:22:38 +00:00
|
|
|
)
|
|
|
|
return
|
|
|
|
|
2007-08-30 19:08:37 +00:00
|
|
|
# in addition to this user's own notebooks, add to that list the anonymous user's notebooks
|
2007-08-30 23:57:56 +00:00
|
|
|
self.__database.load( u"User anonymous", self.__scheduler.thread )
|
|
|
|
anonymous = ( yield Scheduler.SLEEP )
|
|
|
|
login_url = None
|
|
|
|
|
2007-08-30 19:08:37 +00:00
|
|
|
if user_id:
|
|
|
|
notebooks = anonymous.notebooks
|
|
|
|
else:
|
|
|
|
notebooks = []
|
2007-08-30 23:57:56 +00:00
|
|
|
if len( anonymous.notebooks ) > 0:
|
|
|
|
anon_notebook = anonymous.notebooks[ 0 ]
|
|
|
|
login_note = anon_notebook.lookup_note_by_title( u"login" )
|
|
|
|
if login_note:
|
|
|
|
login_url = "%s/notebooks/%s?note_id=%s" % ( self.__https_url, anon_notebook.object_id, login_note.object_id )
|
|
|
|
|
2007-08-30 19:08:37 +00:00
|
|
|
notebooks += user.notebooks
|
|
|
|
|
2007-07-16 20:22:38 +00:00
|
|
|
yield dict(
|
|
|
|
user = user,
|
2007-08-30 19:08:37 +00:00
|
|
|
notebooks = notebooks,
|
|
|
|
startup_notes = include_startup_notes and len( notebooks ) > 0 and notebooks[ 0 ].startup_notes or [],
|
2007-08-02 18:52:20 +00:00
|
|
|
http_url = self.__http_url,
|
2007-08-30 23:57:56 +00:00
|
|
|
login_url = login_url,
|
2007-09-20 20:36:19 +00:00
|
|
|
rate_plan = ( user.rate_plan < len( self.__rate_plans ) ) and self.__rate_plans[ user.rate_plan ] or {},
|
2007-07-16 20:22:38 +00:00
|
|
|
)
|
|
|
|
|
2007-09-12 22:42:46 +00:00
|
|
|
def calculate_storage( self, user ):
|
|
|
|
"""
|
|
|
|
Calculate total storage utilization for all notebooks and all notes of the given user,
|
|
|
|
including storage for all past revisions.
|
|
|
|
@type user: User
|
|
|
|
@param user: user for which to calculate storage utilization
|
|
|
|
@rtype: int
|
|
|
|
@return: total bytes used for storage
|
|
|
|
"""
|
|
|
|
total_bytes = 0
|
|
|
|
|
|
|
|
def sum_revisions( obj ):
|
|
|
|
return \
|
|
|
|
self.__database.size( obj.object_id ) + \
|
|
|
|
sum( [ self.__database.size( obj.object_id, revision ) or 0 for revision in obj.revisions_list ], 0 )
|
|
|
|
|
|
|
|
def sum_notebook( notebook ):
|
|
|
|
return \
|
2007-09-21 19:15:02 +00:00
|
|
|
self.__database.size( notebook.object_id ) + \
|
2007-09-12 22:42:46 +00:00
|
|
|
sum( [ sum_revisions( note ) for note in notebook.notes ], 0 )
|
|
|
|
|
|
|
|
for notebook in user.notebooks:
|
|
|
|
total_bytes += sum_notebook( notebook )
|
|
|
|
|
|
|
|
if notebook.trash:
|
|
|
|
total_bytes += sum_notebook( notebook.trash )
|
|
|
|
|
|
|
|
return total_bytes
|
|
|
|
|
2007-09-13 00:53:49 +00:00
|
|
|
@async
|
2007-09-20 20:36:19 +00:00
|
|
|
def update_storage( self, user_id, callback = None ):
|
2007-09-13 00:53:49 +00:00
|
|
|
"""
|
|
|
|
Calculate and record total storage utilization for the given user.
|
2007-09-20 20:36:19 +00:00
|
|
|
@type user_id: unicode or NoneType
|
|
|
|
@param user_id: id of user for which to calculate storage utilization
|
|
|
|
@type callback: generator or NoneType
|
|
|
|
@param callback: generator to wakeup when the update is complete (optional)
|
2007-09-13 00:53:49 +00:00
|
|
|
"""
|
2007-09-20 20:36:19 +00:00
|
|
|
self.__database.load( user_id, self.__scheduler.thread )
|
|
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if user:
|
|
|
|
user.storage_bytes = self.calculate_storage( user )
|
|
|
|
|
|
|
|
yield callback, user
|
2007-09-13 00:53:49 +00:00
|
|
|
|
2007-09-26 23:49:27 +00:00
|
|
|
@expose( view = Json )
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
@validate(
|
|
|
|
email_address = ( Valid_string( min = 1, max = 60 ), valid_email_address ),
|
|
|
|
send_reset_button = unicode,
|
|
|
|
)
|
|
|
|
def send_reset( self, email_address, send_reset_button ):
|
|
|
|
"""
|
|
|
|
Send a password reset email to the given email address.
|
|
|
|
@type email_address: unicode
|
|
|
|
@param email_address: an existing user's email address
|
|
|
|
@type send_reset_button: unicode
|
|
|
|
@param send_reset_button: ignored
|
|
|
|
@rtype: json dict
|
|
|
|
@return: { 'error': message }
|
|
|
|
@raise Password_reset_error: an error occured when sending the password reset email
|
|
|
|
@raise Validation_error: one of the arguments is invalid
|
|
|
|
"""
|
|
|
|
import sha
|
|
|
|
import random
|
|
|
|
import smtplib
|
|
|
|
from email import Message
|
|
|
|
|
|
|
|
# check whether there are actually any users with the given email address
|
|
|
|
self.__database.load( u"User_list all", self.scheduler.thread )
|
|
|
|
user_list = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not user_list:
|
|
|
|
raise Password_reset_error( "There was an error when sending your password reset email. Please contact %s." % self.__support_email )
|
|
|
|
|
|
|
|
users = [ user for user in user_list.users if user.email_address == email_address ]
|
|
|
|
if len( users ) == 0:
|
|
|
|
raise Password_reset_error( u"There are no Luminotes users with the email address %s" % email_address )
|
|
|
|
|
|
|
|
# record the sending of this reset email
|
|
|
|
self.__database.next_id( self.__scheduler.thread )
|
|
|
|
password_reset_id = ( yield Scheduler.SLEEP )
|
|
|
|
password_reset = Password_reset( password_reset_id, email_address )
|
|
|
|
self.__database.save( password_reset )
|
|
|
|
|
|
|
|
# create an email message with a unique link
|
|
|
|
message = Message.Message()
|
|
|
|
message[ u"from" ] = u"Luminotes support <%s>" % self.__support_email
|
|
|
|
message[ u"to" ] = email_address
|
|
|
|
message[ u"subject" ] = u"Luminotes password reset"
|
|
|
|
message.set_payload(
|
|
|
|
u"Someone has requested a password reset for a Luminotes user with your email\n" +
|
|
|
|
u"address. If this someone is you, please visit the following link for a\n" +
|
|
|
|
u"username reminder or a password reset:\n\n" +
|
|
|
|
u"%s/%s\n\n" % ( self.__https_url or self.__http_url, password_reset.object_id ) +
|
|
|
|
u"This link will expire in 24 hours.\n\n" +
|
|
|
|
u"Thanks!"
|
|
|
|
)
|
|
|
|
|
|
|
|
# send the message out through localhost's smtp server
|
|
|
|
server = smtplib.SMTP()
|
|
|
|
server.connect()
|
|
|
|
server.sendmail( message[ u"from" ], [ email_address ], message.as_string() )
|
|
|
|
server.quit()
|
|
|
|
|
|
|
|
yield dict(
|
2007-09-27 22:40:18 +00:00
|
|
|
message = u"Please check your inbox. A password reset email has been sent to %s" % email_address,
|
2007-09-26 23:49:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
@expose( view = Main_page )
|
|
|
|
@strongly_expire
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
@validate(
|
|
|
|
password_reset_id = Valid_id(),
|
|
|
|
)
|
|
|
|
def redeem_reset( self, password_reset_id ):
|
|
|
|
"""
|
|
|
|
Provide the information necessary to display the web site's main page along with a dynamically
|
|
|
|
generated "complete your password reset" note.
|
|
|
|
@type password_reset_id: unicode
|
|
|
|
@param password_reset_id: id of model.Password_reset to redeem
|
|
|
|
@rtype: unicode
|
|
|
|
@return: rendered HTML page
|
|
|
|
@raise Password_reset_error: an error occured when redeeming the password reset, such as an expired link
|
|
|
|
@raise Validation_error: one of the arguments is invalid
|
|
|
|
"""
|
|
|
|
self.__database.load( u"User anonymous", self.__scheduler.thread )
|
|
|
|
anonymous = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not anonymous or len( anonymous.notebooks ) == 0:
|
|
|
|
raise Password_reset_error( "There was an error when completing your password reset. Please contact %s." % self.__support_email )
|
|
|
|
|
|
|
|
self.__database.load( password_reset_id, self.__scheduler.thread )
|
|
|
|
password_reset = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not password_reset or datetime.now() - password_reset.revision > timedelta( hours = 25 ):
|
|
|
|
raise Password_reset_error( "Your password reset link has expired. Please request a new password reset email." )
|
|
|
|
|
|
|
|
if password_reset.redeemed:
|
|
|
|
raise Password_reset_error( "Your password has already been reset. Please request a new password reset email." )
|
|
|
|
|
|
|
|
self.__database.load( u"User_list all", self.__scheduler.thread )
|
|
|
|
user_list = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not user_list:
|
|
|
|
raise Password_reset_error( u"There are no Luminotes users with the email address %s" % password_reset.email_address )
|
|
|
|
|
|
|
|
# find the user(s) with the email address from the password reset request
|
|
|
|
matching_users = [ user for user in user_list.users if user.email_address == password_reset.email_address ]
|
|
|
|
|
|
|
|
if len( matching_users ) == 0:
|
|
|
|
raise Password_reset_error( u"There are no Luminotes users with the email address %s" % password_reset.email_address )
|
|
|
|
|
|
|
|
yield dict(
|
|
|
|
notebook_id = anonymous.notebooks[ 0 ].object_id,
|
|
|
|
note_id = u"blank",
|
|
|
|
note_contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ),
|
|
|
|
)
|
|
|
|
|
|
|
|
@expose( view = Json )
|
|
|
|
@wait_for_update
|
|
|
|
@async
|
|
|
|
@update_client
|
|
|
|
def reset_password( self, password_reset_id, reset_button, **new_passwords ):
|
|
|
|
"""
|
|
|
|
Reset all the users with the provided passwords.
|
|
|
|
@type password_reset_id: unicode
|
|
|
|
@param password_reset_id: id of model.Password_reset to use
|
|
|
|
@type reset_button: unicode
|
|
|
|
@param reset_button: return
|
|
|
|
@type new_passwords: { userid: [ newpassword, newpasswordrepeat ] }
|
|
|
|
@param new_passwords: map of user id to new passwords or empty strings
|
|
|
|
@rtype: json dict
|
|
|
|
@return: { 'redirect': '/' }
|
|
|
|
@raise Password_reset_error: an error occured when resetting the passwords, such as an expired link
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
id_validator = Valid_id()
|
|
|
|
id_validator( password_reset_id )
|
|
|
|
except ValueError:
|
|
|
|
raise Validation_error( "password_reset_id", password_reset_id, id_validator, "is not a valid id" )
|
|
|
|
|
|
|
|
self.__database.load( password_reset_id, self.__scheduler.thread )
|
|
|
|
password_reset = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not password_reset or datetime.now() - password_reset.revision > timedelta( hours = 25 ):
|
|
|
|
raise Password_reset_error( "Your password reset link has expired. Please request a new password reset email." )
|
|
|
|
|
|
|
|
if password_reset.redeemed:
|
|
|
|
raise Password_reset_error( "Your password has already been reset. Please request a new password reset email." )
|
|
|
|
|
|
|
|
self.__database.load( u"User_list all", self.__scheduler.thread )
|
|
|
|
user_list = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not user_list:
|
|
|
|
raise Password_reset_error( "There was an error when resetting your password. Please contact %s." % self.__support_email )
|
|
|
|
|
|
|
|
# find the user(s) with the email address from the password reset request
|
|
|
|
matching_users = [ user for user in user_list.users if user.email_address == password_reset.email_address ]
|
|
|
|
allowed_user_ids = [ user.object_id for user in matching_users ]
|
|
|
|
|
|
|
|
# reset any passwords that are non-blank
|
|
|
|
users_to_reset = []
|
|
|
|
for ( user_id, ( new_password, new_password_repeat ) ) in new_passwords.items():
|
|
|
|
if user_id not in allowed_user_ids:
|
|
|
|
raise Password_reset_error( "There was an error when resetting your password. Please contact %s." % self.__support_email )
|
|
|
|
|
|
|
|
# skip blank passwords
|
|
|
|
if new_password == u"" and new_password_repeat == u"":
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.__database.load( user_id, self.__scheduler.thread )
|
|
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
|
|
|
|
if not user:
|
|
|
|
raise Password_reset_error( "There was an error when resetting your password. Please contact %s." % self.__support_email )
|
|
|
|
|
|
|
|
# ensure the passwords match
|
|
|
|
if new_password != new_password_repeat:
|
|
|
|
raise Password_reset_error( u"The new passwords you entered for user %s do not match. Please try again." % user.username )
|
|
|
|
|
|
|
|
# ensure the new password isn't too long
|
|
|
|
if len( new_password ) > 30:
|
|
|
|
raise Password_reset_error( u"Your password can be no longer than 30 characters." )
|
|
|
|
|
|
|
|
users_to_reset.append( ( user, new_password ) )
|
|
|
|
|
|
|
|
for ( user, new_password ) in users_to_reset:
|
|
|
|
user.password = new_password
|
|
|
|
self.__database.save( user )
|
|
|
|
|
|
|
|
# if all the new passwords provided are blank, bail
|
|
|
|
if not users_to_reset:
|
|
|
|
raise Password_reset_error( u"Please enter a new password. Or, if you already know your password, just click the login link above." )
|
|
|
|
|
|
|
|
password_reset.redeemed = True
|
|
|
|
self.__database.save( password_reset )
|
|
|
|
|
|
|
|
yield dict( redirect = u"/" )
|
|
|
|
|
2007-07-16 20:22:38 +00:00
|
|
|
scheduler = property( lambda self: self.__scheduler )
|