1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00
Files
peeringdb-peeringdb/peeringdb_server/forms.py
Stefan Pratter a0f1970fec Support 202211 (#1304)
* Carrier object implementation #909

* API keys: disabling of user account by a PeeringDB admin does not disable access via a User API key. Also no disable mech, only revoke. #1140

* Ops: django needs lightweight healthcheck route that confirms database connectivity #1284

* Ops: various indexes are needed #1285

* API requests with invalid Authentication headers should notify users in some way. #1220

* Allow user to change account username #1130

* UX to remove carriers from facilities more inline the other similar UX

* more UX fixes for removing carriers from facilities

* Cache hints are needed for optimal CDN use #970

* fixes Commandline tool "Run command" button gone #1278

* RIR status gets deleted when changes are made to the network #1279

* Improve MTU field #658

* CSRF cookie not set error from email confirmation view #1296

* expose CSP_CONNECT_SRC

* fix confirm email path checking in session middleware

* Ops: Emails to OPERATIONS_EMAIL need to be rate-limited #1282

* add website field to carrier ux

* website field on carrier optional with org fallback

* linting

* add *.google-analytics.com to CSP_CONNECT_SRC

* poetry relock

* fix issues with confirm-email reverse during session creation validation

* fix tests

* fix tests

* pin django-peeringdb to support_202211

* linting

* django ratelimit to <4

* regen docs

* fix automated net stats to only include networks with status `ok` #1283

* linting

* poetry lock

Co-authored-by: Matt Griswold <grizz@20c.com>
2023-01-18 10:32:46 -06:00

244 lines
6.9 KiB
Python

"""
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.
"""
import os.path
import re
import uuid
import requests
from captcha.fields import CaptchaField
from captcha.models import CaptchaStore
from django import forms
from django.conf import settings as dj_settings
from django.contrib.auth import forms as auth_forms
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from grainy.const import PERM_CRUD, PERM_DENY, PERM_READ
from peeringdb_server.inet import get_client_ip
from peeringdb_server.models import Organization, User
class OrganizationAPIKeyForm(forms.Form):
name = forms.CharField()
email = forms.EmailField()
org_id = forms.IntegerField()
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:
asn = int(re.sub(r"\D", r"", asn))
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:
raise forms.ValidationError(_("Needs to be at least 10 characters long"))
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(
_("Passwords need to match"), code="password_mismatch"
)
return password_v
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
class PasswordResetForm(forms.Form):
email = forms.EmailField()
class UsernameRetrieveForm(forms.Form):
email = forms.EmailField()
class UserCreationForm(auth_forms.UserCreationForm):
recaptcha = forms.CharField(required=False)
captcha = forms.CharField(required=False)
captcha_generator = CaptchaField(required=False)
require_captcha = True
class Meta:
model = User
fields = (
"username",
"email",
"first_name",
"last_name",
)
def clean(self):
super().clean()
recaptcha = self.cleaned_data.get("recaptcha", "")
captcha = self.cleaned_data.get("captcha", "")
if not self.require_captcha:
return
elif not recaptcha and not captcha:
raise forms.ValidationError(
_("Please fill out the anti-spam challenge (captcha) field")
)
elif recaptcha:
cpt_params = {
"secret": dj_settings.RECAPTCHA_SECRET_KEY,
"response": recaptcha,
"remoteip": get_client_ip(self.request),
}
cpt_response = requests.post(
dj_settings.RECAPTCHA_VERIFY_URL, params=cpt_params
).json()
if not cpt_response.get("success"):
raise forms.ValidationError(_("reCAPTCHA invalid"))
else:
try:
hashkey, value = captcha.split(":")
self.captcha_object = CaptchaStore.objects.get(
response=value, hashkey=hashkey, expiration__gt=timezone.now()
)
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()
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
fields = "locale"
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
# normalize the file name
ext = os.path.splitext(logo.name)[1].lower()
randomize = str(uuid.uuid4())[:8]
logo.name = f"org-{self.instance.id}-{randomize}{ext}"
# 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
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
class OrgUserOptions(forms.ModelForm):
class Meta:
model = Organization
fields = [
"restrict_user_emails",
"email_domains",
"periodic_reauth",
"periodic_reauth_period",
]