You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
5.3 KiB

10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
  1. import base64
  2. import binascii
  3. from datetime import timedelta as td
  4. from django import forms
  5. from django.core.exceptions import ValidationError
  6. from django.contrib.auth import authenticate
  7. from django.contrib.auth.models import User
  8. from hc.accounts.models import REPORT_CHOICES, Member
  9. from hc.api.models import TokenBucket
  10. import pytz
  11. class LowercaseEmailField(forms.EmailField):
  12. def clean(self, value):
  13. value = super(LowercaseEmailField, self).clean(value)
  14. return value.lower()
  15. class Base64Field(forms.CharField):
  16. def to_python(self, value):
  17. if value is None:
  18. return None
  19. try:
  20. return base64.b64decode(value.encode())
  21. except binascii.Error:
  22. raise ValidationError(message="Cannot decode base64")
  23. class SignupForm(forms.Form):
  24. # Call it "identity" instead of "email"
  25. # to avoid some of the dumber bots
  26. identity = LowercaseEmailField(
  27. error_messages={"required": "Please enter your email address."}
  28. )
  29. tz = forms.CharField(required=False)
  30. def clean_identity(self):
  31. v = self.cleaned_data["identity"]
  32. if len(v) > 254:
  33. raise forms.ValidationError("Address is too long.")
  34. if User.objects.filter(email=v).exists():
  35. raise forms.ValidationError(
  36. "An account with this email address already exists."
  37. )
  38. return v
  39. def clean_tz(self):
  40. # Declare tz as "clean" only if we can find it in pytz.all_timezones
  41. if self.cleaned_data["tz"] in pytz.all_timezones:
  42. return self.cleaned_data["tz"]
  43. # Otherwise, return None, and *don't* throw a validation exception:
  44. # If user's browser reports a timezone we don't recognize, we
  45. # should ignore the timezone but still save the rest of the form.
  46. class EmailLoginForm(forms.Form):
  47. # Call it "identity" instead of "email"
  48. # to avoid some of the dumber bots
  49. identity = LowercaseEmailField()
  50. def clean_identity(self):
  51. v = self.cleaned_data["identity"]
  52. if not TokenBucket.authorize_login_email(v):
  53. raise forms.ValidationError("Too many attempts, please try later.")
  54. try:
  55. self.user = User.objects.get(email=v)
  56. except User.DoesNotExist:
  57. raise forms.ValidationError("Unknown email address.")
  58. return v
  59. class PasswordLoginForm(forms.Form):
  60. email = LowercaseEmailField()
  61. password = forms.CharField()
  62. def clean(self):
  63. username = self.cleaned_data.get("email")
  64. password = self.cleaned_data.get("password")
  65. if username and password:
  66. if not TokenBucket.authorize_login_password(username):
  67. raise forms.ValidationError("Too many attempts, please try later.")
  68. self.user = authenticate(username=username, password=password)
  69. if self.user is None or not self.user.is_active:
  70. raise forms.ValidationError("Incorrect email or password.")
  71. return self.cleaned_data
  72. class ReportSettingsForm(forms.Form):
  73. reports = forms.ChoiceField(choices=REPORT_CHOICES)
  74. nag_period = forms.IntegerField(min_value=0, max_value=86400)
  75. tz = forms.CharField()
  76. def clean_nag_period(self):
  77. seconds = self.cleaned_data["nag_period"]
  78. if seconds not in (0, 3600, 86400):
  79. raise forms.ValidationError("Bad nag_period: %d" % seconds)
  80. return td(seconds=seconds)
  81. def clean_tz(self):
  82. # Declare tz as "clean" only if we can find it in pytz.all_timezones
  83. if self.cleaned_data["tz"] in pytz.all_timezones:
  84. return self.cleaned_data["tz"]
  85. # Otherwise, return None, and *don't* throw a validation exception:
  86. # If user's browser reports a timezone we don't recognize, we
  87. # should ignore the timezone but still save the rest of the form.
  88. class SetPasswordForm(forms.Form):
  89. password = forms.CharField(min_length=8)
  90. class ChangeEmailForm(forms.Form):
  91. error_css_class = "has-error"
  92. email = LowercaseEmailField()
  93. def clean_email(self):
  94. v = self.cleaned_data["email"]
  95. if User.objects.filter(email=v).exists():
  96. raise forms.ValidationError("%s is already registered" % v)
  97. return v
  98. class InviteTeamMemberForm(forms.Form):
  99. email = LowercaseEmailField(max_length=254)
  100. role = forms.ChoiceField(choices=Member.Role.choices)
  101. class RemoveTeamMemberForm(forms.Form):
  102. email = LowercaseEmailField()
  103. class ProjectNameForm(forms.Form):
  104. name = forms.CharField(max_length=60)
  105. class TransferForm(forms.Form):
  106. email = LowercaseEmailField()
  107. class AddWebAuthnForm(forms.Form):
  108. name = forms.CharField(max_length=100)
  109. client_data_json = Base64Field()
  110. attestation_object = Base64Field()
  111. class WebAuthnForm(forms.Form):
  112. credential_id = Base64Field()
  113. client_data_json = Base64Field()
  114. authenticator_data = Base64Field()
  115. signature = Base64Field()
  116. class TotpForm(forms.Form):
  117. error_css_class = "has-error"
  118. code = forms.RegexField(regex=r"^\d{6}$")
  119. def __init__(self, totp, post=None, files=None):
  120. self.totp = totp
  121. super(TotpForm, self).__init__(post, files)
  122. def clean_code(self):
  123. if not self.totp.verify(self.cleaned_data["code"], valid_window=1):
  124. raise forms.ValidationError("The code you entered was incorrect.")
  125. return self.cleaned_data["code"]