Sorry, I see signing the email scrambled the patch: > diff --git a/applications/welcome/controllers/default.py > b/applications/welcome/controllers/default.py > index c775603..e99ef0f 100644 > --- a/applications/welcome/controllers/default.py > +++ b/applications/welcome/controllers/default.py > @@ -57,4 +57,10 @@ def call(): > """ > return service() > > +@auth.requires_membership('masters') > +def master_zone(): > + return dict(a='Hello, Master', b=request.vars.message) > > +def get_master_code(): > + url = auth.get_authorization_url(a='welcome', c='default', > f='master_zone', vars=dict(message='Greetings')) > + return dict(a = A('copy this link', _href=url)) > diff --git a/applications/welcome/languages/es.py > b/applications/welcome/languages/es.py > index 7579cc3..54a57c6 100644 > --- a/applications/welcome/languages/es.py > +++ b/applications/welcome/languages/es.py > @@ -55,6 +55,7 @@ > 'Available Databases and Tables': 'Bases de datos y tablas disponibles', > 'Back': 'Atrás', > 'Buy this book': 'Compra este libro', > +"Buy web2py's book": "Buy web2py's book", > 'Cache': 'Caché', > 'cache': 'caché', > 'Cache Keys': 'Llaves de la Caché', > @@ -83,6 +84,7 @@ > 'compile': 'compilar', > 'compiled application removed': 'aplicación compilada eliminada', > 'Components and Plugins': 'Componentes y Plugins', > +'Config.ini': 'Config.ini', > 'contains': 'contiene', > 'Controller': 'Controlador', > 'Controllers': 'Controladores', > @@ -119,6 +121,7 @@ > 'Description': 'Descripción', > 'design': 'diseño', > 'DESIGN': 'DISEÑO', > +'Design': 'Design', > 'Design for': 'Diseño por', > 'detecting': 'detectando', > 'DISK': 'DISCO', > @@ -145,6 +148,7 @@ > 'End of impersonation': 'Fin de suplantación', > 'enter a number between %(min)g and %(max)g': 'introduzca un número > entre %(min)g y %(max)g', > 'enter a value': 'introduzca un valor', > +'Enter an integer between %(min)g and %(max)g': 'Enter an integer > between %(min)g and %(max)g', > 'enter an integer between %(min)g and %(max)g': 'introduzca un entero > entre %(min)g y %(max)g', > 'enter date and time as %(format)s': 'introduzca fecha y hora como > %(format)s', > 'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"', > @@ -177,6 +181,7 @@ > 'Groups': 'Grupos', > 'Hello World': 'Hola Mundo', > 'help': 'ayuda', > +'Helping web2py': 'Helping web2py', > 'Home': 'Inicio', > 'How did you get here?': '¿Cómo llegaste aquí?', > 'htmledit': 'htmledit', > @@ -217,6 +222,7 @@ > 'License for': 'Licencia para', > 'Live Chat': 'Chat en vivo', > 'loading...': 'cargando...', > +'Log In': 'Log In', > 'Logged in': 'Sesión iniciada', > 'Logged out': 'Sesión finalizada', > 'Login': 'Inicio de sesión', > @@ -256,6 +262,7 @@ > 'not in': 'no en', > 'Object or table name': 'Nombre del objeto o tabla', > 'Old password': 'Contraseña vieja', > +'Online book': 'Online book', > 'Online examples': 'Ejemplos en línea', > 'Or': 'O', > 'or import from csv file': 'o importar desde archivo CSV', > @@ -319,6 +326,7 @@ > 'Services': 'Servicios', > 'session expired': 'sesión expirada', > 'shell': 'terminal', > +'Sign Up': 'Sign Up', > 'site': 'sitio', > 'Size of cache:': 'Tamaño de la Caché:', > 'some files could not be removed': 'algunos archivos no pudieron ser > removidos', > diff --git a/applications/welcome/models/db.py > b/applications/welcome/models/db.py > index 606dc6f..3438621 100644 > --- a/applications/welcome/models/db.py > +++ b/applications/welcome/models/db.py > @@ -8,6 +8,7 @@ > ## if SSL/HTTPS is properly configured and you want all HTTP requests to > ## be redirected to HTTPS, uncomment the line below: > # request.requires_https() > +import random > > ## app configuration made easy. Look inside private/appconfig.ini > from gluon.contrib.appconfig import AppConfig > @@ -71,6 +72,8 @@ auth.settings.registration_requires_verification = False > auth.settings.registration_requires_approval = False > auth.settings.reset_password_requires_verification = True > > +auth.settings.jwt_secret = myconf.take('auth.jwt_secret') > + > ######################################################################### > ## Define your tables below (or better in another model file) for example > ## > diff --git a/applications/welcome/private/appconfig.ini > b/applications/welcome/private/appconfig.ini > index f45efbf..6db7544 100644 > --- a/applications/welcome/private/appconfig.ini > +++ b/applications/welcome/private/appconfig.ini > @@ -8,7 +8,7 @@ pool_size = 1 > > ; smtp address and credentials > [smtp] > -server = smtp.gmail.com:587 > +server = logging > sender = y...@gmail.com > login = username:password > > @@ -16,4 +16,7 @@ login = username:password > ; form styling > [forms] > formstyle = bootstrap3_inline > -separator = > \ No newline at end of file > +separator = > + > +[auth] > +jwt_secret = very_secret_even_random > diff --git a/gluon/tools.py b/gluon/tools.py > index 9d90b9a..da18795 100644 > --- a/gluon/tools.py > +++ b/gluon/tools.py > @@ -1171,6 +1171,8 @@ class Auth(object): > remember_me_form=True, > allow_basic_login=False, > allow_basic_login_only=False, > + jwt_algorithm='HS512', > + jwt_secret='', > on_failed_authentication=lambda x: redirect(x), > formstyle=None, > label_separator=None, > @@ -3724,6 +3726,16 @@ class Auth(object): > raise HTTP(403, 'ACCESS DENIED') > return self.messages.access_denied > > + def get_authorization_url(self, a, c, f, vars): > + keycode = self.get_authorization_code(a, c, f, vars) > + return URL(a=a, c=c, f=f, vars=dict(_keycode=keycode)) > + > + def get_authorization_code(self, a, c, f, vars): > + import jwt > + payload = {'_application':a, '_controller':c, '_function':f} > + payload.update(vars) > + return jwt.encode(payload, self.settings.jwt_secret, > algorithm=self.settings.jwt_algorithm) > + > def requires(self, condition, requires_login=True, otherwise=None): > """ > Decorator that prevents access to action if not logged in > @@ -3732,27 +3744,47 @@ class Auth(object): > def decorator(action): > > def f(*a, **b): > + r = current.request > > basic_allowed, basic_accepted, user = self.basic() > user = user or self.user > - if requires_login: > + > + authorization_failed = False > + if self.settings.jwt_secret and r.vars._keycode: > + import jwt > + try: > + encoded = r.vars._keycode > + payload = jwt.decode(encoded, > self.settings.jwt_secret, algorithms=[self.settings.jwt_algorithm]) > + except (jwt.DecodeError, jwt.InvalidTokenError): > + authorization_failed = True > + if not authorization_failed: > + acf = tuple(payload.pop(s) for s in > ('_application', '_controller', '_function')) > + if acf != (r.application, r.controller, > r.function): > + authorization_failed = True > + r.vars.clear() > + r.vars.update(payload) > + condition = True > + elif requires_login: > if not user: > - if current.request.ajax: > - raise HTTP(401, > self.messages.ajax_failed_authentication) > - elif not otherwise is None: > - if callable(otherwise): > - return otherwise() > - redirect(otherwise) > - elif self.settings.allow_basic_login_only or \ > - basic_accepted or > current.request.is_restful: > - raise HTTP(403, "Not authorized") > - else: > - next = self.here() > - current.session.flash = > current.response.flash > - return call_or_redirect( > - self.settings.on_failed_authentication, > - self.settings.login_url + > - '?_next=' + urllib.quote(next)) > + authorization_failed = True > + > + if authorization_failed: > + if r.ajax: > + raise HTTP(401, > self.messages.ajax_failed_authentication) > + elif not otherwise is None: > + if callable(otherwise): > + return otherwise() > + redirect(otherwise) > + elif self.settings.allow_basic_login_only or \ > + basic_accepted or r.is_restful: > + raise HTTP(403, "Not authorized") > + else: > + next = self.here() > + current.session.flash = current.response.flash > + return call_or_redirect( > + self.settings.on_failed_authentication, > + self.settings.login_url + > + '?_next=' + urllib.quote(next)) > > if callable(condition): > flag = condition()
-- Resources: - http://web2py.com - http://web2py.com/book (Documentation) - http://github.com/web2py/web2py (Source code) - https://code.google.com/p/web2py/issues/list (Report Issues) --- You received this message because you are subscribed to the Google Groups "web2py-users" group. To unsubscribe from this group and stop receiving emails from it, send an email to web2py+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
diff --git a/applications/welcome/controllers/default.py b/applications/welcome/controllers/default.py index c775603..e99ef0f 100644 --- a/applications/welcome/controllers/default.py +++ b/applications/welcome/controllers/default.py @@ -57,4 +57,10 @@ def call(): """ return service() +@auth.requires_membership('masters') +def master_zone(): + return dict(a='Hello, Master', b=request.vars.message) +def get_master_code(): + url = auth.get_authorization_url(a='welcome', c='default', f='master_zone', vars=dict(message='Greetings')) + return dict(a = A('copy this link', _href=url)) diff --git a/applications/welcome/languages/es.py b/applications/welcome/languages/es.py index 7579cc3..54a57c6 100644 --- a/applications/welcome/languages/es.py +++ b/applications/welcome/languages/es.py @@ -55,6 +55,7 @@ 'Available Databases and Tables': 'Bases de datos y tablas disponibles', 'Back': 'Atrás', 'Buy this book': 'Compra este libro', +"Buy web2py's book": "Buy web2py's book", 'Cache': 'Caché', 'cache': 'caché', 'Cache Keys': 'Llaves de la Caché', @@ -83,6 +84,7 @@ 'compile': 'compilar', 'compiled application removed': 'aplicación compilada eliminada', 'Components and Plugins': 'Componentes y Plugins', +'Config.ini': 'Config.ini', 'contains': 'contiene', 'Controller': 'Controlador', 'Controllers': 'Controladores', @@ -119,6 +121,7 @@ 'Description': 'Descripción', 'design': 'diseño', 'DESIGN': 'DISEÃO', +'Design': 'Design', 'Design for': 'Diseño por', 'detecting': 'detectando', 'DISK': 'DISCO', @@ -145,6 +148,7 @@ 'End of impersonation': 'Fin de suplantación', 'enter a number between %(min)g and %(max)g': 'introduzca un número entre %(min)g y %(max)g', 'enter a value': 'introduzca un valor', +'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g', 'enter an integer between %(min)g and %(max)g': 'introduzca un entero entre %(min)g y %(max)g', 'enter date and time as %(format)s': 'introduzca fecha y hora como %(format)s', 'Error logs for "%(app)s"': 'Bitácora de errores en "%(app)s"', @@ -177,6 +181,7 @@ 'Groups': 'Grupos', 'Hello World': 'Hola Mundo', 'help': 'ayuda', +'Helping web2py': 'Helping web2py', 'Home': 'Inicio', 'How did you get here?': '¿Cómo llegaste aquÃ?', 'htmledit': 'htmledit', @@ -217,6 +222,7 @@ 'License for': 'Licencia para', 'Live Chat': 'Chat en vivo', 'loading...': 'cargando...', +'Log In': 'Log In', 'Logged in': 'Sesión iniciada', 'Logged out': 'Sesión finalizada', 'Login': 'Inicio de sesión', @@ -256,6 +262,7 @@ 'not in': 'no en', 'Object or table name': 'Nombre del objeto o tabla', 'Old password': 'Contraseña vieja', +'Online book': 'Online book', 'Online examples': 'Ejemplos en lÃnea', 'Or': 'O', 'or import from csv file': 'o importar desde archivo CSV', @@ -319,6 +326,7 @@ 'Services': 'Servicios', 'session expired': 'sesión expirada', 'shell': 'terminal', +'Sign Up': 'Sign Up', 'site': 'sitio', 'Size of cache:': 'Tamaño de la Caché:', 'some files could not be removed': 'algunos archivos no pudieron ser removidos', diff --git a/applications/welcome/models/db.py b/applications/welcome/models/db.py index 606dc6f..3438621 100644 --- a/applications/welcome/models/db.py +++ b/applications/welcome/models/db.py @@ -8,6 +8,7 @@ ## if SSL/HTTPS is properly configured and you want all HTTP requests to ## be redirected to HTTPS, uncomment the line below: # request.requires_https() +import random ## app configuration made easy. Look inside private/appconfig.ini from gluon.contrib.appconfig import AppConfig @@ -71,6 +72,8 @@ auth.settings.registration_requires_verification = False auth.settings.registration_requires_approval = False auth.settings.reset_password_requires_verification = True +auth.settings.jwt_secret = myconf.take('auth.jwt_secret') + ######################################################################### ## Define your tables below (or better in another model file) for example ## diff --git a/applications/welcome/private/appconfig.ini b/applications/welcome/private/appconfig.ini index f45efbf..6db7544 100644 --- a/applications/welcome/private/appconfig.ini +++ b/applications/welcome/private/appconfig.ini @@ -8,7 +8,7 @@ pool_size = 1 ; smtp address and credentials [smtp] -server = smtp.gmail.com:587 +server = logging sender = y...@gmail.com login = username:password @@ -16,4 +16,7 @@ login = username:password ; form styling [forms] formstyle = bootstrap3_inline -separator = \ No newline at end of file +separator = + +[auth] +jwt_secret = very_secret_even_random diff --git a/gluon/tools.py b/gluon/tools.py index 9d90b9a..da18795 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -1171,6 +1171,8 @@ class Auth(object): remember_me_form=True, allow_basic_login=False, allow_basic_login_only=False, + jwt_algorithm='HS512', + jwt_secret='', on_failed_authentication=lambda x: redirect(x), formstyle=None, label_separator=None, @@ -3724,6 +3726,16 @@ class Auth(object): raise HTTP(403, 'ACCESS DENIED') return self.messages.access_denied + def get_authorization_url(self, a, c, f, vars): + keycode = self.get_authorization_code(a, c, f, vars) + return URL(a=a, c=c, f=f, vars=dict(_keycode=keycode)) + + def get_authorization_code(self, a, c, f, vars): + import jwt + payload = {'_application':a, '_controller':c, '_function':f} + payload.update(vars) + return jwt.encode(payload, self.settings.jwt_secret, algorithm=self.settings.jwt_algorithm) + def requires(self, condition, requires_login=True, otherwise=None): """ Decorator that prevents access to action if not logged in @@ -3732,27 +3744,47 @@ class Auth(object): def decorator(action): def f(*a, **b): + r = current.request basic_allowed, basic_accepted, user = self.basic() user = user or self.user - if requires_login: + + authorization_failed = False + if self.settings.jwt_secret and r.vars._keycode: + import jwt + try: + encoded = r.vars._keycode + payload = jwt.decode(encoded, self.settings.jwt_secret, algorithms=[self.settings.jwt_algorithm]) + except (jwt.DecodeError, jwt.InvalidTokenError): + authorization_failed = True + if not authorization_failed: + acf = tuple(payload.pop(s) for s in ('_application', '_controller', '_function')) + if acf != (r.application, r.controller, r.function): + authorization_failed = True + r.vars.clear() + r.vars.update(payload) + condition = True + elif requires_login: if not user: - if current.request.ajax: - raise HTTP(401, self.messages.ajax_failed_authentication) - elif not otherwise is None: - if callable(otherwise): - return otherwise() - redirect(otherwise) - elif self.settings.allow_basic_login_only or \ - basic_accepted or current.request.is_restful: - raise HTTP(403, "Not authorized") - else: - next = self.here() - current.session.flash = current.response.flash - return call_or_redirect( - self.settings.on_failed_authentication, - self.settings.login_url + - '?_next=' + urllib.quote(next)) + authorization_failed = True + + if authorization_failed: + if r.ajax: + raise HTTP(401, self.messages.ajax_failed_authentication) + elif not otherwise is None: + if callable(otherwise): + return otherwise() + redirect(otherwise) + elif self.settings.allow_basic_login_only or \ + basic_accepted or r.is_restful: + raise HTTP(403, "Not authorized") + else: + next = self.here() + current.session.flash = current.response.flash + return call_or_redirect( + self.settings.on_failed_authentication, + self.settings.login_url + + '?_next=' + urllib.quote(next)) if callable(condition): flag = condition()