[Commits] commit r205 - twisted/trunk/contrib/nevow/doc/txtcommit a svn. commit a svn.Mar 1 Ago 2006 21:03:31 CEST
Author: manlio Date: Tue Aug 1 21:03:12 2006 New Revision: 205 Modified: twisted/trunk/contrib/nevow/doc/txt/nevow-authentication.txt twisted/trunk/contrib/nevow/doc/txt/nevow-rendering-base.txt Log: nuovo capitolo + correzioni Modified: twisted/trunk/contrib/nevow/doc/txt/nevow-authentication.txt ============================================================================== --- twisted/trunk/contrib/nevow/doc/txt/nevow-authentication.txt (original) +++ twisted/trunk/contrib/nevow/doc/txt/nevow-authentication.txt Tue Aug 1 21:03:12 2006 @@ -1,5 +1,516 @@ +========================= How to authenticate users ========================= +See also: http://pdos.lcs./cookies/pubs/webauth:tr.pdf + +Preface +======= + +Some general words on authentication... XXX TODO + +Authentication in web applications poses some problems. +This is due to the nature of the HTTP protocol that is stateful. + +Authenticate user means not only to assure that a user is who claims +he is, but also to assure that a hostile user can gain unauthorized +access. + +The most secure way is to use HTTPS, based on SSL. But SSL can cause +too burden on an application. + +HTTP Basic Authentication +========================= + +The standard way to do authentication is via HTTP Basic +Authentication, witch is supported in Twisted Web. + +Here is a simple example:: + + class SimpleRoot(rend.Page): + def renderHTTP(self, ctx): + request = inevow.IRequest(ctx) + username, password = request.getUser(), request.getPassword() + + if (username, password) == (‘‘, ‘‘): + request.setHeader(‘WWW-Authenticate‘, + ‘Basic realm="MyRealm"‘) + request.setResponseCode(http.UNAUTHORIZED) + + return "Authentication required." + + self.data_username, self.data_password = username, password + return rend.Page.renderHTTP(self, ctx) + + docFactory = loaders.stan(tags.html[ + tags.body[ + tags.h1["Welcome user!"], + tags.div["You said: ", + tags.span(data=tags.directive(‘username‘), render=str), + " ", + tags.span(data=tags.directive(‘password‘), render=str)] + ] + ]) + + + def locateChild(self, ctx, segments): + return self, () + + +Note that we need to hook renderHTTP to make sure to catch +unauthorized logins. + +HTTP Basic Authentication has several problems, both with user +interface and with security. + +security considerations +----------------------- + +The `Authenticator` is sent on clear. +This is a feature common with all authentications methods that do not +use HTTPS, but HTTP Basic Authentications **sends** the password in +clear. + +You should avoid this. User password **must be** protected with care. +Infact some users tend to reuse the same credentials. + +Take care of password: + +* keep them in yor database encrypted +* when an user want to change a password, ask for the original + password again +* when an user want to change the email and you send the forgotten + password via email, ask for the password. + +With these precautions, even if an hostile user hack into in an user +account (via an `eavesdrop` or `man in the middle` attack), he is unable +to obtain user credentials. + + +user interface considerations +----------------------------- + +* With HTTP Basic Authentication you have no control on how users login + into your site. + +* Some user agent send the credentials at every request, + preemptively. + +* The access to the page is exclusive: of the user is granted access + or it is denied. + +XXX what else? + + +HTTP Digest Authentication +========================== + +This is a more robust method, since it does not send the credentials +in clear. But it still have security (and privacy?) problems. + +Moreover it is not available in Twisted Web. + + +Sessions +======== + +Since HTTP is a stateles protocol, one solution is to store state +with the help of the client. + +The standard way is to use Cookies (RFC xxxx) or URLs. +Both have problems but it is suggested to use Cookies. + +Here is a simple example:: + + class DoLogin(rend.Page): + docFactory = loaders.stan( + tags.html[ + tags.head[tags.title["Welcome"]], + tags.body[ + tags.p["Welcome, user!"] + ] + ] + ) + + def renderHTTP(self, ctx): + request = inevow.IRequest(ctx) + args = request.args + username = request.args["username"][0] + password = request.args["password"][0] + + if username == "username" and password == "password": + # login ok, set a cookie to remember authenticated username + request.addCookie("username", username, path="/") + + return rend.Page.renderHTTP(self, ctx) + + + class MyPage(rend.Page): + addSlash = True + docFactory = loaders.stan( + tags.html[ + tags.head[tags.title["Hi Boy"]], + tags.body[ + tags.invisible(render=tags.directive("isLogged"))[ + tags.div(pattern="False")[ + tags.form(action=‘dologin‘, method=‘post‘)[ + tags.table[ + tags.tr[ + tags.td[ "Username:" ], + tags.td[ tags.input(type=‘text‘,name=‘username‘) ], + ], + tags.tr[ + tags.td[ "Password:" ], + tags.td[ tags.input(type=‘password‘,name=‘password‘) ], + ] + ], + tags.input(type=‘submit‘), + tags.p, + ] + ], + tags.invisible(pattern="True")[ + tags.h3["Hi bro"], + tags.p(data=tags.directive("username")) + ] + ] + ] + ] + ) + + child_dologin = DoLogin() + + def renderHTTP(self, ctx): + request = inevow.IRequest(ctx) + + # get the username, None if anonymous access + request.username = request.getCookie("username") + return rend.Page.renderHTTP(self, ctx) + + + def render_isLogged(self, context, data): + q = inevow.IQ(context) + r = inevow.IRequest(context) + + true_pattern = q.onePattern(‘True‘) + false_pattern = q.onePattern(‘False‘) + if r.username: + return true_pattern or context.tag().clear() + else: + return false_pattern or context.tag().clear() + + def data_username(self, context, data): + request = inevow.IRequest(context) + return request.username or "anonymous" + + +This solution has several flaws. +It stores **sensible** information in the cookie. + +This must be avoided, cookies where not developed with security in +mind. +At least you should send the cookies on SSL (and do not forget to set +the `secure` attribute. + +The solution is simple: + +* you can encrypt the cookie value +* you can set the cookie value to a secure ID, used to lookup into an + internal (serverside) state. + + +We look into the last solution. + +server side sessions +-------------------- + +As the name suggests, server side sessions are objects that live on the +server. +Usually sessions are stored in a global dictionary (or on a database). + +To find the right session you need to create session‘s IDs. + +This solution avoid sending in clear sensible informations about an +user but sessions IDs should be created with care: they **must** not be +predicable. + +A good method is:: + + def createSessionID(self): + """Generate a new session ID. + """ + data = "%s_%s" % (str(random.random()) , str(time.time())) + return md5.new(data).hexdigest() + +If you are using Python 2.4, you can use urandom (that returns a +string instead of a float). + +However the default random generator is a good one, and it is reseeded +every time the module is initialized. + +Let‘s rewrite the previous example using session‘s IDs:: + + # global sessions dictionary + sessions = {} + + + class Session(object): + def createSessionID(self): + """Generate a new session ID. + """ + data = "%s_%s" % (str(random.random()) , str(time.time())) + return md5.new(data).hexdigest() + + + class DoLogin(rend.Page): + docFactory = loaders.stan( + tags.html[ + tags.head[tags.title["Welcome"]], + tags.body[ + tags.p["Welcome, user!"] + ] + ] + ) + + def renderHTTP(self, ctx): + request = inevow.IRequest(ctx) + args = request.args + username = request.args["username"][0] + password = request.args["password"][0] + + if username == "username" and password == "password": + # login ok, create a new session + session = Session() + uid = session.createSessionID() + request.addCookie("MY_SESSION", uid, path="/") + + # store the username on the session + session.username = username + sessions[uid] = session + + return rend.Page.renderHTTP(self, ctx) + + + class MyPage(rend.Page): + addSlash = True + docFactory = loaders.stan( + tags.html[ + tags.head[tags.title["Hi Boy"]], + tags.body[ + tags.invisible(render=tags.directive("isLogged"))[ + tags.div(pattern="False")[ + tags.form(action=‘dologin‘, method=‘post‘)[ + tags.table[ + tags.tr[ + tags.td[ "Username:" ], + tags.td[ tags.input(type=‘text‘,name=‘username‘) ], + ], + tags.tr[ + tags.td[ "Password:" ], + tags.td[ tags.input(type=‘password‘,name=‘password‘) ], + ] + ], + tags.input(type=‘submit‘), + tags.p, + ] + ], + tags.invisible(pattern="True")[ + tags.h3["Hi bro"], + tags.p(data=tags.directive("username")) + ] + ] + ] + ] + ) + + child_dologin = DoLogin() + + def renderHTTP(self, ctx): + request = inevow.IRequest(ctx) + + # get the session, if any + uid = request.getCookie("MY_SESSION") + session = sessions.get(uid) + if session is None: + request.username = None + else: + request.username = session.username + + return rend.Page.renderHTTP(self, ctx) + + + def render_isLogged(self, context, data): + q = inevow.IQ(context) + r = inevow.IRequest(context) + + true_pattern = q.onePattern(‘True‘) + false_pattern = q.onePattern(‘False‘) + if r.username: + return true_pattern or context.tag().clear() + else: + return false_pattern or context.tag().clear() + + def data_username(self, context, data): + request = inevow.IRequest(context) + return request.username or "anonymous" + + +It is worth to note that this simplicity is due to the asyncronous +nature of Twisted. + +However you should not abuse the sessions, as an example by storing +user status/data on it. Use a database to do this. + + +security considerations +----------------------- + +This is the most secure solution you can employ without use SSL for +every request. + +Of course, to protect user passwords, you can put *only* the login +resource on SSL. + +user interface considerations +----------------------------- + +There are many user interface issues with this solution. + +Where should be the user redirected after a successful login? +And what about a failed one? + +As a general case you can use the Referer header to redirect the user +from where it come. + +This is very useful if you put a login form in every page. + +If you put the login form on a fixed, well know page, you have to +decide where to redirect authenticated users. + + +more control over sessions +========================== + +The sessions IDs solution seems a good one. However it still have some +problems. + +First of all, if you read the Cookie spec, you will learn that a +cookie will expired when the user session terminates (user close the +browser -- XXX or only your window?). + +Setting the `expires` (XXX what about max-age?) attribute you can tell +the user agent to store the cookie in a permanent way, deleting it +after the date you specify. + +The is the usually called "Remember me" feature. + +This features, though handy, poses some security problems. + +Remember that the session ID is sent in clear. So an intruder can +simply obtain the session id and, using it, granting access as an +authenticated user. + +Your application should be designed so that such an introder can make +as little damage as possible (and **never** stole the user password). + +This means that you should set sessions lifetime with care, and +warning your user about the security implication. + +However you should never trust a cookie, since it is easy to alter its +content. + +Of course you can add some control code (as an example storing both on +server side session and on the cookie - using cryptography - a +sequence number) but a better solution is to check for expirations on +the server. + +An example can be found on +http:///svn/Divmod/sandbox/dialtone/newguard/guard.py + +Here a timer is setup to every N minutes, to delete all expired +sessions. + +Another best pracite is to present to users a logout form, that will +delete the session. + + +Sessions and HTTP Basic Authentication +====================================== + +Until now we have used sessions only to support the standard login +form. + +However you can use HTTP Basic Authentication too, and cookie enable +you to require user credentials only once (hopefully on a SSL +channel). + + +Shared Sessions +=============== + +We do not mention it, but the previous solution **still** has a +problem. + +What happen if you have a distributed server architecture? +The answer is simple: you have troubles! + +In fact since the sessions dictionary is local to a single server, if +a login request is served by server #1, then server #2 does not know +this. + +Before trying to invent a syncronization protocol, you should consider +the use of a database to store sessions. + +See http:///svn/Divmod/sandbox/dialtone/newguard/session.py +as an example. + +Another solution is to use a "smart" load balancer (as the one in +Lighttpd mod_proxy). + + +Nevow Guard +=========== + +We presented here a simple solution for user authentication. + +Nevow come with guard that provides a more reusable and flexible +infrastructure and, more important, cred integration. + +One of the most powerful features of guard is that you can serve +**different** resources for different users. +Usually you serve a personalized resource for anonymous users and a +parametrized (with the avatarID) resource for authenticated users. + +Examples of guard usage are supplied with Nevow. + +However consider that guard is **very** complex. Moreover it support +sessions using URLs, not only Cookies. + +Guard is a very flexible solution, however flexibility comes with +cost. + +For every request a login into the realm is needed, with the creation +of a custom (dynamic) resource. + +Of cource you can do some caching in the realm, but if all you need is +to render selected portions of your page depending of the user, maybe +it is better to use a simpler approach, ad tha one presented here. + + +Sessions and caching +==================== + +Cookies poses some problems with cache. XXX TODO +HTTP 1.1 has additional control for caching, but you should implement +it by hand (and what about user agent that see HTTP 1.x version?) + +In general you should avoid to use cookies where not really needed +(XXX and guard always uses cookies...) + + +Authorization and Authentication with SSL +========================================= + +See nevow-ssl (coming soon). + Modified: twisted/trunk/contrib/nevow/doc/txt/nevow-rendering-base.txt ============================================================================== --- twisted/trunk/contrib/nevow/doc/txt/nevow-rendering-base.txt (original) +++ twisted/trunk/contrib/nevow/doc/txt/nevow-rendering-base.txt Tue Aug 1 21:03:12 2006 @@ -166,7 +166,9 @@ </root> -Note that the return value from these two methods is ignored. +Note that the return value from these two methods is ignored (XXX); +however if you return a deferred the rendering machinery will wait for +it. What about HEAD, PUT, DELETE?
Maggiori informazioni sulla lista Commits |
|