| Аутор | SHA1 | Порука | Датум |
|---|---|---|---|
|
|
e0b50b620c | autenticazione e controllo (elementare) dei permessi. NON funziona come singleton. | пре 13 година |
|
|
3e5d83ba62 | persistenza su file del dizionario associato al cookie | пре 13 година |
|
|
58a525f2ed | aggiunto commento | пре 13 година |
|
|
672176401b | bozza recupero/salvataggio uuid per autenticazione | пре 13 година |
| @@ -1,2 +1,4 @@ | |||||
| nanowsgi.wpu | nanowsgi.wpu | ||||
| auth_files/* | |||||
| @@ -1,5 +1,7 @@ | |||||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | ||||
| # questa configurazione è solo per fare delle prove di gitlab | |||||
| config = { | config = { | ||||
| 'MYSQL_HOST': 'localhost', | 'MYSQL_HOST': 'localhost', | ||||
| 'MYSQL_USER': 'corso', | '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 | #!/usr/bin/python | ||||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | # -*- 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() | wsgitmpl = WSGITemplate() | ||||
| # | # | ||||
| # esempio minimo di controller WSGI | # esempio minimo di controller WSGI | ||||
| # | # | ||||
| @auth.require() | |||||
| @wsgitmpl.template( 'template1.tmpl' ) | @wsgitmpl.template( 'template1.tmpl' ) | ||||
| def application( environ, start_response ): | def application( environ, start_response ): | ||||
| from pprint import pformat | from pprint import pformat | ||||
| @@ -16,4 +18,4 @@ def application( environ, start_response ): | |||||
| start_response( '200 OK', [('content-type', 'text/html; charset=utf-8')] ) | 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 | #!/usr/bin/python | ||||
| # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | # -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- | ||||
| ## all | |||||
| from singleton import Singleton | from singleton import Singleton | ||||
| ## WSGITemplate | |||||
| from template import render | from template import render | ||||
| from functools import partial | 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 ): | class WSGITemplate( object ): | ||||
| __metaclass__ = Singleton | __metaclass__ = Singleton | ||||
| @@ -76,7 +95,6 @@ class WSGIMySQL( object ): | |||||
| self.__dict_cursor = MySQLdb.cursors.DictCursor | self.__dict_cursor = MySQLdb.cursors.DictCursor | ||||
| def __newconn( self, alias ): | def __newconn( self, alias ): | ||||
| import MySQLdb | import MySQLdb | ||||
| @@ -125,3 +143,167 @@ class WSGIMySQL( object ): | |||||
| for dsndict in self.__dsn.items(): | for dsndict in self.__dsn.items(): | ||||
| for conn in dsndict['pool']: | for conn in dsndict['pool']: | ||||
| conn.close() | 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 | # importazione handler definiti esternamente | ||||
| @@ -47,7 +52,7 @@ from router import WSGIRouter, WSGISmartRouter | |||||
| # | # | ||||
| def index( environ, start_response ): | def index( environ, start_response ): | ||||
| start_response( '200 OK', [('content-type', 'text/html')] ) | 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 | # hello: esempio minimo di applicazione WSGI | ||||
| @@ -71,12 +76,10 @@ def fallback( environ, start_response ): | |||||
| # | # | ||||
| # definiamo gli handler per le url che vogliamo servire | # definiamo gli handler per le url che vogliamo servire | ||||
| # | # | ||||
| ################################################################################ from test_mysql import simple_mysql | |||||
| handlers = ( | handlers = ( | ||||
| ( r'/', index ), | ( r'/', index ), | ||||
| ( r'/hello', hello ), | ( 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 ] | 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 | #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' ) | module = os.path.normpath( self.cont_dir + '/'.join( tokens[:idx] ) + '.py' ) | ||||
| args = tokens[idx:] | args = tokens[idx:] | ||||
| @@ -88,8 +87,6 @@ class WSGISmartRouter(object): | |||||
| environ['router.args'] = args | environ['router.args'] = args | ||||
| if environ['REQUEST_METHOD'] == 'GET' and hasattr( handler, 'get' ): | 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 ) | return handler.get( environ, start_response ) | ||||
| elif environ['REQUEST_METHOD'] == 'POST' and hasattr( handler, 'post' ): | 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">test autorouting</a></li> | ||||
| <li><a href="/demo/due">test autorouting (longest path possible)</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="/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> | </ul> | ||||
| <div style="position:fixed; bottom:0; left:10px;"> | <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> | <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> | <br> | ||||
| mentre il valore di v2 è {{= v2 }} | mentre il valore di v2 è {{= v2 }} | ||||
| <br> | <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 }} | {{ import random }} | ||||
| {{ for x in range( 10 ): }} | {{ 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> | </div> | ||||
| {{ pass }} | {{ pass }} | ||||