Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

309 linhas
9.3 KiB

  1. #!/usr/bin/python
  2. # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
  3. ## all
  4. from singleton import Singleton
  5. ## WSGITemplate
  6. from template import render
  7. from functools import partial
  8. ## WSGIAuth
  9. import threading
  10. import re
  11. import Cookie
  12. import hashlib
  13. import hmac
  14. import os
  15. import random
  16. import time
  17. import cPickle
  18. md5 = lambda x : hashlib.md5( x ).hexdigest()
  19. sha1 = lambda key,value: hmac.new( key, value, hashlib.sha1 ).hexdigest()
  20. #------------------------------------------------------------------------------
  21. class WSGITemplate( object ):
  22. __metaclass__ = Singleton
  23. def __init__( self, basedir='' ):
  24. import os
  25. self.__basedir = os.path.normpath( os.path.join( os.path.split(__file__)[0], basedir ) ) + '/'
  26. self.__fallback_template = \
  27. "<h1> NO TEMPLATE DEFINED </h1>\n" \
  28. "{{ from pprint import pformat }}" \
  29. "<pre>{{ = pformat( { k:v for k,v in locals().iteritems() if k not in ('NOESCAPE','__builtins__','pformat','response') }, width=132 ) }}</pre>\n\n"
  30. def template( self, filename=None ):
  31. def real_decorator( wsgi_application ):
  32. def wrapper( environ, start_response ):
  33. if filename:
  34. environ[ 'template' ] = partial( render, filename=self.__basedir + filename )
  35. else:
  36. environ[ 'template' ] = partial( render, content=self.__fallback_template )
  37. return wsgi_application( environ, start_response )
  38. return wrapper
  39. return real_decorator
  40. class WSGIMySQL( object ):
  41. __metaclass__ = Singleton
  42. def __init__( self, dsn, *args ):
  43. """ inizializza le connessioni a 1 o più database.
  44. In caso di connessioni a databese multipli,
  45. le connessioni sono identificate tramite ALIAS
  46. o in mancanza di questo tramite DB
  47. ogni singolo dsn deve essere un dizionario con le chiavi:
  48. - DB : nome del database
  49. - HOST : host a cui connettersi
  50. - USER : username da utilizzare per connettersi
  51. - PASSWORD : password da utilizzare per connettersi
  52. - ALIAS : (opzionale) identificativo della connessione
  53. """
  54. import MySQLdb
  55. #
  56. # aggiungiamo il primo dsn in cima alla lista
  57. #
  58. args = list( args )
  59. args.insert( 0, dsn )
  60. #
  61. # creiamo il nostro dizionario di dizionari
  62. #
  63. self.__dsn = { dsndict.get( 'ALIAS', dsndict['DB'] ):dsndict for dsndict in args }
  64. #
  65. # verifichiamo che non ci siano alias duplicati
  66. #
  67. if len( self.__dsn.keys() ) != len( args ):
  68. raise Exception( "WSGIMySQL :: conflicting alias in dsn list" )
  69. #
  70. # tentiamo di creare la prima connessione verso TUTTI i dsn passati
  71. #
  72. for alias, dsndict in self.__dsn.iteritems():
  73. dsndict['pool'] = [ self.__newconn( alias ) ]
  74. self.__dict_cursor = MySQLdb.cursors.DictCursor
  75. def __newconn( self, alias ):
  76. import MySQLdb
  77. return MySQLdb.connect(
  78. host = self.__dsn[ alias ]["HOST"],
  79. user = self.__dsn[ alias ]["USER"],
  80. passwd = self.__dsn[ alias ]["PASSWORD"],
  81. db = self.__dsn[ alias ]["DB"]
  82. )
  83. def db( self, *args ):
  84. def real_decorator( wsgi_application ):
  85. def wrapper( environ, start_response ):
  86. connections = []
  87. for arg in args:
  88. try:
  89. conn = self.__dsn[ arg ]['pool'].pop( 0 )
  90. except IndexError:
  91. conn = self.__newconn( arg )
  92. connections.append( conn )
  93. cur = conn.cursor( self.__dict_cursor )
  94. environ['mysql.' + arg + '.cur'] = cur
  95. try:
  96. for item in wsgi_application( environ, start_response ):
  97. yield item
  98. finally:
  99. for arg in args:
  100. conn = connections.pop(0)
  101. conn.commit()
  102. self.__dsn[ arg ]['pool'].append( conn )
  103. return wrapper
  104. return real_decorator
  105. def __del__( self ):
  106. #
  107. # chiudiamo tutte le connessioni attualmente aperte
  108. #
  109. for dsndict in self.__dsn.items():
  110. for conn in dsndict['pool']:
  111. conn.close()
  112. class WSGISimpleAuth( object ):
  113. __metaclass__ = Singleton
  114. def __init__( self, timeout=900, auth_dir = 'auth_files', login_url=None, forbidden_url=None ):
  115. import os
  116. self.__authdir = os.path.normpath( os.path.join( os.path.split(__file__)[0], auth_dir ) )
  117. self.__timeout = timeout
  118. self._lock = threading.RLock()
  119. def uuid( self ):
  120. """Generate a unique session ID"""
  121. return hashlib.sha256(
  122. str(os.getpid())
  123. + str(time.time())
  124. + str(random.random())
  125. ).hexdigest()
  126. def acquire_lock(self):
  127. self._lock.acquire()
  128. def release_lock(self):
  129. self._lock.release()
  130. def require( self, permissions=[], groups=[], p_g_mode='AND', p_mode='OR', g_mode='OR' ):
  131. def real_decorator( wsgi_application ):
  132. def wrapper( environ, start_response ):
  133. #--------------------------------------------------------------
  134. def my_start_response( status, response_headers ):
  135. #
  136. # aggiunge il cookie all'header
  137. #
  138. cookie = Cookie.SimpleCookie()
  139. cookie["uuid"] = uuid
  140. response_headers.append( ('Set-Cookie',cookie.output()) )
  141. #
  142. # salva le informazioni legate al cookie
  143. #
  144. timeout = storage.get( 'timeout', self.__timeout )
  145. storage['epoch_timeout'] = time.time() + timeout
  146. storage['epoch_write'] = time.time()
  147. self.acquire_lock() ## LOCK
  148. f = open( path, 'w' )
  149. try:
  150. cPickle.dump(storage, f)
  151. finally:
  152. f.close()
  153. self.release_lock() ## RELEASE
  154. #
  155. # start response originale
  156. #
  157. start_response( status, response_headers );
  158. #--------------------------------------------------------------
  159. #
  160. # recupera UUID dal cookie
  161. #
  162. try:
  163. uuid = Cookie.SimpleCookie(environ["HTTP_COOKIE"])["uuid"].value
  164. except:
  165. uuid = self.uuid()
  166. #
  167. # utilizza lo UUID per recuperare le informazioni (locali) ad esso legate
  168. #
  169. path = os.path.join( self.__authdir, uuid )
  170. self.acquire_lock() ## LOCK
  171. try:
  172. f = open( path, 'r' )
  173. storage = cPickle.load( f )
  174. f.close()
  175. if storage['epoch_timeout'] < time.time():
  176. # dati scaduti rimuove il file dei permessi..
  177. os.unlink( path )
  178. # ... e finge di non everlo mai trovato
  179. raise Exception
  180. logged_in = True
  181. except:
  182. # UUID assente, crea una nuova struttura dati
  183. storage = {
  184. 'epoch_created': time.time(),
  185. 'permissions': [],
  186. 'groups': [],
  187. 'timeout': self.__timeout
  188. }
  189. logged_in = False
  190. self.release_lock() ## RELEASE
  191. storage['epoch_read'] = time.time()
  192. if permissions:
  193. if isinstance( permissions, str ):
  194. p = permissions.split()
  195. else:
  196. p = permissions
  197. if not logged_in:
  198. # non autenticato -> redirect to 'login_url'
  199. start_response( '302 Found', [('Location', '/login'),] )
  200. yield 'Please <a href="%s">login</a> first.' % '/login'
  201. return
  202. for perm in p:
  203. if perm in storage['permissions']:
  204. break
  205. else:
  206. # permessi insufficienti -> redirect to 'forbidden_url'
  207. start_response( '302 Found', [('Location', '/forbidden'),] )
  208. yield 'This page is not for your eyes.'
  209. return
  210. #
  211. # popola environ
  212. #
  213. environ['auth.uuid'] = uuid
  214. environ['auth.storage'] = storage
  215. #
  216. # output dei contenuti generati
  217. #
  218. for item in wsgi_application( environ, my_start_response ):
  219. yield item
  220. return wrapper
  221. return real_decorator
  222. # EOF
  223. """
  224. {
  225. uuid: jkfghkjgdhfgkjlsk,
  226. permissions=[],
  227. groups=[],
  228. timeout=3600
  229. }
  230. {
  231. uuid: jkfghkjgdhfgkjlsk,
  232. permissions=[],
  233. groups=[],
  234. timeout=3600
  235. }
  236. """