The built-in LoginUser class is the base class for all User objects that Snakelets uses. If you declare on a page or in a snakelet that the session requires a logged in user, Snakelets will only allow a visitor to access that page if there is a logged in user on the session. You have to code that login mechanism yourself, but Snakelets then uses the authenticated user object for various security related things.
The class contains the following properties, and some methods, that will be explained below:
userid (read-only)
name (read/write)
password (read/write)
privileges (read/write/delete)
Passwords: the password attribute of the User object contains a secure hash of the user's userid+password (not the password itself!). You must use the checkPassword() method to check if the given password is equal to the password of that user. Note that the hash is a binary string and that you may have to convert it to hex codes or similar if you want to store or print it.
Configuring the Secure Hash function:
The default hash function is the sha-module (SHA-1).
If you need to use a different function, for instance MD5 (maybe because of backward compatibility with older
Snakelet versions, or when you can't or don't want to reset all passwords) you'll have to specify that like this in your webapp init code:
import md5
import snakeserver.user
snakeserver.user.securehash=md5
Note that you must set it to a module or object that has the new() method to create a new hashing object (Python's
md5 and sha modules both have this).
Also note that this is a global setting that will affect all webapps on the server.
(Even if you set this in the init code of only one of your webapps).
The password hash function was changed in Snakelets 1.44 from md5 to sha. If you have passwords on storage created
by an older Snakelets version, you will have to reset all passwords. Conversion is impossible, because
the original passwords cannot be reconstructed from the existing md5 hash.
Snakelets supports browser-based HTTP authentication (you know, where the browser shows a username/password dialog). Use the 'httpbasic' or 'httpdigest' method for this. You can also use a login page to do the authentication ('loginpage' method). If user authentication is required (because the session type is 'user', or there is an authorizedRole defined for the page), Snakelets will start using the configured authentication method to validate the user. The following methods are available:
How to specify the method to use?
<%@authmethod=method;argument%> declaration.getAuthMethod(self) function that returns a tuple (authmethod, argument).authenticationMethod = (method, argument) attribute in your webapp's init file.When you use any of the above authentication methods, and the session type is suitable, Snakelets will automatically log in an authenticated user (see above). You won't have to do anything by yourself when using the 'http'-methods, but you will have to do a little extra work if you use 'loginpage'.
Your login page must have a session (session type may not be 'no'), and a form containing a 'login' and a 'password' input field, and the form's action url should be the login page itself (self.getURL() is handy). Inherit the page from the snakeserver.user.LoginPage baseclass to gain access to the attemptLogin(returnpage) method. Call this method first thing in your login page, and you're set. If the login succeeds, it will redirect you to the return page (you can override this by passing it as argument to the authentication method). If the login fails, your Ypage simply continues. (The attemptLogin method does some trickery to remember the page you must return to after a succesful login!)
The login logic looks first for form field values for the 'login' and 'password' form fields, if these are not available
it also looks on the request Context object for the same attributes. This is useful to be able to set the login/password
by another means than a user form (such as a remember-me-cookie).
Password check: If you use one of Snakelet's built-in authentication methods (as described above) you still have to provide a special method in your webapp's init file, that Snakelets will use to check username+password when it needs to:
def authorizeUser(authmethod, url, username, password, request)
It must return a snakeserver.user.LoginUser object (or subclass thereof), or a set/sequence of privilege names, or None if the given user+password is not correct. The url parameter is the page that was requested, and the request parameter is the Request object (you can get ip addresses or session information from it, for instance).
Logging out: Somewhere in your code you have to logout the user and delete the session associated with it. You must use the logoutUser and deleteSession functions for that, see the next paragraph. Note that when using http authentication it is not possible to truly log a user out; the web browser continues to send the previously accepted credentials, and the user is logged in again at the next request...
Custom login mechanism: If you program your own authentication/login mechanism, you won't need any of the stuff described above (no authmethod and so on, because you are programming this yourself). In your custom login code you will need the following methods:
Session.loginUser(user) - to log in (see Session object)Session.logoutUser() - to log out (see Session object)Session.getLoggedInUser() - to get the current user (see Session object)Request.deleteSession() - to log out and also remove the session (see Request object)See the 'account' example webapp for a possible implementation of all this. The 'manage' webapp also uses authentication with a login page. In the 'test' webapp is an example of httpbasic authentication.
You can require a user to simply log in using a password, and be done with it, but every authenticated user in Snakelets can also have a set of "roles" (or "privileges"). When creating a User object you can provide a list or set of privileges (usually just strings/names, such as "administrator", "moderator"...) that are given to this user. You can then grant or deny access to pages based on these privileges. The user object has a property (attribute) privileges that contains a set of the privileges the user has, and some methods to aid checking for privileges:
hasPrivileges(privs) -- does the user have all of the privileges (list or set)?hasAnyPrivilege(privs) -- does the user have one or more of the privileges (list or set)?if "privilege" in user.privileges: ...You can set required privileges on several levels:
<%@authorized=role1,role2%> (this requires session='user')def getAuthorizedRoles(self): return set-of-privilegenames (or list/tuple)authorizationPatterns as a dictionary containing 'fnmatch'-style url patterns (the same as the Snakelet patterns) as keys, with lists of accepted privileges/roles as values. If you use None for the privileges, it means "except this one"; which means that for that specific URL pattern no privileges are required (even if other patterns match).
authorizationPatterns = {
"data/accounts/*": [ "accountmgr", "controller" ],
"*.doc": [ "worduser" ],
"info.doc": None
}
Note that multiple patterns may match. For instance, with the above example, the file "data/accounts/finance.doc" can only be accessed by somebody who is a 'worduser' and one or both of 'accountmanager' and 'controller'.
Be careful with patterns; a pattern such as "*" matches for all urls in the webapp. This includes static files such as .css style sheets!
Snakelets automatically appends the *-wildcard to the end of all patterns to avoid security holes. The server-wide url prefix is automatically prepended to your patterns, so your patterns are relative to the webapp root.
Smart suffixes are taken into account when checking authorization patterns. This means that if you have a pattern "admin.y", the url without the suffix ("admin") will also be protected (because smart suffixes would add .y automatically to this url!) The other way around: if you have a pattern "admin" (without suffix), urls of the form "admin.y", "admin.sn" etc. will also be protected, because of the automatic appending of the '*'-wildcard.
When you're not authorized to view a page, you'll get a HTTP 403 error page 'You don't have the required privileges to access this page' (or a similar message).
You can get the currently logged in user from the Session object, using something like:
req.getSession().getLoggedInUser()
or, when in an Ypage, using the shortcut object:
self.User.
You will get the currently logged-in user object, or None when nobody is logged in.
With the help of the "SharedAuth" plugin (available separately in the Plugins package; this function is not available by default) it is possible to do shared authentication (share a single authentication method over multiple webapps). This means that if you have some central user administration datastore, you only have to program an authentication method in one webapp that uses this datastore to authenticate users. It can then register other webapps to be allowed to 'share' the authentication method. Now the other webapps can ask the SharedAuth plugin to do the authentication for them, and it will use the authentication method of the first webapp. For more information on how to do this, see the source code of the SharedAuth plugin.
Single singon is different: this means that a user only logs in on one webapp, and other webapps will now share this login information, so that the user doesn't have to log in for each webapp. The latter is the normal behavior, because every webapp has its own session (for security and containment). By setting the sharedSession config item to True (default is False), you can tell the server to use the global shared session. Every webapp that has this on True will not use its own session, but instead share a single global session, and so they share the session object, the session user information, and the session context. Because it may be dangerous, Snakelets prints a warning when starting up when it detects that a webapp uses the shared session. There are several things that are important:
LoginUser objects, you're safe, but if you are using your own subclassed User object with custom methods or properties, you may be in trouble, unless every webapp that shares the global session knows about this and sets the correct user object. It's probably better to define a User class that is then also shared across all your webapps that have a shared session. The 'shared1' and 'shared2' example webapps show how you can use the shared session to achieve single signon.
Snakelets manual - Back to index