2010-01-16 00:05:49 +03:00
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
2009-09-23 17:57:21 +04:00
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
2010-01-16 00:05:49 +03:00
#
2009-09-23 17:57:21 +04:00
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
2010-01-16 00:05:49 +03:00
# for the specific language governing rights and limitations under the
# License.
#
2009-09-23 17:57:21 +04:00
# The Original Code is Bespin.
2010-01-16 00:05:49 +03:00
#
# The Initial Developer of the Original Code is
# Mozilla.
2009-09-23 17:57:21 +04:00
# Portions created by the Initial Developer are Copyright (C) 2009
# the Initial Developer. All Rights Reserved.
2010-01-16 00:05:49 +03:00
#
2009-09-23 17:57:21 +04:00
# Contributor(s):
2010-01-16 00:05:49 +03:00
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
2009-09-23 17:57:21 +04:00
# ***** END LICENSE BLOCK *****
#
from urlrelay import url
from webob import Request , Response
import logging
2010-03-10 06:14:53 +03:00
from bespin import filesystem , database , config , plugins
2009-09-23 17:57:21 +04:00
from bespin . __init__ import API_VERSION
from bespin . database import User
log = logging . getLogger ( " bespin.framework " )
class BadRequest ( Exception ) :
pass
class BespinRequest ( Request ) :
""" Custom request object for Bespin.
Provides the user object and the username of the
logged in user , among other features . """
def __init__ ( self , environ ) :
super ( BespinRequest , self ) . __init__ ( environ )
if ' bespin.user ' in environ :
self . _user = environ [ ' bespin.user ' ]
else :
self . _user = None
self . username = environ . get ( ' REMOTE_USER ' )
self . kwargs = environ . get ( ' wsgiorg.routing_args ' ) [ 1 ]
self . session_token = environ . get ( " HTTP_X_DOMAIN_TOKEN " )
@property
def user ( self ) :
if self . _user :
return self . _user
if self . username :
self . _user = User . find_user ( self . username )
return self . _user
return None
class BespinResponse ( Response ) :
2009-12-09 22:28:54 +03:00
def __init__ ( self , environ , start_response , * * kw ) :
2009-09-23 17:57:21 +04:00
super ( BespinResponse , self ) . __init__ ( * * kw )
self . environ = environ
2009-12-09 22:28:54 +03:00
self . start_response = start_response
2009-09-23 17:57:21 +04:00
def __call__ ( self ) :
2009-12-09 22:28:54 +03:00
return super ( BespinResponse , self ) . __call__ ( self . environ , self . start_response )
2009-09-23 17:57:21 +04:00
def error ( self , status , e ) :
self . status = status
self . body = str ( e )
self . environ [ ' bespin.docommit ' ] = False
def _add_base_headers ( response ) :
response . headers [ ' X-Bespin-API ' ] = API_VERSION
response . headers [ ' Cache-Control ' ] = " no-store, no-cache, must-revalidate, post-check=0, pre-check=0, private "
response . headers [ ' Pragma ' ] = " no-cache "
def expose ( url_pattern , method = None , auth = True , skip_token_check = False , profile = False ) :
""" Expose this function to the world, matching the given URL pattern
and , optionally , HTTP method . By default , the user is required to
be authenticated . If auth is False , the user is not required to be
authenticated . """
def entangle ( func ) :
@url ( url_pattern , method )
def wrapped ( environ , start_response ) :
# reply and action are somewhat nasty but needed to allow the
# profiler to run code by a "action()" string. Why?
reply = [ ]
def action ( ) :
if auth and ' REMOTE_USER ' not in environ :
response = Response ( status = ' 401 ' )
_add_base_headers ( response )
reply . append ( response ( environ , start_response ) )
return
config . c . stats . incr ( " requests_DATE " )
config . c . stats . incr ( " requests " )
request = BespinRequest ( environ )
response = BespinResponse ( environ , start_response )
skip_test = environ . get ( " BespinTestApp " )
if not skip_token_check and skip_test != " True " :
cookie_token = request . cookies . get ( " Domain-Token " )
header_token = environ . get ( " HTTP_X_DOMAIN_TOKEN " )
if cookie_token is None or header_token != cookie_token :
2010-04-28 16:35:52 +04:00
# log.info("request.url=%s" % request.url)
# log.info("cookies[Domain-Token]=%s" % cookie_token)
# log.info("headers[X-Domain-Token]=%s" % header_token)
# log.info("WARNING: The anti CSRF attack trip wire just went off. This means an unprotected request has been made. This could be a hacking attempt, or incomplete protection. The request has NOT been halted")
2009-09-23 17:57:21 +04:00
config . c . stats . incr ( " csrf_fail_DATE " )
# Do we need to do this?
user = request . user
_add_base_headers ( response )
try :
reply . append ( func ( request , response ) )
return
except filesystem . NotAuthorized , e :
response . error ( " 401 Not Authorized " , e )
except filesystem . FileNotFound , e :
environ [ ' bespin.good_url_but_not_found ' ] = True
response . error ( " 404 Not Found " , e )
except filesystem . FileConflict , e :
response . error ( " 409 Conflict " , e )
except database . ConflictError , e :
response . error ( " 409 Conflict " , e )
except filesystem . OverQuota , e :
response . error ( " 400 Bad Request " , " Over quota " )
except filesystem . FSException , e :
response . error ( " 400 Bad Request " , e )
except filesystem . BadValue , e :
response . error ( " 400 Bad Request " , e )
2010-03-10 06:14:53 +03:00
except plugins . PluginError , e :
response . error ( " 400 Bad Request " , e )
2009-09-23 17:57:21 +04:00
except BadRequest , e :
response . error ( " 400 Bad Request " , e )
reply . append ( response ( ) )
return
if profile :
# The output probably needs tuning for your needs
import cProfile , pstats , StringIO
prof = cProfile . Profile ( )
prof = prof . runctx ( " action() " , globals ( ) , locals ( ) )
stream = StringIO . StringIO ( )
stats = pstats . Stats ( prof , stream = stream )
stats . sort_stats ( " time " ) # Or cumulative
stats . print_stats ( 80 ) # 80 = how many to print
# The rest is optional.
stats . print_callees ( )
stats . print_callers ( )
log . info ( " Profile data: \n %s " , stream . getvalue ( ) )
else :
action ( )
return reply . pop ( )
return entangle