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 import validators, missing
|
||||
|
||||
from lemur.common.fields import ArrowDateTime
|
||||
|
||||
|
||||
class AuthorityInputSchema(LemurInputSchema):
|
||||
name = fields.String(required=True)
|
||||
@ -23,8 +25,8 @@ class AuthorityInputSchema(LemurInputSchema):
|
||||
description = fields.String()
|
||||
common_name = fields.String(required=True, validate=validators.sensitive_domain)
|
||||
|
||||
validity_start = fields.DateTime()
|
||||
validity_end = fields.DateTime()
|
||||
validity_start = ArrowDateTime()
|
||||
validity_end = ArrowDateTime()
|
||||
validity_years = fields.Integer()
|
||||
|
||||
# certificate body fields
|
||||
|
@ -23,6 +23,8 @@ from lemur.common.schema import LemurInputSchema, LemurOutputSchema
|
||||
from lemur.common import validators, missing
|
||||
from lemur.notifications import service as notification_service
|
||||
|
||||
from lemur.common.fields import ArrowDateTime
|
||||
|
||||
|
||||
class CertificateSchema(LemurInputSchema):
|
||||
owner = fields.Email(required=True)
|
||||
@ -46,8 +48,8 @@ class CertificateInputSchema(CertificateCreationSchema):
|
||||
common_name = fields.String(required=True, validate=validators.sensitive_domain)
|
||||
authority = fields.Nested(AssociatedAuthoritySchema, required=True)
|
||||
|
||||
validity_start = fields.DateTime()
|
||||
validity_end = fields.DateTime()
|
||||
validity_start = ArrowDateTime()
|
||||
validity_end = ArrowDateTime()
|
||||
validity_years = fields.Integer()
|
||||
|
||||
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||
|
93
lemur/common/fields.py
Normal file
93
lemur/common/fields.py
Normal file
@ -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'):
|
||||
now = arrow.utcnow()
|
||||
data['validity_start'] = now.date().isoformat()
|
||||
data['validity_start'] = now.isoformat()
|
||||
|
||||
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 is_weekend(end):
|
||||
end = end.replace(days=-2)
|
||||
data['validity_end'] = end.date().isoformat()
|
||||
|
||||
data['validity_end'] = end.isoformat()
|
||||
return data
|
||||
|
@ -109,10 +109,10 @@ def dates(data):
|
||||
raise ValidationError('Validity start must be before validity end.')
|
||||
|
||||
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))
|
||||
|
||||
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))
|
||||
|
||||
return data
|
||||
|
@ -56,13 +56,12 @@ def determine_validity_years(end_date):
|
||||
:return: str validity in years
|
||||
"""
|
||||
now = arrow.utcnow()
|
||||
then = arrow.get(end_date)
|
||||
|
||||
if then < now.replace(years=+1):
|
||||
if end_date < now.replace(years=+1):
|
||||
return 1
|
||||
elif then < now.replace(years=+2):
|
||||
elif end_date < now.replace(years=+2):
|
||||
return 2
|
||||
elif then < now.replace(years=+3):
|
||||
elif end_date < now.replace(years=+3):
|
||||
return 3
|
||||
|
||||
raise Exception("DigiCert issued certificates cannot exceed three"
|
||||
@ -75,9 +74,8 @@ def get_issuance(options):
|
||||
:param options:
|
||||
:return:
|
||||
"""
|
||||
end_date = arrow.get(options['validity_end'])
|
||||
validity_years = determine_validity_years(end_date)
|
||||
return end_date, validity_years
|
||||
validity_years = determine_validity_years(options['validity_end'])
|
||||
return validity_years
|
||||
|
||||
|
||||
def process_options(options, csr):
|
||||
@ -109,8 +107,8 @@ def process_options(options, csr):
|
||||
|
||||
data['certificate']['dns_names'] = dns_names
|
||||
|
||||
end_date, validity_years = get_issuance(options)
|
||||
data['custom_expiration_date'] = end_date.format('YYYY-MM-DD')
|
||||
validity_years = get_issuance(options)
|
||||
data['custom_expiration_date'] = options['validity_end'].format('YYYY-MM-DD')
|
||||
data['validity_years'] = validity_years
|
||||
|
||||
return data
|
||||
|
@ -32,7 +32,7 @@ def test_process_options(app):
|
||||
'dns_names': names,
|
||||
'signature_hash': 'sha256'
|
||||
},
|
||||
'organization': {'id': 0},
|
||||
'organization': {'id': 111111},
|
||||
'validity_years': 1,
|
||||
'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)
|
||||
}
|
||||
|
||||
end_date, period = get_issuance(options)
|
||||
|
||||
assert period == 2
|
||||
assert get_issuance(options) == 2
|
||||
|
||||
options = {
|
||||
'validity_end': arrow.get(2017, 5, 7),
|
||||
'validity_start': arrow.get(2016, 10, 30)
|
||||
}
|
||||
|
||||
end_date, period = get_issuance(options)
|
||||
|
||||
assert period == 1
|
||||
assert get_issuance(options) == 1
|
||||
|
||||
options = {
|
||||
'validity_end': arrow.get(2020, 5, 7),
|
||||
@ -66,7 +62,7 @@ def test_issuance():
|
||||
}
|
||||
|
||||
with pytest.raises(Exception):
|
||||
end_date, period = get_issuance(options)
|
||||
period = get_issuance(options)
|
||||
|
||||
|
||||
def test_signature_hash(app):
|
||||
|
@ -80,8 +80,8 @@ def process_options(options):
|
||||
}
|
||||
|
||||
if options.get('validity_end'):
|
||||
end_date, period = get_default_issuance(options)
|
||||
data['specificEndDate'] = str(end_date)
|
||||
period = get_default_issuance(options)
|
||||
data['specificEndDate'] = options['validity_end'].format("MM/DD/YYYY")
|
||||
data['validityPeriod'] = period
|
||||
|
||||
elif options.get('validity_years'):
|
||||
@ -100,19 +100,16 @@ def get_default_issuance(options):
|
||||
:param options:
|
||||
:return:
|
||||
"""
|
||||
specific_end_date = arrow.get(options['validity_end']).replace(days=-1).format("MM/DD/YYYY")
|
||||
|
||||
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'
|
||||
elif then < now.replace(years=+2):
|
||||
elif options['validity_end'] < now.replace(years=+2):
|
||||
validity_period = '2Y'
|
||||
else:
|
||||
raise Exception("Verisign issued certificates cannot exceed two years in validity")
|
||||
|
||||
return specific_end_date, validity_period
|
||||
return validity_period
|
||||
|
||||
|
||||
def handle_response(content):
|
||||
|
@ -72,7 +72,9 @@ LEMUR_INSTANCE_PROFILE = 'Lemur'
|
||||
|
||||
DIGICERT_URL = 'https://www.digicert.com'
|
||||
DIGICERT_API_KEY = 'api-key'
|
||||
DIGICERT_ORG_ID = 000000
|
||||
DIGICERT_ORG_ID = 111111
|
||||
DIGICERT_ROOT = "ROOT"
|
||||
DIGICERT_INTERMEDIATE = "INTERMEDIATE"
|
||||
|
||||
|
||||
VERISIGN_URL = 'http://example.com'
|
||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals # at top of module
|
||||
import json
|
||||
import pytest
|
||||
import datetime
|
||||
import arrow
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
@ -133,6 +134,8 @@ def test_certificate_input_schema(client, authority):
|
||||
'owner': 'jim@example.com',
|
||||
'authority': {'id': authority.id},
|
||||
'description': 'testtestest',
|
||||
'validityEnd': arrow.get(2016, 11, 9).isoformat(),
|
||||
'validityStart': arrow.get(2015, 11, 9).isoformat()
|
||||
}
|
||||
|
||||
data, errors = CertificateInputSchema().load(input_data)
|
||||
@ -145,7 +148,7 @@ def test_certificate_input_schema(client, authority):
|
||||
assert data['country'] == 'US'
|
||||
assert data['location'] == 'Los Gatos'
|
||||
|
||||
assert len(data.keys()) == 13
|
||||
assert len(data.keys()) == 15
|
||||
|
||||
|
||||
def test_certificate_input_with_extensions(client, authority):
|
||||
|
@ -9,9 +9,9 @@ def test_convert_validity_years(session):
|
||||
with freeze_time("2016-01-01"):
|
||||
data = convert_validity_years(dict(validity_years=2))
|
||||
|
||||
assert data['validity_start'] == arrow.utcnow().date().isoformat()
|
||||
assert data['validity_end'] == arrow.utcnow().replace(years=+2).date().isoformat()
|
||||
assert data['validity_start'] == arrow.utcnow().isoformat()
|
||||
assert data['validity_end'] == arrow.utcnow().replace(years=+2).isoformat()
|
||||
|
||||
with freeze_time("2015-01-10"):
|
||||
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
Block a user