Source code for pluserable.schemas

"""Colander and Deform schemas."""

import re
from typing import List

from bag.text import strip_preparer, strip_lower_preparer
import colander as c
from deform.schema import CSRFSchema
import deform.widget as w
from kerno.kerno import Kerno
from kerno.web.pyramid import KRequest

from pluserable.strings import get_strings, _


[docs]def email_exists(node, val): """Colander validator that ensures a User exists with the email.""" request: KRequest = node.bindings["request"] user = request.repo.get_user_by_email(val) if not user: raise c.Invalid( node, get_strings(request.registry).reset_password_email_must_exist.format(val), )
[docs]def unique_email(node, val): """Colander validator that ensures the email does not exist.""" request: KRequest = node.bindings["request"] other = request.repo.get_user_by_email(val) if other: raise c.Invalid( node, get_strings(request.registry).registration_email_exists.format(other.email), )
[docs]def email_domain_allowed(node, val): """Colander validator that blocks configured email domains.""" kerno: Kerno = node.bindings["kerno"] request: KRequest = node.bindings["request"] blocked_domains: List[str] = kerno.settings["pluserable"].get( "email_domains_blacklist", [] ) try: left, domain = val.split("@", 1) except ValueError: raise c.Invalid(node, "An email address must contain an @ character") if domain in blocked_domains: raise c.Invalid( node, get_strings(request.registry).email_domain_blocked.format(domain), )
[docs]def unique_username(node, val): """Colander validator that ensures the username does not exist.""" request: KRequest = node.bindings["request"] user = request.repo.get_user_by_username(val) if user is not None: raise c.Invalid( node, get_strings(request.registry).registration_username_exists )
[docs]def unix_username(node, value): """Colander validator that ensures the username is alphanumeric.""" request: KRequest = node.bindings["request"] if not ALPHANUM.match(value): raise c.Invalid(node, get_strings(request.registry).unacceptable_characters)
ALPHANUM = re.compile(r"^[a-zA-Z0-9_.-]+$")
[docs]def username_does_not_contain_at(node, value): """Ensure the username does not contain an ``@`` character. This is important because the system can be configured to accept an email or a username in the same field at login time, so the presence or absence of the @ tells us whether it is an email address. This Colander validator is not being used by default. We are using the ``unix_username`` validator which does more. But we are keeping this validator here in case someone wishes to use it instead of ``unix_username``. """ request: KRequest = node.bindings["request"] if "@" in value: raise c.Invalid(node, get_strings(request.registry).username_may_not_contain_at)
# Schema fragments # ---------------- # These functions reduce duplication in the schemas defined below, # while ensuring some constant values are consistent among those schemas.
[docs]def get_username_creation_node( title=_("User name"), description=_("Name with which you will log in"), validator=None, ): """Return a reusable username node for Colander schemas.""" return c.SchemaNode( c.String(), title=title, description=description, preparer=strip_preparer, validator=validator or c.All(c.Length(max=30), unix_username, unique_username), )
[docs]def get_email_node(validator=None, description=None): """Return a reusable email address node for Colander schemas.""" return c.SchemaNode( c.String(), title=_("Email"), description=description, preparer=strip_lower_preparer, validator=validator or c.All(c.Email(), unique_email, email_domain_allowed), widget=w.TextInputWidget( size=40, maxlength=260, type="email", placeholder=_("joe@example.com"), ), )
[docs]def get_checked_password_node( description=_( "Your password must be harder than a dictionary word or proper name!" ), **kw, ): return c.SchemaNode( c.String(), title=_("Password"), validator=c.Length(min=4), widget=w.CheckedPasswordWidget(), description=description, **kw, )
# Schemas # -------
[docs]class UsernameLoginSchema(CSRFSchema): handle = c.SchemaNode(c.String(), title=_("User name"), preparer=strip_preparer) password = c.SchemaNode(c.String(), widget=w.PasswordWidget())
[docs]class EmailLoginSchema(CSRFSchema): """For login, some apps just use email and have no username column.""" handle = get_email_node(validator=c.Email()) password = c.SchemaNode(c.String(), widget=w.PasswordWidget())
[docs]class UsernameRegisterSchema(CSRFSchema): username = get_username_creation_node() email = get_email_node() password = get_checked_password_node()
[docs]class EmailRegisterSchema(CSRFSchema): email = get_email_node() password = get_checked_password_node()
[docs]class ForgotPasswordSchema(CSRFSchema): email = get_email_node( validator=c.All(c.Email(), email_exists), description=_("The email address under which you have your account."), )
[docs]class UsernameResetPasswordSchema(CSRFSchema): username = c.SchemaNode( c.String(), title=_("User name"), missing=c.null, preparer=strip_preparer, widget=w.TextInputWidget(template="readonly/textinput"), ) password = get_checked_password_node()
[docs]class EmailResetPasswordSchema(CSRFSchema): email = c.SchemaNode( c.String(), title=_("Email"), missing=c.null, preparer=strip_lower_preparer, widget=w.TextInputWidget(template="readonly/textinput"), ) password = get_checked_password_node()
[docs]class UsernameProfileSchema(CSRFSchema): username = c.SchemaNode( c.String(), widget=w.TextInputWidget(template="readonly/textinput"), preparer=strip_preparer, missing=c.null, ) email = get_email_node(description=None, validator=c.Email()) password = get_checked_password_node(missing=c.null)
[docs]class EmailProfileSchema(CSRFSchema): email = get_email_node(description=None, validator=c.Email()) password = get_checked_password_node(missing=c.null)