# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
from collections import OrderedDict
import six
from django import forms
from django.core.urlresolvers import Resolver404, resolve
from django.core.validators import URLValidator
from django.http import QueryDict
from .utils import Base64
[docs]class NextUrlField(forms.CharField):
"""
Custom ``CharField`` which validates that a value is a valid next URL.
It validates that by checking that the value can be resolved to a view
hence guaranteeing that when redirected URL will not fail.
"""
default_error_messages = {
'invalid_url': 'Invalid next url.'
}
[docs] def to_python(self, value):
"""
Validate that value is a valid URL for this project.
"""
value = super(NextUrlField, self).to_python(value)
if value in self.empty_values:
return value
parsed = six.moves.urllib.parse.urlparse(value)
path = parsed.path
try:
resolve(path)
except Resolver404:
raise forms.ValidationError(self.error_messages['invalid_url'])
else:
return path
[docs]class SQRLURLValidator(URLValidator):
"""
Custom URL validator which validates that a URL is a valid SQRL url.
These are the differences with regular HTTP URLs:
* scheme is either sqrl (secure) and qrl (non-secure)
* ``:`` is a valid path separator which can be used to indicate
which section of the SQRL should be used to generate
public/provate keypair for the domain.
"""
schemes = ['sqrl', 'qrl']
[docs]class SQRLURLField(forms.URLField):
"""
SQRL URL field which uses :obj:`.SQRLURLValidator` for validation.
"""
default_validators = [SQRLURLValidator()]
[docs]class Base64Field(forms.CharField):
"""
Field which decodes base64 values using :meth:`.utils.Base64.decode`.
"""
default_error_messages = {
'base64': 'Invalid value. Must be base64url encoded string.',
}
[docs] def to_python(self, value):
"""
Decodes base64 value and returns binary data.
"""
value = super(Base64Field, self).to_python(value)
if not value:
return b''
try:
return Base64.decode(value)
except (ValueError, TypeError):
raise forms.ValidationError(self.error_messages['base64'])
[docs]class Base64CharField(Base64Field):
"""
Similar to :obj:`.Base64Field` however this field normalizes to ``str`` (``unicode``) data.
"""
default_error_messages = {
'base64_ascii': 'Invalid value. Must be ascii base64url encoded string.',
}
[docs] def to_python(self, value):
"""
Returns base64 decoded data as string.
Uses :meth:`.Base64Field.to_python` to decode base64 value
which returns binary data and then this method further
decodes ascii data to return ``str`` (``unicode``) data.
"""
value = super(Base64CharField, self).to_python(value)
if not value:
return ''
try:
return value.decode('ascii')
except UnicodeDecodeError:
raise forms.ValidationError(self.error_messages['base64_ascii'])
[docs]class Base64PairsField(Base64CharField):
"""
Field which normalizes base64 encoded multistring key-value pairs to ``OrderedDict``.
Attributes
----------
always_pairs : bool
Boolean which enforces that the value must always be keypairs.
When ``False`` and the value is not a keypair, the value itself
is returned.
"""
default_error_messages = {
'crlf': 'Invalid value. Must be multi-line string separated by CRLF.',
'pairs': 'Invalid value. Must be multi-line string of pair of values.',
}
always_pairs = True
[docs] def to_python(self, value):
"""
Normalizes multiline base64 keypairs string to ``OrderedDict``.
"""
value = super(Base64PairsField, self).to_python(value)
if not value:
return OrderedDict()
if not value.endswith('\r\n'):
if self.always_pairs:
raise forms.ValidationError(self.error_messages['crlf'])
else:
return value
try:
return OrderedDict(
line.split('=', 1) for line in filter(None, value.splitlines())
)
except ValueError:
raise forms.ValidationError(self.error_messages['pairs'])
[docs]class Base64ConditionalPairsField(Base64PairsField):
"""
Similar to :obj:`.Base64PairsField` but this field does not force
the value to be keypairs.
"""
always_pairs = False
[docs]class TildeMultipleValuesField(forms.CharField):
"""
Field which returns tilde-separated list.
"""
[docs] def to_python(self, value):
"""
Normalizes to a Python list by splitting string by tilde (~) delimiter.
"""
value = super(TildeMultipleValuesField, self).to_python(value)
if not value:
return []
return value.split('~')
[docs]class TildeMultipleValuesFieldChoiceField(TildeMultipleValuesField, forms.ChoiceField):
"""
Similar to :obj:`.TildeMultipleValuesField` however this field also validates
each value to be a valid choice.
"""
[docs] def validate(self, value):
for i in value:
super(TildeMultipleValuesFieldChoiceField, self).validate(i)