From 64d8da21c6515315d19c8bc76128d15474f4031e Mon Sep 17 00:00:00 2001 From: Martin Basti Date: Thu, 27 Mar 2014 14:36:39 +0100 Subject: DNSNameParam parameter New param type for domain names Part of ticket: IPA should allow internationalized domain names https://fedorahosted.org/freeipa/ticket/3169 Reviewed-By: Jan Cholasta --- ipalib/__init__.py | 3 ++- ipalib/parameters.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ make-lint | 1 + 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/ipalib/__init__.py b/ipalib/__init__.py index 2a87103b8..6895b46d4 100644 --- a/ipalib/__init__.py +++ b/ipalib/__init__.py @@ -886,7 +886,8 @@ from frontend import Command, LocalOrRemote, Updater, Advice from frontend import Object, Method from crud import Create, Retrieve, Update, Delete, Search from parameters import DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str, Password, DNParam, DeprecatedParam -from parameters import BytesEnum, StrEnum, IntEnum, AccessTime, File, DateTime +from parameters import (BytesEnum, StrEnum, IntEnum, AccessTime, File, + DateTime, DNSNameParam) from errors import SkipPluginModule from text import _, ngettext, GettextFactory, NGettextFactory diff --git a/ipalib/parameters.py b/ipalib/parameters.py index 728faee53..1dff13cc1 100644 --- a/ipalib/parameters.py +++ b/ipalib/parameters.py @@ -114,6 +114,9 @@ from constants import TYPE_ERROR, CALLABLE_ERROR, LDAP_GENERALIZED_TIME_FORMAT from text import Gettext, FixMe from util import json_serialize from ipapython.dn import DN +from ipapython.dnsutil import DNSName +import dns.name +import encodings.idna def _is_null(value): return not value and value != 0 # NOTE: False == 0 @@ -1919,3 +1922,69 @@ def create_param(spec): TYPE_ERROR % ('spec', (str, Param), spec, type(spec)) ) return Str(spec) + + +class DNSNameParam(Param): + """ + Domain name parameter type. + + :only_absolute a domain name has to be absolute + (makes it absolute from unicode input) + :only_relative a domain name has to be relative + """ + type = DNSName + type_error = _('must be DNS name') + kwargs = Param.kwargs + ( + ('only_absolute', bool, False), + ('only_relative', bool, False), + ) + + def __init__(self, name, *rules, **kw): + super(DNSNameParam, self).__init__(name, *rules, **kw) + if self.only_absolute and self.only_relative: + raise ValueError('%s: cannot be both absolute and relative' % + self.nice) + + def _convert_scalar(self, value, index=None): + if isinstance(value, unicode): + error = None + + try: + domain_name = DNSName(value) + except dns.name.BadEscape: + error = _('invalid escape code in domain name') + except dns.name.EmptyLabel: + error = _('empty DNS label') + except dns.name.NameTooLong: + error = _('domain name cannot be longer than 255 characters') + except dns.name.LabelTooLong: + error = _('DNS label cannot be longer than 63 characters') + except dns.exception.SyntaxError: + error = _('invalid domain name') + + #compare if IDN normalized and original domain match + #there is N:1 mapping between unicode and IDNA names + #user should use normalized names to avoid mistakes + normalized_domain_name = encodings.idna.nameprep(value) + if value != normalized_domain_name: + error = _("domain name '%(domain)s' and normalized domain name" + " '%(normalized)s' do not match. Please use only" + " normalized domains") % {'domain': value, + 'normalized': normalized_domain_name} + if error: + raise ConversionError(name=self.get_param_name(), index=index, + error=error) + value = domain_name + + if self.only_absolute and not value.is_absolute(): + value = value.make_absolute() + + return super(DNSNameParam, self)._convert_scalar(value, index) + + def _rule_only_absolute(self, _, value): + if self.only_absolute and not value.is_absolute(): + return _('must be absolute') + + def _rule_only_relative(self, _, value): + if self.only_relative and value.is_absolute(): + return _('must be relative') diff --git a/make-lint b/make-lint index 40483a64b..2554c68e6 100755 --- a/make-lint +++ b/make-lint @@ -87,6 +87,7 @@ class IPATypeChecker(TypeChecker): 'ipalib.parameters.Number': ['minvalue', 'maxvalue'], 'ipalib.parameters.Decimal': ['precision', 'exponential', 'numberclass'], + 'ipalib.parameters.DNSNameParam': ['only_absolute', 'only_relative'], 'ipalib.plugable.API': NAMESPACE_ATTRS + LOGGING_ATTRS, 'ipalib.plugable.Plugin': ['api', 'env'] + NAMESPACE_ATTRS + LOGGING_ATTRS, -- cgit