2021-10-15 03:25:38 -05:00
|
|
|
"""
|
|
|
|
Custom django forms.
|
|
|
|
|
|
|
|
Note: This does not includes forms pointed directly
|
|
|
|
at the REST api to handle updates (such as /net, /ix, /fac or /org endpoints).
|
|
|
|
|
|
|
|
Look in rest.py and serializers.py for those.
|
|
|
|
"""
|
|
|
|
|
2023-10-24 20:17:03 +03:00
|
|
|
import json
|
2021-10-14 02:03:10 -05:00
|
|
|
import os.path
|
2021-10-15 01:30:04 -05:00
|
|
|
import re
|
|
|
|
import uuid
|
2021-01-13 20:35:07 +00:00
|
|
|
|
2021-07-10 10:12:35 -05:00
|
|
|
import requests
|
|
|
|
from captcha.fields import CaptchaField
|
|
|
|
from captcha.models import CaptchaStore
|
2018-11-08 19:45:21 +00:00
|
|
|
from django import forms
|
2021-07-10 10:12:35 -05:00
|
|
|
from django.conf import settings as dj_settings
|
|
|
|
from django.contrib.auth import forms as auth_forms
|
2021-10-12 11:05:25 -05:00
|
|
|
from django.core.exceptions import ValidationError
|
2019-05-02 17:57:55 +00:00
|
|
|
from django.utils import timezone
|
2023-06-20 03:26:06 +03:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2021-08-18 08:21:22 -05:00
|
|
|
from grainy.const import PERM_CRUD, PERM_DENY, PERM_READ
|
2023-10-24 20:17:03 +03:00
|
|
|
from schema import Schema, SchemaError
|
2019-05-02 17:57:55 +00:00
|
|
|
|
|
|
|
from peeringdb_server.inet import get_client_ip
|
2021-08-18 08:21:22 -05:00
|
|
|
from peeringdb_server.models import Organization, User
|
2018-11-08 19:45:21 +00:00
|
|
|
|
|
|
|
|
2021-03-09 13:30:30 -06:00
|
|
|
class OrganizationAPIKeyForm(forms.Form):
|
|
|
|
name = forms.CharField()
|
|
|
|
email = forms.EmailField()
|
|
|
|
org_id = forms.IntegerField()
|
|
|
|
|
|
|
|
|
2018-11-08 19:45:21 +00:00
|
|
|
class OrgAdminUserPermissionForm(forms.Form):
|
|
|
|
entity = forms.CharField()
|
|
|
|
perms = forms.IntegerField()
|
|
|
|
|
|
|
|
def clean_perms(self):
|
|
|
|
perms = self.cleaned_data.get("perms")
|
|
|
|
if not perms & PERM_READ:
|
|
|
|
perms = perms | PERM_READ
|
|
|
|
if perms & PERM_DENY:
|
|
|
|
perms = perms ^ PERM_DENY
|
|
|
|
if perms > PERM_CRUD or perms < PERM_READ:
|
|
|
|
raise forms.ValidationError(_("Invalid permission level"))
|
|
|
|
return perms
|
|
|
|
|
|
|
|
|
|
|
|
class AffiliateToOrgForm(forms.Form):
|
|
|
|
asn = forms.CharField(required=False)
|
|
|
|
org = forms.CharField(required=False)
|
|
|
|
|
|
|
|
def clean_org(self):
|
|
|
|
org_id = self.cleaned_data.get("org")
|
|
|
|
if not org_id:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
# if org id can be inted, an existing org id has been submitted
|
|
|
|
# otherwise an org name has been submitted that may or may not exist
|
|
|
|
try:
|
|
|
|
org_id = int(org_id)
|
|
|
|
if not Organization.objects.filter(id=org_id).exists():
|
|
|
|
if self.cleaned_data.get("asn"):
|
|
|
|
return 0
|
|
|
|
except ValueError:
|
|
|
|
try:
|
|
|
|
org = Organization.objects.get(name=org_id)
|
|
|
|
return org.id
|
|
|
|
except Organization.DoesNotExist:
|
|
|
|
self.cleaned_data["org_name"] = org_id
|
|
|
|
return 0
|
|
|
|
|
|
|
|
return org_id
|
|
|
|
|
|
|
|
def clean_asn(self):
|
|
|
|
asn = self.cleaned_data.get("asn")
|
|
|
|
if not asn:
|
|
|
|
return 0
|
|
|
|
try:
|
2020-01-08 13:29:58 -06:00
|
|
|
asn = int(re.sub(r"\D", r"", asn))
|
2018-11-08 19:45:21 +00:00
|
|
|
except ValueError:
|
|
|
|
raise forms.ValidationError(_("ASN needs to be a number"))
|
|
|
|
return asn
|
|
|
|
|
|
|
|
|
|
|
|
class PasswordChangeForm(forms.Form):
|
|
|
|
password = forms.CharField()
|
|
|
|
password_v = forms.CharField()
|
|
|
|
|
|
|
|
def clean_password(self):
|
|
|
|
password = self.cleaned_data.get("password")
|
|
|
|
if len(password) < 10:
|
2019-12-05 16:57:52 +00:00
|
|
|
raise forms.ValidationError(_("Needs to be at least 10 characters long"))
|
2018-11-08 19:45:21 +00:00
|
|
|
return password
|
|
|
|
|
|
|
|
def clean_password_v(self):
|
|
|
|
password = self.cleaned_data.get("password")
|
|
|
|
password_v = self.cleaned_data.get("password_v")
|
|
|
|
|
|
|
|
if password != password_v:
|
|
|
|
raise forms.ValidationError(
|
2019-12-05 16:57:52 +00:00
|
|
|
_("Passwords need to match"), code="password_mismatch"
|
|
|
|
)
|
2018-11-08 19:45:21 +00:00
|
|
|
return password_v
|
|
|
|
|
|
|
|
|
2023-01-18 18:32:46 +02:00
|
|
|
class UsernameChangeForm(forms.Form):
|
|
|
|
username = forms.CharField()
|
|
|
|
|
|
|
|
def clean_username(self):
|
|
|
|
username = self.cleaned_data.get("username")
|
|
|
|
|
|
|
|
if User.objects.filter(username=username).exists():
|
|
|
|
raise forms.ValidationError(_("This username is already taken"))
|
|
|
|
return username
|
|
|
|
|
|
|
|
|
2018-11-08 19:45:21 +00:00
|
|
|
class PasswordResetForm(forms.Form):
|
|
|
|
email = forms.EmailField()
|
|
|
|
|
|
|
|
|
|
|
|
class UsernameRetrieveForm(forms.Form):
|
|
|
|
email = forms.EmailField()
|
|
|
|
|
|
|
|
|
|
|
|
class UserCreationForm(auth_forms.UserCreationForm):
|
2019-05-02 17:57:55 +00:00
|
|
|
recaptcha = forms.CharField(required=False)
|
|
|
|
captcha = forms.CharField(required=False)
|
|
|
|
captcha_generator = CaptchaField(required=False)
|
|
|
|
|
2021-08-18 08:21:22 -05:00
|
|
|
require_captcha = True
|
|
|
|
|
2018-11-08 19:45:21 +00:00
|
|
|
class Meta:
|
|
|
|
model = User
|
|
|
|
fields = (
|
|
|
|
"username",
|
|
|
|
"email",
|
|
|
|
"first_name",
|
|
|
|
"last_name",
|
|
|
|
)
|
|
|
|
|
2019-05-02 17:57:55 +00:00
|
|
|
def clean(self):
|
2020-07-15 02:07:01 -05:00
|
|
|
super().clean()
|
2019-05-02 17:57:55 +00:00
|
|
|
recaptcha = self.cleaned_data.get("recaptcha", "")
|
|
|
|
captcha = self.cleaned_data.get("captcha", "")
|
|
|
|
|
2021-08-18 08:21:22 -05:00
|
|
|
if not self.require_captcha:
|
|
|
|
return
|
|
|
|
elif not recaptcha and not captcha:
|
2019-12-05 16:57:52 +00:00
|
|
|
raise forms.ValidationError(
|
|
|
|
_("Please fill out the anti-spam challenge (captcha) field")
|
|
|
|
)
|
2019-05-02 17:57:55 +00:00
|
|
|
|
|
|
|
elif recaptcha:
|
|
|
|
cpt_params = {
|
|
|
|
"secret": dj_settings.RECAPTCHA_SECRET_KEY,
|
|
|
|
"response": recaptcha,
|
2019-12-05 16:57:52 +00:00
|
|
|
"remoteip": get_client_ip(self.request),
|
2019-05-02 17:57:55 +00:00
|
|
|
}
|
2019-12-05 16:57:52 +00:00
|
|
|
cpt_response = requests.post(
|
|
|
|
dj_settings.RECAPTCHA_VERIFY_URL, params=cpt_params
|
|
|
|
).json()
|
2019-05-02 17:57:55 +00:00
|
|
|
if not cpt_response.get("success"):
|
|
|
|
raise forms.ValidationError(_("reCAPTCHA invalid"))
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
hashkey, value = captcha.split(":")
|
2019-12-05 16:57:52 +00:00
|
|
|
self.captcha_object = CaptchaStore.objects.get(
|
|
|
|
response=value, hashkey=hashkey, expiration__gt=timezone.now()
|
|
|
|
)
|
2019-05-02 17:57:55 +00:00
|
|
|
except CaptchaStore.DoesNotExist:
|
|
|
|
raise forms.ValidationError(_("captcha invalid"))
|
|
|
|
|
|
|
|
def delete_captcha(self):
|
|
|
|
captcha_object = getattr(self, "captcha_object", None)
|
|
|
|
if captcha_object:
|
|
|
|
captcha_object.delete()
|
|
|
|
|
|
|
|
|
2018-11-08 19:45:21 +00:00
|
|
|
class UserLocaleForm(forms.Form):
|
|
|
|
locale = forms.CharField()
|
|
|
|
|
|
|
|
def clean_locale(self):
|
|
|
|
loc = self.cleaned_data.get("locale")
|
|
|
|
# django.utils.translation.check_for_language() #lang_code
|
|
|
|
if loc:
|
|
|
|
return loc
|
|
|
|
return None
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = User
|
2019-12-05 16:57:52 +00:00
|
|
|
fields = "locale"
|
2021-10-12 11:05:25 -05:00
|
|
|
|
|
|
|
|
2023-10-24 20:17:03 +03:00
|
|
|
class VerifiedUpdateForm(forms.Form):
|
|
|
|
source = forms.CharField(required=False)
|
|
|
|
reason = forms.CharField(required=False)
|
|
|
|
updates = forms.JSONField(required=True)
|
|
|
|
|
|
|
|
def clean(self):
|
|
|
|
cleaned_data = super().clean()
|
|
|
|
updates = cleaned_data.get("updates")
|
|
|
|
schema = Schema([{"ref_tag": str, "obj_id": int, "data": dict}])
|
|
|
|
try:
|
|
|
|
updates = json.loads(json.dumps(updates))
|
|
|
|
schema.validate(updates)
|
|
|
|
except (json.JSONDecodeError, TypeError, SchemaError):
|
|
|
|
raise ValidationError("Malformed update data.")
|
|
|
|
|
|
|
|
|
2023-02-15 09:55:01 +02:00
|
|
|
class UserOrgForm(forms.Form):
|
|
|
|
"""
|
|
|
|
Sets primary organization of the user
|
|
|
|
"""
|
|
|
|
|
|
|
|
organization = forms.CharField()
|
|
|
|
|
|
|
|
def clean_org(self):
|
|
|
|
org = self.cleaned_data.get("organization")
|
|
|
|
if org:
|
|
|
|
return org
|
|
|
|
return None
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = User
|
|
|
|
fields = "primary_org"
|
|
|
|
|
|
|
|
|
2021-10-12 11:05:25 -05:00
|
|
|
class OrganizationLogoUploadForm(forms.ModelForm):
|
|
|
|
logo = forms.FileField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Organization
|
|
|
|
fields = ["logo"]
|
|
|
|
|
|
|
|
def clean_logo(self):
|
|
|
|
logo = self.cleaned_data["logo"]
|
|
|
|
max_size = dj_settings.ORG_LOGO_MAX_SIZE
|
|
|
|
|
2021-10-14 02:03:10 -05:00
|
|
|
# normalize the file name
|
|
|
|
ext = os.path.splitext(logo.name)[1].lower()
|
2021-10-15 01:30:04 -05:00
|
|
|
randomize = str(uuid.uuid4())[:8]
|
|
|
|
logo.name = f"org-{self.instance.id}-{randomize}{ext}"
|
2021-10-14 02:03:10 -05:00
|
|
|
|
|
|
|
# validate file type
|
|
|
|
if ext not in dj_settings.ORG_LOGO_ALLOWED_FILE_TYPE.split(","):
|
|
|
|
raise ValidationError(
|
|
|
|
_("File type %(value)s not allowed"),
|
|
|
|
code="invalid",
|
|
|
|
params={"value": ext},
|
|
|
|
)
|
|
|
|
|
|
|
|
# validate file size
|
2021-10-12 11:05:25 -05:00
|
|
|
if logo.size > max_size:
|
|
|
|
raise ValidationError(
|
|
|
|
_("File size too big, max. %(value)s"),
|
|
|
|
code="invalid",
|
|
|
|
params={"value": f"{max_size / 1024:.0f} kb"},
|
|
|
|
)
|
|
|
|
|
|
|
|
return logo
|
2022-09-12 16:29:28 +03:00
|
|
|
|
|
|
|
|
|
|
|
class OrgUserOptions(forms.ModelForm):
|
|
|
|
class Meta:
|
|
|
|
model = Organization
|
|
|
|
fields = [
|
2023-07-11 16:20:46 +03:00
|
|
|
"require_2fa",
|
2022-09-12 16:29:28 +03:00
|
|
|
"restrict_user_emails",
|
|
|
|
"email_domains",
|
|
|
|
"periodic_reauth",
|
|
|
|
"periodic_reauth_period",
|
|
|
|
]
|