ipalib: Add DateTime parameter

Adds a parameter that represents a DateTime format using datetime.datetime
object from python's native datetime library.

In the CLI, accepts one of the following formats:
    Accepts LDAP Generalized time without in the following format:
       '%Y%m%d%H%M%SZ'

    Accepts subset of values defined by ISO 8601:
        '%Y-%m-%dT%H:%M:%SZ'
        '%Y-%m-%dT%H:%MZ'
        '%Y-%m-%dZ'

    Also accepts above formats using ' ' (space) as a separator instead of 'T'.

As a simplification, it does not deal with timezone info and ISO 8601
values with timezone info (+-hhmm) are rejected. Values are expected
to be in the UTC timezone.

Values are saved to LDAP as LDAP Generalized time values in the format
'%Y%m%d%H%SZ' (no time fractions and UTC timezone is assumed). To avoid
confusion, in addition to subset of ISO 8601 values, the LDAP generalized
time in the format '%Y%m%d%H%M%SZ' is also accepted as an input (as this is the
format user will see on the output).

Part of: https://fedorahosted.org/freeipa/ticket/3306

Reviewed-By: Jan Cholasta <jcholast@redhat.com>
This commit is contained in:
Tomas Babej
2014-01-09 11:14:56 +01:00
committed by Alexander Bokovoy
parent 093c72d60e
commit 1df696f543
9 changed files with 96 additions and 8 deletions

View File

@@ -4007,3 +4007,4 @@ capability: messages 2.52
capability: optional_uid_params 2.54
capability: permissions2 2.69
capability: primary_key_types 2.83
capability: datetime_values 2.84

View File

@@ -89,5 +89,5 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=83
# Last change: jcholast - add 'primary_key_types' capability
IPA_API_VERSION_MINOR=84
# Last change: tbabej - added datetime value support

View File

@@ -886,7 +886,7 @@ 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
from parameters import BytesEnum, StrEnum, IntEnum, AccessTime, File, DateTime
from errors import SkipPluginModule
from text import _, ngettext, GettextFactory, NGettextFactory

View File

@@ -48,6 +48,9 @@ capabilities = dict(
# primary_key_types: Non-unicode primary keys in command output
primary_key_types=u'2.83',
# support for datetime values on the client
datetime_values=u'2.84'
)

View File

@@ -46,11 +46,13 @@ import plugable
from errors import (PublicError, CommandError, HelpError, InternalError,
NoSuchNamespaceError, ValidationError, NotFound,
NotConfiguredError, PromptFailed)
from constants import CLI_TAB
from constants import CLI_TAB, LDAP_GENERALIZED_TIME_FORMAT
from parameters import File, Str, Enum, Any
from text import _
from ipapython.version import API_VERSION
import datetime
def to_cli(name):
"""
@@ -155,6 +157,8 @@ class textui(backend.Backend):
"""
if type(value) is str:
return base64.b64encode(value)
elif type(value) is datetime.datetime:
return value.strftime(LDAP_GENERALIZED_TIME_FORMAT)
else:
return value

View File

@@ -206,3 +206,5 @@ DEFAULT_CONFIG = (
('jsonrpc_uri', object), # derived from xmlrpc_uri in Env._finalize_core()
)
LDAP_GENERALIZED_TIME_FORMAT = "%Y%m%d%H%M%SZ"

View File

