diff --git a/controller/Html_cleaner.py b/controller/Html_cleaner.py
index 651bfe9..bb5f4b1 100644
--- a/controller/Html_cleaner.py
+++ b/controller/Html_cleaner.py
@@ -7,10 +7,12 @@ from formatter import AbstractFormatter, NullWriter
from htmlentitydefs import entitydefs
from xml.sax.saxutils import quoteattr
+
def xssescape(text):
- """Gets rid of < and > and & and, for good measure, :"""
+ """Gets rid of < and > and & and, for good measure"""
return escape(text, quote=True)
+
class Html_cleaner(HTMLParser):
"""
Cleans HTML of any tags not matching a whitelist.
diff --git a/controller/Notebooks.py b/controller/Notebooks.py
index 9ac1669..0230d08 100644
--- a/controller/Notebooks.py
+++ b/controller/Notebooks.py
@@ -103,6 +103,7 @@ class Notebooks( object ):
@rtype: json dict
@return: { 'notebook': notebookdict, 'note': notedict or None }
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -152,6 +153,7 @@ class Notebooks( object ):
@rtype: json dict
@return: { 'note': notedict or None }
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -197,6 +199,7 @@ class Notebooks( object ):
@rtype: json dict
@return: { 'note': notedict or None }
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -246,6 +249,7 @@ class Notebooks( object ):
@rtype: json dict
@return: { 'new_revision': new revision of saved note, or None }
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -305,6 +309,7 @@ class Notebooks( object ):
@rtype: json dict
@return: {}
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -349,6 +354,7 @@ class Notebooks( object ):
@rtype: json dict
@return: {}
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -393,6 +399,7 @@ class Notebooks( object ):
@rtype: json dict
@return: {}
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -443,6 +450,7 @@ class Notebooks( object ):
@rtype: json dict
@return: {}
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -481,6 +489,7 @@ class Notebooks( object ):
@type id: id of the note
@rtype: unicode
@return: rendered HTML page
+ @raise Validation_error: the argument is invalid
"""
return dict( id = id )
@@ -510,6 +519,7 @@ class Notebooks( object ):
@rtype: json dict
@return: { 'notes': [ matching notes ] }
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -561,6 +571,7 @@ class Notebooks( object ):
@rtype: json dict
@return: { 'notes': [ recent notes ] }
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
@@ -601,6 +612,7 @@ class Notebooks( object ):
@rtype: unicode
@return: rendered HTML page
@raise Access_error: the current user doesn't have access to the given notebook
+ @raise Validation_error: one of the arguments is invalid
"""
self.check_access( notebook_id, user_id, self.__scheduler.thread )
if not ( yield Scheduler.SLEEP ):
diff --git a/controller/Scheduler.py b/controller/Scheduler.py
index ddeb50b..637ca62 100644
--- a/controller/Scheduler.py
+++ b/controller/Scheduler.py
@@ -84,9 +84,14 @@ class Scheduler( object ):
self.__idle.release()
yield None
- # used for unit tests
IDLE_SLEEP_SECONDS = 0.01
def wait_for( self, thread ):
+ """
+ Block until the given thread exits. Intended for use in unit tests only.
+
+ @type thread: generator
+ @param thread: thread to wait for
+ """
while thread in self.__running or thread in self.__sleeping:
sleep( self.IDLE_SLEEP_SECONDS )
@@ -94,15 +99,32 @@ class Scheduler( object ):
raise self.__last_error
def wait_until_idle( self ):
+ """
+ Block until all threads have exited. Intended for use in unit tests only.
+ """
while len( self.__running ) > 1 or len( self.__sleeping ) > 0:
sleep( self.IDLE_SLEEP_SECONDS )
def sleep( self, thread ):
+ """
+ Put the given thread to sleep so that is is no longer actively running.
+
+ @type thread: generator
+ @param thread: thread to put to sleep
+ """
self.__idle.acquire( blocking = False )
self.__sleeping.append( thread )
self.__running.remove( thread )
def add( self, thread, *args ):
+ """
+ Add the given thread to the running list for this Scheduler, and wake it up if it's asleep.
+
+ @type thread: generator
+ @param thread: thread to add
+ @type args: tuple
+ @param args: arguments to send() to the given thread when it is executed
+ """
self.__idle.release()
if thread in self.__sleeping:
@@ -116,6 +138,9 @@ class Scheduler( object ):
self.__messages[ thread ].append( args )
def shutdown( self ):
+ """
+ Stop all running threads and shutdown the Scheduler.
+ """
self.__done = True
self.__idle.release()
self.__scheduler_thread.join()
diff --git a/controller/Updater.py b/controller/Updater.py
index 51bfe84..4c08276 100644
--- a/controller/Updater.py
+++ b/controller/Updater.py
@@ -39,7 +39,8 @@ def update_client( function ):
Note that this decorator itself is a generator function and works by passing along next()/send()
calls to its decorated generator. Only yielded values that are dictionaries are sent to the
- client. All other yielded values are in turn yielded by this decorator itself.
+ client via the provided queue. All other types of yielded values are in turn yielded by this
+ decorator itself.
"""
def put_message( *args, **kwargs ):
# look in the called function's kwargs for the queue where results should be sent
diff --git a/controller/Users.py b/controller/Users.py
index 34926e3..c2e459e 100644
--- a/controller/Users.py
+++ b/controller/Users.py
@@ -103,7 +103,22 @@ def update_auth( function ):
class Users( object ):
+ """
+ Controller for dealing with users, corresponding to the "/users" URL.
+ """
def __init__( self, scheduler, database, http_url ):
+ """
+ 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
+ @rtype: Users
+ @return: newly constructed Users
+ """
self.__scheduler = scheduler
self.__database = database
self.__http_url = http_url
@@ -121,6 +136,25 @@ class Users( object ):
signup_button = unicode,
)
def signup( self, username, password, password_repeat, email_address, signup_button ):
+ """
+ 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
+ """
if password != password_repeat:
raise Signup_error( u"The passwords you entered do not match. Please try again." )
@@ -171,6 +205,19 @@ class Users( object ):
login_button = unicode,
)
def login( self, username, password, login_button ):
+ """
+ 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
+ """
self.__database.load( "User %s" % username, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
@@ -194,6 +241,12 @@ class Users( object ):
@async
@update_client
def logout( self ):
+ """
+ Deauthenticate the user and log them out of their current session.
+
+ @rtype: json dict
+ @return: { 'redirect': url, 'deauthenticated': True }
+ """
yield dict(
redirect = self.__http_url + u"/",
deauthenticated = True,
@@ -209,6 +262,15 @@ class Users( object ):
user_id = Valid_id( none_okay = True ),
)
def current( self, user_id ):
+ """
+ Return information on the currently logged-in user. If not logged in, default to the anonymous
+ user.
+
+ @type user_id: unicode
+ @param user_id: id of current logged-in user (if any), determined by @grab_user_id
+ @rtype: json dict
+ @return: { 'user': userdict or None, 'notebooks': notebooksdict, 'http_url': url }
+ """
# if there's no logged-in user, default to the anonymous user
self.__database.load( user_id or u"User anonymous", self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
@@ -217,6 +279,7 @@ class Users( object ):
yield dict(
user = None,
notebooks = None,
+ http_url = u"",
)
return