Mercurial > p > roundup > code
comparison roundup/cgi/client.py @ 2005:fc52d57c6c3e
documentation cleanup
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Wed, 11 Feb 2004 23:55:10 +0000 |
| parents | 1782fe36e7b8 |
| children | 1b11ffd8015e |
comparison
equal
deleted
inserted
replaced
| 2004:1782fe36e7b8 | 2005:fc52d57c6c3e |
|---|---|
| 1 # $Id: client.py,v 1.155 2004-02-11 21:34:31 jlgijsbers Exp $ | 1 # $Id: client.py,v 1.156 2004-02-11 23:55:09 richard Exp $ |
| 2 | 2 |
| 3 __doc__ = """ | 3 """WWW request handler (also used in the stand-alone server). |
| 4 WWW request handler (also used in the stand-alone server). | |
| 5 """ | 4 """ |
| 5 __docformat__ = 'restructuredtext' | |
| 6 | 6 |
| 7 import os, os.path, cgi, StringIO, urlparse, re, traceback, mimetypes, urllib | 7 import os, os.path, cgi, StringIO, urlparse, re, traceback, mimetypes, urllib |
| 8 import binascii, Cookie, time, random, stat, rfc822 | 8 import binascii, Cookie, time, random, stat, rfc822 |
| 9 | 9 |
| 10 from roundup import roundupdb, date, hyperdb, password | 10 from roundup import roundupdb, date, hyperdb, password |
| 14 from roundup.cgi.exceptions import * | 14 from roundup.cgi.exceptions import * |
| 15 from roundup.cgi.form_parser import FormParser | 15 from roundup.cgi.form_parser import FormParser |
| 16 from roundup.mailer import Mailer, MessageSendError | 16 from roundup.mailer import Mailer, MessageSendError |
| 17 | 17 |
| 18 def initialiseSecurity(security): | 18 def initialiseSecurity(security): |
| 19 ''' Create some Permissions and Roles on the security object | 19 '''Create some Permissions and Roles on the security object |
| 20 | 20 |
| 21 This function is directly invoked by security.Security.__init__() | 21 This function is directly invoked by security.Security.__init__() |
| 22 as a part of the Security object instantiation. | 22 as a part of the Security object instantiation. |
| 23 ''' | 23 ''' |
| 24 security.addPermission(name="Web Registration", | 24 security.addPermission(name="Web Registration", |
| 25 description="User may register through the web") | 25 description="User may register through the web") |
| 26 p = security.addPermission(name="Web Access", | 26 p = security.addPermission(name="Web Access", |
| 27 description="User may access the web interface") | 27 description="User may access the web interface") |
| 44 if ok.has_key(match.group(3).lower()): | 44 if ok.has_key(match.group(3).lower()): |
| 45 return match.group(1) | 45 return match.group(1) |
| 46 return '<%s>'%match.group(2) | 46 return '<%s>'%match.group(2) |
| 47 | 47 |
| 48 class Client: | 48 class Client: |
| 49 ''' Instantiate to handle one CGI request. | 49 '''Instantiate to handle one CGI request. |
| 50 | 50 |
| 51 See inner_main for request processing. | 51 See inner_main for request processing. |
| 52 | 52 |
| 53 Client attributes at instantiation: | 53 Client attributes at instantiation: |
| 54 "path" is the PATH_INFO inside the instance (with no leading '/') | 54 |
| 55 "base" is the base URL for the instance | 55 - "path" is the PATH_INFO inside the instance (with no leading '/') |
| 56 "form" is the cgi form, an instance of FieldStorage from the standard | 56 - "base" is the base URL for the instance |
| 57 cgi module | 57 - "form" is the cgi form, an instance of FieldStorage from the standard |
| 58 "additional_headers" is a dictionary of additional HTTP headers that | 58 cgi module |
| 59 should be sent to the client | 59 - "additional_headers" is a dictionary of additional HTTP headers that |
| 60 "response_code" is the HTTP response code to send to the client | 60 should be sent to the client |
| 61 - "response_code" is the HTTP response code to send to the client | |
| 61 | 62 |
| 62 During the processing of a request, the following attributes are used: | 63 During the processing of a request, the following attributes are used: |
| 63 "error_message" holds a list of error messages | 64 |
| 64 "ok_message" holds a list of OK messages | 65 - "error_message" holds a list of error messages |
| 65 "session" is the current user session id | 66 - "ok_message" holds a list of OK messages |
| 66 "user" is the current user's name | 67 - "session" is the current user session id |
| 67 "userid" is the current user's id | 68 - "user" is the current user's name |
| 68 "template" is the current :template context | 69 - "userid" is the current user's id |
| 69 "classname" is the current class context name | 70 - "template" is the current :template context |
| 70 "nodeid" is the current context item id | 71 - "classname" is the current class context name |
| 72 - "nodeid" is the current context item id | |
| 71 | 73 |
| 72 User Identification: | 74 User Identification: |
| 73 If the user has no login cookie, then they are anonymous and are logged | 75 If the user has no login cookie, then they are anonymous and are logged |
| 74 in as that user. This typically gives them all Permissions assigned to the | 76 in as that user. This typically gives them all Permissions assigned to the |
| 75 Anonymous Role. | 77 Anonymous Role. |
| 76 | 78 |
| 77 Once a user logs in, they are assigned a session. The Client instance | 79 Once a user logs in, they are assigned a session. The Client instance |
| 78 keeps the nodeid of the session as the "session" attribute. | 80 keeps the nodeid of the session as the "session" attribute. |
| 79 | |
| 80 | 81 |
| 81 Special form variables: | 82 Special form variables: |
| 82 Note that in various places throughout this code, special form | 83 Note that in various places throughout this code, special form |
| 83 variables of the form :<name> are used. The colon (":") part may | 84 variables of the form :<name> are used. The colon (":") part may |
| 84 actually be one of either ":" or "@". | 85 actually be one of either ":" or "@". |
| 144 finally: | 145 finally: |
| 145 if hasattr(self, 'db'): | 146 if hasattr(self, 'db'): |
| 146 self.db.close() | 147 self.db.close() |
| 147 | 148 |
| 148 def inner_main(self): | 149 def inner_main(self): |
| 149 ''' Process a request. | 150 '''Process a request. |
| 150 | 151 |
| 151 The most common requests are handled like so: | 152 The most common requests are handled like so: |
| 152 1. figure out who we are, defaulting to the "anonymous" user | 153 |
| 153 see determine_user | 154 1. figure out who we are, defaulting to the "anonymous" user |
| 154 2. figure out what the request is for - the context | 155 see determine_user |
| 155 see determine_context | 156 2. figure out what the request is for - the context |
| 156 3. handle any requested action (item edit, search, ...) | 157 see determine_context |
| 157 see handle_action | 158 3. handle any requested action (item edit, search, ...) |
| 158 4. render a template, resulting in HTML output | 159 see handle_action |
| 159 | 160 4. render a template, resulting in HTML output |
| 160 In some situations, exceptions occur: | 161 |
| 161 - HTTP Redirect (generally raised by an action) | 162 In some situations, exceptions occur: |
| 162 - SendFile (generally raised by determine_context) | 163 |
| 163 serve up a FileClass "content" property | 164 - HTTP Redirect (generally raised by an action) |
| 164 - SendStaticFile (generally raised by determine_context) | 165 - SendFile (generally raised by determine_context) |
| 165 serve up a file from the tracker "html" directory | 166 serve up a FileClass "content" property |
| 166 - Unauthorised (generally raised by an action) | 167 - SendStaticFile (generally raised by determine_context) |
| 167 the action is cancelled, the request is rendered and an error | 168 serve up a file from the tracker "html" directory |
| 168 message is displayed indicating that permission was not | 169 - Unauthorised (generally raised by an action) |
| 169 granted for the action to take place | 170 the action is cancelled, the request is rendered and an error |
| 170 - templating.Unauthorised (templating action not permitted) | 171 message is displayed indicating that permission was not |
| 171 raised by an attempted rendering of a template when the user | 172 granted for the action to take place |
| 172 doesn't have permission | 173 - templating.Unauthorised (templating action not permitted) |
| 173 - NotFound (raised wherever it needs to be) | 174 raised by an attempted rendering of a template when the user |
| 174 percolates up to the CGI interface that called the client | 175 doesn't have permission |
| 176 - NotFound (raised wherever it needs to be) | |
| 177 percolates up to the CGI interface that called the client | |
| 175 ''' | 178 ''' |
| 176 self.ok_message = [] | 179 self.ok_message = [] |
| 177 self.error_message = [] | 180 self.error_message = [] |
| 178 try: | 181 try: |
| 179 # figure out the context and desired content template | 182 # figure out the context and desired content template |
| 260 if interval > week: | 263 if interval > week: |
| 261 otks.destroy(sessid) | 264 otks.destroy(sessid) |
| 262 sessions.set('last_clean', last_use=time.time()) | 265 sessions.set('last_clean', last_use=time.time()) |
| 263 | 266 |
| 264 def determine_user(self): | 267 def determine_user(self): |
| 265 '''Determine who the user is. | 268 ''' Determine who the user is |
| 266 ''' | 269 ''' |
| 267 # open the database as admin | 270 # determine the uid to use |
| 268 self.opendb('admin') | 271 self.opendb('admin') |
| 269 | 272 |
| 270 # clean age sessions | 273 # make sure we have the session Class |
| 271 self.clean_sessions() | 274 self.clean_sessions() |
| 272 | |
| 273 # make sure we have the session Class | |
| 274 sessions = self.db.sessions | 275 sessions = self.db.sessions |
| 275 | 276 |
| 276 # look up the user session cookie | 277 # first up, try the REMOTE_USER var (from HTTP Basic Auth handled |
| 277 cookie = Cookie.SimpleCookie(self.env.get('HTTP_COOKIE', '')) | 278 # by a front-end HTTP server) |
| 279 try: | |
| 280 user = os.getenv('REMOTE_USER') | |
| 281 except KeyError: | |
| 282 pass | |
| 283 | |
| 284 # look up the user session cookie (may override the REMOTE_USER) | |
| 285 cookie = Cookie.Cookie(self.env.get('HTTP_COOKIE', '')) | |
| 278 user = 'anonymous' | 286 user = 'anonymous' |
| 279 | |
| 280 # bump the "revision" of the cookie since the format changed | |
| 281 if (cookie.has_key(self.cookie_name) and | 287 if (cookie.has_key(self.cookie_name) and |
| 282 cookie[self.cookie_name].value != 'deleted'): | 288 cookie[self.cookie_name].value != 'deleted'): |
| 283 | 289 |
| 284 # get the session key from the cookie | 290 # get the session key from the cookie |
| 285 self.session = cookie[self.cookie_name].value | 291 self.session = cookie[self.cookie_name].value |
| 288 # update the lifetime datestamp | 294 # update the lifetime datestamp |
| 289 sessions.set(self.session, last_use=time.time()) | 295 sessions.set(self.session, last_use=time.time()) |
| 290 sessions.commit() | 296 sessions.commit() |
| 291 user = sessions.get(self.session, 'user') | 297 user = sessions.get(self.session, 'user') |
| 292 except KeyError: | 298 except KeyError: |
| 293 user = 'anonymous' | 299 # not valid, ignore id |
| 300 pass | |
| 294 | 301 |
| 295 # sanity check on the user still being valid, getting the userid | 302 # sanity check on the user still being valid, getting the userid |
| 296 # at the same time | 303 # at the same time |
| 297 try: | 304 try: |
| 298 self.userid = self.db.user.lookup(user) | 305 self.userid = self.db.user.lookup(user) |
| 307 | 314 |
| 308 # reopen the database as the correct user | 315 # reopen the database as the correct user |
| 309 self.opendb(self.user) | 316 self.opendb(self.user) |
| 310 | 317 |
| 311 def determine_context(self, dre=re.compile(r'([^\d]+)(\d+)')): | 318 def determine_context(self, dre=re.compile(r'([^\d]+)(\d+)')): |
| 312 """ Determine the context of this page from the URL: | 319 """Determine the context of this page from the URL: |
| 313 | 320 |
| 314 The URL path after the instance identifier is examined. The path | 321 The URL path after the instance identifier is examined. The path |
| 315 is generally only one entry long. | 322 is generally only one entry long. |
| 316 | 323 |
| 317 - if there is no path, then we are in the "home" context. | 324 - if there is no path, then we are in the "home" context. |
| 318 * if the path is "_file", then the additional path entry | 325 - if the path is "_file", then the additional path entry |
| 319 specifies the filename of a static file we're to serve up | 326 specifies the filename of a static file we're to serve up |
| 320 from the instance "html" directory. Raises a SendStaticFile | 327 from the instance "html" directory. Raises a SendStaticFile |
| 321 exception. | 328 exception.(*) |
| 322 - if there is something in the path (eg "issue"), it identifies | 329 - if there is something in the path (eg "issue"), it identifies |
| 323 the tracker class we're to display. | 330 the tracker class we're to display. |
| 324 - if the path is an item designator (eg "issue123"), then we're | 331 - if the path is an item designator (eg "issue123"), then we're |
| 325 to display a specific item. | 332 to display a specific item. |
| 326 * if the path starts with an item designator and is longer than | 333 - if the path starts with an item designator and is longer than |
| 327 one entry, then we're assumed to be handling an item of a | 334 one entry, then we're assumed to be handling an item of a |
| 328 FileClass, and the extra path information gives the filename | 335 FileClass, and the extra path information gives the filename |
| 329 that the client is going to label the download with (ie | 336 that the client is going to label the download with (ie |
| 330 "file123/image.png" is nicer to download than "file123"). This | 337 "file123/image.png" is nicer to download than "file123"). This |
| 331 raises a SendFile exception. | 338 raises a SendFile exception.(*) |
| 332 | 339 |
| 333 Both of the "*" types of contexts stop before we bother to | 340 Both of the "*" types of contexts stop before we bother to |
| 334 determine the template we're going to use. That's because they | 341 determine the template we're going to use. That's because they |
| 335 don't actually use templates. | 342 don't actually use templates. |
| 336 | 343 |
| 337 The template used is specified by the :template CGI variable, | 344 The template used is specified by the :template CGI variable, |
| 338 which defaults to: | 345 which defaults to: |
| 339 | 346 |
| 340 only classname suplied: "index" | 347 - only classname suplied: "index" |
| 341 full item designator supplied: "item" | 348 - full item designator supplied: "item" |
| 342 | 349 |
| 343 We set: | 350 We set: |
| 351 | |
| 344 self.classname - the class to display, can be None | 352 self.classname - the class to display, can be None |
| 353 | |
| 345 self.template - the template to render the current context with | 354 self.template - the template to render the current context with |
| 355 | |
| 346 self.nodeid - the nodeid of the class we're displaying | 356 self.nodeid - the nodeid of the class we're displaying |
| 347 """ | 357 """ |
| 348 # default the optional variables | 358 # default the optional variables |
| 349 self.classname = None | 359 self.classname = None |
| 350 self.nodeid = None | 360 self.nodeid = None |