@@ -102,6 +102,7 @@ a more detailed description for clarity.
import re
import decimal
import base64
import datetime
from xmlrpclib import MAXINT, MININT
from types import NoneType
@@ -109,7 +110,7 @@ from text import _ as ugettext
from plugable import ReadOnly, lock, check_name
from errors import ConversionError, RequirementError, ValidationError
from errors import PasswordMismatch, Base64DecodeError
from constants import TYPE_ERROR, CALLABLE_ERROR
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
@@ -1609,6 +1610,55 @@ class File(Str):
('noextrawhitespace', bool, False),
)
class DateTime(Param):
"""
DateTime parameter type.
Accepts LDAP Generalized time without in the following format:
'%Y%m%d%H%M%SZ'
Accepts subset of values defined by ISO 8601:
'%Y-%m-%dT%H:%M:%SZ'
'%Y-%m-%dT%H:%MZ'
'%Y-%m-%dZ'
Also accepts above formats using ' ' (space) as a separator instead of 'T'.
Refer to the `man strftime` for the explanations for the %Y,%m,%d,%H.%M,%S.
"""
accepted_formats = [LDAP_GENERALIZED_TIME_FORMAT, # generalized time
'%Y-%m-%dT%H:%M:%SZ', # ISO 8601, second precision
'%Y-%m-%dT%H:%MZ', # ISO 8601, minute precision
'%Y-%m-%dZ', # ISO 8601, date only
'%Y-%m-%d %H:%M:%SZ', # non-ISO 8601, second precision
'%Y-%m-%d %H:%MZ'] # non-ISO 8601, minute precision
type = datetime.datetime
type_error = _('must be datetime value')
def _convert_scalar(self, value, index=None):
if isinstance(value, basestring):
for date_format in self.accepted_formats:
try:
time = datetime.datetime.strptime(value, date_format)
return time
except ValueError:
pass
# If we get here, the strptime call did not succeed for any
# the accepted formats, therefore raise error
error = (_("does not match any of accepted formats: ") +
(', '.join(self.accepted_formats)))
raise ConversionError(name=self.get_param_name(),
index=index,
error=error)
return super(DateTime, self)._convert_scalar(value, index)
class AccessTime(Str):
"""

View File

@@ -33,6 +33,7 @@ Also see the `ipaserver.rpcserver` module.
from types import NoneType
from decimal import Decimal
import sys
import datetime
import os
import locale
import base64
@@ -41,17 +42,18 @@ import json
import socket
from urllib2 import urlparse
from xmlrpclib import (Binary, Fault, dumps, loads, ServerProxy, Transport,
ProtocolError, MININT, MAXINT)
from xmlrpclib import (Binary, Fault, DateTime, dumps, loads, ServerProxy,
Transport, ProtocolError, MININT, MAXINT)
import kerberos
from dns import resolver, rdatatype
from dns.exception import DNSException
from nss.error import NSPRError
from ipalib.backend import Connectible
from ipalib.constants import LDAP_GENERALIZED_TIME_FORMAT
from ipalib.errors import (public_errors, UnknownError, NetworkError,
KerberosError, XMLRPCMarshallError, JSONError, ConversionError)
from ipalib import errors
from ipalib import errors, capabilities
from ipalib.request import context, Connection
from ipalib.util import get_current_principal
from ipapython.ipa_log_manager import root_logger
@@ -163,6 +165,14 @@ def xml_wrap(value, version):
return unicode(value)
if isinstance(value, DN):
return str(value)
# Encode datetime.datetime objects as xmlrpclib.DateTime objects
if isinstance(value, datetime.datetime):
if capabilities.client_has_capability(version, 'datetime_values'):
return DateTime(value)
else:
return value.strftime(LDAP_GENERALIZED_TIME_FORMAT)
assert type(value) in (unicode, int, long, float, bool, NoneType)
return value
@@ -196,6 +206,9 @@ def xml_unwrap(value, encoding='UTF-8'):
if isinstance(value, Binary):
assert type(value.data) is str
return value.data
if isinstance(value, DateTime):
# xmlprc DateTime is converted to string of %Y%m%dT%H:%M:%S format
return datetime.datetime.strptime(str(value), "%Y%m%dT%H:%M:%S")
assert type(value) in (unicode, int, float, bool, NoneType)
return value
@@ -266,6 +279,11 @@ def json_encode_binary(val, version):
return {'__base64__': base64.b64encode(str(val))}
elif isinstance(val, DN):
return str(val)
elif isinstance(val, datetime.datetime):
if capabilities.client_has_capability(version, 'datetime_values'):
return {'__datetime__': val.strftime(LDAP_GENERALIZED_TIME_FORMAT)}
else:
return val.strftime(LDAP_GENERALIZED_TIME_FORMAT)
else:
return val
@@ -293,6 +311,9 @@ def json_decode_binary(val):
if isinstance(val, dict):
if '__base64__' in val:
return base64.b64decode(val['__base64__'])
elif '__datetime__' in val:
return datetime.datetime.strptime(val['__datetime__'],
LDAP_GENERALIZED_TIME_FORMAT)
else:
return dict((k, json_decode_binary(v)) for k, v in val.items())
elif isinstance(val, list):

View File

@@ -21,6 +21,7 @@
import string
import time
import datetime
import shutil
from decimal import Decimal
from copy import deepcopy
@@ -35,6 +36,7 @@ from ldap.controls import SimplePagedResultsControl
import ldapurl
from ipalib import errors, _
from ipalib.constants import LDAP_GENERALIZED_TIME_FORMAT
from ipapython import ipautil
from ipapython.ipautil import (
format_netloc, wait_for_open_socket, wait_for_open_ports, CIDict)
@@ -239,6 +241,7 @@ class IPASimpleLDAPObject(object):
'2.16.840.1.113719.1.301.4.41.1' : DN, # krbSubTrees
'2.16.840.1.113719.1.301.4.52.1' : DN, # krbObjectReferences
'2.16.840.1.113719.1.301.4.53.1' : DN, # krbPrincContainerRef
'1.3.6.1.4.1.1466.115.121.1.24' : datetime.datetime,
}
# In most cases we lookup the syntax from the schema returned by
@@ -408,6 +411,8 @@ class IPASimpleLDAPObject(object):
elif isinstance(val, dict):
dct = dict((self.encode(k), self.encode(v)) for k, v in val.iteritems())
return dct
elif isinstance(val, datetime.datetime):
return val.strftime(LDAP_GENERALIZED_TIME_FORMAT)
elif val is None:
return None
else:
@@ -426,6 +431,8 @@ class IPASimpleLDAPObject(object):
return val
elif target_type is unicode:
return val.decode('utf-8')
elif target_type is datetime.datetime:
return datetime.datetime.strptime(val, LDAP_GENERALIZED_TIME_FORMAT)
else:
return target_type(val)
except Exception, e: