Time (#482)
* adding python 3.5 as a target * adding env flag * Aligning on arrow dates.
This commit is contained in:
parent
b0eef03c73
commit
e6b291d034
|
@ -16,6 +16,8 @@ from lemur.users.schemas import UserNestedOutputSchema
|
||||||
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||||
from lemur.common import validators, missing
|
from lemur.common import validators, missing
|
||||||
|
|
||||||
|
from lemur.common.fields import ArrowDateTime
|
||||||
|
|
||||||
|
|
||||||
class AuthorityInputSchema(LemurInputSchema):
|
class AuthorityInputSchema(LemurInputSchema):
|
||||||
name = fields.String(required=True)
|
name = fields.String(required=True)
|
||||||
|
@ -23,8 +25,8 @@ class AuthorityInputSchema(LemurInputSchema):
|
||||||
description = fields.String()
|
description = fields.String()
|
||||||
common_name = fields.String(required=True, validate=validators.sensitive_domain)
|
common_name = fields.String(required=True, validate=validators.sensitive_domain)
|
||||||
|
|
||||||
validity_start = fields.DateTime()
|
validity_start = ArrowDateTime()
|
||||||
validity_end = fields.DateTime()
|
validity_end = ArrowDateTime()
|
||||||
validity_years = fields.Integer()
|
validity_years = fields.Integer()
|
||||||
|
|
||||||
# certificate body fields
|
# certificate body fields
|
||||||
|
|
|
@ -23,6 +23,8 @@ from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||||
from lemur.common import validators, missing
|
from lemur.common import validators, missing
|
||||||
from lemur.notifications import service as notification_service
|
from lemur.notifications import service as notification_service
|
||||||
|
|
||||||
|
from lemur.common.fields import ArrowDateTime
|
||||||
|
|
||||||
|
|
||||||
class CertificateSchema(LemurInputSchema):
|
class CertificateSchema(LemurInputSchema):
|
||||||
owner = fields.Email(required=True)
|
owner = fields.Email(required=True)
|
||||||
|
@ -46,8 +48,8 @@ class CertificateInputSchema(CertificateCreationSchema):
|
||||||
common_name = fields.String(required=True, validate=validators.sensitive_domain)
|
common_name = fields.String(required=True, validate=validators.sensitive_domain)
|
||||||
authority = fields.Nested(AssociatedAuthoritySchema, required=True)
|
authority = fields.Nested(AssociatedAuthoritySchema, required=True)
|
||||||
|
|
||||||
validity_start = fields.DateTime()
|
validity_start = ArrowDateTime()
|
||||||
validity_end = fields.DateTime()
|
validity_end = ArrowDateTime()
|
||||||
validity_years = fields.Integer()
|
validity_years = fields.Integer()
|
||||||
|
|
||||||
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
import arrow
|
||||||
|
import warnings
|
||||||
|
from datetime import datetime as dt
|
||||||
|
from marshmallow.fields import Field
|
||||||
|
from marshmallow import utils
|
||||||
|
|
||||||
|
|
||||||
|
class ArrowDateTime(Field):
|
||||||
|
"""A formatted datetime string in UTC.
|
||||||
|
|
||||||
|
Example: ``'2014-12-22T03:12:58.019077+00:00'``
|
||||||
|
|
||||||
|
Timezone-naive `datetime` objects are converted to
|
||||||
|
UTC (+00:00) by :meth:`Schema.dump <marshmallow.Schema.dump>`.
|
||||||
|
:meth:`Schema.load <marshmallow.Schema.load>` returns `datetime`
|
||||||
|
objects that are timezone-aware.
|
||||||
|
|
||||||
|
:param str format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
|
||||||
|
or a date format string. If `None`, defaults to "iso".
|
||||||
|
:param kwargs: The same keyword arguments that :class:`Field` receives.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
DATEFORMAT_SERIALIZATION_FUNCS = {
|
||||||
|
'iso': utils.isoformat,
|
||||||
|
'iso8601': utils.isoformat,
|
||||||
|
'rfc': utils.rfcformat,
|
||||||
|
'rfc822': utils.rfcformat,
|
||||||
|
}
|
||||||
|
|
||||||
|
DATEFORMAT_DESERIALIZATION_FUNCS = {
|
||||||
|
'iso': utils.from_iso,
|
||||||
|
'iso8601': utils.from_iso,
|
||||||
|
'rfc': utils.from_rfc,
|
||||||
|
'rfc822': utils.from_rfc,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_FORMAT = 'iso'
|
||||||
|
|
||||||
|
localtime = False
|
||||||
|
default_error_messages = {
|
||||||
|
'invalid': 'Not a valid datetime.',
|
||||||
|
'format': '"{input}" cannot be formatted as a datetime.',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, format=None, **kwargs):
|
||||||
|
super(ArrowDateTime, self).__init__(**kwargs)
|
||||||
|
# Allow this to be None. It may be set later in the ``_serialize``
|
||||||
|
# or ``_desrialize`` methods This allows a Schema to dynamically set the
|
||||||
|
# dateformat, e.g. from a Meta option
|
||||||
|
self.dateformat = format
|
||||||
|
|
||||||
|
def _add_to_schema(self, field_name, schema):
|
||||||
|
super(ArrowDateTime, self)._add_to_schema(field_name, schema)
|
||||||
|
self.dateformat = self.dateformat or schema.opts.dateformat
|
||||||
|
|
||||||
|
def _serialize(self, value, attr, obj):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
self.dateformat = self.dateformat or self.DEFAULT_FORMAT
|
||||||
|
format_func = self.DATEFORMAT_SERIALIZATION_FUNCS.get(self.dateformat, None)
|
||||||
|
if format_func:
|
||||||
|
try:
|
||||||
|
return format_func(value, localtime=self.localtime)
|
||||||
|
except (AttributeError, ValueError) as err:
|
||||||
|
self.fail('format', input=value)
|
||||||
|
else:
|
||||||
|
return value.strftime(self.dateformat)
|
||||||
|
|
||||||
|
def _deserialize(self, value, attr, data):
|
||||||
|
if not value: # Falsy values, e.g. '', None, [] are not valid
|
||||||
|
raise self.fail('invalid')
|
||||||
|
self.dateformat = self.dateformat or self.DEFAULT_FORMAT
|
||||||
|
func = self.DATEFORMAT_DESERIALIZATION_FUNCS.get(self.dateformat)
|
||||||
|
if func:
|
||||||
|
try:
|
||||||
|
return arrow.get(func(value))
|
||||||
|
except (TypeError, AttributeError, ValueError):
|
||||||
|
raise self.fail('invalid')
|
||||||
|
elif self.dateformat:
|
||||||
|
try:
|
||||||
|
return dt.datetime.strptime(value, self.dateformat)
|
||||||
|
except (TypeError, AttributeError, ValueError):
|
||||||
|
raise self.fail('invalid')
|
||||||
|
elif utils.dateutil_available:
|
||||||
|
try:
|
||||||
|
return arrow.get(utils.from_datestring(value))
|
||||||
|
except TypeError:
|
||||||
|
raise self.fail('invalid')
|
||||||
|
else:
|
||||||
|
warnings.warn('It is recommended that you install python-dateutil '
|
||||||
|
'for improved datetime deserialization.')
|
||||||
|
raise self.fail('invalid')
|
|
@ -13,14 +13,12 @@ def convert_validity_years(data):
|
||||||
"""
|
"""
|
||||||
if data.get('validity_years'):
|
if data.get('validity_years'):
|
||||||
now = arrow.utcnow()
|
now = arrow.utcnow()
|
||||||
data['validity_start'] = now.date().isoformat()
|
data['validity_start'] = now.isoformat()
|
||||||
|
|
||||||
end = now.replace(years=+int(data['validity_years']))
|
end = now.replace(years=+int(data['validity_years']))
|
||||||
data['validity_end'] = end.date().isoformat()
|
|
||||||
|
|
||||||
if not current_app.config.get('LEMUR_ALLOW_WEEKEND_EXPIRATION', True):
|
if not current_app.config.get('LEMUR_ALLOW_WEEKEND_EXPIRATION', True):
|
||||||
if is_weekend(end):
|
if is_weekend(end):
|
||||||
end = end.replace(days=-2)
|
end = end.replace(days=-2)
|
||||||
data['validity_end'] = end.date().isoformat()
|
|
||||||
|
|
||||||
|
data['validity_end'] = end.isoformat()
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -109,10 +109,10 @@ def dates(data):
|
||||||
raise ValidationError('Validity start must be before validity end.')
|
raise ValidationError('Validity start must be before validity end.')
|
||||||
|
|
||||||
if data.get('authority'):
|
if data.get('authority'):
|
||||||
if data.get('validity_start').replace(hour=0, minute=0, second=0, tzinfo=None) < data['authority'].authority_certificate.not_before.replace(hour=0, minute=0, second=0):
|
if data.get('validity_start').date() < data['authority'].authority_certificate.not_before.date():
|
||||||
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].authority_certificate.not_before))
|
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].authority_certificate.not_before))
|
||||||
|
|
||||||
if data.get('validity_end').replace(hour=0, minute=0, second=0, tzinfo=None) > data['authority'].authority_certificate.not_after.replace(hour=0, minute=0, second=0):
|
if data.get('validity_end').date() > data['authority'].authority_certificate.not_after.date():
|
||||||
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after))
|
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].authority_certificate.not_after))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -56,13 +56,12 @@ def determine_validity_years(end_date):
|
||||||
:return: str validity in years
|
:return: str validity in years
|
||||||
"""
|
"""
|
||||||
now = arrow.utcnow()
|
now = arrow.utcnow()
|
||||||
then = arrow.get(end_date)
|
|
||||||
|
|
||||||
if then < now.replace(years=+1):
|
if end_date < now.replace(years=+1):
|
||||||
return 1
|
return 1
|
||||||
elif then < now.replace(years=+2):
|
elif end_date < now.replace(years=+2):
|
||||||
return 2
|
return 2
|
||||||
elif then < now.replace(years=+3):
|
elif end_date < now.replace(years=+3):
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
raise Exception("DigiCert issued certificates cannot exceed three"
|
raise Exception("DigiCert issued certificates cannot exceed three"
|
||||||
|
@ -75,9 +74,8 @@ def get_issuance(options):
|
||||||
:param options:
|
:param options:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
end_date = arrow.get(options['validity_end'])
|
validity_years = determine_validity_years(options['validity_end'])
|
||||||
validity_years = determine_validity_years(end_date)
|
return validity_years
|
||||||
return end_date, validity_years
|
|
||||||
|
|
||||||
|
|
||||||
def process_options(options, csr):
|
def process_options(options, csr):
|
||||||
|
@ -109,8 +107,8 @@ def process_options(options, csr):
|
||||||
|
|
||||||
data['certificate']['dns_names'] = dns_names
|
data['certificate']['dns_names'] = dns_names
|
||||||
|
|
||||||
end_date, validity_years = get_issuance(options)
|
validity_years = get_issuance(options)
|
||||||
data['custom_expiration_date'] = end_date.format('YYYY-MM-DD')
|
data['custom_expiration_date'] = options['validity_end'].format('YYYY-MM-DD')
|
||||||
data['validity_years'] = validity_years
|
data['validity_years'] = validity_years
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -32,7 +32,7 @@ def test_process_options(app):
|
||||||
'dns_names': names,
|
'dns_names': names,
|
||||||
'signature_hash': 'sha256'
|
'signature_hash': 'sha256'
|
||||||
},
|
},
|
||||||
'organization': {'id': 0},
|
'organization': {'id': 111111},
|
||||||
'validity_years': 1,
|
'validity_years': 1,
|
||||||
'custom_expiration_date': arrow.get(2017, 5, 7).format('YYYY-MM-DD')
|
'custom_expiration_date': arrow.get(2017, 5, 7).format('YYYY-MM-DD')
|
||||||
}
|
}
|
||||||
|
@ -47,18 +47,14 @@ def test_issuance():
|
||||||
'validity_start': arrow.get(2016, 10, 30)
|
'validity_start': arrow.get(2016, 10, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
end_date, period = get_issuance(options)
|
assert get_issuance(options) == 2
|
||||||
|
|
||||||
assert period == 2
|
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'validity_end': arrow.get(2017, 5, 7),
|
'validity_end': arrow.get(2017, 5, 7),
|
||||||
'validity_start': arrow.get(2016, 10, 30)
|
'validity_start': arrow.get(2016, 10, 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
end_date, period = get_issuance(options)
|
assert get_issuance(options) == 1
|
||||||
|
|
||||||
assert period == 1
|
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'validity_end': arrow.get(2020, 5, 7),
|
'validity_end': arrow.get(2020, 5, 7),
|
||||||
|
@ -66,7 +62,7 @@ def test_issuance():
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
end_date, period = get_issuance(options)
|
period = get_issuance(options)
|
||||||
|
|
||||||
|
|
||||||
def test_signature_hash(app):
|
def test_signature_hash(app):
|
||||||
|
|
|
@ -80,8 +80,8 @@ def process_options(options):
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.get('validity_end'):
|
if options.get('validity_end'):
|
||||||
end_date, period = get_default_issuance(options)
|
period = get_default_issuance(options)
|
||||||
data['specificEndDate'] = str(end_date)
|
data['specificEndDate'] = options['validity_end'].format("MM/DD/YYYY")
|
||||||
data['validityPeriod'] = period
|
data['validityPeriod'] = period
|
||||||
|
|
||||||
elif options.get('validity_years'):
|
elif options.get('validity_years'):
|
||||||
|
@ -100,19 +100,16 @@ def get_default_issuance(options):
|
||||||
:param options:
|
:param options:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
specific_end_date = arrow.get(options['validity_end']).replace(days=-1).format("MM/DD/YYYY")
|
|
||||||
|
|
||||||
now = arrow.utcnow()
|
now = arrow.utcnow()
|
||||||
then = arrow.get(options['validity_end'])
|
|
||||||
|
|
||||||
if then < now.replace(years=+1):
|
if options['validity_end'] < now.replace(years=+1):
|
||||||
validity_period = '1Y'
|
validity_period = '1Y'
|
||||||
elif then < now.replace(years=+2):
|
elif options['validity_end'] < now.replace(years=+2):
|
||||||
validity_period = '2Y'
|
validity_period = '2Y'
|
||||||
else:
|
else:
|
||||||
raise Exception("Verisign issued certificates cannot exceed two years in validity")
|
raise Exception("Verisign issued certificates cannot exceed two years in validity")
|
||||||
|
|
||||||
return specific_end_date, validity_period
|
return validity_period
|
||||||
|
|
||||||
|
|
||||||
def handle_response(content):
|
def handle_response(content):
|
||||||
|
|
|
@ -72,7 +72,9 @@ LEMUR_INSTANCE_PROFILE = 'Lemur'
|
||||||
|
|
||||||
DIGICERT_URL = 'https://www.digicert.com'
|
DIGICERT_URL = 'https://www.digicert.com'
|
||||||
DIGICERT_API_KEY = 'api-key'
|
DIGICERT_API_KEY = 'api-key'
|
||||||
DIGICERT_ORG_ID = 000000
|
DIGICERT_ORG_ID = 111111
|
||||||
|
DIGICERT_ROOT = "ROOT"
|
||||||
|
DIGICERT_INTERMEDIATE = "INTERMEDIATE"
|
||||||
|
|
||||||
|
|
||||||
VERISIGN_URL = 'http://example.com'
|
VERISIGN_URL = 'http://example.com'
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals # at top of module
|
||||||
import json
|
import json
|
||||||
import pytest
|
import pytest
|
||||||
import datetime
|
import datetime
|
||||||
|
import arrow
|
||||||
|
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
@ -133,6 +134,8 @@ def test_certificate_input_schema(client, authority):
|
||||||
'owner': 'jim@example.com',
|
'owner': 'jim@example.com',
|
||||||
'authority': {'id': authority.id},
|
'authority': {'id': authority.id},
|
||||||
'description': 'testtestest',
|
'description': 'testtestest',
|
||||||
|
'validityEnd': arrow.get(2016, 11, 9).isoformat(),
|
||||||
|
'validityStart': arrow.get(2015, 11, 9).isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
data, errors = CertificateInputSchema().load(input_data)
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
@ -145,7 +148,7 @@ def test_certificate_input_schema(client, authority):
|
||||||
assert data['country'] == 'US'
|
assert data['country'] == 'US'
|
||||||
assert data['location'] == 'Los Gatos'
|
assert data['location'] == 'Los Gatos'
|
||||||
|
|
||||||
assert len(data.keys()) == 13
|
assert len(data.keys()) == 15
|
||||||
|
|
||||||
|
|
||||||
def test_certificate_input_with_extensions(client, authority):
|
def test_certificate_input_with_extensions(client, authority):
|
||||||
|
|
|
@ -9,9 +9,9 @@ def test_convert_validity_years(session):
|
||||||
with freeze_time("2016-01-01"):
|
with freeze_time("2016-01-01"):
|
||||||
data = convert_validity_years(dict(validity_years=2))
|
data = convert_validity_years(dict(validity_years=2))
|
||||||
|
|
||||||
assert data['validity_start'] == arrow.utcnow().date().isoformat()
|
assert data['validity_start'] == arrow.utcnow().isoformat()
|
||||||
assert data['validity_end'] == arrow.utcnow().replace(years=+2).date().isoformat()
|
assert data['validity_end'] == arrow.utcnow().replace(years=+2).isoformat()
|
||||||
|
|
||||||
with freeze_time("2015-01-10"):
|
with freeze_time("2015-01-10"):
|
||||||
data = convert_validity_years(dict(validity_years=1))
|
data = convert_validity_years(dict(validity_years=1))
|
||||||
assert data['validity_end'] == arrow.utcnow().replace(years=+1, days=-2).date().isoformat()
|
assert data['validity_end'] == arrow.utcnow().replace(years=+1, days=-2).isoformat()
|
||||||
|
|
Loading…
Reference in New Issue