SP/web2py/gluon/authapi.py

1058 lines
44 KiB
Python
Raw Normal View History

2018-10-25 15:33:07 +00:00
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
from gluon._compat import long
from gluon import current
from gluon.storage import Messages, Settings, Storage
from gluon.utils import web2py_uuid
from gluon.validators import CRYPT, IS_EMAIL, IS_EQUAL_TO, IS_INT_IN_RANGE, IS_LOWER, IS_MATCH, IS_NOT_EMPTY, \
IS_NOT_IN_DB
from pydal.objects import Table, Field, Row
import datetime
from gluon.settings import global_settings
DEFAULT = lambda: None
class AuthAPI(object):
"""
AuthAPI is a barebones Auth implementation which does not have a concept of
HTML forms or redirects, emailing or even an URL, you are responsible for
all that if you use it.
The main Auth functions such as login, logout, register, profile are designed
in a Dict In -> Dict Out logic so, for instance, if you set
registration_requires_verification you are responsible for sending the key to
the user and even rolling back the transaction if you can't do it.
NOTES: * It does not support all the callbacks Traditional Auth does yet.
Some of the callbacks will not be supported.
Check the method signatures to find out which ones are supported.
* register_fields and profile_fields settings are ignored for now.
WARNING: No builtin CSRF protection whatsoever.
"""
default_settings = {
'create_user_groups': 'user_%(id)s',
'email_case_sensitive': False,
'everybody_group_id': None,
'expiration': 3600,
'keep_session_onlogin': True,
'keep_session_onlogout': False,
'logging_enabled': True,
'login_after_registration': False,
'login_email_validate': True,
'login_userfield': None,
'logout_onlogout': None,
'long_expiration': 3600 * 24 * 30,
'ondelete': 'CASCADE',
'password_field': 'password',
'password_min_length': 4,
'registration_requires_approval': False,
'registration_requires_verification': False,
'renew_session_onlogin': True,
'renew_session_onlogout': True,
'table_event_name': 'auth_event',
'table_group_name': 'auth_group',
'table_membership_name': 'auth_membership',
'table_permission_name': 'auth_permission',
'table_user_name': 'auth_user',
'use_username': False,
'username_case_sensitive': True
}
default_messages = {
'add_group_log': 'Group %(group_id)s created',
'add_membership_log': None,
'add_permission_log': None,
'change_password_log': 'User %(id)s Password changed',
'del_group_log': 'Group %(group_id)s deleted',
'del_membership_log': None,
'del_permission_log': None,
'email_taken': 'This email already has an account',
'group_description': 'Group uniquely assigned to user %(id)s',
'has_membership_log': None,
'has_permission_log': None,
'invalid_email': 'Invalid email',
'key_verified': 'Key verified',
'invalid_login': 'Invalid login',
'invalid_password': 'Invalid password',
'invalid_user': 'Invalid user',
'invalid_key': 'Invalid key',
'invalid_username': 'Invalid username',
'logged_in': 'Logged in',
'logged_out': 'Logged out',
'login_failed_log': None,
'login_log': 'User %(id)s Logged-in',
'logout_log': 'User %(id)s Logged-out',
'mismatched_password': "Password fields don't match",
'password_changed': 'Password changed',
'profile_log': 'User %(id)s Profile updated',
'profile_updated': 'Profile updated',
'register_log': 'User %(id)s Registered',
'registration_pending': 'Registration is pending approval',
'registration_successful': 'Registration successful',
'registration_verifying': 'Registration needs verification',
'username_taken': 'Username already taken',
'verify_log': 'User %(id)s verified registration key'
}
def __init__(self, db=None, hmac_key=None, signature=True):
self.db = db
session = current.session
auth = session.auth
self.user_groups = auth and auth.user_groups or {}
now = current.request.now
# if we have auth info
# if not expired it, used it
# if expired, clear the session
# else, only clear auth info in the session
if auth:
delta = datetime.timedelta(days=0, seconds=auth.expiration)
if auth.last_visit and auth.last_visit + delta > now:
self.user = auth.user
# this is a trick to speed up sessions to avoid many writes
if (now - auth.last_visit).seconds > (auth.expiration // 10):
auth.last_visit = now
else:
self.user = None
if session.auth:
del session.auth
session.renew(clear_session=True)
else:
self.user = None
if session.auth:
del session.auth
settings = self.settings = Settings(self.__class__.default_settings)
settings.update(
extra_fields={},
hmac_key=hmac_key,
)
settings.lock_keys = True
messages = self.messages = Messages(current.T)
messages.update(self.default_messages)
messages.lock_keys = True
if signature is True:
self.define_signature()
else:
self.signature = signature or None
def __validate(self, value, requires):
if not isinstance(requires, (list, tuple)):
requires = [requires]
for validator in requires:
(value, error) = validator(value)
if error:
return (value, error)
return (value, None)
def _get_migrate(self, tablename, migrate=True):
if type(migrate).__name__ == 'str':
return (migrate + tablename + '.table')
elif not migrate:
return False
else:
return True
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 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, migrate=None,
fake_migrate=None):
"""
To be called unless tables are defined manually
Examples:
Use as::
# defines all needed tables and table files
# 'myprefix_auth_user.table', ...
auth.define_tables(migrate='myprefix_')
# defines all needed tables without migration/table files
auth.define_tables(migrate=False)
"""
db = self.db
if migrate is None:
migrate = db._migrate
if fake_migrate is None:
fake_migrate = db._fake_migrate
settings = self.settings
if username is None:
username = settings.use_username
else:
settings.use_username = username
if not self.signature:
self.define_signature()
if signature is True:
signature_list = [self.signature]
elif not signature:
signature_list = []
elif isinstance(signature, Table):
signature_list = [signature]
else:
signature_list = signature
self._table_signature_list = signature_list # Should it defined in __init__ first??
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
# cas_provider Will always be None here but we compare it anyway so subclasses can use our define_tables
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))
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
if not user_id or self.table_user()[user_id]:
self.table_event().insert(
description=str(description % vars), origin=origin, user_id=user_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 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 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 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 and user_id == self.user_id:
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 and user_id == self.user_id:
del self.user_groups[group_id]
return ret
def has_membership(self, group_id=None, user_id=None, role=None, cached=False):
"""
Checks if user is member of group_id or role
NOTE: To avoid database query at each page load that use auth.has_membership, someone can use cached=True.
If cached is set to True has_membership() check group_id or role only against auth.user_groups variable
which is populated properly only at login time. This means that if an user membership change during a
given session the user has to log off and log in again in order to auth.user_groups to be properly
recreated and reflecting the user membership modification. There is one exception to this log off and
log in process which is in case that the user change his own membership, in this case auth.user_groups
can be properly update for the actual connected user because web2py has access to the proper session
user_groups variable. To make use of this exception someone has to place an "auth.update_groups()"
instruction in his app code to force auth.user_groups to be updated. As mention this will only work if it
the user itself that change it membership not if another user, let say an administrator, change someone
else's membership.
"""
if not user_id and self.user:
user_id = self.user.id
if cached:
id_role = group_id or role
r = (user_id and id_role in self.user_groups.values()) or (user_id and id_role in self.user_groups)
else:
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
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_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 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 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 _update_session_user(self, user):
if global_settings.web2py_runtime_gae:
user = Row(self.table_user()._filter_fields(user, id=True))
delattr(user, self.settings.password_field)
else:
user = Row(user)
for key in list(user.keys()):
value = user[key]
if callable(value) or key == self.settings.password_field:
delattr(user, key)
current.session.auth = Storage(user=user,
last_visit=current.request.now,
expiration=self.settings.expiration,
hmac_key=web2py_uuid())
return user
def login_user(self, user):
"""
Logins the `user = db.auth_user(id)`
"""
user = self._update_session_user(user)
if self.settings.renew_session_onlogin:
current.session.renew(clear_session=not self.settings.keep_session_onlogin)
self.user = user
self.update_groups()
def login(self, log=DEFAULT, **kwargs):
"""
Login a user
Keyword Args:
username/email/name_of_your_username_field (string) - username
password/name_of_your_passfield (string) - user's password
remember_me (boolean) - extend the duration of the login to settings.long_expiration
"""
settings = self.settings
session = current.session
table_user = self.table_user()
if 'username' in table_user.fields or \
not settings.login_email_validate:
userfield_validator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
if not settings.username_case_sensitive:
userfield_validator = [IS_LOWER(), userfield_validator]
else:
userfield_validator = IS_EMAIL(error_message=self.messages.invalid_email)
if not settings.email_case_sensitive:
userfield_validator = [IS_LOWER(), userfield_validator]
passfield = settings.password_field
if log is DEFAULT:
log = self.messages['login_log']
user = None
# Setup the default field used for the userfield
if self.settings.login_userfield:
userfield = self.settings.login_userfield
else:
if 'username' in table_user.fields:
userfield = 'username'
else:
userfield = 'email'
# Get the userfield from kwargs and validate it
userfield_value = kwargs.get(userfield)
if userfield_value is None:
raise KeyError('%s not found in kwargs' % userfield)
validated, error = self.__validate(userfield_value, userfield_validator)
if error:
return {'errors': {userfield: error}, 'message': self.messages.invalid_login, 'user': None}
# Get the user for this userfield and check it
user = table_user(**{userfield: validated})
if user is None:
return {'errors': {userfield: self.messages.invalid_user},
'message': self.messages.invalid_login, 'user': None}
if (user.registration_key or '').startswith('pending'):
return {'errors': None, 'message': self.messages.registration_pending, 'user': None}
elif user.registration_key in ('disabled', 'blocked'):
return {'errors': None, 'message': self.messages.login_disabled, 'user': None}
elif (user.registration_key is not None and user.registration_key.strip()):
return {'errors': None, 'message': self.messages.registration_verifying, 'user': None}
# Finally verify the password
passfield = settings.password_field
password = table_user[passfield].validate(kwargs.get(passfield, ''))[0]
if password == user[passfield]:
self.login_user(user)
session.auth.expiration = \
kwargs.get('remember_me', False) and \
settings.long_expiration or \
settings.expiration
session.auth.remember_me = kwargs.get('remember_me', False)
self.log_event(log, user)
return {'errors': None, 'message': self.messages.logged_in,
'user': {k: user[k] for k in table_user.fields if table_user[k].readable}}
else:
self.log_event(self.messages['login_failed_log'], kwargs)
return {'errors': {passfield: self.messages.invalid_password},
'message': self.messages.invalid_login, 'user': None}
def logout(self, log=DEFAULT, onlogout=DEFAULT, **kwargs):
"""
Logs out user
"""
settings = self.settings
session = current.session
if onlogout is DEFAULT:
onlogout = settings.logout_onlogout
if onlogout:
onlogout(self.user)
if log is DEFAULT:
log = self.messages['logout_log']
if self.user:
self.log_event(log, self.user)
session.auth = None
self.user = None
if settings.renew_session_onlogout:
session.renew(clear_session=not settings.keep_session_onlogout)
return {'errors': None, 'message': self.messages.logged_out, 'user': None}
def register(self, log=DEFAULT, **kwargs):
"""
Register a user.
"""
table_user = self.table_user()
settings = self.settings
if self.is_logged_in():
raise AssertionError('User trying to register is logged in')
if log is DEFAULT:
log = self.messages['register_log']
if self.settings.login_userfield:
userfield = self.settings.login_userfield
elif 'username' in table_user.fields:
userfield = 'username'
else:
userfield = 'email'
# Ensure the username field is unique.
unique_validator = IS_NOT_IN_DB(self.db, table_user[userfield])
userfield_validator = table_user[userfield].requires
if userfield_validator is None:
userfield_validator = unique_validator
elif isinstance(userfield_validator, (list, tuple)):
if not any([isinstance(validator, IS_NOT_IN_DB) for validator in
userfield_validator]):
if isinstance(userfield_validator, list):
userfield_validator.append(unique_validator)
else:
userfield_validator += (unique_validator, )
elif not isinstance(userfield_validator, IS_NOT_IN_DB):
userfield_validator = [userfield_validator, unique_validator]
table_user[userfield].requires = userfield_validator
passfield = settings.password_field
try: # Make sure we have our original minimum length
table_user[passfield].requires[-1].min_length = settings.password_min_length
except:
pass
key = web2py_uuid()
if settings.registration_requires_approval:
key = 'pending-' + key
table_user.registration_key.default = key
result = table_user.validate_and_insert(**kwargs)
if result.errors:
return {'errors': result.errors.as_dict(), 'message': None, 'user': None}
user = table_user[result.id]
message = self.messages.registration_successful
if settings.create_user_groups:
d = user.as_dict()
description = self.messages.group_description % d
group_id = self.add_group(settings.create_user_groups % d, description)
self.add_membership(group_id, result.id)
if self.settings.everybody_group_id:
self.add_membership(self.settings.everybody_group_id, result)
if settings.registration_requires_verification:
d = {k: user[k] for k in table_user.fields if table_user[k].readable}
d['key'] = key
if settings.login_after_registration and not settings.registration_requires_approval:
self.login_user(user)
return {'errors': None, 'message': None, 'user': d}
if settings.registration_requires_approval:
user.update_record(registration_key='pending')
message = self.messages.registration_pending
elif settings.login_after_registration:
user.update_record(registration_key='')
self.login_user(user)
message = self.messages.logged_in
self.log_event(log, user)
return {'errors': None, 'message': message,
'user': {k: user[k] for k in table_user.fields if table_user[k].readable}}
def profile(self, log=DEFAULT, **kwargs):
"""
Lets the user change his/her profile
"""
table_user = self.table_user()
settings = self.settings
table_user[settings.password_field].writable = False
if not self.is_logged_in():
raise AssertionError('User is not logged in')
if not kwargs:
user = table_user[self.user.id]
return {'errors': None, 'message': None,
'user': {k: user[k] for k in table_user.fields if table_user[k].readable}}
result = self.db(table_user.id == self.user.id).validate_and_update(**kwargs)
user = table_user[self.user.id]
if result.errors:
return {'errors': result.errors, 'message': None,
'user': {k: user[k] for k in table_user.fields if table_user[k].readable}}
if log is DEFAULT:
log = self.messages['profile_log']
self.log_event(log, user)
self._update_session_user(user)
return {'errors': None, 'message': self.messages.profile_updated,
'user': {k: user[k] for k in table_user.fields if table_user[k].readable}}
def change_password(self, log=DEFAULT, **kwargs):
"""
Lets the user change password
Keyword Args:
old_password (string) - User's current password
new_password (string) - User's new password
new_password2 (string) - Verify the new password
"""
settings = self.settings
messages = self.messages
if not self.is_logged_in():
raise AssertionError('User is not logged in')
db = self.db
table_user = self.table_user()
s = db(table_user.id == self.user.id)
request = current.request
session = current.session
passfield = settings.password_field
requires = table_user[passfield].requires
if not isinstance(requires, (list, tuple)):
requires = [requires]
requires = list(filter(lambda t: isinstance(t, CRYPT), requires))
if requires:
requires[0] = CRYPT(**requires[0].__dict__) # Copy the existing CRYPT attributes
requires[0].min_length = 0 # But do not enforce minimum length for the old password
old_password = kwargs.get('old_password', '')
new_password = kwargs.get('new_password', '')
new_password2 = kwargs.get('new_password2', '')
validator_old = requires
validator_pass2 = IS_EQUAL_TO(new_password, error_message=messages.mismatched_password)
old_password, error_old = self.__validate(old_password, validator_old)
new_password2, error_new2 = self.__validate(new_password2, validator_pass2)
errors = {}
if error_old:
errors['old_password'] = error_old
if error_new2:
errors['new_password2'] = error_new2
if errors:
return {'errors': errors, 'message': None}
current_user = s.select(limitby=(0, 1), orderby_on_limitby=False).first()
if not old_password == current_user[passfield]:
return {'errors': {'old_password': messages.invalid_password}, 'message': None}
else:
d = {passfield: new_password}
resp = s.validate_and_update(**d)
if resp.errors:
return {'errors': {'new_password': resp.errors[passfield]}, 'message': None}
if log is DEFAULT:
log = messages['change_password_log']
self.log_event(log, self.user)
return {'errors': None, 'message': messages.password_changed}
def verify_key(self,
key=None,
ignore_approval=False,
log=DEFAULT,
):
"""
Verify a given registration_key actually exists in the user table.
Resets the key to empty string '' or 'pending' if
setttings.registration_requires_approval is true.
Keyword Args:
key (string) - User's registration key
"""
table_user = self.table_user()
user = table_user(registration_key=key)
if (user is None) or (key is None):
return {'errors': {'key': self.messages.invalid_key}, 'message': self.messages.invalid_key}
if self.settings.registration_requires_approval:
user.update_record(registration_key='pending')
result = {'errors': None, 'message': self.messages.registration_pending}
else:
user.update_record(registration_key='')
result = {'errors': None, 'message': self.messages.key_verified}
# make sure session has same user.registration_key as db record
if current.session.auth and current.session.auth.user:
current.session.auth.user.registration_key = user.registration_key
if log is DEFAULT:
log = self.messages['verify_log']
self.log_event(log, user)
return result