[Commits] python.it commit r205 - twisted/trunk/contrib/nevow/doc/txt

commit a svn.python.it commit a svn.python.it
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.mit.edu/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://divmod.org/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://divmod.org/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