* adding python 3.5 as a target

* adding env flag

* Aligning on arrow dates.
This commit is contained in:
kevgliss 2016-11-09 10:56:22 -08:00 committed by GitHub
parent b0eef03c73
commit e6b291d034
11 changed files with 131 additions and 40 deletions

View File

@ -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

View File

@ -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)

93
lemur/common/fields.py Normal file
View 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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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'

View File

@ -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):

View File

@ -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()