Auth refactor, extracted many methods into a base class for more generic auth mechanisms.
Partially addresses #1526 Includes a solution for IS_LOWER and IS_UPPER validator problems I mentioned in #1353
This commit is contained in:
+1020
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ from .test_contribs import *
|
|||||||
from .test_routes import *
|
from .test_routes import *
|
||||||
from .test_router import *
|
from .test_router import *
|
||||||
from .test_validators import *
|
from .test_validators import *
|
||||||
|
from .test_authapi import *
|
||||||
from .test_tools import *
|
from .test_tools import *
|
||||||
from .test_utils import *
|
from .test_utils import *
|
||||||
from .test_serializers import *
|
from .test_serializers import *
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Unit tests for authapi """
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from gluon.globals import Request, Response, Session
|
||||||
|
from gluon.languages import translator
|
||||||
|
from gluon.dal import DAL, Field
|
||||||
|
from gluon.authapi import AuthAPI
|
||||||
|
from gluon.storage import Storage
|
||||||
|
from gluon._compat import to_bytes, to_native, add_charset
|
||||||
|
|
||||||
|
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||||
|
|
||||||
|
|
||||||
|
class TestAuthAPI(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.request = Request(env={})
|
||||||
|
self.request.application = 'a'
|
||||||
|
self.request.controller = 'c'
|
||||||
|
self.request.function = 'f'
|
||||||
|
self.request.folder = 'applications/admin'
|
||||||
|
self.response = Response()
|
||||||
|
self.session = Session()
|
||||||
|
T = translator('', 'en')
|
||||||
|
self.session.connect(self.request, self.response)
|
||||||
|
from gluon.globals import current
|
||||||
|
self.current = current
|
||||||
|
self.current.request = self.request
|
||||||
|
self.current.response = self.response
|
||||||
|
self.current.session = self.session
|
||||||
|
self.current.T = T
|
||||||
|
self.db = DAL(DEFAULT_URI, check_reserved=['all'])
|
||||||
|
self.auth = AuthAPI(self.db)
|
||||||
|
self.auth.define_tables(username=True, signature=False)
|
||||||
|
# Create a user
|
||||||
|
self.auth.table_user().validate_and_insert(first_name='Bart',
|
||||||
|
last_name='Simpson',
|
||||||
|
username='bart',
|
||||||
|
email='bart@simpson.com',
|
||||||
|
password='bart_password',
|
||||||
|
registration_key='',
|
||||||
|
registration_id=''
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
def test_login(self):
|
||||||
|
result = self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
self.assertTrue(result['user']['email'] == 'bart@simpson.com')
|
||||||
|
self.auth.logout()
|
||||||
|
self.assertFalse(self.auth.is_logged_in())
|
||||||
|
self.auth.settings.username_case_sensitive = False
|
||||||
|
result = self.auth.login(**{'username': 'BarT', 'password': 'bart_password'})
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
|
||||||
|
def test_logout(self):
|
||||||
|
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
result = self.auth.logout()
|
||||||
|
self.assertTrue(not self.auth.is_logged_in())
|
||||||
|
self.assertTrue(result['user'] is None)
|
||||||
|
|
||||||
|
def test_register(self):
|
||||||
|
self.auth.settings.login_after_registration = True
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'lisa',
|
||||||
|
'first_name': 'Lisa',
|
||||||
|
'last_name': 'Simpson',
|
||||||
|
'email': 'lisa@simpson.com',
|
||||||
|
'password': 'lisa_password'
|
||||||
|
})
|
||||||
|
self.assertTrue(result['user']['email'] == 'lisa@simpson.com')
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
with self.assertRaises(AssertionError): # Can't register if you're logged in
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'lisa',
|
||||||
|
'first_name': 'Lisa',
|
||||||
|
'last_name': 'Simpson',
|
||||||
|
'email': 'lisa@simpson.com',
|
||||||
|
'password': 'lisa_password'
|
||||||
|
})
|
||||||
|
self.auth.logout()
|
||||||
|
self.auth.settings.login_after_registration = False
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'barney',
|
||||||
|
'first_name': 'Barney',
|
||||||
|
'last_name': 'Gumble',
|
||||||
|
'email': 'barney@simpson.com',
|
||||||
|
'password': 'barney_password'
|
||||||
|
})
|
||||||
|
self.assertTrue(result['user']['email'] == 'barney@simpson.com')
|
||||||
|
self.assertFalse(self.auth.is_logged_in())
|
||||||
|
self.auth.settings.login_userfield = 'email'
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'lisa',
|
||||||
|
'first_name': 'Lisa',
|
||||||
|
'last_name': 'Simpson',
|
||||||
|
'email': 'lisa@simpson.com',
|
||||||
|
'password': 'lisa_password'
|
||||||
|
})
|
||||||
|
self.assertTrue(result['errors']['email'] == self.auth.messages.email_taken)
|
||||||
|
self.assertTrue(result['user'] is None)
|
||||||
|
self.auth.settings.registration_requires_verification = True
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'homer',
|
||||||
|
'first_name': 'Homer',
|
||||||
|
'last_name': 'Simpson',
|
||||||
|
'email': 'homer@simpson.com',
|
||||||
|
'password': 'homer_password'
|
||||||
|
})
|
||||||
|
self.assertTrue('key' in result['user'])
|
||||||
|
|
||||||
|
def test_profile(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
# We are not logged in
|
||||||
|
self.auth.profile()
|
||||||
|
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
result = self.auth.profile(email='bartolo@simpson.com')
|
||||||
|
self.assertTrue(result['user']['email'] == 'bartolo@simpson.com')
|
||||||
|
self.assertTrue(self.auth.table_user()[result['user']['id']].email == 'bartolo@simpson.com')
|
||||||
|
|
||||||
|
def test_change_password(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
# We are not logged in
|
||||||
|
self.auth.change_password()
|
||||||
|
self.auth.login(**{'username': 'bart', 'password': 'bart_password'})
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
|
||||||
|
self.auth.logout()
|
||||||
|
self.assertTrue(not self.auth.is_logged_in())
|
||||||
|
self.auth.login(username='bart', password='1234')
|
||||||
|
self.assertTrue(self.auth.is_logged_in())
|
||||||
|
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='5678')
|
||||||
|
self.assertTrue('new_password2' in result['errors'])
|
||||||
|
result = self.auth.change_password(old_password='bart_password', new_password='1234', new_password2='1234')
|
||||||
|
self.assertTrue('old_password' in result['errors'])
|
||||||
|
|
||||||
|
def test_verify_key(self):
|
||||||
|
self.auth.settings.registration_requires_verification = True
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'homer',
|
||||||
|
'first_name': 'Homer',
|
||||||
|
'last_name': 'Simpson',
|
||||||
|
'email': 'homer@simpson.com',
|
||||||
|
'password': 'homer_password'
|
||||||
|
})
|
||||||
|
self.assertTrue('key' in result['user'])
|
||||||
|
homer_id = result['user']['id']
|
||||||
|
homers_key = result['user']['key']
|
||||||
|
result = self.auth.verify_key(key=None)
|
||||||
|
self.assertTrue(result['errors'] is not None)
|
||||||
|
result = self.auth.verify_key(key='12345')
|
||||||
|
self.assertTrue(result['errors'] is not None)
|
||||||
|
result = self.auth.verify_key(key=homers_key)
|
||||||
|
self.assertTrue(result['errors'] is None)
|
||||||
|
self.assertEqual(self.auth.table_user()[homer_id].registration_key, '')
|
||||||
|
self.auth.settings.registration_requires_approval = True
|
||||||
|
result = self.auth.register(**{
|
||||||
|
'username': 'lisa',
|
||||||
|
'first_name': 'Lisa',
|
||||||
|
'last_name': 'Simpson',
|
||||||
|
'email': 'lisa@simpson.com',
|
||||||
|
'password': 'lisa_password'
|
||||||
|
})
|
||||||
|
lisa_id = result['user']['id']
|
||||||
|
result = self.auth.verify_key(key=result['user']['key'])
|
||||||
|
self.assertEqual(self.auth.table_user()[lisa_id].registration_key, 'pending')
|
||||||
@@ -702,15 +702,19 @@ class TestValidators(unittest.TestCase):
|
|||||||
|
|
||||||
def test_IS_LOWER(self):
|
def test_IS_LOWER(self):
|
||||||
rtn = IS_LOWER()('ABC')
|
rtn = IS_LOWER()('ABC')
|
||||||
|
self.assertEqual(rtn, ('abc', None))
|
||||||
|
rtn = IS_LOWER()(b'ABC')
|
||||||
self.assertEqual(rtn, (b'abc', None))
|
self.assertEqual(rtn, (b'abc', None))
|
||||||
rtn = IS_LOWER()('Ñ')
|
rtn = IS_LOWER()('Ñ')
|
||||||
self.assertEqual(rtn, (b'\xc3\xb1', None))
|
self.assertEqual(rtn, ('ñ', None))
|
||||||
|
|
||||||
def test_IS_UPPER(self):
|
def test_IS_UPPER(self):
|
||||||
rtn = IS_UPPER()('abc')
|
rtn = IS_UPPER()('abc')
|
||||||
|
self.assertEqual(rtn, ('ABC', None))
|
||||||
|
rtn = IS_UPPER()(b'abc')
|
||||||
self.assertEqual(rtn, (b'ABC', None))
|
self.assertEqual(rtn, (b'ABC', None))
|
||||||
rtn = IS_UPPER()('ñ')
|
rtn = IS_UPPER()('ñ')
|
||||||
self.assertEqual(rtn, (b'\xc3\x91', None))
|
self.assertEqual(rtn, ('Ñ', None))
|
||||||
|
|
||||||
def test_IS_SLUG(self):
|
def test_IS_SLUG(self):
|
||||||
rtn = IS_SLUG()('abc123')
|
rtn = IS_SLUG()('abc123')
|
||||||
|
|||||||
+108
-690
@@ -36,6 +36,7 @@ import json
|
|||||||
|
|
||||||
from email import message_from_string
|
from email import message_from_string
|
||||||
|
|
||||||
|
from gluon.authapi import AuthAPI
|
||||||
from gluon.contenttype import contenttype
|
from gluon.contenttype import contenttype
|
||||||
from gluon.storage import Storage, StorageList, Settings, Messages
|
from gluon.storage import Storage, StorageList, Settings, Messages
|
||||||
from gluon.utils import web2py_uuid, compare
|
from gluon.utils import web2py_uuid, compare
|
||||||
@@ -55,7 +56,7 @@ Field = DAL.Field
|
|||||||
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
|
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
|
||||||
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
|
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
|
||||||
|
|
||||||
### mind there are two loggers here (logger and crud.settings.logger)!
|
# mind there are two loggers here (logger and crud.settings.logger)!
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
DEFAULT = lambda: None
|
DEFAULT = lambda: None
|
||||||
@@ -1318,9 +1319,9 @@ class AuthJWT(object):
|
|||||||
while sticking to a somewhat-stable Auth API.
|
while sticking to a somewhat-stable Auth API.
|
||||||
"""
|
"""
|
||||||
# TODO: Check the following comment
|
# TODO: Check the following comment
|
||||||
## is the following safe or should we use
|
# is the following safe or should we use
|
||||||
## calendar.timegm(datetime.datetime.utcnow().timetuple())
|
# calendar.timegm(datetime.datetime.utcnow().timetuple())
|
||||||
## result seem to be the same (seconds since epoch, in UTC)
|
# result seem to be the same (seconds since epoch, in UTC)
|
||||||
now = time.mktime(datetime.datetime.now().timetuple())
|
now = time.mktime(datetime.datetime.now().timetuple())
|
||||||
expires = now + self.expiration
|
expires = now + self.expiration
|
||||||
payload = dict(
|
payload = dict(
|
||||||
@@ -1484,172 +1485,119 @@ class AuthJWT(object):
|
|||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
class Auth(object):
|
|
||||||
|
|
||||||
default_settings = dict(
|
class Auth(AuthAPI):
|
||||||
hideerror=False,
|
|
||||||
password_min_length=4,
|
default_settings = dict(AuthAPI.default_settings,
|
||||||
cas_maps=None,
|
|
||||||
reset_password_requires_verification=False,
|
|
||||||
registration_requires_verification=False,
|
|
||||||
registration_requires_approval=False,
|
|
||||||
bulk_register_enabled=False,
|
|
||||||
login_after_registration=False,
|
|
||||||
login_after_password_change=True,
|
|
||||||
alternate_requires_registration=False,
|
|
||||||
create_user_groups="user_%(id)s",
|
|
||||||
everybody_group_id=None,
|
|
||||||
manager_actions={},
|
|
||||||
auth_manager_role=None,
|
|
||||||
two_factor_authentication_group=None,
|
|
||||||
auth_two_factor_enabled=False,
|
|
||||||
auth_two_factor_tries_left=3,
|
|
||||||
login_captcha=None,
|
|
||||||
register_captcha=None,
|
|
||||||
pre_registration_div=None,
|
|
||||||
retrieve_username_captcha=None,
|
|
||||||
retrieve_password_captcha=None,
|
|
||||||
captcha=None,
|
|
||||||
prevent_open_redirect_attacks=True,
|
|
||||||
prevent_password_reset_attacks=True,
|
|
||||||
expiration=3600, # one hour
|
|
||||||
long_expiration=3600 * 30 * 24, # one month
|
|
||||||
remember_me_form=True,
|
|
||||||
allow_basic_login=False,
|
allow_basic_login=False,
|
||||||
allow_basic_login_only=False,
|
allow_basic_login_only=False,
|
||||||
on_failed_authentication=lambda x: redirect(x),
|
|
||||||
formstyle=None,
|
|
||||||
label_separator=None,
|
|
||||||
logging_enabled=True,
|
|
||||||
allow_delete_accounts=False,
|
allow_delete_accounts=False,
|
||||||
password_field='password',
|
alternate_requires_registration=False,
|
||||||
table_user_name='auth_user',
|
auth_manager_role=None,
|
||||||
table_group_name='auth_group',
|
auth_two_factor_enabled=False,
|
||||||
table_membership_name='auth_membership',
|
auth_two_factor_tries_left=3,
|
||||||
table_permission_name='auth_permission',
|
bulk_register_enabled=False,
|
||||||
table_event_name='auth_event',
|
captcha=None,
|
||||||
|
cas_maps=None,
|
||||||
|
client_side=True,
|
||||||
|
formstyle=None,
|
||||||
|
hideerror=False,
|
||||||
|
label_separator=None,
|
||||||
|
login_after_password_change=True,
|
||||||
|
login_after_registration=False,
|
||||||
|
login_captcha=None,
|
||||||
|
long_expiration=3600 * 30 * 24, # one month
|
||||||
|
mailer=None,
|
||||||
|
manager_actions={},
|
||||||
|
multi_login=False,
|
||||||
|
on_failed_authentication=lambda x: redirect(x),
|
||||||
|
pre_registration_div=None,
|
||||||
|
prevent_open_redirect_attacks=True,
|
||||||
|
prevent_password_reset_attacks=True,
|
||||||
|
profile_fields=None,
|
||||||
|
register_captcha=None,
|
||||||
|
register_fields=None,
|
||||||
|
register_verify_password=True,
|
||||||
|
remember_me_form=True,
|
||||||
|
reset_password_requires_verification=False,
|
||||||
|
retrieve_password_captcha=None,
|
||||||
|
retrieve_username_captcha=None,
|
||||||
|
showid=False,
|
||||||
|
table_cas=None,
|
||||||
table_cas_name='auth_cas',
|
table_cas_name='auth_cas',
|
||||||
table_token_name='auth_token',
|
table_event=None,
|
||||||
table_user=None,
|
|
||||||
table_group=None,
|
table_group=None,
|
||||||
table_membership=None,
|
table_membership=None,
|
||||||
table_permission=None,
|
table_permission=None,
|
||||||
table_event=None,
|
table_token_name='auth_token',
|
||||||
table_cas=None,
|
table_user=None,
|
||||||
showid=False,
|
two_factor_authentication_group=None,
|
||||||
use_username=False,
|
|
||||||
login_email_validate=True,
|
|
||||||
login_userfield=None,
|
|
||||||
multi_login=False,
|
|
||||||
logout_onlogout=None,
|
|
||||||
register_fields=None,
|
|
||||||
register_verify_password=True,
|
|
||||||
profile_fields=None,
|
|
||||||
email_case_sensitive=True,
|
|
||||||
username_case_sensitive=True,
|
|
||||||
update_fields=['email'],
|
update_fields=['email'],
|
||||||
ondelete="CASCADE",
|
wiki=Settings()
|
||||||
client_side=True,
|
|
||||||
renew_session_onlogin=True,
|
|
||||||
renew_session_onlogout=True,
|
|
||||||
keep_session_onlogin=True,
|
|
||||||
keep_session_onlogout=False,
|
|
||||||
wiki=Settings(),
|
|
||||||
)
|
)
|
||||||
# ## these are messages that can be customized
|
# ## these are messages that can be customized
|
||||||
default_messages = dict(
|
default_messages = dict(AuthAPI.default_messages,
|
||||||
login_button='Log In',
|
|
||||||
register_button='Sign Up',
|
|
||||||
password_reset_button='Request reset password',
|
|
||||||
password_change_button='Change password',
|
|
||||||
profile_save_button='Apply changes',
|
|
||||||
submit_button='Submit',
|
|
||||||
verify_password='Verify Password',
|
|
||||||
delete_label='Check to delete',
|
|
||||||
function_disabled='Function disabled',
|
|
||||||
access_denied='Insufficient privileges',
|
access_denied='Insufficient privileges',
|
||||||
registration_verifying='Registration needs verification',
|
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete the process',
|
||||||
registration_pending='Registration is pending approval',
|
bulk_invite_subject='Invitation to join %(site)s',
|
||||||
email_taken='This email already has an account',
|
delete_label='Check to delete',
|
||||||
invalid_username='Invalid username',
|
|
||||||
username_taken='Username already taken',
|
|
||||||
login_disabled='Login disabled by administrator',
|
|
||||||
logged_in='Logged in',
|
|
||||||
email_sent='Email sent',
|
email_sent='Email sent',
|
||||||
unable_to_send_email='Unable to send email',
|
|
||||||
email_verified='Email verified',
|
email_verified='Email verified',
|
||||||
logged_out='Logged out',
|
function_disabled='Function disabled',
|
||||||
registration_successful='Registration successful',
|
impersonate_log='User %(id)s is impersonating %(other_id)s',
|
||||||
invalid_email='Invalid email',
|
invalid_reset_password='Invalid reset password',
|
||||||
invalid_login='Invalid login',
|
|
||||||
invalid_user='Invalid user',
|
|
||||||
invalid_password='Invalid password',
|
|
||||||
invalid_two_factor_code='Incorrect code. {0} more attempt(s) remaining.',
|
invalid_two_factor_code='Incorrect code. {0} more attempt(s) remaining.',
|
||||||
is_empty="Cannot be empty",
|
is_empty="Cannot be empty",
|
||||||
mismatched_password="Password fields don't match",
|
label_client_ip='Client IP',
|
||||||
verify_email='Welcome %(username)s! Click on the link %(link)s to verify your email',
|
label_description='Description',
|
||||||
verify_email_subject='Email verification',
|
label_email='E-mail',
|
||||||
username_sent='Your username was emailed to you',
|
label_first_name='First name',
|
||||||
|
label_group_id='Group ID',
|
||||||
|
label_last_name='Last name',
|
||||||
|
label_name='Name',
|
||||||
|
label_origin='Origin',
|
||||||
|
label_password='Password',
|
||||||
|
label_record_id='Record ID',
|
||||||
|
label_registration_id='Registration identifier',
|
||||||
|
label_registration_key='Registration key',
|
||||||
|
label_remember_me="Remember me (for 30 days)",
|
||||||
|
label_reset_password_key='Reset Password key',
|
||||||
|
label_role='Role',
|
||||||
|
label_table_name='Object or table name',
|
||||||
|
label_time_stamp='Timestamp',
|
||||||
|
label_two_factor='Authentication code',
|
||||||
|
label_user_id='User ID',
|
||||||
|
label_username='Username',
|
||||||
|
login_button='Log In',
|
||||||
|
login_disabled='Login disabled by administrator',
|
||||||
|
new_password='New password',
|
||||||
new_password_sent='A new password was emailed to you',
|
new_password_sent='A new password was emailed to you',
|
||||||
password_changed='Password changed',
|
old_password='Old password',
|
||||||
retrieve_username='Your username is: %(username)s',
|
password_change_button='Change password',
|
||||||
retrieve_username_subject='Username retrieve',
|
password_reset_button='Request reset password',
|
||||||
retrieve_password='Your password is: %(password)s',
|
profile_save_button='Apply changes',
|
||||||
retrieve_password_subject='Password retrieve',
|
register_button='Sign Up',
|
||||||
reset_password='Click on the link %(link)s to reset your password',
|
reset_password='Click on the link %(link)s to reset your password',
|
||||||
|
reset_password_log='User %(id)s Password reset',
|
||||||
reset_password_subject='Password reset',
|
reset_password_subject='Password reset',
|
||||||
bulk_invite_subject='Invitation to join %(site)s',
|
retrieve_password='Your password is: %(password)s',
|
||||||
|
retrieve_password_log='User %(id)s Password retrieved',
|
||||||
|
retrieve_password_subject='Password retrieve',
|
||||||
retrieve_two_factor_code='Your temporary login code is {0}',
|
retrieve_two_factor_code='Your temporary login code is {0}',
|
||||||
retrieve_two_factor_code_subject='Two-step Login Authentication Code',
|
retrieve_two_factor_code_subject='Two-step Login Authentication Code',
|
||||||
bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete the process',
|
retrieve_username='Your username is: %(username)s',
|
||||||
invalid_reset_password='Invalid reset password',
|
|
||||||
profile_updated='Profile updated',
|
|
||||||
new_password='New password',
|
|
||||||
old_password='Old password',
|
|
||||||
group_description='Group uniquely assigned to user %(id)s',
|
|
||||||
register_log='User %(id)s Registered',
|
|
||||||
login_log='User %(id)s Logged-in',
|
|
||||||
login_failed_log=None,
|
|
||||||
logout_log='User %(id)s Logged-out',
|
|
||||||
profile_log='User %(id)s Profile updated',
|
|
||||||
verify_email_log='User %(id)s Verification email sent',
|
|
||||||
retrieve_username_log='User %(id)s Username retrieved',
|
retrieve_username_log='User %(id)s Username retrieved',
|
||||||
retrieve_password_log='User %(id)s Password retrieved',
|
retrieve_username_subject='Username retrieve',
|
||||||
reset_password_log='User %(id)s Password reset',
|
submit_button='Submit',
|
||||||
change_password_log='User %(id)s Password changed',
|
|
||||||
add_group_log='Group %(group_id)s created',
|
|
||||||
del_group_log='Group %(group_id)s deleted',
|
|
||||||
add_membership_log=None,
|
|
||||||
del_membership_log=None,
|
|
||||||
has_membership_log=None,
|
|
||||||
add_permission_log=None,
|
|
||||||
del_permission_log=None,
|
|
||||||
has_permission_log=None,
|
|
||||||
impersonate_log='User %(id)s is impersonating %(other_id)s',
|
|
||||||
label_first_name='First name',
|
|
||||||
label_last_name='Last name',
|
|
||||||
label_username='Username',
|
|
||||||
label_email='E-mail',
|
|
||||||
label_password='Password',
|
|
||||||
label_registration_key='Registration key',
|
|
||||||
label_reset_password_key='Reset Password key',
|
|
||||||
label_registration_id='Registration identifier',
|
|
||||||
label_role='Role',
|
|
||||||
label_description='Description',
|
|
||||||
label_user_id='User ID',
|
|
||||||
label_group_id='Group ID',
|
|
||||||
label_name='Name',
|
|
||||||
label_table_name='Object or table name',
|
|
||||||
label_record_id='Record ID',
|
|
||||||
label_time_stamp='Timestamp',
|
|
||||||
label_client_ip='Client IP',
|
|
||||||
label_origin='Origin',
|
|
||||||
label_remember_me="Remember me (for 30 days)",
|
|
||||||
label_two_factor='Authentication code',
|
|
||||||
two_factor_comment='This code was emailed to you and is required for login.',
|
two_factor_comment='This code was emailed to you and is required for login.',
|
||||||
verify_password_comment='please input your password again',
|
unable_send_email='Unable to send email',
|
||||||
|
username_sent='Your username was emailed to you',
|
||||||
|
verify_email='Welcome %(username)s! Click on the link %(link)s to verify your email',
|
||||||
|
verify_email_log='User %(id)s Verification email sent',
|
||||||
|
verify_email_subject='Email verification',
|
||||||
|
verify_password='Verify Password',
|
||||||
|
verify_password_comment='please input your password again'
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Class for authentication, authorization, role based access control.
|
Class for authentication, authorization, role based access control.
|
||||||
|
|
||||||
@@ -1816,7 +1764,7 @@ class Auth(object):
|
|||||||
self.user = auth.user
|
self.user = auth.user
|
||||||
# this is a trick to speed up sessions to avoid many writes
|
# this is a trick to speed up sessions to avoid many writes
|
||||||
if (now - auth.last_visit).seconds > (auth.expiration / 10):
|
if (now - auth.last_visit).seconds > (auth.expiration / 10):
|
||||||
auth.last_visit = request.now
|
auth.last_visit = now
|
||||||
else:
|
else:
|
||||||
self.user = None
|
self.user = None
|
||||||
if session.auth:
|
if session.auth:
|
||||||
@@ -1893,8 +1841,7 @@ class Auth(object):
|
|||||||
# ## these are messages that can be customized
|
# ## these are messages that can be customized
|
||||||
messages = self.messages = Messages(current.T)
|
messages = self.messages = Messages(current.T)
|
||||||
messages.update(Auth.default_messages)
|
messages.update(Auth.default_messages)
|
||||||
messages.update(ajax_failed_authentication=
|
messages.update(ajax_failed_authentication=DIV(H4('NOT AUTHORIZED'),
|
||||||
DIV(H4('NOT AUTHORIZED'),
|
|
||||||
'Please ',
|
'Please ',
|
||||||
A('login',
|
A('login',
|
||||||
_href=self.settings.login_url +
|
_href=self.settings.login_url +
|
||||||
@@ -1936,27 +1883,6 @@ class Auth(object):
|
|||||||
return next
|
return next
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_user_id(self):
|
|
||||||
"""accessor for auth.user_id"""
|
|
||||||
return self.user and self.user.id or None
|
|
||||||
|
|
||||||
user_id = property(_get_user_id, doc="user.id or None")
|
|
||||||
|
|
||||||
def table_user(self):
|
|
||||||
return self.db[self.settings.table_user_name]
|
|
||||||
|
|
||||||
def table_group(self):
|
|
||||||
return self.db[self.settings.table_group_name]
|
|
||||||
|
|
||||||
def table_membership(self):
|
|
||||||
return self.db[self.settings.table_membership_name]
|
|
||||||
|
|
||||||
def table_permission(self):
|
|
||||||
return self.db[self.settings.table_permission_name]
|
|
||||||
|
|
||||||
def table_event(self):
|
|
||||||
return self.db[self.settings.table_event_name]
|
|
||||||
|
|
||||||
def table_cas(self):
|
def table_cas(self):
|
||||||
return self.db[self.settings.table_cas_name]
|
return self.db[self.settings.table_cas_name]
|
||||||
|
|
||||||
@@ -2213,15 +2139,6 @@ class Auth(object):
|
|||||||
|
|
||||||
return self.bar
|
return self.bar
|
||||||
|
|
||||||
def __get_migrate(self, tablename, migrate=True):
|
|
||||||
|
|
||||||
if type(migrate).__name__ == 'str':
|
|
||||||
return (migrate + tablename + '.table')
|
|
||||||
elif migrate == False:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def enable_record_versioning(self,
|
def enable_record_versioning(self,
|
||||||
tables,
|
tables,
|
||||||
archive_db=None,
|
archive_db=None,
|
||||||
@@ -2267,49 +2184,6 @@ class Auth(object):
|
|||||||
current_record=current_record,
|
current_record=current_record,
|
||||||
current_record_label=current_record_label)
|
current_record_label=current_record_label)
|
||||||
|
|
||||||
def define_signature(self):
|
|
||||||
db = self.db
|
|
||||||
settings = self.settings
|
|
||||||
request = current.request
|
|
||||||
T = current.T
|
|
||||||
reference_user = 'reference %s' % settings.table_user_name
|
|
||||||
|
|
||||||
def lazy_user(auth=self):
|
|
||||||
return auth.user_id
|
|
||||||
|
|
||||||
def represent(id, record=None, s=settings):
|
|
||||||
try:
|
|
||||||
user = s.table_user(id)
|
|
||||||
return '%s %s' % (user.get("first_name", user.get("email")),
|
|
||||||
user.get("last_name", ''))
|
|
||||||
except:
|
|
||||||
return id
|
|
||||||
ondelete = self.settings.ondelete
|
|
||||||
self.signature = Table(
|
|
||||||
self.db, 'auth_signature',
|
|
||||||
Field('is_active', 'boolean',
|
|
||||||
default=True,
|
|
||||||
readable=False, writable=False,
|
|
||||||
label=T('Is Active')),
|
|
||||||
Field('created_on', 'datetime',
|
|
||||||
default=request.now,
|
|
||||||
writable=False, readable=False,
|
|
||||||
label=T('Created On')),
|
|
||||||
Field('created_by',
|
|
||||||
reference_user,
|
|
||||||
default=lazy_user, represent=represent,
|
|
||||||
writable=False, readable=False,
|
|
||||||
label=T('Created By'), ondelete=ondelete),
|
|
||||||
Field('modified_on', 'datetime',
|
|
||||||
update=request.now, default=request.now,
|
|
||||||
writable=False, readable=False,
|
|
||||||
label=T('Modified On')),
|
|
||||||
Field('modified_by',
|
|
||||||
reference_user, represent=represent,
|
|
||||||
default=lazy_user, update=lazy_user,
|
|
||||||
writable=False, readable=False,
|
|
||||||
label=T('Modified By'), ondelete=ondelete))
|
|
||||||
|
|
||||||
def define_tables(self, username=None, signature=None, enable_tokens=False,
|
def define_tables(self, username=None, signature=None, enable_tokens=False,
|
||||||
migrate=None, fake_migrate=None):
|
migrate=None, fake_migrate=None):
|
||||||
"""
|
"""
|
||||||
@@ -2333,179 +2207,11 @@ class Auth(object):
|
|||||||
if fake_migrate is None:
|
if fake_migrate is None:
|
||||||
fake_migrate = db._fake_migrate
|
fake_migrate = db._fake_migrate
|
||||||
settings = self.settings
|
settings = self.settings
|
||||||
if username is None:
|
|
||||||
username = settings.use_username
|
|
||||||
else:
|
|
||||||
settings.use_username = username
|
|
||||||
settings.enable_tokens = enable_tokens
|
settings.enable_tokens = enable_tokens
|
||||||
if not self.signature:
|
super(Auth, self).define_tables(username, signature, migrate, fake_migrate)
|
||||||
self.define_signature()
|
|
||||||
if signature:
|
|
||||||
signature_list = [self.signature]
|
|
||||||
elif not signature:
|
|
||||||
signature_list = []
|
|
||||||
elif isinstance(signature, Table):
|
|
||||||
signature_list = [signature]
|
|
||||||
else:
|
|
||||||
signature_list = signature
|
|
||||||
is_not_empty = IS_NOT_EMPTY(error_message=self.messages.is_empty)
|
|
||||||
is_crypted = CRYPT(key=settings.hmac_key,
|
|
||||||
min_length=settings.password_min_length)
|
|
||||||
is_unique_email = [
|
|
||||||
IS_EMAIL(error_message=self.messages.invalid_email),
|
|
||||||
IS_NOT_IN_DB(db, '%s.email' % settings.table_user_name,
|
|
||||||
error_message=self.messages.email_taken)]
|
|
||||||
if not settings.email_case_sensitive:
|
|
||||||
is_unique_email.insert(1, IS_LOWER())
|
|
||||||
if settings.table_user_name not in db.tables:
|
|
||||||
passfield = settings.password_field
|
|
||||||
extra_fields = settings.extra_fields.get(
|
|
||||||
settings.table_user_name, []) + signature_list
|
|
||||||
if username or settings.cas_provider:
|
|
||||||
is_unique_username = \
|
|
||||||
[IS_MATCH('[\w\.\-]+', strict=True,
|
|
||||||
error_message=self.messages.invalid_username),
|
|
||||||
IS_NOT_IN_DB(db, '%s.username' % settings.table_user_name,
|
|
||||||
error_message=self.messages.username_taken)]
|
|
||||||
if not settings.username_case_sensitive:
|
|
||||||
is_unique_username.insert(1, IS_LOWER())
|
|
||||||
db.define_table(
|
|
||||||
settings.table_user_name,
|
|
||||||
Field('first_name', length=128, default='',
|
|
||||||
label=self.messages.label_first_name,
|
|
||||||
requires=is_not_empty),
|
|
||||||
Field('last_name', length=128, default='',
|
|
||||||
label=self.messages.label_last_name,
|
|
||||||
requires=is_not_empty),
|
|
||||||
Field('email', length=512, default='',
|
|
||||||
label=self.messages.label_email,
|
|
||||||
requires=is_unique_email),
|
|
||||||
Field('username', length=128, default='',
|
|
||||||
label=self.messages.label_username,
|
|
||||||
requires=is_unique_username),
|
|
||||||
Field(passfield, 'password', length=512,
|
|
||||||
readable=False, label=self.messages.label_password,
|
|
||||||
requires=[is_crypted]),
|
|
||||||
Field('registration_key', length=512,
|
|
||||||
writable=False, readable=False, default='',
|
|
||||||
label=self.messages.label_registration_key),
|
|
||||||
Field('reset_password_key', length=512,
|
|
||||||
writable=False, readable=False, default='',
|
|
||||||
label=self.messages.label_reset_password_key),
|
|
||||||
Field('registration_id', length=512,
|
|
||||||
writable=False, readable=False, default='',
|
|
||||||
label=self.messages.label_registration_id),
|
|
||||||
*extra_fields,
|
|
||||||
**dict(
|
|
||||||
migrate=self.__get_migrate(settings.table_user_name,
|
|
||||||
migrate),
|
|
||||||
fake_migrate=fake_migrate,
|
|
||||||
format='%(username)s'))
|
|
||||||
else:
|
|
||||||
db.define_table(
|
|
||||||
settings.table_user_name,
|
|
||||||
Field('first_name', length=128, default='',
|
|
||||||
label=self.messages.label_first_name,
|
|
||||||
requires=is_not_empty),
|
|
||||||
Field('last_name', length=128, default='',
|
|
||||||
label=self.messages.label_last_name,
|
|
||||||
requires=is_not_empty),
|
|
||||||
Field('email', length=512, default='',
|
|
||||||
label=self.messages.label_email,
|
|
||||||
requires=is_unique_email),
|
|
||||||
Field(passfield, 'password', length=512,
|
|
||||||
readable=False, label=self.messages.label_password,
|
|
||||||
requires=[is_crypted]),
|
|
||||||
Field('registration_key', length=512,
|
|
||||||
writable=False, readable=False, default='',
|
|
||||||
label=self.messages.label_registration_key),
|
|
||||||
Field('reset_password_key', length=512,
|
|
||||||
writable=False, readable=False, default='',
|
|
||||||
label=self.messages.label_reset_password_key),
|
|
||||||
Field('registration_id', length=512,
|
|
||||||
writable=False, readable=False, default='',
|
|
||||||
label=self.messages.label_registration_id),
|
|
||||||
*extra_fields,
|
|
||||||
**dict(
|
|
||||||
migrate=self.__get_migrate(settings.table_user_name,
|
|
||||||
migrate),
|
|
||||||
fake_migrate=fake_migrate,
|
|
||||||
format='%(first_name)s %(last_name)s (%(id)s)'))
|
|
||||||
reference_table_user = 'reference %s' % settings.table_user_name
|
|
||||||
if settings.table_group_name not in db.tables:
|
|
||||||
extra_fields = settings.extra_fields.get(
|
|
||||||
settings.table_group_name, []) + signature_list
|
|
||||||
db.define_table(
|
|
||||||
settings.table_group_name,
|
|
||||||
Field('role', length=512, default='',
|
|
||||||
label=self.messages.label_role,
|
|
||||||
requires=IS_NOT_IN_DB(db, '%s.role' % settings.table_group_name)),
|
|
||||||
Field('description', 'text',
|
|
||||||
label=self.messages.label_description),
|
|
||||||
*extra_fields,
|
|
||||||
**dict(
|
|
||||||
migrate=self.__get_migrate(
|
|
||||||
settings.table_group_name, migrate),
|
|
||||||
fake_migrate=fake_migrate,
|
|
||||||
format='%(role)s (%(id)s)'))
|
|
||||||
reference_table_group = 'reference %s' % settings.table_group_name
|
|
||||||
if settings.table_membership_name not in db.tables:
|
|
||||||
extra_fields = settings.extra_fields.get(
|
|
||||||
settings.table_membership_name, []) + signature_list
|
|
||||||
db.define_table(
|
|
||||||
settings.table_membership_name,
|
|
||||||
Field('user_id', reference_table_user,
|
|
||||||
label=self.messages.label_user_id),
|
|
||||||
Field('group_id', reference_table_group,
|
|
||||||
label=self.messages.label_group_id),
|
|
||||||
*extra_fields,
|
|
||||||
**dict(
|
|
||||||
migrate=self.__get_migrate(
|
|
||||||
settings.table_membership_name, migrate),
|
|
||||||
fake_migrate=fake_migrate))
|
|
||||||
if settings.table_permission_name not in db.tables:
|
|
||||||
extra_fields = settings.extra_fields.get(
|
|
||||||
settings.table_permission_name, []) + signature_list
|
|
||||||
db.define_table(
|
|
||||||
settings.table_permission_name,
|
|
||||||
Field('group_id', reference_table_group,
|
|
||||||
label=self.messages.label_group_id),
|
|
||||||
Field('name', default='default', length=512,
|
|
||||||
label=self.messages.label_name,
|
|
||||||
requires=is_not_empty),
|
|
||||||
Field('table_name', length=512,
|
|
||||||
label=self.messages.label_table_name),
|
|
||||||
Field('record_id', 'integer', default=0,
|
|
||||||
label=self.messages.label_record_id,
|
|
||||||
requires=IS_INT_IN_RANGE(0, 10 ** 9)),
|
|
||||||
*extra_fields,
|
|
||||||
**dict(
|
|
||||||
migrate=self.__get_migrate(
|
|
||||||
settings.table_permission_name, migrate),
|
|
||||||
fake_migrate=fake_migrate))
|
|
||||||
if settings.table_event_name not in db.tables:
|
|
||||||
db.define_table(
|
|
||||||
settings.table_event_name,
|
|
||||||
Field('time_stamp', 'datetime',
|
|
||||||
default=current.request.now,
|
|
||||||
label=self.messages.label_time_stamp),
|
|
||||||
Field('client_ip',
|
|
||||||
default=current.request.client,
|
|
||||||
label=self.messages.label_client_ip),
|
|
||||||
Field('user_id', reference_table_user, default=None,
|
|
||||||
label=self.messages.label_user_id),
|
|
||||||
Field('origin', default='auth', length=512,
|
|
||||||
label=self.messages.label_origin,
|
|
||||||
requires=is_not_empty),
|
|
||||||
Field('description', 'text', default='',
|
|
||||||
label=self.messages.label_description,
|
|
||||||
requires=is_not_empty),
|
|
||||||
*settings.extra_fields.get(settings.table_event_name, []),
|
|
||||||
**dict(
|
|
||||||
migrate=self.__get_migrate(
|
|
||||||
settings.table_event_name, migrate),
|
|
||||||
fake_migrate=fake_migrate))
|
|
||||||
now = current.request.now
|
now = current.request.now
|
||||||
|
reference_table_user = 'reference %s' % settings.table_user_name
|
||||||
if settings.cas_domains:
|
if settings.cas_domains:
|
||||||
if settings.table_cas_name not in db.tables:
|
if settings.table_cas_name not in db.tables:
|
||||||
db.define_table(
|
db.define_table(
|
||||||
@@ -2518,7 +2224,7 @@ class Auth(object):
|
|||||||
Field('renew', 'boolean', default=False),
|
Field('renew', 'boolean', default=False),
|
||||||
*settings.extra_fields.get(settings.table_cas_name, []),
|
*settings.extra_fields.get(settings.table_cas_name, []),
|
||||||
**dict(
|
**dict(
|
||||||
migrate=self.__get_migrate(
|
migrate=self._get_migrate(
|
||||||
settings.table_cas_name, migrate),
|
settings.table_cas_name, migrate),
|
||||||
fake_migrate=fake_migrate))
|
fake_migrate=fake_migrate))
|
||||||
if settings.enable_tokens:
|
if settings.enable_tokens:
|
||||||
@@ -2532,7 +2238,7 @@ class Auth(object):
|
|||||||
Field('expires_on', 'datetime', default=datetime.datetime(2999, 12, 31)),
|
Field('expires_on', 'datetime', default=datetime.datetime(2999, 12, 31)),
|
||||||
Field('token', writable=False, default=web2py_uuid, unique=True),
|
Field('token', writable=False, default=web2py_uuid, unique=True),
|
||||||
*extra_fields,
|
*extra_fields,
|
||||||
**dict(migrate=self.__get_migrate(settings.table_token_name, migrate),
|
**dict(migrate=self._get_migrate(settings.table_token_name, migrate),
|
||||||
fake_migrate=fake_migrate))
|
fake_migrate=fake_migrate))
|
||||||
if not db._lazy_tables:
|
if not db._lazy_tables:
|
||||||
settings.table_user = db[settings.table_user_name]
|
settings.table_user = db[settings.table_user_name]
|
||||||
@@ -2566,26 +2272,6 @@ class Auth(object):
|
|||||||
maps=maps)
|
maps=maps)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def log_event(self, description, vars=None, origin='auth'):
|
|
||||||
"""
|
|
||||||
Examples:
|
|
||||||
Use as::
|
|
||||||
|
|
||||||
auth.log_event(description='this happened', origin='auth')
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not self.settings.logging_enabled or not description:
|
|
||||||
return
|
|
||||||
elif self.is_logged_in():
|
|
||||||
user_id = self.user.id
|
|
||||||
else:
|
|
||||||
user_id = None # user unknown
|
|
||||||
vars = vars or {}
|
|
||||||
# log messages should not be translated
|
|
||||||
if type(description).__name__ == 'lazyT':
|
|
||||||
description = description.m
|
|
||||||
self.table_event().insert(description=str(description % vars), origin=origin, user_id=user_id)
|
|
||||||
|
|
||||||
def get_or_create_user(self, keys, update_fields=['email'],
|
def get_or_create_user(self, keys, update_fields=['email'],
|
||||||
login=True, get=True):
|
login=True, get=True):
|
||||||
"""
|
"""
|
||||||
@@ -2683,29 +2369,6 @@ class Auth(object):
|
|||||||
raise http_401
|
raise http_401
|
||||||
return (True, True, is_valid_user)
|
return (True, True, is_valid_user)
|
||||||
|
|
||||||
def login_user(self, user):
|
|
||||||
"""
|
|
||||||
Logins the `user = db.auth_user(id)`
|
|
||||||
"""
|
|
||||||
from gluon.settings import global_settings
|
|
||||||
if global_settings.web2py_runtime_gae:
|
|
||||||
user = Row(self.table_user()._filter_fields(user, id=True))
|
|
||||||
delattr(user, 'password')
|
|
||||||
else:
|
|
||||||
user = Row(user)
|
|
||||||
for key in list(user.keys()):
|
|
||||||
value = user[key]
|
|
||||||
if callable(value) or key == 'password':
|
|
||||||
delattr(user, key)
|
|
||||||
if self.settings.renew_session_onlogin:
|
|
||||||
current.session.renew(clear_session=not self.settings.keep_session_onlogin)
|
|
||||||
current.session.auth = Storage(user=user,
|
|
||||||
last_visit=current.request.now,
|
|
||||||
expiration=self.settings.expiration,
|
|
||||||
hmac_key=web2py_uuid())
|
|
||||||
self.user = user
|
|
||||||
self.update_groups()
|
|
||||||
|
|
||||||
def _get_login_settings(self):
|
def _get_login_settings(self):
|
||||||
table_user = self.table_user()
|
table_user = self.table_user()
|
||||||
userfield = self.settings.login_userfield or 'username' \
|
userfield = self.settings.login_userfield or 'username' \
|
||||||
@@ -3447,16 +3110,6 @@ class Auth(object):
|
|||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def is_logged_in(self):
|
|
||||||
"""
|
|
||||||
Checks if the user is logged in and returns True/False.
|
|
||||||
If so user is in auth.user as well as in session.auth.user
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.user:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def verify_email(self,
|
def verify_email(self,
|
||||||
next=DEFAULT,
|
next=DEFAULT,
|
||||||
onaccept=DEFAULT,
|
onaccept=DEFAULT,
|
||||||
@@ -3684,7 +3337,6 @@ class Auth(object):
|
|||||||
raise Exception
|
raise Exception
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
session.flash = self.messages.invalid_reset_password
|
session.flash = self.messages.invalid_reset_password
|
||||||
redirect(self.url('login', vars=dict(test=e)))
|
|
||||||
redirect(next, client_side=self.settings.client_side)
|
redirect(next, client_side=self.settings.client_side)
|
||||||
passfield = self.settings.password_field
|
passfield = self.settings.password_field
|
||||||
form = SQLFORM.factory(
|
form = SQLFORM.factory(
|
||||||
@@ -4205,21 +3857,6 @@ class Auth(object):
|
|||||||
else:
|
else:
|
||||||
return SQLFORM(table_user, user.id, readonly=True)
|
return SQLFORM(table_user, user.id, readonly=True)
|
||||||
|
|
||||||
def update_groups(self):
|
|
||||||
if not self.user:
|
|
||||||
return
|
|
||||||
user_groups = self.user_groups = {}
|
|
||||||
if current.session.auth:
|
|
||||||
current.session.auth.user_groups = self.user_groups
|
|
||||||
table_group = self.table_group()
|
|
||||||
table_membership = self.table_membership()
|
|
||||||
memberships = self.db(
|
|
||||||
table_membership.user_id == self.user.id).select()
|
|
||||||
for membership in memberships:
|
|
||||||
group = table_group(membership.group_id)
|
|
||||||
if group:
|
|
||||||
user_groups[membership.group_id] = group.role
|
|
||||||
|
|
||||||
def groups(self):
|
def groups(self):
|
||||||
"""
|
"""
|
||||||
Displays the groups and their roles for the logged in user
|
Displays the groups and their roles for the logged in user
|
||||||
@@ -4337,6 +3974,7 @@ class Auth(object):
|
|||||||
If role is provided instead of group_id then the
|
If role is provided instead of group_id then the
|
||||||
group_id is calculated.
|
group_id is calculated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def has_membership(self=self, group_id=group_id, role=role):
|
def has_membership(self=self, group_id=group_id, role=role):
|
||||||
return self.has_membership(group_id=group_id, role=role)
|
return self.has_membership(group_id=group_id, role=role)
|
||||||
return self.requires(has_membership, otherwise=otherwise)
|
return self.requires(has_membership, otherwise=otherwise)
|
||||||
@@ -4348,6 +3986,7 @@ class Auth(object):
|
|||||||
if user logged in is not a member of any group (role) that
|
if user logged in is not a member of any group (role) that
|
||||||
has 'name' access to 'table_name', 'record_id'.
|
has 'name' access to 'table_name', 'record_id'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def has_permission(self=self, name=name, table_name=table_name, record_id=record_id):
|
def has_permission(self=self, name=name, table_name=table_name, record_id=record_id):
|
||||||
return self.has_permission(name, table_name, record_id)
|
return self.has_permission(name, table_name, record_id)
|
||||||
return self.requires(has_permission, otherwise=otherwise)
|
return self.requires(has_permission, otherwise=otherwise)
|
||||||
@@ -4363,231 +4002,6 @@ class Auth(object):
|
|||||||
return URL.verify(current.request, user_signature=True, hash_vars=hash_vars)
|
return URL.verify(current.request, user_signature=True, hash_vars=hash_vars)
|
||||||
return self.requires(verify, otherwise)
|
return self.requires(verify, otherwise)
|
||||||
|
|
||||||
def add_group(self, role, description=''):
|
|
||||||
"""
|
|
||||||
Creates a group associated to a role
|
|
||||||
"""
|
|
||||||
group_id = self.table_group().insert(role=role, description=description)
|
|
||||||
self.log_event(self.messages['add_group_log'], dict(group_id=group_id, role=role))
|
|
||||||
return group_id
|
|
||||||
|
|
||||||
def del_group(self, group_id):
|
|
||||||
"""
|
|
||||||
Deletes a group
|
|
||||||
"""
|
|
||||||
self.db(self.table_group().id == group_id).delete()
|
|
||||||
self.db(self.table_membership().group_id == group_id).delete()
|
|
||||||
self.db(self.table_permission().group_id == group_id).delete()
|
|
||||||
if group_id in self.user_groups:
|
|
||||||
del self.user_groups[group_id]
|
|
||||||
self.log_event(self.messages.del_group_log, dict(group_id=group_id))
|
|
||||||
|
|
||||||
def id_group(self, role):
|
|
||||||
"""
|
|
||||||
Returns the group_id of the group specified by the role
|
|
||||||
"""
|
|
||||||
rows = self.db(self.table_group().role == role).select()
|
|
||||||
if not rows:
|
|
||||||
return None
|
|
||||||
return rows[0].id
|
|
||||||
|
|
||||||
def user_group(self, user_id=None):
|
|
||||||
"""
|
|
||||||
Returns the group_id of the group uniquely associated to this user
|
|
||||||
i.e. `role=user:[user_id]`
|
|
||||||
"""
|
|
||||||
return self.id_group(self.user_group_role(user_id))
|
|
||||||
|
|
||||||
def user_group_role(self, user_id=None):
|
|
||||||
if not self.settings.create_user_groups:
|
|
||||||
return None
|
|
||||||
if user_id:
|
|
||||||
user = self.table_user()[user_id]
|
|
||||||
else:
|
|
||||||
user = self.user
|
|
||||||
return self.settings.create_user_groups % user
|
|
||||||
|
|
||||||
def has_membership(self, group_id=None, user_id=None, role=None):
|
|
||||||
"""
|
|
||||||
Checks if user is member of group_id or role
|
|
||||||
"""
|
|
||||||
group_id = group_id or self.id_group(role)
|
|
||||||
try:
|
|
||||||
group_id = int(group_id)
|
|
||||||
except:
|
|
||||||
group_id = self.id_group(group_id) # interpret group_id as a role
|
|
||||||
if not user_id and self.user:
|
|
||||||
user_id = self.user.id
|
|
||||||
membership = self.table_membership()
|
|
||||||
if group_id and user_id and self.db((membership.user_id == user_id) &
|
|
||||||
(membership.group_id == group_id)).select():
|
|
||||||
r = True
|
|
||||||
else:
|
|
||||||
r = False
|
|
||||||
self.log_event(self.messages['has_membership_log'],
|
|
||||||
dict(user_id=user_id, group_id=group_id, check=r))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def add_membership(self, group_id=None, user_id=None, role=None):
|
|
||||||
"""
|
|
||||||
Gives user_id membership of group_id or role
|
|
||||||
if user is None than user_id is that of current logged in user
|
|
||||||
"""
|
|
||||||
|
|
||||||
group_id = group_id or self.id_group(role)
|
|
||||||
try:
|
|
||||||
group_id = int(group_id)
|
|
||||||
except:
|
|
||||||
group_id = self.id_group(group_id) # interpret group_id as a role
|
|
||||||
if not user_id and self.user:
|
|
||||||
user_id = self.user.id
|
|
||||||
if not group_id:
|
|
||||||
raise ValueError('group_id not provided or invalid')
|
|
||||||
if not user_id:
|
|
||||||
raise ValueError('user_id not provided or invalid')
|
|
||||||
membership = self.table_membership()
|
|
||||||
db = membership._db
|
|
||||||
record = db((membership.user_id == user_id) &
|
|
||||||
(membership.group_id == group_id),
|
|
||||||
ignore_common_filters=True).select().first()
|
|
||||||
if record:
|
|
||||||
if hasattr(record, 'is_active') and not record.is_active:
|
|
||||||
record.update_record(is_active=True)
|
|
||||||
return record.id
|
|
||||||
else:
|
|
||||||
id = membership.insert(group_id=group_id, user_id=user_id)
|
|
||||||
if role:
|
|
||||||
self.user_groups[group_id] = role
|
|
||||||
else:
|
|
||||||
self.update_groups()
|
|
||||||
self.log_event(self.messages['add_membership_log'],
|
|
||||||
dict(user_id=user_id, group_id=group_id))
|
|
||||||
return id
|
|
||||||
|
|
||||||
def del_membership(self, group_id=None, user_id=None, role=None):
|
|
||||||
"""
|
|
||||||
Revokes membership from group_id to user_id
|
|
||||||
if user_id is None than user_id is that of current logged in user
|
|
||||||
"""
|
|
||||||
|
|
||||||
group_id = group_id or self.id_group(role)
|
|
||||||
try:
|
|
||||||
group_id = int(group_id)
|
|
||||||
except:
|
|
||||||
group_id = self.id_group(group_id) # interpret group_id as a role
|
|
||||||
if not user_id and self.user:
|
|
||||||
user_id = self.user.id
|
|
||||||
membership = self.table_membership()
|
|
||||||
self.log_event(self.messages['del_membership_log'],
|
|
||||||
dict(user_id=user_id, group_id=group_id))
|
|
||||||
ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete()
|
|
||||||
if group_id in self.user_groups:
|
|
||||||
del self.user_groups[group_id]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def has_permission(self,
|
|
||||||
name='any',
|
|
||||||
table_name='',
|
|
||||||
record_id=0,
|
|
||||||
user_id=None,
|
|
||||||
group_id=None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Checks if user_id or current logged in user is member of a group
|
|
||||||
that has 'name' permission on 'table_name' and 'record_id'
|
|
||||||
if group_id is passed, it checks whether the group has the permission
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not group_id and self.settings.everybody_group_id and \
|
|
||||||
self.has_permission(name, table_name, record_id, user_id=None,
|
|
||||||
group_id=self.settings.everybody_group_id):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not user_id and not group_id and self.user:
|
|
||||||
user_id = self.user.id
|
|
||||||
if user_id:
|
|
||||||
membership = self.table_membership()
|
|
||||||
rows = self.db(membership.user_id == user_id).select(membership.group_id)
|
|
||||||
groups = set([row.group_id for row in rows])
|
|
||||||
if group_id and group_id not in groups:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
groups = set([group_id])
|
|
||||||
permission = self.table_permission()
|
|
||||||
rows = self.db(permission.name ==
|
|
||||||
name)(permission.table_name ==
|
|
||||||
str(table_name))(permission.record_id ==
|
|
||||||
record_id).select(permission.group_id)
|
|
||||||
groups_required = set([row.group_id for row in rows])
|
|
||||||
if record_id:
|
|
||||||
rows = self.db(permission.name ==
|
|
||||||
name)(permission.table_name ==
|
|
||||||
str(table_name))(permission.record_id ==
|
|
||||||
0).select(permission.group_id)
|
|
||||||
groups_required = groups_required.union(set([row.group_id for row in rows]))
|
|
||||||
if groups.intersection(groups_required):
|
|
||||||
r = True
|
|
||||||
else:
|
|
||||||
r = False
|
|
||||||
if user_id:
|
|
||||||
self.log_event(self.messages['has_permission_log'],
|
|
||||||
dict(user_id=user_id, name=name,
|
|
||||||
table_name=table_name, record_id=record_id))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def add_permission(self,
|
|
||||||
group_id,
|
|
||||||
name='any',
|
|
||||||
table_name='',
|
|
||||||
record_id=0,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Gives group_id 'name' access to 'table_name' and 'record_id'
|
|
||||||
"""
|
|
||||||
|
|
||||||
permission = self.table_permission()
|
|
||||||
if group_id == 0:
|
|
||||||
group_id = self.user_group()
|
|
||||||
record = self.db((permission.group_id == group_id) &
|
|
||||||
(permission.name == name) &
|
|
||||||
(permission.table_name == str(table_name)) &
|
|
||||||
(permission.record_id == long(record_id)),
|
|
||||||
ignore_common_filters=True
|
|
||||||
).select(limitby=(0, 1), orderby_on_limitby=False).first()
|
|
||||||
if record:
|
|
||||||
if hasattr(record, 'is_active') and not record.is_active:
|
|
||||||
record.update_record(is_active=True)
|
|
||||||
id = record.id
|
|
||||||
else:
|
|
||||||
id = permission.insert(group_id=group_id, name=name,
|
|
||||||
table_name=str(table_name),
|
|
||||||
record_id=long(record_id))
|
|
||||||
self.log_event(self.messages['add_permission_log'],
|
|
||||||
dict(permission_id=id, group_id=group_id,
|
|
||||||
name=name, table_name=table_name,
|
|
||||||
record_id=record_id))
|
|
||||||
return id
|
|
||||||
|
|
||||||
def del_permission(self,
|
|
||||||
group_id,
|
|
||||||
name='any',
|
|
||||||
table_name='',
|
|
||||||
record_id=0,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Revokes group_id 'name' access to 'table_name' and 'record_id'
|
|
||||||
"""
|
|
||||||
|
|
||||||
permission = self.table_permission()
|
|
||||||
self.log_event(self.messages['del_permission_log'],
|
|
||||||
dict(group_id=group_id, name=name,
|
|
||||||
table_name=table_name, record_id=record_id))
|
|
||||||
return self.db(permission.group_id ==
|
|
||||||
group_id)(permission.name ==
|
|
||||||
name)(permission.table_name ==
|
|
||||||
str(table_name))(permission.record_id ==
|
|
||||||
long(record_id)).delete()
|
|
||||||
|
|
||||||
def accessible_query(self, name, table, user_id=None):
|
def accessible_query(self, name, table, user_id=None):
|
||||||
"""
|
"""
|
||||||
Returns a query with all accessible records for user_id or
|
Returns a query with all accessible records for user_id or
|
||||||
@@ -5702,6 +5116,7 @@ class Service(object):
|
|||||||
self.error()
|
self.error()
|
||||||
|
|
||||||
class JsonRpcException(Exception):
|
class JsonRpcException(Exception):
|
||||||
|
|
||||||
def __init__(self, code, info):
|
def __init__(self, code, info):
|
||||||
jrpc_error = Service.jsonrpc_errors.get(code)
|
jrpc_error = Service.jsonrpc_errors.get(code)
|
||||||
if jrpc_error:
|
if jrpc_error:
|
||||||
@@ -6218,6 +5633,7 @@ class PluginManager(object):
|
|||||||
|
|
||||||
|
|
||||||
class Expose(object):
|
class Expose(object):
|
||||||
|
|
||||||
def __init__(self, base=None, basename=None, extensions=None,
|
def __init__(self, base=None, basename=None, extensions=None,
|
||||||
allow_download=True, follow_symlink_out=False):
|
allow_download=True, follow_symlink_out=False):
|
||||||
"""
|
"""
|
||||||
@@ -6245,7 +5661,8 @@ class Expose(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# why would this not be callable? but otherwise tests do not pass
|
# why would this not be callable? but otherwise tests do not pass
|
||||||
if current.session and callable(current.session.forget): current.session.forget()
|
if current.session and callable(current.session.forget):
|
||||||
|
current.session.forget()
|
||||||
self.follow_symlink_out = follow_symlink_out
|
self.follow_symlink_out = follow_symlink_out
|
||||||
self.base = self.normalize_path(
|
self.base = self.normalize_path(
|
||||||
base or os.path.join(current.request.folder, 'static'))
|
base or os.path.join(current.request.folder, 'static'))
|
||||||
@@ -7032,6 +6449,7 @@ class Wiki(object):
|
|||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
filename,
|
filename,
|
||||||
|
|||||||
+14
-3
@@ -2478,9 +2478,14 @@ class IS_LOWER(Validator):
|
|||||||
('\\xc3\\xb1', None)
|
('\\xc3\\xb1', None)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
return (to_bytes(to_unicode(value).lower()), None)
|
cast_back = lambda x: x
|
||||||
|
if isinstance(value, str):
|
||||||
|
cast_back = to_native
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
cast_back = to_bytes
|
||||||
|
value = to_unicode(value).lower()
|
||||||
|
return (cast_back(value), None)
|
||||||
|
|
||||||
|
|
||||||
class IS_UPPER(Validator):
|
class IS_UPPER(Validator):
|
||||||
@@ -2495,7 +2500,13 @@ class IS_UPPER(Validator):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
return (to_bytes(to_unicode(value).upper()), None)
|
cast_back = lambda x: x
|
||||||
|
if isinstance(value, str):
|
||||||
|
cast_back = to_native
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
cast_back = to_bytes
|
||||||
|
value = to_unicode(value).upper()
|
||||||
|
return (cast_back(value), None)
|
||||||
|
|
||||||
|
|
||||||
def urlify(s, maxlen=80, keep_underscores=False):
|
def urlify(s, maxlen=80, keep_underscores=False):
|
||||||
|
|||||||
Reference in New Issue
Block a user