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