Skip to content

whole-tale/wt_home_dirs

Repository files navigation

WholeTale Home Dirs

This is a Girder plugin that adds user home directory capability accessible through WebDAV. It uses WsgiDAV. It also allows access to tale directories.

Access

The WebDAV URL for home directories is /homes/<username>. Example:

fusedav https://localhost:8080/homes/wtuser ~/wthome

Similarly, the URL for tales is /tales/<taleId>.

Configuration

wt.homedir.root

Specifies where the home directory data is stored. This should be a filesystem path accessible by the Girder server. If this path does not exist, it will be created. User home directories are stored in specific user directories and are named <wt.homedir.root>/<username>.

wt.taledir.root

Like wt.homedir.root except for tale directories.

Updating the root directories does not copy data. Since girder maintains duplicate filesystem data, such an update without a manual copy of the data from the old root to the new one may result in inconsistencies between what girder sees and what the WebDAV server sees.

Authentication

The plugin is configured to only accept basic authentication. It should only be used over HTTPS. The other choice would have been digest authentication. The reason for avoiding that is that it either requires storing plaintext (or obfuscated) passwords or a hash (HA1 - see Digest Access Authentication) that could potentially be susceptible to dictionary attacks. So complexity + not such a good idea = use the other thing.

Authentication itself is done using either the OAUTH token, a user-specified password, or an automatically generated password. If the Girder OAUTH token is used, the password must be constructed by pre-pending token: to the token value. Example:

fusedav -u wtuser \
-p token:jPuIdOjh9A1Q1Bhshxop7yuhToKSM0WgdVZxGQqHjUTLEeHQ65qzVZ9faBW6WpEz \
https://localhost:8080/homes/wtuser ~/wthome

Users can either set a password or request a random password using /#homedir/password. Only one password can be active at a time and generating a random password or setting a new password overwrited previous passwords. If user-set or generated passwords are used, they should be specified directly when authenticating:

fusedav -u wtuser -p p7yuhToK \
https://localhost:8080/homes/wtuser ~/wthome

There is some throttling enabled. More than 5 failed authentication requests for one user in one minute will trigger a lockout for the remainig time in that minute (or at least that's the intention of the code).

API

At this point the API is limited to password management.

Set password

PUT /homedirpass/set

The user for which the WebDAV password is being set must be the current user. The password must be sent as form data in a string.

Generate password

GET /homedirpass/generate

There are no parameters. The generated password is returned as a JSON object in the form {'password': <password>}

Internals

In order to allow filesystem browsing through existing infrastructure (i.e., Girder), the home directory plugin maintains a "shadow" filesystem structure in Girder. The Girder filesystem structure is synchronized with the WebDAV version. Only metadata is stored in Girder and data is only maintained in WebDAV accessible directories. The synchronization between Girder and WebDAV is a two way process.

In the WebDAV -> Girder direction, hooks into the WebDAV implementation are used to send relevant requests to Girder. Such requests include file/folder create/copy/move/delete. File data is handled using a custom assetstore and assetstore adapter which simply get the file data from the home directory backing storage, which stores files in a standard filesystem.

The Girder -> WebDAV synchronization is handled through events generated by Girder as well as assetstore adapter operations. Specifically, the mapping between operations and mechanisms handling them is:

Operation Handling
create file AssetstoreAdapter.finalizeUpload
delete file AssetstoreAdapter.deleteFile
copy file model.item.save event
move file model.item.save event
rename file model.item.save event
create folder model.folder.save event
delete folder model.folder.remove event
copy folder model.folder.save event
move folder model.folder.save event
rename folder model.folder.save event

Since Girder does not generate distinct events for the various operations, the code has to infer the type of operation from data associated with events. In particular, the following algorithm is used to distinguish between the various folder operations using a folder object obtained from a model.folder.save event:

    if '_id' in folder:
        # the object has an ID, so it is not a new folder

        # find the object that's already stored in the Girder database
        storedFolder = Girder.findFolder(folder['_id'])

        if storedFolder['name'] != folder['name']:
            DAV.renameFolder(...)
        if storedFolder['parentId'] != folder['parentId']:
            DAV.moveFolder(...)
    else: # '_id' not in folder
        DAV.createFolder(...)

There is no special handling needed for file and folder copy operations since Girder handles them recursively through relevant create file/folder operations.