| Autore | SHA1 | Messaggio | Data |
|---|---|---|---|
|
|
e0b50b620c | autenticazione e controllo (elementare) dei permessi. NON funziona come singleton. | 13 anni fa |
|
|
3e5d83ba62 | persistenza su file del dizionario associato al cookie | 13 anni fa |
|
|
58a525f2ed | aggiunto commento | 13 anni fa |
|
|
672176401b | bozza recupero/salvataggio uuid per autenticazione | 13 anni fa |
| @@ -1,2 +1,4 @@ | |||
| nanowsgi.wpu | |||
| auth_files/* | |||
| @@ -1,5 +1,7 @@ | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| # questa configurazione è solo per fare delle prove di gitlab | |||
| config = { | |||
| 'MYSQL_HOST': 'localhost', | |||
| 'MYSQL_USER': 'corso', | |||
| @@ -0,0 +1,16 @@ | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| from decorators import WSGISimpleAuth # decoratore ( singleton ) | |||
| auth = WSGISimpleAuth() | |||
| @auth.require( 'admin superadmin hyperadmin' ) | |||
| def application( environ, start_response ): | |||
| storage = environ['auth.storage'] | |||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | |||
| return [ | |||
| "<h1>GOSH BATMAN! HOW DID SHE DO THE IMPOSSIBLE?</h1>", | |||
| "<a href='/index.html'>index</a>" | |||
| ] | |||
| @@ -1,13 +1,15 @@ | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| from decorators import WSGITemplate # decoratore ( singleton ) | |||
| from decorators import WSGISimpleAuth, WSGITemplate # decoratore ( singleton ) | |||
| auth = WSGISimpleAuth() | |||
| wsgitmpl = WSGITemplate() | |||
| # | |||
| # esempio minimo di controller WSGI | |||
| # | |||
| @auth.require() | |||
| @wsgitmpl.template( 'template1.tmpl' ) | |||
| def application( environ, start_response ): | |||
| from pprint import pformat | |||
| @@ -16,4 +18,4 @@ def application( environ, start_response ): | |||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | |||
| return [ html, pformat( environ, width=132 ).replace('\n','<br>\n') ] # TODO: pformat..... ---> trasformarlo in un decoratore | |||
| return [ html, '<br><br><hr><br>', '<pre>', pformat( environ, width=132 ), '</pre>' ] | |||
| @@ -0,0 +1,17 @@ | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| from decorators import WSGISimpleAuth # decoratore ( singleton ) | |||
| auth = WSGISimpleAuth() | |||
| def application( environ, start_response ): | |||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | |||
| return [ | |||
| "<center>", | |||
| "<h1>FORBIDDEN</h1>", | |||
| "<img src='/attack_dog.jpg'>", | |||
| "<br><br><br>", | |||
| "Maybe you should go back to <a href='/index.html'>index</a>", | |||
| "</center>" | |||
| ] | |||
| @@ -0,0 +1,40 @@ | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| from decorators import WSGISimpleAuth # decoratore ( singleton ) | |||
| auth = WSGISimpleAuth() | |||
| @auth.require() | |||
| def application( environ, start_response ): | |||
| storage = environ['auth.storage'] | |||
| import cgi | |||
| post_environ = environ.copy() | |||
| post_environ['QUERY_STRING'] = '' | |||
| post = cgi.FieldStorage( | |||
| fp=environ['wsgi.input'], | |||
| environ=post_environ, | |||
| keep_blank_values=True | |||
| ) | |||
| if 'password' in post: | |||
| password = post['password'].value | |||
| storage['permissions'] = [ 'auth' ] | |||
| html = [ | |||
| "You are logged in, now you can see the <a href='/secret'>Secret</a> page.", | |||
| ] | |||
| else: | |||
| storage['timeout'] = -1 | |||
| html = [ | |||
| "<form method='POST'>" | |||
| "Password: <input type='password' value='' name='password' />", | |||
| "<input type='submit'>", | |||
| "</form>", | |||
| ] | |||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | |||
| return html | |||
| @@ -0,0 +1,19 @@ | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| from decorators import WSGISimpleAuth # decoratore ( singleton ) | |||
| auth = WSGISimpleAuth() | |||
| @auth.require() | |||
| def application( environ, start_response ): | |||
| storage = environ['auth.storage'] | |||
| storage['timeout'] = -1 | |||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | |||
| return [ | |||
| "<h5>Now you are logged out</h5>", | |||
| "<a href='/index.html'>index</a>" | |||
| ] | |||
| @@ -0,0 +1,18 @@ | |||
| #!/usr/bin/python | |||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | |||
| from decorators import WSGISimpleAuth # decoratore ( singleton ) | |||
| auth = WSGISimpleAuth() | |||
| @auth.require( 'auth' ) | |||
| def application( environ, start_response ): | |||
| storage = environ['auth.storage'] | |||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | |||
| return [ | |||
| "<h1>The secret code is:</h1>", | |||
| "<h6>''keep calm and carry on''</h6>" | |||
| "<h5>uuid:%s</h5>" % environ['auth.uuid'], | |||
| "<a href='/index.html'>index</a>" | |||
| ] | |||
| @@ -1,10 +1,29 @@ | |||
| #!/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 | |||
| @@ -76,7 +95,6 @@ class WSGIMySQL( object ): | |||
| self.__dict_cursor = MySQLdb.cursors.DictCursor | |||
| def __newconn( self, alias ): | |||
| import MySQLdb | |||
| @@ -125,3 +143,167 @@ class WSGIMySQL( object ): | |||
| 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 | |||
| } | |||
| """ | |||
| @@ -36,6 +36,11 @@ WSGIMySQL( | |||
| ), | |||
| ) | |||
| # | |||
| # inizializzazione sistema di autenticazione | |||
| # | |||
| from decorators import WSGIMySQL | |||
| WSGIMySQL( authdir='authfiles' ) | |||
| # | |||
| # importazione handler definiti esternamente | |||
| @@ -47,7 +52,7 @@ from router import WSGIRouter, WSGISmartRouter | |||
| # | |||
| def index( environ, start_response ): | |||
| start_response( '200 OK', [('content-type', 'text/html')] ) | |||
| return [ 'Index was here' ] | |||
| return [ 'Index was <a href="/index.html">here</a>' ] | |||
| # | |||
| # hello: esempio minimo di applicazione WSGI | |||
| @@ -71,12 +76,10 @@ def fallback( environ, start_response ): | |||
| # | |||
| # definiamo gli handler per le url che vogliamo servire | |||
| # | |||
| ################################################################################ from test_mysql import simple_mysql | |||
| handlers = ( | |||
| ( r'/', index ), | |||
| ( r'/hello', hello ), | |||
| ############################################################################ ( r'/mysql', simple_mysql ), | |||
| ) | |||
| @@ -78,8 +78,7 @@ class WSGISmartRouter(object): | |||
| tokens = [ token for token in environ['SCRIPT_URL'].split('/') if token ] | |||
| #for idx in range( 1, len( tokens ) + 1 ): # from shortest path to longest one | |||
| # from longest path to shortest one | |||
| for idx in range( len( tokens ) , 0, -1 ): | |||
| for idx in range( len( tokens ) , 0, -1 ): # from longest path to shortest one | |||
| module = os.path.normpath( self.cont_dir + '/'.join( tokens[:idx] ) + '.py' ) | |||
| args = tokens[idx:] | |||
| @@ -88,8 +87,6 @@ class WSGISmartRouter(object): | |||
| environ['router.args'] = args | |||
| if environ['REQUEST_METHOD'] == 'GET' and hasattr( handler, 'get' ): | |||
| # FIXME: forse è meglio eseguire QUI la start_response | |||
| # e compilare il template (solo per GET e POST) | |||
| return handler.get( environ, start_response ) | |||
| elif environ['REQUEST_METHOD'] == 'POST' and hasattr( handler, 'post' ): | |||
| @@ -12,6 +12,10 @@ | |||
| <li><a href="/demo">test autorouting</a></li> | |||
| <li><a href="/demo/due">test autorouting (longest path possible)</a></li> | |||
| <li><a href="/demo/sql">test sql access</a></li> | |||
| <li><a href="/login">login</a></li> | |||
| <li><a href="/secret">secret page</a></li> | |||
| <li><a href="/admin">admin page (you have no permissions)</a></li> | |||
| <li><a href="/logout">logout</a></li> | |||
| </ul> | |||
| <div style="position:fixed; bottom:0; left:10px;"> | |||
| <a rel="license" href="http://creativecommons.org/licenses/by/3.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by/3.0/88x31.png" /></a> | |||
| @@ -3,11 +3,10 @@ il valore di v1 è {{= v1 }} | |||
| <br> | |||
| mentre il valore di v2 è {{= v2 }} | |||
| <br> | |||
| questo è il risultato di un loop che genera 10 valori casuali:<br> | |||
| questo è il risultato di un loop che genera 10 valori interi casuali:<br> | |||
| {{ import random }} | |||
| {{ for x in range( 10 ): }} | |||
| <div style='background-color:{{ = random.choice( ('#fcc','yellow','#ccc','cyan',) ) }};'> | |||
| {{= random.randint( 10, 100 ) }} | |||
| <div style='background-color:#{{ = random.choice('89abcdef')*3 }};'> | |||
| {{= random.randint( 10, 10000 ) }} | |||
| </div> | |||
| {{ pass }} | |||