Comparar commits

...

4 Commits
master ... auth

Se han modificado 14 ficheros con 315 adiciones y 14 borrados
Dividir vista
  1. +2
    -0
      .gitignore
  2. +2
    -0
      config.py
  3. +16
    -0
      controllers/admin.py
  4. +4
    -2
      controllers/demo/due.py
  5. +17
    -0
      controllers/forbidden.py
  6. +40
    -0
      controllers/login.py
  7. +19
    -0
      controllers/logout.py
  8. +18
    -0
      controllers/secret.py
  9. +183
    -1
      decorators.py
  10. +6
    -3
      dispatch_wsgi.py
  11. +1
    -4
      router.py
  12. BIN
     
  13. +4
    -0
      static/index.html
  14. +3
    -4
      views/template1.tmpl

+ 2
- 0
.gitignore Ver fichero

@@ -1,2 +1,4 @@
nanowsgi.wpu
auth_files/*



+ 2
- 0
config.py Ver fichero

@@ -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',


+ 16
- 0
controllers/admin.py Ver fichero

@@ -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>"
]

+ 4
- 2
controllers/demo/due.py Ver fichero

@@ -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>' ]

+ 17
- 0
controllers/forbidden.py Ver fichero

@@ -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>"
]

+ 40
- 0
controllers/login.py Ver fichero

@@ -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



+ 19
- 0
controllers/logout.py Ver fichero

@@ -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>"
]


+ 18
- 0
controllers/secret.py Ver fichero

@@ -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>"
]

+ 183
- 1
decorators.py Ver fichero

@@ -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
}
"""

+ 6
- 3
dispatch_wsgi.py Ver fichero

@@ -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 ),
)




+ 1
- 4
router.py Ver fichero

@@ -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' ):



+ 4
- 0
static/index.html Ver fichero

@@ -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
- 4
views/template1.tmpl Ver fichero

@@ -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 }}


Cargando…
Cancelar
Guardar