* Closes #278 and #199, Starting transition to marshmallow
This commit is contained in:
parent
941d36ebfe
commit
52f44c3ea6
|
@ -1,3 +1,4 @@
|
||||||
|
/.cache
|
||||||
.coverage
|
.coverage
|
||||||
.tox
|
.tox
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
@ -9,10 +9,8 @@ matrix:
|
||||||
include:
|
include:
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: TOXENV=py27
|
env: TOXENV=py27
|
||||||
- python: "3.3"
|
- python: "3.5"
|
||||||
env: TOXENV=py33
|
env: TOXENV=py35
|
||||||
- python: "3.4"
|
|
||||||
env: TOXENV=py34
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"angular-ui-switch": "~0.1.0",
|
"angular-ui-switch": "~0.1.0",
|
||||||
"angular-chart.js": "~0.7.1",
|
"angular-chart.js": "~0.7.1",
|
||||||
"satellizer": "~0.9.4",
|
"satellizer": "~0.9.4",
|
||||||
"angularjs-toaster": "~0.4.14",
|
"angularjs-toaster": "~1.0.0",
|
||||||
"ngletteravatar": "~3.0.1",
|
"ngletteravatar": "~3.0.1",
|
||||||
"angular-ui-router": "~0.2.15",
|
"angular-ui-router": "~0.2.15",
|
||||||
"angular-clipboard": "~1.1.1",
|
"angular-clipboard": "~1.1.1",
|
||||||
|
|
|
@ -42,13 +42,15 @@ class Authority(db.Model):
|
||||||
self.body = body
|
self.body = body
|
||||||
self.chain = chain
|
self.chain = chain
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
|
self.description = description
|
||||||
self.plugin_name = plugin_name
|
self.plugin_name = plugin_name
|
||||||
cert = x509.load_pem_x509_certificate(str(body), default_backend())
|
cert = x509.load_pem_x509_certificate(bytes(body), default_backend())
|
||||||
self.cn = get_cn(cert)
|
self.cn = get_cn(cert)
|
||||||
self.not_before = get_not_before(cert)
|
self.not_before = get_not_before(cert)
|
||||||
self.not_after = get_not_after(cert)
|
self.not_after = get_not_after(cert)
|
||||||
self.roles = roles
|
|
||||||
self.description = description
|
if roles:
|
||||||
|
self.roles = roles
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||||
|
|
|
@ -228,7 +228,7 @@ class Certificate(db.Model):
|
||||||
cn = Column(String(128))
|
cn = Column(String(128))
|
||||||
description = Column(String(1024))
|
description = Column(String(1024))
|
||||||
active = Column(Boolean, default=True)
|
active = Column(Boolean, default=True)
|
||||||
san = Column(String(1024))
|
san = Column(String(1024)) # TODO this should be migrated to boolean
|
||||||
not_before = Column(DateTime)
|
not_before = Column(DateTime)
|
||||||
not_after = Column(DateTime)
|
not_after = Column(DateTime)
|
||||||
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
||||||
|
@ -250,7 +250,7 @@ class Certificate(db.Model):
|
||||||
# We encrypt the private_key on creation
|
# We encrypt the private_key on creation
|
||||||
self.private_key = private_key
|
self.private_key = private_key
|
||||||
self.chain = chain
|
self.chain = chain
|
||||||
cert = x509.load_pem_x509_certificate(str(self.body), default_backend())
|
cert = x509.load_pem_x509_certificate(bytes(self.body), default_backend())
|
||||||
self.signing_algorithm = get_signing_algorithm(cert)
|
self.signing_algorithm = get_signing_algorithm(cert)
|
||||||
self.bits = get_bitstrength(cert)
|
self.bits = get_bitstrength(cert)
|
||||||
self.issuer = get_issuer(cert)
|
self.issuer = get_issuer(cert)
|
||||||
|
|
|
@ -0,0 +1,302 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.certificates.schemas
|
||||||
|
:platform: unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
|
||||||
|
from marshmallow import fields, validates_schema, pre_load, post_dump
|
||||||
|
from marshmallow.exceptions import ValidationError
|
||||||
|
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
|
from lemur.auth.permissions import SensitiveDomainPermission
|
||||||
|
from lemur.schemas import AssociatedAuthoritySchema, AssociatedDestinationSchema, AssociatedCertificateSchema, \
|
||||||
|
AssociatedNotificationSchema, PluginSchema
|
||||||
|
from lemur.common.schema import LemurInputSchema, LemurOutputSchema, LemurSchema
|
||||||
|
|
||||||
|
from lemur.domains import service as domain_service
|
||||||
|
|
||||||
|
|
||||||
|
def validate_public_certificate(body):
|
||||||
|
"""
|
||||||
|
Determines if specified string is valid public certificate.
|
||||||
|
|
||||||
|
:param body:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
x509.load_pem_x509_certificate(bytes(body), default_backend())
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError('Public certificate presented is not valid.')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_private_key(key):
|
||||||
|
"""
|
||||||
|
User to validate that a given string is a RSA private key
|
||||||
|
|
||||||
|
:param key:
|
||||||
|
:return: :raise ValueError:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
serialization.load_pem_private_key(bytes(key), None, backend=default_backend())
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError('Private key presented is not valid.')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_domain(domain):
|
||||||
|
"""
|
||||||
|
Determines if domain has been marked as sensitive.
|
||||||
|
:param domain:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
domains = domain_service.get_by_name(domain)
|
||||||
|
for domain in domains:
|
||||||
|
# we only care about non-admins
|
||||||
|
if not SensitiveDomainPermission().can():
|
||||||
|
if domain.sensitive:
|
||||||
|
raise ValidationError(
|
||||||
|
'Domain {0} has been marked as sensitive, contact and administrator \
|
||||||
|
to issue the certificate.'.format(domain))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_oid_type(oid_type):
|
||||||
|
"""
|
||||||
|
Determines if the specified oid type is valid.
|
||||||
|
:param oid_type:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
valid_types = ['b64asn1', 'string', 'ia5string']
|
||||||
|
if oid_type.lower() not in [o_type.lower() for o_type in valid_types]:
|
||||||
|
raise ValidationError('Invalid Oid Type: {0} choose from {1}'.format(oid_type, ",".join(valid_types)))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_sub_alt_type(alt_type):
|
||||||
|
"""
|
||||||
|
Determines if the specified subject alternate type is valid.
|
||||||
|
:param alt_type:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
valid_types = ['DNSName', 'IPAddress', 'uniFormResourceIdentifier', 'directoryName', 'rfc822Name', 'registrationID',
|
||||||
|
'otherName', 'x400Address', 'EDIPartyName']
|
||||||
|
if alt_type.lower() not in [a_type.lower() for a_type in valid_types]:
|
||||||
|
raise ValidationError('Invalid SubAltName Type: {0} choose from {1}'.format(type, ",".join(valid_types)))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_csr(data):
|
||||||
|
"""
|
||||||
|
Determines if the CSR is valid.
|
||||||
|
:param data:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
x509.load_pem_x509_csr(bytes(data), default_backend())
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError('CSR presented is not valid.')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseExtensionSchema(LemurSchema):
|
||||||
|
@pre_load(pass_many=True)
|
||||||
|
def preprocess(self, data, many):
|
||||||
|
return self.under(data, many=many)
|
||||||
|
|
||||||
|
@post_dump(pass_many=True)
|
||||||
|
def post_process(self, data, many):
|
||||||
|
if data:
|
||||||
|
data = self.camel(data, many=many)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class BasicConstraintsSchema(BaseExtensionSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorityIdentifierSchema(BaseExtensionSchema):
|
||||||
|
use_authority_cert = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorityKeyIdentifierSchema(BaseExtensionSchema):
|
||||||
|
use_key_identifier = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateInfoAccessSchema(BaseExtensionSchema):
|
||||||
|
include_aia = fields.Boolean()
|
||||||
|
|
||||||
|
@post_dump
|
||||||
|
def handle_keys(self, data):
|
||||||
|
return {'includeAIA': data['include_aia']}
|
||||||
|
|
||||||
|
|
||||||
|
class KeyUsageSchema(BaseExtensionSchema):
|
||||||
|
use_crl_sign = fields.Boolean()
|
||||||
|
use_data_encipherment = fields.Boolean()
|
||||||
|
use_decipher_only = fields.Boolean()
|
||||||
|
use_encipher_only = fields.Boolean()
|
||||||
|
use_key_encipherment = fields.Boolean()
|
||||||
|
use_digital_signature = fields.Boolean()
|
||||||
|
use_non_repudiation = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendedKeyUsageSchema(BaseExtensionSchema):
|
||||||
|
use_server_authentication = fields.Boolean()
|
||||||
|
use_client_authentication = fields.Boolean()
|
||||||
|
use_eap_over_lan = fields.Boolean()
|
||||||
|
use_eap_over_ppp = fields.Boolean()
|
||||||
|
use_ocsp_signing = fields.Boolean()
|
||||||
|
use_smart_card_authentication = fields.Boolean()
|
||||||
|
use_timestamping = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectKeyIdentifierSchema(BaseExtensionSchema):
|
||||||
|
include_ski = fields.Boolean()
|
||||||
|
|
||||||
|
@post_dump
|
||||||
|
def handle_keys(self, data):
|
||||||
|
return {'includeSKI': data['include_ski']}
|
||||||
|
|
||||||
|
|
||||||
|
class SubAltNameSchema(BaseExtensionSchema):
|
||||||
|
name_type = fields.String(validate=validate_sub_alt_type)
|
||||||
|
value = fields.String()
|
||||||
|
|
||||||
|
@validates_schema
|
||||||
|
def check_sensitive(self, data):
|
||||||
|
if data['name_type'] == 'DNSName':
|
||||||
|
validate_domain(data['value'])
|
||||||
|
|
||||||
|
|
||||||
|
class SubAltNamesSchema(BaseExtensionSchema):
|
||||||
|
names = fields.Nested(SubAltNameSchema, many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomOIDSchema(BaseExtensionSchema):
|
||||||
|
oid = fields.String()
|
||||||
|
oid_type = fields.String(validate=validate_oid_type)
|
||||||
|
value = fields.String()
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionSchema(BaseExtensionSchema):
|
||||||
|
basic_constraints = fields.Nested(BasicConstraintsSchema)
|
||||||
|
key_usage = fields.Nested(KeyUsageSchema)
|
||||||
|
extended_key_usage = fields.Nested(ExtendedKeyUsageSchema)
|
||||||
|
subject_key_identifier = fields.Nested(SubjectKeyIdentifierSchema)
|
||||||
|
sub_alt_names = fields.Nested(SubAltNamesSchema)
|
||||||
|
authority_identifier = fields.Nested(AuthorityIdentifierSchema)
|
||||||
|
authority_key_identifier = fields.Nested(AuthorityKeyIdentifierSchema)
|
||||||
|
certificate_info_access = fields.Nested(CertificateInfoAccessSchema)
|
||||||
|
custom = fields.List(fields.Nested(CustomOIDSchema))
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateInputSchema(LemurInputSchema):
|
||||||
|
name = fields.String()
|
||||||
|
owner = fields.Email(required=True)
|
||||||
|
description = fields.String()
|
||||||
|
common_name = fields.String(required=True, validate=validate_domain)
|
||||||
|
authority = fields.Nested(AssociatedAuthoritySchema, required=True)
|
||||||
|
|
||||||
|
validity_start = fields.DateTime()
|
||||||
|
validity_end = fields.DateTime()
|
||||||
|
validity_years = fields.Integer()
|
||||||
|
|
||||||
|
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||||
|
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
||||||
|
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
||||||
|
|
||||||
|
csr = fields.String(validate=validate_csr)
|
||||||
|
|
||||||
|
# certificate body fields
|
||||||
|
organizational_unit = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'))
|
||||||
|
organization = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'))
|
||||||
|
location = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_LOCATION'))
|
||||||
|
country = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_COUNTRY'))
|
||||||
|
state = fields.String(missing=lambda: current_app.config.get('LEMUR_DEFAULT_STATE'))
|
||||||
|
|
||||||
|
extensions = fields.Nested(ExtensionSchema)
|
||||||
|
|
||||||
|
@validates_schema
|
||||||
|
def validate_dates(self, data):
|
||||||
|
if not data.get('validity_start') and data.get('validity_end'):
|
||||||
|
raise ValidationError('If validity start is specified so must validity end.')
|
||||||
|
|
||||||
|
if not data.get('validity_end') and data.get('validity_start'):
|
||||||
|
raise ValidationError('If validity end is specified so must validity start.')
|
||||||
|
|
||||||
|
if data.get('validity_end') and data.get('validity_years'):
|
||||||
|
raise ValidationError('Cannot specify both validity end and validity years.')
|
||||||
|
|
||||||
|
if data.get('validity_start') and data.get('validity_end'):
|
||||||
|
if not data['validity_start'] < data['validity_end']:
|
||||||
|
raise ValidationError('Validity start must be before validity end.')
|
||||||
|
|
||||||
|
if data.get('validity_start').replace(tzinfo=None) < data['authority'].not_before:
|
||||||
|
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].not_before))
|
||||||
|
|
||||||
|
if data.get('validity_end').replace(tzinfo=None) > data['authority'].not_after:
|
||||||
|
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].not_after))
|
||||||
|
|
||||||
|
if data.get('validity_years'):
|
||||||
|
now = arrow.utcnow()
|
||||||
|
end = now.replace(years=+data['validity_years'])
|
||||||
|
|
||||||
|
if now.naive < data['authority'].not_before:
|
||||||
|
raise ValidationError('Validity start must not be before {0}'.format(data['authority'].not_before))
|
||||||
|
|
||||||
|
if end.naive > data['authority'].not_after:
|
||||||
|
raise ValidationError('Validity end must not be after {0}'.format(data['authority'].not_after))
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateOutputSchema(LemurOutputSchema):
|
||||||
|
id = fields.Integer()
|
||||||
|
active = fields.Boolean()
|
||||||
|
bits = fields.Integer()
|
||||||
|
body = fields.String()
|
||||||
|
chain = fields.String()
|
||||||
|
deleted = fields.Boolean(default=False)
|
||||||
|
description = fields.String()
|
||||||
|
issuer = fields.String()
|
||||||
|
name = fields.String()
|
||||||
|
not_after = fields.DateTime()
|
||||||
|
not_before = fields.DateTime()
|
||||||
|
owner = fields.Email()
|
||||||
|
san = fields.Boolean()
|
||||||
|
serial = fields.String()
|
||||||
|
signing_algorithm = fields.String()
|
||||||
|
status = fields.Boolean()
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateUploadInputSchema(LemurInputSchema):
|
||||||
|
name = fields.String()
|
||||||
|
owner = fields.Email(required=True)
|
||||||
|
description = fields.String()
|
||||||
|
active = fields.Boolean(missing=True)
|
||||||
|
|
||||||
|
private_key = fields.String(validate=validate_private_key)
|
||||||
|
public_cert = fields.String(required=True, validate=validate_public_certificate)
|
||||||
|
chain = fields.String(validate=validate_public_certificate)
|
||||||
|
|
||||||
|
destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True)
|
||||||
|
notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True)
|
||||||
|
replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True)
|
||||||
|
|
||||||
|
@validates_schema
|
||||||
|
def keys(self, data):
|
||||||
|
if data.get('destinations'):
|
||||||
|
if not data.get('private_key'):
|
||||||
|
raise ValidationError('Destinations require private key.')
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateExportInputSchema(LemurInputSchema):
|
||||||
|
export = fields.Nested(PluginSchema)
|
||||||
|
|
||||||
|
|
||||||
|
certificate_input_schema = CertificateInputSchema()
|
||||||
|
certificate_output_schema = CertificateOutputSchema()
|
||||||
|
certificates_output_schema = CertificateOutputSchema(many=True)
|
||||||
|
certificate_upload_input_schema = CertificateUploadInputSchema()
|
||||||
|
certificate_export_input_schema = CertificateExportInputSchema()
|
|
@ -88,7 +88,6 @@ def export(cert, export_plugin):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
plugin = plugins.get(export_plugin['slug'])
|
plugin = plugins.get(export_plugin['slug'])
|
||||||
|
|
||||||
return plugin.export(cert.body, cert.chain, cert.private_key, export_plugin['pluginOptions'])
|
return plugin.export(cert.body, cert.chain, cert.private_key, export_plugin['pluginOptions'])
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,8 +221,8 @@ def upload(**kwargs):
|
||||||
|
|
||||||
g.user.certificates.append(cert)
|
g.user.certificates.append(cert)
|
||||||
|
|
||||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
database.update_list(cert, 'destinations', Destination, kwargs['destinations'])
|
||||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
database.update_list(cert, 'notifications', Notification, kwargs['notifications'])
|
||||||
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
||||||
|
|
||||||
# create default notifications for this certificate if none are provided
|
# create default notifications for this certificate if none are provided
|
||||||
|
@ -256,9 +255,9 @@ def create(**kwargs):
|
||||||
|
|
||||||
# do this after the certificate has already been created because if it fails to upload to the third party
|
# do this after the certificate has already been created because if it fails to upload to the third party
|
||||||
# we do not want to lose the certificate information.
|
# we do not want to lose the certificate information.
|
||||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
database.update_list(cert, 'destinations', Destination, kwargs['destinations'])
|
||||||
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
database.update_list(cert, 'replaces', Certificate, kwargs['replacements'])
|
||||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
database.update_list(cert, 'notifications', Notification, kwargs['notifications'])
|
||||||
|
|
||||||
# create default notifications for this certificate if none are provided
|
# create default notifications for this certificate if none are provided
|
||||||
notifications = cert.notifications
|
notifications = cert.notifications
|
||||||
|
@ -364,9 +363,9 @@ def create_csr(csr_config):
|
||||||
# TODO When we figure out a better way to validate these options they should be parsed as str
|
# TODO When we figure out a better way to validate these options they should be parsed as str
|
||||||
builder = x509.CertificateSigningRequestBuilder()
|
builder = x509.CertificateSigningRequestBuilder()
|
||||||
builder = builder.subject_name(x509.Name([
|
builder = builder.subject_name(x509.Name([
|
||||||
x509.NameAttribute(x509.OID_COMMON_NAME, csr_config['commonName']),
|
x509.NameAttribute(x509.OID_COMMON_NAME, csr_config['common_name']),
|
||||||
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, csr_config['organization']),
|
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, csr_config['organization']),
|
||||||
x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, csr_config['organizationalUnit']),
|
x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, csr_config['organizational_unit']),
|
||||||
x509.NameAttribute(x509.OID_COUNTRY_NAME, csr_config['country']),
|
x509.NameAttribute(x509.OID_COUNTRY_NAME, csr_config['country']),
|
||||||
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, csr_config['state']),
|
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, csr_config['state']),
|
||||||
x509.NameAttribute(x509.OID_LOCALITY_NAME, csr_config['location']),
|
x509.NameAttribute(x509.OID_LOCALITY_NAME, csr_config['location']),
|
||||||
|
@ -378,11 +377,11 @@ def create_csr(csr_config):
|
||||||
|
|
||||||
if csr_config.get('extensions'):
|
if csr_config.get('extensions'):
|
||||||
for k, v in csr_config.get('extensions', {}).items():
|
for k, v in csr_config.get('extensions', {}).items():
|
||||||
if k == 'subAltNames':
|
if k == 'sub_alt_names':
|
||||||
# map types to their x509 objects
|
# map types to their x509 objects
|
||||||
general_names = []
|
general_names = []
|
||||||
for name in v['names']:
|
for name in v['names']:
|
||||||
if name['nameType'] == 'DNSName':
|
if name['name_type'] == 'DNSName':
|
||||||
general_names.append(x509.DNSName(name['value']))
|
general_names.append(x509.DNSName(name['value']))
|
||||||
|
|
||||||
builder = builder.add_extension(
|
builder = builder.add_extension(
|
||||||
|
|
|
@ -9,129 +9,24 @@ import base64
|
||||||
from builtins import str
|
from builtins import str
|
||||||
|
|
||||||
from flask import Blueprint, make_response, jsonify
|
from flask import Blueprint, make_response, jsonify
|
||||||
from flask.ext.restful import reqparse, Api, fields
|
from flask.ext.restful import reqparse, Api
|
||||||
|
|
||||||
from cryptography import x509
|
from lemur.common.schema import validate_schema
|
||||||
from cryptography.hazmat.backends import default_backend
|
from lemur.common.utils import paginated_parser
|
||||||
from cryptography.hazmat.primitives import serialization
|
|
||||||
|
|
||||||
from lemur.plugins import plugins
|
|
||||||
|
|
||||||
from lemur.auth.service import AuthenticatedResource
|
from lemur.auth.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import ViewKeyPermission
|
from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, UpdateCertificatePermission
|
||||||
from lemur.auth.permissions import AuthorityPermission
|
|
||||||
from lemur.auth.permissions import UpdateCertificatePermission
|
|
||||||
from lemur.auth.permissions import SensitiveDomainPermission
|
|
||||||
|
|
||||||
from lemur.certificates import service
|
from lemur.certificates import service
|
||||||
from lemur.authorities.models import Authority
|
from lemur.certificates.schemas import certificate_input_schema, certificate_output_schema, \
|
||||||
|
certificate_upload_input_schema, certificates_output_schema, certificate_export_input_schema
|
||||||
|
|
||||||
from lemur.roles import service as role_service
|
from lemur.roles import service as role_service
|
||||||
from lemur.domains import service as domain_service
|
|
||||||
from lemur.common.utils import marshal_items, paginated_parser
|
|
||||||
from lemur.notifications.views import notification_list
|
|
||||||
|
|
||||||
mod = Blueprint('certificates', __name__)
|
mod = Blueprint('certificates', __name__)
|
||||||
api = Api(mod)
|
api = Api(mod)
|
||||||
|
|
||||||
FIELDS = {
|
|
||||||
'name': fields.String,
|
|
||||||
'id': fields.Integer,
|
|
||||||
'bits': fields.Integer,
|
|
||||||
'deleted': fields.String,
|
|
||||||
'issuer': fields.String,
|
|
||||||
'serial': fields.String,
|
|
||||||
'owner': fields.String,
|
|
||||||
'chain': fields.String,
|
|
||||||
'san': fields.String,
|
|
||||||
'active': fields.Boolean,
|
|
||||||
'description': fields.String,
|
|
||||||
'notBefore': fields.DateTime(dt_format='iso8601', attribute='not_before'),
|
|
||||||
'notAfter': fields.DateTime(dt_format='iso8601', attribute='not_after'),
|
|
||||||
'cn': fields.String,
|
|
||||||
'signingAlgorithm': fields.String(attribute='signing_algorithm'),
|
|
||||||
'status': fields.String,
|
|
||||||
'body': fields.String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def valid_authority(authority_options):
|
|
||||||
"""
|
|
||||||
Defends against invalid authorities
|
|
||||||
|
|
||||||
:param authority_options:
|
|
||||||
:return: :raise ValueError:
|
|
||||||
"""
|
|
||||||
name = authority_options['name']
|
|
||||||
authority = Authority.query.filter(Authority.name == name).one()
|
|
||||||
|
|
||||||
if not authority:
|
|
||||||
raise ValueError("Unable to find authority specified")
|
|
||||||
|
|
||||||
if not authority.active:
|
|
||||||
raise ValueError("Selected authority [{0}] is not currently active".format(name))
|
|
||||||
|
|
||||||
return authority
|
|
||||||
|
|
||||||
|
|
||||||
def get_domains_from_options(options):
|
|
||||||
"""
|
|
||||||
Retrive all domains from certificate options
|
|
||||||
:param options:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
domains = [options['commonName']]
|
|
||||||
if options.get('extensions'):
|
|
||||||
if options['extensions'].get('subAltNames'):
|
|
||||||
for k, v in options['extensions']['subAltNames']['names']:
|
|
||||||
if k == 'DNSName':
|
|
||||||
domains.append(v)
|
|
||||||
return domains
|
|
||||||
|
|
||||||
|
|
||||||
def check_sensitive_domains(domains):
|
|
||||||
"""
|
|
||||||
Determines if any certificates in the given certificate
|
|
||||||
are marked as sensitive
|
|
||||||
:param domains:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
for domain in domains:
|
|
||||||
domain_objs = domain_service.get_by_name(domain)
|
|
||||||
for d in domain_objs:
|
|
||||||
if d.sensitive:
|
|
||||||
raise ValueError("The domain {0} has been marked as sensitive. Contact an administrator to "
|
|
||||||
"issue this certificate".format(d.name))
|
|
||||||
|
|
||||||
|
|
||||||
def pem_str(value, name):
|
|
||||||
"""
|
|
||||||
Used to validate that the given string is a PEM formatted string
|
|
||||||
|
|
||||||
:param value:
|
|
||||||
:param name:
|
|
||||||
:return: :raise ValueError:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
x509.load_pem_x509_certificate(bytes(value), default_backend())
|
|
||||||
except Exception:
|
|
||||||
raise ValueError("The parameter '{0}' needs to be a valid PEM string".format(name))
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def private_key_str(value, name):
|
|
||||||
"""
|
|
||||||
User to validate that a given string is a RSA private key
|
|
||||||
|
|
||||||
:param value:
|
|
||||||
:param name:
|
|
||||||
:return: :raise ValueError:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
serialization.load_pem_private_key(bytes(value), None, backend=default_backend())
|
|
||||||
except Exception:
|
|
||||||
raise ValueError("The parameter '{0}' needs to be a valid RSA private key".format(name))
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class CertificatesList(AuthenticatedResource):
|
class CertificatesList(AuthenticatedResource):
|
||||||
""" Defines the 'certificates' endpoint """
|
""" Defines the 'certificates' endpoint """
|
||||||
|
@ -140,7 +35,7 @@ class CertificatesList(AuthenticatedResource):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificatesList, self).__init__()
|
super(CertificatesList, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(None, certificates_output_schema)
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /certificates
|
.. http:get:: /certificates
|
||||||
|
@ -208,8 +103,8 @@ class CertificatesList(AuthenticatedResource):
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return service.render(args)
|
return service.render(args)
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(certificate_input_schema, certificate_output_schema)
|
||||||
def post(self):
|
def post(self, data=None):
|
||||||
"""
|
"""
|
||||||
.. http:post:: /certificates
|
.. http:post:: /certificates
|
||||||
|
|
||||||
|
@ -346,48 +241,24 @@ class CertificatesList(AuthenticatedResource):
|
||||||
:arg state: state for the CSR
|
:arg state: state for the CSR
|
||||||
:arg location: location for the CSR
|
:arg location: location for the CSR
|
||||||
:arg organization: organization for CSR
|
:arg organization: organization for CSR
|
||||||
:arg commonName: certiifcate common name
|
:arg commonName: certificate common name
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('extensions', type=dict, location='json')
|
role = role_service.get_by_name(data['authority'].owner)
|
||||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('replacements', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
|
|
||||||
self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate
|
|
||||||
self.reqparse.add_argument('validityYears', type=int, location='json') # TODO validate
|
|
||||||
self.reqparse.add_argument('authority', type=valid_authority, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('description', type=str, location='json')
|
|
||||||
self.reqparse.add_argument('country', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('state', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('location', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('organization', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('organizationalUnit', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('owner', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('commonName', type=str, location='json', required=True)
|
|
||||||
self.reqparse.add_argument('csr', type=str, location='json')
|
|
||||||
|
|
||||||
args = self.reqparse.parse_args()
|
|
||||||
|
|
||||||
authority = args['authority']
|
|
||||||
role = role_service.get_by_name(authority.owner)
|
|
||||||
|
|
||||||
# all the authority role members should be allowed
|
# all the authority role members should be allowed
|
||||||
roles = [x.name for x in authority.roles]
|
roles = [x.name for x in data['authority'].roles]
|
||||||
|
|
||||||
# allow "owner" roles by team DL
|
# allow "owner" roles by team DL
|
||||||
roles.append(role)
|
roles.append(role)
|
||||||
authority_permission = AuthorityPermission(authority.id, roles)
|
authority_permission = AuthorityPermission(data['authority'].id, roles)
|
||||||
|
|
||||||
if authority_permission.can():
|
if authority_permission.can():
|
||||||
# if we are not admins lets make sure we aren't issuing anything sensitive
|
return service.create(**data)
|
||||||
if not SensitiveDomainPermission().can():
|
|
||||||
check_sensitive_domains(get_domains_from_options(args))
|
|
||||||
return service.create(**args)
|
|
||||||
|
|
||||||
return dict(message="You are not authorized to use {0}".format(args['authority'].name)), 403
|
return dict(message="You are not authorized to use {0}".format(data['authority'].name)), 403
|
||||||
|
|
||||||
|
|
||||||
class CertificatesUpload(AuthenticatedResource):
|
class CertificatesUpload(AuthenticatedResource):
|
||||||
|
@ -397,8 +268,8 @@ class CertificatesUpload(AuthenticatedResource):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificatesUpload, self).__init__()
|
super(CertificatesUpload, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(certificate_upload_input_schema, certificate_output_schema)
|
||||||
def post(self):
|
def post(self, data=None):
|
||||||
"""
|
"""
|
||||||
.. http:post:: /certificates/upload
|
.. http:post:: /certificates/upload
|
||||||
|
|
||||||
|
@ -460,23 +331,12 @@ class CertificatesUpload(AuthenticatedResource):
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('description', type=str, location='json')
|
if data.get('destinations'):
|
||||||
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
if data.get('private_key'):
|
||||||
self.reqparse.add_argument('name', type=str, location='json')
|
return service.upload(**data)
|
||||||
self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json')
|
|
||||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('replacements', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('intermediateCert', type=pem_str, dest='intermediate_cert', location='json')
|
|
||||||
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', location='json')
|
|
||||||
|
|
||||||
args = self.reqparse.parse_args()
|
|
||||||
if args.get('destinations'):
|
|
||||||
if args.get('private_key'):
|
|
||||||
return service.upload(**args)
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Private key must be provided in order to upload certificate to AWS")
|
raise Exception("Private key must be provided in order to upload certificate to AWS")
|
||||||
return service.upload(**args)
|
return service.upload(**data)
|
||||||
|
|
||||||
|
|
||||||
class CertificatesStats(AuthenticatedResource):
|
class CertificatesStats(AuthenticatedResource):
|
||||||
|
@ -554,7 +414,7 @@ class Certificates(AuthenticatedResource):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(Certificates, self).__init__()
|
super(Certificates, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(None, certificate_output_schema)
|
||||||
def get(self, certificate_id):
|
def get(self, certificate_id):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /certificates/1
|
.. http:get:: /certificates/1
|
||||||
|
@ -603,8 +463,8 @@ class Certificates(AuthenticatedResource):
|
||||||
"""
|
"""
|
||||||
return service.get(certificate_id)
|
return service.get(certificate_id)
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(certificate_upload_input_schema, certificate_output_schema)
|
||||||
def put(self, certificate_id):
|
def put(self, certificate_id, data=None):
|
||||||
"""
|
"""
|
||||||
.. http:put:: /certificates/1
|
.. http:put:: /certificates/1
|
||||||
|
|
||||||
|
@ -657,14 +517,6 @@ class Certificates(AuthenticatedResource):
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('active', type=bool, location='json')
|
|
||||||
self.reqparse.add_argument('owner', type=str, location='json')
|
|
||||||
self.reqparse.add_argument('description', type=str, location='json')
|
|
||||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('notifications', type=notification_list, default=[], location='json')
|
|
||||||
self.reqparse.add_argument('replacements', type=list, default=[], location='json')
|
|
||||||
args = self.reqparse.parse_args()
|
|
||||||
|
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
role = role_service.get_by_name(cert.owner)
|
role = role_service.get_by_name(cert.owner)
|
||||||
|
|
||||||
|
@ -673,12 +525,12 @@ class Certificates(AuthenticatedResource):
|
||||||
if permission.can():
|
if permission.can():
|
||||||
return service.update(
|
return service.update(
|
||||||
certificate_id,
|
certificate_id,
|
||||||
args['owner'],
|
data['owner'],
|
||||||
args['description'],
|
data['description'],
|
||||||
args['active'],
|
data['active'],
|
||||||
args['destinations'],
|
data['destinations'],
|
||||||
args['notifications'],
|
data['notifications'],
|
||||||
args['replacements']
|
data['replacements']
|
||||||
)
|
)
|
||||||
|
|
||||||
return dict(message='You are not authorized to update this certificate'), 403
|
return dict(message='You are not authorized to update this certificate'), 403
|
||||||
|
@ -691,7 +543,7 @@ class NotificationCertificatesList(AuthenticatedResource):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(NotificationCertificatesList, self).__init__()
|
super(NotificationCertificatesList, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(None, certificates_output_schema)
|
||||||
def get(self, notification_id):
|
def get(self, notification_id):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /notifications/1/certificates
|
.. http:get:: /notifications/1/certificates
|
||||||
|
@ -767,7 +619,7 @@ class CertificatesReplacementsList(AuthenticatedResource):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificatesReplacementsList, self).__init__()
|
super(CertificatesReplacementsList, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@validate_schema(None, certificates_output_schema)
|
||||||
def get(self, certificate_id):
|
def get(self, certificate_id):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /certificates/1/replacements
|
.. http:get:: /certificates/1/replacements
|
||||||
|
@ -822,7 +674,8 @@ class CertificateExport(AuthenticatedResource):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(CertificateExport, self).__init__()
|
super(CertificateExport, self).__init__()
|
||||||
|
|
||||||
def post(self, certificate_id):
|
@validate_schema(None, certificate_export_input_schema)
|
||||||
|
def post(self, certificate_id, data=None):
|
||||||
"""
|
"""
|
||||||
.. http:post:: /certificates/1/export
|
.. http:post:: /certificates/1/export
|
||||||
|
|
||||||
|
@ -887,22 +740,21 @@ class CertificateExport(AuthenticatedResource):
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('export', type=dict, required=True, location='json')
|
|
||||||
args = self.reqparse.parse_args()
|
|
||||||
|
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
role = role_service.get_by_name(cert.owner)
|
role = role_service.get_by_name(cert.owner)
|
||||||
|
|
||||||
permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None))
|
permission = UpdateCertificatePermission(certificate_id, getattr(role, 'name', None))
|
||||||
|
|
||||||
plugin = plugins.get(args['export']['plugin']['slug'])
|
options = data['export']['plugin']['plugin_options']
|
||||||
|
plugin = data['export']['plugin']
|
||||||
|
|
||||||
if plugin.requires_key:
|
if plugin.requires_key:
|
||||||
if permission.can():
|
if permission.can():
|
||||||
extension, passphrase, data = plugin.export(cert.body, cert.chain, cert.private_key, args['export']['plugin']['pluginOptions'])
|
extension, passphrase, data = plugin.export(cert.body, cert.chain, cert.private_key, options)
|
||||||
else:
|
else:
|
||||||
return dict(message='You are not authorized to export this certificate'), 403
|
return dict(message='You are not authorized to export this certificate'), 403
|
||||||
else:
|
else:
|
||||||
extension, passphrase, data = plugin.export(cert.body, cert.chain, cert.private_key, args['export']['plugin']['pluginOptions'])
|
extension, passphrase, data = plugin.export(cert.body, cert.chain, cert.private_key, options)
|
||||||
|
|
||||||
# we take a hit in message size when b64 encoding
|
# we take a hit in message size when b64 encoding
|
||||||
return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data))
|
return dict(extension=extension, passphrase=passphrase, data=base64.b64encode(data))
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.common.schema
|
||||||
|
:platform: unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
|
||||||
|
"""
|
||||||
|
from functools import wraps
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from sqlalchemy.orm.collections import InstrumentedList
|
||||||
|
|
||||||
|
from marshmallow import Schema, post_dump, pre_load, pre_dump
|
||||||
|
from inflection import camelize, underscore
|
||||||
|
|
||||||
|
|
||||||
|
class LemurSchema(Schema):
|
||||||
|
"""
|
||||||
|
Base schema from which all grouper schema's inherit
|
||||||
|
"""
|
||||||
|
__envelope__ = True
|
||||||
|
|
||||||
|
def under(self, data, many=None):
|
||||||
|
items = []
|
||||||
|
if many:
|
||||||
|
for i in data:
|
||||||
|
items.append(
|
||||||
|
{underscore(key): value for key, value in i.items()}
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
return {
|
||||||
|
underscore(key): value
|
||||||
|
for key, value in data.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def camel(self, data, many=None):
|
||||||
|
items = []
|
||||||
|
if many:
|
||||||
|
for i in data:
|
||||||
|
items.append(
|
||||||
|
{camelize(key, uppercase_first_letter=False): value for key, value in i.items()}
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
return {
|
||||||
|
camelize(key, uppercase_first_letter=False): value
|
||||||
|
for key, value in data.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def wrap_with_envelope(self, data, many):
|
||||||
|
if many:
|
||||||
|
if 'total' in self.context.keys():
|
||||||
|
return dict(total=self.context['total'], items=data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class LemurInputSchema(LemurSchema):
|
||||||
|
@pre_load(pass_many=True)
|
||||||
|
def preprocess(self, data, many):
|
||||||
|
return self.under(data, many=many)
|
||||||
|
|
||||||
|
|
||||||
|
class LemurOutputSchema(LemurSchema):
|
||||||
|
@pre_load(pass_many=True)
|
||||||
|
def preprocess(self, data, many):
|
||||||
|
if many:
|
||||||
|
data = self.unwrap_envelope(data, many)
|
||||||
|
return self.under(data, many=many)
|
||||||
|
|
||||||
|
@pre_dump(pass_many=True)
|
||||||
|
def unwrap_envelope(self, data, many):
|
||||||
|
if many:
|
||||||
|
if data:
|
||||||
|
if isinstance(data, InstrumentedList) or isinstance(data, list):
|
||||||
|
self.context['total'] = len(data)
|
||||||
|
return data
|
||||||
|
else:
|
||||||
|
self.context['total'] = data['total']
|
||||||
|
else:
|
||||||
|
self.context['total'] = 0
|
||||||
|
data = {'items': []}
|
||||||
|
|
||||||
|
return data['items']
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
@post_dump(pass_many=True)
|
||||||
|
def post_process(self, data, many):
|
||||||
|
if data:
|
||||||
|
data = self.camel(data, many=many)
|
||||||
|
if self.__envelope__:
|
||||||
|
return self.wrap_with_envelope(data, many=many)
|
||||||
|
else:
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def format_errors(messages):
|
||||||
|
errors = {}
|
||||||
|
for k, v in messages.items():
|
||||||
|
key = camelize(k, uppercase_first_letter=False)
|
||||||
|
if isinstance(v, dict):
|
||||||
|
errors[key] = format_errors(v)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
errors[key] = v[0]
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_errors(messages):
|
||||||
|
errors = dict(message='Validation Error.')
|
||||||
|
if messages.get('_schema'):
|
||||||
|
errors['reasons'] = {'Schema': {'rule': messages['_schema']}}
|
||||||
|
else:
|
||||||
|
errors['reasons'] = format_errors(messages)
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def validate_schema(input_schema, output_schema):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if input_schema:
|
||||||
|
if request.get_json():
|
||||||
|
request_data = request.get_json()
|
||||||
|
else:
|
||||||
|
request_data = request.args
|
||||||
|
|
||||||
|
data, errors = input_schema.load(request_data)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return wrap_errors(errors), 400
|
||||||
|
|
||||||
|
kwargs['data'] = data
|
||||||
|
|
||||||
|
resp = f(*args, **kwargs)
|
||||||
|
|
||||||
|
if not resp:
|
||||||
|
return dict(message="No data found"), 404
|
||||||
|
|
||||||
|
if output_schema:
|
||||||
|
data = output_schema.dump(resp)
|
||||||
|
return data.data, 200
|
||||||
|
return resp, 200
|
||||||
|
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
|
@ -287,4 +287,7 @@ def sort_and_page(query, model, args):
|
||||||
if sort_by and sort_dir:
|
if sort_by and sort_dir:
|
||||||
query = sort(query, model, sort_by, sort_dir)
|
query = sort(query, model, sort_by, sort_dir)
|
||||||
|
|
||||||
return paginate(query, page, count)
|
total = query.count()
|
||||||
|
|
||||||
|
items = query.offset(count * page).limit(count).all()
|
||||||
|
return dict(items=items, total=total)
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
:license: Apache, see LICENSE for more details.
|
:license: Apache, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
from flask.ext.migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
|
|
||||||
from flask.ext.bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
bcrypt = Bcrypt()
|
bcrypt = Bcrypt()
|
||||||
|
|
||||||
from flask.ext.principal import Principal
|
from flask_principal import Principal
|
||||||
principal = Principal()
|
principal = Principal()
|
||||||
|
|
||||||
from flask_mail import Mail
|
from flask_mail import Mail
|
||||||
|
|
|
@ -391,7 +391,7 @@ class LemurServer(Command):
|
||||||
settings = make_settings()
|
settings = make_settings()
|
||||||
options = (
|
options = (
|
||||||
Option(*klass.cli, action=klass.action)
|
Option(*klass.cli, action=klass.action)
|
||||||
for setting, klass in settings.iteritems() if klass.cli
|
for setting, klass in settings.items() if klass.cli
|
||||||
)
|
)
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
|
@ -2,7 +2,7 @@ from moto import mock_iam, mock_sts
|
||||||
|
|
||||||
from lemur.certificates.models import Certificate
|
from lemur.certificates.models import Certificate
|
||||||
|
|
||||||
from lemur.tests.certs import EXTERNAL_VALID_STR, PRIVATE_KEY_STR
|
from lemur.tests.vectors import EXTERNAL_VALID_STR, PRIVATE_KEY_STR
|
||||||
|
|
||||||
|
|
||||||
def test_get_name_from_arn():
|
def test_get_name_from_arn():
|
||||||
|
|
|
@ -77,14 +77,14 @@ def process_options(options):
|
||||||
'email': current_app.config.get("VERISIGN_EMAIL")
|
'email': current_app.config.get("VERISIGN_EMAIL")
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.get('validityEnd'):
|
if options.get('validity_end'):
|
||||||
end_date, period = get_default_issuance(options)
|
end_date, period = get_default_issuance(options)
|
||||||
data['specificEndDate'] = str(end_date)
|
data['specificEndDate'] = str(end_date)
|
||||||
data['validityPeriod'] = period
|
data['validityPeriod'] = period
|
||||||
|
|
||||||
elif options.get('validityYears'):
|
elif options.get('validity_years'):
|
||||||
if options['validityYears'] in [1, 2]:
|
if options['validity_years'] in [1, 2]:
|
||||||
data['validityPeriod'] = str(options['validityYears']) + 'Y'
|
data['validityPeriod'] = str(options['validity_years']) + 'Y'
|
||||||
else:
|
else:
|
||||||
raise Exception("Verisign issued certificates cannot exceed two years in validity")
|
raise Exception("Verisign issued certificates cannot exceed two years in validity")
|
||||||
|
|
||||||
|
@ -98,10 +98,10 @@ def get_default_issuance(options):
|
||||||
:param options:
|
:param options:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
specific_end_date = arrow.get(options['validityEnd']).replace(days=-1).format("MM/DD/YYYY")
|
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['validityEnd'])
|
then = arrow.get(options['validity_end'])
|
||||||
|
|
||||||
if then < now.replace(years=+1):
|
if then < now.replace(years=+1):
|
||||||
validity_period = '1Y'
|
validity_period = '1Y'
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.schemas
|
||||||
|
:platform: unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
|
||||||
|
"""
|
||||||
|
from marshmallow import fields, post_load
|
||||||
|
from lemur.authorities.models import Authority
|
||||||
|
from lemur.destinations.models import Destination
|
||||||
|
from lemur.certificates.models import Certificate
|
||||||
|
from lemur.notifications.models import Notification
|
||||||
|
from lemur.common.schema import LemurInputSchema
|
||||||
|
|
||||||
|
from lemur.plugins import plugins
|
||||||
|
|
||||||
|
|
||||||
|
class AssociatedAuthoritySchema(LemurInputSchema):
|
||||||
|
id = fields.Int(required=True)
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def get_object(self, data, many=False):
|
||||||
|
return Authority.query.filter(Authority.id == data['id']).one()
|
||||||
|
|
||||||
|
|
||||||
|
class AssociatedDestinationSchema(LemurInputSchema):
|
||||||
|
id = fields.Int(required=True)
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def get_object(self, data, many=False):
|
||||||
|
if many:
|
||||||
|
ids = [d['id'] for d in data]
|
||||||
|
return Destination.query.filter(Destination.id.in_(ids)).all()
|
||||||
|
else:
|
||||||
|
return Destination.query.filter(Destination.id == data['id']).one()
|
||||||
|
|
||||||
|
|
||||||
|
class AssociatedNotificationSchema(LemurInputSchema):
|
||||||
|
id = fields.Int(required=True)
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def get_object(self, data, many=False):
|
||||||
|
if many:
|
||||||
|
ids = [d['id'] for d in data]
|
||||||
|
return Notification.query.filter(Notification.id.in_(ids)).all()
|
||||||
|
else:
|
||||||
|
return Notification.query.filter(Notification.id == data['id']).one()
|
||||||
|
|
||||||
|
|
||||||
|
class AssociatedCertificateSchema(LemurInputSchema):
|
||||||
|
id = fields.Int(required=True)
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def get_object(self, data, many=False):
|
||||||
|
if many:
|
||||||
|
ids = [d['id'] for d in data]
|
||||||
|
return Certificate.query.filter(Certificate.id.in_(ids)).all()
|
||||||
|
else:
|
||||||
|
return Certificate.query.filter(Certificate.id == data['id']).one()
|
||||||
|
|
||||||
|
|
||||||
|
class PluginSchema(LemurInputSchema):
|
||||||
|
plugin_options = fields.Dict()
|
||||||
|
slug = fields.String()
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def get_object(self, data, many=False):
|
||||||
|
if many:
|
||||||
|
return [plugins.get(plugin['slug']) for plugin in data]
|
||||||
|
else:
|
||||||
|
return plugins.get(data['slug'])
|
|
@ -89,6 +89,15 @@
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lemur.directive('lemurBadRequest', [function () {
|
||||||
|
return {
|
||||||
|
template: '<h4>{{ directiveData.message }}</h4>' +
|
||||||
|
'<div ng-repeat="(key, value) in directiveData.reasons">' +
|
||||||
|
'<strong>{{ key | titleCase }}</strong> - {{ value }}</strong>' +
|
||||||
|
'</div>'
|
||||||
|
};
|
||||||
|
}]);
|
||||||
|
|
||||||
lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
|
lemur.factory('LemurRestangular', function (Restangular, $location, $auth) {
|
||||||
return Restangular.withConfig(function (RestangularConfigurer) {
|
return Restangular.withConfig(function (RestangularConfigurer) {
|
||||||
RestangularConfigurer.setBaseUrl('http://localhost:8000/api/1');
|
RestangularConfigurer.setBaseUrl('http://localhost:8000/api/1');
|
||||||
|
@ -109,18 +118,6 @@
|
||||||
return extractedData;
|
return extractedData;
|
||||||
});
|
});
|
||||||
|
|
||||||
RestangularConfigurer.setErrorInterceptor(function(response) {
|
|
||||||
if (response.status === 400) {
|
|
||||||
if (response.data.message) {
|
|
||||||
var data = '';
|
|
||||||
_.each(response.data.message, function (value, key) {
|
|
||||||
data = data + ' ' + key + ' ' + value;
|
|
||||||
});
|
|
||||||
response.data.message = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) {
|
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params) {
|
||||||
// We want to make sure the user is auth'd before any requests
|
// We want to make sure the user is auth'd before any requests
|
||||||
if (!$auth.isAuthenticated()) {
|
if (!$auth.isAuthenticated()) {
|
||||||
|
|
|
@ -41,7 +41,9 @@ angular.module('lemur')
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: certificate.name,
|
title: certificate.name,
|
||||||
body: 'Failed to export ' + response.data.message,
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: response.data,
|
||||||
timeout: 100000
|
timeout: 100000
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -73,7 +75,9 @@ angular.module('lemur')
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: certificate.name,
|
title: certificate.name,
|
||||||
body: 'Failed to update ' + response.data.message,
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: response.data,
|
||||||
timeout: 100000
|
timeout: 100000
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -109,9 +113,12 @@ angular.module('lemur')
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: certificate.name,
|
title: certificate.name,
|
||||||
body: 'Was not created! ' + response.data.message,
|
body: 'lemur-bad-request',
|
||||||
|
bodyOutputType: 'directive',
|
||||||
|
directiveData: response.data,
|
||||||
timeout: 100000
|
timeout: 100000
|
||||||
});
|
});
|
||||||
|
|
||||||
WizardHandler.wizard().context.loading = false;
|
WizardHandler.wizard().context.loading = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,10 +71,10 @@
|
||||||
<ul ng-show="currentUser.username" class="nav navbar-nav navbar-right">
|
<ul ng-show="currentUser.username" class="nav navbar-nav navbar-right">
|
||||||
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
||||||
<a href class="dropdown-toggle profile-nav" dropdown-toggle>
|
<a href class="dropdown-toggle profile-nav" dropdown-toggle>
|
||||||
<span ng-show="currentUser.profileImage">
|
<span ng-if="currentUser.profileImage">
|
||||||
{{ currentUser.username }}<img src="{{ currentUser.profileImage }}" class="profile img-circle">
|
{{ currentUser.username }}<img src="{{ currentUser.profileImage }}" class="profile img-circle">
|
||||||
</span>
|
</span>
|
||||||
<span ng-show="!currentUser.profileImage">
|
<span ng-if="!currentUser.profileImage">
|
||||||
{{ currentUser.username }}<ng-letter-avatar height="35" width="35" data="currentUser.username" shape="round"></ng-letter-avatar>
|
{{ currentUser.username }}<ng-letter-avatar height="35" width="35" data="currentUser.username" shape="round"></ng-letter-avatar>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -38,6 +38,12 @@ LEMUR_SECURITY_TEAM_EMAIL = []
|
||||||
LOG_LEVEL = "DEBUG"
|
LOG_LEVEL = "DEBUG"
|
||||||
LOG_FILE = "lemur.log"
|
LOG_FILE = "lemur.log"
|
||||||
|
|
||||||
|
LEMUR_DEFAULT_COUNTRY = 'US'
|
||||||
|
LEMUR_DEFAULT_STATE = 'California'
|
||||||
|
LEMUR_DEFAULT_LOCATION = 'Los Gatos'
|
||||||
|
LEMUR_DEFAULT_ORGANIZATION = 'Example, Inc.'
|
||||||
|
LEMUR_DEFAULT_ORGANIZATIONAL_UNIT = 'Example'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from flask.ext.principal import identity_changed, Identity
|
||||||
|
|
||||||
from lemur import create_app
|
from lemur import create_app
|
||||||
from lemur.database import db as _db
|
from lemur.database import db as _db
|
||||||
from lemur.users import service as user_service
|
|
||||||
from lemur.roles import service as role_service
|
|
||||||
|
|
||||||
|
from .factories import AuthorityFactory, NotificationFactory, DestinationFactory, \
|
||||||
def pytest_addoption(parser):
|
CertificateFactory, UserFactory, RoleFactory
|
||||||
parser.addoption("--lemurconfig", help="override the default test config")
|
|
||||||
parser.addoption("--runslow", action="store_true", help="run slow tests")
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
|
@ -35,10 +35,7 @@ def app(request):
|
||||||
Creates a new Flask application for a test duration.
|
Creates a new Flask application for a test duration.
|
||||||
Uses application factory `create_app`.
|
Uses application factory `create_app`.
|
||||||
"""
|
"""
|
||||||
if request.config.getoption('--lemurconfig'):
|
_app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py')
|
||||||
_app = create_app(request.config.getoption('--lemurconfig'))
|
|
||||||
else:
|
|
||||||
_app = create_app(os.path.dirname(os.path.realpath(__file__)) + '/conf.py')
|
|
||||||
ctx = _app.app_context()
|
ctx = _app.app_context()
|
||||||
ctx.push()
|
ctx.push()
|
||||||
|
|
||||||
|
@ -54,9 +51,10 @@ def db(app, request):
|
||||||
|
|
||||||
_db.app = app
|
_db.app = app
|
||||||
|
|
||||||
user = user_service.create('user', 'test', 'user@example.com', True, None, [])
|
UserFactory()
|
||||||
admin_role = role_service.create('admin')
|
r = RoleFactory(name='admin')
|
||||||
admin = user_service.create('admin', 'admin', 'admin@example.com', True, None, [admin_role])
|
UserFactory(roles=[r])
|
||||||
|
|
||||||
_db.session.commit()
|
_db.session.commit()
|
||||||
yield _db
|
yield _db
|
||||||
|
|
||||||
|
@ -68,10 +66,52 @@ def session(db, request):
|
||||||
for test duration.
|
for test duration.
|
||||||
"""
|
"""
|
||||||
db.session.begin_nested()
|
db.session.begin_nested()
|
||||||
yield session
|
yield db.session
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
@pytest.yield_fixture(scope="function")
|
@pytest.yield_fixture(scope="function")
|
||||||
def client(app, session, client):
|
def client(app, session, client):
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def authority(session):
|
||||||
|
a = AuthorityFactory()
|
||||||
|
session.commit()
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def destination(session):
|
||||||
|
d = DestinationFactory()
|
||||||
|
session.commit()
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def notification(session):
|
||||||
|
n = NotificationFactory()
|
||||||
|
session.commit()
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def certificate(session):
|
||||||
|
c = CertificateFactory()
|
||||||
|
session.commit()
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.yield_fixture(scope="function")
|
||||||
|
def logged_in_user(app, user):
|
||||||
|
with app.test_request_context():
|
||||||
|
identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.yield_fixture(scope="function")
|
||||||
|
def logged_in_admin(app, admin_user):
|
||||||
|
with app.test_request_context():
|
||||||
|
identity_changed.send(current_app._get_current_object(), identity=Identity(admin_user.id))
|
||||||
|
yield
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from factory import Sequence, post_generation
|
||||||
|
from factory.alchemy import SQLAlchemyModelFactory
|
||||||
|
from factory.fuzzy import FuzzyChoice, FuzzyText, FuzzyDate
|
||||||
|
|
||||||
|
|
||||||
|
from lemur.database import db
|
||||||
|
from lemur.authorities.models import Authority
|
||||||
|
from lemur.certificates.models import Certificate
|
||||||
|
from lemur.destinations.models import Destination
|
||||||
|
from lemur.notifications.models import Notification
|
||||||
|
from lemur.users.models import User
|
||||||
|
from lemur.roles.models import Role
|
||||||
|
|
||||||
|
from .vectors import INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFactory(SQLAlchemyModelFactory):
|
||||||
|
"""Base factory."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory configuration."""
|
||||||
|
abstract = True
|
||||||
|
sqlalchemy_session = db.session
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorityFactory(BaseFactory):
|
||||||
|
"""Authority factory."""
|
||||||
|
name = Sequence(lambda n: 'authority{0}'.format(n))
|
||||||
|
owner = 'joe@example.com'
|
||||||
|
plugin_name = 'TheRing'
|
||||||
|
body = INTERNAL_VALID_SAN_STR
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory configuration."""
|
||||||
|
model = Authority
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def roles(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for role in extracted:
|
||||||
|
self.roles.append(role)
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateFactory(BaseFactory):
|
||||||
|
"""Certificate factory."""
|
||||||
|
name = Sequence(lambda n: 'certificate{0}'.format(n))
|
||||||
|
chain = INTERNAL_VALID_SAN_STR
|
||||||
|
body = INTERNAL_VALID_SAN_STR
|
||||||
|
private_key = PRIVATE_KEY_STR
|
||||||
|
owner = 'joe@example.com'
|
||||||
|
status = FuzzyChoice(['valid', 'revoked', 'unknown'])
|
||||||
|
deleted = False
|
||||||
|
bits = 2048
|
||||||
|
issuer = 'Example'
|
||||||
|
serial = FuzzyText(length=128)
|
||||||
|
cn = 'test.example.com'
|
||||||
|
description = FuzzyText(length=128)
|
||||||
|
active = True
|
||||||
|
san = 'true'
|
||||||
|
not_before = FuzzyDate(date(2016, 1, 1), date(2020, 1, 1))
|
||||||
|
not_after = FuzzyDate(date(2016, 1, 1), date(2020, 1, 1))
|
||||||
|
date_created = FuzzyDate(date(2016, 1, 1), date(2020, 1, 1))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory Configuration."""
|
||||||
|
model = Certificate
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def user(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
self.user_id = extracted.id
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def authority(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
self.authority_id = extracted.id
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def notifications(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for notification in extracted:
|
||||||
|
self.notifications.append(notification)
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def destinations(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for destination in extracted:
|
||||||
|
self.destintations.append(destination)
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def replaces(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for replace in extracted:
|
||||||
|
self.replaces.append(replace)
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def sources(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for source in extracted:
|
||||||
|
self.sources.append(source)
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def domains(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for domain in extracted:
|
||||||
|
self.domains.append(domain)
|
||||||
|
|
||||||
|
|
||||||
|
class DestinationFactory(BaseFactory):
|
||||||
|
"""Destination factory."""
|
||||||
|
plugin_name = Sequence(lambda n: 'destination{0}'.format(n))
|
||||||
|
label = Sequence(lambda n: 'destination{0}'.format(n))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory Configuration."""
|
||||||
|
model = Destination
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationFactory(BaseFactory):
|
||||||
|
"""Notification factory."""
|
||||||
|
plugin_name = Sequence(lambda n: 'notification{0}'.format(n))
|
||||||
|
label = Sequence(lambda n: 'notification{0}'.format(n))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory Configuration."""
|
||||||
|
model = Notification
|
||||||
|
|
||||||
|
|
||||||
|
class RoleFactory(BaseFactory):
|
||||||
|
"""Role factory."""
|
||||||
|
name = Sequence(lambda n: 'role{0}'.format(n))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory Configuration."""
|
||||||
|
model = Role
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def users(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for user in extracted:
|
||||||
|
self.users.append(user)
|
||||||
|
|
||||||
|
|
||||||
|
class UserFactory(BaseFactory):
|
||||||
|
"""User Factory."""
|
||||||
|
username = Sequence(lambda n: 'user{0}'.format(n))
|
||||||
|
email = Sequence(lambda n: 'user{0}@example.com'.format(n))
|
||||||
|
active = True
|
||||||
|
password = FuzzyText(length=24)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Factory Configuration."""
|
||||||
|
model = User
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def roles(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for role in extracted:
|
||||||
|
self.roles.append(role)
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def certificates(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for cert in extracted:
|
||||||
|
self.certificates.append(cert)
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def authorities(self, create, extracted, **kwargs):
|
||||||
|
if not create:
|
||||||
|
return
|
||||||
|
|
||||||
|
if extracted:
|
||||||
|
for authority in extracted:
|
||||||
|
self.authorities.append(authority)
|
|
@ -1,35 +1,288 @@
|
||||||
from __future__ import unicode_literals # at top of module
|
from __future__ import unicode_literals # at top of module
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import json
|
||||||
from lemur.certificates.views import * # noqa
|
from lemur.certificates.views import * # noqa
|
||||||
|
|
||||||
|
from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN
|
||||||
def test_pem_str():
|
|
||||||
from lemur.tests.certs import INTERNAL_VALID_LONG_STR
|
|
||||||
assert pem_str(INTERNAL_VALID_LONG_STR, 'test') == INTERNAL_VALID_LONG_STR
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
pem_str('sdfsdfds', 'test')
|
|
||||||
|
|
||||||
|
|
||||||
def test_private_key_str():
|
def test_authority_identifier_schema():
|
||||||
from lemur.tests.certs import PRIVATE_KEY_STR
|
from lemur.certificates.schemas import AuthorityIdentifierSchema
|
||||||
assert private_key_str(PRIVATE_KEY_STR, 'test') == PRIVATE_KEY_STR
|
input_data = {'useAuthorityCert': True}
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
data, errors = AuthorityIdentifierSchema().load(input_data)
|
||||||
private_key_str('dfsdfsdf', 'test')
|
|
||||||
|
assert data == {'use_authority_cert': True}
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
data, errors = AuthorityIdentifierSchema().dumps(data)
|
||||||
|
assert not errors
|
||||||
|
assert data == json.dumps(input_data)
|
||||||
|
|
||||||
|
|
||||||
def test_create_basic_csr():
|
def test_authority_key_identifier_schema():
|
||||||
|
from lemur.certificates.schemas import AuthorityKeyIdentifierSchema
|
||||||
|
input_data = {'useKeyIdentifier': True}
|
||||||
|
|
||||||
|
data, errors = AuthorityKeyIdentifierSchema().load(input_data)
|
||||||
|
|
||||||
|
assert data == {'use_key_identifier': True}
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
data, errors = AuthorityKeyIdentifierSchema().dumps(data)
|
||||||
|
assert data == json.dumps(input_data)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_certificate_info_access_schema():
|
||||||
|
from lemur.certificates.schemas import CertificateInfoAccessSchema
|
||||||
|
input_data = {'includeAIA': True}
|
||||||
|
|
||||||
|
data, errors = CertificateInfoAccessSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
assert data == {'include_aia': True}
|
||||||
|
|
||||||
|
data, errors = CertificateInfoAccessSchema().dump(data)
|
||||||
|
assert not errors
|
||||||
|
assert data == input_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_subject_key_identifier_schema():
|
||||||
|
from lemur.certificates.schemas import SubjectKeyIdentifierSchema
|
||||||
|
|
||||||
|
input_data = {'includeSKI': True}
|
||||||
|
|
||||||
|
data, errors = SubjectKeyIdentifierSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
assert data == {'include_ski': True}
|
||||||
|
data, errors = SubjectKeyIdentifierSchema().dump(data)
|
||||||
|
assert not errors
|
||||||
|
assert data == input_data
|
||||||
|
|
||||||
|
|
||||||
|
def test_extension_schema():
|
||||||
|
from lemur.certificates.schemas import ExtensionSchema
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
'keyUsage': {
|
||||||
|
'useKeyEncipherment': True,
|
||||||
|
'useDigitalSignature': True
|
||||||
|
},
|
||||||
|
'extendedKeyUsage': {
|
||||||
|
'useServerAuthentication': True
|
||||||
|
},
|
||||||
|
'subjectKeyIdentifier': {
|
||||||
|
'includeSKI': True
|
||||||
|
},
|
||||||
|
'subAltNames': {
|
||||||
|
'names': [
|
||||||
|
{'nameType': 'DNSName', 'value': 'test.example.com'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = ExtensionSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_certificate_input_schema(client, authority):
|
||||||
|
from lemur.certificates.schemas import CertificateInputSchema
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
'commonName': 'test.example.com',
|
||||||
|
'owner': 'jim@example.com',
|
||||||
|
'authority': {'id': authority.id},
|
||||||
|
'description': 'testtestest',
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
|
||||||
|
assert not errors
|
||||||
|
assert data['authority'].id == authority.id
|
||||||
|
|
||||||
|
# make sure the defaults got set
|
||||||
|
assert data['common_name'] == 'test.example.com'
|
||||||
|
assert data['country'] == 'US'
|
||||||
|
assert data['location'] == 'Los Gatos'
|
||||||
|
|
||||||
|
assert len(data.keys()) == 12
|
||||||
|
|
||||||
|
|
||||||
|
def test_certificate_input_with_extensions(client, authority):
|
||||||
|
from lemur.certificates.schemas import CertificateInputSchema
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
'commonName': 'test.example.com',
|
||||||
|
'owner': 'jim@example.com',
|
||||||
|
'authority': {'id': authority.id},
|
||||||
|
'description': 'testtestest',
|
||||||
|
'extensions': {
|
||||||
|
'keyUsage': {
|
||||||
|
'useKeyEncipherment': True,
|
||||||
|
'useDigitalSignature': True
|
||||||
|
},
|
||||||
|
'extendedKeyUsage': {
|
||||||
|
'useServerAuthentication': True
|
||||||
|
},
|
||||||
|
'subjectKeyIdentifier': {
|
||||||
|
'includeSKI': True
|
||||||
|
},
|
||||||
|
'subAltNames': {
|
||||||
|
'names': [
|
||||||
|
{'nameType': 'DNSName', 'value': 'test.example.com'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_certificate_out_of_range_date(client, authority):
|
||||||
|
from lemur.certificates.schemas import CertificateInputSchema
|
||||||
|
input_data = {
|
||||||
|
'commonName': 'test.example.com',
|
||||||
|
'owner': 'jim@example.com',
|
||||||
|
'authority': {'id': authority.id},
|
||||||
|
'description': 'testtestest',
|
||||||
|
'validityYears': 100
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
assert errors
|
||||||
|
|
||||||
|
input_data['validityStart'] = '2017-04-30T00:12:34.513631'
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
assert errors
|
||||||
|
|
||||||
|
input_data['validityEnd'] = '2018-04-30T00:12:34.513631'
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
assert errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_certificate_valid_years(client, authority):
|
||||||
|
from lemur.certificates.schemas import CertificateInputSchema
|
||||||
|
input_data = {
|
||||||
|
'commonName': 'test.example.com',
|
||||||
|
'owner': 'jim@example.com',
|
||||||
|
'authority': {'id': authority.id},
|
||||||
|
'description': 'testtestest',
|
||||||
|
'validityYears': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_certificate_valid_dates(client, authority):
|
||||||
|
from lemur.certificates.schemas import CertificateInputSchema
|
||||||
|
input_data = {
|
||||||
|
'commonName': 'test.example.com',
|
||||||
|
'owner': 'jim@example.com',
|
||||||
|
'authority': {'id': authority.id},
|
||||||
|
'description': 'testtestest',
|
||||||
|
'validityStart': '2017-04-30T00:12:34.513631',
|
||||||
|
'validityEnd': '2018-04-30T00:12:34.513631'
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = CertificateInputSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_sub_alt_name_schema():
|
||||||
|
from lemur.certificates.schemas import SubAltNameSchema, SubAltNamesSchema
|
||||||
|
input_data = {'nameType': 'DNSName', 'value': 'test.example.com'}
|
||||||
|
|
||||||
|
data, errors = SubAltNameSchema().load(input_data)
|
||||||
|
assert not errors
|
||||||
|
assert data == {'name_type': 'DNSName', 'value': 'test.example.com'}
|
||||||
|
|
||||||
|
data, errors = SubAltNameSchema().dumps(data)
|
||||||
|
assert data == json.dumps(input_data)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
input_datas = {'names': [input_data]}
|
||||||
|
|
||||||
|
data, errors = SubAltNamesSchema().load(input_datas)
|
||||||
|
assert not errors
|
||||||
|
assert data == {'names': [{'name_type': 'DNSName', 'value': 'test.example.com'}]}
|
||||||
|
|
||||||
|
data, errors = SubAltNamesSchema().dumps(data)
|
||||||
|
assert data == json.dumps(input_datas)
|
||||||
|
assert not errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_usage_schema():
|
||||||
|
from lemur.certificates.schemas import KeyUsageSchema
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
'useCRLSign': True,
|
||||||
|
'useDataEncipherment': True,
|
||||||
|
'useDecipherOnly': True,
|
||||||
|
'useEncipherOnly': True,
|
||||||
|
'useKeyEncipherment': True,
|
||||||
|
'useDigitalSignature': True,
|
||||||
|
'useNonRepudiation': True
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = KeyUsageSchema().load(input_data)
|
||||||
|
|
||||||
|
assert not errors
|
||||||
|
assert data == {
|
||||||
|
'use_crl_sign': True,
|
||||||
|
'use_data_encipherment': True,
|
||||||
|
'use_decipher_only': True,
|
||||||
|
'use_encipher_only': True,
|
||||||
|
'use_key_encipherment': True,
|
||||||
|
'use_digital_signature': True,
|
||||||
|
'use_non_repudiation': True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_extended_key_usage_schema():
|
||||||
|
from lemur.certificates.schemas import ExtendedKeyUsageSchema
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
'useServerAuthentication': True,
|
||||||
|
'useClientAuthentication': True,
|
||||||
|
'useEapOverLAN': True,
|
||||||
|
'useEapOverPPP': True,
|
||||||
|
'useOCSPSigning': True,
|
||||||
|
'useSmartCardAuthentication': True,
|
||||||
|
'useTimestamping': True
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errors = ExtendedKeyUsageSchema().load(input_data)
|
||||||
|
|
||||||
|
assert not errors
|
||||||
|
assert data == {
|
||||||
|
'use_server_authentication': True,
|
||||||
|
'use_client_authentication': True,
|
||||||
|
'use_eap_over_lan': True,
|
||||||
|
'use_eap_over_ppp': True,
|
||||||
|
'use_ocsp_signing': True,
|
||||||
|
'use_smart_card_authentication': True,
|
||||||
|
'use_timestamping': True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_basic_csr(client):
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
from lemur.certificates.service import create_csr
|
from lemur.certificates.service import create_csr
|
||||||
csr_config = dict(
|
csr_config = dict(
|
||||||
commonName='example.com',
|
common_name='example.com',
|
||||||
organization='Example, Inc.',
|
organization='Example, Inc.',
|
||||||
organizationalUnit='Operations',
|
organizational_unit='Operations',
|
||||||
country='US',
|
country='US',
|
||||||
state='CA',
|
state='CA',
|
||||||
location='A place',
|
location='A place',
|
||||||
extensions=dict(names=dict(subAltNames=['test.example.com', 'test2.example.com']))
|
extensions=dict(names=dict(sub_alt_names=['test.example.com', 'test2.example.com']))
|
||||||
)
|
)
|
||||||
csr, pem = create_csr(csr_config)
|
csr, pem = create_csr(csr_config)
|
||||||
|
|
||||||
|
@ -39,61 +292,61 @@ def test_create_basic_csr():
|
||||||
assert name.value in csr_config.values()
|
assert name.value in csr_config.values()
|
||||||
|
|
||||||
|
|
||||||
def test_cert_get_cn():
|
def test_cert_get_cn(client):
|
||||||
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
|
from .vectors import INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import get_cn
|
from lemur.certificates.models import get_cn
|
||||||
|
|
||||||
assert get_cn(INTERNAL_VALID_LONG_CERT) == 'long.lived.com'
|
assert get_cn(INTERNAL_VALID_LONG_CERT) == 'long.lived.com'
|
||||||
|
|
||||||
|
|
||||||
def test_cert_get_subAltDomains():
|
def test_cert_get_sub_alt_domains(client):
|
||||||
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
|
from .vectors import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import get_domains
|
from lemur.certificates.models import get_domains
|
||||||
|
|
||||||
assert get_domains(INTERNAL_VALID_LONG_CERT) == []
|
assert get_domains(INTERNAL_VALID_LONG_CERT) == []
|
||||||
assert get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com']
|
assert get_domains(INTERNAL_VALID_SAN_CERT) == ['example2.long.com', 'example3.long.com']
|
||||||
|
|
||||||
|
|
||||||
def test_cert_is_san():
|
def test_cert_is_san(client):
|
||||||
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
|
from .vectors import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import is_san
|
from lemur.certificates.models import is_san
|
||||||
|
|
||||||
assert is_san(INTERNAL_VALID_LONG_CERT) == None # noqa
|
assert not is_san(INTERNAL_VALID_LONG_CERT)
|
||||||
assert is_san(INTERNAL_VALID_SAN_CERT) == True # noqa
|
assert is_san(INTERNAL_VALID_SAN_CERT)
|
||||||
|
|
||||||
|
|
||||||
def test_cert_is_wildcard():
|
def test_cert_is_wildcard(client):
|
||||||
from lemur.tests.certs import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT
|
from .vectors import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import is_wildcard
|
from lemur.certificates.models import is_wildcard
|
||||||
assert is_wildcard(INTERNAL_VALID_WILDCARD_CERT) == True # noqa
|
assert is_wildcard(INTERNAL_VALID_WILDCARD_CERT)
|
||||||
assert is_wildcard(INTERNAL_VALID_LONG_CERT) == None # noqa
|
assert not is_wildcard(INTERNAL_VALID_LONG_CERT)
|
||||||
|
|
||||||
|
|
||||||
def test_cert_get_bitstrength():
|
def test_cert_get_bitstrength(client):
|
||||||
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
|
from .vectors import INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import get_bitstrength
|
from lemur.certificates.models import get_bitstrength
|
||||||
assert get_bitstrength(INTERNAL_VALID_LONG_CERT) == 2048
|
assert get_bitstrength(INTERNAL_VALID_LONG_CERT) == 2048
|
||||||
|
|
||||||
|
|
||||||
def test_cert_get_issuer():
|
def test_cert_get_issuer(client):
|
||||||
from lemur.tests.certs import INTERNAL_VALID_LONG_CERT
|
from .vectors import INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import get_issuer
|
from lemur.certificates.models import get_issuer
|
||||||
assert get_issuer(INTERNAL_VALID_LONG_CERT) == 'Example'
|
assert get_issuer(INTERNAL_VALID_LONG_CERT) == 'Example'
|
||||||
|
|
||||||
|
|
||||||
def test_get_name_from_arn():
|
def test_get_name_from_arn(client):
|
||||||
from lemur.certificates.models import get_name_from_arn
|
from lemur.certificates.models import get_name_from_arn
|
||||||
arn = 'arn:aws:iam::11111111:server-certificate/mycertificate'
|
arn = 'arn:aws:iam::11111111:server-certificate/mycertificate'
|
||||||
assert get_name_from_arn(arn) == 'mycertificate'
|
assert get_name_from_arn(arn) == 'mycertificate'
|
||||||
|
|
||||||
|
|
||||||
def test_get_account_number():
|
def test_get_account_number(client):
|
||||||
from lemur.certificates.models import get_account_number
|
from lemur.certificates.models import get_account_number
|
||||||
arn = 'arn:aws:iam::11111111:server-certificate/mycertificate'
|
arn = 'arn:aws:iam::11111111:server-certificate/mycertificate'
|
||||||
assert get_account_number(arn) == '11111111'
|
assert get_account_number(arn) == '11111111'
|
||||||
|
|
||||||
|
|
||||||
def test_create_name():
|
def test_create_name(client):
|
||||||
from lemur.certificates.models import create_name
|
from lemur.certificates.models import create_name
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
assert create_name(
|
assert create_name(
|
||||||
|
@ -112,203 +365,181 @@ def test_create_name():
|
||||||
) == 'SAN-example.com-ExampleInc-20150507-20150512'
|
) == 'SAN-example.com-ExampleInc-20150507-20150512'
|
||||||
|
|
||||||
|
|
||||||
def test_certificate_get(client):
|
@pytest.mark.parametrize("token,status", [
|
||||||
assert client.get(api.url_for(Certificates, certificate_id=1)).status_code == 401
|
(VALID_USER_HEADER_TOKEN, 404),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 404),
|
||||||
|
('', 401)
|
||||||
def test_certificate_post(client):
|
])
|
||||||
assert client.post(api.url_for(Certificates, certificate_id=1), data={}).status_code == 405
|
def test_certificate_get(client, token, status):
|
||||||
|
assert client.get(api.url_for(Certificates, certificate_id=1), headers=token).status_code == status
|
||||||
|
|
||||||
def test_certificate_put(client):
|
|
||||||
assert client.put(api.url_for(Certificates, certificate_id=1), data={}).status_code == 401
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
def test_certificate_delete(client):
|
('', 405)
|
||||||
assert client.delete(api.url_for(Certificates, certificate_id=1)).status_code == 405
|
])
|
||||||
|
def test_certificate_post(client, token, status):
|
||||||
|
assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=token).status_code == status
|
||||||
def test_certificate_patch(client):
|
|
||||||
assert client.patch(api.url_for(Certificates, certificate_id=1), data={}).status_code == 405
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 400),
|
||||||
def test_certificates_get(client):
|
(VALID_ADMIN_HEADER_TOKEN, 400),
|
||||||
assert client.get(api.url_for(CertificatesList)).status_code == 401
|
('', 401)
|
||||||
|
])
|
||||||
|
def test_certificate_put(client, token, status):
|
||||||
def test_certificates_post(client):
|
assert client.put(api.url_for(Certificates, certificate_id=1), data={}, headers=token).status_code == status
|
||||||
assert client.post(api.url_for(CertificatesList), data={}).status_code == 401
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
def test_certificates_put(client):
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
assert client.put(api.url_for(CertificatesList), data={}).status_code == 405
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
|
])
|
||||||
def test_certificates_delete(client):
|
def test_certificate_delete(client, token, status):
|
||||||
assert client.delete(api.url_for(CertificatesList)).status_code == 405
|
assert client.delete(api.url_for(Certificates, certificate_id=1), headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
def test_certificates_patch(client):
|
@pytest.mark.parametrize("token,status", [
|
||||||
assert client.patch(api.url_for(CertificatesList), data={}).status_code == 405
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
def test_certificate_credentials_get(client):
|
])
|
||||||
assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1)).status_code == 401
|
def test_certificate_patch(client, token, status):
|
||||||
|
assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=token).status_code == status
|
||||||
|
|
||||||
def test_certificate_credentials_post(client):
|
|
||||||
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 200),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 200),
|
||||||
def test_certificate_credentials_put(client):
|
('', 401)
|
||||||
assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405
|
])
|
||||||
|
def test_certificates_get(client, token, status):
|
||||||
|
assert client.get(api.url_for(CertificatesList), headers=token).status_code == status
|
||||||
def test_certificate_credentials_delete(client):
|
|
||||||
assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1)).status_code == 405
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 400),
|
||||||
def test_certificate_credentials_patch(client):
|
(VALID_ADMIN_HEADER_TOKEN, 400),
|
||||||
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405
|
('', 401)
|
||||||
|
])
|
||||||
|
def test_certificates_post(client, token, status):
|
||||||
def test_certificates_upload_get(client):
|
assert client.post(api.url_for(CertificatesList), data={}, headers=token).status_code == status
|
||||||
assert client.get(api.url_for(CertificatesUpload)).status_code == 405
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
def test_certificates_upload_post(client):
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
assert client.post(api.url_for(CertificatesUpload), data={}).status_code == 401
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
|
])
|
||||||
def test_certificates_upload_put(client):
|
def test_certificates_put(client, token, status):
|
||||||
assert client.put(api.url_for(CertificatesUpload), data={}).status_code == 405
|
assert client.put(api.url_for(CertificatesList), data={}, headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
def test_certificates_upload_delete(client):
|
@pytest.mark.parametrize("token,status", [
|
||||||
assert client.delete(api.url_for(CertificatesUpload)).status_code == 405
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
def test_certificates_upload_patch(client):
|
])
|
||||||
assert client.patch(api.url_for(CertificatesUpload), data={}).status_code == 405
|
def test_certificates_delete(client, token, status):
|
||||||
|
assert client.delete(api.url_for(CertificatesList), headers=token).status_code == status
|
||||||
|
|
||||||
VALID_USER_HEADER_TOKEN = {
|
|
||||||
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
def test_auth_certificate_get(client):
|
('', 405)
|
||||||
assert client.get(api.url_for(Certificates, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
])
|
||||||
|
def test_certificates_patch(client, token, status):
|
||||||
|
assert client.patch(api.url_for(CertificatesList), data={}, headers=token).status_code == status
|
||||||
def test_auth_certificate_post_(client):
|
|
||||||
assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 404),
|
||||||
def test_auth_certificate_put(client):
|
(VALID_ADMIN_HEADER_TOKEN, 404),
|
||||||
assert client.put(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
('', 401)
|
||||||
|
])
|
||||||
|
def test_certificate_credentials_get(client, token, status):
|
||||||
def test_auth_certificate_delete(client):
|
assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1), headers=token).status_code == status
|
||||||
assert client.delete(api.url_for(Certificates, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
def test_auth_certificate_patch(client):
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
|
])
|
||||||
def test_auth_certificates_get(client):
|
def test_certificate_credentials_post(client, token, status):
|
||||||
assert client.get(api.url_for(CertificatesList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
def test_auth_certificates_post(client):
|
@pytest.mark.parametrize("token,status", [
|
||||||
assert client.post(api.url_for(CertificatesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
def test_auth_certificate_credentials_get(client):
|
])
|
||||||
assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 404
|
def test_certificate_credentials_put(client, token, status):
|
||||||
|
assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=token).status_code == status
|
||||||
|
|
||||||
def test_auth_certificate_credentials_post(client):
|
|
||||||
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
def test_auth_certificate_credentials_put(client):
|
('', 405)
|
||||||
assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
])
|
||||||
|
def test_certificate_credentials_delete(client, token, status):
|
||||||
|
assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1), headers=token).status_code == status
|
||||||
def test_auth_certificate_credentials_delete(client):
|
|
||||||
assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
def test_auth_certificate_credentials_patch(client):
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
('', 405)
|
||||||
|
])
|
||||||
|
def test_certificate_credentials_patch(client, token, status):
|
||||||
def test_auth_certificates_upload_get(client):
|
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=token).status_code == status
|
||||||
assert client.get(api.url_for(CertificatesUpload), headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
def test_auth_certificates_upload_post(client):
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
assert client.post(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
|
])
|
||||||
def test_auth_certificates_upload_put(client):
|
def test_certificates_upload_get(client, token, status):
|
||||||
assert client.put(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
assert client.get(api.url_for(CertificatesUpload), headers=token).status_code == status
|
||||||
|
|
||||||
|
|
||||||
def test_auth_certificates_upload_delete(client):
|
@pytest.mark.parametrize("token,status", [
|
||||||
assert client.delete(api.url_for(CertificatesUpload), headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
(VALID_USER_HEADER_TOKEN, 400),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 400),
|
||||||
|
('', 401)
|
||||||
def test_auth_certificates_upload_patch(client):
|
])
|
||||||
assert client.patch(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
def test_certificates_upload_post(client, token, status):
|
||||||
|
assert client.post(api.url_for(CertificatesUpload), data={}, headers=token).status_code == status
|
||||||
|
|
||||||
VALID_ADMIN_HEADER_TOKEN = {
|
|
||||||
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
def test_admin_certificate_get(client):
|
('', 405)
|
||||||
assert client.get(api.url_for(Certificates, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
])
|
||||||
|
def test_certificates_upload_put(client, token, status):
|
||||||
|
assert client.put(api.url_for(CertificatesUpload), data={}, headers=token).status_code == status
|
||||||
def test_admin_certificate_post(client):
|
|
||||||
assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
def test_admin_certificate_put(client):
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
assert client.put(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
('', 405)
|
||||||
|
])
|
||||||
|
def test_certificates_upload_delete(client, token, status):
|
||||||
def test_admin_certificate_delete(client):
|
assert client.delete(api.url_for(CertificatesUpload), headers=token).status_code == status
|
||||||
assert client.delete(api.url_for(Certificates, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("token,status", [
|
||||||
def test_admin_certificate_patch(client):
|
(VALID_USER_HEADER_TOKEN, 405),
|
||||||
assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
(VALID_ADMIN_HEADER_TOKEN, 405),
|
||||||
|
('', 405)
|
||||||
|
])
|
||||||
def test_admin_certificates_get(client):
|
def test_certificates_upload_patch(client, token, status):
|
||||||
resp = client.get(api.url_for(CertificatesList), headers=VALID_ADMIN_HEADER_TOKEN)
|
assert client.patch(api.url_for(CertificatesUpload), data={}, headers=token).status_code == status
|
||||||
assert resp.status_code == 200
|
|
||||||
assert resp.json['total'] == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_certificate_credentials_get(client):
|
|
||||||
assert client.get(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 404
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_certificate_credentials_post(client):
|
|
||||||
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_certificate_credentials_put(client):
|
|
||||||
assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_certificate_credentials_delete(client):
|
|
||||||
assert client.delete(api.url_for(CertificatePrivateKey, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_certificate_credentials_patch(client):
|
|
||||||
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
|
VALID_USER_HEADER_TOKEN = {
|
||||||
|
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
|
||||||
|
|
||||||
|
|
||||||
|
VALID_ADMIN_HEADER_TOKEN = {
|
||||||
|
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
|
||||||
|
|
||||||
|
|
||||||
INTERNAL_VALID_LONG_STR = b"""
|
INTERNAL_VALID_LONG_STR = b"""
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx
|
MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx
|
6
setup.py
6
setup.py
|
@ -50,12 +50,15 @@ install_requires = [
|
||||||
'boto==2.38.0', # we might make this optional
|
'boto==2.38.0', # we might make this optional
|
||||||
'six==1.10.0',
|
'six==1.10.0',
|
||||||
'gunicorn==19.4.1',
|
'gunicorn==19.4.1',
|
||||||
|
'marshmallow-sqlalchemy==0.8.0',
|
||||||
|
'marshmallow==2.4.0',
|
||||||
'pycrypto==2.6.1',
|
'pycrypto==2.6.1',
|
||||||
'cryptography==1.1.2',
|
'cryptography==1.3.1',
|
||||||
'pyopenssl==0.15.1',
|
'pyopenssl==0.15.1',
|
||||||
'pyjwt==1.4.0',
|
'pyjwt==1.4.0',
|
||||||
'xmltodict==0.9.2',
|
'xmltodict==0.9.2',
|
||||||
'lockfile==0.12.2',
|
'lockfile==0.12.2',
|
||||||
|
'inflection==0.3.1',
|
||||||
'future==0.15.2',
|
'future==0.15.2',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -64,6 +67,7 @@ tests_require = [
|
||||||
'moto==0.4.19',
|
'moto==0.4.19',
|
||||||
'nose==1.3.7',
|
'nose==1.3.7',
|
||||||
'pytest==2.8.5',
|
'pytest==2.8.5',
|
||||||
|
'factory-boy==2.7.0',
|
||||||
'pytest-flask==0.10.0'
|
'pytest-flask==0.10.0'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue