auth.define_tables(username=False, signature=False, api_tokens=True)
Then where appropriate replace
@auth.requires_login()
def test(): return 'hello %s' % auth.user.first_name
with
@auth.requires_login_or_token()
def test(): return 'hello %s' % auth.user.first_name
Now your users can go to
http://..../welcome/default/user/manage_tokens
create and expire tokens and call the decorated functions with
http://..../welcome/default/test?_token=<one-of-the-tokens>
The token will give access to the function (test in the example) as if the user were logged in.
This will make it easier for you to create API for your app and delegate to your users the job of creating and expiring their tokens.
This is an EXPERIMENTAL feature. It works but it may change.
Please test, and submit comments/suggestions.
Massimo
--
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+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
> To unsubscribe from this group and all its topics, send an email to web2py+unsubscribe@googlegroups.com <mailto:web2py+unsubscribe@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.
do you know what a jwt token is instead of just blindly bashing a solution?
Can you elaborate. I like the
current system (I wrote it ;-) but I was considering adding jwt.
The question is, is there duplication of functionality? Should jwt
replace the current token system? pros/cons?
auth.jwt_secret='secret_password'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))
@auth.requires_membership('masters')
def master_zone():
return dict(a='Hello, Master', b=request.vars.message)
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()