Source code for noggin.form.edit_user
import re
from urllib.parse import urlparse, urlunparse
from flask_babel import lazy_gettext as _
from pyotp import TOTP
from wtforms.fields import (
BooleanField,
EmailField,
FieldList,
HiddenField,
PasswordField,
SelectField,
StringField,
TextAreaField,
URLField,
)
from wtforms.validators import (
URL,
AnyOf,
DataRequired,
Length,
Optional,
ValidationError,
)
from noggin.form.validators import Email
from noggin.l10n import LOCALES
from noggin.utility.timezones import TIMEZONES
from .base import (
BaseForm,
CSVListField,
ModestForm,
NonEmptyFieldList,
SubmitButtonField,
TypeAndStringField,
replace,
strip,
strip_at,
)
NICK_RE = {
"irc": re.compile(r"^[a-z_\[\]\\^{}|`-][a-z0-9_\[\]\\^{}|`-]*$", re.IGNORECASE),
"matrix": re.compile(r"^[a-z0-9.=_/-]+$", re.IGNORECASE),
}
SERVER_RE = re.compile(r"^[a-z0-9][a-z0-9.-]*(:[0-9]+)?$", re.IGNORECASE)
[docs]
class ProtocolAndNickField(TypeAndStringField):
def __init__(self, *args, **kwargs):
kwargs["choices"] = [("irc", "IRC"), ("matrix", "Matrix")]
kwargs["filters"] = [replace(" ", ""), strip_at, replace("@", ":")]
kwargs["validators"] = [self._validate]
super().__init__(*args, **kwargs)
def _parse_data(self, data):
url = urlparse(data)
nick = url.path.lstrip("/")
if url.netloc:
nick = f"{nick}:{url.netloc}"
scheme = url.scheme or "irc"
return (scheme, nick)
def _serialize_data(self, scheme, value):
nick, sep_, server = value.partition(":")
return urlunparse((scheme, server.strip(), f"/{nick.strip()}", "", "", ""))
@staticmethod
def _validate(form, field):
scheme = field.subfields[0].data
value = field.subfields[1].data
if not value:
return
nick, sep_, server = value.partition(":")
if not NICK_RE[scheme].match(nick):
raise ValidationError(_("This does not look like a valid nickname."))
if server and not SERVER_RE.match(server):
raise ValidationError(_("This does not look like a valid server name."))
[docs]
class UserSettingsProfileForm(BaseForm):
firstname = StringField(
_('First Name'),
validators=[DataRequired(message=_('First name must not be empty'))],
)
lastname = StringField(
_('Last Name'),
validators=[DataRequired(message=_('Last name must not be empty'))],
)
locale = SelectField(
_('Locale'),
choices=[(locale, locale) for locale in LOCALES],
validators=[
DataRequired(message=_('Locale must not be empty')),
AnyOf(LOCALES, message=_('Locale must be a valid locale short-code')),
],
)
ircnick = NonEmptyFieldList(
ProtocolAndNickField(validators=[Optional()]),
label=_('Chat Nicknames'),
)
timezone = SelectField(
_('Timezone'),
choices=[(t, t) for t in TIMEZONES],
validators=[
DataRequired(message=_('Timezone must not be empty')),
AnyOf(TIMEZONES, message=_('Timezone must be a valid timezone')),
],
)
github = StringField(
_('GitHub Username'), validators=[Optional()], filters=[strip_at]
)
gitlab = StringField(
_('GitLab Username'), validators=[Optional()], filters=[strip_at]
)
website_url = URLField(
_('Website or Blog URL'),
validators=[Optional(), URL(message=_('Valid URL required'))],
)
rss_url = URLField(
_('RSS URL'),
validators=[Optional(), URL(message=_('Valid URL required'))],
)
is_private = BooleanField(
_('Private'),
description=_(
"Hide information from other users, see the Privacy Policy for details."
),
validators=[Optional()],
)
pronouns = CSVListField(_('Pronouns'), validators=[Optional()])
[docs]
class UserSettingsEmailForm(BaseForm):
mail = EmailField(
_('E-mail Address'),
validators=[
DataRequired(message=_('Email must not be empty')),
Email(message=_('Email must be valid')),
],
)
rhbz_mail = EmailField(_('Red Hat Bugzilla Email'), validators=[Optional()])
[docs]
class UserSettingsKeysForm(BaseForm):
sshpubkeys = FieldList(
TextAreaField(validators=[Optional()], render_kw={"rows": 4}, filters=[strip]),
label=_('SSH Keys'),
)
gpgkeys = FieldList(
StringField(validators=[Optional(), Length(min=16, max=40)]),
label=_('GPG Keys'),
)
[docs]
class UserSettingsAddOTPForm(ModestForm):
description = StringField(
_('Token name'),
validators=[Optional()],
description=_("Add an optional name to help you identify this token"),
)
password = PasswordField(
_('Enter your current password'),
validators=[DataRequired(message=_('You must provide a password'))],
description=_("please reauthenticate so we know it is you"),
)
otp = StringField(
_('One-Time Password'),
validators=[Optional()],
description=_("Enter your One-Time Password"),
)
submit = SubmitButtonField(_("Generate OTP Token"))
[docs]
class UserSettingsConfirmOTPForm(ModestForm):
secret = HiddenField(
"secret",
validators=[DataRequired(message=_('Could not find the token secret'))],
)
description = HiddenField(
"description",
validators=[Optional()],
)
code = StringField(
_("Verification Code"),
validators=[DataRequired(message=_('You must provide a verification code'))],
)
submit = SubmitButtonField(_("Verify and Enable OTP Token"))
[docs]
def validate_code(form, field):
totp = TOTP(form.secret.data)
if not totp.verify(field.data, valid_window=1):
raise ValidationError(_('The code is wrong, please try again.'))
[docs]
class UserSettingsOTPStatusChange(BaseForm):
token = HiddenField(
'token', validators=[DataRequired(message=_('Token must not be empty'))]
)
[docs]
class UserSettingsOTPNameChange(BaseForm):
token = HiddenField(
'token', validators=[DataRequired(message=_('Token must not be empty'))]
)
description = StringField(
validators=[Optional()],
)
[docs]
class UserSettingsAgreementSign(BaseForm):
agreement = HiddenField(
'agreement', validators=[DataRequired(message=_('Agreement must not be empty'))]
)