freeipa/ipatests/test_ipalib/test_parameters.py
Christian Heimes a297ebbb8a Add max/min safe integer
JSON cannot safely handle integers outside range ``-(2**53) - 1`` to
``(2**53) - 1``. Add constants for safe integers and limit the Int
parameter to safe JSON values.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER

See: https://pagure.io/freeipa/issue/8802
See: https://pagure.io/freeipa/issue/8361
Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
2021-04-27 13:10:26 -04:00

1861 lines
65 KiB
Python

# -*- coding: utf-8 -*-
# Authors:
# Jason Gerard DeRose <jderose@redhat.com>
#
# Copyright (C) 2008 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Test the `ipalib.parameters` module.
"""
# FIXME: Pylint errors
# pylint: disable=no-member
import base64
import datetime
import re
import sys
from decimal import Decimal
from inspect import isclass
from xmlrpc.client import MAXINT, MININT
import pytest
import six
from cryptography import x509 as crypto_x509
from cryptography.hazmat.backends import default_backend
from ipatests.util import raises, ClassChecker, read_only
from ipatests.util import dummy_ugettext, assert_equal
from ipatests.data import binary_bytes, utf8_bytes, unicode_str
from ipalib import parameters, text, errors, config, x509
from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR
from ipalib.errors import ValidationError, ConversionError
from ipalib import _
from ipapython.dn import DN
if six.PY3:
unicode = str
long = int
NULLS = (None, b'', u'', tuple(), [])
pytestmark = pytest.mark.tier0
class test_DefaultFrom(ClassChecker):
"""
Test the `ipalib.parameters.DefaultFrom` class.
"""
_cls = parameters.DefaultFrom
def test_init(self):
"""
Test the `ipalib.parameters.DefaultFrom.__init__` method.
"""
def callback(*args):
return args
keys = ('givenname', 'sn')
o = self.cls(callback, *keys)
assert read_only(o, 'callback') is callback
assert read_only(o, 'keys') == keys
lam = lambda first, last: first[0] + last
o = self.cls(lam)
assert read_only(o, 'keys') == ('first', 'last')
# Test that TypeError is raised when callback isn't callable:
e = raises(TypeError, self.cls, 'whatever')
assert str(e) == CALLABLE_ERROR % ('callback', 'whatever', str)
# Test that TypeError is raised when a key isn't an str:
e = raises(TypeError, self.cls, callback, 'givenname', 17)
assert str(e) == TYPE_ERROR % ('keys', str, 17, int)
# Test that ValueError is raised when inferring keys from a callback
# which has *args:
e = raises(ValueError, self.cls, lambda foo, *args: None)
assert str(e) == "callback: variable-length argument list not allowed"
# Test that ValueError is raised when inferring keys from a callback
# which has **kwargs:
e = raises(ValueError, self.cls, lambda foo, **kwargs: None)
assert str(e) == "callback: variable-length argument list not allowed"
def test_repr(self):
"""
Test the `ipalib.parameters.DefaultFrom.__repr__` method.
"""
def stuff(one, two):
pass
o = self.cls(stuff)
assert repr(o) == "DefaultFrom('one', 'two')"
o = self.cls(stuff, 'aye', 'bee', 'see')
assert repr(o) == "DefaultFrom('aye', 'bee', 'see')"
cb = lambda first, last: first[0] + last
o = self.cls(cb)
assert repr(o) == "DefaultFrom('first', 'last')"
o = self.cls(cb, 'aye', 'bee', 'see')
assert repr(o) == "DefaultFrom('aye', 'bee', 'see')"
def test_call(self):
"""
Test the `ipalib.parameters.DefaultFrom.__call__` method.
"""
def callback(givenname, sn):
return givenname[0] + sn[0]
keys = ('givenname', 'sn')
o = self.cls(callback, *keys)
kw = dict(
givenname='John',
sn='Public',
hello='world',
)
assert o(**kw) == 'JP'
assert o() is None
for key in ('givenname', 'sn'):
kw_copy = dict(kw)
del kw_copy[key]
assert o(**kw_copy) is None
# Test using implied keys:
o = self.cls(lambda first, last: first[0] + last)
assert o(first='john', last='doe') == 'jdoe'
assert o(first='', last='doe') is None
assert o(one='john', two='doe') is None
# Test that co_varnames slice is used:
def callback2(first, last):
letter = first[0]
return letter + last
o = self.cls(callback2)
assert o.keys == ('first', 'last')
assert o(first='john', last='doe') == 'jdoe'
def test_parse_param_spec():
"""
Test the `ipalib.parameters.parse_param_spec` function.
"""
f = parameters.parse_param_spec
assert f('name') == ('name', dict(required=True, multivalue=False))
assert f('name?') == ('name', dict(required=False, multivalue=False))
assert f('name*') == ('name', dict(required=False, multivalue=True))
assert f('name+') == ('name', dict(required=True, multivalue=True))
# Make sure other "funny" endings are *not* treated special:
assert f('name^') == ('name^', dict(required=True, multivalue=False))
# Test that TypeError is raised if spec isn't an str:
if six.PY2:
bad_value = u'name?'
else:
bad_value = b'name?'
e = raises(TypeError, f, bad_value)
assert str(e) == TYPE_ERROR % ('spec', str, bad_value, type(bad_value))
class DummyRule:
def __init__(self, error=None):
assert error is None or type(error) is unicode
self.error = error
self.reset()
def __call__(self, *args):
self.calls.append(args)
return self.error
def reset(self):
self.calls = []
class test_Param(ClassChecker):
"""
Test the `ipalib.parameters.Param` class.
"""
_cls = parameters.Param
def test_init(self):
"""
Test the `ipalib.parameters.Param.__init__` method.
"""
name = 'my_param'
o = self.cls(name)
assert o.param_spec is name
assert o.name is name
assert o.nice == "Param('my_param')"
assert o.password is False
assert o.__islocked__() is True
# Test default rules:
assert o.rules == tuple()
assert o.class_rules == tuple()
assert o.all_rules == tuple()
# Test default kwarg values:
assert o.cli_name is name
assert o.label.msg == 'my_param'
assert o.doc.msg == 'my_param'
assert o.required is True
assert o.multivalue is False
assert o.primary_key is False
assert o.normalizer is None
assert o.default is None
assert o.default_from is None
assert o.autofill is False
assert o.query is False
assert o.attribute is False
assert o.include is None
assert o.exclude is None
assert o.flags == frozenset()
assert o.sortorder == 2
# Test that doc defaults from label:
o = self.cls('my_param', doc=_('Hello world'))
assert o.label.msg == 'my_param'
assert o.doc.msg == 'Hello world'
o = self.cls('my_param', label='My Param')
assert o.label == 'My Param'
assert o.doc == 'My Param'
# Test that ValueError is raised when a kwarg from a subclass
# conflicts with an attribute:
class Subclass1(self.cls):
kwargs = self.cls.kwargs + (
('convert', callable, None),
)
e = raises(ValueError, Subclass1, name)
assert str(e) == "kwarg 'convert' conflicts with attribute on Subclass1"
# Test type validation of keyword arguments:
class Subclass(self.cls):
kwargs = self.cls.kwargs + (
('extra1', bool, True),
('extra2', str, 'Hello'),
('extra3', (int, float), 42),
('extra4', callable, lambda whatever: whatever + 7),
)
o = Subclass('my_param') # Test with no **kw:
for key, kind, _default in o.kwargs:
# Test with a type invalid for all:
value = object()
kw = {key: value}
e = raises(TypeError, Subclass, 'my_param', **kw)
if kind is callable:
assert str(e) == CALLABLE_ERROR % (key, value, type(value))
else:
assert str(e) == TYPE_ERROR % (key, kind, value, type(value))
# Test with None:
kw = {key: None}
Subclass('my_param', **kw)
# Test when using unknown kwargs:
e = raises(TypeError, self.cls, 'my_param',
flags=['hello', 'world'],
whatever=u'Hooray!',
)
assert str(e) == \
"Param('my_param'): takes no such kwargs: 'whatever'"
e = raises(TypeError, self.cls, 'my_param', great='Yes', ape='he is!')
assert str(e) == \
"Param('my_param'): takes no such kwargs: 'ape', 'great'"
# Test that ValueError is raised if you provide both include and
# exclude:
e = raises(ValueError, self.cls, 'my_param',
include=['server', 'foo'],
exclude=['client', 'bar'],
)
assert str(e) == '%s: cannot have both %s=%r and %s=%r' % (
"Param('my_param')",
'include', frozenset(['server', 'foo']),
'exclude', frozenset(['client', 'bar']),
)
# Test that default_from gets set:
call = lambda first, last: first[0] + last
o = self.cls('my_param', default_from=call)
assert type(o.default_from) is parameters.DefaultFrom
assert o.default_from.callback is call
def test_repr(self):
"""
Test the `ipalib.parameters.Param.__repr__` method.
"""
for name in ['name', 'name?', 'name*', 'name+']:
o = self.cls(name)
assert repr(o) == 'Param(%r)' % name
o = self.cls('name', required=False)
assert repr(o) == "Param('name?')"
o = self.cls('name', multivalue=True)
assert repr(o) == "Param('name+')"
def test_use_in_context(self):
"""
Test the `ipalib.parameters.Param.use_in_context` method.
"""
set1 = ('one', 'two', 'three')
set2 = ('four', 'five', 'six')
param1 = self.cls('param1')
param2 = self.cls('param2', include=set1)
param3 = self.cls('param3', exclude=set2)
for context in set1:
env = config.Env()
env.context = context
assert param1.use_in_context(env) is True, context
assert param2.use_in_context(env) is True, context
assert param3.use_in_context(env) is True, context
for context in set2:
env = config.Env()
env.context = context
assert param1.use_in_context(env) is True, context
assert param2.use_in_context(env) is False, context
assert param3.use_in_context(env) is False, context
def test_safe_value(self):
"""
Test the `ipalib.parameters.Param.safe_value` method.
"""
values = (unicode_str, binary_bytes, utf8_bytes)
o = self.cls('my_param')
for value in values:
assert o.safe_value(value) is value
assert o.safe_value(None) is None
p = parameters.Password('my_passwd')
for value in values:
assert_equal(p.safe_value(value), u'********')
assert p.safe_value(None) is None
def test_password_whitespaces(self):
values = ('Secret123', ' Secret123', 'Secret123 ', ' Secret123 ',)
p = parameters.Password('my_passwd')
for value in values:
assert(p.validate(value)) is None
def test_clone(self):
"""
Test the `ipalib.parameters.Param.clone` method.
"""
# Test with the defaults
orig = self.cls('my_param')
clone = orig.clone()
assert clone is not orig
assert type(clone) is self.cls
assert clone.name is orig.name
for key, _kind, _default in self.cls.kwargs:
assert getattr(clone, key) == getattr(orig, key)
# Test with a param spec:
orig = self.cls('my_param*')
assert orig.param_spec == 'my_param*'
clone = orig.clone()
assert clone.param_spec == 'my_param*'
assert clone is not orig
assert type(clone) is self.cls
for key, _kind, _default in self.cls.kwargs:
assert getattr(clone, key) == getattr(orig, key)
# Test with overrides:
orig = self.cls('my_param*')
assert orig.required is False
assert orig.multivalue is True
clone = orig.clone(required=True)
assert clone is not orig
assert type(clone) is self.cls
assert clone.required is True
assert clone.multivalue is True
assert clone.param_spec == 'my_param+'
assert clone.name == 'my_param'
def test_clone_rename(self):
"""
Test the `ipalib.parameters.Param.clone` method.
"""
new_name = 'my_new_param'
# Test with the defaults
orig = self.cls('my_param')
clone = orig.clone_rename(new_name)
assert clone is not orig
assert type(clone) is self.cls
assert clone.name == new_name
for key, _kind, _default in self.cls.kwargs:
if key in ('cli_name', 'label', 'doc', 'cli_metavar'):
continue
assert getattr(clone, key) is getattr(orig, key)
# Test with overrides:
orig = self.cls('my_param*')
assert orig.required is False
assert orig.multivalue is True
clone = orig.clone_rename(new_name, required=True)
assert clone is not orig
assert type(clone) is self.cls
assert clone.required is True
assert clone.multivalue is True
assert clone.param_spec == "{0}+".format(new_name)
assert clone.name == new_name
def test_convert(self):
"""
Test the `ipalib.parameters.Param.convert` method.
"""
okay = ('Hello', u'Hello', 0, 4.2, True, False, unicode_str)
class Subclass(self.cls):
def _convert_scalar(self, value, index=None):
return value
# Test when multivalue=False:
o = Subclass('my_param')
for value in NULLS:
assert o.convert(value) is None
assert o.convert(None) is None
for value in okay:
assert o.convert(value) is value
# Test when multivalue=True:
o = Subclass('my_param', multivalue=True)
for value in NULLS:
assert o.convert(value) is None
assert o.convert(okay) == okay
assert o.convert(NULLS) is None
assert o.convert(okay + NULLS) == okay
assert o.convert(NULLS + okay) == okay
for value in okay:
assert o.convert(value) == (value,)
assert o.convert([None, value]) == (value,)
assert o.convert([value, None]) == (value,)
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.Param._convert_scalar` method.
"""
dummy = dummy_ugettext()
# Test with correct type:
o = self.cls('my_param')
assert o._convert_scalar(None) is None
assert dummy.called() is False
# Test with incorrect type
raises(errors.ConversionError, o._convert_scalar, 'hello')
def test_validate(self):
"""
Test the `ipalib.parameters.Param.validate` method.
"""
# Test in default state (with no rules, no kwarg):
o = self.cls('my_param')
e = raises(errors.RequirementError, o.validate, None)
assert e.name == 'my_param'
# Test with required=False
o = self.cls('my_param', required=False)
assert o.required is False
assert o.validate(None) is None
# Test with query=True:
o = self.cls('my_param', query=True)
assert o.query is True
e = raises(errors.RequirementError, o.validate, None)
assert_equal(e.name, u'my_param')
# Test with multivalue=True:
o = self.cls('my_param', multivalue=True)
e = raises(TypeError, o.validate, [])
assert str(e) == TYPE_ERROR % ('value', tuple, [], list)
e = raises(ValueError, o.validate, tuple())
assert str(e) == 'value: empty tuple must be converted to None'
# Test with wrong (scalar) type:
e = raises(TypeError, o.validate, (None, None, 42, None))
assert str(e) == TYPE_ERROR % ('my_param', type(None), 42, int)
o = self.cls('my_param')
e = raises(TypeError, o.validate, 'Hello')
assert str(e) == TYPE_ERROR % ('my_param', type(None), 'Hello', str)
class Example(self.cls):
type = int
# Test with some rules and multivalue=False
pass1 = DummyRule()
pass2 = DummyRule()
fail = DummyRule(u'no good')
o = Example('example', pass1, pass2)
assert o.multivalue is False
assert o.validate(11) is None
assert pass1.calls == [(text.ugettext, 11)]
assert pass2.calls == [(text.ugettext, 11)]
pass1.reset()
pass2.reset()
o = Example('example', pass1, pass2, fail)
e = raises(errors.ValidationError, o.validate, 42)
assert e.name == 'example'
assert e.error == u'no good'
assert pass1.calls == [(text.ugettext, 42)]
assert pass2.calls == [(text.ugettext, 42)]
assert fail.calls == [(text.ugettext, 42)]
# Test with some rules and multivalue=True
pass1 = DummyRule()
pass2 = DummyRule()
fail = DummyRule(u'this one is not good')
o = Example('example', pass1, pass2, multivalue=True)
assert o.multivalue is True
assert o.validate((3, 9)) is None
assert pass1.calls == [
(text.ugettext, 3),
(text.ugettext, 9),
]
assert pass2.calls == [
(text.ugettext, 3),
(text.ugettext, 9),
]
pass1.reset()
pass2.reset()
o = Example('multi_example', pass1, pass2, fail, multivalue=True)
assert o.multivalue is True
e = raises(errors.ValidationError, o.validate, (3, 9))
assert e.name == 'multi_example'
assert e.error == u'this one is not good'
assert pass1.calls == [(text.ugettext, 3)]
assert pass2.calls == [(text.ugettext, 3)]
assert fail.calls == [(text.ugettext, 3)]
def test_validate_scalar(self):
"""
Test the `ipalib.parameters.Param._validate_scalar` method.
"""
class MyParam(self.cls):
type = bool
okay = DummyRule()
o = MyParam('my_param', okay)
# Test that TypeError is appropriately raised:
e = raises(TypeError, o._validate_scalar, 0)
assert str(e) == TYPE_ERROR % ('my_param', bool, 0, int)
# Test with passing rule:
assert o._validate_scalar(True) is None
assert o._validate_scalar(False) is None
assert okay.calls == [
(text.ugettext, True),
(text.ugettext, False),
]
# Test with a failing rule:
okay = DummyRule()
fail = DummyRule(u'this describes the error')
o = MyParam('my_param', okay, fail)
e = raises(errors.ValidationError, o._validate_scalar, True)
assert e.name == 'my_param'
assert e.error == u'this describes the error'
e = raises(errors.ValidationError, o._validate_scalar, False)
assert e.name == 'my_param'
assert e.error == u'this describes the error'
assert okay.calls == [
(text.ugettext, True),
(text.ugettext, False),
]
assert fail.calls == [
(text.ugettext, True),
(text.ugettext, False),
]
def test_get_default(self):
"""
Test the `ipalib.parameters.Param.get_default` method.
"""
class PassThrough:
value = None
def __call__(self, value):
assert self.value is None
assert value is not None
self.value = value
return value
def reset(self):
assert self.value is not None
self.value = None
class Str(self.cls):
type = unicode
def __init__(self, name, **kw):
# (Pylint complains because the superclass is unknowm)
# pylint: disable=bad-super-call, super-on-old-class
self._convert_scalar = PassThrough()
super(Str, self).__init__(name, **kw)
# Test with only a static default:
o = Str('my_str',
normalizer=PassThrough(),
default=u'Static Default',
)
assert_equal(o.get_default(), u'Static Default')
assert o._convert_scalar.value is None
assert o.normalizer.value is None
# Test with default_from:
o = Str('my_str',
normalizer=PassThrough(),
default=u'Static Default',
default_from=lambda first, last: first[0] + last,
)
assert_equal(o.get_default(), u'Static Default')
assert o._convert_scalar.value is None
assert o.normalizer.value is None
default = o.get_default(first=u'john', last='doe')
assert_equal(default, u'jdoe')
assert o._convert_scalar.value is default
assert o.normalizer.value is default
class test_Flag(ClassChecker):
"""
Test the `ipalib.parameters.Flag` class.
"""
_cls = parameters.Flag
def test_init(self):
"""
Test the `ipalib.parameters.Flag.__init__` method.
"""
# Test with no kwargs:
o = self.cls('my_flag')
assert o.type is bool
assert isinstance(o, parameters.Bool)
assert o.autofill is True
assert o.default is False
# Test that TypeError is raise if default is not a bool:
e = raises(TypeError, self.cls, 'my_flag', default=None)
assert str(e) == TYPE_ERROR % ('default', bool, None, type(None))
# Test with autofill=False, default=True
o = self.cls('my_flag', autofill=False, default=True)
assert o.autofill is True
assert o.default is True
# Test when cloning:
orig = self.cls('my_flag')
for clone in [orig.clone(), orig.clone(autofill=False)]:
assert clone.autofill is True
assert clone.default is False
assert clone is not orig
assert type(clone) is self.cls
# Test when cloning with default=True/False
orig = self.cls('my_flag')
assert orig.clone().default is False
assert orig.clone(default=True).default is True
orig = self.cls('my_flag', default=True)
assert orig.clone().default is True
assert orig.clone(default=False).default is False
class test_Data(ClassChecker):
"""
Test the `ipalib.parameters.Data` class.
"""
_cls = parameters.Data
def test_init(self):
"""
Test the `ipalib.parameters.Data.__init__` method.
"""
o = self.cls('my_data')
assert o.type is type(None) # noqa
assert o.password is False
assert o.rules == tuple()
assert o.class_rules == tuple()
assert o.all_rules == tuple()
assert o.minlength is None
assert o.maxlength is None
assert o.length is None
# Test mixing length with minlength or maxlength:
o = self.cls('my_data', length=5)
assert o.length == 5
permutations = [
dict(minlength=3),
dict(maxlength=7),
dict(minlength=3, maxlength=7),
]
for kw in permutations:
o = self.cls('my_data', **kw)
for (key, value) in kw.items():
assert getattr(o, key) == value
e = raises(ValueError, self.cls, 'my_data', length=5, **kw)
assert str(e) == \
"Data('my_data'): cannot mix length with minlength or maxlength"
# Test when minlength or maxlength are less than 1:
e = raises(ValueError, self.cls, 'my_data', minlength=0)
assert str(e) == "Data('my_data'): minlength must be >= 1; got 0"
e = raises(ValueError, self.cls, 'my_data', maxlength=0)
assert str(e) == "Data('my_data'): maxlength must be >= 1; got 0"
# Test when minlength > maxlength:
e = raises(ValueError, self.cls, 'my_data', minlength=22, maxlength=15)
assert str(e) == \
"Data('my_data'): minlength > maxlength (minlength=22, maxlength=15)"
# Test when minlength == maxlength
e = raises(ValueError, self.cls, 'my_data', minlength=7, maxlength=7)
assert str(e) == \
"Data('my_data'): minlength == maxlength; use length=7 instead"
class test_Bytes(ClassChecker):
"""
Test the `ipalib.parameters.Bytes` class.
"""
_cls = parameters.Bytes
def test_init(self):
"""
Test the `ipalib.parameters.Bytes.__init__` method.
"""
o = self.cls('my_bytes')
assert o.type is bytes
assert o.password is False
assert o.rules == tuple()
assert o.class_rules == tuple()
assert o.all_rules == tuple()
assert o.minlength is None
assert o.maxlength is None
assert o.length is None
assert o.pattern is None
assert o.re is None
# Test mixing length with minlength or maxlength:
o = self.cls('my_bytes', length=5)
assert o.length == 5
assert len(o.class_rules) == 1
assert len(o.rules) == 0
assert len(o.all_rules) == 1
permutations = [
dict(minlength=3),
dict(maxlength=7),
dict(minlength=3, maxlength=7),
]
for kw in permutations:
o = self.cls('my_bytes', **kw)
assert len(o.class_rules) == len(kw)
assert len(o.rules) == 0
assert len(o.all_rules) == len(kw)
for (key, value) in kw.items():
assert getattr(o, key) == value
e = raises(ValueError, self.cls, 'my_bytes', length=5, **kw)
assert str(e) == \
"Bytes('my_bytes'): cannot mix length with minlength or maxlength"
# Test when minlength or maxlength are less than 1:
e = raises(ValueError, self.cls, 'my_bytes', minlength=0)
assert str(e) == "Bytes('my_bytes'): minlength must be >= 1; got 0"
e = raises(ValueError, self.cls, 'my_bytes', maxlength=0)
assert str(e) == "Bytes('my_bytes'): maxlength must be >= 1; got 0"
# Test when minlength > maxlength:
e = raises(ValueError, self.cls, 'my_bytes', minlength=22, maxlength=15)
assert str(e) == \
"Bytes('my_bytes'): minlength > maxlength (minlength=22, maxlength=15)"
# Test when minlength == maxlength
e = raises(ValueError, self.cls, 'my_bytes', minlength=7, maxlength=7)
assert str(e) == \
"Bytes('my_bytes'): minlength == maxlength; use length=7 instead"
def test_rule_minlength(self):
"""
Test the `ipalib.parameters.Bytes._rule_minlength` method.
"""
o = self.cls('my_bytes', minlength=3)
assert o.minlength == 3
rule = o._rule_minlength
translation = u'minlength=%(minlength)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (b'abc', b'four', b'12345'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (b'', b'a', b'12'):
assert_equal(
rule(dummy, value),
translation % dict(minlength=3)
)
assert dummy.message == 'must be at least %(minlength)d bytes'
assert dummy.called() is True
dummy.reset()
def test_rule_maxlength(self):
"""
Test the `ipalib.parameters.Bytes._rule_maxlength` method.
"""
o = self.cls('my_bytes', maxlength=4)
assert o.maxlength == 4
rule = o._rule_maxlength
translation = u'maxlength=%(maxlength)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (b'ab', b'123', b'four'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (b'12345', b'sixsix'):
assert_equal(
rule(dummy, value),
translation % dict(maxlength=4)
)
assert dummy.message == 'can be at most %(maxlength)d bytes'
assert dummy.called() is True
dummy.reset()
def test_rule_length(self):
"""
Test the `ipalib.parameters.Bytes._rule_length` method.
"""
o = self.cls('my_bytes', length=4)
assert o.length == 4
rule = o._rule_length
translation = u'length=%(length)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (b'1234', b'four'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (b'ab', b'123', b'12345', b'sixsix'):
assert_equal(
rule(dummy, value),
translation % dict(length=4),
)
assert dummy.message == 'must be exactly %(length)d bytes'
assert dummy.called() is True
dummy.reset()
def test_rule_pattern(self):
"""
Test the `ipalib.parameters.Bytes._rule_pattern` method.
"""
# Test our assumptions about Python re module and Unicode:
pat = br'\w+$'
r = re.compile(pat)
assert r.match(b'Hello_World') is not None
assert r.match(utf8_bytes) is None
assert r.match(binary_bytes) is None
# Create instance:
o = self.cls('my_bytes', pattern=pat)
assert o.pattern is pat
rule = o._rule_pattern
translation = u'pattern=%(pattern)r'
dummy = dummy_ugettext(translation)
# Test with passing values:
for value in (b'HELLO', b'hello', b'Hello_World'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (b'Hello!', b'Hello World', utf8_bytes, binary_bytes):
assert_equal(
rule(dummy, value),
translation % dict(pattern=pat),
)
assert_equal(dummy.message, 'must match pattern "%(pattern)s"')
assert dummy.called() is True
dummy.reset()
class test_Str(ClassChecker):
"""
Test the `ipalib.parameters.Str` class.
"""
_cls = parameters.Str
def test_init(self):
"""
Test the `ipalib.parameters.Str.__init__` method.
"""
o = self.cls('my_str')
assert o.type is unicode
assert o.password is False
assert o.minlength is None
assert o.maxlength is None
assert o.length is None
assert o.pattern is None
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.Str._convert_scalar` method.
"""
o = self.cls('my_str')
mthd = o._convert_scalar
for value in (u'Hello', 42, 1.2, unicode_str):
assert mthd(value) == unicode(value)
bad = [True, b'Hello', dict(one=1), utf8_bytes]
for value in bad:
e = raises(errors.ConversionError, mthd, value)
assert e.name == 'my_str'
assert_equal(unicode(e.error), u'must be Unicode text')
bad = [(u'Hello',), [42.3]]
for value in bad:
e = raises(errors.ConversionError, mthd, value)
assert e.name == 'my_str'
assert_equal(unicode(e.error), u'Only one value is allowed')
assert o.convert(None) is None
def test_rule_minlength(self):
"""
Test the `ipalib.parameters.Str._rule_minlength` method.
"""
o = self.cls('my_str', minlength=3)
assert o.minlength == 3
rule = o._rule_minlength
translation = u'minlength=%(minlength)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (u'abc', u'four', u'12345'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (u'', u'a', u'12'):
assert_equal(
rule(dummy, value),
translation % dict(minlength=3)
)
assert dummy.message == 'must be at least %(minlength)d characters'
assert dummy.called() is True
dummy.reset()
def test_rule_maxlength(self):
"""
Test the `ipalib.parameters.Str._rule_maxlength` method.
"""
o = self.cls('my_str', maxlength=4)
assert o.maxlength == 4
rule = o._rule_maxlength
translation = u'maxlength=%(maxlength)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (u'ab', u'123', u'four'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (u'12345', u'sixsix'):
assert_equal(
rule(dummy, value),
translation % dict(maxlength=4)
)
assert dummy.message == 'can be at most %(maxlength)d characters'
assert dummy.called() is True
dummy.reset()
def test_rule_length(self):
"""
Test the `ipalib.parameters.Str._rule_length` method.
"""
o = self.cls('my_str', length=4)
assert o.length == 4
rule = o._rule_length
translation = u'length=%(length)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (u'1234', u'four'):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (u'ab', u'123', u'12345', u'sixsix'):
assert_equal(
rule(dummy, value),
translation % dict(length=4),
)
assert dummy.message == 'must be exactly %(length)d characters'
assert dummy.called() is True
dummy.reset()
def test_rule_pattern(self):
"""
Test the `ipalib.parameters.Str._rule_pattern` method.
"""
# Test our assumptions about Python re module and Unicode:
pat = r'\w{5}$'
r1 = re.compile(pat)
r2 = re.compile(pat, re.UNICODE)
if six.PY2:
assert r1.match(unicode_str) is None
else:
assert r1.match(unicode_str) is not None
assert r2.match(unicode_str) is not None
# Create instance:
o = self.cls('my_str', pattern=pat)
assert o.pattern is pat
rule = o._rule_pattern
translation = u'pattern=%(pattern)r'
dummy = dummy_ugettext(translation)
# Test with passing values:
for value in (u'HELLO', u'hello', unicode_str):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (u'H LLO', u'***lo', unicode_str + unicode_str):
assert_equal(
rule(dummy, value),
translation % dict(pattern=pat),
)
assert_equal(dummy.message, 'must match pattern "%(pattern)s"')
assert dummy.called() is True
dummy.reset()
class test_Password(ClassChecker):
"""
Test the `ipalib.parameters.Password` class.
"""
_cls = parameters.Password
def test_init(self):
"""
Test the `ipalib.parameters.Password.__init__` method.
"""
o = self.cls('my_password')
assert o.type is unicode
assert o.minlength is None
assert o.maxlength is None
assert o.length is None
assert o.pattern is None
assert o.password is True
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.Password._convert_scalar` method.
"""
o = self.cls('my_password')
e = raises(errors.PasswordMismatch, o._convert_scalar, [u'one', u'two'])
assert e.name == 'my_password'
assert o._convert_scalar([u'one', u'one']) == u'one'
assert o._convert_scalar(u'one') == u'one'
class EnumChecker(ClassChecker):
"""
Test *Enum classes.
"""
_cls = parameters.StrEnum
def test_init(self):
"""Test the `__init__` method"""
values = self._test_values
o = self.cls(self._name, values=values)
assert o.type is self._datatype
assert o.values is values
assert o.class_rules == (o._rule_values,)
assert o.rules == tuple()
assert o.all_rules == (o._rule_values,)
def test_bad_types(self):
"""Test failure with incorrect types"""
badvalues = self._bad_type_values
e = raises(TypeError, self.cls, 'my_enum', values=badvalues)
assert str(e) == TYPE_ERROR % (
"%s('my_enum') values[1]" % self._cls.__name__,
self._datatype, badvalues[1], self._bad_type)
def test_empty(self):
"""Test that ValueError is raised when list of values is empty"""
badvalues = tuple()
e = raises(ValueError, self.cls, 'empty_enum', values=badvalues)
assert_equal(str(e), "%s('empty_enum'): list of values must not "
"be empty" % self._cls.__name__)
def test_rules_values(self):
"""Test the `_rule_values` method"""
def test_rules_with_passing_rules(self):
"""Test with passing values"""
o = self.cls('my_enum', values=self._test_values)
rule = o._rule_values
dummy = dummy_ugettext(self._translation)
for v in self._test_values:
assert rule(dummy, v) is None
assert dummy.called() is False
def test_rules_with_failing_rules(self):
"""Test with failing values"""
o = self.cls('my_enum', values=self._test_values)
rule = o._rule_values
dummy = dummy_ugettext(self._translation)
for val in self._bad_values:
assert_equal(
rule(dummy, val),
self._translation % dict(values=self._test_values),
)
assert_equal(dummy.message, "must be one of %(values)s")
dummy.reset()
def test_one_value(self):
"""test a special case when we have just one allowed value"""
values = (self._test_values[0], )
o = self.cls('my_enum', values=values)
rule = o._rule_values
dummy = dummy_ugettext(self._single_value_translation)
for val in self._bad_values:
assert_equal(
rule(dummy, val),
self._single_value_translation % dict(values=values),
)
assert_equal(dummy.message, "must be '%(value)s'")
dummy.reset()
class test_StrEnum(EnumChecker):
"""
Test the `ipalib.parameters.StrEnum` class.
"""
_cls = parameters.StrEnum
_name = 'my_strenum'
_datatype = unicode
_test_values = u'Hello', u'tall', u'nurse!'
_bad_type_values = u'Hello', 1, u'nurse!'
_bad_type = int
_translation = u"values='Hello', 'tall', 'nurse!'"
_bad_values = u'Howdy', u'quiet', u'library!'
_single_value_translation = u"value='Hello'"
def check_int_scalar_conversions(o):
"""
Assure radix prefixes work, str objects fail,
floats (native & string) are truncated,
large magnitude values are promoted to long,
empty strings & invalid numerical representations fail
"""
# Assure invalid inputs raise error
for bad in ['hello', u'hello', True, None, u'', u'.', 8j, ()]:
e = raises(errors.ConversionError, o._convert_scalar, bad)
assert e.name == 'my_number'
# Assure large magnitude values are handled correctly
assert type(o._convert_scalar(sys.maxsize * 2)) == long
assert o._convert_scalar(sys.maxsize * 2) == sys.maxsize * 2
assert o._convert_scalar(unicode(sys.maxsize * 2)) == sys.maxsize * 2
assert o._convert_scalar(long(16)) == 16
# Assure normal conversions produce expected result
assert o._convert_scalar(u'16.99') == 16
assert o._convert_scalar(16.99) == 16
assert o._convert_scalar(u'16') == 16
assert o._convert_scalar(u'0x10') == 16
assert o._convert_scalar(u'020') == 16
assert o._convert_scalar(u'0o20') == 16
class test_IntEnum(EnumChecker):
"""
Test the `ipalib.parameters.IntEnum` class.
"""
_cls = parameters.IntEnum
_name = 'my_intenum'
_datatype = int
_test_values = 1, 2, -3
_bad_type_values = 1, 2.0, -3
_bad_type = float
_translation = u"values=1, 2, 3"
_bad_values = 4, 5, -6
_single_value_translation = u"value=1"
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.IntEnum._convert_scalar` method.
"""
param = self.cls('my_number', values=(1, 2, 3, 4, 5))
check_int_scalar_conversions(param)
class test_Number(ClassChecker):
"""
Test the `ipalib.parameters.Number` class.
"""
_cls = parameters.Number
def test_init(self):
"""
Test the `ipalib.parameters.Number.__init__` method.
"""
o = self.cls('my_number')
assert o.type is type(None) # noqa
assert o.password is False
assert o.rules == tuple()
assert o.class_rules == tuple()
assert o.all_rules == tuple()
class test_Int(ClassChecker):
"""
Test the `ipalib.parameters.Int` class.
"""
_cls = parameters.Int
def test_init(self):
"""
Test the `ipalib.parameters.Int.__init__` method.
"""
# Test with no kwargs:
o = self.cls('my_number')
assert o.type == int
assert o.allowed_types == (int,)
assert isinstance(o, parameters.Int)
assert o.minvalue == int(MININT)
assert o.maxvalue == int(MAXINT)
# Test when min > max:
e = raises(ValueError, self.cls, 'my_number', minvalue=22, maxvalue=15)
assert str(e) == \
"Int('my_number'): minvalue > maxvalue (minvalue=22, maxvalue=15)"
def test_rule_minvalue(self):
"""
Test the `ipalib.parameters.Int._rule_minvalue` method.
"""
o = self.cls('my_number', minvalue=3)
assert o.minvalue == 3
rule = o._rule_minvalue
translation = u'minvalue=%(minvalue)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (4, 99, 1001):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (-1, 0, 2):
assert_equal(
rule(dummy, value),
translation % dict(minvalue=3)
)
assert dummy.message == 'must be at least %(minvalue)d'
assert dummy.called() is True
dummy.reset()
def test_rule_maxvalue(self):
"""
Test the `ipalib.parameters.Int._rule_maxvalue` method.
"""
o = self.cls('my_number', maxvalue=4)
assert o.maxvalue == 4
rule = o._rule_maxvalue
translation = u'maxvalue=%(maxvalue)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (-1, 0, 4):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (5, 99, 1009):
assert_equal(
rule(dummy, value),
translation % dict(maxvalue=4)
)
assert dummy.message == 'can be at most %(maxvalue)d'
assert dummy.called() is True
dummy.reset()
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.Int._convert_scalar` method.
"""
param = self.cls('my_number')
check_int_scalar_conversions(param)
def test_safe_json(self):
param = self.cls(
"large",
minvalue=self.cls.MIN_SAFE_INTEGER,
maxvalue=self.cls.MAX_SAFE_INTEGER
)
for value in (-((2 ** 53) - 1), 0, (2 ** 53) - 1):
param.validate(value)
for value in (-2 ** 53, 2 ** 53):
with pytest.raises(errors.ValidationError):
param.validate(value)
with pytest.raises(ValueError):
self.cls("toolarge", maxvalue=2**53)
with pytest.raises(ValueError):
self.cls("toosmall", minvalue=-2**53)
class test_Decimal(ClassChecker):
"""
Test the `ipalib.parameters.Decimal` class.
"""
_cls = parameters.Decimal
def test_init(self):
"""
Test the `ipalib.parameters.Decimal.__init__` method.
"""
# Test with no kwargs:
o = self.cls('my_number')
assert o.type is Decimal
assert isinstance(o, parameters.Decimal)
assert o.minvalue is None
assert o.maxvalue is None
# Test when min > max:
e = raises(ValueError, self.cls, 'my_number', minvalue=Decimal('22.5'), maxvalue=Decimal('15.1'))
assert str(e) == \
"Decimal('my_number'): minvalue > maxvalue (minvalue=22.5, maxvalue=15.1)"
def test_rule_minvalue(self):
"""
Test the `ipalib.parameters.Decimal._rule_minvalue` method.
"""
o = self.cls('my_number', minvalue='3.1')
assert o.minvalue == Decimal('3.1')
rule = o._rule_minvalue
translation = u'minvalue=%(minvalue)s'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (Decimal('3.2'), Decimal('99.0')):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (Decimal('-1.2'), Decimal('0.0'), Decimal('3.0')):
assert_equal(
rule(dummy, value),
translation % dict(minvalue=Decimal('3.1'))
)
assert dummy.message == 'must be at least %(minvalue)s'
assert dummy.called() is True
dummy.reset()
def test_rule_maxvalue(self):
"""
Test the `ipalib.parameters.Decimal._rule_maxvalue` method.
"""
o = self.cls('my_number', maxvalue='4.7')
assert o.maxvalue == Decimal('4.7')
rule = o._rule_maxvalue
translation = u'maxvalue=%(maxvalue)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
# Test with passing values:
for value in (Decimal('-1.0'), Decimal('0.1'), Decimal('4.2')):
assert rule(dummy, value) is None
assert dummy.called() is False
# Test with failing values:
for value in (Decimal('5.3'), Decimal('99.9')):
assert_equal(
rule(dummy, value),
translation % dict(maxvalue=Decimal('4.7'))
)
assert dummy.message == 'can be at most %(maxvalue)s'
assert dummy.called() is True
dummy.reset()
def test_precision(self):
"""
Test the `ipalib.parameters.Decimal` precision attribute
"""
# precission is None
param = self.cls('my_number')
for value in (Decimal('0'), Decimal('4.4'), Decimal('4.67')):
assert_equal(
param(value),
value)
# precision is 0
param = self.cls('my_number', precision=0)
for original,expected in ((Decimal('0'), '0'),
(Decimal('1.1'), '1'),
(Decimal('4.67'), '5')):
assert_equal(
str(param(original)),
expected)
# precision is 1
param = self.cls('my_number', precision=1)
for original,expected in ((Decimal('0'), '0.0'),
(Decimal('1.1'), '1.1'),
(Decimal('4.67'), '4.7')):
assert_equal(
str(param(original)),
expected)
# value has too many digits
param = self.cls('my_number', precision=1)
e = raises(ConversionError, param, '123456789012345678901234567890')
assert str(e).startswith("invalid 'my_number': ")
def test_exponential(self):
"""
Test the `ipalib.parameters.Decimal` exponential attribute
"""
param = self.cls('my_number', exponential=True)
for original,expected in ((Decimal('0'), '0'),
(Decimal('1E3'), '1E+3'),
(Decimal('3.4E2'), '3.4E+2')):
assert_equal(
str(param(original)),
expected)
param = self.cls('my_number', exponential=False)
for original,expected in ((Decimal('0'), '0'),
(Decimal('1E3'), '1000'),
(Decimal('3.4E2'), '340')):
assert_equal(
str(param(original)),
expected)
def test_numberclass(self):
"""
Test the `ipalib.parameters.Decimal` numberclass attribute
"""
# test default value: '-Normal', '+Zero', '+Normal'
param = self.cls('my_number')
for value,raises_verror in ((Decimal('0'), False),
(Decimal('-0'), True),
(Decimal('1E8'), False),
(Decimal('-1.1'), False),
(Decimal('-Infinity'), True),
(Decimal('+Infinity'), True),
(Decimal('NaN'), True)):
if raises_verror:
raises(ValidationError, param, value)
else:
param(value)
param = self.cls('my_number', exponential=True,
numberclass=('-Normal', '+Zero', '+Infinity'))
for value,raises_verror in ((Decimal('0'), False),
(Decimal('-0'), True),
(Decimal('1E8'), True),
(Decimal('-1.1'), False),
(Decimal('-Infinity'), True),
(Decimal('+Infinity'), False),
(Decimal('NaN'), True)):
if raises_verror:
raises(ValidationError, param, value)
else:
param(value)
class test_AccessTime(ClassChecker):
"""
Test the `ipalib.parameters.AccessTime` class.
"""
_cls = parameters.AccessTime
def test_init(self):
"""
Test the `ipalib.parameters.AccessTime.__init__` method.
"""
# Test with no kwargs:
o = self.cls('my_time')
assert o.type is unicode
assert isinstance(o, parameters.AccessTime)
assert o.multivalue is False
translation = u'length=%(length)r'
dummy = dummy_ugettext(translation)
assert dummy.translation is translation
rule = o._rule_required
# Check some good rules
for value in (u'absolute 201012161032 ~ 201012161033',
u'periodic monthly week 2 day Sat,Sun 0900-1300',
u'periodic yearly month 4 day 1-31 0800-1400',
u'periodic weekly day 7 0800-1400',
u'periodic daily 0800-1400',
):
assert rule(dummy, value) is None
assert dummy.called() is False
# And some bad ones
for value in (u'absolute 201012161032 - 201012161033',
u'absolute 201012161032 ~',
u'periodic monthly day Sat,Sun 0900-1300',
u'periodical yearly month 4 day 1-31 0800-1400',
u'periodic weekly day 8 0800-1400',
):
raises(ValidationError, o._rule_required, None, value)
def test_create_param():
"""
Test the `ipalib.parameters.create_param` function.
"""
f = parameters.create_param
# Test that Param instances are returned unchanged:
params = (
parameters.Param('one?'),
parameters.Int('two+'),
parameters.Str('three*'),
parameters.Bytes('four'),
)
for p in params:
assert f(p) is p
# Test that the spec creates an Str instance:
for spec in ('one?', 'two+', 'three*', 'four'):
(name, kw) = parameters.parse_param_spec(spec)
p = f(spec)
assert p.param_spec == spec
assert p.name == name
assert p.required is kw['required']
assert p.multivalue is kw['multivalue']
# Test that TypeError is raised when spec is neither a Param nor a str:
if six.PY2:
bad_value = u'one'
else:
bad_value = b'one'
for spec in (bad_value, 42, parameters.Param, parameters.Str):
e = raises(TypeError, f, spec)
assert str(e) == \
TYPE_ERROR % ('spec', (str, parameters.Param), spec, type(spec))
def test_messages():
"""
Test module level message in `ipalib.parameters`.
"""
for name in dir(parameters):
if name.startswith('_'):
continue
attr = getattr(parameters, name)
if not (isclass(attr) and issubclass(attr, parameters.Param)):
continue
assert type(attr.type_error) is str
assert attr.type_error in parameters.__messages
class test_IA5Str(ClassChecker):
"""
Test the `ipalib.parameters.IA5Str` class.
"""
_cls = parameters.IA5Str
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.IA5Str._convert_scalar` method.
"""
o = self.cls('my_str')
mthd = o._convert_scalar
for value in (u'Hello', 42, 1.2):
assert mthd(value) == unicode(value)
bad = ['Helloá']
for value in bad:
e = raises(errors.ConversionError, mthd, value)
assert e.name == 'my_str'
if six.PY2:
assert_equal(e.error, u"The character '\\xc3' is not allowed.")
else:
assert_equal(e.error, u"The character 'á' is not allowed.")
class test_DateTime(ClassChecker):
"""
Test the `ipalib.parameters.DateTime` class.
"""
_cls = parameters.DateTime
def test_init(self):
"""
Test the `ipalib.parameters.DateTime.__init__` method.
"""
# Test with no kwargs:
o = self.cls('my_datetime')
assert o.type is datetime.datetime
assert isinstance(o, parameters.DateTime)
assert o.multivalue is False
# Check full time formats
date = datetime.datetime(1991, 12, 7, 6, 30, 5)
assert date == o.convert(u'19911207063005Z')
assert date == o.convert(u'1991-12-07T06:30:05Z')
assert date == o.convert(u'1991-12-07 06:30:05Z')
# Check time formats without seconds
date = datetime.datetime(1991, 12, 7, 6, 30)
assert date == o.convert(u'1991-12-07T06:30Z')
assert date == o.convert(u'1991-12-07 06:30Z')
# Check date formats
date = datetime.datetime(1991, 12, 7)
assert date == o.convert(u'1991-12-07Z')
# Check some wrong formats
for value in (u'19911207063005',
u'1991-12-07T06:30:05',
u'1991-12-07 06:30:05',
u'1991-12-07T06:30',
u'1991-12-07 06:30',
u'1991-12-07',
u'1991-31-12Z',
u'1991-12-07T25:30:05Z',
):
raises(ConversionError, o.convert, value)
class test_CertificateSigningRequest(ClassChecker):
"""
Test the `ipalib.parameters.CertificateSigningRequest` class
"""
_cls = parameters.CertificateSigningRequest
sample_csr = (
b'-----BEGIN CERTIFICATE REQUEST-----\n'
b'MIIBjjCB+AIBADBPMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEQ\n'
b'MA4GA1UEChMHRXhhbXBsZTEZMBcGA1UEAxMQdGVzdC5leGFtcGxlLmNvbTCBnzAN\n'
b'BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyxsN5dmvyKiw+5nyrcO3a61sivZRg+ja\n'
b'kyNIyUo+tIUiYwTdpPESAHTWRlk0XhydauAkWfOIN7pR3a5Z+kQw8W7F+DuZze2M\n'
b'6wRNmN+NTrTlqnKOiMHBXhIM0Qxrx68GDctYqtnKTVT94FvvLl9XYVdUEi2ePTc2\n'
b'Nyfr1z66+W0CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIf3r+Y6WHrFnttUqDow\n'
b'9/UCHtCeQlQoJqjjxi5wcjbkGwTgHbx/BPOd/8OVaHElboMXLGaZx+L/eFO6E9Yg\n'
b'mDOYv3OsibDFGaEhJrU8EnfuFZKnbrGeSC9Hkqrq+3OjqacaPla5N7MHKbfLY377\n'
b'ddbOHKzR0sURZ+ro4z3fATW2\n'
b'-----END CERTIFICATE REQUEST-----\n'
)
# certmonger <= 0.79.5 (most probably in higher versions, too) will be
# sending us just base64-encoded DER certs without the information it's
# base64-encoded bytes (__base64__: in the JSON request), we need to
# support that too, unfortunately
sample_base64 = (
"MIICETCCAXoCAQAwTzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx"
"EDAOBgNVBAoTB0V4YW1wbGUxGTAXBgNVBAMTEHRlc3QuZXhhbXBsZS5jb20wgZ8w"
"DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOXfP8LeiU7g6wLCclgkT1lVskK+Lxm1"
"6ijE4LmEQBk5nn2P46im+E/UOgTddbDo5cdJlkoCnqXkO4RkqJckXYDxfI34KL3C"
"CRFPvOa5Sg02m1x5Rg3boZfS6NciP62lRp0SI+0TCt3F16wYZxMahVIOXjbJ6Lu5"
"mGjNn7XaWJhFAgMBAAGggYEwfwYJKoZIhvcNAQkOMXIwcDAeBgNVHREEFzAVghN0"
"ZXN0bG93LmV4YW1wbGUuY29tME4GA1UdHwRHMEUwQ6BBoD+GHGh0dHA6Ly9jYS5l"
"eGFtcGxlLmNvbS9teS5jcmyGH2h0dHA6Ly9vdGhlci5leGFtcGxlLmNvbS9teS5j"
"cmwwDQYJKoZIhvcNAQEFBQADgYEAkv8pppcgGhX7erJmvg9r2UHrRriuKaOYgKZQ"
"lf/eBt2N0L2mV4QvCY82H7HWuE+7T3mra9ikfvz0nYkPJQe2gntjZzECE0Jt5LWR"
"UZOFwX8N6wrX11U2xu0NlvsbjU6siWd6OZjZ1p5/V330lzut/q3CNzaAcW1Fx3wL"
"sV5SXSw="
)
sample_der_csr = (
b'0\x82\x02\x110\x82\x01z\x02\x01\x000O1\x0b0\t\x06\x03U\x04\x06\x13'
b'\x02US1\x130\x11\x06\x03U\x04\x08\x13\nCalifornia1\x100\x0e\x06\x03U'
b'\x04\n\x13\x07Example1\x190\x17\x06\x03U\x04\x03\x13\x10test.example'
b'.com0\x81\x9f0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x81'
b'\x8d\x000\x81\x89\x02\x81\x81\x00\xe5\xdf?\xc2\xde\x89N\xe0\xeb\x02'
b'\xc2rX$OYU\xb2B\xbe/\x19\xb5\xea(\xc4\xe0\xb9\x84@\x199\x9e}\x8f\xe3'
b'\xa8\xa6\xf8O\xd4:\x04\xddu\xb0\xe8\xe5\xc7I\x96J\x02\x9e\xa5\xe4;'
b'\x84d\xa8\x97$]\x80\xf1|\x8d\xf8(\xbd\xc2\t\x11O\xbc\xe6\xb9J\r6\x9b'
b'\\yF\r\xdb\xa1\x97\xd2\xe8\xd7"?\xad\xa5F\x9d\x12#\xed\x13\n\xdd\xc5'
b'\xd7\xac\x18g\x13\x1a\x85R\x0e^6\xc9\xe8\xbb\xb9\x98h\xcd\x9f\xb5'
b'\xdaX\x98E\x02\x03\x01\x00\x01\xa0\x81\x810\x7f\x06\t*\x86H\x86\xf7'
b'\r\x01\t\x0e1r0p0\x1e\x06\x03U\x1d\x11\x04\x170\x15\x82\x13'
b'testlow.example.com0N\x06\x03U\x1d\x1f\x04G0E0C\xa0A\xa0?\x86'
b'\x1chttp://ca.example.com/my.crl\x86\x1fhttp://other.example.com/'
b'my.crl0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x05\x05\x00\x03\x81\x81\x00'
b'\x92\xff)\xa6\x97 \x1a\x15\xfbz\xb2f\xbe\x0fk\xd9A\xebF\xb8\xae)\xa3'
b'\x98\x80\xa6P\x95\xff\xde\x06\xdd\x8d\xd0\xbd\xa6W\x84/\t\x8f6\x1f'
b'\xb1\xd6\xb8O\xbbOy\xabk\xd8\xa4~\xfc\xf4\x9d\x89\x0f%\x07\xb6\x82'
b'{cg1\x02\x13Bm\xe4\xb5\x91Q\x93\x85\xc1\x7f\r\xeb\n\xd7\xd7U6\xc6'
b'\xed\r\x96\xfb\x1b\x8dN\xac\x89gz9\x98\xd9\xd6\x9e\x7fW}\xf4\x97;'
b'\xad\xfe\xad\xc276\x80qmE\xc7|\x0b\xb1^R],'
)
malformed_csr = (
b'-----BEGIN CERTIFICATE REQUEST-----\n'
b'VGhpcyBpcyBhbiBpbnZhbGlkIENTUg==\n'
b'-----END CERTIFICATE REQUEST-----\n'
)
def test_init(self):
# create the parameter
o = self.cls('csr')
assert o.type is crypto_x509.CertificateSigningRequest
assert isinstance(o, parameters.CertificateSigningRequest)
assert o.multivalue is False
def test_convert(self):
o = self.cls('csr')
# test that we're able to handle PEM CSR input equally as bytes, string
# and cryptography x509._CertificateSigningRequest object
for prep_input in (
lambda x: x,
lambda x: x.decode('utf-8'),
lambda x: crypto_x509.load_pem_x509_csr(x, default_backend())
):
# test that input is correctly converted to python-crytography
# object representation
csr_object = o.convert(prep_input(self.sample_csr))
assert isinstance(csr_object,
crypto_x509.CertificateSigningRequest)
assert (csr_object.public_bytes(x509.Encoding.PEM) ==
self.sample_csr)
# test that we fail the same with malformed CSR as bytes or str
for prep_input in (
lambda x: x,
lambda x: x.decode('utf-8'),
):
# test that malformed CSRs won't be accepted
raises(errors.CertificateOperationError,
o.convert,
prep_input(self.malformed_csr))
# test DER as an input to the convert method
csr_object = o.convert(self.sample_der_csr)
assert isinstance(csr_object, crypto_x509.CertificateSigningRequest)
assert (csr_object.public_bytes(x509.Encoding.DER) ==
self.sample_der_csr)
# test base64-encoded DER as an input to the convert method
csr_object = o.convert(self.sample_base64)
assert isinstance(csr_object, crypto_x509.CertificateSigningRequest)
assert (csr_object.public_bytes(x509.Encoding.DER) ==
base64.b64decode(self.sample_base64))
# test that wrong type will not be accepted
bad_value = datetime.date.today()
raises(ConversionError, o.convert, bad_value)
class test_DNParam(ClassChecker):
"""
Test the `ipalib.parameters.DN` class.
"""
_cls = parameters.DNParam
def test_init(self):
"""
Test the `ipalib.parameters.DN.__init__` method.
"""
o = self.cls('my_dn')
assert o.type is DN
assert o.password is False
def test_convert_scalar(self):
"""
Test the `ipalib.parameters.DNParam._convert_scalar` method.
"""
o = self.cls('my_dn')
mthd = o._convert_scalar
# param can be built from a string or from a DN
good = [
'cn=dn,cn=suffix',
DN('cn=dn,cn=suffix')
]
for value in good:
assert mthd(value) == DN(value)
# param cannot be built from a tuple or list if single-valued
bad = [
['cn=dn,cn=suffix'],
['cn=dn', 'cn=suffix'],
('cn=dn', 'cn=suffix'),
(('cn', 'dn'), ('cn', 'suffix'))
]
for value in bad:
e = raises(errors.ConversionError, mthd, value)
assert e.name == 'my_dn'
assert_equal(unicode(e.error), u'Only one value is allowed')
assert o.convert(None) is None
def test_convert_singlevalued(self):
"""
Test the `DNParam.convert` method with a single-valued param.
"""
o = self.cls('my_dn?')
mthd = o.convert
# param can be built from a string or from a DN
good = [
'cn=dn,cn=suffix',
DN('cn=dn,cn=suffix')
]
for value in good:
assert mthd(value) == DN(value)
# param cannot be built from a tuple or list if single-valued
bad = [
['cn=dn,cn=suffix'],
['cn=dn', 'cn=suffix'],
('cn=dn', 'cn=suffix'),
(('cn', 'dn'), ('cn', 'suffix'))
]
for value in bad:
e = raises(errors.ConversionError, mthd, value)
assert e.name == 'my_dn'
assert_equal(unicode(e.error), u'Only one value is allowed')
assert o.convert(None) is None
def test_convert_multivalued(self):
"""
Test the `DNParam.convert` method with a multivalued param.
"""
o = self.cls('my_dn*')
mthd = o.convert
# param can be built from a tuple/list
good = [
('cn=dn1,cn=suffix',),
('cn=dn1,cn=suffix', 'cn=dn2,cn=suffix'),
['cn=dn1,cn=suffix'],
['cn=dn1,cn=suffix', 'cn=dn2,cn=suffix'],
]
for value in good:
assert mthd(value) == tuple(DN(oneval) for oneval in value)
assert o.convert(None) is None