|
- #!/usr/bin/python
- # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
-
- ## all
- from singleton import Singleton
-
- ## WSGITemplate
- from template import render
- from functools import partial
-
- ## WSGIAuth
- import threading
- import re
- import Cookie
- import hashlib
- import hmac
- import os
- import random
- import time
- import cPickle
-
-
- md5 = lambda x : hashlib.md5( x ).hexdigest()
- sha1 = lambda key,value: hmac.new( key, value, hashlib.sha1 ).hexdigest()
-
- #------------------------------------------------------------------------------
-
- class WSGITemplate( object ):
- __metaclass__ = Singleton
-
- def __init__( self, basedir='' ):
- import os
- self.__basedir = os.path.normpath( os.path.join( os.path.split(__file__)[0], basedir ) ) + '/'
- self.__fallback_template = \
- "<h1> NO TEMPLATE DEFINED </h1>\n" \
- "{{ from pprint import pformat }}" \
- "<pre>{{ = pformat( { k:v for k,v in locals().iteritems() if k not in ('NOESCAPE','__builtins__','pformat','response') }, width=132 ) }}</pre>\n\n"
-
-
- def template( self, filename=None ):
- def real_decorator( wsgi_application ):
- def wrapper( environ, start_response ):
- if filename:
- environ[ 'template' ] = partial( render, filename=self.__basedir + filename )
- else:
- environ[ 'template' ] = partial( render, content=self.__fallback_template )
- return wsgi_application( environ, start_response )
-
- return wrapper
-
- return real_decorator
-
-
- class WSGIMySQL( object ):
- __metaclass__ = Singleton
-
- def __init__( self, dsn, *args ):
- """ inizializza le connessioni a 1 o più database.
- In caso di connessioni a databese multipli,
- le connessioni sono identificate tramite ALIAS
- o in mancanza di questo tramite DB
-
- ogni singolo dsn deve essere un dizionario con le chiavi:
- - DB : nome del database
- - HOST : host a cui connettersi
- - USER : username da utilizzare per connettersi
- - PASSWORD : password da utilizzare per connettersi
- - ALIAS : (opzionale) identificativo della connessione
- """
- import MySQLdb
-
- #
- # aggiungiamo il primo dsn in cima alla lista
- #
- args = list( args )
- args.insert( 0, dsn )
-
- #
- # creiamo il nostro dizionario di dizionari
- #
- self.__dsn = { dsndict.get( 'ALIAS', dsndict['DB'] ):dsndict for dsndict in args }
-
- #
- # verifichiamo che non ci siano alias duplicati
- #
- if len( self.__dsn.keys() ) != len( args ):
- raise Exception( "WSGIMySQL :: conflicting alias in dsn list" )
-
- #
- # tentiamo di creare la prima connessione verso TUTTI i dsn passati
- #
-
- for alias, dsndict in self.__dsn.iteritems():
- dsndict['pool'] = [ self.__newconn( alias ) ]
-
- self.__dict_cursor = MySQLdb.cursors.DictCursor
-
- def __newconn( self, alias ):
- import MySQLdb
-
- return MySQLdb.connect(
- host = self.__dsn[ alias ]["HOST"],
- user = self.__dsn[ alias ]["USER"],
- passwd = self.__dsn[ alias ]["PASSWORD"],
- db = self.__dsn[ alias ]["DB"]
- )
-
- def db( self, *args ):
- def real_decorator( wsgi_application ):
- def wrapper( environ, start_response ):
- connections = []
-
- for arg in args:
- try:
- conn = self.__dsn[ arg ]['pool'].pop( 0 )
- except IndexError:
- conn = self.__newconn( arg )
-
- connections.append( conn )
-
- cur = conn.cursor( self.__dict_cursor )
-
- environ['mysql.' + arg + '.cur'] = cur
-
- try:
- for item in wsgi_application( environ, start_response ):
- yield item
- finally:
- for arg in args:
- conn = connections.pop(0)
- conn.commit()
- self.__dsn[ arg ]['pool'].append( conn )
-
- return wrapper
-
- return real_decorator
-
-
- def __del__( self ):
- #
- # chiudiamo tutte le connessioni attualmente aperte
- #
- for dsndict in self.__dsn.items():
- for conn in dsndict['pool']:
- conn.close()
-
-
- class WSGISimpleAuth( object ):
- __metaclass__ = Singleton
-
- def __init__( self, timeout=900, auth_dir = 'auth_files', login_url=None, forbidden_url=None ):
- import os
- self.__authdir = os.path.normpath( os.path.join( os.path.split(__file__)[0], auth_dir ) )
- self.__timeout = timeout
- self._lock = threading.RLock()
-
- def uuid( self ):
- """Generate a unique session ID"""
- return hashlib.sha256(
- str(os.getpid())
- + str(time.time())
- + str(random.random())
- ).hexdigest()
-
- def acquire_lock(self):
- self._lock.acquire()
-
- def release_lock(self):
- self._lock.release()
-
-
- def require( self, permissions=[], groups=[], p_g_mode='AND', p_mode='OR', g_mode='OR' ):
- def real_decorator( wsgi_application ):
- def wrapper( environ, start_response ):
- #--------------------------------------------------------------
-
- def my_start_response( status, response_headers ):
- #
- # aggiunge il cookie all'header
- #
- cookie = Cookie.SimpleCookie()
- cookie["uuid"] = uuid
- response_headers.append( ('Set-Cookie',cookie.output()) )
-
- #
- # salva le informazioni legate al cookie
- #
- timeout = storage.get( 'timeout', self.__timeout )
- storage['epoch_timeout'] = time.time() + timeout
- storage['epoch_write'] = time.time()
-
- self.acquire_lock() ## LOCK
-
- f = open( path, 'w' )
- try:
- cPickle.dump(storage, f)
- finally:
- f.close()
-
- self.release_lock() ## RELEASE
-
- #
- # start response originale
- #
- start_response( status, response_headers );
-
- #--------------------------------------------------------------
-
- #
- # recupera UUID dal cookie
- #
- try:
- uuid = Cookie.SimpleCookie(environ["HTTP_COOKIE"])["uuid"].value
- except:
- uuid = self.uuid()
-
- #
- # utilizza lo UUID per recuperare le informazioni (locali) ad esso legate
- #
- path = os.path.join( self.__authdir, uuid )
-
- self.acquire_lock() ## LOCK
-
- try:
- f = open( path, 'r' )
- storage = cPickle.load( f )
- f.close()
-
- if storage['epoch_timeout'] < time.time():
- # dati scaduti rimuove il file dei permessi..
- os.unlink( path )
-
- # ... e finge di non everlo mai trovato
- raise Exception
-
- logged_in = True
-
- except:
- # UUID assente, crea una nuova struttura dati
- storage = {
- 'epoch_created': time.time(),
- 'permissions': [],
- 'groups': [],
- 'timeout': self.__timeout
- }
-
- logged_in = False
-
- self.release_lock() ## RELEASE
-
- storage['epoch_read'] = time.time()
-
- if permissions:
- if isinstance( permissions, str ):
- p = permissions.split()
- else:
- p = permissions
-
- if not logged_in:
- # non autenticato -> redirect to 'login_url'
- start_response( '302 Found', [('Location', '/login'),] )
- yield 'Please <a href="%s">login</a> first.' % '/login'
- return
-
- for perm in p:
- if perm in storage['permissions']:
- break
- else:
- # permessi insufficienti -> redirect to 'forbidden_url'
- start_response( '302 Found', [('Location', '/forbidden'),] )
- yield 'This page is not for your eyes.'
- return
-
- #
- # popola environ
- #
- environ['auth.uuid'] = uuid
- environ['auth.storage'] = storage
-
- #
- # output dei contenuti generati
- #
- for item in wsgi_application( environ, my_start_response ):
- yield item
-
- return wrapper
-
- return real_decorator
-
-
- # EOF
-
- """
- {
- uuid: jkfghkjgdhfgkjlsk,
- permissions=[],
- groups=[],
- timeout=3600
- }
-
-
-
- {
- uuid: jkfghkjgdhfgkjlsk,
- permissions=[],
- groups=[],
- timeout=3600
- }
- """
|