serializers.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. import binascii
  2. import json
  3. import re
  4. from base64 import urlsafe_b64decode, urlsafe_b64encode
  5. import psl_dns
  6. from django.core.validators import MinValueValidator
  7. from django.db.models import Model, Q
  8. from rest_framework import serializers
  9. from rest_framework.exceptions import ValidationError
  10. from rest_framework.serializers import ListSerializer
  11. from rest_framework.settings import api_settings
  12. from rest_framework.validators import UniqueTogetherValidator, UniqueValidator, qs_filter
  13. from api import settings
  14. # TODO organize imports
  15. from desecapi.models import Domain, Donation, User, RRset, Token, RR, AuthenticatedUserAction, \
  16. AuthenticatedActivateUserAction, AuthenticatedChangeEmailUserAction, \
  17. AuthenticatedDeleteUserAction, AuthenticatedResetPasswordUserAction, AuthenticatedAction
  18. class TokenSerializer(serializers.ModelSerializer):
  19. auth_token = serializers.ReadOnlyField(source='key')
  20. # note this overrides the original "id" field, which is the db primary key
  21. id = serializers.ReadOnlyField(source='user_specific_id')
  22. class Meta:
  23. model = Token
  24. fields = ('id', 'created', 'name', 'auth_token',)
  25. read_only_fields = ('created', 'auth_token', 'id')
  26. class RequiredOnPartialUpdateCharField(serializers.CharField):
  27. """
  28. This field is always required, even for partial updates (e.g. using PATCH).
  29. """
  30. def validate_empty_values(self, data):
  31. if data is serializers.empty:
  32. self.fail('required')
  33. return super().validate_empty_values(data)
  34. class Validator:
  35. message = 'This field did not pass validation.'
  36. def __init__(self, message=None):
  37. self.field_name = None
  38. self.message = message or self.message
  39. self.instance = None
  40. def __call__(self, value):
  41. raise NotImplementedError
  42. def __repr__(self):
  43. return '<%s>' % self.__class__.__name__
  44. class ReadOnlyOnUpdateValidator(Validator):
  45. message = 'Can only be written on create.'
  46. def set_context(self, serializer_field):
  47. """
  48. This hook is called by the serializer instance,
  49. prior to the validation call being made.
  50. """
  51. self.field_name = serializer_field.source_attrs[-1]
  52. self.instance = getattr(serializer_field.parent, 'instance', None)
  53. def __call__(self, value):
  54. if isinstance(self.instance, Model) and value != getattr(self.instance, self.field_name):
  55. raise serializers.ValidationError(self.message, code='read-only-on-update')
  56. class StringField(serializers.CharField):
  57. def to_internal_value(self, data):
  58. return data
  59. def run_validation(self, data=serializers.empty):
  60. data = super().run_validation(data)
  61. if not isinstance(data, str):
  62. raise serializers.ValidationError('Must be a string.', code='must-be-a-string')
  63. return data
  64. class RRsField(serializers.ListField):
  65. def __init__(self, **kwargs):
  66. super().__init__(child=StringField(), **kwargs)
  67. def to_representation(self, data):
  68. return [rr.content for rr in data.all()]
  69. class ConditionalExistenceModelSerializer(serializers.ModelSerializer):
  70. """
  71. Only considers data with certain condition as existing data.
  72. If the existence condition does not hold, given instances are deleted, and no new instances are created,
  73. respectively. Also, to_representation and data will return None.
  74. Contrary, if the existence condition holds, the behavior is the same as DRF's ModelSerializer.
  75. """
  76. def exists(self, arg):
  77. """
  78. Determine if arg is to be considered existing.
  79. :param arg: Either a model instance or (possibly invalid!) data object.
  80. :return: Whether we treat this as non-existing instance.
  81. """
  82. raise NotImplementedError
  83. def to_representation(self, instance):
  84. return None if not self.exists(instance) else super().to_representation(instance)
  85. @property
  86. def data(self):
  87. try:
  88. return super().data
  89. except TypeError:
  90. return None
  91. def save(self, **kwargs):
  92. validated_data = {}
  93. validated_data.update(self.validated_data)
  94. validated_data.update(kwargs)
  95. known_instance = self.instance is not None
  96. data_exists = self.exists(validated_data)
  97. if known_instance and data_exists:
  98. self.instance = self.update(self.instance, validated_data)
  99. elif known_instance and not data_exists:
  100. self.delete()
  101. elif not known_instance and data_exists:
  102. self.instance = self.create(validated_data)
  103. elif not known_instance and not data_exists:
  104. pass # nothing to do
  105. return self.instance
  106. def delete(self):
  107. self.instance.delete()
  108. class NonBulkOnlyDefault:
  109. """
  110. This class may be used to provide default values that are only used
  111. for non-bulk operations, but that do not return any value for bulk
  112. operations.
  113. Implementation inspired by CreateOnlyDefault.
  114. """
  115. def __init__(self, default):
  116. self.default = default
  117. def set_context(self, serializer_field):
  118. # noinspection PyAttributeOutsideInit
  119. self.is_many = getattr(serializer_field.root, 'many', False)
  120. if callable(self.default) and hasattr(self.default, 'set_context') and not self.is_many:
  121. # noinspection PyUnresolvedReferences
  122. self.default.set_context(serializer_field)
  123. def __call__(self):
  124. if self.is_many:
  125. raise serializers.SkipField()
  126. if callable(self.default):
  127. return self.default()
  128. return self.default
  129. def __repr__(self):
  130. return '%s(%s)' % (self.__class__.__name__, repr(self.default))
  131. class RRsetSerializer(ConditionalExistenceModelSerializer):
  132. domain = serializers.SlugRelatedField(read_only=True, slug_field='name')
  133. records = RRsField(allow_empty=True)
  134. ttl = serializers.IntegerField(max_value=604800)
  135. class Meta:
  136. model = RRset
  137. fields = ('domain', 'subname', 'name', 'records', 'ttl', 'type',)
  138. extra_kwargs = {
  139. 'subname': {'required': False, 'default': NonBulkOnlyDefault('')}
  140. }
  141. def __init__(self, instance=None, data=serializers.empty, domain=None, **kwargs):
  142. if domain is None:
  143. raise ValueError('RRsetSerializer() must be given a domain object (to validate uniqueness constraints).')
  144. self.domain = domain
  145. super().__init__(instance, data, **kwargs)
  146. @classmethod
  147. def many_init(cls, *args, **kwargs):
  148. domain = kwargs.pop('domain')
  149. kwargs['child'] = cls(domain=domain)
  150. return RRsetListSerializer(*args, **kwargs)
  151. def get_fields(self):
  152. fields = super().get_fields()
  153. fields['subname'].validators.append(ReadOnlyOnUpdateValidator())
  154. fields['type'].validators.append(ReadOnlyOnUpdateValidator())
  155. fields['ttl'].validators.append(MinValueValidator(limit_value=self.domain.minimum_ttl))
  156. return fields
  157. def get_validators(self):
  158. return [UniqueTogetherValidator(
  159. self.domain.rrset_set, ('subname', 'type'),
  160. message='Another RRset with the same subdomain and type exists for this domain.'
  161. )]
  162. @staticmethod
  163. def validate_type(value):
  164. if value in RRset.DEAD_TYPES:
  165. raise serializers.ValidationError(
  166. "The %s RRset type is currently unsupported." % value)
  167. if value in RRset.RESTRICTED_TYPES:
  168. raise serializers.ValidationError(
  169. "You cannot tinker with the %s RRset." % value)
  170. if value.startswith('TYPE'):
  171. raise serializers.ValidationError(
  172. "Generic type format is not supported.")
  173. return value
  174. def validate_records(self, value):
  175. # `records` is usually allowed to be empty (for idempotent delete), except for POST requests which are intended
  176. # for RRset creation only. We use the fact that DRF generic views pass the request in the serializer context.
  177. request = self.context.get('request')
  178. if request and request.method == 'POST' and not value:
  179. raise serializers.ValidationError('This field must not be empty when using POST.')
  180. return value
  181. def exists(self, arg):
  182. if isinstance(arg, RRset):
  183. return arg.records.exists()
  184. else:
  185. return bool(arg.get('records')) if 'records' in arg.keys() else True
  186. def create(self, validated_data):
  187. rrs_data = validated_data.pop('records')
  188. rrset = RRset.objects.create(**validated_data)
  189. self._set_all_record_contents(rrset, rrs_data)
  190. return rrset
  191. def update(self, instance: RRset, validated_data):
  192. rrs_data = validated_data.pop('records', None)
  193. if rrs_data is not None:
  194. self._set_all_record_contents(instance, rrs_data)
  195. ttl = validated_data.pop('ttl', None)
  196. if ttl and instance.ttl != ttl:
  197. instance.ttl = ttl
  198. instance.save()
  199. return instance
  200. @staticmethod
  201. def _set_all_record_contents(rrset: RRset, record_contents):
  202. """
  203. Updates this RR set's resource records, discarding any old values.
  204. To do so, two large select queries and one query per changed (added or removed) resource record are needed.
  205. Changes are saved to the database immediately.
  206. :param rrset: the RRset at which we overwrite all RRs
  207. :param record_contents: set of strings
  208. """
  209. # Remove RRs that we didn't see in the new list
  210. removed_rrs = rrset.records.exclude(content__in=record_contents) # one SELECT
  211. for rr in removed_rrs:
  212. rr.delete() # one DELETE query
  213. # Figure out which entries in record_contents have not changed
  214. unchanged_rrs = rrset.records.filter(content__in=record_contents) # one SELECT
  215. unchanged_content = [unchanged_rr.content for unchanged_rr in unchanged_rrs]
  216. added_content = filter(lambda c: c not in unchanged_content, record_contents)
  217. rrs = [RR(rrset=rrset, content=content) for content in added_content]
  218. RR.objects.bulk_create(rrs) # One INSERT
  219. class RRsetListSerializer(ListSerializer):
  220. default_error_messages = {
  221. **serializers.Serializer.default_error_messages,
  222. **ListSerializer.default_error_messages,
  223. **{'not_a_list': 'Expected a list of items but got {input_type}.'},
  224. }
  225. @staticmethod
  226. def _key(data_item):
  227. return data_item.get('subname', None), data_item.get('type', None)
  228. def to_internal_value(self, data):
  229. if not isinstance(data, list):
  230. message = self.error_messages['not_a_list'].format(input_type=type(data).__name__)
  231. raise serializers.ValidationError({api_settings.NON_FIELD_ERRORS_KEY: [message]}, code='not_a_list')
  232. if not self.allow_empty and len(data) == 0:
  233. if self.parent and self.partial:
  234. raise serializers.SkipField()
  235. else:
  236. self.fail('empty')
  237. ret = []
  238. errors = []
  239. partial = self.partial
  240. # build look-up objects for instances and data, so we can look them up with their keys
  241. try:
  242. known_instances = {(x.subname, x.type): x for x in self.instance}
  243. except TypeError: # in case self.instance is None (as during POST)
  244. known_instances = {}
  245. indices_by_key = {}
  246. for idx, item in enumerate(data):
  247. # Validate item type before using anything from it
  248. if not isinstance(item, dict):
  249. self.fail('invalid', datatype=type(item).__name__)
  250. items = indices_by_key.setdefault(self._key(item), set())
  251. items.add(idx)
  252. # Iterate over all rows in the data given
  253. for idx, item in enumerate(data):
  254. try:
  255. # see if other rows have the same key
  256. if len(indices_by_key[self._key(item)]) > 1:
  257. raise serializers.ValidationError({
  258. '__all__': [
  259. 'Same subname and type as in position(s) %s, but must be unique.' %
  260. ', '.join(map(str, indices_by_key[self._key(item)] - {idx}))
  261. ]
  262. })
  263. # determine if this is a partial update (i.e. PATCH):
  264. # we allow partial update if a partial update method (i.e. PATCH) is used, as indicated by self.partial,
  265. # and if this is not actually a create request because it is unknown and nonempty
  266. unknown = self._key(item) not in known_instances.keys()
  267. nonempty = item.get('records', None) != []
  268. self.partial = partial and not (unknown and nonempty)
  269. self.child.instance = known_instances.get(self._key(item), None)
  270. # with partial value and instance in place, let the validation begin!
  271. validated = self.child.run_validation(item)
  272. except serializers.ValidationError as exc:
  273. errors.append(exc.detail)
  274. else:
  275. ret.append(validated)
  276. errors.append({})
  277. self.partial = partial
  278. if any(errors):
  279. raise serializers.ValidationError(errors)
  280. return ret
  281. def update(self, instance, validated_data):
  282. """
  283. Creates, updates and deletes RRsets according to the validated_data given. Relevant instances must be passed as
  284. a queryset in the `instance` argument.
  285. RRsets that appear in `instance` are considered "known", other RRsets are considered "unknown". RRsets that
  286. appear in `validated_data` with records == [] are considered empty, otherwise non-empty.
  287. The update proceeds as follows:
  288. 1. All unknown, non-empty RRsets are created.
  289. 2. All known, non-empty RRsets are updated.
  290. 3. All known, empty RRsets are deleted.
  291. 4. Unknown, empty RRsets will not cause any action.
  292. Rationale:
  293. As both "known"/"unknown" and "empty"/"non-empty" are binary partitions on `everything`, the combination of
  294. both partitions `everything` in four disjoint subsets. Hence, every RRset in `everything` is taken care of.
  295. empty | non-empty
  296. ------- | -------- | -----------
  297. known | delete | update
  298. unknown | no-op | create
  299. :param instance: QuerySet of relevant RRset objects, i.e. the Django.Model subclass instances. Relevant are all
  300. instances that are referenced in `validated_data`. If a referenced RRset is missing from instances, it will be
  301. considered unknown and hence be created. This may cause a database integrity error. If an RRset is given, but
  302. not relevant (i.e. not referred to by `validated_data`), a ValueError will be raised.
  303. :param validated_data: List of RRset data objects, i.e. dictionaries.
  304. :return: List of RRset objects (Django.Model subclass) that have been created or updated.
  305. """
  306. def is_empty(data_item):
  307. return data_item.get('records', None) == []
  308. query = Q()
  309. for item in validated_data:
  310. query |= Q(type=item['type'], subname=item['subname']) # validation has ensured these fields exist
  311. instance = instance.filter(query)
  312. instance_index = {(rrset.subname, rrset.type): rrset for rrset in instance}
  313. data_index = {self._key(data): data for data in validated_data}
  314. if data_index.keys() | instance_index.keys() != data_index.keys():
  315. raise ValueError('Given set of known RRsets (`instance`) is not a subset of RRsets referred to in'
  316. '`validated_data`. While this would produce a correct result, this is illegal due to its'
  317. ' inefficiency.')
  318. everything = instance_index.keys() | data_index.keys()
  319. known = instance_index.keys()
  320. unknown = everything - known
  321. # noinspection PyShadowingNames
  322. empty = {self._key(data) for data in validated_data if is_empty(data)}
  323. nonempty = everything - empty
  324. # noinspection PyUnusedLocal
  325. noop = unknown & empty
  326. created = unknown & nonempty
  327. updated = known & nonempty
  328. deleted = known & empty
  329. ret = []
  330. for subname, type_ in created:
  331. ret.append(self.child.create(
  332. validated_data=data_index[(subname, type_)]
  333. ))
  334. for subname, type_ in updated:
  335. ret.append(self.child.update(
  336. instance=instance_index[(subname, type_)],
  337. validated_data=data_index[(subname, type_)]
  338. ))
  339. for subname, type_ in deleted:
  340. instance_index[(subname, type_)].delete()
  341. return ret
  342. class DomainSerializer(serializers.ModelSerializer):
  343. psl = psl_dns.PSL(resolver=settings.PSL_RESOLVER)
  344. class Meta:
  345. model = Domain
  346. fields = ('created', 'published', 'name', 'keys', 'minimum_ttl',)
  347. extra_kwargs = {
  348. 'name': {'trim_whitespace': False},
  349. 'published': {'read_only': True},
  350. 'minimum_ttl': {'read_only': True},
  351. }
  352. def get_fields(self):
  353. fields = super().get_fields()
  354. fields['name'].validators.append(ReadOnlyOnUpdateValidator())
  355. return fields
  356. def validate_name(self, value):
  357. # Check if domain is a public suffix
  358. try:
  359. public_suffix = self.psl.get_public_suffix(value)
  360. is_public_suffix = self.psl.is_public_suffix(value)
  361. except psl_dns.exceptions.UnsupportedRule as e:
  362. # It would probably be fine to just create the domain (with the TLD acting as the
  363. # public suffix and setting both public_suffix and is_public_suffix accordingly).
  364. # However, in order to allow to investigate the situation, it's better not catch
  365. # this exception. Our error handler turns it into a 503 error and makes sure
  366. # admins are notified.
  367. raise e
  368. is_restricted_suffix = is_public_suffix and value not in settings.LOCAL_PUBLIC_SUFFIXES
  369. # Generate a list of all domains connecting this one and its public suffix.
  370. # If another user owns a zone with one of these names, then the requested
  371. # domain is unavailable because it is part of the other user's zone.
  372. private_components = value.rsplit(public_suffix, 1)[0].rstrip('.')
  373. private_components = private_components.split('.') if private_components else []
  374. private_components += [public_suffix]
  375. private_domains = ['.'.join(private_components[i:]) for i in range(0, len(private_components) - 1)]
  376. assert is_public_suffix or value == private_domains[0]
  377. # Deny registration for non-local public suffixes and for domains covered by other users' zones
  378. owner = self.context['request'].user
  379. queryset = Domain.objects.filter(Q(name__in=private_domains) & ~Q(owner=owner))
  380. if is_restricted_suffix or queryset.exists():
  381. msg = 'This domain name is unavailable.'
  382. raise serializers.ValidationError(msg, code='name_unavailable')
  383. return value
  384. def validate(self, attrs): # TODO I believe this should be a permission, not a validation
  385. # Check user's domain limit
  386. owner = self.context['request'].user
  387. if (owner.limit_domains is not None and
  388. owner.domains.count() >= owner.limit_domains):
  389. msg = 'You reached the maximum number of domains allowed for your account.'
  390. raise serializers.ValidationError(msg, code='domain_limit')
  391. return attrs
  392. class DonationSerializer(serializers.ModelSerializer):
  393. class Meta:
  394. model = Donation
  395. fields = ('name', 'iban', 'bic', 'amount', 'message', 'email')
  396. @staticmethod
  397. def validate_bic(value):
  398. return re.sub(r'[\s]', '', value)
  399. @staticmethod
  400. def validate_iban(value):
  401. return re.sub(r'[\s]', '', value)
  402. class UserSerializer(serializers.ModelSerializer):
  403. class Meta:
  404. model = User
  405. fields = ('created', 'email', 'id', 'limit_domains', 'password',)
  406. extra_kwargs = {
  407. 'password': {
  408. 'write_only': True, # Do not expose password field
  409. }
  410. }
  411. def create(self, validated_data):
  412. return User.objects.create_user(**validated_data)
  413. class RegisterAccountSerializer(UserSerializer):
  414. domain = serializers.CharField(required=False) # TODO Needs more validation
  415. class Meta:
  416. model = UserSerializer.Meta.model
  417. fields = ('email', 'password', 'domain')
  418. extra_kwargs = UserSerializer.Meta.extra_kwargs
  419. def create(self, validated_data):
  420. validated_data.pop('domain', None)
  421. return super().create(validated_data)
  422. class EmailSerializer(serializers.Serializer):
  423. email = serializers.EmailField()
  424. class EmailPasswordSerializer(EmailSerializer):
  425. password = serializers.CharField()
  426. class ChangeEmailSerializer(serializers.Serializer):
  427. new_email = serializers.EmailField()
  428. def validate_new_email(self, value):
  429. if value == self.context['request'].user.email:
  430. raise serializers.ValidationError('Email address unchanged.')
  431. return value
  432. class CustomFieldNameUniqueValidator(UniqueValidator):
  433. """
  434. Does exactly what rest_framework's UniqueValidator does, however allows to further customize the
  435. query that is used to determine the uniqueness.
  436. More specifically, we allow that the field name the value is queried against is passed when initializing
  437. this validator. (At the time of writing, UniqueValidator insists that the field's name is used for the
  438. database query field; only how the lookup must match is allowed to be changed.)
  439. """
  440. def __init__(self, queryset, message=None, lookup='exact', lookup_field=None):
  441. self.lookup_field = lookup_field
  442. super().__init__(queryset, message, lookup)
  443. def filter_queryset(self, value, queryset):
  444. """
  445. Filter the queryset to all instances matching the given value on the specified lookup field.
  446. """
  447. filter_kwargs = {'%s__%s' % (self.lookup_field or self.field_name, self.lookup): value}
  448. return qs_filter(queryset, **filter_kwargs)
  449. class AuthenticatedActionSerializer(serializers.ModelSerializer):
  450. mac = serializers.CharField() # serializer read-write, but model read-only field
  451. class Meta:
  452. model = AuthenticatedAction
  453. fields = ('mac', 'created')
  454. @classmethod
  455. def _pack_code(cls, unpacked_data):
  456. return urlsafe_b64encode(json.dumps(unpacked_data).encode()).decode()
  457. @classmethod
  458. def _unpack_code(cls, packed_data):
  459. try:
  460. return json.loads(urlsafe_b64decode(packed_data.encode()).decode())
  461. except (TypeError, UnicodeDecodeError, UnicodeEncodeError, json.JSONDecodeError, binascii.Error):
  462. raise ValueError
  463. def to_representation(self, instance: AuthenticatedUserAction):
  464. # do the regular business
  465. data = super().to_representation(instance)
  466. # encode into single string
  467. return {'code': self._pack_code(data)}
  468. def to_internal_value(self, data):
  469. data = data.copy() # avoid side effect from .pop
  470. try:
  471. # decode from single string
  472. unpacked_data = self._unpack_code(data.pop('code'))
  473. except KeyError:
  474. raise ValidationError({'code': ['No verification code.']})
  475. except ValueError:
  476. raise ValidationError({'code': ['Invalid verification code.']})
  477. # add extra fields added by the user
  478. unpacked_data.update(**data)
  479. # do the regular business
  480. return super().to_internal_value(unpacked_data)
  481. def validate(self, attrs):
  482. if not self.instance:
  483. self.instance = self.Meta.model(**attrs) # TODO This creates an attribute on self. Side-effect intended?
  484. # check if expired
  485. expired = not self.instance.check_expiration(settings.VALIDITY_PERIOD_VERIFICATION_SIGNATURE)
  486. if expired:
  487. raise ValidationError(detail='Code expired, please restart the process.', code='expired')
  488. # check if MAC valid
  489. mac_valid = self.instance.check_mac(attrs['mac'])
  490. if not mac_valid:
  491. raise ValidationError(detail='Bad signature.', code='bad_sig')
  492. return attrs
  493. def act(self):
  494. self.instance.act()
  495. return self.instance
  496. def save(self, **kwargs):
  497. raise ValueError
  498. class AuthenticatedUserActionSerializer(AuthenticatedActionSerializer):
  499. user = serializers.PrimaryKeyRelatedField(
  500. queryset=User.objects.all(),
  501. error_messages={'does_not_exist': 'This user does not exist.'}
  502. )
  503. class Meta:
  504. model = AuthenticatedUserAction
  505. fields = AuthenticatedActionSerializer.Meta.fields + ('user',)
  506. class AuthenticatedActivateUserActionSerializer(AuthenticatedUserActionSerializer):
  507. class Meta(AuthenticatedUserActionSerializer.Meta):
  508. model = AuthenticatedActivateUserAction
  509. fields = AuthenticatedUserActionSerializer.Meta.fields + ('domain',)
  510. extra_kwargs = {
  511. 'domain': {'default': None, 'allow_null': True}
  512. }
  513. class AuthenticatedChangeEmailUserActionSerializer(AuthenticatedUserActionSerializer):
  514. new_email = serializers.EmailField(
  515. validators=[
  516. CustomFieldNameUniqueValidator(
  517. queryset=User.objects.all(),
  518. lookup_field='email',
  519. message='You already have another account with this email address.',
  520. )
  521. ],
  522. required=True,
  523. )
  524. class Meta(AuthenticatedUserActionSerializer.Meta):
  525. model = AuthenticatedChangeEmailUserAction
  526. fields = AuthenticatedUserActionSerializer.Meta.fields + ('new_email',)
  527. class AuthenticatedResetPasswordUserActionSerializer(AuthenticatedUserActionSerializer):
  528. new_password = serializers.CharField(write_only=True)
  529. class Meta(AuthenticatedUserActionSerializer.Meta):
  530. model = AuthenticatedResetPasswordUserAction
  531. fields = AuthenticatedUserActionSerializer.Meta.fields + ('new_password',)
  532. class AuthenticatedDeleteUserActionSerializer(AuthenticatedUserActionSerializer):
  533. class Meta(AuthenticatedUserActionSerializer.Meta):
  534. model = AuthenticatedDeleteUserAction