233 lines
6.6 KiB
Python
233 lines
6.6 KiB
Python
import re
|
|
import cherrypy
|
|
from model.User import User
|
|
from model.Notebook import Notebook
|
|
from model.Note import Note
|
|
from Scheduler import Scheduler
|
|
from Expose import expose
|
|
from Validate import validate, Valid_string, Validation_error
|
|
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
|
|
|
|
|
|
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
|
|
)
|
|
|
|
|
|
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 ):
|
|
def __init__( self, scheduler, database ):
|
|
self.__scheduler = scheduler
|
|
self.__database = database
|
|
|
|
@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 ):
|
|
if password != password_repeat:
|
|
raise Signup_error( u"The passwords you entered do not match. Please try again." )
|
|
|
|
self.__database.load( username, self.__scheduler.thread )
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
if user is not None:
|
|
raise Signup_error( u"Sorry, that username is not available. Please try something else." )
|
|
|
|
# create a notebook for this user
|
|
self.__database.next_id( self.__scheduler.thread )
|
|
notebook_id = ( yield Scheduler.SLEEP )
|
|
notebook = Notebook( notebook_id, u"my notebook" )
|
|
|
|
# create a startup note for this user's notebook
|
|
self.__database.next_id( self.__scheduler.thread )
|
|
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 )
|
|
|
|
# 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 )
|
|
|
|
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 ):
|
|
self.__database.load( username, self.__scheduler.thread )
|
|
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 ):
|
|
yield dict(
|
|
redirect = u"/",
|
|
deauthenticated = True,
|
|
)
|
|
|
|
@expose( view = Json )
|
|
@strongly_expire
|
|
@grab_user_id
|
|
@wait_for_update
|
|
@async
|
|
@update_client
|
|
@validate(
|
|
user_id = Valid_id( none_okay = True ),
|
|
)
|
|
def current( self, user_id ):
|
|
# if there's no logged-in user, default to the anonymous user
|
|
self.__database.load( user_id or u"anonymous", self.__scheduler.thread )
|
|
user = ( yield Scheduler.SLEEP )
|
|
|
|
if not user:
|
|
yield dict(
|
|
user = None,
|
|
notebooks = None,
|
|
)
|
|
return
|
|
|
|
# in addition to this user's own notebooks, add to that list the anonymous user's notebooks
|
|
if user_id:
|
|
self.__database.load( u"anonymous", self.__scheduler.thread )
|
|
anonymous = ( yield Scheduler.SLEEP )
|
|
notebooks = anonymous.notebooks
|
|
else:
|
|
notebooks = []
|
|
notebooks += user.notebooks
|
|
|
|
yield dict(
|
|
user = user,
|
|
notebooks = notebooks,
|
|
)
|
|
|
|
scheduler = property( lambda self: self.__scheduler )
|