summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomas Babej <tbabej@redhat.com>2014-01-09 11:14:56 +0100
committerAlexander Bokovoy <abokovoy@redhat.com>2014-05-05 18:57:29 +0300
commit1df696f5432a673a24ff5cb273fe068a7d88d6ea (patch)
tree38b8ab1445b03806a482c9e58949c169373cc630
parent093c72d60e94d9545e38f2ad90cb24e6f44cbd62 (diff)
downloadfreeipa-1df696f5432a673a24ff5cb273fe068a7d88d6ea.tar.gz
freeipa-1df696f5432a673a24ff5cb273fe068a7d88d6ea.tar.xz
freeipa-1df696f5432a673a24ff5cb273fe068a7d88d6ea.zip
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>
-rw-r--r--API.txt1
-rw-r--r--VERSION4
-rw-r--r--ipalib/__init__.py2
-rw-r--r--ipalib/capabilities.py3
-rw-r--r--ipalib/cli.py6
-rw-r--r--ipalib/constants.py2
-rw-r--r--ipalib/parameters.py52
-rw-r--r--ipalib/rpc.py27
-rw-r--r--ipapython/ipaldap.py7
9 files changed, 96 insertions, 8 deletions
diff --git a/API.txt b/API.txt
index c2654b144..eb8a61f64 100644
--- a/API.txt
+++ b/API.txt
@@ -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
diff --git a/VERSION b/VERSION
index 3b48d6407..32bddcf9d 100644
--- a/VERSION
+++ b/VERSION
@@ -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
diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index 553c07197..2a87103b8 100644
--- a/ipalib/__init__.py
+++ b/ipalib/__init__.py
@@ -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
diff --git a/ipalib/capabilities.py b/ipalib/capabilities.py
index 3dd93294f..f2e45a0f6 100644
--- a/ipalib/capabilities.py
+++ b/ipalib/capabilities.py
@@ -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'
)
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 4250aaf54..f03db9c61 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -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
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 6cc50eacf..e98eee6f8 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -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"
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index fc5e64981..33ae182b5 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -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):
"""
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 73ae115b3..c44ffb6e1 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -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):
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 56e7d1384..12450e8f2 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -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: